Upload - Add input file with progress with plugin bigupload - refs BT#19380

pull/4174/head
Christian 4 years ago
parent e3b3bdb92f
commit 28fee3c731
  1. 7
      main/document/upload.php
  2. 6
      main/inc/lib/fileUpload.lib.php
  3. 76
      main/inc/lib/formvalidator/Element/BigUpload.php
  4. 53
      main/inc/lib/javascript/bigupload/README.md
  5. BIN
      main/inc/lib/javascript/bigupload/css/import_scorm.png
  6. BIN
      main/inc/lib/javascript/bigupload/css/load745.gif
  7. 78
      main/inc/lib/javascript/bigupload/css/style.css
  8. 25
      main/inc/lib/javascript/bigupload/css/upload.css
  9. 34
      main/inc/lib/javascript/bigupload/example.html
  10. 302
      main/inc/lib/javascript/bigupload/inc/bigUpload.php
  11. 336
      main/inc/lib/javascript/bigupload/js/bigUpload.js
  12. 350
      main/inc/lib/javascript/bigupload/js/big_upload.js
  13. 55
      main/inc/lib/javascript/bigupload/js/init.js
  14. 4
      main/inc/lib/javascript/bigupload/js/jquery1.8.2.min.js
  15. 30
      main/inc/lib/javascript/bigupload/js/ui-sco.js
  16. 17
      main/lp/lp_upload.php
  17. 3
      main/upload/form.scorm.php
  18. 17
      main/work/work.lib.php

