// load.js:
ADS.addEvent(window, 'load', function (event) { var fileList = ADS.$('fileList'); // 按照需要修改uoloadForm
addProgressBar('uploadForm', function (response) { var files = response.filesProcessed; for (var i in files) { // 跳过空文件
if (files[i] === null) { continue; } // 创建一个新的文件列表元素
var li = document.createElement('li'); var a = document.createElement('a'); a.setAttribute('href', 'uploads/' + files[i]); a.appendChild(document.createTextNode(files[i])); li.appendChild(a); fileList.appendChild(li); } // 更新文件计数器
var countContainer = ADS.$('fileCount'); ADS.removeChildren(countContainer); var numFiles = fileList.getElementsByTagName('li').length; countContainer.appendChild(document.createTextNode(numFiles)); }); }); // uploader.js: /** * User: lindongpeng * Date: 13-2-19 */
function verifyFileType(fileInput) { if (!fileInput.value || !fileInput.accept) { return true; } var extension = fileInput.value.split('.').pop().toLowerCase(), mimetypes = fileInput.accept.toLowerCase().split(','), type; for (var i = 0, len = mimetypes.length; i < len; i++) { type = mimetypes[i].split('/')[1]; if (type === extension || (type === 'jpeg' && extension === 'jpg')) { return true; } } return false; } var addProgressBar = function (form, modificationHander) { // 检查表单是否存在
if (!(form = ADS.$(form))) { return false; } // 查找所有文件输入元素
var allInputs = form.getElementsByTagName('input'); var input; var fileInputs = []; for (var i = 0; (input = allInputs[i]); i++) { if (input.getAttribute('type') === 'file') { fileInputs.push(input); } } // 如果没有文件输入元素则停止脚本
if (!fileInputs.length) { return false; } // 添加change事件以基于MIME类型验证扩展名
for (var j = 0, len = fileInputs[i]; i < len; j++) { // 使用change事件侦听器进行文件类型检查
ADS.addEvent(fileInputs[i], 'change', function (event) { var ok = verifyFileType(this); if (!ok) { if (!ADS.hasClassName(this, 'error')) { ADS.addClassName(this, 'error'); } alert('Sorru, that file type is not allowed.Please select one of:' + this.accept.toLowerCase()); } else { ADS.removeClassName(this, 'error'); } }); } // 为上传而附加iframe元素
// 在IE中,不能像下面这样通过DOM设置name属性,例如:
// var uploadTargetFrame = document.createElement('iframe');
// uploadTargetFrame.setAttribute('id', 'uploadTargetFrame');
// uploadTargetFrame.setAttribute('name', 'uploadTargetFrame');
// 为解决这个问题,需要创建一个div并使用其innerHTML属性
// 从而确保在IE和其他浏览器中都能正确的设置name属性
var uploadTargetFrame = document.createElement('div'); uploadTargetFrame.innerHTML = '<iframe name="uploadTargetFrame" id="uploadTargetFrame"></iframe>'; ADS.setStyleById(uploadTargetFrame, { 'width': 0, 'height': 0, 'border': 0, 'visibility': 'hidden', 'z-index': -1 }); document.body.appendChild(uploadTargetFrame); // 将表单的target属性修改为新iframe元素
// 这样可以避免页面重载
form.setAttribute('target', 'uploadTargetFrame'); // 创建一个唯一的ID以跟踪上传进度
var uniqueID = 'A' + Math.floor(Math.random() * 1000000000000000); // 为APC_UPLOAD_PROGRESS键添加这个唯一ID
// 这个字段必须添加到文件输入字段之前,以便
// 服务器首先取得改键并触发存储进度信息的操作
var uniqueIDField = document.createElement('input'); uniqueIDField.setAttribute('type', 'hidden'); uniqueIDField.setAttribute('value', uniqueID); uniqueIDField.setAttribute('name', 'APC_UPLOAD_PROGRESS'); form.insertBefore(uniqueIDField, form.firstChild); // 创建进度条的不同部分
// 进度条
var progressBar = document.createElement('div'); progressBar.className = 'progressBar'; // 内部的背景容器
var progressBackground = document.createElement('div'); progressBackground.className = 'progressBackground'; ADS.setStyle(progressBackground, { 'height': '10px' }); progressBackground.appendChild(progressBar); // 检查已有的定位点
// 必须是带有progressContainer类的span元素
var progressContainer = ADS.getElementsByClassName( 'progressContainer', 'div' )[0]; // 如果该定位点不存在则创建一个并将其添加到表单中
if (!progressContainer) { progressContainer = document.createElement('div'); progressContainer.className = 'progressContainer'; form.appendChild(progressContainer); } // 添加进度条的其余部分
progressContainer.appendChild(progressBackground); // 同时也添加一个进度信息显示区域
var progressMessage = document.createElement('div'); progressMessage.className = 'progressMessage'; progressContainer.appendChild(progressMessage); // 创建一个将由后面的进度监视方法使用
// 的私有方法,以方便更新进度条和相应信息
function updateProgressBar(percent, message, satus) { progressMessage.innerHTML = message; ADS.removeClassName(progressMessage, 'error complete waiting uploading'); ADS.addClassName(progressMessage, satus); // CSS样式和className将负责指示状态
ADS.setStyle(progressBar, { 'width': percent }); } // 从0%和waiting开始初始化进度条
updateProgressBar('0%', 'Waiting for upload', 'waiting'); // 为表单添加提交事件侦听器,用于
// 验证表单信息和更新进度条
ADS.addEvent(form, 'submit', function (event) { // 再次检查输入以确保
// 其包含正确的扩展名
var ok = true; var hasFiles = false; for (var i = 0, fileInput; (fileInput = fileInputs[i]); i++) { if (fileInput.value) { hasFiles = true; } if (!verifyFileType(fileInput)) { // 突出显示出错的文件输入元素
if (!ADS.hasClassName(fileInput, 'error')) { ADS.addClassName(fileInput, 'error'); } ok = false; } } if (!ok || !hasFiles) { // 如果检查为通过则提示用户解决问题
ADS.preventDefault(event); alert('Please select some valid files'); return false; } // 通过发出警告信息来禁用表单元素
function warning(event) { ADS.preventDefault(event); alert('There is an upload in progress. Please wait.'); } for (var i = 0, input; (input = allInputs[i]); i++) { // input.setAttribute('disabled', 'disabled');
ADS.addEvent(input, 'mousedown', warning); } // 创建一个函数以便在上传完成后重启表单
// 该函数将在ajax事件侦听器内部被调用
function clearWarnings() { // 从表单元素移除警告侦听器
for (var i = 0, input; (input = allInputs[i]); i++) { ADS.removeEvent(input, 'mousedown', warning); } // 以新ID数值更新原ID和表单
// 以确保下次上传不影响本次上传
uniqueID = Math.floor(Math.random() * 100000000000000); uniqueIDFiled.setAttribute('value', uniqueID); } // 更新进度条
updateProgressBar('0%', 'Waiting for upload', 'waiting'); // 为模拟脚本设置计数器
var counter = 0; // 创建一个新方法以触发一次新的进度请求
var progressWatcher = function () { // 使用唯一键来请求进度信息
ADS.ajaxRequest(form.action + (form.action.indexOf('?') === -1 ? '?' : '&') + 'key=' + uniqueID + '&sim=' + (++counter), { // 服务器端脚本将返回适当的头部信息
// 因此我们可以使用JSON侦听器
jsonResponseListener: function (response) { // 检测响应以确认服务器端
// 脚本中是否存在错误
if (!response) { // 没有有效的响应
updateProgressBar( '0%', 'Invalid response from progress watcher', 'error' ); // 请求完成故清除警告提示
clearWarnings(); } else if (response.error) { // 服务器端报告了错误
updateProgressBar('0%', response.error, 'error'); // 请求完成故清除警告提示
clearWarnings(); } else if (response.done === 1) { // POST请求已经完成
updateProgressBar('100%', 'Upload complete', 'complete'); // 请求完成故清除警告提示
clearWarnings(); // 为提供更改处理程序的
// 用户传递新信息
if (modificationHander.constructor === Function) { modificationHander(response); } } else { // 更新进度条并返回结果
// 由于结果是null, 所以
// 返回会简单地停止执行
// 方法中其余的代码
updateProgressBar( Math.round(response.current / response.total * 100) + '&', response.current + 'of'
+ response.total + '. Uploading file: '
+ response.currentFileName, 'uploading' ); // 再次执行进度监视程序
setTimeout(progressWatcher, 1000); } }, errorListener: function () { // ajax请求发生了错误
// 因此需要让用户知道
updateProgressBar('0%', this.status, 'error'); // 并清除警告提示
clearWarnings(); } }); }; // 开始监视
setTimeout(progressWatcher, 1000); }); }; // 主页
index.php: <?php // 循环遍历uploads文件夹 // 以便取得已经上传的文件
$uploads = new DirectoryIterator('./uploads'); $files=array(); foreach($uploads as $file) { // 跳过,并。。。
if(!$file->isDot() && $file->isFile()) { // 添加到数组,。稍后,该数组
// 将在HTML中被连接起来
$files[]=sprintf( '<li><a href="uploads/%s">%s</a> <em>%skb</em></li>', $file->getFilename(), $file->getFilename(), round($file->getSize()/1024)
); } } // 输出页面
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Image Uploader with Progress (php5-APC)</title>
<!-- Inclue some CSS style sheet to make everything look a little nicer -->
<link rel="stylesheet" href="http://www.cnblogs.com/../shared/source.css" />
<link rel="stylesheet" href="http://www.cnblogs.com/chapter.css" />
<link rel="stylesheet" href="style.css" />
<!-- Your ADS library with the common JavaScript objects -->
<script src="http://www.cnblogs.com/../ADS.js"></script>
<!-- Progress bar script -->
<script src="uploader.js"></script>
<!-- load script -->
<script src="load.js"></script>
</head>
<body>
<h1>Image Uploader with Progress (php5-APC)</h1>
<div id="content">
<form action="actions/" enctype="multipart/form-data" method="post" id="uploadForm">
<fieldset>
<legend>Upload a new image</legend>
<p>Only jpg/gif/png files less than 100kb allowed.</p>
<div class="fileSelector">
<label for="newFile1">File 1</label>
<input type="file" id="newFile1" name="newFile1" accept="image/jpeg,image/gif,image/png"/>
</div>
<div class="fileSelector">
<label for="newFile2">File 2</label>
<input type="file" id="newFile2" name="newFile2" accept="image/jpeg,image/gif,image/png"/>
</div>
<div class="fileSelector">
<label for="newFile3">File 3</label>
<input type="file" id="newFile3" name="newFile3" accept="image/jpeg,image/gif,image/png"/>
</div>
<input id="submitUpload" name="submitUpload" type="submit" value="Upload Files" />
</fieldset>
</form>
<div id="browserPane">
<h2>
<span id="fileCount">
<?php echo count($files); ?>
</span>
Existing Files in <em>uploads/</em>
</h2>
<ul id="fileList">
<?php echo join($files,"\n\t\t\t\t"); ?>
</ul>
</div>
</div>
<div id="where-from"> From <a href="http://advanceddomscripting.com" title="AdvancED DOM Scripting">AdvancED DOM Scripting</a>
| <a href="http://www.amazon.com/exec/obidos/ASIN/1590598563/jeffreysamb05-20" title="Buy it on Amazon">Paperback</a>
</div>
</body>
</html>
// 表单action指向的php文件
action/index.php:
<?php //check if the request is using the ADS.ajaxRequest() method.
if($_SERVER['HTTP_X_ADS_AJAX_REQUEST']) { // Return the progress information as a JSON string
// Send some headers to prevent caching of the progress request
header('Expires: Fri, 13 Dec 1901 20:45:54 GMT') ; header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT') ; header('Cache-Control: no-store, no-cache, must-revalidate') ; header('Cache-Control: post-check=0, pre-check=0', false) ; header('Pragma: no-cache') ; // This will be a JavaScript JSON response
header('Content-Type:application/json; charset=utf-8' ) ; // Retrieve the status using the getStatus() function below
echo json_encode(getStatusAPC($_GET['key'])); die(); } // Process any files in the PHP $_FILES[] array and return to the // main script if everything went well. Otherwise we'll display a // error page.
$allowedExtensions = array('jpg','jpeg','gif','png'); $errorMessage = null ; $storedFiles = array(); if(count($_FILES) > 0) { try { // Process each file
foreach($_FILES as $key=>$info) { if($_FILES[$key]['name']) { // storeFile() throws exceptions
$file = storeFile($key,'../uploads/',$allowedExtensions); } else { $file = null; } // Keep track of stored files incase you need to
// remove them.
$storedFiles[$key] = $file['basename']; } if($_POST['APC_UPLOAD_PROGRESS'] && function_exists('apc_store')) { // Store the file information so it can be
// retrieved in the progress watcher
apc_store('upload_finished_'.$_POST['APC_UPLOAD_PROGRESS'],$storedFiles); // Die. This message will display in the iframe
die('Upload complete.'); } // Everything was successful so redirect back
// to the main index page
header('Location: ../'); die(); } catch (Exception $e) { // There was an error so remove any files that were uploaded
foreach($storedFiles as $file) { if(is_file($file)) unlink('uploads/'.$file); } $storedFiles = array(); if($_POST['APC_UPLOAD_PROGRESS'] && function_exists('apc_store')) { // Store the error message so it can be
// retrieved in the progress watcher
apc_store( 'upload_error_'.$_POST['APC_UPLOAD_PROGRESS'], $e->getMessage() ); // Die. This message will display in the iframe
die('There was an error'); } else { // Get the error message
$errorMessage = sprintf( '<p>%s failed: %s</p>', $key, $e->getMessage() ); // Display a simple error page with a
// link back to the main index file
echo <<<XHTML <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Oops!</title>
</head>
<body>
<h1>Error</h1>
<p>The system reported an error with the file(s) you were trying to upload:</p>
{$errorMessage} <p><a href="../">Return to the upload page</a></p>
<div id="where-from"> From <a href="http://advanceddomscripting.com" title="AdvancED DOM Scripting">AdvancED DOM Scripting</a>
| <a href="http://www.amazon.com/exec/obidos/ASIN/1590598563/jeffreysamb05-20" title="Buy it on Amazon">Paperback</a>
</div>
</body>
</html>
XHTML; } } } /** * Store a file uploaded through HTTP on the server * * This function will access the global $_FILES array to retrieve the * information: * * The original name of the file on the client machine. * $_FILES['userfile']['name'] * * The mime type of the file, if the browser provided this information. * An example would be 'image/gif'. This mime type is however not checked * on the PHP side and therefore don't take its value for granted. * $_FILES['userfile']['type'] * * The size, in bytes, of the uploaded file. * $_FILES['userfile']['size'] * * The temporary filename of the file in which the uploaded file was stored on the server. * $_FILES['userfile']['tmp_name'] * * The error code associated with this file upload. * This element was added in PHP 4.2.0 * $_FILES['userfile']['error'] * * @param string $key The key in $_FILES that represents the file you wish to * store. This is generally the name attribute from the form. * @param string $where The directory on the server where you wish to store * the file. This can be absolute or relative to the location of execution. * @param array $extensions An array of acceptable extensions. (white list) * @param int $maxBytes The maximum number of bytes * @return array */
function storeFile($key,$where,$extensions,$maxBytes=null) { try { // Check for the file
if(!$_FILES[$key]) { throw new Exception('The specified key does not exist in the $_FILES array'); } // Check the uplod location
if(!$where) { throw new Exception('Upload location not specified. If the current directory is desired, use "."'); } elseif ($where[strlen($where)-1] != DIRECTORY_SEPARATOR) { $where .= DIRECTORY_SEPARATOR; } // Check for permissions
if(!is_writeable($where)) { throw new Exception('This page can not access the specified upload directory.'); } // convert the extensions to an array
// (if a single extension as a string was supplied)
settype($extensions,'array'); //check for extensions
if(count($extensions) == 0) { throw new Exception('No valid extensions were specified.'); } // Convert ini to bytes and store in maxBytes if required
$maxBytes = ($maxBytes ? $maxBytes : preg_replace_callback( '/([0-9]+)([gmk])/i', 'toBytes', ini_get('upload_max_filesize') )); // check PHP upload errors
switch ($_FILES[$key]['error']) { case UPLOAD_ERR_OK: // everything was fine. Proceed
break; case UPLOAD_ERR_INI_SIZE: throw new Exception('The uploaded file exceeds the upload_max_filesize directive ('.ini_get('upload_max_filesize').') in php.ini.'); break; case UPLOAD_ERR_FORM_SIZE: throw new Exception('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'); break; case UPLOAD_ERR_PARTIAL: throw new Exception('The uploaded file was only partially uploaded.'); break; case UPLOAD_ERR_NO_FILE: throw new Exception('No file was uploaded.'); break; case UPLOAD_ERR_NO_TMP_DIR: throw new Exception('Missing a temporary folder.'); break; case UPLOAD_ERR_CANT_WRITE: throw new Exception('Failed to write file to disk'); break; default: throw new Exception('Unknown PHP File Error'); } // Check if the files size is greater than the
// file size in the arguments
if($_FILES['userfile']['size'] > $maxBytes) { throw new Exception('The uploaded file exceeds the maximum size specified in the application.'); } // Sanitize the file name
$cleanName = str_replace(' ','-',$_FILES[$key]['name']); $cleanName = preg_replace('/-+/','-',$cleanName); $cleanName = preg_replace('/[^a-z0-9_.\/-]/i','',$cleanName); $fileNameParts = pathinfo($cleanName); // Verify the sanitized name is good
$fileNameParts['filename'] = str_replace('.','_',$fileNameParts['filename']); if(!$fileNameParts['filename']) { throw new Exception('The desired file name contains no valid characters.'); } // Verify the extension is valid
$fileNameParts['extension'] = strtolower($fileNameParts['extension']); if(!in_array($fileNameParts['extension'], $extensions)) { throw new Exception('The file extension is not one of: '.join($extensions,', ')); } // Postfix the file with a counter to avoid duplicates
$count = 0; $postfix = ''; while(file_exists($uploadLocation = $where.$fileNameParts['filename'].$postfix.'.'.$fileNameParts['extension'])) { $postfix = '-'.++$count; } // Move the upload into place
if(!move_uploaded_file($_FILES[$key]['tmp_name'], $uploadLocation)) { throw new Exception('Failed to move uploaded tmp file.'); } } catch (Exception $e) { // Catch exceptions for garbage collection and error storage
// Remove the temp file
if($_FILES[$key] && is_uploaded_file($_FILES[$key]['tmp_name'])) { @unlink($_FILES[$key]['tmp_name']); } // Throw the exception again for developers to catch
throw $e; } // Return the information about the new file using pathinfo
$return = pathinfo($uploadLocation); $return['rawname'] = basename($_FILES[$key]['name']); return $return; } function toBytes($matches) { switch(strtolower($matches[2])) { case "k": return $matches[1] * 1024; break; case "m": return $matches[1] * 1048576; break; case "g": return $matches[1] * 1073741824; break; } } /** * PHP 5.2 has a new set of hooks for checking the progress of a file upload * with APC 3.5 * * http://viewcvs.php.net/viewvc.cgi/pecl/apc/INSTALL?revision=3.53&view=markup * * apc.rfc1867 * RFC1867 File Upload Progress hook handler is only available * if you compiled APC against PHP 5.2.0 or later. When enabled * any file uploads which includes a field called * APC_UPLOAD_PROGRESS before the file field in an upload form * will cause APC to automatically create an upload_ * user cache entry where is the value of the * APC_UPLOAD_PROGRESS form entry. * (Default: 0) * */
function getStatusAPC($key) { $response = false; // will return false if not found
if($status = apc_fetch('upload_'.$_GET['key'])) { /* status { "total":2676099, "current":102685, "filename":"test_large.jpg", "name":"test_file", "done":0 } */ $response = array( 'total' => $status['total'], 'current' => $status['current'], 'currentFileName' => $status['filename'], 'currentFieldName' => $status['name'], 'filesProcessed' => null, 'error' => null, 'done' => $status['done'], 'debug'=>null ); if($message = apc_fetch('upload_error_'.$_GET['key'])) { $response['error'] = $message; $response['debug'] = 'There was an error'; } else if ($status['done']==1 && ($filesProcessed = apc_fetch('upload_finished_'.$_GET['key']))) { //wait until the last file processed matches the one in status
$response['debug'] = 'Files were processed '; if(($last = array_pop(array_keys(((array)$filesProcessed)))) == $status['name']) { $response['filesProcessed'] = $filesProcessed; $response['debug'] .= ' - all'; } else { // Override the done state because the upload
// has finished but the server is still processing
// the files
$response['done']= 0; $response['debug'] .= " - \"$last\" != \"{$status['name']}\""; } } } return $response; } ?>