Merge branch '1.11.x' of github.com:chamilo/chamilo-lms into 1.11.x

pull/4890/head
Yannick Warnier 2 years ago
commit 83cf9adaf7
  1. 4
      .htaccess
  2. 1
      main/auth/inscription.php
  3. 3
      main/exercise/ReadingComprehension.php
  4. 7
      main/inc/ajax/document.ajax.php
  5. 2
      main/inc/lib/groupmanager.lib.php
  6. 65
      main/inc/lib/javascript/bigupload/inc/bigUpload.php
  7. 350
      main/inc/lib/javascript/bigupload/js/big_upload.js
  8. 55
      main/inc/lib/javascript/bigupload/js/init.js
  9. 30
      main/inc/lib/javascript/bigupload/js/ui-sco.js
  10. 10
      main/lp/openoffice_document.class.php
  11. 15
      main/lp/openoffice_presentation.class.php
  12. 6
      main/lp/openoffice_text.class.php
  13. 6
      main/lp/openoffice_text_document.class.php
  14. 1
      main/lp/scorm.class.php
  15. 2
      main/session/add_users_to_session_course.php

@ -113,3 +113,7 @@ AddType application/font-woff .woff .woff2
#<IfModule mod_headers.c>
# Header always set Content-Security-Policy "upgrade-insecure-requests;"
#</IfModule>
<IfModule mod_headers.c>
Header always set X-Content-Type-Options nosniff
</IfModule>

@ -916,6 +916,7 @@ if ($form->validate()) {
$_user['language'] = $values['language'];
$_user['user_id'] = $user_id;
$_user['status'] = $values['status'] ?? STUDENT;
ConditionalLogin::check_conditions($_user);
Session::write('_user', $_user);
$is_allowedCreateCourse = isset($values['status']) && $values['status'] == 1;

@ -73,7 +73,8 @@ class ReadingComprehension extends UniqueAnswer
parent::__construct();
$this->type = READING_COMPREHENSION;
$this->isContent = $this->getIsContent();
$customSpeeds = api_get_configuration_value('exercise_question_reading_comprehension_extra_speeds')['speeds'];
$extraSpeeds = api_get_configuration_value('exercise_question_reading_comprehension_extra_speeds');
$customSpeeds = $extraSpeeds['speeds'] ?? [];
if (!empty($customSpeeds) && is_array($customSpeeds)) {
$this->allSpeeds = self::$speeds;
foreach ($customSpeeds as $speed) {

@ -59,9 +59,12 @@ switch ($action) {
}
if (!empty($fileList)) {
foreach ($fileList as $n => $file) {
$tmpFile = $tempDirectory.$file['name'];
$tmpFile = disable_dangerous_file(
api_replace_dangerous_char($file['name'])
);
file_put_contents(
$tmpFile,
$tempDirectory.$tmpFile,
fopen($file['tmp_name'], 'r'),
FILE_APPEND
);

@ -499,7 +499,7 @@ class GroupManager
// to_group_id is related to c_group_info.iid
$itemPropertyTable = Database::get_course_table(TABLE_ITEM_PROPERTY);
$sql = "DELETE FROM $itemPropertyTable
WHERE c_id = $course_id AND to_group_id = $groupIid ";
WHERE to_group_id = $groupIid ";
Database::query($sql);
// delete the groups

@ -5,16 +5,6 @@ require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
class BigUploadResponse
{
/**
* 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.
@ -28,13 +18,6 @@ class BigUploadResponse
*/
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.
*
@ -49,7 +32,6 @@ class BigUploadResponse
{
$tempDirectory = api_get_path(SYS_ARCHIVE_PATH);
$this->setTempDirectory($tempDirectory);
$this->setMainDirectory(self::MAIN_DIRECTORY);
$this->maxSize = getIniMaxFileSizeInBytes();
}
@ -105,26 +87,6 @@ class BigUploadResponse
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.
*
@ -288,30 +250,6 @@ class BigUploadResponse
return json_encode(['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) > $this->maxSize) {
return get_lang('UplFileTooBig');
}
if (move_uploaded_file($tempName, $this->getMainDirectory().$name)) {
return get_lang('FileUploadSucces');
} else {
return get_lang('UplUnableToSaveFile');
}
}
}
$sessionBigUpload = ChamiloSession::read('bigupload', []);
@ -358,7 +296,4 @@ switch ($_GET['action']) {
ChamiloSession::write('bigupload', $sessionBigUpload);
}
break;
case 'post-unsupported':
print $bigUpload->postUnsupported();
break;
}

@ -1,350 +0,0 @@
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;
}
};
}

@ -1,55 +0,0 @@
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();

@ -1,30 +0,0 @@
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);