@ -85,8 +85,8 @@ $group_properties = [];
$htmlHeadXtra[] = '<script>
function check_unzip() {
if (document.upload.unzip.checked) {
document.upload.if_exists[1].checked=true;
if (document.upload.unzip.checked) {
document.upload.if_exists[1].checked=true;
} else {
document.upload.if_exists[2].checked=true;
}
@ -260,7 +260,8 @@ $label =
get_lang('MaxFileSize').': '.ini_get('upload_max_filesize').'<br/>'.
get_lang('DocumentQuota').': '.$courseQuota;
$form->addElement('file', 'file', [get_lang('File'), $label], 'style="width: 250px" id="user_upload"');
$form->addElement('BigUpload', 'file', [get_lang('File'), $label], ['id' => 'bigUploadFile', 'data-origin' => 'document']);
//$form->addElement('file', 'file', [get_lang('File'), $label], ['id' => 'user_upload']);
$form->addElement('text', 'title', get_lang('Title'), ['id' => 'title_file']);
$form->addElement('textarea', 'comment', get_lang('Comment'));

@ -749,6 +749,7 @@ function moveUploadedFile($file, $storePath)
{
$handleFromFile = isset($file['from_file']) && $file['from_file'] ? true : false;
$moveFile = isset($file['move_file']) && $file['move_file'] ? true : false;
$copyFile = isset($file['copy_file']) && $file['copy_file'] ? true : false;
if ($moveFile) {
$copied = copy($file['tmp_name'], $storePath);
@ -756,6 +757,11 @@ function moveUploadedFile($file, $storePath)
return false;
}
}
if ($copyFile) {
return copy($file['tmp_name'], $storePath);
}
if ($handleFromFile) {
return file_exists($file['tmp_name']);
} else {

@ -0,0 +1,76 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Input file with progress element.
*
* Class BigUpload
*/
class BigUpload extends HTML_QuickForm_file
{
/**
* @param string $elementName
* @param string $elementLabel
* @param array $attributes
*/
public function __construct($elementName = null, $elementLabel = null, $attributes = null)
{
parent::__construct($elementName, $elementLabel, $attributes);
}
/**
* @return string
*/
public function toHtml()
{
$origin = $this->getAttribute('data-origin');
$id = $this->getAttribute('id');
$html = parent::toHtml();
$html .= '<div id="'.$id.'-bigUploadProgressBarContainer">
<div id="'.$id.'-bigUploadProgressBarFilled"></div>
</div>
<div id="'.$id.'-bigUploadTimeRemaining"></div>
<div id="'.$id.'-bigUploadResponse"></div>';
$js = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'bigupload/js/bigUpload.js"></script>';
$js .= '<script>
var bigUpload = new bigUpload();
var uploadForm, formId, submitButtonId;
$(function() {
uploadForm = $("#'.$id.'").closest("form");
formId = uploadForm.attr("id");
submitButtonId = uploadForm.find("[type=\'submit\']").attr("id");
$("#"+submitButtonId).click(function(e) {
e.preventDefault();
setBigUploadSettings();
bigUpload.fire();
});
});
function setBigUploadSettings() {
//The id of the file input
bigUpload.settings.inputField = "'.$id.'";
//The id of the form with the file upload.
bigUpload.settings.formId = formId;
//The id of the progress bar
bigUpload.settings.progressBarField = "'.$id.'-bigUploadProgressBarFilled";
//The id of the time remaining field
bigUpload.settings.timeRemainingField = "'.$id.'-bigUploadTimeRemaining";
//The id of the text response field
bigUpload.settings.responseField = "'.$id.'-bigUploadResponse";
//The id of the submit button
bigUpload.settings.submitButton = submitButtonId;
//Color of the background of the progress bar
bigUpload.settings.progressBarColor = "#5bb75b";
//Color of the background of the progress bar when an error is triggered
bigUpload.settings.progressBarColorError = "#da4f49";
//Path to the php script for handling the uploads
bigUpload.settings.scriptPath = "'.api_get_path(WEB_LIBRARY_JS_PATH).'bigupload/inc/bigUpload.php";
//Set the origin upload
bigUpload.settings.origin = "'.$origin.'";
//The parameters from the upload form
bigUpload.settings.formParams = uploadForm.serialize();
}
</script>';
return $js.$html;
}
}

@ -0,0 +1,53 @@
-------------------------------------------------------------------------
BigUpload
version 1.2
Created by: Sean Thielen <sean@p27.us>
[BigUpload: Uploading really big files in the browser](http://p27.us/2013/03/bigupload-uploading-really-big-files-in-the-browser/)
-------------------------------------------------------------------------
BigUpload is a tool for handling large file uploads (tested up to 2GB) through the browser.
![Screenshot](http://i.imgur.com/vESk5dp.png)
-------------------------------------------------------------------------
It uses the HTML5 FileReader library to split large files into manageable chunks,
and then sends these chunks to the server one at a time using an XmlHttpRequest.
The php script then pieces these chunks together into one large file.
Because the chunks are all the same size, it is easy to calculate an accurate progress bar
and a fairly accurate time remaining variable.
This tool is capable of handling file uploads of up to 2GB in size, without the need to tweak
the max_upload and timeout variables on your httpd.
This tool only works on Chrome and Firefox, but falls back to a normal file upload form on other browsers.
If you want to deploy this as-is, the variables you need to worry about are in the top of
* js/bigUpload.js
* inc/bigUpload.php
And you need to be sure to make /BigUpload/files and /BigUpload/files/tmp writeable
Please feel free to contribute and use this in your projects!
-------------------------------------------------------------------------
v 1.2
* Cleaned up the code quite a lot
* Added pause/resume functionality
* Added fallback for unsupported browsers
v 1.0.1
* Added time remaining calculator
* Response from php script is now a json object, allowing for error processing
* Minor script changes and bugfixes
* Better comments
v 1.0.0
* Initial version

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,78 @@
body {
margin: 0 auto;
background-color:#ffffff;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #000000;
font-size: 12px;
}
h1 {
font-size:32px;
margin-top:0;
}
.bigUpload .bigUploadContainer {
max-width: 350px;
padding: 20px 30px 30px;
margin: 0 auto 20px;
margin-top:75px;
background-color: #ffffff;
border: 1px solid #e5e5e5;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
box-shadow: 0 1px 2px rgba(0,0,0,.05);
text-align: center;
}
.bigUpload #bigUploadFile {
border: 1px solid #e5e5e5;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
box-shadow: 0 1px 2px rgba(0,0,0,.05);
padding: 10px;
}
.bigUpload .bigUploadButton {
background-color: rgb(0, 109, 204);
border: 1px solid rgba(0, 0, 0, 0.09);
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius:6px;
padding-bottom: 11px;
padding-left: 19px;
padding-right: 19px;
padding-top: 11px;
font-size: 18px;
color:#ffffff;
cursor: pointer;
margin-top:15px;
font-size: 18px;
}
.bigUpload .bigUploadAbort {
background-color:rgb(218, 79, 73);
}
.bigUpload #bigUploadProgressBarContainer {
width:94%;
height:19px;
margin-left: 3%;
margin-top:1%;
border: 1px solid #e5e5e5;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.bigUpload #bigUploadProgressBarFilled {
border-radius: 5px;
margin: 1px;
height:17px;
width:0;
background-color: rgb(91, 183, 91);
font-size: 14px;
}

@ -0,0 +1,25 @@
.bigUploadContainer{
width : 450px;
border : solid 1px gray;
border-radius: 14px;
padding : 20px;
}
.bigUploadContainer input[type="file"] {
appearance: auto;
user-select: none;
white-space: pre;
align-items: flex-start;
text-align: center;
cursor: default;
padding: 1px 6px;
margin : 8px;
padding : 8px;
padding-left : 2px;
margin-left : 2px;
}
.bigUploadButton{
margin-left : 16px;
}

@ -0,0 +1,34 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="js/bigUpload.js"></script>
<script>
bigUpload = new bigUpload();
function upload() {
bigUpload.fire();
}
function abort() {
bigUpload.abortFileUpload();
}
</script>
</head>
<body>
<div class="bigUpload">
<div class="bigUploadContainer">
<h1>BigUpload</h1>
<form action="inc/bigUpload.php?action=post-unsupported" method="post" enctype="multipart/form-data" id="bigUploadForm">
<input type="file" id="bigUploadFile" name="bigUploadFile" />
<input type="button" class="bigUploadButton" value="Start Upload" id="bigUploadSubmit" onclick="upload()" />
<input type="button" class="bigUploadButton bigUploadAbort" value="Cancel" onclick="abort()" />
</form>
<div id="bigUploadProgressBarContainer">
<div id="bigUploadProgressBarFilled">
</div>
</div>
<div id="bigUploadTimeRemaining"></div>
<div id="bigUploadResponse"></div>
</div>
</div>
</body>
</html>

@ -0,0 +1,302 @@
<?php
require_once '../../../../global.inc.php';
require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
class BigUpload
{
/**
* Temporary directory for uploading files
*/
const TEMP_DIRECTORY = '/tmp/';
/**
* Directory files will be moved to after the upload is completed
*/
const MAIN_DIRECTORY = '../files/';
/**
* Max allowed filesize. This is for unsupported browsers and
* as an additional security check in case someone bypasses the js filesize check.
*
* This must match the value specified in main.js
*/
const MAX_SIZE = 2147483648;
/**
* Temporary directory
* @var string
*/
private $tempDirectory;
/**
* Directory for completed uploads
* @var string
*/
private $mainDirectory;
/**
* Name of the temporary file. Used as a reference to make sure chunks get written to the right file.
* @var string
*/
private $tempName;
/**
* Constructor function, sets the temporary directory and main directory
*/
public function __construct()
{
$tempDirectory = api_get_path(SYS_ARCHIVE_PATH);
$this->setTempDirectory($tempDirectory);
$this->setMainDirectory(self::MAIN_DIRECTORY);
}
/**
* Create a random file name for the file to use as it's being uploaded
* @param string $value Temporary filename
*/
public function setTempName($value = null)
{
if($value) {
$this->tempName = $value;
}
else {
$this->tempName = mt_rand() . '.tmp';
}
}
/**
* Return the name of the temporary file
* @return string Temporary filename
*/
public function getTempName()
{
return $this->tempName;
}
/**
* Set the name of the temporary directory
* @param string $value Temporary directory
*/
public function setTempDirectory($value)
{
$this->tempDirectory = $value;
return true;
}
/**
* Return the name of the temporary directory
* @return string Temporary directory
*/
public function getTempDirectory()
{
return $this->tempDirectory;
}
/**
* Set the name of the main directory
* @param string $value Main directory
*/
public function setMainDirectory($value)
{
$this->mainDirectory = $value;
}
/**
* Return the name of the main directory
* @return string Main directory
*/
public function getMainDirectory()
{
return $this->mainDirectory;
}
/**
* Function to upload the individual file chunks
* @return string JSON object with result of upload
*/
public function uploadFile()
{
//Make sure the total file we're writing to hasn't surpassed the file size limit
if(file_exists($this->getTempDirectory() . $this->getTempName())) {
if(filesize($this->getTempDirectory() . $this->getTempName()) > self::MAX_SIZE) {
$this->abortUpload();
return json_encode(array(
'errorStatus' => 1,
'errorText' => 'File is too large.'
));
}
}
//Open the raw POST data from php://input
$fileData = file_get_contents('php://input');
//Write the actual chunk to the larger file
$handle = fopen($this->getTempDirectory() . $this->getTempName(), 'a');
fwrite($handle, $fileData);
fclose($handle);
return json_encode(array(
'key' => $this->getTempName(),
'errorStatus' => 0
));
}
/**
* Function for cancelling uploads while they're in-progress; deletes the temp file
* @return string JSON object with result of deletion
*/
public function abortUpload()
{
if(unlink($this->getTempDirectory() . $this->getTempName())) {
return json_encode(array('errorStatus' => 0));
}
else {
return json_encode(array(
'errorStatus' => 1,
'errorText' => 'Unable to delete temporary file.'
));
}
}
/**
* Function to rename and move the finished file
* @param string $final_name Name to rename the finished upload to
* @return string JSON object with result of rename
*/
public function finishUpload($finalName)
{
$origin = $_POST['origin'];
if ($origin == 'document') {
$tmpFile = $this->getTempDirectory() . $this->getTempName();
chmod($tmpFile, '0777');
$file = [
'name' => $finalName,
'type' => $_POST['type'],
'tmp_name' => $tmpFile,
'error' => 0,
'size' => $_POST['size'],
'copy_file' => true,
];
$files = ['file' => $file];
$unzip = isset($_POST['unzip']) ? $_POST['unzip'] : null;
$index = isset($_POST['index_document']) ? $_POST['index_document'] : null;
DocumentManager::upload_document(
$files,
$_POST['curdirpath'],
$_POST['title'],
$_POST['comment'],
$unzip,
$_POST['if_exists'],
$index,
true
);
$redirectUrl = api_get_path(WEB_CODE_PATH).'document/document.php?'.api_get_cidreq();
if (!empty($_POST['id'])) {
$redirectUrl .= '&'.http_build_query(
[
'id' => $_POST['id'],
]
);
}
return json_encode(array('errorStatus' => 0, 'redirect' => $redirectUrl));
} else if ($origin == 'learnpath') {
unset($_REQUEST['origin']);
$redirectUrl = api_get_path(WEB_CODE_PATH).'upload/upload.php?'.api_get_cidreq().'&from=bigUpload&name='.$this->getTempName();
return json_encode(array('errorStatus' => 0, 'redirect' => $redirectUrl));
} else if ($origin == 'work') {
$tmpFile = $this->getTempDirectory() . $this->getTempName();
chmod($tmpFile, '0777');
$workInfo = get_work_data_by_id($_REQUEST['id']);
$values = $_REQUEST;
$course_info = api_get_course_info();
$session_id = api_get_session_id();
$group_id = api_get_group_id();
$user_id = api_get_user_id();
$values['contains_file'] = 1;
$file = [
'name' => $finalName,
'type' => $_POST['type'],
'tmp_name' => $tmpFile,
'error' => 0,
'size' => $_POST['size'],
'copy_file' => true,
];
// Process work
$result = processWorkForm(
$workInfo,
$values,
$course_info,
$session_id,
$group_id,
$user_id,
$file,
api_get_configuration_value('assignment_prevent_duplicate_upload')
);
$redirectUrl = api_get_path(WEB_CODE_PATH).'work/work.php?'.api_get_cidreq();
return json_encode(array('errorStatus' => 0, 'redirect' => $redirectUrl));
}
return json_encode(array('errorStatus' => 0));
}
/**
* Basic php file upload function, used for unsupported browsers.
* The output on success/failure is very basic, and it would be best to have these errors return the user to index.html
* with the errors printed on the form, but that is beyond the scope of this project as it is very application specific.
* @return string Success or failure of upload
*/
public function postUnsupported()
{
$name = $_FILES['bigUploadFile']['name'];
$size = $_FILES['bigUploadFile']['size'];
$tempName = $_FILES['bigUploadFile']['tmp_name'];
if(filesize($tempName) > self::MAX_SIZE) {
return 'File is too large.';
}
if(move_uploaded_file($tempName, $this->getMainDirectory() . $name)) {
return 'File uploaded.';
}
else {
return 'There was an error uploading the file';
}
}
}
//Instantiate the class
$bigUpload = new BigUpload;
//Set the temporary filename
$tempName = null;
if(isset($_GET['key'])) {
$tempName = $_GET['key'];
}
if(isset($_POST['key'])) {
$tempName = $_POST['key'];
}
$bigUpload->setTempName($tempName);
switch($_GET['action']) {
case 'upload':
print $bigUpload->uploadFile();
break;
case 'abort':
print $bigUpload->abortUpload();
break;
case 'finish':
print $bigUpload->finishUpload($_POST['name']);
break;
case 'post-unsupported':
print $bigUpload->postUnsupported();
break;
}
?>

@ -0,0 +1,336 @@
function bigUpload () {
//These are the main config variables and should be able to take care of most of the customization
this.settings = {
//The id of the file input
'inputField': 'bigUploadFile',
//The id of the form with the file upload.
//This should be a valid html form (see index.html) so there is a fallback for unsupported browsers
'formId': 'bigUploadForm',
//The id of the progress bar
//Width of this element will change based on progress
//Content of this element will display a percentage
//See bigUpload.progressUpdate() to change this code
'progressBarField': 'bigUploadProgressBarFilled',
//The id of the time remaining field
//Content of this element will display the estimated time remaining for the upload
//See bigUpload.progressUpdate() to change this code
'timeRemainingField': 'bigUploadTimeRemaining',
//The id of the text response field
//Content of this element will display the response from the server on success or error
'responseField': 'bigUploadResponse',
//The id of the submit button
//This is then changed to become the pause/resume button based on the status of the upload
'submitButton': 'bigUploadSubmit',
//Color of the background of the progress bar
//This must also be defined in the progressBarField css, but it's used here to reset the color after an error
//Default: green
'progressBarColor': '#5bb75b',
//Color of the background of the progress bar when an error is triggered
//Default: red
'progressBarColorError': '#da4f49',
//Path to the php script for handling the uploads
'scriptPath': 'inc/bigUpload.php',
//Size of chunks to upload (in bytes)
//Default: 1MB
'chunkSize': 1000000,
//Max file size allowed
//Default: 2GB
'maxFileSize': 2147483648
};
//Upload specific variables
this.uploadData = {
'uploadStarted': false,
'file': false,
'numberOfChunks': 0,
'aborted': false,
'paused': false,
'pauseChunk': 0,
'key': 0,
'timeStart': 0,
'totalTime': 0
};
parent = this;
//Quick function for accessing objects
this.$ = function(id) {
return document.getElementById(id);
};
//Resets all the upload specific data before a new upload
this.resetKey = function() {
this.uploadData = {
'uploadStarted': false,
'file': false,
'numberOfChunks': 0,
'aborted': false,
'paused': false,
'pauseChunk': 0,
'key': 0,
'timeStart': 0,
'totalTime': 0
};
};
//Inital method called
//Determines whether to begin/pause/resume an upload based on whether or not one is already in progress
this.fire = function() {
if(this.uploadData.uploadStarted === true && this.uploadData.paused === false) {
this.pauseUpload();
}
else if(this.uploadData.uploadStarted === true && this.uploadData.paused === true) {
this.resumeUpload();
}
else {
this.processFiles();
}
};
//Initial upload method
//Pulls the size of the file being uploaded and calculated the number of chunks, then calls the recursive upload method
this.processFiles = function() {
//If the user is using an unsupported browser, the form just submits as a regular form
if(!Blob.prototype.slice) {
this.$(this.settings.formId).submit();
return;
}
//Reset the upload-specific variables
this.resetKey();
this.uploadData.uploadStarted = true;
//Some HTML tidying
//Reset the background color of the progress bar in case it was changed by any earlier errors
//Change the Upload button to a Pause button
this.$(this.settings.progressBarField).style.backgroundColor = this.settings.progressBarColor;
this.$(this.settings.responseField).textContent = '';
this.$(this.settings.submitButton).value = 'Pause';
//Alias the file input object to this.uploadData
this.uploadData.file = this.$(this.settings.inputField).files[0];
//Check the filesize. Obviously this is not very secure, so it has another check in inc/bigUpload.php
//But this should be good enough to catch any immediate errors
var fileSize = this.uploadData.file.size;
if(fileSize > this.settings.maxFileSize) {
this.printResponse('The file you have chosen is too large.', true);
return;
}
//Calculate the total number of file chunks
this.uploadData.numberOfChunks = Math.ceil(fileSize / this.settings.chunkSize);
//Start the upload
this.sendFile(0);
};
//Main upload method
this.sendFile = function (chunk) {
//Set the time for the beginning of the upload, used for calculating time remaining
this.uploadData.timeStart = new Date().getTime();
//Check if the upload has been cancelled by the user
if(this.uploadData.aborted === true) {
return;
}
//Check if the upload has been paused by the user
if(this.uploadData.paused === true) {
this.uploadData.pauseChunk = chunk;
this.printResponse('Upload paused.', false);
return;
}
//Set the byte to start uploading from and the byte to end uploading at
var start = chunk * this.settings.chunkSize;
var stop = start + this.settings.chunkSize;
//Initialize a new FileReader object
var reader = new FileReader();
reader.onloadend = function(evt) {
//Build the AJAX request
//
//this.uploadData.key is the temporary filename
//If the server sees it as 0 it will generate a new filename and pass it back in the JSON object
//this.uploadData.key is then populated with the filename to use for subsequent requests
//When this method sends a valid filename (i.e. key != 0), the server will just append the data being sent to that file.
xhr = new XMLHttpRequest();
xhr.open("POST", parent.settings.scriptPath + '?action=upload&origin=' + parent.settings.origin+'&key=' + parent.uploadData.key, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var response = JSON.parse(xhr.response);
//If there's an error, call the error method and break the loop
if(response.errorStatus !== 0 || xhr.status != 200) {
parent.printResponse(response.errorText, true);
return;
}
//If it's the first chunk, set this.uploadData.key to the server response (see above)
if(chunk === 0 || parent.uploadData.key === 0) {
parent.uploadData.key = response.key;
}
//If the file isn't done uploading, update the progress bar and run this.sendFile again for the next chunk
if(chunk < parent.uploadData.numberOfChunks) {
parent.progressUpdate(chunk + 1);
parent.sendFile(chunk + 1);
}
//If the file is complete uploaded, instantiate the finalizing method
else {
parent.sendFileData();
}
}
};
//Send the file chunk
xhr.send(blob);
};
//Slice the file into the desired chunk
//This is the core of the script. Everything else is just fluff.
var blob = this.uploadData.file.slice(start, stop);
reader.readAsBinaryString(blob);
};
//This method is for whatever housekeeping work needs to be completed after the file is finished uploading.
//As it's setup now, it passes along the original filename to the server and the server renames the file and removes it form the temp directory.
//This function could also pass things like this.uploadData.file.type for the mime-type (although it would be more accurate to use php for that)
//Or it could pass along user information or something like that, depending on the context of the application.
this.sendFileData = function() {
var data = 'key=' + this.uploadData.key + '&name=' + this.uploadData.file.name + '&type=' + this.uploadData.file.type + '&size=' + this.uploadData.file.size + '&origin=' + parent.settings.origin + '&' + parent.settings.formParams;
xhr = new XMLHttpRequest();
xhr.open("POST", parent.settings.scriptPath + '?action=finish', true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var response = JSON.parse(xhr.response);
//If there's an error, call the error method
if(response.errorStatus !== 0 || xhr.status != 200) {
parent.printResponse(response.errorText, true);
return;
}
//Reset the upload-specific data so we can process another upload
parent.resetKey();
//Change the submit button text so it's ready for another upload and spit out a sucess message
parent.$(parent.settings.submitButton).value = 'Start Upload';
parent.printResponse('File uploaded successfully.', false);
if (response.redirect) {
location.href = response.redirect;
}
}
};
//Send the reques
xhr.send(data);
};
//This method cancels the upload of a file.
//It sets this.uploadData.aborted to true, which stops the recursive upload script.
//The server then removes the incomplete file from the temp directory, and the html displays an error message.
this.abortFileUpload = function() {
this.uploadData.aborted = true;
var data = 'key=' + this.uploadData.key;
xhr = new XMLHttpRequest();
xhr.open("POST", this.settings.scriptPath + '?action=abort', true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var response = JSON.parse(xhr.response);
//If there's an error, call the error method.
if(response.errorStatus !== 0 || xhr.status != 200) {
parent.printResponse(response.errorText, true);
return;
}
parent.printResponse('File upload was cancelled.', true);
}
};
//Send the request
xhr.send(data);
};
//Pause the upload
//Sets this.uploadData.paused to true, which breaks the upload loop.
//The current chunk is still stored in this.uploadData.pauseChunk, so the upload can later be resumed.
//In a production environment, you might want to have a cron job to clean up files that have been paused and never resumed,
//because this method won't delete the file from the temp directory if the user pauses and then leaves the page.
this.pauseUpload = function() {
this.uploadData.paused = true;
this.printResponse('', false);
this.$(this.settings.submitButton).value = 'Resume';
};
//Resume the upload
//Undoes the doings of this.pauseUpload and then re-enters the loop at the last chunk uploaded
this.resumeUpload = function() {
this.uploadData.paused = false;
this.$(this.settings.submitButton).value = 'Pause';
this.sendFile(this.uploadData.pauseChunk);
};
//This method updates a simple progress bar by calculating the percentage of chunks uploaded.
//Also includes a method to calculate the time remaining by taking the average time to upload individual chunks
//and multiplying it by the number of chunks remaining.
this.progressUpdate = function(progress) {
var percent = Math.ceil((progress / this.uploadData.numberOfChunks) * 100);
this.$(this.settings.progressBarField).style.width = percent + '%';
this.$(this.settings.progressBarField).textContent = percent + '%';
//Calculate the estimated time remaining
//Only run this every five chunks, otherwise the time remaining jumps all over the place (see: http://xkcd.com/612/)
if(progress % 5 === 0) {
//Calculate the total time for all of the chunks uploaded so far
this.uploadData.totalTime += (new Date().getTime() - this.uploadData.timeStart);
console.log(this.uploadData.totalTime);
//Estimate the time remaining by finding the average time per chunk upload and
//multiplying it by the number of chunks remaining, then convert into seconds
var timeLeft = Math.ceil((this.uploadData.totalTime / progress) * (this.uploadData.numberOfChunks - progress) / 100);
//Update this.settings.timeRemainingField with the estimated time remaining
this.$(this.settings.timeRemainingField).textContent = timeLeft + ' seconds remaining';
}
};
//Simple response/error handler
this.printResponse = function(responseText, error) {
this.$(this.settings.responseField).textContent = responseText;
this.$(this.settings.timeRemainingField).textContent = '';
if(error === true) {
this.$(this.settings.progressBarField).style.backgroundColor = this.settings.progressBarColorError;
}
};
}

@ -0,0 +1,350 @@
function bigUpload () {
//These are the main config variables and should be able to take care of most of the customization
this.settings = {
//The id of the file input
'inputField': 'bigUploadFile',
//The id of the form with the file upload.
//This should be a valid html form (see index.html) so there is a fallback for unsupported browsers
'formId': 'bigUploadForm',
//The id of the progress bar
//Width of this element will change based on progress
//Content of this element will display a percentage
//See bigUpload.progressUpdate() to change this code
'progressBarField': 'bigUploadProgressBarFilled',
//The id of the time remaining field
//Content of this element will display the estimated time remaining for the upload
//See bigUpload.progressUpdate() to change this code
'timeRemainingField': 'bigUploadTimeRemaining',
//The id of the text response field
//Content of this element will display the response from the server on success or error
'responseField': 'bigUploadResponse',
//The id of the submit button
//This is then changed to become the pause/resume button based on the status of the upload
'submitButton': 'bigUploadSubmit',
//Color of the background of the progress bar
//This must also be defined in the progressBarField css, but it's used here to reset the color after an error
//Default: green
'progressBarColor': '#5bb75b',
//Color of the background of the progress bar when an error is triggered
//Default: red
'progressBarColorError': '#da4f49',
//Path to the php script for handling the uploads
'scriptPath': 'inc/big-upload.php',
//Additional URL variables to be passed to the script path
//ex: &foo=bar
'scriptPathParams': '',
//Size of chunks to upload (in bytes)
//Default: 1MB defaut : 1000000
'chunkSize': 50000,
//Max file size allowed
//Default: 2GB defaut : 2147483648
'maxFileSize': 414748364
};
this.scormid = '';
//Upload specific variables
this.uploadData = {
'uploadStarted': false,
'file': false,
'numberOfChunks': 0,
'aborted': false,
'paused': false,
'pauseChunk': 0,
'key': 0,
'timeStart': 0,
'totalTime': 0
};
//Success callback
this.success = function(response) {
};
parent = this;
//Quick function for accessing objects
this.$ = function(id) {
return document.getElementById(id);
};
//Resets all the upload specific data before a new upload
this.resetKey = function() {
this.uploadData = {
'uploadStarted': false,
'file': false,
'numberOfChunks': 0,
'aborted': false,
'paused': false,
'pauseChunk': 0,
'key': 0,
'timeStart': 0,
'totalTime': 0
};
};
//Inital method called
//Determines whether to begin/pause/resume an upload based on whether or not one is already in progress
this.fire = function() {
if(this.uploadData.uploadStarted === true && this.uploadData.paused === false) {
this.pauseUpload();
}
else if(this.uploadData.uploadStarted === true && this.uploadData.paused === true) {
this.resumeUpload();
}
else {
this.processFiles();
}
};
//Initial upload method
//Pulls the size of the file being uploaded and calculated the number of chunks, then calls the recursive upload method
this.processFiles = function() {
//If the user is using an unsupported browser, the form just submits as a regular form
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
this.$(this.settings.formId).submit();
return;
}
//Reset the upload-specific variables
this.resetKey();
this.uploadData.uploadStarted = true;
//Some HTML tidying
//Reset the background color of the progress bar in case it was changed by any earlier errors
//Change the Upload button to a Pause button
this.$(this.settings.progressBarField).style.backgroundColor = this.settings.progressBarColor;
this.$(this.settings.responseField).textContent = 'Envoi...';
this.$(this.settings.submitButton).value = 'Pause';
//Alias the file input object to this.uploadData
this.uploadData.file = this.$(this.settings.inputField).files[0];
//Check the filesize. Obviously this is not very secure, so it has another check in inc/bigUpload.php
//But this should be good enough to catch any immediate errors
var fileSize = this.uploadData.file.size;
if(fileSize > this.settings.maxFileSize) {
this.printResponse('Votre fichier est trop gros.', true);
return;
}
//Calculate the total number of file chunks
this.uploadData.numberOfChunks = Math.ceil(fileSize / this.settings.chunkSize);
//Start the upload
this.sendFile(0);
};
//Main upload method
this.sendFile = function (chunk) {
//Set the time for the beginning of the upload, used for calculating time remaining
this.uploadData.timeStart = new Date().getTime();
//Check if the upload has been cancelled by the user
if(this.uploadData.aborted === true) {
return;
}
//Check if the upload has been paused by the user
if(this.uploadData.paused === true) {
this.uploadData.pauseChunk = chunk;
this.printResponse('En pause.', false);
return;
}
//Set the byte to start uploading from and the byte to end uploading at
var start = chunk * this.settings.chunkSize;
var stop = start + this.settings.chunkSize;
//Initialize a new FileReader object
var reader = new FileReader();
reader.onloadend = function(evt) {
//Build the AJAX request
//
//this.uploadData.key is the temporary filename
//If the server sees it as 0 it will generate a new filename and pass it back in the JSON object
//this.uploadData.key is then populated with the filename to use for subsequent requests
//When this method sends a valid filename (i.e. key != 0), the server will just append the data being sent to that file.
xhr = new XMLHttpRequest();
xhr.open("POST", parent.settings.scriptPath + '?action=upload&key=' + parent.uploadData.key + parent.settings.scriptPathParams, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var response = JSON.parse(xhr.response);
//If there's an error, call the error method and break the loop
if(response.errorStatus !== 0 || xhr.status != 200) {
parent.printResponse(response.errorText, true);
return;
}
//If it's the first chunk, set this.uploadData.key to the server response (see above)
if(chunk === 0 || parent.uploadData.key === 0) {
parent.uploadData.key = response.key;
}
//If the file isn't done uploading, update the progress bar and run this.sendFile again for the next chunk
if(chunk < parent.uploadData.numberOfChunks) {
parent.progressUpdate(chunk + 1);
parent.sendFile(chunk + 1);
}
//If the file is complete uploaded, instantiate the finalizing method
else {
parent.sendFileData();
}
}
};
//Send the file chunk
xhr.send(blob);
};
//Slice the file into the desired chunk
//This is the core of the script. Everything else is just fluff.
var blob = this.uploadData.file.slice(start, stop);
reader.readAsBinaryString(blob);
};
//This method is for whatever housekeeping work needs to be completed after the file is finished uploading.
//As it's setup now, it passes along the original filename to the server and the server renames the file and removes it form the temp directory.
//This function could also pass things like this.uploadData.file.type for the mime-type (although it would be more accurate to use php for that)
//Or it could pass along user information or something like that, depending on the context of the application.
this.sendFileData = function() {
var data = 'key=' + this.uploadData.key + '&name=' + this.uploadData.file.name + '&scormid=' + this.scormid;
xhr = new XMLHttpRequest();
xhr.open("POST", parent.settings.scriptPath + '?action=finish', true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var response = JSON.parse(xhr.response);
//If there's an error, call the error method
if(response.errorStatus !== 0 || xhr.status != 200) {
parent.printResponse(response.errorText, true);
return;
}
//Reset the upload-specific data so we can process another upload
parent.resetKey();
//Change the submit button text so it's ready for another upload and spit out a sucess message
parent.$(parent.settings.submitButton).value = 'Start Upload';
//response.errorText = 'VOTRE CODE : ' + this.scormid;
var mess = 'CODEisOK';
parent.printResponse(mess, false);
$('#finalNameSrc').html(response.finalName);
parent.success(response);
}
};
//Send the reques
xhr.send(data);
};
//This method cancels the upload of a file.
//It sets this.uploadData.aborted to true, which stops the recursive upload script.
//The server then removes the incomplete file from the temp directory, and the html displays an error message.
this.abortFileUpload = function() {
this.uploadData.aborted = true;
var data = 'key=' + this.uploadData.key;
xhr = new XMLHttpRequest();
xhr.open("POST", this.settings.scriptPath + '?action=abort', true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var response = JSON.parse(xhr.response);
//If there's an error, call the error method.
if(response.errorStatus !== 0 || xhr.status != 200) {
parent.printResponse(response.errorText, true);
return;
}
parent.printResponse('File upload was cancelled.', true);
}
};
//Send the request
xhr.send(data);
};
//Pause the upload
//Sets this.uploadData.paused to true, which breaks the upload loop.
//The current chunk is still stored in this.uploadData.pauseChunk, so the upload can later be resumed.
//In a production environment, you might want to have a cron job to clean up files that have been paused and never resumed,
//because this method won't delete the file from the temp directory if the user pauses and then leaves the page.
this.pauseUpload = function() {
this.uploadData.paused = true;
this.printResponse('', false);
this.$(this.settings.submitButton).value = 'Resume';
};
//Resume the upload
//Undoes the doings of this.pauseUpload and then re-enters the loop at the last chunk uploaded
this.resumeUpload = function() {
this.uploadData.paused = false;
this.$(this.settings.submitButton).value = 'Pause';
this.sendFile(this.uploadData.pauseChunk);
};
//This method updates a simple progress bar by calculating the percentage of chunks uploaded.
//Also includes a method to calculate the time remaining by taking the average time to upload individual chunks
//and multiplying it by the number of chunks remaining.
this.progressUpdate = function(progress) {
var percent = Math.ceil((progress / this.uploadData.numberOfChunks) * 100);
this.$(this.settings.progressBarField).style.width = percent + '%';
this.$(this.settings.progressBarField).textContent = percent + '%';
//Calculate the estimated time remaining
//Only run this every five chunks, otherwise the time remaining jumps all over the place (see: http://xkcd.com/612/)
if(progress % 5 === 0) {
//Calculate the total time for all of the chunks uploaded so far
this.uploadData.totalTime += (new Date().getTime() - this.uploadData.timeStart);
console.log(this.uploadData.totalTime);
//Estimate the time remaining by finding the average time per chunk upload and
//multiplying it by the number of chunks remaining, then convert into seconds
var timeLeft = Math.ceil((this.uploadData.totalTime / progress) * (this.uploadData.numberOfChunks - progress) / 100);
console.log(Math.ceil(((this.uploadData.totalTime / progress) * this.settings.chunkSize) / 1024) + 'kb/s');
//Update this.settings.timeRemainingField with the estimated time remaining
this.$(this.settings.timeRemainingField).textContent = timeLeft + ' seconds remaining';
}
};
//Simple response/error handler
this.printResponse = function(responseText, error) {
this.$(this.settings.responseField).textContent = responseText;
this.$(this.settings.timeRemainingField).textContent = '';
if(error === true) {
this.$(this.settings.progressBarField).style.backgroundColor = this.settings.progressBarColorError;
}
};
}