@ -70,8 +70,8 @@ abstract class OpenofficeDocument extends learnpath
if (!empty($size)) {
list($w, $h) = explode('x', $size);
if (!empty($w) && !empty($h)) {
$this->slide_width = $w;
$this->slide_height = $h;
$this->slide_width = (int) $w;
$this->slide_height = (int) $h;
}
}
@ -106,6 +106,7 @@ abstract class OpenofficeDocument extends learnpath
$files = [];
$return = 0;
$cmd = escapeshellcmd($cmd);
$shell = exec($cmd, $files, $return);
if ($return != 0) { // If the java application returns an error code.
@ -211,7 +212,9 @@ abstract class OpenofficeDocument extends learnpath
$cmd .= ' -p '.api_get_setting('service_ppt2lp', 'port');
// Call to the function implemented by child.
$cmd .= ' "'.$this->base_work_dir.'/'.$this->file_path.'" "'.$this->base_work_dir.'/'.$this->created_dir.'"';
$cmd .= ' "'.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.'" "'
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->created_dir).'"';
// To allow openoffice to manipulate docs.
@chmod($this->base_work_dir, $permissionFolder);
@chmod($this->base_work_dir.'/'.$this->file_path, $permissionFile);
@ -221,6 +224,7 @@ abstract class OpenofficeDocument extends learnpath
$files = [];
$return = 0;
$cmd = escapeshellcmd($cmd);
$shell = exec($cmd, $files, $return);
// TODO: Chown is not working, root keep user privileges, should be www-data
@chown($this->base_work_dir.'/'.$this->created_dir, 'www-data');

@ -247,16 +247,23 @@ class OpenofficePresentation extends OpenofficeDocument
public function add_command_parameters()
{
if (empty($this->slide_width) || empty($this->slide_height)) {
list($this->slide_width, $this->slide_height) = explode('x', api_get_setting('service_ppt2lp', 'size'));
list($w, $h) = explode('x', api_get_setting('service_ppt2lp', 'size'));
$this->slide_width = (int) $w;
$this->slide_height = (int) $h;
}
return ' -w '.$this->slide_width.' -h '.$this->slide_height.' -d oogie "'.$this->base_work_dir.'/'.$this->file_path.'" "'.$this->base_work_dir.$this->created_dir.'.html"';
return ' -w '.$this->slide_width.' -h '.$this->slide_height.' -d oogie "'
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.'" "'
.Security::sanitizeExecParam($this->base_work_dir.$this->created_dir.'.html')
.'"';
}
public function set_slide_size($width, $height)
{
$this->slide_width = $width;
$this->slide_height = $height;
$this->slide_width = (int) $width;
$this->slide_height = (int) $height;
}
public function add_docs_to_visio($files = [])

@ -331,7 +331,11 @@ class OpenofficeText extends OpenofficeDocument
*/
public function add_command_parameters()
{
return ' -d woogie "'.$this->base_work_dir.'/'.$this->file_path.'" "'.$this->base_work_dir.$this->created_dir.'/'.$this->file_name.'.html"';
return ' -d woogie "'
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.'" "'
.Security::sanitizeExecParam($this->base_work_dir.$this->created_dir.'/'.$this->file_name.'.html')
.'"';
}
/**

@ -333,7 +333,11 @@ class OpenOfficeTextDocument extends OpenofficeDocument
*/
public function add_command_parameters()
{
return ' -d woogie "'.$this->base_work_dir.'/'.$this->file_path.'" "'.$this->base_work_dir.$this->created_dir.'/'.$this->file_name.'.html"';
return ' -d woogie "'
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.'" "'
.Security::sanitizeExecParam($this->base_work_dir.$this->created_dir.'/'.$this->file_name.'.html')
.'"';
}
/**

@ -720,7 +720,6 @@ class scorm extends learnpath
if ($updateDirContents && $lpToCheck) {
$originalPath = str_replace('/.', '', $lpToCheck->path);
$originalPath = dirname($originalPath);
if ($originalPath != $newDir) {
Display::addFlash(Display::return_message(get_lang('FileError')));

@ -257,7 +257,7 @@ function search_users($needle, $type)
}
$return .= '<a href="javascript: void(0);" onclick="javascript: add_user_to_session(\''.$user['id']
.'\',\''.$person_name.' '.'\')">'.$person_name.' </a><br />';
.'\',\''.addslashes(htmlentities($person_name)).' '.'\')">'.$person_name.' </a><br />';
} else {
$return .= '...<br />';
}

Loading…
Cancel
Save