@ -0,0 +1,55 @@
bigUpload = new bigUpload();
bigUpload.scormid = document.getElementById("scormid").value;
function upload() {
bigUpload.fire();
keepProgress();
$("#bigUploadSubmit").css('display','none');
$(".bigUploadAbort").css('display','');
}
function abort() {
bigUpload.abortFileUpload();
$("#bigUploadSubmit").css('display','');
$(".bigUploadAbort").css('display','none');
}
var refreshCount = 0;
var refreshpProgress = 1;
function keepProgress(){
$("#bigUploadProgressBarFilled").html(refreshpProgress + '%');
$("#bigUploadProgressBarFilled").css("width",refreshpProgress + '%');
refreshpProgress++;
if(refreshpProgress>91){
refreshpProgress = 80;
}
setTimeout('keepProgress()',2000);
}
function keepAlive(){
var kp = document.getElementById('kp');
kp.src = kp.src;
setTimeout('keepAlive()',20000);
refreshCount++;
}
keepAlive();
function controlAlive(){
var kp = document.getElementById('bigUploadResponse').innerHTML;
if (kp.indexOf('CODEisOK')!=-1) {
var nameSrc = document.getElementById('finalNameSrc').innerHTML;
if (nameSrc!='') {
document.getElementById('run').style.display = 'none';
document.getElementById('see').style.display = '';
var linkact = document.getElementById('linkact').innerHTML;
window.location = linkact + "&namesrc=" + nameSrc;
}
} else {
setTimeout('controlAlive()',250);
}
}
controlAlive();

File diff suppressed because one or more lines are too long

@ -0,0 +1,30 @@
function addLinkBigUpload(){
if (!document.getElementById("file_user_file_bu")){
var ludiiconplus = _p['web_plugin'] + 'chamilo_upload_large/css/import_scorm.png';
var lbu = $('#linkbu').html();
var h = '<a id="file_user_file_bu" href="'+lbu +'" ';
h += ' style="cursor:pointer;" ';
h += ' alt="large files" title="large files">';
h += '<img id="studioeltools" src="'+ ludiiconplus + '" ';
h += ' alt="large files" title="large files" style="cursor:pointer;" /> ';
h += '</a>';
var num = $("#toolbar-upload").length;
if(num==1){
$('#toolbar-upload').find("div:first-child").find("div:first-child").append(h);
} else {
$('.actions').append(h);
}
}
}
setTimeout(function(){
addLinkBigUpload();
},300);

@ -130,19 +130,28 @@ if (isset($_POST) && $is_error) {
return false;
break;
}
} elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
} elseif ($_SERVER['REQUEST_METHOD'] == 'POST' || ('bigUpload' === $_REQUEST['from'] && !empty($_REQUEST['name']))) {
// end if is_uploaded_file
// If file name given to get in /upload/, try importing this way.
// A file upload has been detected, now deal with the file...
// Directory creation.
$stopping_error = false;
if (!isset($_POST['file_name'])) {
return false;
// When it is used from bigupload input
if ('bigUpload' === $_REQUEST['from']) {
if (empty($_REQUEST['name'])) {
return false;
}
$tempName = $_REQUEST['name'];
} else {
if (!isset($_POST['file_name'])) {
return false;
}
$tempName = $_POST['file_name'];
}
// Escape path with basename so it can only be directly into the archive/ directory.
$s = api_get_path(SYS_ARCHIVE_PATH).basename($_POST['file_name']);
$s = api_get_path(SYS_ARCHIVE_PATH).basename($tempName);
// Get name of the zip file without the extension
$info = pathinfo($s);
$filename = $info['basename'];

@ -69,7 +69,8 @@ $form->addHeader($nameTools);
$form->addLabel(null, Display::return_icon('scorm_logo.jpg', null, ['style' => 'width:230px;height:100px']));
$form->addElement('hidden', 'curdirpath', $path);
$form->addElement('hidden', 'tool', $my_tool);
$form->addElement('file', 'user_file', get_lang('FileToUpload'));
//$form->addElement('file', 'user_file', get_lang('FileToUpload'));
$form->addElement('BigUpload', 'user_file', get_lang('FileToUpload'), ['id' => 'bigUploadFile', 'data-origin' => 'learnpath']);
$form->addProgress();
$form->addRule('user_file', get_lang('ThisFieldIsRequired'), 'required');

@ -4506,6 +4506,7 @@ function setWorkUploadForm($form, $uploadFormType = 0)
);
$form->addProgress();
$form->addRule('file', get_lang('ThisFieldIsRequired'), 'required');
//$form->addElement('BigUpload', 'file', get_lang('UploadADocument'), ['id' => 'bigUploadFile', 'data-origin' => 'work']);
break;
}
@ -4588,10 +4589,18 @@ function uploadWork($my_folder_data, $_course, $isCorrection = false, $workInfo
// If we come from the group tools the groupid will be saved in $work_table
if (is_dir($updir.$curdirpath) || empty($curdirpath)) {
$result = move_uploaded_file(
$file['tmp_name'],
$updir.$curdirpath.'/'.$new_file_name
);
if ($file['copy_file']) {
$result = copy(
$file['tmp_name'],
$updir.$curdirpath.'/'.$new_file_name
);
unlink($file['tmp_name']);
} else {
$result = move_uploaded_file(
$file['tmp_name'],
$updir.$curdirpath.'/'.$new_file_name
);
}
} else {
return [
'error' => Display :: return_message(

Loading…
Cancel
Save