upgrade
This commit is contained in:
135
main/exercise/Annotation.php
Normal file
135
main/exercise/Annotation.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class Annotation
|
||||
* Allow instanciate an object of type HotSpot extending the class question.
|
||||
*
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*/
|
||||
class Annotation extends Question
|
||||
{
|
||||
public $typePicture = 'annotation.png';
|
||||
public $explanationLangVar = 'Annotation';
|
||||
|
||||
/**
|
||||
* Annotation constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = ANNOTATION;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
public function display()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createForm(&$form, $exercise)
|
||||
{
|
||||
parent::createForm($form, $exercise);
|
||||
|
||||
$form->addElement(
|
||||
'number',
|
||||
'weighting',
|
||||
get_lang('Weighting'),
|
||||
['step' => '0.1']
|
||||
);
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults(['weighting' => float_format($this->weighting, 1)]);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults(['weighting' => '10']);
|
||||
}
|
||||
}
|
||||
|
||||
global $text;
|
||||
if (isset($_GET['editQuestion'])) {
|
||||
$form->addButtonUpdate($text, 'submitQuestion');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$form->addElement(
|
||||
'file',
|
||||
'imageUpload',
|
||||
[
|
||||
Display::img(
|
||||
Display::return_icon(
|
||||
'annotation.png',
|
||||
null,
|
||||
null,
|
||||
ICON_SIZE_BIG,
|
||||
false,
|
||||
true
|
||||
)
|
||||
),
|
||||
get_lang('UploadJpgPicture'),
|
||||
]
|
||||
);
|
||||
|
||||
$form->addButtonSave(get_lang('GoToQuestion'), 'submitQuestion');
|
||||
$form->addRule(
|
||||
'imageUpload',
|
||||
get_lang('OnlyImagesAllowed'),
|
||||
'filetype',
|
||||
['jpg', 'jpeg', 'png', 'gif']
|
||||
);
|
||||
$form->addRule('imageUpload', get_lang('NoImage'), 'uploadedfile');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processCreation($form, $exercise)
|
||||
{
|
||||
$fileInfo = $form->getSubmitValue('imageUpload');
|
||||
parent::processCreation($form, $exercise);
|
||||
|
||||
if (!empty($fileInfo['tmp_name'])) {
|
||||
$result = $this->uploadPicture($fileInfo['tmp_name']);
|
||||
if ($result) {
|
||||
$this->weighting = $form->getSubmitValue('weighting');
|
||||
$this->save($exercise);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormValidator $form
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$this->weighting = $form->getSubmitValue('weighting');
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$score['revised'] = $this->isQuestionWaitingReview($score);
|
||||
|
||||
return parent::return_header($exercise, $counter, $score); // TODO: Change the autogenerated stub
|
||||
}
|
||||
}
|
||||
216
main/exercise/AnswerInOfficeDoc.php
Normal file
216
main/exercise/AnswerInOfficeDoc.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class AnswerInOfficeDoc
|
||||
* Allows a question type where the answer is written in an Office document.
|
||||
*
|
||||
* @author Cristian
|
||||
*/
|
||||
class AnswerInOfficeDoc extends Question
|
||||
{
|
||||
public $typePicture = 'options_evaluation.png';
|
||||
public $explanationLangVar = 'AnswerInOfficeDoc';
|
||||
public $sessionId;
|
||||
public $userId;
|
||||
public $exerciseId;
|
||||
public $exeId;
|
||||
private $storePath;
|
||||
private $fileName;
|
||||
private $filePath;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if ('true' !== OnlyofficePlugin::create()->get('enable_onlyoffice_plugin')) {
|
||||
throw new Exception(get_lang('OnlyOfficePluginRequired'));
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
$this->type = ANSWER_IN_OFFICE_DOC;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the file path structure.
|
||||
*/
|
||||
public function initFile(int $sessionId, int $userId, int $exerciseId, int $exeId): void
|
||||
{
|
||||
$this->sessionId = $sessionId ?: 0;
|
||||
$this->userId = $userId;
|
||||
$this->exerciseId = $exerciseId ?: 0;
|
||||
$this->exeId = $exeId;
|
||||
|
||||
$this->storePath = $this->generateDirectory();
|
||||
$this->fileName = $this->generateFileName();
|
||||
$this->filePath = $this->storePath.$this->fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create form for uploading an Office document.
|
||||
*/
|
||||
public function createAnswersForm($form): void
|
||||
{
|
||||
if (!empty($this->exerciseList)) {
|
||||
$this->exerciseId = reset($this->exerciseList);
|
||||
}
|
||||
|
||||
$allowedFormats = [
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
||||
'application/msword', // .doc
|
||||
'application/vnd.ms-excel', // .xls
|
||||
];
|
||||
|
||||
$form->addElement('file', 'office_file', get_lang('UploadOfficeDoc'));
|
||||
$form->addRule('office_file', get_lang('ThisFieldIsRequired'), 'required');
|
||||
$form->addRule('office_file', get_lang('InvalidFileFormat'), 'mimetype', $allowedFormats);
|
||||
|
||||
$allowedExtensions = implode(', ', ['.docx', '.xlsx', '.doc', '.xls']);
|
||||
$form->addElement('static', 'file_hint', get_lang('AllowedFormats'), "<p>{$allowedExtensions}</p>");
|
||||
|
||||
if (!empty($this->extra)) {
|
||||
$fileUrl = api_get_path(WEB_COURSE_PATH).$this->getStoredFilePath();
|
||||
$form->addElement('static', 'current_office_file', get_lang('CurrentOfficeDoc'), "<a href='{$fileUrl}' target='_blank'>{$this->extra}</a>");
|
||||
}
|
||||
|
||||
$form->addText('weighting', get_lang('Weighting'), ['class' => 'span1']);
|
||||
|
||||
global $text;
|
||||
$form->addButtonSave($text, 'submitQuestion');
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults(['weighting' => float_format($this->weighting, 1)]);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults(['weighting' => '10']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the uploaded document and save it.
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise): void
|
||||
{
|
||||
if (!empty($_FILES['office_file']['name'])) {
|
||||
$extension = pathinfo($_FILES['office_file']['name'], PATHINFO_EXTENSION);
|
||||
$tempFilename = "office_".uniqid().".".$extension;
|
||||
$tempPath = sys_get_temp_dir().'/'.$tempFilename;
|
||||
|
||||
if (!move_uploaded_file($_FILES['office_file']['tmp_name'], $tempPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->weighting = $form->getSubmitValue('weighting');
|
||||
$this->extra = "";
|
||||
$this->save($exercise);
|
||||
|
||||
$this->exerciseId = $exercise->iid;
|
||||
$uploadDir = $this->generateDirectory();
|
||||
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0775, true);
|
||||
}
|
||||
|
||||
$filename = "office_".$this->iid.".".$extension;
|
||||
$filePath = $uploadDir.$filename;
|
||||
|
||||
if (!rename($tempPath, $filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->extra = $filename;
|
||||
$this->save($exercise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored file path dynamically.
|
||||
*/
|
||||
public function getStoredFilePath(): ?string
|
||||
{
|
||||
if (empty($this->extra)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "{$this->course['path']}/exercises/onlyoffice/{$this->exerciseId}/{$this->iid}/{$this->extra}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute file path. Returns null if the file doesn't exist.
|
||||
*/
|
||||
public function getFileUrl(bool $loadFromDatabase = false): ?string
|
||||
{
|
||||
if ($loadFromDatabase) {
|
||||
$em = Database::getManager();
|
||||
$result = $em->getRepository('ChamiloCoreBundle:TrackEAttempt')->findOneBy([
|
||||
'exeId' => $this->exeId,
|
||||
'userId' => $this->userId,
|
||||
'questionId' => $this->iid,
|
||||
'sessionId' => $this->sessionId,
|
||||
'cId' => $this->course['real_id'],
|
||||
]);
|
||||
|
||||
if (!$result || empty($result->getFilename())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->fileName = $result->getFilename();
|
||||
} else {
|
||||
if (empty($this->extra)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->fileName = $this->extra;
|
||||
}
|
||||
|
||||
$filePath = $this->getStoredFilePath();
|
||||
|
||||
if (is_file(api_get_path(SYS_COURSE_PATH).$filePath)) {
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the question in an exercise.
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$score['revised'] = $this->isQuestionWaitingReview($score);
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'">
|
||||
<tr>
|
||||
<th>'.get_lang("Answer").'</th>
|
||||
</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the necessary directory for OnlyOffice documents.
|
||||
*/
|
||||
private function generateDirectory(): string
|
||||
{
|
||||
$exercisePath = api_get_path(SYS_COURSE_PATH).$this->course['path']."/exercises/onlyoffice/{$this->exerciseId}/{$this->iid}/";
|
||||
|
||||
if (!is_dir($exercisePath)) {
|
||||
mkdir($exercisePath, 0775, true);
|
||||
}
|
||||
|
||||
return rtrim($exercisePath, '/').'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the file name for the OnlyOffice document.
|
||||
*/
|
||||
private function generateFileName(): string
|
||||
{
|
||||
return 'office_'.uniqid();
|
||||
}
|
||||
}
|
||||
234
main/exercise/Draggable.php
Normal file
234
main/exercise/Draggable.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class Draggable.
|
||||
*
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*/
|
||||
class Draggable extends Question
|
||||
{
|
||||
public $typePicture = 'ordering.png';
|
||||
public $explanationLangVar = 'Draggable';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = DRAGGABLE;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$defaults = [];
|
||||
$nb_matches = $nb_options = 2;
|
||||
$matches = [];
|
||||
$answer = null;
|
||||
if ($form->isSubmitted()) {
|
||||
$nb_matches = $form->getSubmitValue('nb_matches');
|
||||
$nb_options = $form->getSubmitValue('nb_options');
|
||||
|
||||
if (isset($_POST['lessMatches'])) {
|
||||
$nb_matches--;
|
||||
}
|
||||
|
||||
if (isset($_POST['moreMatches'])) {
|
||||
$nb_matches++;
|
||||
}
|
||||
|
||||
if (isset($_POST['lessOptions'])) {
|
||||
$nb_options--;
|
||||
}
|
||||
|
||||
if (isset($_POST['moreOptions'])) {
|
||||
$nb_options++;
|
||||
}
|
||||
} elseif (!empty($this->iid)) {
|
||||
$defaults['orientation'] = in_array($this->extra, ['h', 'v']) ? $this->extra : 'v';
|
||||
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
|
||||
if ($answer->nbrAnswers > 0) {
|
||||
$nb_matches = $nb_options = 0;
|
||||
for ($i = 1; $i <= $answer->nbrAnswers; $i++) {
|
||||
if ($answer->isCorrect($i)) {
|
||||
$nb_matches++;
|
||||
$defaults['answer['.$nb_matches.']'] = $answer->selectAnswer($i);
|
||||
$defaults['weighting['.$nb_matches.']'] = float_format($answer->selectWeighting($i), 1);
|
||||
$answerInfo = $answer->getAnswerByAutoId($answer->correct[$i]);
|
||||
$defaults['matches['.$nb_matches.']'] = isset($answerInfo['answer']) ? $answerInfo['answer'] : '';
|
||||
} else {
|
||||
$nb_options++;
|
||||
$defaults['option['.$nb_options.']'] = $answer->selectAnswer($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$defaults['answer[1]'] = get_lang('DefaultMakeCorrespond1');
|
||||
$defaults['answer[2]'] = get_lang('DefaultMakeCorrespond2');
|
||||
$defaults['matches[2]'] = '2';
|
||||
$defaults['option[1]'] = get_lang('DefaultMatchingOptA');
|
||||
$defaults['option[2]'] = get_lang('DefaultMatchingOptB');
|
||||
$defaults['orientation'] = 'v';
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$matches[$i] = $i;
|
||||
}
|
||||
|
||||
$form->addElement('hidden', 'nb_matches', $nb_matches);
|
||||
$form->addElement('hidden', 'nb_options', $nb_options);
|
||||
|
||||
$form->addRadio(
|
||||
'orientation',
|
||||
get_lang('ChooseOrientation'),
|
||||
['v' => get_lang('Vertical'), 'h' => get_lang('Horizontal')]
|
||||
);
|
||||
|
||||
// DISPLAY MATCHES
|
||||
$html = '<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="85%">'.get_lang('Answer').'</th>
|
||||
<th width="15%">'.get_lang('MatchesTo').'</th>
|
||||
<th width="10">'.get_lang('Weighting').'</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
$form->addHeader(get_lang('MakeCorrespond'));
|
||||
$form->addHtml($html);
|
||||
|
||||
if ($nb_matches < 1) {
|
||||
$nb_matches = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'), 'normal');
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$renderer = &$form->defaultRenderer();
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"answer[$i]"
|
||||
);
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"matches[$i]"
|
||||
);
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"weighting[$i]"
|
||||
);
|
||||
|
||||
$form->addHtml('<tr>');
|
||||
$form->addText("answer[$i]", null);
|
||||
$form->addSelect("matches[$i]", null, $matches);
|
||||
$form->addText("weighting[$i]", null, true, ['value' => 10, 'style' => 'width: 60px;']);
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</tbody></table>');
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<div class="form-group"><div class="col-sm-offset-2">{element}',
|
||||
'lessMatches'
|
||||
);
|
||||
$renderer->setElementTemplate('{element}</div></div>', 'moreMatches');
|
||||
|
||||
global $text;
|
||||
|
||||
$group = [
|
||||
$form->addButtonDelete(get_lang('DelElem'), 'lessMatches', true),
|
||||
$form->addButtonCreate(get_lang('AddElem'), 'moreMatches', true),
|
||||
$form->addButtonSave($text, 'submitQuestion', true),
|
||||
];
|
||||
|
||||
$form->addGroup($group);
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
$form->setDefaults(['orientation' => 'v']);
|
||||
|
||||
if (1 == $this->isContent) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
|
||||
$form->setConstants(
|
||||
[
|
||||
'nb_matches' => $nb_matches,
|
||||
'nb_options' => $nb_options,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$this->extra = $form->exportValue('orientation');
|
||||
$nb_matches = $form->getSubmitValue('nb_matches');
|
||||
$this->weighting = 0;
|
||||
$position = 0;
|
||||
$objAnswer = new Answer($this->iid);
|
||||
// Insert the options
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$position++;
|
||||
$objAnswer->createAnswer($position, 0, '', 0, $position);
|
||||
}
|
||||
|
||||
// Insert the answers
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$position++;
|
||||
$answer = $form->getSubmitValue('answer['.$i.']');
|
||||
$matches = $form->getSubmitValue('matches['.$i.']');
|
||||
$weighting = $form->getSubmitValue('weighting['.$i.']');
|
||||
$this->weighting += $weighting;
|
||||
$objAnswer->createAnswer(
|
||||
$answer,
|
||||
$matches,
|
||||
'',
|
||||
$weighting,
|
||||
$position
|
||||
);
|
||||
}
|
||||
|
||||
$objAnswer->save();
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'"><tr>';
|
||||
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
$header .= '<th>'.get_lang('YourChoice').'</th>';
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
} else {
|
||||
$header .= '<th>'.get_lang('ElementList').'</th>';
|
||||
$header .= '<th>'.get_lang('YourChoice').'</th>';
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
21
main/exercise/FillBlanksCombination.php
Normal file
21
main/exercise/FillBlanksCombination.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* FillBlanksCombination.
|
||||
*/
|
||||
class FillBlanksCombination extends FillBlanks
|
||||
{
|
||||
public $typePicture = 'fill_in_blanks_co.png';
|
||||
public $explanationLangVar = 'FillBlanksCombination';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = FILL_IN_BLANKS_COMBINATION;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
}
|
||||
20
main/exercise/HotSpotCombination.php
Normal file
20
main/exercise/HotSpotCombination.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* HotSpotCombination.
|
||||
*/
|
||||
class HotSpotCombination extends HotSpot
|
||||
{
|
||||
public $typePicture = 'hotspot_co.png';
|
||||
public $explanationLangVar = 'HotSpotCombination';
|
||||
|
||||
/**
|
||||
* HotSpot constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = HOT_SPOT_COMBINATION;
|
||||
}
|
||||
}
|
||||
21
main/exercise/HotSpotDelineation.php
Normal file
21
main/exercise/HotSpotDelineation.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class HotSpotDelineation.
|
||||
*/
|
||||
class HotSpotDelineation extends HotSpot
|
||||
{
|
||||
public $typePicture = 'hotspot-delineation.png';
|
||||
public $explanationLangVar = 'HotspotDelineation';
|
||||
|
||||
/**
|
||||
* HotSpotDelineation constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = HOT_SPOT_DELINEATION;
|
||||
}
|
||||
}
|
||||
149
main/exercise/Hpdownload.php
Normal file
149
main/exercise/Hpdownload.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* This script shows the list of exercises for administrators and students.
|
||||
*
|
||||
* @author Istvan Mandak
|
||||
*
|
||||
* @version $Id: Hpdownload.php 22201 2009-07-17 19:57:03Z cfasanando $
|
||||
*/
|
||||
session_cache_limiter('public');
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
$tbl_document = Database::get_course_table(TABLE_DOCUMENT);
|
||||
|
||||
$doc_url = str_replace(['../', '\\..', '\\0', '..\\'], ['', '', '', ''], urldecode($_GET['doc_url']));
|
||||
$filename = basename($doc_url);
|
||||
|
||||
// launch event
|
||||
//Event::event_download($doc_url);
|
||||
if (isset($_course['path'])) {
|
||||
$course_path = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
$full_file_name = $course_path.Security::remove_XSS($doc_url);
|
||||
} else {
|
||||
$course_path = api_get_path(SYS_COURSE_PATH).$cid.'/document';
|
||||
$full_file_name = $course_path.Security::remove_XSS($doc_url);
|
||||
}
|
||||
|
||||
if (!is_file($full_file_name)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!Security::check_abs_path($full_file_name, $course_path.'/')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$extension = explode('.', $filename);
|
||||
$extension = strtolower($extension[count($extension) - 1]);
|
||||
|
||||
switch ($extension) {
|
||||
case 'gz':
|
||||
$content_type = 'application/x-gzip';
|
||||
break;
|
||||
case 'zip':
|
||||
$content_type = 'application/zip';
|
||||
break;
|
||||
case 'pdf':
|
||||
$content_type = 'application/pdf';
|
||||
break;
|
||||
case 'png':
|
||||
$content_type = 'image/png';
|
||||
break;
|
||||
case 'gif':
|
||||
$content_type = 'image/gif';
|
||||
break;
|
||||
case 'jpg':
|
||||
$content_type = 'image/jpeg';
|
||||
break;
|
||||
case 'txt':
|
||||
$content_type = 'text/plain';
|
||||
break;
|
||||
case 'htm':
|
||||
$content_type = 'text/html';
|
||||
break;
|
||||
case 'html':
|
||||
$content_type = 'text/html';
|
||||
break;
|
||||
default:
|
||||
$content_type = 'application/octet-stream';
|
||||
break;
|
||||
}
|
||||
|
||||
header('Content-disposition: filename='.$filename);
|
||||
header('Content-Type: '.$content_type);
|
||||
header('Expires: '.gmdate('D, d M Y H:i:s', time() + 10).' GMT');
|
||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s', time() + 10).' GMT');
|
||||
|
||||
/*
|
||||
Dynamic parsing section
|
||||
is activated whenever a user views an html file
|
||||
work in progress
|
||||
- question: we could also parse per line,
|
||||
perhaps this would be faster.
|
||||
($file_content = file($full_file_name) returns file in array)
|
||||
*/
|
||||
|
||||
if ($content_type == 'text/html') {
|
||||
$directory_name = dirname($full_file_name);
|
||||
$coursePath = api_get_path(SYS_COURSE_PATH);
|
||||
$dir = str_replace(['\\', $coursePath.$_course['path'].'/document'], ['/', ''], $directory_name);
|
||||
|
||||
if ($dir[strlen($dir) - 1] != '/') {
|
||||
$dir .= '/';
|
||||
}
|
||||
|
||||
//Parse whole file at one
|
||||
$fp = fopen($full_file_name, "r");
|
||||
$file_content = fread($fp, filesize($full_file_name));
|
||||
fclose($fp);
|
||||
$exercisePath = api_get_self();
|
||||
$exfile = explode('/', $exercisePath);
|
||||
$exfile = $exfile[sizeof($exfile) - 1];
|
||||
$exercisePath = substr($exercisePath, 0, strpos($exercisePath, $exfile));
|
||||
|
||||
$content = $file_content;
|
||||
$mit = "function Finish(){";
|
||||
$js_content = "var SaveScoreVariable = 0; // This variable included by Chamilo\n".
|
||||
"function mySaveScore() // This function included by Chamilo\n".
|
||||
"{\n".
|
||||
" if (SaveScoreVariable==0)\n".
|
||||
" {\n".
|
||||
" SaveScoreVariable = 1;\n".
|
||||
" if (C.ie)\n".
|
||||
" {\n".
|
||||
" document.location.href = \"".$exercisePath."savescores.php?origin=$origin&time=$time&test=".$doc_url."&uid=".$_user['user_id']."&cid=".$cid."&score=\"+Score;\n".
|
||||
" //window.alert(Score);\n".
|
||||
" }\n".
|
||||
" else\n".
|
||||
" {\n".
|
||||
" }\n".
|
||||
" }\n".
|
||||
"}\n".
|
||||
"// Must be included \n".
|
||||
"function Finish(){\n".
|
||||
" mySaveScore();";
|
||||
$newcontent = str_replace($mit, $js_content, $content);
|
||||
|
||||
$prehref = "javascript:void(0);";
|
||||
$posthref = api_get_path(WEB_CODE_PATH)."main/exercise/Hpdownload.php?doc_url=".$doc_url."&cid=".$cid."&uid=".$uid;
|
||||
$newcontent = str_replace($prehref, $posthref, $newcontent);
|
||||
$prehref = "class=\"GridNum\" onclick=";
|
||||
$posthref = "class=\"GridNum\" onMouseover=";
|
||||
$newcontent = str_replace($prehref, $posthref, $newcontent);
|
||||
header('Content-length: '.strlen($newcontent));
|
||||
// Dipsp.
|
||||
echo $newcontent;
|
||||
exit();
|
||||
}
|
||||
|
||||
//normal case, all non-html files
|
||||
//header('Content-length: ' . filesize($full_file_name));
|
||||
$fp = fopen($full_file_name, 'rb');
|
||||
fpassthru($fp);
|
||||
fclose($fp);
|
||||
21
main/exercise/MatchingCombination.php
Normal file
21
main/exercise/MatchingCombination.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* MatchingCombination.
|
||||
*/
|
||||
class MatchingCombination extends Matching
|
||||
{
|
||||
public $typePicture = 'matching_co.png';
|
||||
public $explanationLangVar = 'MatchingCombination';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MATCHING_COMBINATION;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
}
|
||||
312
main/exercise/MatchingDraggable.php
Normal file
312
main/exercise/MatchingDraggable.php
Normal file
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* MatchingDraggable.
|
||||
*
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*/
|
||||
class MatchingDraggable extends Question
|
||||
{
|
||||
public $typePicture = 'matchingdrag.png';
|
||||
public $explanationLangVar = 'MatchingDraggable';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MATCHING_DRAGGABLE;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$defaults = [];
|
||||
$nb_matches = $nb_options = 2;
|
||||
$matches = [];
|
||||
$answer = null;
|
||||
$counter = 1;
|
||||
if (!empty($this->iid)) {
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
|
||||
if ($answer->nbrAnswers > 0) {
|
||||
for ($i = 1; $i <= $answer->nbrAnswers; $i++) {
|
||||
$correct = $answer->isCorrect($i);
|
||||
if (empty($correct)) {
|
||||
$matches[$answer->selectAutoId($i)] = chr(64 + $counter);
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($form->isSubmitted()) {
|
||||
$nb_matches = $form->getSubmitValue('nb_matches');
|
||||
$nb_options = $form->getSubmitValue('nb_options');
|
||||
if (isset($_POST['lessOptions'])) {
|
||||
$nb_matches--;
|
||||
$nb_options--;
|
||||
}
|
||||
if (isset($_POST['moreOptions'])) {
|
||||
$nb_matches++;
|
||||
$nb_options++;
|
||||
}
|
||||
} elseif (!empty($this->iid)) {
|
||||
if ($answer->nbrAnswers > 0) {
|
||||
$nb_matches = $nb_options = 0;
|
||||
for ($i = 1; $i <= $answer->nbrAnswers; $i++) {
|
||||
if ($answer->isCorrect($i)) {
|
||||
$nb_matches++;
|
||||
$defaults['answer['.$nb_matches.']'] = $answer->selectAnswer($i);
|
||||
$defaults['weighting['.$nb_matches.']'] = float_format($answer->selectWeighting($i), 1);
|
||||
$defaults['matches['.$nb_matches.']'] = $answer->correct[$i];
|
||||
} else {
|
||||
$nb_options++;
|
||||
$defaults['option['.$nb_options.']'] = $answer->selectAnswer($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$defaults['answer[1]'] = get_lang('DefaultMakeCorrespond1');
|
||||
$defaults['answer[2]'] = get_lang('DefaultMakeCorrespond2');
|
||||
$defaults['matches[2]'] = '2';
|
||||
$defaults['option[1]'] = get_lang('DefaultMatchingOptA');
|
||||
$defaults['option[2]'] = get_lang('DefaultMatchingOptB');
|
||||
}
|
||||
|
||||
if (empty($matches)) {
|
||||
for ($i = 1; $i <= $nb_options; $i++) {
|
||||
// fill the array with A, B, C.....
|
||||
$matches[$i] = chr(64 + $i);
|
||||
}
|
||||
} else {
|
||||
for ($i = $counter; $i <= $nb_options; $i++) {
|
||||
// fill the array with A, B, C.....
|
||||
$matches[$i] = chr(64 + $i);
|
||||
}
|
||||
}
|
||||
|
||||
$form->addElement('hidden', 'nb_matches', $nb_matches);
|
||||
$form->addElement('hidden', 'nb_options', $nb_options);
|
||||
|
||||
$thWeighting = '';
|
||||
if (MATCHING_DRAGGABLE === $this->type) {
|
||||
$thWeighting = '<th width="10">'.get_lang('Weighting').'</th>';
|
||||
}
|
||||
|
||||
// DISPLAY MATCHES
|
||||
$html = '<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10">'.get_lang('Number').'</th>
|
||||
<th width="85%">'.get_lang('Answer').'</th>
|
||||
<th width="15%">'.get_lang('MatchesTo').'</th>
|
||||
'.$thWeighting.'
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
$form->addHeader(get_lang('MakeCorrespond'));
|
||||
$form->addHtml($html);
|
||||
|
||||
if ($nb_matches < 1) {
|
||||
$nb_matches = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'), 'normal');
|
||||
}
|
||||
|
||||
$editorConfig = [
|
||||
'ToolbarSet' => 'TestMatching',
|
||||
'Width' => '100%',
|
||||
'Height' => '125',
|
||||
];
|
||||
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$renderer = &$form->defaultRenderer();
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"answer[$i]"
|
||||
);
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"matches[$i]"
|
||||
);
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"weighting[$i]"
|
||||
);
|
||||
|
||||
$form->addHtml('<tr>');
|
||||
$form->addHtml("<td>$i</td>");
|
||||
|
||||
//$form->addText("answer[$i]", null);
|
||||
$form->addHtmlEditor(
|
||||
"answer[$i]",
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
$editorConfig
|
||||
);
|
||||
|
||||
$form->addSelect("matches[$i]", null, $matches);
|
||||
if (MATCHING_DRAGGABLE === $this->type) {
|
||||
$form->addText("weighting[$i]", null, true, ['style' => 'width: 60px;', 'value' => 10]);
|
||||
} else {
|
||||
$form->addHidden("weighting[$i]", "0");
|
||||
}
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</tbody></table>');
|
||||
|
||||
// DISPLAY OPTIONS
|
||||
$html = '<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%">'.get_lang('Number').'</th>
|
||||
<th width="85%">'.get_lang('Answer').'</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
$form->addHtml($html);
|
||||
|
||||
if ($nb_options < 1) {
|
||||
$nb_options = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'), 'normal');
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $nb_options; $i++) {
|
||||
$renderer = &$form->defaultRenderer();
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"option[$i]"
|
||||
);
|
||||
|
||||
$form->addHtml('<tr>');
|
||||
$form->addHtml('<td>'.chr(64 + $i).'</td>');
|
||||
$form->addHtmlEditor(
|
||||
"option[$i]",
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
$editorConfig
|
||||
);
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</table>');
|
||||
|
||||
if (MATCHING_DRAGGABLE_COMBINATION === $this->type) {
|
||||
//only 1 answer the all deal ...
|
||||
$form->addText('questionWeighting', get_lang('Score'), true, ['value' => 10]);
|
||||
if (!empty($this->iid)) {
|
||||
$defaults['questionWeighting'] = $this->weighting;
|
||||
}
|
||||
}
|
||||
|
||||
global $text;
|
||||
$group = [];
|
||||
// setting the save button here and not in the question class.php
|
||||
$group[] = $form->addButtonDelete(get_lang('DelElem'), 'lessOptions', true);
|
||||
$group[] = $form->addButtonCreate(get_lang('AddElem'), 'moreOptions', true);
|
||||
$group[] = $form->addButtonSave($text, 'submitQuestion', true);
|
||||
$form->addGroup($group);
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
|
||||
$form->setConstants(
|
||||
[
|
||||
'nb_matches' => $nb_matches,
|
||||
'nb_options' => $nb_options,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$nb_matches = $form->getSubmitValue('nb_matches');
|
||||
$nb_options = $form->getSubmitValue('nb_options');
|
||||
$this->weighting = 0;
|
||||
$position = 0;
|
||||
$objAnswer = new Answer($this->iid);
|
||||
|
||||
// Insert the options
|
||||
for ($i = 1; $i <= $nb_options; $i++) {
|
||||
$position++;
|
||||
$option = $form->getSubmitValue("option[$i]");
|
||||
$objAnswer->createAnswer($option, 0, '', 0, $position);
|
||||
}
|
||||
|
||||
// Insert the answers
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$position++;
|
||||
$answer = $form->getSubmitValue("answer[$i]");
|
||||
$matches = $form->getSubmitValue("matches[$i]");
|
||||
$weighting = $form->getSubmitValue("weighting[$i]");
|
||||
$this->weighting += $weighting;
|
||||
|
||||
$objAnswer->createAnswer(
|
||||
$answer,
|
||||
$matches,
|
||||
'',
|
||||
$weighting,
|
||||
$position
|
||||
);
|
||||
}
|
||||
|
||||
if (MATCHING_DRAGGABLE_COMBINATION == $this->type) {
|
||||
$this->weighting = $form->getSubmitValue('questionWeighting');
|
||||
}
|
||||
|
||||
$objAnswer->save();
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="matching '.$this->question_table_class.'"><tr>';
|
||||
$header .= '<th>'.get_lang('ElementList').'</th>';
|
||||
if (!in_array($exercise->results_disabled, [
|
||||
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
|
||||
//RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
|
||||
])
|
||||
) {
|
||||
$header .= '<th>'.get_lang('YourChoice').'</th>';
|
||||
}
|
||||
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
} else {
|
||||
$header .= '<th>'.get_lang('CorrespondsTo').'</th>';
|
||||
}
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
21
main/exercise/MatchingDraggableCombination.php
Normal file
21
main/exercise/MatchingDraggableCombination.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* MatchingDraggableCombination.
|
||||
*/
|
||||
class MatchingDraggableCombination extends MatchingDraggable
|
||||
{
|
||||
public $typePicture = 'matchingdrag_co.png';
|
||||
public $explanationLangVar = 'MatchingDraggableCombination';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MATCHING_DRAGGABLE_COMBINATION;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
}
|
||||
138
main/exercise/MultipleAnswerDropdown.php
Normal file
138
main/exercise/MultipleAnswerDropdown.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
class MultipleAnswerDropdown extends Question
|
||||
{
|
||||
public $typePicture = 'mcma_dropdown.png';
|
||||
public $explanationLangVar = 'MultipleAnswerDropdown';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->type = MULTIPLE_ANSWER_DROPDOWN;
|
||||
}
|
||||
|
||||
public function createForm(&$form, $exercise)
|
||||
{
|
||||
global $text;
|
||||
|
||||
parent::createForm($form, $exercise);
|
||||
|
||||
$objExe = ChamiloSession::read('objExercise');
|
||||
|
||||
$form->addTextarea(
|
||||
'list_text',
|
||||
[get_lang('AnswerList'), get_lang('EnterListOfAnswersOneAnswerByLine')],
|
||||
['rows' => 8]
|
||||
);
|
||||
$form->addFile(
|
||||
'list_file',
|
||||
['', get_lang('OrSelectCsvFileWithListOfAnswers')],
|
||||
['accept' => 'text/csv']
|
||||
);
|
||||
|
||||
$buttonGroup = [];
|
||||
|
||||
if ($objExe->edit_exercise_in_lp == true ||
|
||||
(empty($this->exerciseList) && empty($objExe->iid))
|
||||
) {
|
||||
$buttonGroup[] = $form->addButton(
|
||||
'submitQuestion',
|
||||
$text,
|
||||
'check',
|
||||
'primary',
|
||||
'default',
|
||||
null,
|
||||
['id' => 'submit-question'],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$form->addGroup($buttonGroup);
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$objAnswer = new Answer($this->iid, 0, $exercise, false);
|
||||
$optionData = array_column(
|
||||
$objAnswer->getAnswers(),
|
||||
'answer'
|
||||
);
|
||||
|
||||
$form->setDefaults(
|
||||
['list_text' => implode(PHP_EOL, $optionData)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
}
|
||||
|
||||
public function processCreation($form, $exercise)
|
||||
{
|
||||
$listFile = $form->getSubmitValue('list_file');
|
||||
$listText = $form->getSubmitValue('list_text');
|
||||
|
||||
parent::processCreation($form, $exercise);
|
||||
|
||||
$lines = [];
|
||||
|
||||
if (UPLOAD_ERR_OK === (int) $listFile['error']) {
|
||||
$lines = Import::csvColumnToArray($listFile['tmp_name']);
|
||||
} elseif (!empty($listText)) {
|
||||
$lines = explode("\n", $listText);
|
||||
}
|
||||
|
||||
$lines = array_map('trim', $lines);
|
||||
$lines = array_filter($lines);
|
||||
|
||||
$objAnswer = new Answer($this->iid);
|
||||
|
||||
$i = 1;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$isCorrect = 0;
|
||||
|
||||
if (isset($objAnswer->correct[$i])) {
|
||||
$isCorrect = (int) $objAnswer->correct[$i];
|
||||
}
|
||||
|
||||
$objAnswer->createAnswer($line, $isCorrect, '', $objAnswer->weighting[$i] ?? 0, $i++);
|
||||
}
|
||||
|
||||
$objAnswer->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormValidator $form
|
||||
* @param Exercise $exercise
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
}
|
||||
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
|
||||
$header .= '<table class="'.$this->question_table_class.'"><thead><tr>';
|
||||
|
||||
$header .= '<th class="text-center">'.get_lang('Choice').'</th>';
|
||||
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th class="text-center">'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
|
||||
$header .= '<th>'.get_lang('Answer').'</th>';
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
$header .= '<th class="text-center">'.get_lang('Status').'</th>';
|
||||
}
|
||||
|
||||
$header .= '</tr></thead>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
18
main/exercise/MultipleAnswerDropdownCombination.php
Normal file
18
main/exercise/MultipleAnswerDropdownCombination.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* MultipleAnswerDropdownCombination.
|
||||
*/
|
||||
class MultipleAnswerDropdownCombination extends MultipleAnswerDropdown
|
||||
{
|
||||
public $typePicture = 'mcma_dropdown_co.png';
|
||||
public $explanationLangVar = 'MultipleAnswerDropdownCombination';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->type = MULTIPLE_ANSWER_DROPDOWN_COMBINATION;
|
||||
}
|
||||
}
|
||||
1340
main/exercise/MultipleAnswerTrueFalseDegreeCertainty.php
Normal file
1340
main/exercise/MultipleAnswerTrueFalseDegreeCertainty.php
Normal file
File diff suppressed because it is too large
Load Diff
251
main/exercise/ReadingComprehension.php
Normal file
251
main/exercise/ReadingComprehension.php
Normal file
@@ -0,0 +1,251 @@
|
||||
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ReadingComprehension.
|
||||
*
|
||||
* This class allows to instantiate an object of type READING_COMPREHENSION
|
||||
* extending the class question
|
||||
*/
|
||||
class ReadingComprehension extends UniqueAnswer
|
||||
{
|
||||
public $typePicture = 'reading-comprehension.png';
|
||||
public $explanationLangVar = 'ReadingComprehension';
|
||||
|
||||
/**
|
||||
* Defines the different speeds of scrolling for the reading window,
|
||||
* in words per minute. If 300 words text in 50w/m, then the moving
|
||||
* window will progress from top to bottom in 6 minutes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $speeds = [
|
||||
1 => 50,
|
||||
2 => 100,
|
||||
3 => 175,
|
||||
4 => 300,
|
||||
5 => 600,
|
||||
];
|
||||
|
||||
/**
|
||||
* The number of words in the question description (which serves as the
|
||||
* text to read).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $wordsCount = 0;
|
||||
|
||||
/**
|
||||
* Number of words expected to show per refresh.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $expectedWordsPerRefresh = 0;
|
||||
|
||||
/**
|
||||
* Refresh delay in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $refreshTime = 3;
|
||||
/**
|
||||
* All speeds (static $speeds + extra speeds defined in configuration.php as 'exercise_question_reading_comprehension_extra_speeds'.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $allSpeeds = [];
|
||||
|
||||
/**
|
||||
* Indicates how show the question list.
|
||||
* 1 = all in one page; 2 = one per page (default).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $exerciseType = 2;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = READING_COMPREHENSION;
|
||||
$this->isContent = $this->getIsContent();
|
||||
$extraSpeeds = api_get_configuration_value('exercise_question_reading_comprehension_extra_speeds');
|
||||
$customSpeeds = $extraSpeeds['speeds'] ?? [];
|
||||
$this->allSpeeds = self::$speeds;
|
||||
if (!empty($customSpeeds) && is_array($customSpeeds)) {
|
||||
foreach ($customSpeeds as $speed) {
|
||||
$this->allSpeeds[] = $speed;
|
||||
}
|
||||
asort($this->allSpeeds);
|
||||
}
|
||||
}
|
||||
|
||||
public function processText($text)
|
||||
{
|
||||
// Refresh is set to 5s, but speed is in words per minute
|
||||
$wordsPerSecond = $this->allSpeeds[$this->level] / 60;
|
||||
$this->expectedWordsPerRefresh = intval($wordsPerSecond * $this->refreshTime);
|
||||
|
||||
if (empty($text)) {
|
||||
// We have an issue here... how do we treat this case?
|
||||
// For now, let's define a default case
|
||||
$text = get_lang('NoExercise');
|
||||
}
|
||||
$words = str_word_count($text, 2, '0..9');
|
||||
$indexes = array_keys($words);
|
||||
|
||||
$tagEnd = '</span>';
|
||||
$tagStart = $tagEnd.'<span class="text-highlight">';
|
||||
$this->wordsCount = count($words);
|
||||
|
||||
$turns = ceil(
|
||||
$this->wordsCount / $this->expectedWordsPerRefresh
|
||||
);
|
||||
|
||||
$firstIndex = $indexes[0];
|
||||
|
||||
for ($i = 1; $i <= $turns; $i++) {
|
||||
$text = substr_replace($text, $tagStart, $firstIndex, 0);
|
||||
|
||||
if ($i * $this->expectedWordsPerRefresh <= count($words)) {
|
||||
$newIndex = $i * $this->expectedWordsPerRefresh;
|
||||
if (isset($indexes[$newIndex])) {
|
||||
$nextFirstIndex = $indexes[$newIndex];
|
||||
$firstIndex = $nextFirstIndex + (strlen($tagStart) * $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pos = strpos($text, $tagEnd);
|
||||
|
||||
$text = substr_replace($text, '', $pos, strlen($tagEnd));
|
||||
$text .= $tagEnd;
|
||||
|
||||
$this->displayReading($this->wordsCount, $turns, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns total count of words of the text to read.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWordsCount()
|
||||
{
|
||||
$words = str_word_count($this->selectDescription(), 2, '0..9');
|
||||
$this->wordsCount = count($words);
|
||||
|
||||
return $this->wordsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createForm(&$form, $exercise)
|
||||
{
|
||||
// Categories
|
||||
$tabCat = TestCategory::getCategoriesIdAndName();
|
||||
$form->addSelect('questionCategory', get_lang('Category'), $tabCat);
|
||||
// Advanced parameters
|
||||
$levels = $this->getReadingSpeeds();
|
||||
$form->addSelect('questionLevel', get_lang('Difficulty'), $levels);
|
||||
$form->addElement('hidden', 'answerType', READING_COMPREHENSION);
|
||||
$form->addTextarea('questionDescription', get_lang('Text'), ['rows' => 20]);
|
||||
// question name
|
||||
if (api_get_configuration_value('save_titles_as_html')) {
|
||||
$editorConfig = ['ToolbarSet' => 'TitleAsHtml'];
|
||||
$form->addHtmlEditor(
|
||||
'questionName',
|
||||
get_lang('Question'),
|
||||
false,
|
||||
false,
|
||||
$editorConfig,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
$form->addText('questionName', get_lang('Question'), false);
|
||||
}
|
||||
|
||||
// hidden values
|
||||
$form->addRule('questionName', get_lang('GiveQuestion'), 'required');
|
||||
$isContent = isset($_REQUEST['isContent']) ? (int) $_REQUEST['isContent'] : null;
|
||||
|
||||
// default values
|
||||
$defaults = [];
|
||||
$defaults['questionName'] = $this->question;
|
||||
$defaults['questionDescription'] = $this->description;
|
||||
$defaults['questionLevel'] = $this->level;
|
||||
$defaults['questionCategory'] = $this->category;
|
||||
|
||||
// Came from he question pool
|
||||
if (isset($_GET['fromExercise'])) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
|
||||
if (!isset($_GET['newQuestion']) || $isContent) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function get_default_levels()
|
||||
{
|
||||
return [
|
||||
1 => sprintf(get_lang('ReadingComprehensionLevelX'), self::$speeds[1]),
|
||||
2 => sprintf(get_lang('ReadingComprehensionLevelX'), self::$speeds[2]),
|
||||
3 => sprintf(get_lang('ReadingComprehensionLevelX'), self::$speeds[3]),
|
||||
4 => sprintf(get_lang('ReadingComprehensionLevelX'), self::$speeds[4]),
|
||||
5 => sprintf(get_lang('ReadingComprehensionLevelX'), self::$speeds[5]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the augmented speeds (using, if defined, the 'exercise_question_reading_comprehension_extra_speeds' setting.
|
||||
*/
|
||||
public function getReadingSpeeds(): array
|
||||
{
|
||||
$defaultLevels = [];
|
||||
foreach ($this->allSpeeds as $i => $v) {
|
||||
$defaultLevels[$i] = sprintf(get_lang('ReadingComprehensionLevelX'), $this->allSpeeds[$i]);
|
||||
}
|
||||
|
||||
return $defaultLevels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type
|
||||
*
|
||||
* @return ReadingComprehension
|
||||
*/
|
||||
public function setExerciseType($type)
|
||||
{
|
||||
$this->exerciseType = (int) $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $wordsCount
|
||||
* @param $turns
|
||||
* @param $text
|
||||
*/
|
||||
private function displayReading($wordsCount, $turns, $text)
|
||||
{
|
||||
$view = new Template('', false, false, false, true, false, false);
|
||||
|
||||
$template = $view->get_template('exercise/reading_comprehension.tpl');
|
||||
|
||||
$view->assign('id', $this->iid);
|
||||
$view->assign('text', nl2br($text));
|
||||
$view->assign('words_count', $wordsCount);
|
||||
$view->assign('turns', $turns);
|
||||
$view->assign('refresh_time', $this->refreshTime);
|
||||
$view->assign('exercise_type', $this->exerciseType);
|
||||
$view->display($template);
|
||||
}
|
||||
}
|
||||
1283
main/exercise/TestCategory.php
Normal file
1283
main/exercise/TestCategory.php
Normal file
File diff suppressed because it is too large
Load Diff
428
main/exercise/UniqueAnswerImage.php
Normal file
428
main/exercise/UniqueAnswerImage.php
Normal file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* UniqueAnswerImage.
|
||||
*
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*/
|
||||
class UniqueAnswerImage extends UniqueAnswer
|
||||
{
|
||||
public $typePicture = 'uaimg.png';
|
||||
public $explanationLangVar = 'UniqueAnswerImage';
|
||||
|
||||
/**
|
||||
* UniqueAnswerImage constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = UNIQUE_ANSWER_IMAGE;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$objExercise = Session::read('objExercise');
|
||||
$editorConfig = [
|
||||
'ToolbarSet' => 'TestProposedAnswer',
|
||||
'Width' => '100%',
|
||||
'Height' => '125',
|
||||
];
|
||||
|
||||
//this line defines how many questions by default appear when creating a choice question
|
||||
// The previous default value was 2. See task #1759.
|
||||
$numberAnswers = isset($_POST['nb_answers']) ? (int) $_POST['nb_answers'] : 4;
|
||||
$numberAnswers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
|
||||
|
||||
$feedbackTitle = '';
|
||||
switch ($objExercise->getFeedbackType()) {
|
||||
case EXERCISE_FEEDBACK_TYPE_DIRECT:
|
||||
// Scenario
|
||||
$commentTitle = '<th width="20%">'.get_lang('Comment').'</th>';
|
||||
$feedbackTitle = '<th width="20%">'.get_lang('Scenario').'</th>';
|
||||
break;
|
||||
case EXERCISE_FEEDBACK_TYPE_POPUP:
|
||||
$commentTitle = '<th width="20%">'.get_lang('Comment').'</th>';
|
||||
break;
|
||||
default:
|
||||
$commentTitle = '<th width="40%">'.get_lang('Comment').'</th>';
|
||||
break;
|
||||
}
|
||||
|
||||
$html = '<div class="alert alert-success" role="alert">'.
|
||||
get_lang('UniqueAnswerImagePreferredSize200x150').'</div>';
|
||||
|
||||
$zoomOptions = api_get_configuration_value('quiz_image_zoom');
|
||||
if (isset($zoomOptions['options'])) {
|
||||
$finderFolder = api_get_path(WEB_PATH).'vendor/studio-42/elfinder/';
|
||||
$html .= '<!-- elFinder CSS (REQUIRED) -->';
|
||||
$html .= '<link rel="stylesheet" type="text/css" media="screen"
|
||||
href="'.$finderFolder.'css/elfinder.full.css">';
|
||||
$html .= '<link rel="stylesheet" type="text/css" media="screen" href="'.$finderFolder.'css/theme.css">';
|
||||
$html .= '<!-- elFinder JS (REQUIRED) -->';
|
||||
$html .= '<script type="text/javascript" src="'.$finderFolder.'js/elfinder.full.js"></script>';
|
||||
$html .= '<!-- elFinder translation (OPTIONAL) -->';
|
||||
$language = 'en';
|
||||
$platformLanguage = api_get_interface_language();
|
||||
$iso = api_get_language_isocode($platformLanguage);
|
||||
$filePart = "vendor/studio-42/elfinder/js/i18n/elfinder.$iso.js";
|
||||
$file = api_get_path(SYS_PATH).$filePart;
|
||||
$includeFile = '';
|
||||
if (file_exists($file)) {
|
||||
$includeFile = '<script type="text/javascript" src="'.api_get_path(WEB_PATH).$filePart.'"></script>';
|
||||
$language = $iso;
|
||||
}
|
||||
$html .= $includeFile;
|
||||
|
||||
$html .= '<script type="text/javascript" charset="utf-8">
|
||||
$(function() {
|
||||
$(".add_img_link").on("click", function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var name = $(this).prop("name");
|
||||
var id = parseInt(name.match(/[0-9]+/));
|
||||
|
||||
$([document.documentElement, document.body]).animate({
|
||||
scrollTop: $("#elfinder").offset().top
|
||||
}, 1000);
|
||||
|
||||
var elf = $("#elfinder").elfinder({
|
||||
url : "'.api_get_path(WEB_LIBRARY_PATH).'elfinder/connectorAction.php?'.api_get_cidreq().'",
|
||||
getFileCallback: function(file) {
|
||||
var filePath = file; //file contains the relative url.
|
||||
var imageZoom = filePath.url;
|
||||
var iname = "answer["+id+"]";
|
||||
|
||||
CKEDITOR.instances[iname].insertHtml(\'
|
||||
<img
|
||||
id="zoom_picture"
|
||||
class="zoom_picture"
|
||||
src="\'+imageZoom+\'"
|
||||
data-zoom-image="\'+imageZoom+\'"
|
||||
width="200px"
|
||||
height="150px"
|
||||
/>\');
|
||||
|
||||
$("#elfinder").elfinder("destroy"); //close the window after image is selected
|
||||
},
|
||||
startPathHash: "l2_Lw", // Sets the course driver as default
|
||||
resizable: false,
|
||||
lang: "'.$language.'"
|
||||
}).elfinder("instance"+id);
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
$html .= '<div id="elfinder"></div>';
|
||||
}
|
||||
|
||||
$html .= '<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr style="text-align: center;">
|
||||
<th width="10">'.get_lang('Number').'</th>
|
||||
<th>'.get_lang('True').'</th>
|
||||
<th>'.get_lang('Answer').'</th>
|
||||
'.$commentTitle.'
|
||||
'.$feedbackTitle.'
|
||||
<th width="15">'.get_lang('Weighting').'</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
$form->addHeader(get_lang('Answers'));
|
||||
$form->addHtml($html);
|
||||
|
||||
$defaults = [];
|
||||
$correct = 0;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
|
||||
if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) {
|
||||
$numberAnswers = $answer->nbrAnswers;
|
||||
}
|
||||
}
|
||||
|
||||
$form->addElement('hidden', 'nb_answers');
|
||||
|
||||
//Feedback SELECT
|
||||
$questionList = $objExercise->selectQuestionList();
|
||||
$selectQuestion = [];
|
||||
$selectQuestion[0] = get_lang('SelectTargetQuestion');
|
||||
|
||||
if (is_array($questionList)) {
|
||||
foreach ($questionList as $key => $questionid) {
|
||||
//To avoid warning messages
|
||||
if (!is_numeric($questionid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$question = Question::read($questionid);
|
||||
$questionTitle = strip_tags($question->selectTitle());
|
||||
$selectQuestion[$questionid] = "Q$key: $questionTitle";
|
||||
}
|
||||
}
|
||||
|
||||
$selectQuestion[-1] = get_lang('ExitTest');
|
||||
|
||||
$list = new LearnpathList(api_get_user_id());
|
||||
$flatList = $list->get_flat_list();
|
||||
$selectLpId = [];
|
||||
$selectLpId[0] = get_lang('SelectTargetLP');
|
||||
|
||||
foreach ($flatList as $id => $details) {
|
||||
$selectLpId[$id] = cut($details['lp_name'], 20);
|
||||
}
|
||||
|
||||
$tempScenario = [];
|
||||
if ($numberAnswers < 1) {
|
||||
$numberAnswers = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'));
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $numberAnswers; $i++) {
|
||||
$form->addHtml('<tr>');
|
||||
if (isset($answer) && is_object($answer)) {
|
||||
if (isset($answer->correct[$i]) && $answer->correct[$i]) {
|
||||
$correct = $i;
|
||||
}
|
||||
|
||||
$defaults['answer['.$i.']'] = $answer->answer[$i] ?? '';
|
||||
$defaults['comment['.$i.']'] = $answer->comment[$i] ?? '';
|
||||
$defaults['weighting['.$i.']'] = isset($answer->weighting[$i]) ? float_format($answer->weighting[$i], 1) : 0;
|
||||
|
||||
$itemList = [];
|
||||
if (isset($answer->destination[$i])) {
|
||||
$itemList = explode('@@', $answer->destination[$i]);
|
||||
}
|
||||
|
||||
$try = $itemList[0] ?? '';
|
||||
$lp = $itemList[1] ?? '';
|
||||
$listDestination = $itemList[2] ?? '';
|
||||
$url = $itemList[3] ?? '';
|
||||
|
||||
$tryResult = 0;
|
||||
if (0 != $try) {
|
||||
$tryResult = 1;
|
||||
}
|
||||
|
||||
$urlResult = '';
|
||||
if (0 != $url) {
|
||||
$urlResult = $url;
|
||||
}
|
||||
|
||||
$tempScenario['url'.$i] = $urlResult;
|
||||
$tempScenario['try'.$i] = $tryResult;
|
||||
$tempScenario['lp'.$i] = $lp;
|
||||
$tempScenario['destination'.$i] = $listDestination;
|
||||
} else {
|
||||
$defaults['answer[1]'] = get_lang('DefaultUniqueAnswer1');
|
||||
$defaults['weighting[1]'] = 10;
|
||||
$defaults['answer[2]'] = get_lang('DefaultUniqueAnswer2');
|
||||
$defaults['weighting[2]'] = 0;
|
||||
|
||||
$tempScenario['destination'.$i] = ['0'];
|
||||
$tempScenario['lp'.$i] = ['0'];
|
||||
}
|
||||
|
||||
$defaults['scenario'] = $tempScenario;
|
||||
$renderer = $form->defaultRenderer();
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'correct'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'counter['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}'.
|
||||
(isset($zoomOptions['options']) ?
|
||||
'<br><div class="form-group ">
|
||||
<label for="question_admin_form_btn_add_img['.$i.']" class="col-sm-2 control-label"></label>
|
||||
<div class="col-sm-8">
|
||||
<button class="add_img_link btn btn-info btn-sm"
|
||||
name="btn_add_img['.$i.']"
|
||||
type="submit"
|
||||
id="question_admin_form_btn_add_img['.$i.']">
|
||||
<em class="fa fa-plus"></em> '.get_lang('AddImageWithZoom').'
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-2"></div>
|
||||
</div>' : '').'</td>',
|
||||
'answer['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'comment['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'weighting['.$i.']'
|
||||
);
|
||||
|
||||
$answerNumber = $form->addElement('text', 'counter['.$i.']', null, ' value = "'.$i.'"');
|
||||
$answerNumber->freeze();
|
||||
|
||||
$form->addElement('radio', 'correct', null, null, $i, 'class="checkbox"');
|
||||
$form->addHtmlEditor('answer['.$i.']', null, null, false, $editorConfig);
|
||||
|
||||
$form->addRule('answer['.$i.']', get_lang('ThisFieldIsRequired'), 'required');
|
||||
|
||||
switch ($objExercise->getFeedbackType()) {
|
||||
case EXERCISE_FEEDBACK_TYPE_DIRECT:
|
||||
$this->setDirectOptions($i, $form, $renderer, $selectLpId, $selectQuestion);
|
||||
break;
|
||||
case EXERCISE_FEEDBACK_TYPE_POPUP:
|
||||
default:
|
||||
$form->addHtmlEditor('comment['.$i.']', null, null, false, $editorConfig);
|
||||
break;
|
||||
}
|
||||
|
||||
$form->addText('weighting['.$i.']', null, null, ['class' => 'col-md-1', 'value' => '0']);
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</tbody>');
|
||||
$form->addHtml('</table>');
|
||||
|
||||
global $text;
|
||||
$buttonGroup = [];
|
||||
if ($objExercise->edit_exercise_in_lp == true ||
|
||||
(empty($this->exerciseList) && empty($objExercise->iid))
|
||||
) {
|
||||
//setting the save button here and not in the question class.php
|
||||
$buttonGroup[] = $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true);
|
||||
$buttonGroup[] = $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true);
|
||||
$buttonGroup[] = $form->addButtonSave($text, 'submitQuestion', true);
|
||||
$form->addGroup($buttonGroup);
|
||||
}
|
||||
|
||||
// We check the first radio button to be sure a radio button will be check
|
||||
if (0 == $correct) {
|
||||
$correct = 1;
|
||||
}
|
||||
|
||||
$defaults['correct'] = $correct;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
if (1 == $this->isContent) {
|
||||
// Default sample content.
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
$form->setDefaults(['correct' => 1]);
|
||||
}
|
||||
}
|
||||
|
||||
$form->setConstants(['nb_answers' => $numberAnswers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$questionWeighting = $nbrGoodAnswers = 0;
|
||||
$correct = $form->getSubmitValue('correct');
|
||||
$objAnswer = new Answer($this->iid);
|
||||
$numberAnswers = $form->getSubmitValue('nb_answers');
|
||||
|
||||
for ($i = 1; $i <= $numberAnswers; $i++) {
|
||||
$answer = trim(str_replace(['<p>', '</p>'], '', $form->getSubmitValue('answer['.$i.']')));
|
||||
$comment = trim(str_replace(['<p>', '</p>'], '', $form->getSubmitValue('comment['.$i.']')));
|
||||
$weighting = trim($form->getSubmitValue('weighting['.$i.']'));
|
||||
|
||||
$scenario = $form->getSubmitValue('scenario');
|
||||
|
||||
$try = null;
|
||||
$lp = null;
|
||||
$destination = null;
|
||||
$url = null;
|
||||
//$listDestination = $form -> getSubmitValue('destination'.$i);
|
||||
//$destinationStr = $form -> getSubmitValue('destination'.$i);
|
||||
if (!empty($scenario)) {
|
||||
$try = $scenario['try'.$i];
|
||||
$lp = $scenario['lp'.$i];
|
||||
$destination = $scenario['destination'.$i];
|
||||
$url = trim($scenario['url'.$i]);
|
||||
}
|
||||
|
||||
/*
|
||||
How we are going to parse the destination value
|
||||
|
||||
here we parse the destination value which is a string
|
||||
1@@3@@2;4;4;@@http://www.chamilo.org
|
||||
|
||||
where: try_again@@lp_id@@selected_questions@@url
|
||||
|
||||
try_again = is 1 || 0
|
||||
lp_id = id of a learning path (0 if dont select)
|
||||
selected_questions= ids of questions
|
||||
url= an url
|
||||
|
||||
$destinationStr='';
|
||||
foreach ($listDestination as $destination_id)
|
||||
{
|
||||
$destinationStr.=$destination_id.';';
|
||||
} */
|
||||
$goodAnswer = $correct == $i ? true : false;
|
||||
if ($goodAnswer) {
|
||||
$nbrGoodAnswers++;
|
||||
$weighting = abs($weighting);
|
||||
|
||||
if ($weighting > 0) {
|
||||
$questionWeighting += $weighting;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($try)) {
|
||||
$try = 0;
|
||||
}
|
||||
|
||||
if (empty($lp)) {
|
||||
$lp = 0;
|
||||
}
|
||||
|
||||
if (empty($destination)) {
|
||||
$destination = 0;
|
||||
}
|
||||
|
||||
if ($url == '') {
|
||||
$url = 0;
|
||||
}
|
||||
|
||||
//1@@1;2;@@2;4;4;@@http://www.chamilo.org
|
||||
$dest = $try.'@@'.$lp.'@@'.$destination.'@@'.$url;
|
||||
|
||||
$objAnswer->createAnswer(
|
||||
$answer,
|
||||
$goodAnswer,
|
||||
$comment,
|
||||
$weighting,
|
||||
$i,
|
||||
null,
|
||||
null,
|
||||
$dest
|
||||
);
|
||||
}
|
||||
|
||||
// saves the answers into the data base
|
||||
$objAnswer->save();
|
||||
|
||||
// sets the total weighting of the question
|
||||
$this->updateWeighting($questionWeighting);
|
||||
$this->save($exercise);
|
||||
}
|
||||
}
|
||||
64
main/exercise/UploadAnswer.php
Normal file
64
main/exercise/UploadAnswer.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Question with file upload, where the file is the answer.
|
||||
* Acts as an open question: requires teacher's review for a score.
|
||||
*/
|
||||
class UploadAnswer extends Question
|
||||
{
|
||||
public $typePicture = 'file_upload_question.png';
|
||||
public $explanationLangVar = 'UploadAnswer';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = UPLOAD_ANSWER;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$form->addElement('text', 'weighting', get_lang('Weighting'));
|
||||
global $text;
|
||||
// setting the save button here and not in the question class.php
|
||||
$form->addButtonSave($text, 'submitQuestion');
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults(['weighting' => float_format($this->weighting, 1)]);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults(['weighting' => '10']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$this->weighting = $form->getSubmitValue('weighting');
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$score['revised'] = $this->isQuestionWaitingReview($score);
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'" >
|
||||
<tr>
|
||||
<th>'.get_lang('Answer').'</th>
|
||||
</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
506
main/exercise/admin.php
Normal file
506
main/exercise/admin.php
Normal file
@@ -0,0 +1,506 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Exercise administration
|
||||
* This script allows to manage (create, modify) an exercise and its questions.
|
||||
*
|
||||
* The following scripts are included :
|
||||
*
|
||||
* - exercise.class.php : for the creation of an Exercise object
|
||||
* - question.class.php : for the creation of a Question object
|
||||
* - answer.class.php : for the creation of an Answer object
|
||||
* - exercise.lib.php : functions used in the exercise tool
|
||||
* - exercise_admin.inc.php : management of the exercise
|
||||
* - question_admin.inc.php : management of a question (statement & answers)
|
||||
* - statement_admin.inc.php : management of a statement
|
||||
* - question_list_admin.inc.php : management of the question list
|
||||
*
|
||||
* Main variables used in this script :
|
||||
*
|
||||
* - $is_allowedToEdit : set to 1 if the user is allowed to manage the exercise
|
||||
* - $objExercise : exercise object
|
||||
* - $objQuestion : question object
|
||||
* - $objAnswer : answer object
|
||||
* - $exerciseId : the exercise ID
|
||||
* - $picturePath : the path of question pictures
|
||||
* - $newQuestion : ask to create a new question
|
||||
* - $modifyQuestion : ID of the question to modify
|
||||
* - $editQuestion : ID of the question to edit
|
||||
* - $submitQuestion : ask to save question modifications
|
||||
* - $cancelQuestion : ask to cancel question modifications
|
||||
* - $deleteQuestion : ID of the question to delete
|
||||
* - $moveUp : ID of the question to move up
|
||||
* - $moveDown : ID of the question to move down
|
||||
* - $modifyExercise : ID of the exercise to modify
|
||||
* - $submitExercise : ask to save exercise modifications
|
||||
* - $cancelExercise : ask to cancel exercise modifications
|
||||
* - $modifyAnswers : ID of the question which we want to modify answers for
|
||||
* - $cancelAnswers : ask to cancel answer modifications
|
||||
* - $buttonBack : ask to go back to the previous page in answers of type "Fill in blanks"
|
||||
*
|
||||
* @author Olivier Brouckaert
|
||||
* Modified by Hubert Borderiou 21-10-2011 Question by category
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
if (isset($_GET['r']) && 1 == $_GET['r']) {
|
||||
Exercise::cleanSessionVariables();
|
||||
}
|
||||
|
||||
// Access control
|
||||
api_protect_course_script(true);
|
||||
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true, false, false);
|
||||
$sessionId = api_get_session_id();
|
||||
$studentViewActive = api_is_student_view_active();
|
||||
$showPagination = api_get_configuration_value('show_question_pagination');
|
||||
|
||||
if (!$is_allowedToEdit) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
// Variables sanitizing
|
||||
$exerciseId = isset($_GET['exerciseId']) ? (int) $_GET['exerciseId'] : 0;
|
||||
|
||||
$newQuestion = isset($_GET['newQuestion']) ? $_GET['newQuestion'] : 0;
|
||||
$modifyAnswers = isset($_GET['modifyAnswers']) ? $_GET['modifyAnswers'] : 0;
|
||||
$editQuestion = isset($_GET['editQuestion']) ? $_GET['editQuestion'] : 0;
|
||||
$page = isset($_GET['page']) && !empty($_GET['page']) ? (int) $_GET['page'] : 1;
|
||||
$modifyQuestion = isset($_GET['modifyQuestion']) ? $_GET['modifyQuestion'] : 0;
|
||||
$deleteQuestion = isset($_GET['deleteQuestion']) ? $_GET['deleteQuestion'] : 0;
|
||||
$clone_question = isset($_REQUEST['clone_question']) ? $_REQUEST['clone_question'] : 0;
|
||||
if (empty($questionId)) {
|
||||
$questionId = Session::read('questionId');
|
||||
}
|
||||
if (empty($modifyExercise)) {
|
||||
$modifyExercise = isset($_GET['modifyExercise']) ? $_GET['modifyExercise'] : null;
|
||||
}
|
||||
|
||||
$fromExercise = isset($fromExercise) ? $fromExercise : null;
|
||||
$cancelExercise = isset($cancelExercise) ? $cancelExercise : null;
|
||||
$cancelAnswers = isset($cancelAnswers) ? $cancelAnswers : null;
|
||||
$modifyIn = isset($modifyIn) ? $modifyIn : null;
|
||||
$cancelQuestion = isset($cancelQuestion) ? $cancelQuestion : null;
|
||||
|
||||
/* Cleaning all incomplete attempts of the admin/teacher to avoid weird problems
|
||||
when changing the exercise settings, number of questions, etc */
|
||||
Event::delete_all_incomplete_attempts(
|
||||
api_get_user_id(),
|
||||
$exerciseId,
|
||||
api_get_course_int_id(),
|
||||
api_get_session_id()
|
||||
);
|
||||
|
||||
// get from session
|
||||
$objExercise = Session::read('objExercise');
|
||||
$objQuestion = Session::read('objQuestion');
|
||||
|
||||
if (isset($_REQUEST['convertAnswer']) || isset($_REQUEST['convertAnswerAlt'])) {
|
||||
$objQuestion = $objQuestion->swapSimpleAnswerTypes(
|
||||
isset($_REQUEST['convertAnswerAlt']) ? 1 : 0
|
||||
);
|
||||
Session::write('objQuestion', $objQuestion);
|
||||
}
|
||||
$objAnswer = Session::read('objAnswer');
|
||||
$_course = api_get_course_info();
|
||||
|
||||
// document path
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
|
||||
// picture path
|
||||
$picturePath = $documentPath.'/images';
|
||||
|
||||
// audio path
|
||||
$audioPath = $documentPath.'/audio';
|
||||
|
||||
if (!empty($_GET['action']) && $_GET['action'] == 'exportqti2' && !empty($_GET['questionId'])) {
|
||||
require_once 'export/qti2/qti2_export.php';
|
||||
$export = export_question_qti($_GET['questionId'], true);
|
||||
$qid = (int) $_GET['questionId'];
|
||||
$archive_path = api_get_path(SYS_ARCHIVE_PATH);
|
||||
$temp_dir_short = uniqid();
|
||||
$temp_zip_dir = $archive_path."/".$temp_dir_short;
|
||||
if (!is_dir($temp_zip_dir)) {
|
||||
mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
|
||||
}
|
||||
$temp_zip_file = $temp_zip_dir."/".api_get_unique_id().".zip";
|
||||
$temp_xml_file = $temp_zip_dir."/qti2export_".$qid.'.xml';
|
||||
file_put_contents($temp_xml_file, $export);
|
||||
$zip_folder = new PclZip($temp_zip_file);
|
||||
$zip_folder->add($temp_xml_file, PCLZIP_OPT_REMOVE_ALL_PATH);
|
||||
$name = 'qti2_export_'.$qid.'.zip';
|
||||
|
||||
DocumentManager::file_send_for_download($temp_zip_file, true, $name);
|
||||
unlink($temp_zip_file);
|
||||
unlink($temp_xml_file);
|
||||
rmdir($temp_zip_dir);
|
||||
exit; //otherwise following clicks may become buggy
|
||||
}
|
||||
|
||||
// Exercise object creation.
|
||||
if (!($objExercise instanceof Exercise)) {
|
||||
// creation of a new exercise if wrong or not specified exercise ID
|
||||
if ($exerciseId) {
|
||||
$objExercise = new Exercise();
|
||||
$parseQuestionList = $showPagination > 0 ? false : true;
|
||||
if ($editQuestion) {
|
||||
$parseQuestionList = false;
|
||||
$showPagination = true;
|
||||
}
|
||||
$objExercise->read($exerciseId, $parseQuestionList);
|
||||
Session::write('objExercise', $objExercise);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($objExercise)) {
|
||||
Session::erase('objExercise');
|
||||
header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
// Exercise can be edited in their course.
|
||||
if ($objExercise->sessionId != $sessionId) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
// Do not select the exercise ID if we come from the question pool
|
||||
if (!$fromExercise) {
|
||||
// gets the right exercise ID, and if 0 creates a new exercise
|
||||
if (!$exerciseId = $objExercise->selectId()) {
|
||||
$modifyExercise = 'yes';
|
||||
}
|
||||
}
|
||||
|
||||
$nbrQuestions = $objExercise->getQuestionCount();
|
||||
|
||||
// Question object creation.
|
||||
if ($editQuestion || $newQuestion || $modifyQuestion || $modifyAnswers) {
|
||||
if ($editQuestion || $newQuestion) {
|
||||
// reads question data
|
||||
if ($editQuestion) {
|
||||
// question not found
|
||||
if (!$objQuestion = Question::read($editQuestion)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
// saves the object into the session
|
||||
Session::write('objQuestion', $objQuestion);
|
||||
}
|
||||
}
|
||||
|
||||
// checks if the object exists
|
||||
if (is_object($objQuestion)) {
|
||||
// gets the question ID
|
||||
$questionId = $objQuestion->selectId();
|
||||
}
|
||||
}
|
||||
|
||||
// if cancelling an exercise
|
||||
if ($cancelExercise) {
|
||||
// existing exercise
|
||||
if ($exerciseId) {
|
||||
unset($modifyExercise);
|
||||
} else {
|
||||
// new exercise
|
||||
// goes back to the exercise list
|
||||
header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// if cancelling question creation/modification
|
||||
if ($cancelQuestion) {
|
||||
// if we are creating a new question from the question pool
|
||||
if (!$exerciseId && !$questionId) {
|
||||
// goes back to the question pool
|
||||
header('Location: question_pool.php?'.api_get_cidreq());
|
||||
exit();
|
||||
} else {
|
||||
// goes back to the question viewing
|
||||
$editQuestion = $modifyQuestion;
|
||||
unset($newQuestion, $modifyQuestion);
|
||||
}
|
||||
}
|
||||
|
||||
// Cloning/copying a question
|
||||
if (!empty($clone_question) && !empty($objExercise->iid)) {
|
||||
$old_question_obj = Question::read($clone_question);
|
||||
$old_question_obj->question = $old_question_obj->question.' - '.get_lang('Copy');
|
||||
|
||||
$new_id = $old_question_obj->duplicate(api_get_course_info());
|
||||
$new_question_obj = Question::read($new_id);
|
||||
$new_question_obj->addToList($exerciseId);
|
||||
|
||||
// Save category to the destination course
|
||||
if (!empty($old_question_obj->category)) {
|
||||
$new_question_obj->saveCategory($old_question_obj->category, api_get_course_int_id());
|
||||
}
|
||||
|
||||
// This should be moved to the duplicate function
|
||||
$new_answer_obj = new Answer($clone_question);
|
||||
$new_answer_obj->read();
|
||||
$new_answer_obj->duplicate($new_question_obj);
|
||||
|
||||
// Reloading tne $objExercise obj
|
||||
$objExercise->read($objExercise->iid, false);
|
||||
|
||||
Display::addFlash(Display::return_message(get_lang('ItemCopied')));
|
||||
|
||||
header('Location: admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid.'&page='.$page);
|
||||
exit;
|
||||
}
|
||||
|
||||
// if cancelling answer creation/modification
|
||||
if ($cancelAnswers) {
|
||||
// goes back to the question viewing
|
||||
$editQuestion = $modifyAnswers;
|
||||
unset($modifyAnswers);
|
||||
}
|
||||
|
||||
$nameTools = '';
|
||||
// modifies the query string that is used in the link of tool name
|
||||
if ($editQuestion || $modifyQuestion || $newQuestion || $modifyAnswers) {
|
||||
$nameTools = get_lang('QuestionManagement');
|
||||
}
|
||||
|
||||
if (api_is_in_gradebook()) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => Category::getUrl(),
|
||||
'name' => get_lang('ToolGradebook'),
|
||||
];
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
|
||||
if (isset($_GET['newQuestion']) || isset($_GET['editQuestion'])) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'admin.php?exerciseId='.$objExercise->iid.'&'.api_get_cidreq(),
|
||||
'name' => Security::remove_XSS($objExercise->selectTitle(true)),
|
||||
];
|
||||
} else {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => '#',
|
||||
'name' => Security::remove_XSS($objExercise->selectTitle(true)),
|
||||
];
|
||||
}
|
||||
|
||||
// shows a link to go back to the question pool
|
||||
if (!$exerciseId && $nameTools != get_lang('ExerciseManagement')) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => api_get_path(WEB_CODE_PATH)."exercise/question_pool.php?fromExercise=$fromExercise&".api_get_cidreq(),
|
||||
'name' => get_lang('QuestionPool'),
|
||||
];
|
||||
}
|
||||
|
||||
// if the question is duplicated, disable the link of tool name
|
||||
if ($modifyIn === 'thisExercise') {
|
||||
if (!empty($buttonBack)) {
|
||||
$modifyIn = 'allExercises';
|
||||
}
|
||||
}
|
||||
|
||||
$htmlHeadXtra[] = api_get_js('jqueryui-touch-punch/jquery.ui.touch-punch.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('jquery.jsPlumb.all.js');
|
||||
|
||||
$template = new Template();
|
||||
$templateName = $template->get_template('exercise/submit.js.tpl');
|
||||
$htmlHeadXtra[] = $template->fetch($templateName);
|
||||
$htmlHeadXtra[] = api_get_js('d3/jquery.xcolor.js');
|
||||
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
|
||||
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
|
||||
|
||||
if (isset($_GET['message'])) {
|
||||
if (in_array($_GET['message'], ['ExerciseStored', 'ItemUpdated', 'ItemAdded'])) {
|
||||
Display::addFlash(Display::return_message(get_lang($_GET['message']), 'confirmation'));
|
||||
}
|
||||
}
|
||||
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
|
||||
// If we are in a test
|
||||
$inATest = isset($exerciseId) && $exerciseId > 0;
|
||||
|
||||
if ($inATest) {
|
||||
echo '<div class="actions">';
|
||||
if (isset($_GET['hotspotadmin']) || isset($_GET['mad_admin']) || isset($_GET['newQuestion'])) {
|
||||
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
}
|
||||
|
||||
if (!isset($_GET['hotspotadmin']) && !isset($_GET['mad_admin']) && !isset($_GET['newQuestion']) && !isset($_GET['editQuestion'])) {
|
||||
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
}
|
||||
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid.'&preview=1">'.
|
||||
Display::return_icon('preview_view.png', get_lang('Preview'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
|
||||
echo Display::url(
|
||||
Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_MEDIUM),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid
|
||||
);
|
||||
|
||||
echo '<a
|
||||
href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->iid.'">'.
|
||||
Display::return_icon('settings.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
|
||||
// Get the cumulative max score of all questions to show it in the upper bar to the editor
|
||||
$maxScoreAllQuestions = 0;
|
||||
if ($showPagination === false) {
|
||||
$questionList = $objExercise->selectQuestionList(true, $objExercise->random > 0 ? false : true);
|
||||
if (!empty($questionList)) {
|
||||
foreach ($questionList as $questionItemId) {
|
||||
$question = Question::read($questionItemId);
|
||||
if ($question) {
|
||||
$maxScoreAllQuestions += $question->selectWeighting();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
if ($objExercise->added_in_lp()) {
|
||||
echo Display::return_message(get_lang('AddedToLPCannotBeAccessed'), 'warning');
|
||||
}
|
||||
// If the question is used in another test, show a warning to the editor
|
||||
if ($editQuestion && $objQuestion->existsInAnotherExercise()) {
|
||||
echo Display::return_message(
|
||||
Display::returnFontAwesomeIcon('exclamation-triangle"')
|
||||
.get_lang('ThisQuestionExistsInAnotherExercisesWarning'),
|
||||
'warning',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
$alert = '';
|
||||
|
||||
if ($showPagination === false) {
|
||||
$originalSelectionType = $objExercise->questionSelectionType;
|
||||
$objExercise->questionSelectionType = EX_Q_SELECTION_ORDERED;
|
||||
|
||||
$outMaxScore = 0;
|
||||
$outMaxScore = array_reduce(
|
||||
$objExercise->selectQuestionList(true, true),
|
||||
function ($acc, $questionId) {
|
||||
$objQuestionTmp = Question::read($questionId);
|
||||
|
||||
return $acc + $objQuestionTmp->selectWeighting();
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
$objExercise->questionSelectionType = $originalSelectionType;
|
||||
|
||||
$alert .= sprintf(
|
||||
get_lang('XQuestionsWithTotalScoreY'),
|
||||
$nbrQuestions,
|
||||
$outMaxScore
|
||||
);
|
||||
}
|
||||
if ($objExercise->random > 0) {
|
||||
$alert .= '<br />'.sprintf(get_lang('OnlyXQuestionsPickedRandomly'), $objExercise->random);
|
||||
$alert .= sprintf(
|
||||
'<br>'.get_lang('XQuestionsSelectedWithTotalScoreY'),
|
||||
$objExercise->random,
|
||||
$maxScoreAllQuestions
|
||||
);
|
||||
}
|
||||
|
||||
if ($showPagination === false) {
|
||||
if ($objExercise->questionSelectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED) {
|
||||
$alert .= sprintf(
|
||||
'<br>'.get_lang('XQuestionsSelectedWithTotalScoreY'),
|
||||
count($questionList),
|
||||
$maxScoreAllQuestions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
echo Display::return_message($alert, 'normal', false);
|
||||
} elseif (isset($_GET['newQuestion'])) {
|
||||
// we are in create a new question from question pool not in a test
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
// If we are in question_pool but not in an test, go back to question create in pool
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/question_pool.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).
|
||||
'</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
if ($newQuestion || $editQuestion) {
|
||||
// Question management
|
||||
$type = isset($_REQUEST['answerType']) ? Security::remove_XSS($_REQUEST['answerType']) : null;
|
||||
echo '<input type="hidden" name="Type" value="'.$type.'" />';
|
||||
|
||||
if ($newQuestion === 'yes') {
|
||||
$objExercise->edit_exercise_in_lp = true;
|
||||
require 'question_admin.inc.php';
|
||||
}
|
||||
if ($editQuestion) {
|
||||
// Question preview if teacher clicked the "switch to student"
|
||||
if ($studentViewActive && $is_allowedToEdit) {
|
||||
echo '<div class="main-question">';
|
||||
echo Display::div(Security::remove_XSS($objQuestion->selectTitle()), ['class' => 'question_title']);
|
||||
ExerciseLib::showQuestion(
|
||||
$objExercise,
|
||||
$editQuestion,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
echo '</div>';
|
||||
} else {
|
||||
require 'question_admin.inc.php';
|
||||
ExerciseLib::showTestsWhereQuestionIsUsed($objQuestion->iid, $objExercise->selectId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['hotspotadmin'])) {
|
||||
if (!is_object($objQuestion)) {
|
||||
$objQuestion = Question::read($_GET['hotspotadmin']);
|
||||
}
|
||||
if (!$objQuestion) {
|
||||
api_not_allowed();
|
||||
}
|
||||
require 'hotspot_admin.inc.php';
|
||||
}
|
||||
|
||||
if (isset($_GET['mad_admin'])) {
|
||||
if (!is_object($objQuestion)) {
|
||||
$objQuestion = Question::read($_GET['mad_admin']);
|
||||
}
|
||||
if (!$objQuestion) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
require 'multiple_answer_dropdown_admin.php';
|
||||
}
|
||||
|
||||
if (!$newQuestion && !$modifyQuestion && !$editQuestion && !isset($_GET['hotspotadmin']) && !isset($_GET['mad_admin'])) {
|
||||
// question list management
|
||||
require 'question_list_admin.inc.php';
|
||||
}
|
||||
|
||||
// if we are in question authoring, display warning to user is feedback not shown at the end of the test -ref #6619
|
||||
// this test to display only message in the question authoring page and not in the question list page too
|
||||
if ($objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_EXAM) {
|
||||
echo Display::return_message(get_lang('TestFeedbackNotShown'), 'normal');
|
||||
}
|
||||
|
||||
Session::write('objQuestion', $objQuestion);
|
||||
Session::write('objAnswer', $objAnswer);
|
||||
Display::display_footer();
|
||||
93
main/exercise/adminhp.php
Normal file
93
main/exercise/adminhp.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* HotPotatoes administration.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Istvan Mandak
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$_course = api_get_course_info();
|
||||
|
||||
if (isset($_REQUEST["cancel"])) {
|
||||
if ($_REQUEST["cancel"] == get_lang('Cancel')) {
|
||||
header("Location: exercise.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$newName = !empty($_REQUEST['newName']) ? $_REQUEST['newName'] : '';
|
||||
$hotpotatoesName = !empty($_REQUEST['hotpotatoesName']) ? Security::remove_XSS($_REQUEST['hotpotatoesName']) : '';
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
|
||||
// document path
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
|
||||
// picture path
|
||||
$picturePath = $documentPath.'/images';
|
||||
|
||||
// audio path
|
||||
$audioPath = $documentPath.'/audio';
|
||||
|
||||
// Database table definitions
|
||||
if (!$is_allowedToEdit) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
if (api_is_in_gradebook()) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => Category::getUrl(),
|
||||
'name' => get_lang('ToolGradebook'),
|
||||
];
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
"url" => "exercise.php",
|
||||
"name" => get_lang('Exercises'),
|
||||
];
|
||||
$nameTools = get_lang('adminHP');
|
||||
|
||||
Display::display_header($nameTools, "Exercise");
|
||||
|
||||
/** @todo probably wrong !!!! */
|
||||
require_once api_get_path(SYS_CODE_PATH).'/exercise/hotpotatoes.lib.php';
|
||||
?>
|
||||
<h4>
|
||||
<?php echo $nameTools; ?>
|
||||
</h4>
|
||||
<?php
|
||||
if (isset($newName)) {
|
||||
if ($newName != "") {
|
||||
//alter database record for that test
|
||||
SetComment($hotpotatoesName, $newName);
|
||||
echo "<script> window.location='exercise.php'; </script>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "<form action=\"".api_get_self()."\" method='post' name='form1'>";
|
||||
echo "<input type=\"hidden\" name=\"hotpotatoesName\" value=\"$hotpotatoesName\">";
|
||||
echo "<input type=\"text\" name=\"newName\" value=\"";
|
||||
|
||||
$lstrComment = '';
|
||||
$lstrComment = GetComment($hotpotatoesName);
|
||||
if ($lstrComment == '') {
|
||||
$lstrComment = GetQuizName($hotpotatoesName, $documentPath);
|
||||
}
|
||||
if ($lstrComment == '') {
|
||||
$lstrComment = basename($hotpotatoesName, $documentPath);
|
||||
}
|
||||
|
||||
echo $lstrComment;
|
||||
echo "\" size=40> ";
|
||||
echo "<button type=\"submit\" class=\"save\" name=\"submit\" value=\"".get_lang('Ok')."\">".get_lang('Ok')."</button>";
|
||||
echo "<button type=\"button\" class=\"cancel\" name=\"cancel\" value=\"".get_lang('Cancel')."\" onclick=\"javascript:document.form1.newName.value='';\">".get_lang('Cancel')."</button>";
|
||||
echo "</form>";
|
||||
|
||||
Display::display_footer();
|
||||
55
main/exercise/aiken.php
Normal file
55
main/exercise/aiken.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Code for Aiken import integration.
|
||||
*
|
||||
* @author Ronny Velasquez <ronny.velasquez@beeznest.com>
|
||||
* @author César Perales <cesar.perales@gmail.com> Updated function names and import files for Aiken format support
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$lib_path = api_get_path(LIBRARY_PATH);
|
||||
$main_path = api_get_path(SYS_CODE_PATH);
|
||||
|
||||
require_once $main_path.'exercise/export/aiken/aiken_import.inc.php';
|
||||
require_once $main_path.'exercise/export/aiken/aiken_classes.php';
|
||||
|
||||
// section (for the tabs)
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
// access restriction: only teachers are allowed here
|
||||
if (!api_is_allowed_to_edit(null, true)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
|
||||
if ((api_is_allowed_to_edit(null, true))) {
|
||||
if (isset($_POST['submit'])) {
|
||||
$id = aiken_import_file($_FILES['userFile']);
|
||||
if (is_numeric($id) && !empty($id)) {
|
||||
header('Location: admin.php?'.api_get_cidreq().'&exerciseId='.$id);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
if (isset($_REQUEST['submit_aiken_generated'])) {
|
||||
$id = aikenImportExercise(null, $_REQUEST);
|
||||
if (is_numeric($id) && !empty($id)) {
|
||||
header('Location: admin.php?'.api_get_cidreq().'&exerciseId='.$id);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Display::display_header(get_lang('ImportAikenQuiz'), 'Exercise');
|
||||
aiken_display_form();
|
||||
generateAikenForm();
|
||||
Display::display_footer();
|
||||
72
main/exercise/annotation_user.php
Normal file
72
main/exercise/annotation_user.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
session_cache_limiter('none');
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$questionId = isset($_GET['question_id']) ? (int) $_GET['question_id'] : 0;
|
||||
$exerciseId = isset($_GET['exe_id']) ? (int) $_GET['exe_id'] : 0;
|
||||
$courseId = isset($_GET['course_id']) ? (int) $_GET['course_id'] : 0;
|
||||
$courseInfo = api_get_course_info_by_id($courseId);
|
||||
|
||||
if (empty($courseInfo)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$objQuestion = Question::read($questionId, $courseInfo);
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
|
||||
$picturePath = $documentPath.'/images';
|
||||
$pictureSize = getimagesize($picturePath.'/'.$objQuestion->getPictureFilename());
|
||||
$pictureWidth = $pictureSize[0];
|
||||
$pictureHeight = $pictureSize[1];
|
||||
|
||||
$data = [
|
||||
'use' => 'user',
|
||||
'image' => [
|
||||
'path' => $objQuestion->selectPicturePath(),
|
||||
'width' => $pictureSize[0],
|
||||
'height' => $pictureSize[1],
|
||||
],
|
||||
'answers' => [
|
||||
'paths' => [],
|
||||
'texts' => [],
|
||||
],
|
||||
];
|
||||
|
||||
$questionAttempt = Event::getQuestionAttemptByExeIdAndQuestion($exerciseId, $questionId);
|
||||
|
||||
if (!empty($questionAttempt['answer'])) {
|
||||
$answers = explode('|', $questionAttempt['answer']);
|
||||
foreach ($answers as $answer) {
|
||||
$parts = explode(')(', $answer);
|
||||
$typeProperties = array_shift($parts);
|
||||
$properties = explode(';', $typeProperties);
|
||||
|
||||
switch ($properties[0]) {
|
||||
case 'P':
|
||||
$points = [];
|
||||
foreach ($parts as $partPoint) {
|
||||
$points[] = Geometry::decodePoint($partPoint);
|
||||
}
|
||||
$data['answers']['paths'][] = [
|
||||
'color' => $properties[1] ?? '#FF0000',
|
||||
'points' => $points,
|
||||
];
|
||||
break;
|
||||
case 'T':
|
||||
$text = [
|
||||
'text' => array_shift($parts),
|
||||
'color' => $properties[1] ?? '#FF0000',
|
||||
'fontSize' => $properties[2] ?? '20',
|
||||
];
|
||||
|
||||
$data['answers']['texts'][] = $text + Geometry::decodePoint($parts[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
echo json_encode($data);
|
||||
1118
main/exercise/answer.class.php
Normal file
1118
main/exercise/answer.class.php
Normal file
File diff suppressed because it is too large
Load Diff
287
main/exercise/calculated_answer.class.php
Normal file
287
main/exercise/calculated_answer.class.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Webit\Util\EvalMath\EvalMath;
|
||||
|
||||
/**
|
||||
* Class CalculatedAnswer
|
||||
* This class contains calculated answer form and answer processing functions.
|
||||
*
|
||||
* @author Imanol Losada
|
||||
*/
|
||||
class CalculatedAnswer extends Question
|
||||
{
|
||||
public $typePicture = 'calculated_answer.png';
|
||||
public $explanationLangVar = 'CalculatedAnswer';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = CALCULATED_ANSWER;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$defaults = [];
|
||||
$defaults['answer'] = get_lang('DefaultTextInBlanks');
|
||||
if (!empty($this->iid)) {
|
||||
$objAnswer = new Answer($this->iid);
|
||||
$preArray = explode('@@', $objAnswer->selectAnswer(1));
|
||||
$defaults['formula'] = array_pop($preArray);
|
||||
$defaults['answer'] = array_shift($preArray);
|
||||
$defaults['answer'] = preg_replace("/\[.*\]/", '', $defaults['answer']);
|
||||
$defaults['weighting'] = $this->weighting;
|
||||
}
|
||||
$lowestValue = '1.00';
|
||||
$highestValue = '20.00';
|
||||
|
||||
// javascript //
|
||||
echo '<script>
|
||||
function parseTextNumber(textNumber, floatValue) {
|
||||
if (textNumber.indexOf(".") > -1) {
|
||||
textNumber = parseFloat(textNumber);
|
||||
floatValue.exists = "true";
|
||||
} else {
|
||||
textNumber = parseInt(textNumber);
|
||||
}
|
||||
return textNumber;
|
||||
}
|
||||
|
||||
function updateRandomValue(element) {
|
||||
// "floatValue" helps to distinguish between an integer (10) and a float with all 0 decimals (10.00)
|
||||
var floatValue = { exists: "false" };
|
||||
var index = (element.name).match(/\[[^\]]*\]/g);
|
||||
var lowestValue = parseTextNumber(document.getElementById("lowestValue"+index).value, floatValue);
|
||||
var highestValue = parseTextNumber(document.getElementById("highestValue"+index).value, floatValue);
|
||||
var result = Math.random() * (highestValue - lowestValue) + lowestValue;
|
||||
if (floatValue.exists == "true") {
|
||||
result = parseFloat(result).toFixed(2);
|
||||
} else {
|
||||
result = parseInt(result);
|
||||
}
|
||||
document.getElementById("randomValue"+index).innerHTML = "'.get_lang("ExampleValue").': " + result;
|
||||
}
|
||||
|
||||
CKEDITOR.on("instanceCreated", function(e) {
|
||||
if (e.editor.name === "answer") {
|
||||
e.editor.on("change", updateBlanks);
|
||||
}
|
||||
});
|
||||
|
||||
var firstTime = true;
|
||||
function updateBlanks(e) {
|
||||
if (firstTime) {
|
||||
field = document.getElementById("answer");
|
||||
var answer = field.value;
|
||||
} else {
|
||||
var answer = e.editor.getData();
|
||||
}
|
||||
var blanks = answer.match(/\[[^\]]*\]/g);
|
||||
var fields = "<div class=\"form-group\"><label class=\"col-sm-2\">'.get_lang('VariableRanges').'</label><div class=\"col-sm-8\"><table>";
|
||||
if (blanks!=null) {
|
||||
if (typeof updateBlanks.randomValues === "undefined") {
|
||||
updateBlanks.randomValues = [];
|
||||
}
|
||||
for (i=0 ; i<blanks.length ; i++){
|
||||
if (document.getElementById("lowestValue["+i+"]") && document.getElementById("highestValue["+i+"]")) {
|
||||
lowestValue = document.getElementById("lowestValue["+i+"]").value;
|
||||
highestValue = document.getElementById("highestValue["+i+"]").value;
|
||||
} else {
|
||||
lowestValue = '.$lowestValue.'.toFixed(2);
|
||||
highestValue = '.$highestValue.'.toFixed(2);
|
||||
for (j=0; j<blanks.length; j++) {
|
||||
updateBlanks.randomValues[j] = parseFloat(Math.random() * (highestValue - lowestValue) + lowestValue).toFixed(2);
|
||||
}
|
||||
}
|
||||
fields += "<tr><td><label>"+blanks[i]+"</label></td><td><input class=\"span1\" style=\"margin-left: 0em;\" size=\"5\" value=\""+lowestValue+"\" type=\"text\" id=\"lowestValue["+i+"]\" name=\"lowestValue["+i+"]\" onblur=\"updateRandomValue(this)\"/></td><td><input class=\"span1\" style=\"margin-left: 0em; width:80px;\" size=\"5\" value=\""+highestValue+"\" type=\"text\" id=\"highestValue["+i+"]\" name=\"highestValue["+i+"]\" onblur=\"updateRandomValue(this)\"/></td><td><label class=\"span3\" id=\"randomValue["+i+"]\"/>'.get_lang('ExampleValue').': "+updateBlanks.randomValues[i]+"</label></td></tr>";
|
||||
}
|
||||
}
|
||||
document.getElementById("blanks_weighting").innerHTML = fields + "</table></div></div>";
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = updateBlanks;
|
||||
|
||||
</script>';
|
||||
|
||||
// answer
|
||||
$form->addElement('label', null, '<br /><br />'.get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank'));
|
||||
$form->addElement(
|
||||
'html_editor',
|
||||
'answer',
|
||||
Display::return_icon('fill_field.png'),
|
||||
[
|
||||
'id' => 'answer',
|
||||
'onkeyup' => 'javascript: updateBlanks(this);',
|
||||
],
|
||||
[
|
||||
'ToolbarSet' => 'TestQuestionDescription',
|
||||
'Width' => '100%',
|
||||
'Height' => '350',
|
||||
]
|
||||
);
|
||||
|
||||
$form->addRule('answer', get_lang('GiveText'), 'required');
|
||||
$form->addRule('answer', get_lang('DefineBlanks'), 'regex', '/\[.*\]/');
|
||||
$form->applyFilter('answer', 'attr_on_filter');
|
||||
|
||||
$form->addElement('label', null, get_lang('IfYouWantOnlyIntegerValuesWriteBothLimitsWithoutDecimals'));
|
||||
$form->addElement('html', '<div id="blanks_weighting"></div>');
|
||||
|
||||
$notationListButton = Display::url(
|
||||
get_lang('NotationList'),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/evalmathnotation.php',
|
||||
[
|
||||
'class' => 'btn btn-info ajax',
|
||||
'data-title' => get_lang('NotationList'),
|
||||
'_target' => '_blank',
|
||||
]
|
||||
);
|
||||
$form->addElement(
|
||||
'label',
|
||||
null,
|
||||
$notationListButton
|
||||
);
|
||||
|
||||
$form->addElement('text', 'formula', [get_lang('Formula'), get_lang('FormulaExample')], ['id' => 'formula']);
|
||||
$form->addRule('formula', get_lang('GiveFormula'), 'required');
|
||||
|
||||
$form->addElement('text', 'weighting', get_lang('Weighting'), ['id' => 'weighting']);
|
||||
$form->setDefaults(['weighting' => '10']);
|
||||
|
||||
$form->addElement('text', 'answerVariations', get_lang('AnswerVariations'));
|
||||
$form->addRule(
|
||||
'answerVariations',
|
||||
get_lang('GiveAnswerVariations'),
|
||||
'required'
|
||||
);
|
||||
$form->setDefaults(['answerVariations' => '1']);
|
||||
|
||||
global $text;
|
||||
// setting the save button here and not in the question class.php
|
||||
$form->addButtonSave($text, 'submitQuestion');
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
if (!self::isAnswered()) {
|
||||
$table = Database::get_course_table(TABLE_QUIZ_ANSWER);
|
||||
Database::delete(
|
||||
$table,
|
||||
[
|
||||
'question_id = ?' => [
|
||||
$this->iid,
|
||||
],
|
||||
]
|
||||
);
|
||||
$answer = $form->getSubmitValue('answer');
|
||||
$formula = $form->getSubmitValue('formula');
|
||||
$lowestValues = $form->getSubmitValue('lowestValue');
|
||||
$highestValues = $form->getSubmitValue('highestValue');
|
||||
$answerVariations = $form->getSubmitValue('answerVariations');
|
||||
$this->weighting = $form->getSubmitValue('weighting');
|
||||
|
||||
// Create as many answers as $answerVariations
|
||||
for ($j = 0; $j < $answerVariations; $j++) {
|
||||
$auxAnswer = $answer;
|
||||
$auxFormula = $formula;
|
||||
$nb = preg_match_all('/\[[^\]]*\]/', $auxAnswer, $blanks);
|
||||
if ($nb > 0) {
|
||||
for ($i = 0; $i < $nb; $i++) {
|
||||
$blankItem = $blanks[0][$i];
|
||||
|
||||
// take random float values when one or both edge values have a decimal point
|
||||
$randomValue =
|
||||
(strpos($lowestValues[$i], '.') !== false ||
|
||||
strpos($highestValues[$i], '.') !== false) ?
|
||||
mt_rand($lowestValues[$i] * 100, $highestValues[$i] * 100) / 100 : mt_rand($lowestValues[$i], $highestValues[$i]);
|
||||
|
||||
$auxAnswer = str_replace($blankItem, $randomValue, $auxAnswer);
|
||||
$auxFormula = str_replace($blankItem, $randomValue, $auxFormula);
|
||||
}
|
||||
$math = new EvalMath();
|
||||
$result = $math->evaluate($auxFormula);
|
||||
$result = number_format($result, 2, '.', '');
|
||||
// Remove decimal trailing zeros
|
||||
$result = rtrim($result, '0');
|
||||
// If it is an integer (ends in .00) remove the decimal point
|
||||
if (mb_substr($result, -1) === '.') {
|
||||
$result = str_replace('.', '', $result);
|
||||
}
|
||||
// Attach formula
|
||||
$auxAnswer .= " [".$result."]@@".$formula;
|
||||
}
|
||||
$this->save($exercise);
|
||||
$objAnswer = new Answer($this->iid);
|
||||
$objAnswer->createAnswer($auxAnswer, 1, '', $this->weighting, '');
|
||||
$objAnswer->position = [];
|
||||
$objAnswer->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'"><tr>';
|
||||
$header .= '<th>'.get_lang('Answer').'</th>';
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
$header .= '<th>'.get_lang('YourChoice').'</th>';
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
}
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current question has been attempted to be answered.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAnswered()
|
||||
{
|
||||
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
|
||||
$result = Database::select(
|
||||
'question_id',
|
||||
$table,
|
||||
[
|
||||
'where' => [
|
||||
'question_id = ? AND c_id = ?' => [
|
||||
$this->iid,
|
||||
$this->course['real_id'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return empty($result) ? false : true;
|
||||
}
|
||||
}
|
||||
147
main/exercise/category.php
Normal file
147
main/exercise/category.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
if (api_get_configuration_value('allow_exercise_categories') === false) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
api_protect_course_script();
|
||||
|
||||
if (!api_is_allowed_to_edit()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
|
||||
$courseId = api_get_course_int_id();
|
||||
|
||||
$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_exercise_categories&c_id='.$courseId.'&'.api_get_cidreq();
|
||||
$action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
|
||||
|
||||
$obj = new ExerciseCategoryManager();
|
||||
|
||||
$check = Security::check_token('request');
|
||||
$token = Security::get_token();
|
||||
|
||||
//Add the JS needed to use the jqgrid
|
||||
$htmlHeadXtra[] = api_get_jqgrid_js();
|
||||
|
||||
//The order is important you need to check the the $column variable in the model.ajax.php file
|
||||
$columns = [
|
||||
get_lang('Name'),
|
||||
get_lang('Actions'),
|
||||
];
|
||||
|
||||
// Column config
|
||||
$column_model = [
|
||||
[
|
||||
'name' => 'name',
|
||||
'index' => 'name',
|
||||
'width' => '140',
|
||||
'align' => 'left',
|
||||
],
|
||||
[
|
||||
'name' => 'actions',
|
||||
'index' => 'actions',
|
||||
'width' => '40',
|
||||
'align' => 'left',
|
||||
'formatter' => 'action_formatter',
|
||||
'sortable' => 'false',
|
||||
],
|
||||
];
|
||||
|
||||
// Autowidth
|
||||
$extra_params['autowidth'] = 'true';
|
||||
// height auto
|
||||
$extra_params['height'] = 'auto';
|
||||
|
||||
$action_links = $obj->getJqgridActionLinks($token);
|
||||
|
||||
$htmlHeadXtra[] = '<script>
|
||||
$(function() {
|
||||
// grid definition see the $obj->display() function
|
||||
'.Display::grid_js(
|
||||
'categories',
|
||||
$url,
|
||||
$columns,
|
||||
$column_model,
|
||||
$extra_params,
|
||||
[],
|
||||
$action_links,
|
||||
true
|
||||
).'
|
||||
});
|
||||
</script>';
|
||||
|
||||
$url = api_get_self().'?'.api_get_cidreq();
|
||||
|
||||
switch ($action) {
|
||||
case 'add':
|
||||
$interbreadcrumb[] = ['url' => $url, 'name' => get_lang('Categories')];
|
||||
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('Add')];
|
||||
$form = $obj->return_form($url.'&action=add', 'add');
|
||||
// The validation or display
|
||||
if ($form->validate()) {
|
||||
$values = $form->exportValues();
|
||||
unset($values['id']);
|
||||
$res = $obj->save($values);
|
||||
if ($res) {
|
||||
Display::addFlash(Display::return_message(get_lang('ItemAdded'), 'confirmation'));
|
||||
}
|
||||
header('Location: '.$url);
|
||||
exit;
|
||||
} else {
|
||||
$content = '<div class="actions">';
|
||||
$content .= '<a href="'.$url.'">'.
|
||||
Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$content .= '</div>';
|
||||
$form->addElement('hidden', 'sec_token');
|
||||
$form->setConstants(['sec_token' => $token]);
|
||||
$content .= $form->returnForm();
|
||||
}
|
||||
break;
|
||||
case 'edit':
|
||||
$interbreadcrumb[] = ['url' => $url, 'name' => get_lang('Categories')];
|
||||
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('Edit')];
|
||||
$form = $obj->return_form($url.'&action=edit&id='.intval($_GET['id']), 'edit');
|
||||
|
||||
// The validation or display
|
||||
if ($form->validate()) {
|
||||
$values = $form->exportValues();
|
||||
$res = $obj->update($values);
|
||||
if ($res) {
|
||||
Display::addFlash(Display::return_message(get_lang('ItemUpdated'), 'confirmation'));
|
||||
}
|
||||
header('Location: '.$url);
|
||||
exit;
|
||||
} else {
|
||||
$content = '<div class="actions">';
|
||||
$content .= '<a href="'.$url.'">'.
|
||||
Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$content .= '</div>';
|
||||
$form->addElement('hidden', 'sec_token');
|
||||
$form->setConstants(['sec_token' => $token]);
|
||||
$content .= $form->returnForm();
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
$res = $obj->delete($_GET['id']);
|
||||
if ($res) {
|
||||
Display::addFlash(Display::return_message(get_lang('ItemDeleted'), 'confirmation'));
|
||||
}
|
||||
header('Location: '.$url);
|
||||
exit;
|
||||
break;
|
||||
default:
|
||||
$content = $obj->display();
|
||||
break;
|
||||
}
|
||||
|
||||
Display::display_header();
|
||||
echo $content;
|
||||
103
main/exercise/comparative_group_report.php
Normal file
103
main/exercise/comparative_group_report.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$isAllowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
|
||||
if (!$isAllowedToEdit) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$exerciseId = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0;
|
||||
$exportXls = isset($_GET['export_xls']) && !empty($_GET['export_xls']) ? (int) $_GET['export_xls'] : 0;
|
||||
if (empty($exerciseId)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$sessionId = api_get_session_id();
|
||||
|
||||
$exercise = new Exercise();
|
||||
$result = $exercise->read($exerciseId);
|
||||
|
||||
if (empty($result)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$nameTools = get_lang('ExerciseManagement');
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'admin.php?exerciseId='.$exercise->iid.'&'.api_get_cidreq(),
|
||||
'name' => $exercise->selectTitle(true),
|
||||
];
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise_report.php?'.api_get_cidreq().'&exerciseId='.$exercise->iid,
|
||||
'name' => get_lang('StudentScore'),
|
||||
];
|
||||
|
||||
$courseId = api_get_course_int_id();
|
||||
$courseInfo = api_get_course_info();
|
||||
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped']);
|
||||
$row = 0;
|
||||
$column = 0;
|
||||
|
||||
$headers = [
|
||||
get_lang('Group'),
|
||||
get_lang('AverageScore'),
|
||||
];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
$table->setHeaderContents($row, $column, $header);
|
||||
$column++;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$tableXls[] = $headers;
|
||||
}
|
||||
$row++;
|
||||
$scoreDisplay = new ScoreDisplay();
|
||||
|
||||
$groups = GroupManager::get_group_list(null, $courseInfo);
|
||||
if (!empty($groups)) {
|
||||
foreach ($groups as $group) {
|
||||
$average = ExerciseLib::get_average_score($exerciseId, $courseId, $sessionId, $group['iid']);
|
||||
$table->setCellContents($row, 0, $group['name']);
|
||||
$averageToDisplay = $scoreDisplay->display_score([$average, 1], SCORE_AVERAGE);
|
||||
$table->setCellContents($row, 1, $averageToDisplay);
|
||||
if ($exportXls) {
|
||||
$tableXls[] = [$group['name'], $averageToDisplay];
|
||||
}
|
||||
$row++;
|
||||
}
|
||||
}
|
||||
if ($exportXls) {
|
||||
$fileName = get_lang('ComparativeGroupReport').'_'.api_get_course_id().'_'.$exerciseId.'_'.api_get_local_time();
|
||||
Export::arrayToXls($tableXls, $fileName);
|
||||
exit;
|
||||
}
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
$actions = '<a href="exercise_report.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'">'.
|
||||
Display::return_icon(
|
||||
'back.png',
|
||||
get_lang('GoBackToQuestionList'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
)
|
||||
.'</a>';
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM),
|
||||
'comparative_group_report.php?id='.$exerciseId.'&export_xls=1&'.api_get_cidreq()
|
||||
);
|
||||
|
||||
$actions = Display::div($actions, ['class' => 'actions']);
|
||||
echo $actions;
|
||||
echo $table->toHtml();
|
||||
Display::display_footer();
|
||||
51
main/exercise/evalmathnotation.php
Normal file
51
main/exercise/evalmathnotation.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
require_once "../inc/global.inc.php";
|
||||
|
||||
echo "<pre>".
|
||||
get_lang('SummationPlus')."\n".
|
||||
get_lang('SubstractionMinus')."\n".
|
||||
get_lang('MultiplicationStar')."\n".
|
||||
get_lang('DivisionSlash')."\n".
|
||||
get_lang('ExponentiationCircumflex')."\n".
|
||||
get_lang('ModuloPercentage')."\n".
|
||||
"\n".
|
||||
get_lang('SquareRootSqrt')."\n".
|
||||
get_lang('AbsoluteValueAbs')."\n".
|
||||
get_lang('NaturalLogarithmLn')."\n".
|
||||
get_lang('LogarithmLog')."\n".
|
||||
get_lang('ENumberE')."\n".
|
||||
get_lang('PiNumberPi')."\n".
|
||||
"\n".
|
||||
get_lang('SineSin')."\n".
|
||||
get_lang('HyperbolicSineSinh')."\n".
|
||||
get_lang('ArcsineArcsin')."\n".
|
||||
get_lang('HyperbolicArcsineArcsinh')."\n".
|
||||
"\n".
|
||||
get_lang('CosineCos')."\n".
|
||||
get_lang('HyperbolicCosineCosh')."\n".
|
||||
get_lang('ArccosineArccos')."\n".
|
||||
get_lang('HyperbolicArccosineArccosh')."\n".
|
||||
"\n".
|
||||
get_lang('TangentTan')."\n".
|
||||
get_lang('HyperbolicTangentTanh')."\n".
|
||||
get_lang('ArctangentArctan')."\n".
|
||||
get_lang('HyperbolicArctangentArctanh')."\n".
|
||||
"\n".
|
||||
get_lang('CotangentCot')."\n".
|
||||
get_lang('HyperbolicCotangentCoth')."\n".
|
||||
get_lang('ArccotangentArccot')."\n".
|
||||
get_lang('HyperbolicArccotangentArccoth')."\n".
|
||||
"\n".
|
||||
get_lang('SecantSec')."\n".
|
||||
get_lang('HyperbolicSecantSech')."\n".
|
||||
get_lang('ArcsecantArcsec')."\n".
|
||||
get_lang('HyperbolicArcsecantArcsech')."\n".
|
||||
"\n".
|
||||
get_lang('CosecantCsc')."\n".
|
||||
get_lang('HyperbolicCosecantCsch')."\n".
|
||||
get_lang('ArccosecantArccsc')."\n".
|
||||
get_lang('HyperbolicArccosecantArccsch')."\n".
|
||||
"\n".
|
||||
"</pre>";
|
||||
10
main/exercise/exercice.php
Normal file
10
main/exercise/exercice.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
// This file serves the only purpose of maintaining backwards compatibility
|
||||
// with previous content of Chamilo that might have pointed directly to
|
||||
// exercise.php as it was called before.
|
||||
// The *previous* exercise.php was renamed to exercise.php, which is the file
|
||||
// included here. All new links to the main exercises page should point
|
||||
// directly to exercise.php. This redirection is enabled for 1.10.x (2015-04-21)
|
||||
// The final goal of this file is to be removed in a few years time, if
|
||||
// considered realistically not harmful
|
||||
require __DIR__.'/exercise.php';
|
||||
12409
main/exercise/exercise.class.php
Normal file
12409
main/exercise/exercise.class.php
Normal file
File diff suppressed because it is too large
Load Diff
794
main/exercise/exercise.php
Normal file
794
main/exercise/exercise.php
Normal file
@@ -0,0 +1,794 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Exercise list: This script shows the list of exercises for administrators and students.
|
||||
*
|
||||
* @author Olivier Brouckaert, original author
|
||||
* @author Denes Nagy, HotPotatoes integration
|
||||
* @author Wolfgang Schneider, code/html cleanup
|
||||
* @author Julio Montoya <gugli100@gmail.com>, lots of cleanup + several improvements
|
||||
* Modified by hubert.borderiou (question category)
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
|
||||
// Setting the tabs
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
$htmlHeadXtra[] = api_get_asset('qtip2/jquery.qtip.min.js');
|
||||
$htmlHeadXtra[] = api_get_css_asset('qtip2/jquery.qtip.min.css');
|
||||
$htmlHeadXtra[] = '<script>
|
||||
function sendNotificationToUsers() {
|
||||
var sendTo = $("#toUsers").val().join(",");
|
||||
var url = $("#urlTo").val() + sendTo;
|
||||
$("#toUsers").find("option").remove().end().selectpicker("refresh");
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: "json"
|
||||
}).done(function(response) {
|
||||
$("#cm-tools").html(response.message);
|
||||
}).always(function() {
|
||||
$("#toUsers").find("option").remove().end().selectpicker("refresh");
|
||||
$("#urlTo").val("");
|
||||
});
|
||||
}
|
||||
function showUserToSendNotificacion(element) {
|
||||
var url = $(element).data("link");
|
||||
$("#toUsers").find("option").remove().end().selectpicker("refresh");
|
||||
$("#urlTo").val("");
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: "json",
|
||||
}).done(function(response) {
|
||||
$("#toUsers").find("option").remove().end().selectpicker("refresh");
|
||||
$.each(response,function(a,b){
|
||||
$("#toUsers").append($("<option>", {
|
||||
value: b.user_id,
|
||||
text: b.user_name
|
||||
}));
|
||||
});
|
||||
$("#urlTo").val($(element).data("link").replace("send_reminder","send_reminder_to") + "&users=")
|
||||
$("#toUsers").selectpicker("refresh");
|
||||
$("#NotificarUsuarios").modal()
|
||||
});
|
||||
}
|
||||
</script>';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
|
||||
|
||||
$allowDelete = Exercise::allowAction('delete');
|
||||
$allowClean = Exercise::allowAction('clean_results');
|
||||
|
||||
$check = Security::get_existing_token('get');
|
||||
|
||||
$currentUrl = api_get_self().'?'.api_get_cidreq();
|
||||
|
||||
require_once 'hotpotatoes.lib.php';
|
||||
|
||||
/* Constants and variables */
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
$is_tutor = api_is_allowed_to_edit(true);
|
||||
$is_tutor_course = api_is_course_tutor();
|
||||
$courseInfo = api_get_course_info();
|
||||
$courseId = $courseInfo['real_id'];
|
||||
$userInfo = api_get_user_info();
|
||||
$userId = $userInfo['id'];
|
||||
$sessionId = api_get_session_id();
|
||||
$isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh(
|
||||
$userId,
|
||||
$courseInfo
|
||||
);
|
||||
|
||||
$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
|
||||
$TBL_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
|
||||
$TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
|
||||
$TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
|
||||
$TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
|
||||
// document path
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
|
||||
// picture path
|
||||
$picturePath = $documentPath.'/images';
|
||||
// audio path
|
||||
$audioPath = $documentPath.'/audio';
|
||||
|
||||
// hot potatoes
|
||||
$uploadPath = DIR_HOTPOTATOES; //defined in main_api
|
||||
$exercisePath = api_get_self();
|
||||
$exfile = explode('/', $exercisePath);
|
||||
$exfile = strtolower($exfile[count($exfile) - 1]);
|
||||
$exercisePath = substr($exercisePath, 0, strpos($exercisePath, $exfile));
|
||||
$exercisePath = $exercisePath.'exercise.php';
|
||||
|
||||
// Clear the exercise session
|
||||
Exercise::cleanSessionVariables();
|
||||
|
||||
//General POST/GET/SESSION/COOKIES parameters recovery
|
||||
$origin = api_get_origin();
|
||||
$choice = isset($_REQUEST['choice']) ? Security::remove_XSS($_REQUEST['choice']) : null;
|
||||
$hpchoice = isset($_REQUEST['hpchoice']) ? Security::remove_XSS($_REQUEST['hpchoice']) : null;
|
||||
$exerciseId = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : null;
|
||||
$file = isset($_REQUEST['file']) ? Database::escape_string($_REQUEST['file']) : null;
|
||||
$learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : null;
|
||||
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : null;
|
||||
$categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
|
||||
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
|
||||
$keyword = isset($_REQUEST['keyword']) ? Security::remove_XSS($_REQUEST['keyword']) : '';
|
||||
|
||||
if (api_is_in_gradebook()) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => Category::getUrl(),
|
||||
'name' => get_lang('ToolGradebook'),
|
||||
];
|
||||
}
|
||||
|
||||
$nameTools = get_lang('Exercises');
|
||||
|
||||
// Simple actions
|
||||
if ($is_allowedToEdit) {
|
||||
switch ($action) {
|
||||
case 'export_all_exercises_results':
|
||||
$sessionId = api_get_session_id();
|
||||
$courseId = api_get_course_int_id();
|
||||
ExerciseLib::exportAllExercisesResultsZip($sessionId, $courseId);
|
||||
|
||||
break;
|
||||
case 'clean_all_test':
|
||||
if ($check) {
|
||||
if (false === $allowClean) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
// list of exercises in a course/session
|
||||
// we got variable $courseId $courseInfo session api_get_session_id()
|
||||
$exerciseList = ExerciseLib::get_all_exercises_for_course_id(
|
||||
$courseInfo,
|
||||
$sessionId,
|
||||
$courseId,
|
||||
false
|
||||
);
|
||||
|
||||
$quantity_results_deleted = 0;
|
||||
foreach ($exerciseList as $exeItem) {
|
||||
// delete result for test, if not in a gradebook
|
||||
$exercise_action_locked = api_resource_is_locked_by_gradebook($exeItem['iid'], LINK_EXERCISE);
|
||||
if ($exercise_action_locked == false) {
|
||||
$objExerciseTmp = new Exercise();
|
||||
if ($objExerciseTmp->read($exeItem['iid'])) {
|
||||
$quantity_results_deleted += $objExerciseTmp->cleanResults(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Display::addFlash(Display::return_message(
|
||||
sprintf(
|
||||
get_lang('XResultsCleaned'),
|
||||
$quantity_results_deleted
|
||||
),
|
||||
'confirm'
|
||||
));
|
||||
|
||||
header('Location: '.$currentUrl);
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
case 'exportqti2':
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
require_once api_get_path(SYS_CODE_PATH).'exercise/export/qti2/qti2_export.php';
|
||||
|
||||
$export = export_exercise_to_qti($exerciseId, true);
|
||||
$archive_path = api_get_path(SYS_ARCHIVE_PATH);
|
||||
$temp_dir_short = api_get_unique_id();
|
||||
$temp_zip_dir = $archive_path.$temp_dir_short;
|
||||
if (!is_dir($temp_zip_dir)) {
|
||||
mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
|
||||
}
|
||||
$temp_zip_file = $temp_zip_dir.'/'.api_get_unique_id().'.zip';
|
||||
$temp_xml_file = $temp_zip_dir.'/qti2export_'.$exerciseId.'.xml';
|
||||
file_put_contents($temp_xml_file, $export);
|
||||
|
||||
$xmlReader = new XMLReader();
|
||||
$xmlReader->open($temp_xml_file);
|
||||
$xmlReader->setParserProperty(XMLReader::VALIDATE, true);
|
||||
$isValid = $xmlReader->isValid();
|
||||
|
||||
if ($isValid) {
|
||||
$zip_folder = new PclZip($temp_zip_file);
|
||||
$zip_folder->add($temp_xml_file, PCLZIP_OPT_REMOVE_ALL_PATH);
|
||||
$name = 'qti2_export_'.$exerciseId.'.zip';
|
||||
DocumentManager::file_send_for_download($temp_zip_file, true, $name);
|
||||
unlink($temp_zip_file);
|
||||
unlink($temp_xml_file);
|
||||
rmdir($temp_zip_dir);
|
||||
exit; // otherwise following clicks may become buggy
|
||||
} else {
|
||||
Display::addFlash(Display::return_message(get_lang('ErrorWritingXMLFile'), 'error'));
|
||||
header('Location: '.$currentUrl);
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
case 'up_category':
|
||||
case 'down_category':
|
||||
$categoryIdFromGet = isset($_REQUEST['category_id_edit']) ? $_REQUEST['category_id_edit'] : 0;
|
||||
$em = Database::getManager();
|
||||
$repo = $em->getRepository('ChamiloCourseBundle:CExerciseCategory');
|
||||
$category = $repo->find($categoryIdFromGet);
|
||||
$currentPosition = $category->getPosition();
|
||||
|
||||
if ($action === 'up_category') {
|
||||
$currentPosition--;
|
||||
} else {
|
||||
$currentPosition++;
|
||||
}
|
||||
$category->setPosition($currentPosition);
|
||||
$em->persist($category);
|
||||
$em->flush();
|
||||
Display::addFlash(Display::return_message(get_lang('Updated')));
|
||||
|
||||
header('Location: '.$currentUrl);
|
||||
exit;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Mass actions
|
||||
if (!empty($action) && $is_allowedToEdit) {
|
||||
$exerciseListToEdit = isset($_REQUEST['id']) ? $_REQUEST['id'] : 0;
|
||||
if (!empty($exerciseListToEdit)) {
|
||||
foreach ($exerciseListToEdit as $exerciseIdToEdit) {
|
||||
$objExerciseTmp = new Exercise();
|
||||
$result = $objExerciseTmp->read($exerciseIdToEdit);
|
||||
|
||||
if (empty($result)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'delete':
|
||||
if ($allowDelete) {
|
||||
if ($objExerciseTmp->sessionId == $sessionId) {
|
||||
$objExerciseTmp->delete();
|
||||
} else {
|
||||
Display::addFlash(Display::return_message(sprintf(get_lang('ExerciseXNotDeleted'), $objExerciseTmp->name), 'error'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'visible':
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
|
||||
// enables an exercise
|
||||
if (empty($sessionId)) {
|
||||
$objExerciseTmp->enable();
|
||||
$objExerciseTmp->save();
|
||||
} else {
|
||||
if (!empty($objExerciseTmp->sessionId)) {
|
||||
$objExerciseTmp->enable();
|
||||
$objExerciseTmp->save();
|
||||
}
|
||||
}
|
||||
api_item_property_update(
|
||||
$courseInfo,
|
||||
TOOL_QUIZ,
|
||||
$objExerciseTmp->iid,
|
||||
'visible',
|
||||
$userId
|
||||
);
|
||||
|
||||
break;
|
||||
case 'invisible':
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
|
||||
// enables an exercise
|
||||
if (empty($sessionId)) {
|
||||
$objExerciseTmp->disable();
|
||||
$objExerciseTmp->save();
|
||||
} else {
|
||||
if (!empty($objExerciseTmp->sessionId)) {
|
||||
$objExerciseTmp->disable();
|
||||
$objExerciseTmp->save();
|
||||
}
|
||||
}
|
||||
|
||||
api_item_property_update(
|
||||
$courseInfo,
|
||||
TOOL_QUIZ,
|
||||
$objExerciseTmp->iid,
|
||||
'visible',
|
||||
$userId
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Display::addFlash(Display::return_message(get_lang('Updated')));
|
||||
header('Location: '.$currentUrl);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
Event::event_access_tool(TOOL_QUIZ);
|
||||
|
||||
$logInfo = [
|
||||
'tool' => TOOL_QUIZ,
|
||||
'tool_id' => (int) $exerciseId,
|
||||
'action' => isset($_REQUEST['learnpath_id']) ? 'learnpath_id' : '',
|
||||
'action_details' => isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : '',
|
||||
];
|
||||
Event::registerLog($logInfo);
|
||||
|
||||
HotPotGCt($documentPath, 1, $userId);
|
||||
|
||||
// Only for administrator
|
||||
if ($is_allowedToEdit) {
|
||||
if (!empty($choice)) {
|
||||
// single exercise choice
|
||||
// construction of Exercise
|
||||
$objExerciseTmp = new Exercise();
|
||||
$exercise_action_locked = api_resource_is_locked_by_gradebook(
|
||||
$exerciseId,
|
||||
LINK_EXERCISE
|
||||
);
|
||||
|
||||
if ($objExerciseTmp->read($exerciseId)) {
|
||||
if ($check) {
|
||||
switch ($choice) {
|
||||
case 'enable_launch':
|
||||
$objExerciseTmp->cleanCourseLaunchSettings();
|
||||
$objExerciseTmp->enableAutoLaunch();
|
||||
Display::addFlash(Display::return_message(get_lang('Updated')));
|
||||
break;
|
||||
case 'disable_launch':
|
||||
$objExerciseTmp->cleanCourseLaunchSettings();
|
||||
break;
|
||||
case 'delete':
|
||||
// deletes an exercise
|
||||
if ($allowDelete) {
|
||||
$deleteQuestions = api_get_configuration_value('quiz_question_delete_automatically_when_deleting_exercise') ? true : false;
|
||||
$result = $objExerciseTmp->delete(false, $deleteQuestions);
|
||||
if ($result) {
|
||||
Display::addFlash(Display::return_message(get_lang('ExerciseDeleted'), 'confirmation'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'enable':
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
|
||||
// Enables an exercise
|
||||
if (empty($sessionId)) {
|
||||
$objExerciseTmp->enable();
|
||||
$objExerciseTmp->save();
|
||||
} else {
|
||||
if (!empty($objExerciseTmp->sessionId)) {
|
||||
$objExerciseTmp->enable();
|
||||
$objExerciseTmp->save();
|
||||
}
|
||||
}
|
||||
|
||||
api_item_property_update(
|
||||
$courseInfo,
|
||||
TOOL_QUIZ,
|
||||
$objExerciseTmp->iid,
|
||||
'visible',
|
||||
$userId
|
||||
);
|
||||
Display::addFlash(Display::return_message(get_lang('VisibilityChanged'), 'confirmation'));
|
||||
break;
|
||||
case 'disable':
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
// disables an exercise
|
||||
if (empty($sessionId)) {
|
||||
$objExerciseTmp->disable();
|
||||
$objExerciseTmp->save();
|
||||
} else {
|
||||
// Only change active if it belongs to a session
|
||||
if (!empty($objExerciseTmp->sessionId)) {
|
||||
$objExerciseTmp->disable();
|
||||
$objExerciseTmp->save();
|
||||
}
|
||||
}
|
||||
|
||||
api_item_property_update(
|
||||
$courseInfo,
|
||||
TOOL_QUIZ,
|
||||
$objExerciseTmp->iid,
|
||||
'invisible',
|
||||
$userId
|
||||
);
|
||||
Display::addFlash(Display::return_message(get_lang('VisibilityChanged'), 'confirmation'));
|
||||
break;
|
||||
case 'disable_results':
|
||||
//disable the results for the learners
|
||||
$objExerciseTmp->disable_results();
|
||||
$objExerciseTmp->save();
|
||||
Display::addFlash(Display::return_message(get_lang('ResultsDisabled'), 'confirmation'));
|
||||
|
||||
break;
|
||||
case 'enable_results':
|
||||
//disable the results for the learners
|
||||
$objExerciseTmp->enable_results();
|
||||
$objExerciseTmp->save();
|
||||
Display::addFlash(Display::return_message(get_lang('ResultsEnabled'), 'confirmation'));
|
||||
|
||||
break;
|
||||
case 'clean_results':
|
||||
if (false === $allowClean) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
|
||||
// Clean student results
|
||||
if (false == $exercise_action_locked) {
|
||||
$quantity_results_deleted = $objExerciseTmp->cleanResults(true);
|
||||
$title = $objExerciseTmp->selectTitle();
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message(
|
||||
$title.': '.sprintf(
|
||||
get_lang('XResultsCleaned'),
|
||||
$quantity_results_deleted
|
||||
),
|
||||
'confirmation'
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'copy_exercise': //copy an exercise
|
||||
api_set_more_memory_and_time_limits();
|
||||
$objExerciseTmp->copyExercise();
|
||||
Display::addFlash(Display::return_message(
|
||||
get_lang('ExerciseCopied'),
|
||||
'confirmation'
|
||||
));
|
||||
break;
|
||||
case 'send_reminder_to':
|
||||
$toUsers = $_GET['users'] ?? null;
|
||||
if (!empty($toUsers) && !empty($exerciseId)) {
|
||||
$sessionId = isset($_GET['id_session']) ? (int) $_GET['id_session'] : 0;
|
||||
$courseCode = $_GET['cidReq'] ?? null;
|
||||
$courseId = api_get_course_int_id($courseCode);
|
||||
$temo = [];
|
||||
if (is_int(strpos($toUsers, 'X'))) {
|
||||
// to all users
|
||||
$temo = Exercise::getUsersInExercise(
|
||||
$exerciseId,
|
||||
$courseId,
|
||||
$sessionId,
|
||||
false,
|
||||
[],
|
||||
false
|
||||
);
|
||||
$toUsers = array_column($temo, 'user_id');
|
||||
} else {
|
||||
$toUsers = explode(',', $toUsers);
|
||||
}
|
||||
api_set_more_memory_and_time_limits();
|
||||
Exercise::notifyUsersOfTheExercise(
|
||||
$exerciseId,
|
||||
$courseId,
|
||||
$sessionId,
|
||||
$toUsers
|
||||
);
|
||||
echo json_encode(
|
||||
[
|
||||
'message' => Display::return_message(
|
||||
get_lang('AnnounceSentByEmail'), 'confirmation'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
exit();
|
||||
case 'send_reminder':
|
||||
$users = Exercise::getUsersInExercise(
|
||||
$objExerciseTmp->iid,
|
||||
$courseId,
|
||||
$sessionId
|
||||
);
|
||||
echo json_encode($users);
|
||||
exit();
|
||||
}
|
||||
header('Location: '.$currentUrl);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
// destruction of Exercise
|
||||
unset($objExerciseTmp);
|
||||
Security::clear_token();
|
||||
}
|
||||
|
||||
if (!empty($hpchoice)) {
|
||||
switch ($hpchoice) {
|
||||
case 'delete':
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
// deletes an exercise
|
||||
$imgparams = [];
|
||||
$imgcount = 0;
|
||||
GetImgParams($file, $documentPath, $imgparams, $imgcount);
|
||||
$fld = GetFolderName($file);
|
||||
|
||||
for ($i = 0; $i < $imgcount; $i++) {
|
||||
my_delete($documentPath.$uploadPath."/".$fld."/".$imgparams[$i]);
|
||||
DocumentManager::updateDbInfo("delete", $uploadPath."/".$fld."/".$imgparams[$i]);
|
||||
}
|
||||
|
||||
if (!is_dir($documentPath.$uploadPath."/".$fld."/")) {
|
||||
my_delete($documentPath.$file);
|
||||
DocumentManager::updateDbInfo("delete", $file);
|
||||
} else {
|
||||
if (my_delete($documentPath.$file)) {
|
||||
DocumentManager::updateDbInfo("delete", $file);
|
||||
}
|
||||
}
|
||||
|
||||
/* hotpotatoes folder may contains several tests so
|
||||
don't delete folder if not empty :
|
||||
http://support.chamilo.org/issues/2165
|
||||
*/
|
||||
if (!(strstr($uploadPath, DIR_HOTPOTATOES) &&
|
||||
!folder_is_empty($documentPath.$uploadPath."/".$fld."/"))
|
||||
) {
|
||||
my_delete($documentPath.$uploadPath."/".$fld."/");
|
||||
}
|
||||
break;
|
||||
case 'enable': // enables an exercise
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
|
||||
$newVisibilityStatus = '1'; //"visible"
|
||||
$query = "SELECT iid FROM $TBL_DOCUMENT
|
||||
WHERE c_id = $courseId AND path='".Database::escape_string($file)."'";
|
||||
$res = Database::query($query);
|
||||
$row = Database::fetch_array($res, 'ASSOC');
|
||||
api_item_property_update(
|
||||
$courseInfo,
|
||||
TOOL_DOCUMENT,
|
||||
$row['iid'],
|
||||
'visible',
|
||||
$userId
|
||||
);
|
||||
|
||||
Display::addFlash(Display::return_message(get_lang('Updated')));
|
||||
|
||||
break;
|
||||
case 'disable': // disables an exercise
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
// Teacher change exercise
|
||||
break;
|
||||
}
|
||||
$newVisibilityStatus = '0'; //"invisible"
|
||||
$query = "SELECT iid FROM $TBL_DOCUMENT
|
||||
WHERE c_id = $courseId AND path='".Database::escape_string($file)."'";
|
||||
$res = Database::query($query);
|
||||
$row = Database::fetch_array($res, 'ASSOC');
|
||||
api_item_property_update(
|
||||
$courseInfo,
|
||||
TOOL_DOCUMENT,
|
||||
$row['iid'],
|
||||
'invisible',
|
||||
$userId
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
header('Location: '.$currentUrl);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($origin, ['learnpath', 'mobileapp'])) {
|
||||
//so we are not in learnpath tool
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
if (isset($_GET['message']) && in_array($_GET['message'], ['ExerciseEdited'])) {
|
||||
echo Display::return_message(get_lang('ExerciseEdited'), 'confirmation');
|
||||
}
|
||||
} else {
|
||||
Display::display_reduced_header();
|
||||
}
|
||||
Display::display_introduction_section(TOOL_QUIZ);
|
||||
|
||||
// Selects $limit exercises at the same time
|
||||
// maximum number of exercises on a same page
|
||||
$limit = Exercise::PAGINATION_ITEMS_PER_PAGE;
|
||||
|
||||
HotPotGCt($documentPath, 1, $userId);
|
||||
|
||||
$token = Security::get_token();
|
||||
if ($is_allowedToEdit && $origin !== 'learnpath') {
|
||||
$actionsLeft = '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('new_exercice.png', get_lang('NewEx'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/question_create.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('new_question.png', get_lang('AddQ'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
|
||||
if (api_get_configuration_value('allow_exercise_categories')) {
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/category.php?'.api_get_cidreq().'">';
|
||||
$actionsLeft .= Display::return_icon('folder.png', get_lang('Category'), '', ICON_SIZE_MEDIUM);
|
||||
$actionsLeft .= '</a>';
|
||||
}
|
||||
|
||||
// Question category
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/tests_category.php?'.api_get_cidreq().'">';
|
||||
$actionsLeft .= Display::return_icon('green_open.png', get_lang('QuestionCategory'), '', ICON_SIZE_MEDIUM);
|
||||
$actionsLeft .= '</a>';
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/question_pool.php?'.api_get_cidreq().'">';
|
||||
$actionsLeft .= Display::return_icon('database.png', get_lang('QuestionPool'), '', ICON_SIZE_MEDIUM);
|
||||
$actionsLeft .= '</a>';
|
||||
|
||||
// end question category
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/hotpotatoes.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('import_hotpotatoes.png', get_lang('ImportHotPotatoesQuiz'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
// link to import qti2 ...
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/qti2.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('import_qti2.png', get_lang('ImportQtiQuiz'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/aiken.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('import_aiken.png', get_lang('ImportAikenQuiz'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actionsLeft .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/upload_exercise.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('import_excel.png', get_lang('ImportExcelQuiz'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
|
||||
$cleanAll = null;
|
||||
if ($allowClean) {
|
||||
$cleanAll = Display::url(
|
||||
Display::return_icon(
|
||||
'clean_all.png',
|
||||
get_lang('CleanAllStudentsResultsForAllTests'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
),
|
||||
'#',
|
||||
[
|
||||
'data-item-question' => addslashes(get_lang('AreYouSureToEmptyAllTestResults')),
|
||||
'data-href' => api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq().'&action=clean_all_test&sec_token='.$token,
|
||||
'data-toggle' => 'modal',
|
||||
'data-target' => '#confirm-delete',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$actionsLeft .= Display::url(
|
||||
Display::return_icon('export_pdf.png', get_lang('ExportAllExercisesAllResults'), [], ICON_SIZE_MEDIUM),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/export/export_exercise_results.php?'.api_get_cidreq()
|
||||
);
|
||||
|
||||
if ($limitTeacherAccess) {
|
||||
if (api_is_platform_admin()) {
|
||||
$actionsLeft .= $cleanAll;
|
||||
}
|
||||
} else {
|
||||
$actionsLeft .= $cleanAll;
|
||||
}
|
||||
|
||||
// Create a search-box
|
||||
$form = new FormValidator('search_simple', 'get', $currentUrl, null, null, FormValidator::LAYOUT_INLINE);
|
||||
$form->addCourseHiddenParams();
|
||||
|
||||
if (api_get_configuration_value('allow_exercise_categories')) {
|
||||
$manager = new ExerciseCategoryManager();
|
||||
$options = $manager->getCategoriesForSelect(api_get_course_int_id());
|
||||
if (!empty($options)) {
|
||||
$form->addSelect(
|
||||
'category_id',
|
||||
get_lang('Category'),
|
||||
$options,
|
||||
['placeholder' => get_lang('SelectAnOption'), 'disable_js' => true]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$form->addText(
|
||||
'keyword',
|
||||
get_lang('Search'),
|
||||
false,
|
||||
[
|
||||
'aria-label' => get_lang('Search'),
|
||||
]
|
||||
);
|
||||
$form->addButtonSearch(get_lang('Search'));
|
||||
$actionsRight = $form->returnForm();
|
||||
}
|
||||
|
||||
if ($is_allowedToEdit) {
|
||||
echo Display::toolbarAction(
|
||||
'toolbarUser',
|
||||
[$actionsLeft, '', $actionsRight],
|
||||
[6, 1, 5]
|
||||
);
|
||||
}
|
||||
|
||||
if (api_get_configuration_value('allow_exercise_categories') === false) {
|
||||
echo Exercise::exerciseGrid(0, $keyword);
|
||||
} else {
|
||||
if (empty($categoryId)) {
|
||||
echo Exercise::exerciseGrid(0, $keyword);
|
||||
$counter = 0;
|
||||
$manager = new ExerciseCategoryManager();
|
||||
$categories = $manager->getCategories($courseId);
|
||||
$modifyUrl = api_get_self().'?'.api_get_cidreq();
|
||||
$total = count($categories);
|
||||
$upIcon = Display::return_icon('up.png', get_lang('MoveUp'));
|
||||
$downIcon = Display::return_icon('down.png', get_lang('MoveDown'));
|
||||
/** @var \Chamilo\CourseBundle\Entity\CExerciseCategory $category */
|
||||
foreach ($categories as $category) {
|
||||
$categoryIdItem = $category->getId();
|
||||
$up = '';
|
||||
$down = '';
|
||||
if ($is_allowedToEdit) {
|
||||
$up = Display::url($upIcon, $modifyUrl.'&action=up_category&category_id_edit='.$categoryIdItem);
|
||||
if (0 === $counter) {
|
||||
$up = Display::url(Display::return_icon('up_na.png'), '#');
|
||||
}
|
||||
$down = Display::url($downIcon, $modifyUrl.'&action=down_category&category_id_edit='.$categoryIdItem);
|
||||
$counter++;
|
||||
|
||||
if ($total === $counter) {
|
||||
$down = Display::url(Display::return_icon('down_na.png'), '#');
|
||||
}
|
||||
}
|
||||
echo Display::page_subheader($category->getName().$up.$down);
|
||||
echo Exercise::exerciseGrid($category->getId(), $keyword);
|
||||
}
|
||||
} else {
|
||||
$manager = new ExerciseCategoryManager();
|
||||
$category = $manager->get($categoryId);
|
||||
echo Display::page_subheader($category['name']);
|
||||
echo Exercise::exerciseGrid($category['iid'], $keyword);
|
||||
}
|
||||
}
|
||||
|
||||
echo '<div class="modal fade" id="NotificarUsuarios" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="'.get_lang('Close').'">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="#" class="form-horizontal">
|
||||
<div class="row">
|
||||
<div class="col-md-6" id="myModalLabel">'.get_lang('EmailNotifySubscription').'</div>
|
||||
<div class="col-md-6">
|
||||
<select class="selectpicker form-control" multiple="multiple" id="toUsers" name="toUsers">
|
||||
<option value="">-</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input class="hidden" id="urlTo" type="hidden">
|
||||
</form>
|
||||
<div class="clearfix clear-fix"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" onclick="sendNotificationToUsers()" data-dismiss="modal">'
|
||||
.get_lang('SendMailToUsers').'
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">'.get_lang('Close').'</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
if ('learnpath' !== $origin) {
|
||||
// We are not in learnpath tool
|
||||
Display::display_footer();
|
||||
}
|
||||
224
main/exercise/exercise_admin.php
Normal file
224
main/exercise/exercise_admin.php
Normal file
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Exercise administration
|
||||
* This script allows to manage an exercise. It is included from
|
||||
* the script admin.php.
|
||||
*
|
||||
* @author Olivier Brouckaert, Julio Montoya
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
if (!api_is_allowed_to_edit(null, true)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$htmlHeadXtra[] = '<script>
|
||||
function activate_start_date() {
|
||||
if(document.getElementById(\'start_date_div\').style.display == \'none\') {
|
||||
document.getElementById(\'start_date_div\').style.display = \'block\';
|
||||
} else {
|
||||
document.getElementById(\'start_date_div\').style.display = \'none\';
|
||||
}
|
||||
}
|
||||
|
||||
function activate_end_date() {
|
||||
if(document.getElementById(\'end_date_div\').style.display == \'none\') {
|
||||
document.getElementById(\'end_date_div\').style.display = \'block\';
|
||||
} else {
|
||||
document.getElementById(\'end_date_div\').style.display = \'none\';
|
||||
}
|
||||
}
|
||||
|
||||
function feedbackselection() {
|
||||
var index = document.exercise_admin.exerciseFeedbackType.selectedIndex;
|
||||
|
||||
if (index == \'1\') {
|
||||
document.exercise_admin.exerciseType[1].checked=true;
|
||||
document.exercise_admin.exerciseType[0].disabled=true;
|
||||
} else {
|
||||
document.exercise_admin.exerciseType[0].disabled=false;
|
||||
}
|
||||
}
|
||||
|
||||
function option_time_expired() {
|
||||
if(document.getElementById(\'timercontrol\').style.display == \'none\') {
|
||||
document.getElementById(\'timercontrol\').style.display = \'block\';
|
||||
} else {
|
||||
document.getElementById(\'timercontrol\').style.display = \'none\';
|
||||
}
|
||||
}
|
||||
|
||||
function check_per_page_one() {
|
||||
//document.getElementById(\'exerciseType_0\').checked=true;
|
||||
}
|
||||
|
||||
function check_per_page_all() {
|
||||
if (document.getElementById(\'exerciseType_1\') && document.getElementById(\'exerciseType_1\').checked) {
|
||||
document.getElementById(\'exerciseType_0\').checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function check_feedback() {
|
||||
if (document.getElementById(\'result_disabled_1\').checked == true) {
|
||||
document.getElementById(\'result_disabled_0\').checked = true;
|
||||
}
|
||||
|
||||
if (document.getElementById(\'exerciseType_0\').checked == true) {
|
||||
document.getElementById(\'result_disabled_0\').checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function check_direct_feedback() {
|
||||
document.getElementById(\'option_page_one\').checked = true;
|
||||
document.getElementById(\'result_disabled_0\').checked = true;
|
||||
}
|
||||
|
||||
function check_results_disabled() {
|
||||
document.getElementById(\'exerciseType_2\').checked = true;
|
||||
}
|
||||
|
||||
function disabledHideRandom() {
|
||||
$("#hidden_random option:eq(0)").prop("selected", true);
|
||||
$("#hidden_random").hide();
|
||||
}
|
||||
|
||||
function checkQuestionSelection() {
|
||||
var selection = $("#questionSelection option:selected").val();
|
||||
switch (selection) {
|
||||
case "'.EX_Q_SELECTION_ORDERED.'":
|
||||
disabledHideRandom();
|
||||
$("#hidden_matrix").hide();
|
||||
break;
|
||||
case "'.EX_Q_SELECTION_RANDOM.'":
|
||||
$("#hidden_random").show();
|
||||
$("#hidden_matrix").hide();
|
||||
break;
|
||||
case "'.EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED.'":
|
||||
disabledHideRandom();
|
||||
$("#hidden_matrix").show();
|
||||
break;
|
||||
case "per_categories":
|
||||
$("#questionSelection option:eq('.EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED.')").prop("selected", true);
|
||||
disabledHideRandom();
|
||||
$("#hidden_matrix").show();
|
||||
break;
|
||||
default:
|
||||
disabledHideRandom();
|
||||
$("#hidden_matrix").show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setFocus(){
|
||||
$("#exercise_title").focus();
|
||||
}
|
||||
|
||||
// to correct #4029 Random and number of attempt menu empty added window.onload=advanced_parameters;
|
||||
$(function() {
|
||||
setFocus();
|
||||
});
|
||||
</script>';
|
||||
|
||||
$objExercise = new Exercise();
|
||||
$course_id = api_get_course_int_id();
|
||||
|
||||
if (isset($_GET['exerciseId'])) {
|
||||
$form = new FormValidator(
|
||||
'exercise_admin',
|
||||
'post',
|
||||
api_get_self().'?'.api_get_cidreq().'&exerciseId='.intval($_GET['exerciseId'])
|
||||
);
|
||||
$objExercise->read($_GET['exerciseId'], false);
|
||||
$form->addElement('hidden', 'edit', 'true');
|
||||
} else {
|
||||
$form = new FormValidator(
|
||||
'exercise_admin',
|
||||
'post',
|
||||
api_get_self().'?'.api_get_cidreq()
|
||||
);
|
||||
$form->addElement('hidden', 'edit', 'false');
|
||||
}
|
||||
|
||||
$objExercise->createForm($form);
|
||||
|
||||
if ($form->validate()) {
|
||||
$objExercise->processCreation($form);
|
||||
if ($form->getSubmitValue('edit') === 'true') {
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('ExerciseEdited'), 'success')
|
||||
);
|
||||
} else {
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('ExerciseAdded'), 'success')
|
||||
);
|
||||
}
|
||||
$exercise_id = $objExercise->iid;
|
||||
Session::erase('objExercise');
|
||||
header('Location:admin.php?exerciseId='.$exercise_id.'&'.api_get_cidreq());
|
||||
exit;
|
||||
} else {
|
||||
if (api_is_in_gradebook()) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => Category::getUrl(),
|
||||
'name' => get_lang('ToolGradebook'),
|
||||
];
|
||||
}
|
||||
$nameTools = get_lang('ExerciseManagement');
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'admin.php?exerciseId='.$objExercise->iid.'&'.api_get_cidreq(),
|
||||
'name' => $objExercise->selectTitle(true),
|
||||
];
|
||||
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
|
||||
echo '<div class="actions">';
|
||||
if ($objExercise->iid != 0) {
|
||||
echo '<a href="admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid.'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
} else {
|
||||
if (!empty($_GET['lp_id']) || !empty($_POST['lp_id'])) {
|
||||
if (!empty($_POST['lp_id'])) {
|
||||
$lp_id = $_POST['lp_id'];
|
||||
//TODO:this remains to be implemented after press the first post
|
||||
} else {
|
||||
$lp_id = $_GET['lp_id'];
|
||||
}
|
||||
$lp_id = (int) $lp_id;
|
||||
echo "<a
|
||||
href=\"../lp/lp_controller.php?".api_get_cidreq()."&gradebook=&action=add_item&type=step&lp_id=".$lp_id."#resource_tab-2\">".
|
||||
Display::return_icon('back.png', get_lang("BackTo").' '.get_lang('LearningPaths'), '', ICON_SIZE_MEDIUM)."</a>";
|
||||
} else {
|
||||
echo '<a href="exercise.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).
|
||||
'</a>';
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
if (in_array($objExercise->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
|
||||
echo Display::return_message(get_lang('DirectFeedbackCantModifyTypeQuestion'));
|
||||
}
|
||||
|
||||
if (api_get_setting('search_enabled') === 'true' &&
|
||||
!extension_loaded('xapian')
|
||||
) {
|
||||
echo Display::return_message(get_lang('SearchXapianModuleNotInstalled'), 'error');
|
||||
}
|
||||
|
||||
// to hide the exercise description
|
||||
echo '<style> .media { display:none;}</style>';
|
||||
$form->display();
|
||||
}
|
||||
Display::display_footer();
|
||||
137
main/exercise/exercise_global_report.php
Normal file
137
main/exercise/exercise_global_report.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
|
||||
$TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
|
||||
$TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
$TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
|
||||
$courseId = api_get_course_int_id();
|
||||
$courseCode = api_get_course_id();
|
||||
$data = [];
|
||||
|
||||
$students = CourseManager::get_student_list_from_course_code($courseCode);
|
||||
$categories = TestCategory::getCategoryListInfo('', $courseId);
|
||||
$table = Database::get_course_table(TABLE_QUIZ_TEST);
|
||||
$sql = "SELECT iid, title FROM $table
|
||||
WHERE c_id = $courseId AND active <> -1
|
||||
ORDER by iid";
|
||||
$result = Database::query($sql);
|
||||
$exercises = Database::store_result($result);
|
||||
$list = [];
|
||||
$header = [];
|
||||
|
||||
$header[] = get_lang('Username');
|
||||
$header[] = get_lang('FirstName');
|
||||
$header[] = get_lang('LastName');
|
||||
$header[] = get_lang('Email');
|
||||
$header[] = get_lang('OfficialCode');
|
||||
|
||||
/** @var TestCategory $categoryInfo */
|
||||
foreach ($categories as $categoryInfo) {
|
||||
$header[] = 'Aciertos: '.$categoryInfo->name;
|
||||
$header[] = 'Errores: '.$categoryInfo->name;
|
||||
$header[] = 'Omisiones: '.$categoryInfo->name;
|
||||
$header[] = 'Puntos: '.$categoryInfo->name;
|
||||
}
|
||||
|
||||
foreach ($exercises as $exerciseInfo) {
|
||||
$header[] = $exerciseInfo['title'];
|
||||
}
|
||||
|
||||
$list[] = $header;
|
||||
$objExercise = new Exercise();
|
||||
|
||||
foreach ($students as $studentInfo) {
|
||||
$studentId = $studentInfo['user_id'];
|
||||
$data = [];
|
||||
$data[] = $studentInfo['username'];
|
||||
$data[] = $studentInfo['lastname'];
|
||||
$data[] = $studentInfo['firstname'];
|
||||
$data[] = $studentInfo['email'];
|
||||
$data[] = $studentInfo['official_code'];
|
||||
$userExerciseData = [];
|
||||
$categoryData = [];
|
||||
foreach ($exercises as $exerciseInfo) {
|
||||
$exerciseId = $exerciseInfo['iid'];
|
||||
$objExercise->read($exerciseId);
|
||||
|
||||
$sql = "SELECT exe_id, data_tracking
|
||||
FROM $TBL_TRACK_EXERCISES
|
||||
WHERE
|
||||
c_id = $courseId AND
|
||||
exe_user_id = $studentId AND
|
||||
exe_exo_id = $exerciseId AND
|
||||
status = ''
|
||||
LIMIT 1";
|
||||
$result = Database::query($sql);
|
||||
$attempt = Database::fetch_array($result, 'ASSOC');
|
||||
if (empty($attempt)) {
|
||||
$userExerciseData[$exerciseId] = null;
|
||||
continue;
|
||||
}
|
||||
$exeId = $attempt['exe_id'];
|
||||
|
||||
ob_start();
|
||||
$stats = ExerciseLib::displayQuestionListByAttempt(
|
||||
$objExercise,
|
||||
$exeId,
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
ob_end_clean();
|
||||
foreach ($categories as $categoryInfo) {
|
||||
if (!($categoryInfo instanceof TestCategory)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($stats['category_list'][$categoryInfo->iid])) {
|
||||
$categoryItem = $stats['category_list'][$categoryInfo->iid];
|
||||
if (!isset($categoryData[$categoryInfo->iid])) {
|
||||
$categoryData[$categoryInfo->iid]['passed'] = 0;
|
||||
$categoryData[$categoryInfo->iid]['wrong'] = 0;
|
||||
$categoryData[$categoryInfo->iid]['no_answer'] = 0;
|
||||
$categoryData[$categoryInfo->iid]['score'] = 0;
|
||||
}
|
||||
$categoryData[$categoryInfo->iid]['passed'] += $categoryItem['passed'];
|
||||
$categoryData[$categoryInfo->iid]['wrong'] += $categoryItem['wrong'];
|
||||
$categoryData[$categoryInfo->iid]['no_answer'] += $categoryItem['no_answer'];
|
||||
$categoryData[$categoryInfo->iid]['score'] += $categoryItem['score'];
|
||||
}
|
||||
}
|
||||
$userExerciseData[$exerciseId] = $stats['total_score'];
|
||||
}
|
||||
foreach ($categories as $categoryInfo) {
|
||||
if (isset($categoryData[$categoryInfo->iid])) {
|
||||
$data[] = $categoryData[$categoryInfo->iid]['passed'];
|
||||
$data[] = $categoryData[$categoryInfo->iid]['wrong'];
|
||||
$data[] = $categoryData[$categoryInfo->iid]['no_answer'];
|
||||
$data[] = $categoryData[$categoryInfo->iid]['score'];
|
||||
} else {
|
||||
$data[] = null;
|
||||
$data[] = null;
|
||||
$data[] = null;
|
||||
$data[] = null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($exercises as $exerciseInfo) {
|
||||
$exerciseId = $exerciseInfo['iid'];
|
||||
if (isset($userExerciseData[$exerciseId])) {
|
||||
$data[] = $userExerciseData[$exerciseId];
|
||||
} else {
|
||||
$data[] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$list[] = $data;
|
||||
}
|
||||
|
||||
$filePath = Export::arrayToCsv($list, get_lang('Report'), true);
|
||||
DocumentManager::file_send_for_download($filePath, true, get_lang('Report').'.csv');
|
||||
91
main/exercise/exercise_history.php
Normal file
91
main/exercise/exercise_history.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Exercise list: This script shows the list of exercises for administrators and students.
|
||||
*
|
||||
* @author Olivier Brouckaert, original author
|
||||
* @author Denes Nagy, HotPotatoes integration
|
||||
* @author Wolfgang Schneider, code/html cleanup
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$this_section = SECTION_COURSES;
|
||||
api_protect_course_script(true);
|
||||
|
||||
$show = isset($_GET['show']) && $_GET['show'] === 'result' ? 'result' : 'test';
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
$is_tutor = api_is_allowed_to_edit(true);
|
||||
|
||||
if (!$is_allowedToEdit) {
|
||||
header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise_report.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise_report.php?filter=2&'.api_get_cidreq(),
|
||||
'name' => get_lang('StudentScore'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise_history.php?exe_id='.intval($_GET['exe_id']).'&'.api_get_cidreq(),
|
||||
'name' => get_lang('Details'),
|
||||
];
|
||||
|
||||
$TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
|
||||
$TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
|
||||
$TBL_EXERCISES_QUESTION = Database::get_course_table(TABLE_QUIZ_QUESTION);
|
||||
$TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
|
||||
if (isset($_GET['message'])) {
|
||||
if (in_array($_GET['message'], ['ExerciseEdited'])) {
|
||||
$my_message_history = Security::remove_XSS($_GET['message']);
|
||||
echo Display::return_message(get_lang($my_message_history), 'confirm');
|
||||
}
|
||||
}
|
||||
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="exercise_report.php?'.api_get_cidreq().'&filter=2">'.
|
||||
Display::return_icon('back.png', get_lang('BackToResultList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
echo '</div>';
|
||||
|
||||
?>
|
||||
|
||||
<table class="table table-hover table-striped data_table">
|
||||
<tr>
|
||||
<th><?php echo get_lang('Question'); ?></th>
|
||||
<th width="50px"><?php echo get_lang('Value'); ?></th>
|
||||
<th><?php echo get_lang('Feedback'); ?></th>
|
||||
<th><?php echo get_lang('Author'); ?></th>
|
||||
<th width="160px"><?php echo get_lang('Date'); ?></th>
|
||||
</tr>
|
||||
<?php
|
||||
|
||||
$sql = "SELECT *, quiz_question.question, firstname, lastname
|
||||
FROM $TBL_TRACK_ATTEMPT_RECORDING t, $TBL_USER,
|
||||
$TBL_EXERCISES_QUESTION quiz_question
|
||||
WHERE
|
||||
quiz_question.iid = question_id AND
|
||||
user_id = author AND
|
||||
exe_id = '".(int) $_GET['exe_id']."'
|
||||
ORDER BY position";
|
||||
$query = Database::query($sql);
|
||||
while ($row = Database::fetch_array($query)) {
|
||||
echo '<tr>';
|
||||
echo '<td>'.$row['question'].'</td>';
|
||||
echo '<td>'.$row['marks'].'</td>';
|
||||
if (!empty($row['teacher_comment'])) {
|
||||
echo '<td>'.$row['teacher_comment'].'</td>';
|
||||
} else {
|
||||
echo '<td>'.get_lang('WithoutComment').'</td>';
|
||||
}
|
||||
echo '<td>'.(empty($row['firstname']) && empty($row['lastname']) ? '<i>'.get_lang('OriginalValue').'</i>' : api_get_person_name($row['firstname'], $row['lastname'])).'</td>';
|
||||
echo '<td>'.api_convert_and_format_date($row['insert_date'], DATE_TIME_FORMAT_LONG).'</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
Display::display_footer();
|
||||
223
main/exercise/exercise_question_reminder.php
Normal file
223
main/exercise/exercise_question_reminder.php
Normal file
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
|
||||
if (false === api_get_configuration_value('block_category_questions')) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
api_protect_course_script(true);
|
||||
$origin = api_get_origin();
|
||||
$learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
|
||||
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
|
||||
$learnpath_item_view_id = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0;
|
||||
$exerciseId = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : 0;
|
||||
$currentQuestion = isset($_REQUEST['num']) ? (int) $_REQUEST['num'] : 1;
|
||||
$exeId = isset($_REQUEST['exe_id']) ? (int) $_REQUEST['exe_id'] : 0;
|
||||
$questionCategoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
|
||||
$validateCategory = isset($_REQUEST['validate']) && 1 === (int) $_REQUEST['validate'];
|
||||
|
||||
/** @var Exercise $objExercise */
|
||||
$objExercise = null;
|
||||
$exerciseInSession = Session::read('objExercise');
|
||||
if (!empty($exerciseInSession)) {
|
||||
$objExercise = $exerciseInSession;
|
||||
}
|
||||
|
||||
$category = new TestCategory();
|
||||
$categoryObj = $category->getCategory($questionCategoryId);
|
||||
|
||||
if (empty($objExercise) || empty($questionCategoryId) || empty($exeId) || empty($categoryObj)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$categoryId = (int) $categoryObj->iid;
|
||||
$params = "exe_id=$exeId&exerciseId=$exerciseId&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id&".api_get_cidreq();
|
||||
$url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit.php?'.$params;
|
||||
$validateUrl = api_get_path(WEB_CODE_PATH).'exercise/exercise_question_reminder.php?'.
|
||||
$params.'&category_id='.$categoryId.'&validate=1';
|
||||
|
||||
$time_control = false;
|
||||
$clock_expired_time = ExerciseLib::get_session_time_control_key(
|
||||
$objExercise->iid,
|
||||
$learnpath_id,
|
||||
$learnpath_item_id
|
||||
);
|
||||
|
||||
if ($objExercise->expired_time != 0 && !empty($clock_expired_time)) {
|
||||
$time_control = true;
|
||||
}
|
||||
|
||||
if ($time_control) {
|
||||
// Get time left for expiring time
|
||||
$time_left = api_strtotime($clock_expired_time, 'UTC') - time();
|
||||
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/stylesheet/jquery.epiclock.css');
|
||||
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.epiclock.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
|
||||
$htmlHeadXtra[] = $objExercise->showTimeControlJS($time_left);
|
||||
}
|
||||
$htmlHeadXtra[] = api_get_css_asset('pretty-checkbox/dist/pretty-checkbox.min.css');
|
||||
|
||||
$trackInfo = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
|
||||
if (empty($trackInfo)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
$blockedCategories = [];
|
||||
if (isset($trackInfo['blocked_categories']) && !empty($trackInfo['blocked_categories'])) {
|
||||
$blockedCategories = explode(',', $trackInfo['blocked_categories']);
|
||||
}
|
||||
|
||||
if ($validateCategory) {
|
||||
$blockedCategories[] = $categoryId;
|
||||
$blockedCategories = array_unique($blockedCategories);
|
||||
$value = implode(',', $blockedCategories);
|
||||
$value = Database::escape_string($value);
|
||||
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
$sql = "UPDATE $table
|
||||
SET blocked_categories = '$value'
|
||||
WHERE exe_id = $exeId";
|
||||
Database::query($sql);
|
||||
|
||||
// Cleaning old remind list.
|
||||
$objExercise->removeAllQuestionToRemind($exeId);
|
||||
api_location($url.'&num='.$currentQuestion);
|
||||
}
|
||||
|
||||
$nameTools = get_lang('Exercises');
|
||||
$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
|
||||
$hideHeaderAndFooter = in_array($origin, ['learnpath', 'embeddable', 'iframe']);
|
||||
|
||||
if (!$hideHeaderAndFooter) {
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
} else {
|
||||
Display::display_reduced_header();
|
||||
}
|
||||
|
||||
// I'm in a preview mode as course admin. Display the action menu.
|
||||
if (!$hideHeaderAndFooter && api_is_course_admin()) {
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid.'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), [], 32).'</a>';
|
||||
echo '<a href="exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->iid.'">'.
|
||||
Display::return_icon('edit.png', get_lang('ModifyExercise'), [], 32).'</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo Display::page_header($categoryObj->name);
|
||||
echo '<p>'.Security::remove_XSS($categoryObj->description).'</p>';
|
||||
echo '<p>'.get_lang('BlockCategoryExplanation').'</p>';
|
||||
|
||||
$categoryList = Session::read('categoryList');
|
||||
$disableAllQuestions = '';
|
||||
$questionList = [];
|
||||
if (isset($categoryList[$categoryId])) {
|
||||
$questionList = $categoryList[$categoryId];
|
||||
}
|
||||
if ($objExercise->review_answers) {
|
||||
$disableAllQuestions = 'changeOptionStatus(0);';
|
||||
echo $objExercise->getReminderTable($questionList, $trackInfo);
|
||||
}
|
||||
|
||||
if ($time_control) {
|
||||
echo $objExercise->returnTimeLeftDiv();
|
||||
}
|
||||
|
||||
echo Display::div('', ['id' => 'message']);
|
||||
$previousQuestion = $currentQuestion - 1;
|
||||
|
||||
$nextQuestion = $currentQuestion + 1;
|
||||
if (!empty($questionList)) {
|
||||
$firstQuestionOfCategory = end($questionList);
|
||||
$dataTracking = explode(',', $trackInfo['data_tracking']);
|
||||
$index = 0;
|
||||
foreach ($dataTracking as $index => $question) {
|
||||
if ($firstQuestionOfCategory == $question) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$nextQuestion = $index + 1;
|
||||
}
|
||||
|
||||
echo '<script>
|
||||
function goBack() {
|
||||
window.location = "'.$url.'&num='.$previousQuestion.'";
|
||||
}
|
||||
|
||||
function continueExercise() {
|
||||
'.$disableAllQuestions.'
|
||||
window.location = "'.$validateUrl.'&num='.$nextQuestion.'";
|
||||
}
|
||||
|
||||
function final_submit() {
|
||||
window.location = "'.api_get_path(WEB_CODE_PATH).'exercise/exercise_result.php?'.api_get_cidreq().'&exe_id='.$exeId.'&" + lp_data;
|
||||
}
|
||||
</script>';
|
||||
|
||||
$exercise_result = $objExercise->getUserAnswersSavedInExercise($exeId);
|
||||
echo '<div class="clear"></div><br />';
|
||||
$table = '';
|
||||
$counter = 0;
|
||||
echo Display::div($table, ['class' => 'question-check-test']);
|
||||
|
||||
$exerciseActions = '';
|
||||
if (!in_array($categoryId, $blockedCategories)) {
|
||||
$exerciseActions = ' '.Display::url(
|
||||
get_lang('GoBack'),
|
||||
'javascript://',
|
||||
['onclick' => 'goBack();', 'class' => 'btn btn-default']
|
||||
);
|
||||
}
|
||||
|
||||
if ($objExercise->review_answers) {
|
||||
$exerciseActions .= Display::url(
|
||||
get_lang('ReviewQuestions'),
|
||||
'javascript://',
|
||||
['onclick' => 'reviewQuestions();', 'class' => 'btn btn-primary']
|
||||
);
|
||||
|
||||
$exerciseActions .= ' '.Display::url(
|
||||
get_lang('SelectAll'),
|
||||
'javascript://',
|
||||
['onclick' => 'selectAll();', 'class' => 'btn btn-default']
|
||||
);
|
||||
|
||||
$exerciseActions .= ' '.Display::url(
|
||||
get_lang('UnSelectAll'),
|
||||
'javascript://',
|
||||
['onclick' => 'changeOptionStatus(0);', 'class' => 'btn btn-default']
|
||||
);
|
||||
}
|
||||
|
||||
end($categoryList);
|
||||
|
||||
// This is the last category
|
||||
if (key($categoryList) === $categoryId) {
|
||||
$exerciseActions .= ' '.Display::url(
|
||||
get_lang('EndTest'),
|
||||
'javascript://',
|
||||
['onclick' => 'final_submit();', 'class' => 'btn btn-warning']
|
||||
);
|
||||
} else {
|
||||
$exerciseActions .= ' '.Display::url(
|
||||
get_lang('ContinueTest'),
|
||||
'javascript://',
|
||||
['onclick' => 'continueExercise();', 'class' => 'btn btn-primary']
|
||||
);
|
||||
}
|
||||
|
||||
echo Display::div('', ['class' => 'clear']);
|
||||
echo Display::div($exerciseActions, ['class' => 'form-actions']);
|
||||
|
||||
if (!$hideHeaderAndFooter) {
|
||||
// We are not in learnpath tool or embeddable quiz
|
||||
Display::display_footer();
|
||||
} else {
|
||||
Display::display_reduced_footer();
|
||||
}
|
||||
153
main/exercise/exercise_reminder.php
Normal file
153
main/exercise/exercise_reminder.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Exercise reminder overview
|
||||
* Then it shows the results on the screen.
|
||||
*
|
||||
* @author Julio Montoya switchable fill in blank option added
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
$origin = api_get_origin();
|
||||
|
||||
$learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
|
||||
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
|
||||
$learnpath_item_view_id = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0;
|
||||
$exerciseId = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : 0;
|
||||
/** @var Exercise $objExercise */
|
||||
$objExercise = null;
|
||||
$exerciseInSession = Session::read('objExercise');
|
||||
if (!empty($exerciseInSession)) {
|
||||
$objExercise = $exerciseInSession;
|
||||
}
|
||||
|
||||
if (!$objExercise) {
|
||||
// Redirect to the exercise overview
|
||||
// Check if the exe_id exists
|
||||
header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/overview.php?exerciseId='.$exerciseId.'&'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
$time_control = false;
|
||||
$clock_expired_time = ExerciseLib::get_session_time_control_key(
|
||||
$objExercise->iid,
|
||||
$learnpath_id,
|
||||
$learnpath_item_id
|
||||
);
|
||||
|
||||
if ($objExercise->expired_time != 0 && !empty($clock_expired_time)) {
|
||||
$time_control = true;
|
||||
}
|
||||
|
||||
if ($time_control) {
|
||||
// Get time left for expiring time
|
||||
$time_left = api_strtotime($clock_expired_time, 'UTC') - time();
|
||||
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/stylesheet/jquery.epiclock.css');
|
||||
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.epiclock.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
|
||||
$htmlHeadXtra[] = $objExercise->showTimeControlJS($time_left);
|
||||
}
|
||||
|
||||
$htmlHeadXtra[] = api_get_css_asset('pretty-checkbox/dist/pretty-checkbox.min.css');
|
||||
|
||||
$exe_id = 0;
|
||||
if (isset($_GET['exe_id'])) {
|
||||
$exe_id = (int) $_GET['exe_id'];
|
||||
Session::write('exe_id', $exe_id);
|
||||
}
|
||||
|
||||
$exe_id = (int) Session::read('exe_id');
|
||||
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exe_id);
|
||||
$question_list = [];
|
||||
if (!empty($exercise_stat_info['data_tracking'])) {
|
||||
$question_list = explode(',', $exercise_stat_info['data_tracking']);
|
||||
}
|
||||
|
||||
if (empty($exercise_stat_info) || empty($question_list) || $exercise_stat_info['exe_user_id'] != api_get_user_id()) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$nameTools = get_lang('Exercises');
|
||||
$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
|
||||
$hideHeaderAndFooter = in_array($origin, ['learnpath', 'embeddable', 'iframe']);
|
||||
|
||||
if (!$hideHeaderAndFooter) {
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
} else {
|
||||
Display::display_reduced_header();
|
||||
}
|
||||
|
||||
// I'm in a preview mode as course admin. Display the action menu.
|
||||
if (!$hideHeaderAndFooter && api_is_course_admin()) {
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid.'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), [], 32).'</a>';
|
||||
echo '<a href="exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->iid.'">'.
|
||||
Display::return_icon('edit.png', get_lang('ModifyExercise'), [], 32).'</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo Display::page_header(get_lang('QuestionsToReview'));
|
||||
|
||||
if ($time_control) {
|
||||
echo $objExercise->returnTimeLeftDiv();
|
||||
}
|
||||
|
||||
$selectionType = $objExercise->getQuestionSelectionType();
|
||||
if (api_get_configuration_value('block_category_questions') &&
|
||||
ONE_PER_PAGE == $objExercise->type &&
|
||||
EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $selectionType
|
||||
) {
|
||||
$extraFieldValue = new ExtraFieldValue('exercise');
|
||||
$extraFieldData = $extraFieldValue->get_values_by_handler_and_field_variable($objExercise->iid, 'block_category');
|
||||
if ($extraFieldData && isset($extraFieldData['value']) && 1 === (int) $extraFieldData['value']) {
|
||||
// get last category question list
|
||||
$categoryList = Session::read('categoryList');
|
||||
$question_list = end($categoryList);
|
||||
}
|
||||
}
|
||||
|
||||
echo $objExercise->getReminderTable($question_list, $exercise_stat_info);
|
||||
|
||||
$exerciseActions = Display::url(
|
||||
get_lang('ReviewQuestions'),
|
||||
'javascript://',
|
||||
['onclick' => 'reviewQuestions();', 'class' => 'btn btn-primary']
|
||||
);
|
||||
|
||||
$exerciseActions .= ' '.Display::url(
|
||||
get_lang('SelectAll'),
|
||||
'javascript://',
|
||||
['onclick' => 'changeOptionStatus(1);', 'class' => 'btn btn-default']
|
||||
);
|
||||
|
||||
$exerciseActions .= ' '.Display::url(
|
||||
get_lang('UnSelectAll'),
|
||||
'javascript://',
|
||||
['onclick' => 'changeOptionStatus(0);', 'class' => 'btn btn-default']
|
||||
);
|
||||
|
||||
$exerciseActions .= ' '.Display::url(
|
||||
get_lang('EndTest'),
|
||||
'javascript://',
|
||||
['onclick' => 'final_submit();', 'class' => 'btn btn-warning']
|
||||
);
|
||||
|
||||
echo Display::div('', ['class' => 'clear']);
|
||||
echo Display::div($exerciseActions, ['class' => 'form-actions']);
|
||||
|
||||
if (!$hideHeaderAndFooter) {
|
||||
// We are not in learnpath tool or embeddable quiz
|
||||
Display::display_footer();
|
||||
} else {
|
||||
Display::display_reduced_footer();
|
||||
}
|
||||
998
main/exercise/exercise_report.php
Normal file
998
main/exercise/exercise_report.php
Normal file
@@ -0,0 +1,998 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Exercise list: This script shows the list of exercises for administrators and students.
|
||||
*
|
||||
* @author Julio Montoya <gugli100@gmail.com> jqgrid integration
|
||||
* Modified by hubert.borderiou (question category)
|
||||
*
|
||||
* @todo fix excel export
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
// Setting the tabs
|
||||
$this_section = SECTION_COURSES;
|
||||
$htmlHeadXtra[] = api_get_jqgrid_js();
|
||||
|
||||
$filter_user = isset($_REQUEST['filter_by_user']) ? (int) $_REQUEST['filter_by_user'] : null;
|
||||
$isBossOfStudent = false;
|
||||
if (!empty($filter_user) && api_is_student_boss()) {
|
||||
// Check if boss has access to user info.
|
||||
if (UserManager::userIsBossOfStudent(api_get_user_id(), $filter_user)) {
|
||||
$isBossOfStudent = true;
|
||||
} else {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
} else {
|
||||
api_protect_course_script(true, false, true);
|
||||
}
|
||||
|
||||
$limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
|
||||
$allowClean = Exercise::allowAction('clean_results');
|
||||
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
require_once 'hotpotatoes.lib.php';
|
||||
|
||||
$_course = api_get_course_info();
|
||||
|
||||
// document path
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
$origin = api_get_origin();
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true) ||
|
||||
api_is_drh() ||
|
||||
api_is_student_boss() ||
|
||||
api_is_session_admin();
|
||||
$is_tutor = api_is_allowed_to_edit(true);
|
||||
|
||||
$TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
$TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
|
||||
$TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
|
||||
$TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
|
||||
$allowCoachFeedbackExercises = api_get_setting('allow_coach_feedback_exercises') === 'true';
|
||||
$course_id = api_get_course_int_id();
|
||||
$exercise_id = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : 0;
|
||||
$locked = api_resource_is_locked_by_gradebook($exercise_id, LINK_EXERCISE);
|
||||
$sessionId = api_get_session_id();
|
||||
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : null;
|
||||
|
||||
if ('export_all_exercises_results' !== $action && !(isset($_GET['delete']) && $_GET['delete'] === 'delete')) {
|
||||
if (empty($exercise_id)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
}
|
||||
|
||||
$blockPage = true;
|
||||
if (empty($sessionId)) {
|
||||
if ($is_allowedToEdit) {
|
||||
$blockPage = false;
|
||||
}
|
||||
} else {
|
||||
if ($allowCoachFeedbackExercises && api_is_coach($sessionId, $course_id)) {
|
||||
$blockPage = false;
|
||||
} else {
|
||||
if ($is_allowedToEdit) {
|
||||
$blockPage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($blockPage) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
if (!empty($exercise_id)) {
|
||||
$parameters['exerciseId'] = $exercise_id;
|
||||
}
|
||||
|
||||
if (!empty($_GET['path'])) {
|
||||
$parameters['path'] = Security::remove_XSS($_GET['path']);
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['export_report']) && $_REQUEST['export_report'] == '1') {
|
||||
if (api_is_platform_admin() || api_is_course_admin() ||
|
||||
api_is_course_tutor() || api_is_session_general_coach()
|
||||
) {
|
||||
$loadExtraData = false;
|
||||
if (isset($_REQUEST['extra_data']) && $_REQUEST['extra_data'] == 1) {
|
||||
$loadExtraData = true;
|
||||
}
|
||||
|
||||
$includeAllUsers = false;
|
||||
if (isset($_REQUEST['include_all_users']) &&
|
||||
$_REQUEST['include_all_users'] == 1
|
||||
) {
|
||||
$includeAllUsers = true;
|
||||
}
|
||||
|
||||
$onlyBestAttempts = false;
|
||||
if (isset($_REQUEST['only_best_attempts']) &&
|
||||
$_REQUEST['only_best_attempts'] == 1
|
||||
) {
|
||||
$onlyBestAttempts = true;
|
||||
}
|
||||
|
||||
require_once 'exercise_result.class.php';
|
||||
$export = new ExerciseResult();
|
||||
$export->setIncludeAllUsers($includeAllUsers);
|
||||
$export->setOnlyBestAttempts($onlyBestAttempts);
|
||||
|
||||
switch ($_GET['export_format']) {
|
||||
case 'xls':
|
||||
$export->exportCompleteReportXLS(
|
||||
$documentPath,
|
||||
null,
|
||||
$loadExtraData,
|
||||
null,
|
||||
$exercise_id
|
||||
);
|
||||
exit;
|
||||
break;
|
||||
case 'csv':
|
||||
default:
|
||||
$export->exportCompleteReportCSV(
|
||||
$documentPath,
|
||||
null,
|
||||
$loadExtraData,
|
||||
null,
|
||||
$exercise_id
|
||||
);
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
}
|
||||
|
||||
$objExerciseTmp = new Exercise();
|
||||
$exerciseExists = $objExerciseTmp->read($exercise_id);
|
||||
|
||||
switch ($action) {
|
||||
case 'export_all_results':
|
||||
$sessionId = api_get_session_id();
|
||||
$courseId = api_get_course_int_id();
|
||||
ExerciseLib::exportExerciseAllResultsZip($sessionId, $courseId, $exercise_id);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Send student email @todo move this code in a class, library
|
||||
if (isset($_REQUEST['comments']) &&
|
||||
$_REQUEST['comments'] === 'update' &&
|
||||
($is_allowedToEdit || $is_tutor || $allowCoachFeedbackExercises)
|
||||
) {
|
||||
// Filtered by post-condition
|
||||
$id = (int) $_GET['exeid'];
|
||||
$track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($id);
|
||||
if (empty($track_exercise_info)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
$student_id = (int) $track_exercise_info['exe_user_id'];
|
||||
$session_id = $track_exercise_info['session_id'];
|
||||
$lp_id = $track_exercise_info['orig_lp_id'];
|
||||
$lpItemId = $track_exercise_info['orig_lp_item_id'];
|
||||
$lp_item_view_id = (int) $track_exercise_info['orig_lp_item_view_id'];
|
||||
$exerciseId = $track_exercise_info['exe_exo_id'];
|
||||
$exeWeighting = $track_exercise_info['exe_weighting'];
|
||||
|
||||
$attemptData = Event::get_exercise_results_by_attempt($id);
|
||||
$questionListData = [];
|
||||
if ($attemptData && $attemptData[$id] && $attemptData[$id]['question_list']) {
|
||||
$questionListData = $attemptData[$id]['question_list'];
|
||||
}
|
||||
|
||||
$post_content_id = [];
|
||||
$comments_exist = false;
|
||||
$questionListInPost = [];
|
||||
foreach ($_POST as $key_index => $key_value) {
|
||||
$my_post_info = explode('_', $key_index);
|
||||
$post_content_id[] = isset($my_post_info[1]) ? $my_post_info[1] : null;
|
||||
if ($my_post_info[0] === 'comments') {
|
||||
$comments_exist = true;
|
||||
$questionListInPost[] = $my_post_info[1];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($questionListInPost as $questionId) {
|
||||
$marks = $_POST['marks_'.$questionId] ?? 0;
|
||||
$my_comments = $_POST['comments_'.$questionId] ?? '';
|
||||
$params = [
|
||||
'teacher_comment' => $my_comments,
|
||||
];
|
||||
$question = Question::read($questionId);
|
||||
if (false === $question) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// From the database.
|
||||
$marksFromDatabase = $questionListData[$questionId]['marks'];
|
||||
if (in_array($question->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER, ANSWER_IN_OFFICE_DOC])) {
|
||||
// From the form.
|
||||
$params['marks'] = $marks;
|
||||
if ($marksFromDatabase != $marks) {
|
||||
Event::addEvent(
|
||||
LOG_QUESTION_SCORE_UPDATE,
|
||||
LOG_EXERCISE_ATTEMPT_QUESTION_ID,
|
||||
[
|
||||
'exe_id' => $id,
|
||||
'question_id' => $questionId,
|
||||
'old_marks' => $marksFromDatabase,
|
||||
'new_marks' => $marks,
|
||||
]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$marks = $marksFromDatabase;
|
||||
}
|
||||
|
||||
Database::update(
|
||||
$TBL_TRACK_ATTEMPT,
|
||||
$params,
|
||||
['question_id = ? AND exe_id = ?' => [$questionId, $id]]
|
||||
);
|
||||
|
||||
$params = [
|
||||
'exe_id' => $id,
|
||||
'question_id' => $questionId,
|
||||
'marks' => $marks,
|
||||
'insert_date' => api_get_utc_datetime(),
|
||||
'author' => api_get_user_id(),
|
||||
'teacher_comment' => $my_comments,
|
||||
];
|
||||
Database::insert($TBL_TRACK_ATTEMPT_RECORDING, $params);
|
||||
}
|
||||
$useEvaluationPlugin = false;
|
||||
$pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
|
||||
|
||||
if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
|
||||
$formula = $pluginEvaluation->getFormulaForExercise($exerciseId);
|
||||
|
||||
if (!empty($formula)) {
|
||||
$useEvaluationPlugin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$useEvaluationPlugin) {
|
||||
$qry = 'SELECT DISTINCT question_id, marks
|
||||
FROM '.$TBL_TRACK_ATTEMPT.' WHERE exe_id = '.$id.'
|
||||
GROUP BY question_id';
|
||||
$res = Database::query($qry);
|
||||
$tot = 0;
|
||||
while ($row = Database::fetch_array($res, 'ASSOC')) {
|
||||
$marks = $row['marks'];
|
||||
if (!$objExerciseTmp->propagate_neg && $marks < 0) {
|
||||
continue;
|
||||
}
|
||||
$tot += $marks;
|
||||
}
|
||||
} else {
|
||||
$tot = $pluginEvaluation->getResultWithFormula($id, $formula);
|
||||
}
|
||||
|
||||
$totalScore = (float) $tot;
|
||||
|
||||
$sql = "UPDATE $TBL_TRACK_EXERCISES
|
||||
SET exe_result = '".$totalScore."'
|
||||
WHERE exe_id = ".$id;
|
||||
Database::query($sql);
|
||||
|
||||
// See BT#18165
|
||||
$remedialMessage = RemedialCoursePlugin::create()->getRemedialCourseList(
|
||||
$objExerciseTmp,
|
||||
$student_id,
|
||||
api_get_session_id(),
|
||||
true,
|
||||
$lp_id ?: 0,
|
||||
$lpItemId ?: 0
|
||||
);
|
||||
if (null != $remedialMessage) {
|
||||
Display::addFlash(
|
||||
Display::return_message($remedialMessage, 'warning', false)
|
||||
);
|
||||
}
|
||||
$advancedMessage = RemedialCoursePlugin::create()->getAdvancedCourseList(
|
||||
$objExerciseTmp,
|
||||
$student_id,
|
||||
api_get_session_id(),
|
||||
$lp_id ?: 0,
|
||||
$lpItemId ?: 0
|
||||
);
|
||||
if (!empty($advancedMessage)) {
|
||||
$message = Display::return_message(
|
||||
$advancedMessage,
|
||||
'info',
|
||||
false
|
||||
);
|
||||
}
|
||||
if (isset($_POST['send_notification'])) {
|
||||
//@todo move this somewhere else
|
||||
$subject = get_lang('ExamSheetVCC');
|
||||
$message = $_POST['notification_content'] ?? '';
|
||||
|
||||
$feedbackComments = ExerciseLib::getFeedbackComments($id);
|
||||
$message .= "<div style='padding:10px; border:1px solid #ddd; border-radius:5px;'>";
|
||||
$message .= $feedbackComments;
|
||||
$message .= "</div>";
|
||||
|
||||
MessageManager::send_message_simple(
|
||||
$student_id,
|
||||
$subject,
|
||||
$message,
|
||||
api_get_user_id()
|
||||
);
|
||||
if ($allowCoachFeedbackExercises) {
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('MessageSent'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$notifications = api_get_configuration_value('exercise_finished_notification_settings');
|
||||
if ($notifications) {
|
||||
$oldResultDisabled = $objExerciseTmp->results_disabled;
|
||||
$objExerciseTmp->results_disabled = RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS;
|
||||
|
||||
ob_start();
|
||||
$stats = ExerciseLib::displayQuestionListByAttempt(
|
||||
$objExerciseTmp,
|
||||
$track_exercise_info['exe_id'],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
api_get_configuration_value('quiz_results_answers_report'),
|
||||
false
|
||||
);
|
||||
$objExerciseTmp->results_disabled = $oldResultDisabled;
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
// Show all for teachers.
|
||||
$oldResultDisabled = $objExerciseTmp->results_disabled;
|
||||
$objExerciseTmp->results_disabled = RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS;
|
||||
$objExerciseTmp->forceShowExpectedChoiceColumn = true;
|
||||
ob_start();
|
||||
$statsTeacher = ExerciseLib::displayQuestionListByAttempt(
|
||||
$objExerciseTmp,
|
||||
$track_exercise_info['exe_id'],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
api_get_configuration_value('quiz_results_answers_report'),
|
||||
false
|
||||
);
|
||||
ob_end_clean();
|
||||
$objExerciseTmp->forceShowExpectedChoiceColumn = false;
|
||||
$objExerciseTmp->results_disabled = $oldResultDisabled;
|
||||
|
||||
$attemptCount = Event::getAttemptPosition(
|
||||
$track_exercise_info['exe_id'],
|
||||
$student_id,
|
||||
$objExerciseTmp->iid,
|
||||
$lp_id,
|
||||
$lpItemId,
|
||||
$lp_item_view_id
|
||||
);
|
||||
|
||||
ExerciseLib::sendNotification(
|
||||
$student_id,
|
||||
$objExerciseTmp,
|
||||
$track_exercise_info,
|
||||
api_get_course_info(),
|
||||
$attemptCount,
|
||||
$stats,
|
||||
$statsTeacher
|
||||
);
|
||||
}
|
||||
|
||||
// Updating LP score here
|
||||
if (!empty($lp_id) && !empty($lpItemId)) {
|
||||
$statusCondition = '';
|
||||
$item = new learnpathItem($lpItemId, api_get_user_id(), api_get_course_int_id());
|
||||
if ($item) {
|
||||
$prereqId = $item->get_prereq_string();
|
||||
$minScore = $item->getPrerequisiteMinScore();
|
||||
$maxScore = $item->getPrerequisiteMaxScore();
|
||||
$passed = false;
|
||||
$lp = new learnpath(api_get_course_id(), $lp_id, $student_id);
|
||||
$prereqCheck = $lp->prerequisites_match($lpItemId);
|
||||
if ($prereqCheck) {
|
||||
$passed = true;
|
||||
}
|
||||
if (false === $passed) {
|
||||
if (!empty($objExerciseTmp->pass_percentage)) {
|
||||
$passed = ExerciseLib::isSuccessExerciseResult(
|
||||
$tot,
|
||||
$exeWeighting,
|
||||
$objExerciseTmp->pass_percentage
|
||||
);
|
||||
} else {
|
||||
$passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($passed) {
|
||||
$statusCondition = ', status = "completed" ';
|
||||
} else {
|
||||
$statusCondition = ', status = "failed" ';
|
||||
}
|
||||
Display::addFlash(Display::return_message(get_lang('LearnpathUpdated')));
|
||||
}
|
||||
|
||||
$sql = "UPDATE $TBL_LP_ITEM_VIEW
|
||||
SET score = '".(float) $tot."'
|
||||
$statusCondition
|
||||
WHERE iid = $lp_item_view_id";
|
||||
Database::query($sql);
|
||||
|
||||
header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?id='.$id.'&student='.$student_id.'&'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$actions = null;
|
||||
$hideIp = api_get_configuration_value('exercise_hide_ip');
|
||||
if ($is_allowedToEdit && $origin !== 'learnpath') {
|
||||
// the form
|
||||
if (api_is_platform_admin() || api_is_course_admin() ||
|
||||
api_is_course_tutor() || api_is_session_general_coach()
|
||||
) {
|
||||
$actions .= '<a href="exercise.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actions .= '<a href="live_stats.php?'.api_get_cidreq().'&exerciseId='.$exercise_id.'">'.
|
||||
Display::return_icon('activity_monitor.png', get_lang('LiveResults'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actions .= '<a href="stats.php?'.api_get_cidreq().'&exerciseId='.$exercise_id.'">'.
|
||||
Display::return_icon('statistics.png', get_lang('ReportByQuestion'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actions .= '<a href="stats_attempts.php?'.api_get_cidreq().'&exerciseId='.$exercise_id.'">'.
|
||||
Display::return_icon('survey_reporting_complete.png', get_lang('ReportByAttempts'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actions .= '<a id="export_opener" href="'.api_get_self().'?export_report=1&exerciseId='.$exercise_id.'" >'.
|
||||
Display::return_icon('save.png', get_lang('Export'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('reload.png', get_lang('RecalculateResults'), [], ICON_SIZE_MEDIUM),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/recalculate_all.php?'.api_get_cidreq()."&exercise=$exercise_id"
|
||||
);
|
||||
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('export_pdf.png', get_lang('ExportExerciseAllResults'), [], ICON_SIZE_MEDIUM),
|
||||
api_get_self().'?'.api_get_cidreq().'&action=export_all_results&exerciseId='.$exercise_id
|
||||
);
|
||||
|
||||
// clean result before a selected date icon
|
||||
if ($allowClean) {
|
||||
$actions .= Display::url(
|
||||
Display::return_icon(
|
||||
'clean_before_date.png',
|
||||
get_lang('CleanStudentsResultsBeforeDate'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
),
|
||||
'#',
|
||||
['onclick' => 'javascript:display_date_picker()']
|
||||
);
|
||||
// clean result before a selected date datepicker popup
|
||||
$actions .= Display::span(
|
||||
Display::input(
|
||||
'input',
|
||||
'datepicker_start',
|
||||
get_lang('SelectADateOnTheCalendar'),
|
||||
[
|
||||
'onmouseover' => 'datepicker_input_mouseover()',
|
||||
'id' => 'datepicker_start',
|
||||
'onchange' => 'datepicker_input_changed()',
|
||||
'readonly' => 'readonly',
|
||||
]
|
||||
).
|
||||
Display::button(
|
||||
'delete',
|
||||
get_lang('Delete'),
|
||||
['onclick' => 'submit_datepicker()']
|
||||
),
|
||||
['style' => 'display:none', 'id' => 'datepicker_span']
|
||||
);
|
||||
}
|
||||
|
||||
$actions .= Display::url(
|
||||
get_lang('QuestionStats'),
|
||||
'question_stats.php?'.api_get_cidreq().'&id='.$exercise_id,
|
||||
['class' => 'btn btn-default']
|
||||
);
|
||||
|
||||
$actions .= Display::url(
|
||||
get_lang('ComparativeGroupReport'),
|
||||
'comparative_group_report.php?'.api_get_cidreq().'&id='.$exercise_id,
|
||||
['class' => 'btn btn-default']
|
||||
);
|
||||
|
||||
$actions .= ExerciseFocusedPlugin::create()->getLinkReporting($exercise_id);
|
||||
}
|
||||
} else {
|
||||
$actions .= '<a href="exercise.php">'.
|
||||
Display::return_icon(
|
||||
'back.png',
|
||||
get_lang('GoBackToQuestionList'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
).
|
||||
'</a>';
|
||||
}
|
||||
|
||||
// Deleting an attempt
|
||||
if (($is_allowedToEdit || $is_tutor || api_is_coach()) &&
|
||||
isset($_GET['delete']) && $_GET['delete'] === 'delete' &&
|
||||
!empty($_GET['did']) && $locked == false
|
||||
) {
|
||||
$exe_id = (int) $_GET['did'];
|
||||
if (!empty($exe_id)) {
|
||||
ExerciseLib::deleteExerciseAttempt($exe_id);
|
||||
|
||||
if (empty($exercise_id)) {
|
||||
header('Location: pending.php');
|
||||
exit;
|
||||
}
|
||||
header('Location: exercise_report.php?'.api_get_cidreq().'&exerciseId='.$exercise_id);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_allowedToEdit || $is_tutor) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
|
||||
$nameTools = get_lang('StudentScore');
|
||||
if ($exerciseExists) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => '#',
|
||||
'name' => $objExerciseTmp->selectTitle(true),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
if ($exerciseExists) {
|
||||
$nameTools = get_lang('Results').': '.$objExerciseTmp->selectTitle(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (($is_allowedToEdit || $is_tutor || api_is_coach()) &&
|
||||
isset($_GET['a']) && $_GET['a'] === 'close' &&
|
||||
!empty($_GET['id']) && $locked == false
|
||||
) {
|
||||
// Close the user attempt otherwise left pending
|
||||
$exe_id = (int) $_GET['id'];
|
||||
$sql = "UPDATE $TBL_TRACK_EXERCISES SET status = ''
|
||||
WHERE exe_id = $exe_id AND status = 'incomplete'";
|
||||
Database::query($sql);
|
||||
}
|
||||
|
||||
Display::display_header($nameTools);
|
||||
|
||||
// Clean all results for this test before the selected date
|
||||
if (($is_allowedToEdit || $is_tutor || api_is_coach()) &&
|
||||
isset($_GET['delete_before_date']) && $locked == false
|
||||
) {
|
||||
// ask for the date
|
||||
$check = Security::check_token('get');
|
||||
if ($check && $allowClean) {
|
||||
$objExerciseTmp = new Exercise();
|
||||
if ($objExerciseTmp->read($exercise_id)) {
|
||||
$count = $objExerciseTmp->cleanResults(
|
||||
true,
|
||||
$_GET['delete_before_date'].' 23:59:59'
|
||||
);
|
||||
echo Display::return_message(
|
||||
sprintf(get_lang('XResultsCleaned'), $count),
|
||||
'confirm'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Security token to protect deletion
|
||||
$token = Security::get_token();
|
||||
$actions = Display::div($actions, ['class' => 'actions']);
|
||||
|
||||
$extra = '<script>
|
||||
$(function() {
|
||||
$( "#dialog:ui-dialog" ).dialog( "destroy" );
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
autoOpen: false,
|
||||
show: "blind",
|
||||
resizable: false,
|
||||
height:300,
|
||||
modal: true
|
||||
});
|
||||
|
||||
$("#export_opener").click(function() {
|
||||
var targetUrl = $(this).attr("href");
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
width:400,
|
||||
height:300,
|
||||
buttons: {
|
||||
"'.addslashes(get_lang('Download')).'": function() {
|
||||
var export_format = $("input[name=export_format]:checked").val();
|
||||
var extra_data = $("input[name=load_extra_data]:checked").val();
|
||||
var includeAllUsers = $("input[name=include_all_users]:checked").val();
|
||||
var attempts = $("input[name=only_best_attempts]:checked").val();
|
||||
location.href = targetUrl+"&export_format="+export_format+"&extra_data="+extra_data+"&include_all_users="+includeAllUsers+"&only_best_attempts="+attempts;
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
});
|
||||
$( "#dialog-confirm" ).dialog("open");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
|
||||
$extra .= '<div id="dialog-confirm" title="'.get_lang('ConfirmYourChoice').'">';
|
||||
$form = new FormValidator(
|
||||
'report',
|
||||
'post',
|
||||
null,
|
||||
null,
|
||||
['class' => 'form-vertical']
|
||||
);
|
||||
$form->addElement(
|
||||
'radio',
|
||||
'export_format',
|
||||
null,
|
||||
get_lang('ExportAsCSV'),
|
||||
'csv',
|
||||
['id' => 'export_format_csv_label']
|
||||
);
|
||||
$form->addElement(
|
||||
'radio',
|
||||
'export_format',
|
||||
null,
|
||||
get_lang('ExportAsXLS'),
|
||||
'xls',
|
||||
['id' => 'export_format_xls_label']
|
||||
);
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'load_extra_data',
|
||||
null,
|
||||
get_lang('LoadExtraData'),
|
||||
'0',
|
||||
['id' => 'export_format_xls_label']
|
||||
);
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'include_all_users',
|
||||
null,
|
||||
get_lang('IncludeAllUsers'),
|
||||
'0'
|
||||
);
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'only_best_attempts',
|
||||
null,
|
||||
get_lang('OnlyBestAttempts'),
|
||||
'0'
|
||||
);
|
||||
$form->setDefaults(['export_format' => 'csv']);
|
||||
$extra .= $form->returnForm();
|
||||
$extra .= '</div>';
|
||||
|
||||
if ($is_allowedToEdit) {
|
||||
echo $extra;
|
||||
}
|
||||
|
||||
echo $actions;
|
||||
$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_exercise_results&exerciseId='.$exercise_id.'&filter_by_user='.$filter_user.'&'.api_get_cidreq();
|
||||
$action_links = '';
|
||||
// Generating group list
|
||||
$group_list = GroupManager::get_group_list();
|
||||
$group_parameters = [
|
||||
'group_all:'.get_lang('All'),
|
||||
'group_none:'.get_lang('None'),
|
||||
];
|
||||
|
||||
foreach ($group_list as $group) {
|
||||
$group_parameters[] = $group['id'].':'.$group['name'];
|
||||
}
|
||||
if (!empty($group_parameters)) {
|
||||
$group_parameters = implode(';', $group_parameters);
|
||||
}
|
||||
|
||||
$officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
|
||||
|
||||
if ($is_allowedToEdit || $is_tutor) {
|
||||
// The order is important you need to check the the $column variable in the model.ajax.php file
|
||||
$columns = [
|
||||
get_lang('FirstName'),
|
||||
get_lang('LastName'),
|
||||
get_lang('LoginName'),
|
||||
get_lang('Group'),
|
||||
get_lang('Duration').' ('.get_lang('MinMinute').')',
|
||||
get_lang('StartDate'),
|
||||
get_lang('EndDate'),
|
||||
get_lang('Score'),
|
||||
get_lang('IP'),
|
||||
get_lang('Status'),
|
||||
get_lang('ToolLearnpath'),
|
||||
get_lang('Actions'),
|
||||
];
|
||||
$indexIp = 8;
|
||||
if ($officialCodeInList === 'true') {
|
||||
$indexIp = 9;
|
||||
$columns = array_merge([get_lang('OfficialCode')], $columns);
|
||||
}
|
||||
|
||||
// Column config
|
||||
$column_model = [
|
||||
['name' => 'firstname', 'index' => 'firstname', 'width' => '50', 'align' => 'left', 'search' => 'true'],
|
||||
[
|
||||
'name' => 'lastname',
|
||||
'index' => 'lastname',
|
||||
'width' => '50',
|
||||
'align' => 'left',
|
||||
'formatter' => 'action_formatter',
|
||||
'search' => 'true',
|
||||
],
|
||||
[
|
||||
'name' => 'login',
|
||||
'index' => 'username',
|
||||
'width' => '40',
|
||||
'align' => 'left',
|
||||
'search' => 'true',
|
||||
'hidden' => api_get_configuration_value('exercise_attempts_report_show_username') ? 'false' : 'true',
|
||||
],
|
||||
[
|
||||
'name' => 'group_name',
|
||||
'index' => 'group_id',
|
||||
'width' => '40',
|
||||
'align' => 'left',
|
||||
'search' => 'true',
|
||||
'stype' => 'select',
|
||||
//for the bottom bar
|
||||
'searchoptions' => [
|
||||
'defaultValue' => 'group_all',
|
||||
'value' => $group_parameters,
|
||||
],
|
||||
//for the top bar
|
||||
'editoptions' => ['value' => $group_parameters],
|
||||
],
|
||||
['name' => 'duration', 'index' => 'exe_duration', 'width' => '30', 'align' => 'left', 'search' => 'true'],
|
||||
['name' => 'start_date', 'index' => 'start_date', 'width' => '60', 'align' => 'left', 'search' => 'true'],
|
||||
['name' => 'exe_date', 'index' => 'exe_date', 'width' => '60', 'align' => 'left', 'search' => 'true'],
|
||||
['name' => 'score', 'index' => 'exe_result', 'width' => '50', 'align' => 'center', 'search' => 'true'],
|
||||
['name' => 'ip', 'index' => 'user_ip', 'width' => '40', 'align' => 'center', 'search' => 'true'],
|
||||
[
|
||||
'name' => 'status',
|
||||
'index' => 'revised',
|
||||
'width' => '40',
|
||||
'align' => 'left',
|
||||
'search' => 'true',
|
||||
'stype' => 'select',
|
||||
//for the bottom bar
|
||||
'searchoptions' => [
|
||||
'defaultValue' => '',
|
||||
'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang('NotValidated'),
|
||||
],
|
||||
//for the top bar
|
||||
'editoptions' => [
|
||||
'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang(
|
||||
'NotValidated'
|
||||
),
|
||||
],
|
||||
],
|
||||
['name' => 'lp', 'index' => 'orig_lp_id', 'width' => '60', 'align' => 'left', 'search' => 'false'],
|
||||
['name' => 'actions', 'index' => 'actions', 'width' => '60', 'align' => 'left', 'search' => 'false', 'sortable' => 'false'],
|
||||
];
|
||||
|
||||
if ('true' === $officialCodeInList) {
|
||||
$officialCodeRow = ['name' => 'official_code', 'index' => 'official_code', 'width' => '50', 'align' => 'left', 'search' => 'true'];
|
||||
$column_model = array_merge([$officialCodeRow], $column_model);
|
||||
}
|
||||
|
||||
if ($hideIp) {
|
||||
// It removes the 9th column related to IP
|
||||
unset($columns[$indexIp]);
|
||||
unset($column_model[$indexIp]);
|
||||
$columns = array_values($columns);
|
||||
$column_model = array_values($column_model);
|
||||
}
|
||||
|
||||
$action_links = '
|
||||
// add username as title in lastname filed - ref 4226
|
||||
function action_formatter(cellvalue, options, rowObject) {
|
||||
// rowObject is firstname,lastname,login,... get the third word
|
||||
var loginx = "'.api_htmlentities(sprintf(get_lang('LoginX'), ':::'), ENT_QUOTES).'";
|
||||
var tabLoginx = loginx.split(/:::/);
|
||||
// tabLoginx[0] is before and tabLoginx[1] is after :::
|
||||
// may be empty string but is defined
|
||||
return "<span title=\""+tabLoginx[0]+rowObject[2]+tabLoginx[1]+"\">"+cellvalue+"</span>";
|
||||
}';
|
||||
}
|
||||
|
||||
$extra_params['autowidth'] = 'true';
|
||||
$extra_params['height'] = 'auto';
|
||||
$extra_params['gridComplete'] = "
|
||||
defaultGroupId = Cookies.get('default_group_".$exercise_id."');
|
||||
if (typeof defaultGroupId !== 'undefined') {
|
||||
$('#gs_group_name').val(defaultGroupId);
|
||||
}
|
||||
";
|
||||
|
||||
$extra_params['beforeRequest'] = "
|
||||
var defaultGroupId = $('#gs_group_name').val();
|
||||
|
||||
// Load from group menu
|
||||
if (typeof defaultGroupId !== 'undefined') {
|
||||
Cookies.set('default_group_".$exercise_id."', defaultGroupId);
|
||||
} else {
|
||||
// get from cookies
|
||||
defaultGroupId = Cookies.get('default_group_".$exercise_id."');
|
||||
$('#gs_group_name').val(defaultGroupId);
|
||||
}
|
||||
|
||||
if (typeof defaultGroupId !== 'undefined') {
|
||||
var posted_data = $(\"#results\").jqGrid('getGridParam', 'postData');
|
||||
var extraFilter = ',{\"field\":\"group_id\",\"op\":\"eq\",\"data\":\"'+ defaultGroupId +'\"}]}';
|
||||
var filters = posted_data.filters;
|
||||
var stringObj = new String(filters);
|
||||
stringObj.replace(']}', extraFilter);
|
||||
|
||||
posted_data['group_id_in_toolbar'] = defaultGroupId;
|
||||
$(this).jqGrid('setGridParam', 'postData', posted_data);
|
||||
}
|
||||
";
|
||||
|
||||
$gridJs = Display::grid_js(
|
||||
'results',
|
||||
$url,
|
||||
$columns,
|
||||
$column_model,
|
||||
$extra_params,
|
||||
[],
|
||||
$action_links,
|
||||
true
|
||||
);
|
||||
|
||||
?>
|
||||
<script>
|
||||
function exportExcel()
|
||||
{
|
||||
var mya = $("#results").getDataIDs(); // Get All IDs
|
||||
var data = $("#results").getRowData(mya[0]); // Get First row to get the labels
|
||||
var colNames = new Array();
|
||||
var ii = 0;
|
||||
for (var i in data) {
|
||||
colNames[ii++] = i;
|
||||
}
|
||||
var html = "";
|
||||
for (i = 0; i < mya.length; i++) {
|
||||
data = $("#results").getRowData(mya[i]); // get each row
|
||||
for (j = 0; j < colNames.length; j++) {
|
||||
html = html + data[colNames[j]] + ","; // output each column as tab delimited
|
||||
}
|
||||
html = html + "\n"; // output each row with end of line
|
||||
}
|
||||
html = html + "\n"; // end of line at the end
|
||||
var form = $("#export_report_form");
|
||||
$("#csvBuffer").attr('value', html);
|
||||
form.target='_blank';
|
||||
form.submit();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$("#datepicker_start").datepicker({
|
||||
defaultDate: "",
|
||||
changeMonth: false,
|
||||
numberOfMonths: 1
|
||||
});
|
||||
<?php
|
||||
echo $gridJs;
|
||||
|
||||
if ($is_allowedToEdit || $is_tutor) {
|
||||
?>
|
||||
$("#results").jqGrid(
|
||||
'navGrid',
|
||||
'#results_pager', {
|
||||
view:true, edit:false, add:false, del:false, excel:false
|
||||
},
|
||||
{height:280, reloadAfterSubmit:false}, // view options
|
||||
{height:280, reloadAfterSubmit:false}, // edit options
|
||||
{height:280, reloadAfterSubmit:false}, // add options
|
||||
{reloadAfterSubmit: false}, // del options
|
||||
{width:500}, // search options
|
||||
);
|
||||
|
||||
var sgrid = $("#results")[0];
|
||||
|
||||
// Update group
|
||||
var defaultGroupId = Cookies.get('default_group_<?php echo $exercise_id; ?>');
|
||||
$('#gs_group_name').val(defaultGroupId);
|
||||
// Adding search options
|
||||
var options = {
|
||||
'stringResult': true,
|
||||
'autosearch' : true,
|
||||
'searchOnEnter': false,
|
||||
afterSearch: function () {
|
||||
$('#gs_group_name').on('change', function() {
|
||||
var defaultGroupId = $('#gs_group_name').val();
|
||||
// Save default group id
|
||||
Cookies.set('default_group_<?php echo $exercise_id; ?>', defaultGroupId);
|
||||
});
|
||||
}
|
||||
}
|
||||
jQuery("#results").jqGrid('filterToolbar', options);
|
||||
sgrid.triggerToolbar();
|
||||
$('#results').on('click', 'a.exercise-recalculate', function (e) {
|
||||
e.preventDefault();
|
||||
if (!$(this).data('user') || !$(this).data('exercise') || !$(this).data('id')) {
|
||||
return;
|
||||
}
|
||||
var url = '<?php echo api_get_path(WEB_CODE_PATH); ?>exercise/recalculate.php?<?php echo api_get_cidreq(); ?>';
|
||||
var recalculateXhr = $.post(url, $(this).data());
|
||||
$.when(recalculateXhr).done(function (response) {
|
||||
$('#results').trigger('reloadGrid');
|
||||
});
|
||||
});
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
});
|
||||
// datepicker functions
|
||||
var datapickerInputModified = false;
|
||||
/**
|
||||
* return true if the datepicker input has been modified
|
||||
*/
|
||||
function datepicker_input_changed() {
|
||||
datapickerInputModified = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* disply the datepicker calendar on mouse over the input
|
||||
*/
|
||||
function datepicker_input_mouseover() {
|
||||
$('#datepicker_start').datepicker( "show" );
|
||||
}
|
||||
|
||||
/**
|
||||
* display or hide the datepicker input, calendar and button
|
||||
*/
|
||||
function display_date_picker() {
|
||||
if (!$('#datepicker_span').is(":visible")) {
|
||||
$('#datepicker_span').show();
|
||||
$('#datepicker_start').datepicker( "show" );
|
||||
} else {
|
||||
$('#datepicker_start').datepicker( "hide" );
|
||||
$('#datepicker_span').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* confirm deletion
|
||||
*/
|
||||
function submit_datepicker() {
|
||||
if (datapickerInputModified) {
|
||||
var dateTypeVar = $('#datepicker_start').datepicker('getDate');
|
||||
var dateForBDD = $.datepicker.formatDate('yy-mm-dd', dateTypeVar);
|
||||
// Format the date for confirm box
|
||||
var dateFormat = $( "#datepicker_start" ).datepicker( "option", "dateFormat" );
|
||||
var selectedDate = $.datepicker.formatDate(dateFormat, dateTypeVar);
|
||||
if (confirm("<?php echo convert_double_quote_to_single(get_lang('AreYouSureDeleteTestResultBeforeDateD')).' '; ?>" + selectedDate)) {
|
||||
self.location.href = "exercise_report.php?<?php echo api_get_cidreq(); ?>&exerciseId=<?php echo $exercise_id; ?>&delete_before_date="+dateForBDD+"&sec_token=<?php echo $token; ?>";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<form id="export_report_form" method="post" action="exercise_report.php?<?php echo api_get_cidreq(); ?>">
|
||||
<input type="hidden" name="csvBuffer" id="csvBuffer" value="" />
|
||||
<input type="hidden" name="export_report" id="export_report" value="1" />
|
||||
<input type="hidden" name="exerciseId" id="exerciseId" value="<?php echo $exercise_id; ?>" />
|
||||
</form>
|
||||
|
||||
<?php
|
||||
echo Display::grid_html('results');
|
||||
Display::display_footer();
|
||||
747
main/exercise/exercise_result.class.php
Normal file
747
main/exercise/exercise_result.class.php
Normal file
@@ -0,0 +1,747 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ExerciseResult
|
||||
* which allows you to export exercises results in multiple presentation forms.
|
||||
*
|
||||
* @author Yannick Warnier
|
||||
*/
|
||||
class ExerciseResult
|
||||
{
|
||||
public $includeAllUsers = false;
|
||||
public $onlyBestAttempts = false;
|
||||
private $results = [];
|
||||
|
||||
/**
|
||||
* @param bool $includeAllUsers
|
||||
*/
|
||||
public function setIncludeAllUsers($includeAllUsers)
|
||||
{
|
||||
$this->includeAllUsers = $includeAllUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $value
|
||||
*/
|
||||
public function setOnlyBestAttempts($value)
|
||||
{
|
||||
$this->onlyBestAttempts = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the results of all students (or just one student if access is limited).
|
||||
*
|
||||
* @param string $document_path The document path (for HotPotatoes retrieval)
|
||||
* @param int $user_id User ID. Optional. If no user ID is provided, we take all the results. Defauts to null
|
||||
* @param int $filter
|
||||
* @param int $exercise_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getExercisesReporting(
|
||||
$document_path,
|
||||
$user_id = null,
|
||||
$filter = 0,
|
||||
$exercise_id = 0
|
||||
) {
|
||||
$return = [];
|
||||
$TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
|
||||
$TBL_TABLE_LP_MAIN = Database::get_course_table(TABLE_LP_MAIN);
|
||||
$TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
|
||||
$TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
$TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
|
||||
|
||||
$cid = api_get_course_id();
|
||||
$course_id = api_get_course_int_id();
|
||||
$user_id = (int) $user_id;
|
||||
$sessionId = api_get_session_id();
|
||||
$session_id_and = ' AND te.session_id = '.$sessionId.' ';
|
||||
$exercise_id = (int) $exercise_id;
|
||||
|
||||
if (empty($sessionId) &&
|
||||
api_get_configuration_value('show_exercise_session_attempts_in_base_course')
|
||||
) {
|
||||
$session_id_and = '';
|
||||
}
|
||||
|
||||
if (!empty($exercise_id)) {
|
||||
$session_id_and .= " AND exe_exo_id = $exercise_id ";
|
||||
}
|
||||
|
||||
if (empty($user_id)) {
|
||||
$user_id_and = null;
|
||||
$sql = "SELECT
|
||||
firstname,
|
||||
lastname,
|
||||
official_code,
|
||||
ce.title as extitle,
|
||||
ce.pass_percentage as expasspercentage,
|
||||
te.exe_result as exresult ,
|
||||
te.exe_weighting as exweight,
|
||||
te.exe_date as exdate,
|
||||
te.exe_id as exid,
|
||||
email as exemail,
|
||||
te.start_date as exstart,
|
||||
steps_counter as exstep,
|
||||
exe_user_id as excruid,
|
||||
te.exe_duration as duration,
|
||||
te.orig_lp_id as orig_lp_id,
|
||||
tlm.name as lp_name,
|
||||
user.username,
|
||||
te.status as exstatus
|
||||
FROM $TBL_EXERCISES AS ce
|
||||
INNER JOIN $TBL_TRACK_EXERCISES AS te
|
||||
ON (te.exe_exo_id = ce.iid)
|
||||
INNER JOIN $TBL_USER AS user
|
||||
ON (user.user_id = exe_user_id)
|
||||
LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm
|
||||
ON (tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id)
|
||||
WHERE
|
||||
ce.c_id = $course_id AND
|
||||
te.c_id = ce.c_id $user_id_and $session_id_and AND
|
||||
ce.active <> -1";
|
||||
} else {
|
||||
$user_id_and = ' AND te.exe_user_id = '.api_get_user_id().' ';
|
||||
$orderBy = 'lastname';
|
||||
if (api_is_western_name_order()) {
|
||||
$orderBy = 'firstname';
|
||||
}
|
||||
// get only this user's results
|
||||
$sql = "SELECT
|
||||
firstname,
|
||||
lastname,
|
||||
official_code,
|
||||
ce.title as extitle,
|
||||
ce.pass_percentage as expasspercentage,
|
||||
te.exe_result as exresult,
|
||||
te.exe_weighting as exweight,
|
||||
te.exe_date as exdate,
|
||||
te.exe_id as exid,
|
||||
email as exemail,
|
||||
te.start_date as exstart,
|
||||
steps_counter as exstep,
|
||||
exe_user_id as excruid,
|
||||
te.exe_duration as duration,
|
||||
ce.results_disabled as exdisabled,
|
||||
te.orig_lp_id as orig_lp_id,
|
||||
tlm.name as lp_name,
|
||||
user.username,
|
||||
te.status as exstatus
|
||||
FROM $TBL_EXERCISES AS ce
|
||||
INNER JOIN $TBL_TRACK_EXERCISES AS te
|
||||
ON (te.exe_exo_id = ce.iid)
|
||||
INNER JOIN $TBL_USER AS user
|
||||
ON (user.user_id = exe_user_id)
|
||||
LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm
|
||||
ON (tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id)
|
||||
WHERE
|
||||
ce.c_id = $course_id AND
|
||||
te.c_id = ce.c_id $user_id_and $session_id_and AND
|
||||
ce.active <>-1 AND
|
||||
ORDER BY $orderBy, te.c_id ASC, ce.title ASC, te.exe_date DESC";
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$resx = Database::query($sql);
|
||||
$bestAttemptPerUser = [];
|
||||
while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
|
||||
if ($this->onlyBestAttempts) {
|
||||
if (!isset($bestAttemptPerUser[$rowx['excruid']])) {
|
||||
$bestAttemptPerUser[$rowx['excruid']] = $rowx;
|
||||
} else {
|
||||
if ($rowx['exresult'] > $bestAttemptPerUser[$rowx['excruid']]['exresult']) {
|
||||
$bestAttemptPerUser[$rowx['excruid']] = $rowx;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$results[] = $rowx;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->onlyBestAttempts) {
|
||||
// Remove userId indexes to avoid issues in output
|
||||
foreach ($bestAttemptPerUser as $attempt) {
|
||||
$results[] = $attempt;
|
||||
}
|
||||
}
|
||||
|
||||
$filter_by_not_revised = false;
|
||||
$filter_by_revised = false;
|
||||
|
||||
if ($filter) {
|
||||
switch ($filter) {
|
||||
case 1:
|
||||
$filter_by_not_revised = true;
|
||||
break;
|
||||
case 2:
|
||||
$filter_by_revised = true;
|
||||
break;
|
||||
default:
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($sessionId)) {
|
||||
$students = CourseManager::get_user_list_from_course_code($cid);
|
||||
} else {
|
||||
$students = CourseManager::get_user_list_from_course_code($cid, $sessionId);
|
||||
}
|
||||
$studentsUserIdList = array_keys($students);
|
||||
|
||||
// Print the results of tests
|
||||
$userWithResults = [];
|
||||
if (is_array($results)) {
|
||||
$i = 0;
|
||||
foreach ($results as $result) {
|
||||
$revised = 0;
|
||||
if ($result['exstatus'] === 'incomplete') {
|
||||
$revised = -1;
|
||||
} else {
|
||||
//revised or not
|
||||
$sql_exe = "SELECT exe_id
|
||||
FROM $TBL_TRACK_ATTEMPT_RECORDING
|
||||
WHERE
|
||||
author != '' AND
|
||||
exe_id = ".intval($result['exid'])."
|
||||
LIMIT 1";
|
||||
$query = Database::query($sql_exe);
|
||||
|
||||
if (Database::num_rows($query) > 0) {
|
||||
$revised = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($filter_by_not_revised && $revised === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($filter_by_revised && $revised < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$return[$i] = [];
|
||||
if (empty($user_id)) {
|
||||
$return[$i]['official_code'] = $result['official_code'];
|
||||
$return[$i]['firstname'] = $results[$i]['firstname'];
|
||||
$return[$i]['lastname'] = $results[$i]['lastname'];
|
||||
$return[$i]['user_id'] = $results[$i]['excruid'];
|
||||
$return[$i]['email'] = $results[$i]['exemail'];
|
||||
$return[$i]['username'] = $results[$i]['username'];
|
||||
}
|
||||
$return[$i]['title'] = $result['extitle'];
|
||||
$return[$i]['minimun'] = $result['expasspercentage']
|
||||
? float_format($result['expasspercentage'] / 100 * $result['exweight'])
|
||||
: 0;
|
||||
$return[$i]['start_date'] = api_get_local_time($result['exstart']);
|
||||
$return[$i]['end_date'] = api_get_local_time($result['exdate']);
|
||||
$return[$i]['duration'] = $result['duration'];
|
||||
$return[$i]['result'] = $result['exresult'];
|
||||
$return[$i]['max'] = $result['exweight'];
|
||||
// Revised: 1 = revised, 0 = not revised, -1 = not even finished by user
|
||||
$return[$i]['status'] = $revised === 1 ? get_lang('Validated') : ($revised === 0 ? get_lang('NotValidated') : get_lang('Unclosed'));
|
||||
$return[$i]['lp_id'] = $result['orig_lp_id'];
|
||||
$return[$i]['lp_name'] = $result['lp_name'];
|
||||
|
||||
if (in_array($result['excruid'], $studentsUserIdList)) {
|
||||
$return[$i]['is_user_subscribed'] = get_lang('Yes');
|
||||
} else {
|
||||
$return[$i]['is_user_subscribed'] = get_lang('No');
|
||||
}
|
||||
|
||||
$userWithResults[$result['excruid']] = 1;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->includeAllUsers) {
|
||||
$latestId = count($return);
|
||||
$userWithResults = array_keys($userWithResults);
|
||||
|
||||
if (!empty($students)) {
|
||||
foreach ($students as $student) {
|
||||
if (!in_array($student['user_id'], $userWithResults)) {
|
||||
$i = $latestId;
|
||||
$isWestern = api_is_western_name_order();
|
||||
|
||||
if (empty($user_id)) {
|
||||
$return[$i]['official_code'] = $student['official_code'];
|
||||
$return[$i]['firstname'] = $student['firstname'];
|
||||
$return[$i]['lastname'] = $student['lastname'];
|
||||
$return[$i]['user_id'] = $student['user_id'];
|
||||
$return[$i]['email'] = $student['email'];
|
||||
$return[$i]['username'] = $student['username'];
|
||||
}
|
||||
$return[$i]['title'] = null;
|
||||
$return[$i]['minimun'] = null;
|
||||
$return[$i]['start_date'] = null;
|
||||
$return[$i]['end_date'] = null;
|
||||
$return[$i]['duration'] = null;
|
||||
$return[$i]['result'] = null;
|
||||
$return[$i]['max'] = null;
|
||||
$return[$i]['status'] = get_lang('NotAttempted');
|
||||
$return[$i]['lp_id'] = null;
|
||||
$return[$i]['lp_name'] = null;
|
||||
$return[$i]['is_user_subscribed'] = get_lang('Yes');
|
||||
|
||||
$latestId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->results = $return;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the complete report as a CSV file.
|
||||
*
|
||||
* @param string $document_path Document path inside the document tool
|
||||
* @param int $user_id Optional user ID
|
||||
* @param bool $export_user_fields Whether to include user fields or not
|
||||
* @param int $export_filter
|
||||
* @param int $exercise_id
|
||||
*
|
||||
* @return bool False on error
|
||||
*/
|
||||
public function exportCompleteReportCSV(
|
||||
$document_path = '',
|
||||
$user_id = null,
|
||||
$export_user_fields = false,
|
||||
$export_filter = 0,
|
||||
$exercise_id = 0
|
||||
) {
|
||||
global $charset;
|
||||
$this->getExercisesReporting(
|
||||
$document_path,
|
||||
$user_id,
|
||||
$export_filter,
|
||||
$exercise_id
|
||||
);
|
||||
$now = api_get_local_time();
|
||||
$filename = 'exercise_results_'.$now.'.csv';
|
||||
if (!empty($user_id)) {
|
||||
$filename = 'exercise_results_user_'.$user_id.'_'.$now.'.csv';
|
||||
}
|
||||
|
||||
$filename = api_replace_dangerous_char($filename);
|
||||
$data = '';
|
||||
if (api_is_western_name_order()) {
|
||||
if (!empty($this->results[0]['firstname'])) {
|
||||
$data .= get_lang('FirstName').';';
|
||||
}
|
||||
if (!empty($this->results[0]['lastname'])) {
|
||||
$data .= get_lang('LastName').';';
|
||||
}
|
||||
} else {
|
||||
if (!empty($this->results[0]['lastname'])) {
|
||||
$data .= get_lang('LastName').';';
|
||||
}
|
||||
if (!empty($this->results[0]['firstname'])) {
|
||||
$data .= get_lang('FirstName').';';
|
||||
}
|
||||
}
|
||||
$officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
|
||||
if ($officialCodeInList === 'true') {
|
||||
$data .= get_lang('OfficialCode').';';
|
||||
}
|
||||
|
||||
$data .= get_lang('LoginName').';';
|
||||
$data .= get_lang('Email').';';
|
||||
$data .= get_lang('Groups').';';
|
||||
|
||||
if ($export_user_fields) {
|
||||
//show user fields section with a big th colspan that spans over all fields
|
||||
$extra_user_fields = UserManager::get_extra_fields(
|
||||
0,
|
||||
1000,
|
||||
5,
|
||||
'ASC',
|
||||
false,
|
||||
1
|
||||
);
|
||||
if (!empty($extra_user_fields)) {
|
||||
foreach ($extra_user_fields as $field) {
|
||||
$data .= '"'.str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($field[3]), ENT_QUOTES, $charset)).'";';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data .= get_lang('Title').';';
|
||||
$data .= get_lang('StartDate').';';
|
||||
$data .= get_lang('EndDate').';';
|
||||
$data .= get_lang('Duration').' ('.get_lang('MinMinutes').') ;';
|
||||
$data .= get_lang('WeightNecessary').';';
|
||||
$data .= get_lang('Score').';';
|
||||
$data .= get_lang('Total').';';
|
||||
$data .= get_lang('Status').';';
|
||||
$data .= get_lang('ToolLearnpath').';';
|
||||
$data .= get_lang('UserIsCurrentlySubscribed').';';
|
||||
$data .= get_lang('CourseCode').';';
|
||||
$data .= "\n";
|
||||
|
||||
//results
|
||||
foreach ($this->results as $row) {
|
||||
if (api_is_western_name_order()) {
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['firstname']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['lastname']), ENT_QUOTES, $charset)).';';
|
||||
} else {
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['lastname']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['firstname']), ENT_QUOTES, $charset)).';';
|
||||
}
|
||||
|
||||
// Official code
|
||||
if ($officialCodeInList === 'true') {
|
||||
$data .= $row['official_code'].';';
|
||||
}
|
||||
|
||||
// Email
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['username']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['email']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', implode(", ", GroupManager::get_user_group_name($row['user_id']))).';';
|
||||
|
||||
if ($export_user_fields) {
|
||||
//show user fields data, if any, for this user
|
||||
$user_fields_values = UserManager::get_extra_user_data(
|
||||
$row['user_id'],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
if (!empty($user_fields_values)) {
|
||||
foreach ($user_fields_values as $value) {
|
||||
$data .= '"'.str_replace('"', '""', api_html_entity_decode(strip_tags($value), ENT_QUOTES, $charset)).'";';
|
||||
}
|
||||
}
|
||||
}
|
||||
$duration = !empty($row['duration']) ? round($row['duration'] / 60) : 0;
|
||||
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['title']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['start_date']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['end_date']).';';
|
||||
$data .= str_replace("\r\n", ' ', $duration).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['minimun']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['result']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['max']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['status']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['lp_name']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['is_user_subscribed']).';';
|
||||
$data .= str_replace("\r\n", ' ', api_get_course_id()).';';
|
||||
|
||||
$data .= "\n";
|
||||
}
|
||||
|
||||
// output the results
|
||||
$len = strlen($data);
|
||||
header('Content-type: application/octet-stream');
|
||||
header('Content-Type: application/force-download');
|
||||
header('Content-length: '.$len);
|
||||
if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT'])) {
|
||||
header('Content-Disposition: filename= '.$filename);
|
||||
} else {
|
||||
header('Content-Disposition: attachment; filename= '.$filename);
|
||||
}
|
||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
|
||||
header('Pragma: ');
|
||||
header('Cache-Control: ');
|
||||
header('Cache-Control: public'); // IE cannot download from sessions without a cache
|
||||
}
|
||||
header('Content-Description: '.$filename);
|
||||
header('Content-transfer-encoding: binary');
|
||||
echo $data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the complete report as an XLS file.
|
||||
*
|
||||
* @param string $document_path
|
||||
* @param null $user_id
|
||||
* @param bool $export_user_fields
|
||||
* @param int $export_filter
|
||||
* @param int $exercise_id
|
||||
* @param null $hotpotato_name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exportCompleteReportXLS(
|
||||
$document_path = '',
|
||||
$user_id = null,
|
||||
$export_user_fields = false,
|
||||
$export_filter = 0,
|
||||
$exercise_id = 0,
|
||||
$hotpotato_name = null
|
||||
) {
|
||||
global $charset;
|
||||
$this->getExercisesReporting(
|
||||
$document_path,
|
||||
$user_id,
|
||||
$export_filter,
|
||||
$exercise_id,
|
||||
$hotpotato_name
|
||||
);
|
||||
$now = api_get_local_time();
|
||||
$filename = 'exercise_results_'.$now.'.xlsx';
|
||||
if (!empty($user_id)) {
|
||||
$filename = 'exercise_results_user_'.$user_id.'_'.$now.'.xlsx';
|
||||
}
|
||||
|
||||
$spreadsheet = new PHPExcel();
|
||||
$spreadsheet->setActiveSheetIndex(0);
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$line = 1; // Skip first line
|
||||
$column = 0; //skip the first column (row titles)
|
||||
|
||||
// check if exists column 'user'
|
||||
$with_column_user = false;
|
||||
foreach ($this->results as $result) {
|
||||
if (!empty($result['lastname']) && !empty($result['firstname'])) {
|
||||
$with_column_user = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
|
||||
|
||||
if ($with_column_user) {
|
||||
if (api_is_western_name_order()) {
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('FirstName'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('LastName'));
|
||||
$column++;
|
||||
} else {
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('LastName'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('FirstName'));
|
||||
$column++;
|
||||
}
|
||||
|
||||
if ($officialCodeInList === 'true') {
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('OfficialCode'));
|
||||
$column++;
|
||||
}
|
||||
|
||||
$worksheet->setCellValueByColumnAndRow($column++, $line, get_lang('LoginName'));
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('Email'));
|
||||
$column++;
|
||||
}
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('Groups'));
|
||||
$column++;
|
||||
|
||||
if ($export_user_fields) {
|
||||
//show user fields section with a big th colspan that spans over all fields
|
||||
$extra_user_fields = UserManager::get_extra_fields(
|
||||
0,
|
||||
1000,
|
||||
5,
|
||||
'ASC',
|
||||
false,
|
||||
1
|
||||
);
|
||||
|
||||
//show the fields names for user fields
|
||||
foreach ($extra_user_fields as $field) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($field[3]),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
}
|
||||
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('Title'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('StartDate'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('EndDate'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('Duration').' ('.get_lang('MinMinutes').')');
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('WeightNecessary'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('Score'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('Total'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('Status'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('ToolLearnpath'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('UserIsCurrentlySubscribed'));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, get_lang('CourseCode'));
|
||||
$line++;
|
||||
|
||||
foreach ($this->results as $row) {
|
||||
$column = 0;
|
||||
if ($with_column_user) {
|
||||
if (api_is_western_name_order()) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['firstname']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['lastname']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
} else {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['lastname']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['firstname']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
|
||||
if ($officialCodeInList === 'true') {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['official_code']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column++,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['username']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['email']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags(
|
||||
implode(
|
||||
", ",
|
||||
GroupManager::get_user_group_name($row['user_id'])
|
||||
)
|
||||
),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
|
||||
if ($export_user_fields) {
|
||||
//show user fields data, if any, for this user
|
||||
$user_fields_values = UserManager::get_extra_user_data(
|
||||
$row['user_id'],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
foreach ($user_fields_values as $value) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($value),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
}
|
||||
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['title']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['start_date']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['end_date']);
|
||||
$column++;
|
||||
$duration = !empty($row['duration']) ? round($row['duration'] / 60) : 0;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $duration);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['minimun']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['result']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['max']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['status']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['lp_name']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['is_user_subscribed']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, api_get_course_id());
|
||||
$line++;
|
||||
}
|
||||
|
||||
$file = api_get_path(SYS_ARCHIVE_PATH).api_replace_dangerous_char($filename);
|
||||
$writer = new PHPExcel_Writer_Excel2007($spreadsheet);
|
||||
$writer->save($file);
|
||||
DocumentManager::file_send_for_download($file, true, $filename);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
396
main/exercise/exercise_result.php
Normal file
396
main/exercise/exercise_result.php
Normal file
@@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Exercise result
|
||||
* This script gets information from the script "exercise_submit.php",
|
||||
* through the session, and calculates the score of the student for
|
||||
* that exercise.
|
||||
* Then it shows the results on the screen.
|
||||
*
|
||||
* @author Olivier Brouckaert, main author
|
||||
* @author Roan Embrechts, some refactoring
|
||||
* @author Julio Montoya switchable fill in blank option added
|
||||
*
|
||||
* @todo split more code up in functions, move functions to library?
|
||||
*/
|
||||
$debug = false;
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
$origin = api_get_origin();
|
||||
|
||||
/** @var Exercise $objExercise */
|
||||
if (empty($objExercise)) {
|
||||
$objExercise = Session::read('objExercise');
|
||||
}
|
||||
|
||||
$exeId = isset($_REQUEST['exe_id']) ? (int) $_REQUEST['exe_id'] : 0;
|
||||
if (empty($objExercise)) {
|
||||
// Redirect to the exercise overview
|
||||
// Check if the exe_id exists
|
||||
$objExercise = new Exercise();
|
||||
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
|
||||
if (!empty($exercise_stat_info) && isset($exercise_stat_info['exe_exo_id'])) {
|
||||
header('Location: overview.php?exerciseId='.$exercise_stat_info['exe_exo_id'].'&'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$js = '<script>'.api_get_language_translate_html().'</script>';
|
||||
$htmlHeadXtra[] = $js;
|
||||
|
||||
if (api_is_in_gradebook()) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => Category::getUrl(),
|
||||
'name' => get_lang('ToolGradebook'),
|
||||
];
|
||||
}
|
||||
|
||||
$nameTools = get_lang('Exercises');
|
||||
$currentUserId = api_get_user_id();
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
if (RESULT_DISABLE_RADAR === (int) $objExercise->results_disabled) {
|
||||
$htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js');
|
||||
}
|
||||
|
||||
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
|
||||
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
|
||||
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
|
||||
if (api_get_configuration_value('quiz_prevent_copy_paste')) {
|
||||
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.nocopypaste.js"></script>';
|
||||
}
|
||||
|
||||
if (!empty($objExercise->getResultAccess())) {
|
||||
$htmlHeadXtra[] = api_get_css(
|
||||
api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css'
|
||||
);
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.epiclock.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
|
||||
}
|
||||
|
||||
$showHeader = false;
|
||||
$showFooter = false;
|
||||
$showLearnPath = true;
|
||||
$pageActions = '';
|
||||
$pageTop = '';
|
||||
$pageBottom = '';
|
||||
$pageContent = '';
|
||||
$courseInfo = api_get_course_info();
|
||||
if (!in_array($origin, ['learnpath', 'embeddable', 'mobileapp', 'iframe'])) {
|
||||
// So we are not in learnpath tool
|
||||
$showHeader = true;
|
||||
$showLearnPath = false;
|
||||
}
|
||||
|
||||
// I'm in a preview mode as course admin. Display the action menu.
|
||||
if (api_is_course_admin() && !in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
|
||||
$pageActions = Display::toolbarAction(
|
||||
'exercise_result_actions',
|
||||
[
|
||||
Display::url(
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), [], 32),
|
||||
'admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid
|
||||
)
|
||||
.Display::url(
|
||||
Display::return_icon('settings.png', get_lang('ModifyExercise'), [], 32),
|
||||
'exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->iid
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
|
||||
$learnpath_id = isset($exercise_stat_info['orig_lp_id']) ? $exercise_stat_info['orig_lp_id'] : 0;
|
||||
$learnpath_item_id = isset($exercise_stat_info['orig_lp_item_id']) ? $exercise_stat_info['orig_lp_item_id'] : 0;
|
||||
$learnpath_item_view_id = isset($exercise_stat_info['orig_lp_item_view_id'])
|
||||
? $exercise_stat_info['orig_lp_item_view_id'] : 0;
|
||||
$exerciseId = isset($exercise_stat_info['exe_exo_id']) ? $exercise_stat_info['exe_exo_id'] : 0;
|
||||
|
||||
$logInfo = [
|
||||
'tool' => TOOL_QUIZ,
|
||||
'tool_id' => $objExercise->iid,
|
||||
'action' => $learnpath_id,
|
||||
'action_details' => $learnpath_id,
|
||||
];
|
||||
Event::registerLog($logInfo);
|
||||
|
||||
$allowSignature = ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise);
|
||||
if ($allowSignature) {
|
||||
$htmlHeadXtra[] = api_get_asset('signature_pad/signature_pad.umd.js');
|
||||
}
|
||||
|
||||
if ($origin === 'learnpath') {
|
||||
$pageTop .= '
|
||||
<form method="GET" action="exercise.php?'.api_get_cidreq().'">
|
||||
<input type="hidden" name="origin" value='.$origin.'/>
|
||||
<input type="hidden" name="learnpath_id" value="'.$learnpath_id.'"/>
|
||||
<input type="hidden" name="learnpath_item_id" value="'.$learnpath_item_id.'"/>
|
||||
<input type="hidden" name="learnpath_item_view_id" value="'.$learnpath_item_view_id.'"/>
|
||||
';
|
||||
}
|
||||
|
||||
$i = $total_score = $max_score = 0;
|
||||
$remainingMessage = '';
|
||||
$attemptButton = '';
|
||||
if (!in_array($origin, ['iframe', 'embeddable'])) {
|
||||
$attemptButton = Display::toolbarButton(
|
||||
get_lang('AnotherAttempt'),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&'.http_build_query(
|
||||
[
|
||||
'exerciseId' => $objExercise->iid,
|
||||
'learnpath_id' => $learnpath_id,
|
||||
'learnpath_item_id' => $learnpath_item_id,
|
||||
'learnpath_item_view_id' => $learnpath_item_view_id,
|
||||
]
|
||||
),
|
||||
'pencil-square-o',
|
||||
'info'
|
||||
);
|
||||
}
|
||||
|
||||
// We check if the user attempts before sending to the exercise_result.php
|
||||
$attempt_count = Event::get_attempt_count(
|
||||
$currentUserId,
|
||||
$objExercise->iid,
|
||||
$learnpath_id,
|
||||
$learnpath_item_id,
|
||||
$learnpath_item_view_id
|
||||
);
|
||||
|
||||
if ($objExercise->selectAttempts() > 0) {
|
||||
if ($attempt_count >= $objExercise->selectAttempts()) {
|
||||
Display::addFlash(
|
||||
Display::return_message(
|
||||
sprintf(get_lang('ReachedMaxAttempts'), $objExercise->selectTitle(), $objExercise->selectAttempts()),
|
||||
'warning',
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) {
|
||||
$showFooter = true;
|
||||
}
|
||||
|
||||
$template = new Template($nameTools, $showHeader, $showFooter);
|
||||
$template->assign('actions', $pageActions);
|
||||
$template->display_one_col_template();
|
||||
exit;
|
||||
} else {
|
||||
$attempt_count++;
|
||||
$remainingAttempts = $objExercise->selectAttempts() - $attempt_count;
|
||||
if ($remainingAttempts) {
|
||||
$attemptMessage = sprintf(get_lang('RemainingXAttempts'), $remainingAttempts);
|
||||
$remainingMessage = sprintf('<p>%s</p> %s', $attemptMessage, $attemptButton);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$remainingMessage = $attemptButton ? "<p>$attemptButton</p>" : '';
|
||||
}
|
||||
|
||||
$total_score = 0;
|
||||
if (!empty($exercise_stat_info)) {
|
||||
$total_score = $exercise_stat_info['exe_result'];
|
||||
}
|
||||
|
||||
$max_score = $objExercise->get_max_score();
|
||||
|
||||
if ('embeddable' === $origin) {
|
||||
$pageTop .= showEmbeddableFinishButton();
|
||||
} else {
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('Saved'), 'normal', false)
|
||||
);
|
||||
}
|
||||
$saveResults = true;
|
||||
$feedbackType = $objExercise->getFeedbackType();
|
||||
|
||||
ob_start();
|
||||
$stats = ExerciseLib::displayQuestionListByAttempt(
|
||||
$objExercise,
|
||||
$exeId,
|
||||
$saveResults,
|
||||
$remainingMessage,
|
||||
$allowSignature,
|
||||
api_get_configuration_value('quiz_results_answers_report'),
|
||||
false
|
||||
);
|
||||
$pageContent .= ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// Change settings for teacher access.
|
||||
$oldResultDisabled = $objExercise->results_disabled;
|
||||
$objExercise->results_disabled = RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS;
|
||||
$objExercise->forceShowExpectedChoiceColumn = true;
|
||||
$objExercise->disableHideCorrectAnsweredQuestions = true;
|
||||
|
||||
ob_start();
|
||||
$statsTeacher = ExerciseLib::displayQuestionListByAttempt(
|
||||
$objExercise,
|
||||
$exeId,
|
||||
false,
|
||||
$remainingMessage,
|
||||
$allowSignature,
|
||||
api_get_configuration_value('quiz_results_answers_report'),
|
||||
false
|
||||
);
|
||||
ob_end_clean();
|
||||
|
||||
// Restore settings.
|
||||
$objExercise->results_disabled = $oldResultDisabled;
|
||||
$objExercise->forceShowExpectedChoiceColumn = false;
|
||||
$objExercise->disableHideCorrectAnsweredQuestions = false;
|
||||
|
||||
// Save here LP status
|
||||
if (!empty($learnpath_id) && $saveResults) {
|
||||
// Save attempt in lp
|
||||
Exercise::saveExerciseInLp($learnpath_item_id, $exeId);
|
||||
}
|
||||
|
||||
ExerciseLib::sendNotification(
|
||||
api_get_user_id(),
|
||||
$objExercise,
|
||||
$exercise_stat_info,
|
||||
$courseInfo,
|
||||
$attempt_count++,
|
||||
$stats,
|
||||
$statsTeacher
|
||||
);
|
||||
|
||||
$hookQuizEnd = HookQuizEnd::create();
|
||||
$hookQuizEnd->setEventData(['exe_id' => $exeId]);
|
||||
$hookQuizEnd->notifyQuizEnd();
|
||||
|
||||
$advancedCourseMessage = RemedialCoursePlugin::create()->getAdvancedCourseList(
|
||||
$objExercise,
|
||||
api_get_user_id(),
|
||||
api_get_session_id(),
|
||||
$learnpath_id ?: 0,
|
||||
$learnpath_item_id ?: 0
|
||||
);
|
||||
if (null != $advancedCourseMessage) {
|
||||
Display::addFlash(
|
||||
Display::return_message($advancedCourseMessage, 'info', false)
|
||||
);
|
||||
}
|
||||
|
||||
$remedialMessage = RemedialCoursePlugin::create()->getRemedialCourseList(
|
||||
$objExercise,
|
||||
api_get_user_id(),
|
||||
api_get_session_id(),
|
||||
false,
|
||||
$learnpath_id ?: 0,
|
||||
$learnpath_item_id ?: 0
|
||||
);
|
||||
|
||||
if (null != $remedialMessage) {
|
||||
Display::addFlash(
|
||||
Display::return_message($remedialMessage, 'warning', false)
|
||||
);
|
||||
}
|
||||
// Unset session for clock time
|
||||
ExerciseLib::exercise_time_control_delete(
|
||||
$objExercise->iid,
|
||||
$learnpath_id,
|
||||
$learnpath_item_id
|
||||
);
|
||||
|
||||
ExerciseLib::delete_chat_exercise_session($exeId);
|
||||
|
||||
if (!in_array($origin, ['learnpath', 'embeddable', 'mobileapp', 'iframe'])) {
|
||||
$pageBottom .= '<div class="question-return">';
|
||||
$pageBottom .= Display::url(
|
||||
get_lang('ReturnToCourseHomepage'),
|
||||
api_get_course_url(),
|
||||
['class' => 'btn btn-primary']
|
||||
);
|
||||
$pageBottom .= '</div>';
|
||||
|
||||
if (api_is_allowed_to_session_edit()) {
|
||||
Exercise::cleanSessionVariables();
|
||||
}
|
||||
|
||||
$showFooter = true;
|
||||
} elseif (in_array($origin, ['embeddable', 'mobileapp', 'iframe'])) {
|
||||
if (api_is_allowed_to_session_edit()) {
|
||||
Exercise::cleanSessionVariables();
|
||||
}
|
||||
Session::write('attempt_remaining', $remainingMessage);
|
||||
$showFooter = false;
|
||||
} else {
|
||||
$lp_mode = Session::read('lp_mode');
|
||||
$url = '../lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$learnpath_id
|
||||
.'&lp_item_id='.$learnpath_item_id.'&exeId='.$exeId
|
||||
.'&fb_type='.$objExercise->getFeedbackType().'#atoc_'.$learnpath_item_id;
|
||||
$href = 'fullscreen' === $lp_mode ? ' window.opener.location.href="'.$url.'" ' : ' top.location.href="'.$url.'"';
|
||||
|
||||
if (api_is_allowed_to_session_edit()) {
|
||||
Exercise::cleanSessionVariables();
|
||||
}
|
||||
Session::write('attempt_remaining', $remainingMessage);
|
||||
|
||||
// Record the results in the learning path, using the SCORM interface (API)
|
||||
$pageBottom .= "<script>window.parent.API.void_save_asset('$total_score', '$max_score', 0, 'completed');</script>";
|
||||
if (empty($_SESSION['oLP']->lti_launch_id)) {
|
||||
$pageBottom .= '<script type="text/javascript">'.$href.'</script>';
|
||||
}
|
||||
$showFooter = false;
|
||||
}
|
||||
|
||||
$template = new Template($nameTools, $showHeader, $showFooter, $showLearnPath);
|
||||
$template->assign('page_top', $pageTop);
|
||||
$template->assign('page_content', $pageContent);
|
||||
$template->assign('page_bottom', $pageBottom);
|
||||
$template->assign('allow_signature', $allowSignature);
|
||||
$template->assign('exe_id', $exeId);
|
||||
$template->assign('actions', $pageActions);
|
||||
$template->assign('content', $template->fetch($template->get_template('exercise/result.tpl')));
|
||||
$template->display_one_col_template();
|
||||
|
||||
function showEmbeddableFinishButton()
|
||||
{
|
||||
$js = '<script>
|
||||
$(function () {
|
||||
$(\'.btn-close-quiz\').on(\'click\', function () {
|
||||
var playerId = window.frameElement.parentElement.parentElement.parentElement.id;
|
||||
window.parent.mejs.players[playerId].play();
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
|
||||
$html = Display::tag(
|
||||
'p',
|
||||
Display::toolbarButton(
|
||||
get_lang('GoBackToVideo'),
|
||||
'#',
|
||||
'undo',
|
||||
'warning',
|
||||
['role' => 'button', 'class' => 'btn-close-quiz']
|
||||
),
|
||||
['class' => 'text-center']
|
||||
);
|
||||
|
||||
// We check if a tool provider
|
||||
if (isset($_REQUEST['lti_launch_id'])) {
|
||||
$ltiLaunchId = Security::remove_XSS($_REQUEST['lti_launch_id']);
|
||||
global $exeId;
|
||||
$js .= '<script>
|
||||
$(function () {
|
||||
var url = "'.api_get_path(WEB_PLUGIN_PATH).'lti_provider/tool/api/score.php?'.api_get_cidreq().'<i_tool=quiz&launch_id='.$ltiLaunchId.'<i_result_id='.$exeId.'";
|
||||
$.get(url);
|
||||
});
|
||||
</script>';
|
||||
}
|
||||
|
||||
return $js.PHP_EOL.$html;
|
||||
}
|
||||
1159
main/exercise/exercise_show.php
Normal file
1159
main/exercise/exercise_show.php
Normal file
File diff suppressed because it is too large
Load Diff
1867
main/exercise/exercise_submit.php
Normal file
1867
main/exercise/exercise_submit.php
Normal file
File diff suppressed because it is too large
Load Diff
451
main/exercise/exercise_submit_modal.php
Normal file
451
main/exercise/exercise_submit_modal.php
Normal file
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CourseBundle\Entity\CQuizAnswer;
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* @author Julio Montoya <gugli100@gmail.com>
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
|
||||
api_protect_course_script();
|
||||
|
||||
require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
|
||||
|
||||
/** @var Exercise $objExercise */
|
||||
$objExercise = Session::read('objExercise');
|
||||
$exerciseResult = Session::read('exerciseResult');
|
||||
|
||||
if (empty($objExercise)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$feedbackType = $objExercise->getFeedbackType();
|
||||
$exerciseType = $objExercise->type;
|
||||
if (!in_array($feedbackType, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
|
||||
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
|
||||
$questionList = Session::read('questionList');
|
||||
|
||||
$exerciseId = (int) $_GET['exerciseId'];
|
||||
$questionNum = (int) $_GET['num'];
|
||||
$questionId = $questionList[$questionNum];
|
||||
$choiceValue = $_GET['choice'] ?? '';
|
||||
$hotSpot = $_GET['hotspot'] ?? '';
|
||||
$tryAgain = isset($_GET['tryagain']) && 1 === (int) $_GET['tryagain'];
|
||||
|
||||
$allowTryAgain = false;
|
||||
if ($tryAgain) {
|
||||
// Check if try again exists in this question, otherwise only allow one attempt BT#15827.
|
||||
$objQuestionTmp = Question::read($questionId);
|
||||
$answerType = $objQuestionTmp->selectType();
|
||||
$showResult = false;
|
||||
$objAnswerTmp = new Answer($questionId, api_get_course_int_id());
|
||||
$answers = $objAnswerTmp->getAnswers();
|
||||
if (!empty($answers)) {
|
||||
foreach ($answers as $answerData) {
|
||||
if (isset($answerData['destination'])) {
|
||||
$itemList = explode('@@', $answerData['destination']);
|
||||
if (isset($itemList[0]) && !empty($itemList[0])) {
|
||||
$allowTryAgain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$loaded = isset($_GET['loaded']);
|
||||
if ($allowTryAgain || $feedbackType == EXERCISE_FEEDBACK_TYPE_DIRECT) {
|
||||
unset($exerciseResult[$questionId]);
|
||||
}
|
||||
|
||||
if (empty($choiceValue) && isset($exerciseResult[$questionId])) {
|
||||
$choiceValue = $exerciseResult[$questionId];
|
||||
}
|
||||
|
||||
if (!empty($hotSpot)) {
|
||||
if (isset($hotSpot[$questionId])) {
|
||||
$hotSpot = $hotSpot[$questionId];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($choiceValue)) {
|
||||
if (isset($choiceValue[$questionId])) {
|
||||
$choiceValue = $choiceValue[$questionId];
|
||||
}
|
||||
}
|
||||
|
||||
$header = '';
|
||||
$exeId = 0;
|
||||
if ($objExercise->getFeedbackType() === EXERCISE_FEEDBACK_TYPE_POPUP) {
|
||||
$exeId = Session::read('exe_id');
|
||||
$header = '
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="global-modal-title">'.get_lang('Incorrect').'</h4>
|
||||
</div>';
|
||||
}
|
||||
|
||||
echo '<script>
|
||||
function tryAgain() {
|
||||
$(function () {
|
||||
$("#global-modal").modal("hide");
|
||||
});
|
||||
}
|
||||
|
||||
function SendEx(num) {
|
||||
if (num == -1) {
|
||||
window.location.href = "exercise_result.php?'.api_get_cidreq().'&exe_id='.$exeId.'&take_session=1&exerciseId='.$exerciseId.'&num="+num+"&learnpath_item_id='.$learnpath_item_id.'&learnpath_id='.$learnpath_id.'";
|
||||
} else {
|
||||
num -= 1;
|
||||
window.location.href = "exercise_submit.php?'.api_get_cidreq().'&tryagain=1&exerciseId='.$exerciseId.'&num="+num+"&learnpath_item_id='.$learnpath_item_id.'&learnpath_id='.$learnpath_id.'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>';
|
||||
|
||||
echo '<div id="delineation-container">';
|
||||
// Getting the options by js
|
||||
if (empty($choiceValue) && empty($hotSpot) && $loaded) {
|
||||
$nextQuestion = $questionNum + 1;
|
||||
$destinationId = $questionList[$nextQuestion] ?? -1;
|
||||
$icon = Display::return_icon(
|
||||
'reload.png',
|
||||
'',
|
||||
['style' => 'width:22px; height:22px; padding-left:0px;padding-right:5px;']
|
||||
);
|
||||
$links = '<a onclick="tryAgain();" href="#">'.get_lang('TryAgain').'</a> '.$icon.' ';
|
||||
|
||||
// the link to finish the test
|
||||
if (-1 == $destinationId) {
|
||||
$links .= Display::return_icon(
|
||||
'finish.gif',
|
||||
'',
|
||||
['style' => 'width:22px; height:22px; padding-left:0px;padding-right:5px;']
|
||||
).'<a onclick="SendEx(-1);" href="#">'.get_lang('EndActivity').'</a><br /><br />';
|
||||
} else {
|
||||
// the link to other question
|
||||
if (in_array($destinationId, $questionList)) {
|
||||
$num_value_array = array_keys($questionList, $destinationId);
|
||||
$icon = Display::return_icon(
|
||||
'quiz.png',
|
||||
'',
|
||||
['style' => 'padding-left:0px;padding-right:5px;']
|
||||
);
|
||||
$links .= '<a onclick="SendEx('.$num_value_array[0].');" href="#">'.
|
||||
get_lang('Question').' '.$num_value_array[0].'</a> ';
|
||||
$links .= $icon;
|
||||
}
|
||||
}
|
||||
echo $header;
|
||||
echo '<div class="row"><div class="col-md-5 col-md-offset-7"><h5 class="pull-right">'.$links.'</h5></div></div>';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($choiceValue) && empty($hotSpot)) {
|
||||
echo "<script>
|
||||
// this works for only radio buttons
|
||||
var f = window.document.frm_exercise;
|
||||
var choice_js = {answers: []};
|
||||
var hotspot = new Array();
|
||||
var hotspotcoord = new Array();
|
||||
var counter = 0;
|
||||
|
||||
for (var i = 0; i < f.elements.length; i++) {
|
||||
if (f.elements[i].type == 'radio' && f.elements[i].checked) {
|
||||
choice_js.answers.push(f.elements[i].value);
|
||||
counter ++;
|
||||
}
|
||||
|
||||
if (f.elements[i].type == 'checkbox' && f.elements[i].checked) {
|
||||
choice_js.answers.push(f.elements[i].value);
|
||||
counter ++;
|
||||
}
|
||||
|
||||
if (f.elements[i].type == 'hidden') {
|
||||
var name = f.elements[i].name;
|
||||
|
||||
if (name.substr(0,7) == 'hotspot') {
|
||||
hotspot.push(f.elements[i].value);
|
||||
}
|
||||
|
||||
if (name.substr(0,20) == 'hotspot_coordinates') {
|
||||
hotspotcoord.push(f.elements[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var my_choice = $('*[name*=\"choice[".$questionId."]\"]').serialize();
|
||||
var hotspot = $('*[name*=\"hotspot[".$questionId."]\"]').serialize();
|
||||
";
|
||||
|
||||
// IMPORTANT
|
||||
// This is the real redirect function
|
||||
$extraUrl = '&loaded=1&exerciseId='.$exerciseId.'&num='.$questionNum.'&learnpath_id='.$learnpath_id.'&learnpath_item_id='.$learnpath_item_id;
|
||||
$url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit_modal.php?'.api_get_cidreq().$extraUrl;
|
||||
echo ' url = "'.addslashes($url).'&hotspotcoord="+ hotspotcoord + "&"+ hotspot + "&"+ my_choice;';
|
||||
echo "$('#global-modal .modal-body').load(url);";
|
||||
echo '</script>';
|
||||
exit;
|
||||
}
|
||||
$choice = [];
|
||||
$choice[$questionId] = $choiceValue ?? null;
|
||||
if (!is_array($exerciseResult)) {
|
||||
$exerciseResult = [];
|
||||
}
|
||||
$saveResults = (int) $objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_POPUP;
|
||||
|
||||
// if the user has answered at least one question
|
||||
if (is_array($choice)) {
|
||||
if (in_array($exerciseType, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
|
||||
// $exerciseResult receives the content of the form.
|
||||
// Each choice of the student is stored into the array $choice
|
||||
$exerciseResult = $choice;
|
||||
} else {
|
||||
// gets the question ID from $choice. It is the key of the array
|
||||
[$key] = array_keys($choice);
|
||||
// if the user didn't already answer this question
|
||||
if (!isset($exerciseResult[$key])) {
|
||||
// stores the user answer into the array
|
||||
$exerciseResult[$key] = $choice[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the script "exercise_result.php" will take the variable $exerciseResult from the session
|
||||
Session::write('exerciseResult', $exerciseResult);
|
||||
|
||||
$objQuestionTmp = Question::read($questionId);
|
||||
$answerType = $objQuestionTmp->selectType();
|
||||
$showResult = false;
|
||||
|
||||
$objAnswerTmp = new Answer($questionId, api_get_course_int_id());
|
||||
if (EXERCISE_FEEDBACK_TYPE_DIRECT === $objExercise->getFeedbackType()) {
|
||||
$showResult = true;
|
||||
}
|
||||
switch ($answerType) {
|
||||
case MULTIPLE_ANSWER:
|
||||
if (is_array($choiceValue)) {
|
||||
$choiceValue = array_combine(array_values($choiceValue), array_values($choiceValue));
|
||||
}
|
||||
break;
|
||||
case UNIQUE_ANSWER:
|
||||
if (is_array($choiceValue) && isset($choiceValue[0])) {
|
||||
$choiceValue = $choiceValue[0];
|
||||
}
|
||||
break;
|
||||
case DRAGGABLE:
|
||||
break;
|
||||
case HOT_SPOT_DELINEATION:
|
||||
$showResult = true;
|
||||
if (is_array($hotSpot)) {
|
||||
$choiceValue = $hotSpot[1] ?? '';
|
||||
$_SESSION['exerciseResultCoordinates'][$questionId] = $choiceValue; //needed for exercise_result.php
|
||||
$delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
|
||||
$answer_delineation_destination = $objAnswerTmp->selectDestination(1);
|
||||
$_SESSION['hotspot_coord'][$questionId][1] = $delineation_cord;
|
||||
$_SESSION['hotspot_dest'][$questionId][1] = $answer_delineation_destination;
|
||||
}
|
||||
break;
|
||||
case CALCULATED_ANSWER:
|
||||
break;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$result = $objExercise->manage_answer(
|
||||
$exeId,
|
||||
$questionId,
|
||||
$choiceValue,
|
||||
'exercise_result',
|
||||
[],
|
||||
$saveResults,
|
||||
false,
|
||||
$showResult,
|
||||
null,
|
||||
[],
|
||||
true,
|
||||
false,
|
||||
true
|
||||
);
|
||||
$manageAnswerHtmlContent = ob_get_clean();
|
||||
$contents = '';
|
||||
$answerCorrect = false;
|
||||
$partialCorrect = false;
|
||||
if (!empty($result)) {
|
||||
switch ($answerType) {
|
||||
case MULTIPLE_ANSWER:
|
||||
case UNIQUE_ANSWER:
|
||||
case DRAGGABLE:
|
||||
case HOT_SPOT_DELINEATION:
|
||||
case CALCULATED_ANSWER:
|
||||
if ($result['score'] == $result['weight']) {
|
||||
$answerCorrect = true;
|
||||
}
|
||||
|
||||
// Check partial correct
|
||||
if (false === $answerCorrect) {
|
||||
if (!empty($result['score'])) {
|
||||
$partialCorrect = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$header = '';
|
||||
if ($objExercise->getFeedbackType() === EXERCISE_FEEDBACK_TYPE_DIRECT) {
|
||||
if (isset($result['correct_answer_id'])) {
|
||||
foreach ($result['correct_answer_id'] as $answerId) {
|
||||
/** @var Answer $answer */
|
||||
$contents .= $objAnswerTmp->selectComment($answerId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message = get_lang('Incorrect');
|
||||
if ($answerCorrect) {
|
||||
$message = get_lang('Correct');
|
||||
} else {
|
||||
if ($partialCorrect) {
|
||||
$message = get_lang('PartialCorrect');
|
||||
}
|
||||
}
|
||||
|
||||
$comments = '';
|
||||
if ($answerType != HOT_SPOT_DELINEATION) {
|
||||
if (isset($result['correct_answer_id'])) {
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
|
||||
$row = 0;
|
||||
$table->setCellContents($row, 0, get_lang('YourAnswer'));
|
||||
if ($answerType != DRAGGABLE) {
|
||||
$table->setCellContents($row, 1, get_lang('Comment'));
|
||||
}
|
||||
|
||||
$data = [];
|
||||
foreach ($result['correct_answer_id'] as $answerId) {
|
||||
$answer = $objAnswerTmp->getAnswerByAutoId($answerId);
|
||||
if (!empty($answer) && isset($answer['comment'])) {
|
||||
$data[] = [$answer['answer'], $answer['comment']];
|
||||
} else {
|
||||
$answer = $objAnswerTmp->selectAnswer($answerId);
|
||||
$comment = $objAnswerTmp->selectComment($answerId);
|
||||
$data[] = [$answer, $comment];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
$row = 1;
|
||||
foreach ($data as $dataItem) {
|
||||
$table->setCellContents($row, 0, $dataItem[0]);
|
||||
$table->setCellContents($row, 1, $dataItem[1]);
|
||||
$row++;
|
||||
}
|
||||
$comments = $table->toHtml();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$contents .= $comments;
|
||||
$header = '
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="global-modal-title">'.$message.'</h4>
|
||||
</div>';
|
||||
}
|
||||
|
||||
if ($answerType === HOT_SPOT_DELINEATION) {
|
||||
$contents = $manageAnswerHtmlContent;
|
||||
}
|
||||
$links = '';
|
||||
if (EXERCISE_FEEDBACK_TYPE_DIRECT === $objExercise->getFeedbackType()) {
|
||||
if (isset($choiceValue) && -1 == $choiceValue) {
|
||||
if (HOT_SPOT_DELINEATION != $answerType) {
|
||||
$links .= '<a href="#" onclick="tb_remove();">'.get_lang('ChooseAnAnswer').'</a><br />';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$destinationId = null;
|
||||
if (isset($result['answer_destination']) && CQuizAnswer::DEFAULT_DESTINATION !== $result['answer_destination']) {
|
||||
$itemList = explode('@@', $result['answer_destination']);
|
||||
$try = $itemList[0];
|
||||
$lp = $itemList[1];
|
||||
$destinationId = $itemList[2];
|
||||
$url = $itemList[3];
|
||||
}
|
||||
|
||||
// the link to retry the question
|
||||
if (isset($try) && 1 == $try) {
|
||||
$num_value_array = array_keys($questionList, $questionId);
|
||||
$links .= Display::return_icon(
|
||||
'reload.gif',
|
||||
'',
|
||||
['style' => 'padding-left:0px;padding-right:5px;']
|
||||
).'<a onclick="SendEx('.$num_value_array[0].');" href="#">'.get_lang('TryAgain').'</a><br /><br />';
|
||||
}
|
||||
|
||||
// the link to theory (a learning path)
|
||||
if (!empty($lp)) {
|
||||
$lp_url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$lp;
|
||||
$links .= Display::return_icon(
|
||||
'theory.gif',
|
||||
'',
|
||||
['style' => 'padding-left:0px;padding-right:5px;']
|
||||
).'<a target="_blank" href="'.$lp_url.'">'.get_lang('SeeTheory').'</a><br />';
|
||||
}
|
||||
|
||||
$links .= '<br />';
|
||||
|
||||
// the link to an external website or link
|
||||
if (!empty($url) && $url != -1) {
|
||||
$links .= Display::return_icon(
|
||||
'link.gif',
|
||||
'',
|
||||
['style' => 'padding-left:0px;padding-right:5px;']
|
||||
).'<a target="_blank" href="'.$url.'">'.get_lang('VisitUrl').'</a><br /><br />';
|
||||
}
|
||||
|
||||
if (null === $destinationId) {
|
||||
$nextQuestion = $questionNum + 1;
|
||||
$destinationId = $questionList[$nextQuestion] ?? -1;
|
||||
}
|
||||
|
||||
// the link to finish the test
|
||||
if (-1 == $destinationId) {
|
||||
$links .= Display::return_icon(
|
||||
'finish.gif',
|
||||
'',
|
||||
['style' => 'width:22px; height:22px; padding-left:0px;padding-right:5px;']
|
||||
).'<a onclick="SendEx(-1);" href="#">'.get_lang('EndActivity').'</a><br /><br />';
|
||||
} else {
|
||||
// the link to other question
|
||||
if (in_array($destinationId, $questionList)) {
|
||||
$num_value_array = array_keys($questionList, $destinationId);
|
||||
$icon = Display::return_icon(
|
||||
'quiz.png',
|
||||
'',
|
||||
['style' => 'padding-left:0px;padding-right:5px;']
|
||||
);
|
||||
$links .= '<a onclick="SendEx('.$num_value_array[0].');" href="#">'.
|
||||
get_lang('Question').' '.$num_value_array[0].'</a> ';
|
||||
$links .= $icon;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($links)) {
|
||||
echo $header;
|
||||
echo '<div>'.$contents.'</div>';
|
||||
echo '<div style="padding-left: 450px"><h5>'.$links.'</h5></div>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
$questionNum++;
|
||||
echo '<script>
|
||||
window.location.href = "exercise_submit.php?exerciseId='.$exerciseId.'&num='.$questionNum.'&'.api_get_cidreq().'";
|
||||
</script>';
|
||||
}
|
||||
echo '</div>';
|
||||
50
main/exercise/export/aiken/aiken_classes.php
Normal file
50
main/exercise/export/aiken/aiken_classes.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* @author Claro Team <cvs@claroline.net>
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com> - updated ImsAnswerHotspot to match QTI norms
|
||||
* @author César Perales <cesar.perales@gmail.com> Updated function names and import files for Aiken format support
|
||||
*/
|
||||
|
||||
/**
|
||||
* Aiken2Question transformation class.
|
||||
*/
|
||||
class Aiken2Question extends Question
|
||||
{
|
||||
/**
|
||||
* Include the correct answer class and create answer.
|
||||
*/
|
||||
public function setAnswer()
|
||||
{
|
||||
switch ($this->type) {
|
||||
case MCUA:
|
||||
$answer = new AikenAnswerMultipleChoice($this->iid);
|
||||
|
||||
return $answer;
|
||||
default:
|
||||
$answer = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return $answer;
|
||||
}
|
||||
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class.
|
||||
*/
|
||||
class AikenAnswerMultipleChoice extends Answer
|
||||
{
|
||||
}
|
||||
611
main/exercise/export/aiken/aiken_import.inc.php
Normal file
611
main/exercise/export/aiken/aiken_import.inc.php
Normal file
@@ -0,0 +1,611 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Library for the import of Aiken format.
|
||||
*
|
||||
* @author claro team <cvs@claroline.net>
|
||||
* @author Guillaume Lederer <guillaume@claroline.net>
|
||||
* @author César Perales <cesar.perales@gmail.com> Parse function for Aiken format
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function displays the form for import of the zip file with qti2.
|
||||
*
|
||||
* @param string Report message to show in case of error
|
||||
*/
|
||||
function aiken_display_form()
|
||||
{
|
||||
$name_tools = get_lang('ImportAikenQuiz');
|
||||
$form = '<div class="actions">';
|
||||
$form .= '<a href="exercise.php?show=test&'.api_get_cidreq().'">'.
|
||||
Display::return_icon(
|
||||
'back.png',
|
||||
get_lang('BackToExercisesList'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
).'</a>';
|
||||
$form .= '</div>';
|
||||
$form_validator = new FormValidator(
|
||||
'aiken_upload',
|
||||
'post',
|
||||
api_get_self()."?".api_get_cidreq(),
|
||||
null,
|
||||
['enctype' => 'multipart/form-data']
|
||||
);
|
||||
$form_validator->addElement('header', $name_tools);
|
||||
$form_validator->addElement('text', 'total_weight', get_lang('TotalWeight'));
|
||||
$form_validator->addElement('file', 'userFile', get_lang('File'));
|
||||
$form_validator->addButtonUpload(get_lang('Upload'), 'submit');
|
||||
$form .= $form_validator->returnForm();
|
||||
$form .= '<blockquote>'.get_lang('ImportAikenQuizExplanation').'<br /><pre>'.get_lang('ImportAikenQuizExplanationExample').'</pre></blockquote>';
|
||||
echo $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Aiken format using AI APIs (supports multiple providers).
|
||||
* Requires plugin ai_helper to connect to the API.
|
||||
*/
|
||||
function generateAikenForm()
|
||||
{
|
||||
if (!('true' === api_get_plugin_setting('ai_helper', 'tool_enable') && 'true' === api_get_plugin_setting('ai_helper', 'tool_quiz_enable'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$plugin = AiHelperPlugin::create();
|
||||
$availableApis = $plugin->getApiList();
|
||||
|
||||
$configuredApi = $plugin->get('api_name');
|
||||
$hasSingleApi = count($availableApis) === 1 || isset($availableApis[$configuredApi]);
|
||||
|
||||
$form = new FormValidator(
|
||||
'aiken_generate',
|
||||
'post',
|
||||
api_get_self()."?".api_get_cidreq(),
|
||||
null
|
||||
);
|
||||
$form->addElement('header', get_lang('AIQuestionsGenerator'));
|
||||
|
||||
if ($hasSingleApi) {
|
||||
$apiName = $availableApis[$configuredApi] ?? $configuredApi;
|
||||
$form->addHtml('<div style="margin-bottom: 10px; font-size: 14px; color: #555;">'
|
||||
.sprintf(get_lang('UsingAIProviderX'), '<strong>'.htmlspecialchars($apiName).'</strong>').'</div>');
|
||||
}
|
||||
|
||||
$form->addElement('text', 'quiz_name', get_lang('QuestionsTopic'));
|
||||
$form->addRule('quiz_name', get_lang('ThisFieldIsRequired'), 'required');
|
||||
$form->addElement('number', 'nro_questions', get_lang('NumberOfQuestions'));
|
||||
$form->addRule('nro_questions', get_lang('ThisFieldIsRequired'), 'required');
|
||||
|
||||
$options = [
|
||||
'multiple_choice' => get_lang('MultipleAnswer'),
|
||||
];
|
||||
$form->addElement('select', 'question_type', get_lang('QuestionType'), $options);
|
||||
|
||||
if (!$hasSingleApi) {
|
||||
$form->addElement(
|
||||
'select',
|
||||
'ai_provider',
|
||||
get_lang('AIProvider'),
|
||||
array_combine(array_keys($availableApis), array_keys($availableApis))
|
||||
);
|
||||
}
|
||||
|
||||
$generateUrl = api_get_path(WEB_PLUGIN_PATH).'ai_helper/tool/answers.php';
|
||||
$language = api_get_interface_language();
|
||||
$form->addHtml('<script>
|
||||
$(function () {
|
||||
$("#aiken-area").hide();
|
||||
$("#generate-aiken").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var btnGenerate = $(this);
|
||||
var quizName = $("[name=\'quiz_name\']").val();
|
||||
var nroQ = parseInt($("[name=\'nro_questions\']").val());
|
||||
var qType = $("[name=\'question_type\']").val();'
|
||||
.(!$hasSingleApi ? 'var provider = $("[name=\'ai_provider\']").val();' : 'var provider = "'.$configuredApi.'";').
|
||||
'var valid = (quizName != \'\' && nroQ > 0);
|
||||
if (valid) {
|
||||
btnGenerate.attr("disabled", true);
|
||||
btnGenerate.text("'.get_lang('PleaseWaitThisCouldTakeAWhile').'");
|
||||
$("#textarea-aiken").text("");
|
||||
$("#aiken-area").hide();
|
||||
$.getJSON("'.$generateUrl.'", {
|
||||
"quiz_name": quizName,
|
||||
"nro_questions": nroQ,
|
||||
"question_type": qType,
|
||||
"language": "'.$language.'",
|
||||
"ai_provider": provider
|
||||
}).done(function (data) {
|
||||
btnGenerate.attr("disabled", false);
|
||||
btnGenerate.text("'.get_lang('Generate').'");
|
||||
if (data.success && data.success == true) {
|
||||
$("#aiken-area").show();
|
||||
$("#textarea-aiken").text(data.text);
|
||||
$("input[name=\'ai_total_weight\']").val(nroQ * qWeight);
|
||||
$("#textarea-aiken").focus();
|
||||
} else {
|
||||
var errorMessage = "'.get_lang('NoSearchResults').'. '.get_lang('PleaseTryAgain').'";
|
||||
if (data.text) {
|
||||
errorMessage = data.text;
|
||||
}
|
||||
alert(errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>');
|
||||
|
||||
$form->addButton(
|
||||
'generate_aiken_button',
|
||||
get_lang('Generate'),
|
||||
'',
|
||||
'default',
|
||||
'default',
|
||||
null,
|
||||
['id' => 'generate-aiken']
|
||||
);
|
||||
|
||||
$form->addHtml('<div id="aiken-area">');
|
||||
$form->addElement(
|
||||
'textarea',
|
||||
'aiken_format',
|
||||
get_lang('Answers'),
|
||||
[
|
||||
'id' => 'textarea-aiken',
|
||||
'style' => 'width: 100%; height: 250px;',
|
||||
]
|
||||
);
|
||||
$form->addElement('number', 'ai_total_weight', get_lang('TotalWeight'));
|
||||
$form->addButtonImport(get_lang('Import'), 'submit_aiken_generated');
|
||||
$form->addHtml('</div>');
|
||||
|
||||
echo $form->returnForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uploaded file (from $_FILES) and unzip it to the given directory.
|
||||
*
|
||||
* @param string The directory where to do the work
|
||||
* @param string The path of the temporary directory where the exercise was uploaded and unzipped
|
||||
* @param string $baseWorkDir
|
||||
* @param string $uploadPath
|
||||
*
|
||||
* @return bool True on success, false on failure
|
||||
*/
|
||||
function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)
|
||||
{
|
||||
$_course = api_get_course_info();
|
||||
$_user = api_get_user_info();
|
||||
|
||||
// Check if the file is valid (not to big and exists)
|
||||
if (!isset($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) {
|
||||
// upload failed
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/.zip$/i', $_FILES['userFile']['name']) &&
|
||||
handle_uploaded_document(
|
||||
$_course,
|
||||
$_FILES['userFile'],
|
||||
$baseWorkDir,
|
||||
$uploadPath,
|
||||
$_user['user_id'],
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
'overwrite',
|
||||
false,
|
||||
true
|
||||
)
|
||||
) {
|
||||
if (!function_exists('gzopen')) {
|
||||
return false;
|
||||
}
|
||||
// upload successful
|
||||
return true;
|
||||
} elseif (preg_match('/.txt/i', $_FILES['userFile']['name']) &&
|
||||
handle_uploaded_document(
|
||||
$_course,
|
||||
$_FILES['userFile'],
|
||||
$baseWorkDir,
|
||||
$uploadPath,
|
||||
$_user['user_id'],
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
'overwrite',
|
||||
false
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to import the Aiken exercise.
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $request
|
||||
*
|
||||
* @return mixed True on success, error message on failure
|
||||
*/
|
||||
function aikenImportExercise($file = null, $request = [])
|
||||
{
|
||||
$exerciseInfo = [];
|
||||
$fileIsSet = false;
|
||||
|
||||
if (isset($file)) {
|
||||
$fileIsSet = true;
|
||||
// The import is from aiken file format.
|
||||
$archivePath = api_get_path(SYS_ARCHIVE_PATH).'aiken/';
|
||||
$baseWorkDir = $archivePath;
|
||||
|
||||
$uploadPath = 'aiken_'.api_get_unique_id();
|
||||
if (!is_dir($baseWorkDir.$uploadPath)) {
|
||||
mkdir($baseWorkDir.$uploadPath, api_get_permissions_for_new_directories(), true);
|
||||
}
|
||||
|
||||
// set some default values for the new exercise
|
||||
$exerciseInfo['name'] = preg_replace('/.(zip|txt)$/i', '', $file);
|
||||
$exerciseInfo['total_weight'] = !empty($_POST['total_weight']) ? (int) ($_POST['total_weight']) : 20;
|
||||
$exerciseInfo['question'] = [];
|
||||
|
||||
// if file is not a .zip, then we cancel all
|
||||
if (!preg_match('/.(zip|txt)$/i', $file)) {
|
||||
return 'YouMustUploadAZipOrTxtFile';
|
||||
}
|
||||
|
||||
// unzip the uploaded file in a tmp directory
|
||||
if (preg_match('/.(zip|txt)$/i', $file)) {
|
||||
if (!get_and_unzip_uploaded_exercise($baseWorkDir.$uploadPath, '/')) {
|
||||
return 'ThereWasAProblemWithYourFile';
|
||||
}
|
||||
}
|
||||
|
||||
// find the different manifests for each question and parse them
|
||||
$exerciseHandle = opendir($baseWorkDir.$uploadPath);
|
||||
$fileFound = false;
|
||||
$operation = false;
|
||||
$result = false;
|
||||
|
||||
// Parse every subdirectory to search txt question files
|
||||
while (false !== ($file = readdir($exerciseHandle))) {
|
||||
if (is_dir($baseWorkDir.'/'.$uploadPath.$file) && $file != "." && $file != "..") {
|
||||
//find each manifest for each question repository found
|
||||
$questionHandle = opendir($baseWorkDir.'/'.$uploadPath.$file);
|
||||
while (false !== ($questionFile = readdir($questionHandle))) {
|
||||
if (preg_match('/.txt$/i', $questionFile)) {
|
||||
$result = aiken_parse_file(
|
||||
$exerciseInfo,
|
||||
$baseWorkDir,
|
||||
$file,
|
||||
$questionFile
|
||||
);
|
||||
$fileFound = true;
|
||||
}
|
||||
}
|
||||
} elseif (preg_match('/.txt$/i', $file)) {
|
||||
$result = aiken_parse_file($exerciseInfo, $baseWorkDir.$uploadPath, '', $file);
|
||||
$fileFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$fileFound) {
|
||||
$result = 'NoTxtFileFoundInTheZip';
|
||||
}
|
||||
|
||||
if (true !== $result) {
|
||||
return $result;
|
||||
}
|
||||
} elseif (!empty($request)) {
|
||||
// The import is from aiken generated in textarea.
|
||||
$exerciseInfo['name'] = $request['quiz_name'];
|
||||
$exerciseInfo['total_weight'] = !empty($_POST['ai_total_weight']) ? (int) ($_POST['ai_total_weight']) : (int) $request['nro_questions'];
|
||||
$exerciseInfo['question'] = [];
|
||||
$exerciseInfo['course_id'] = isset($request['course_id']) ? (int) $request['course_id'] : 0;
|
||||
setExerciseInfoFromAikenText($request['aiken_format'], $exerciseInfo);
|
||||
}
|
||||
|
||||
// 1. Create exercise.
|
||||
if (!empty($exerciseInfo)) {
|
||||
$exercise = new Exercise($exerciseInfo['course_id']);
|
||||
$exercise->exercise = $exerciseInfo['name'];
|
||||
$exercise->disable(); // Invisible by default
|
||||
$exercise->updateResultsDisabled(0); // Auto-evaluation mode: show score and expected answers
|
||||
$exercise->save();
|
||||
$lastExerciseId = $exercise->selectId();
|
||||
$tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
|
||||
$tableAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER);
|
||||
if (!empty($lastExerciseId)) {
|
||||
$courseId = !empty($exerciseInfo['course_id']) ? (int) $exerciseInfo['course_id'] : api_get_course_int_id();
|
||||
foreach ($exerciseInfo['question'] as $key => $questionArray) {
|
||||
if (!isset($questionArray['title'])) {
|
||||
continue;
|
||||
}
|
||||
// 2. Create question.
|
||||
$question = new Aiken2Question();
|
||||
$question->type = $questionArray['type'];
|
||||
$question->setAnswer();
|
||||
$question->updateTitle($questionArray['title']);
|
||||
|
||||
if (isset($questionArray['description'])) {
|
||||
$question->updateDescription($questionArray['description']);
|
||||
}
|
||||
$type = $question->selectType();
|
||||
$question->course = api_get_course_info_by_id($courseId);
|
||||
$question->type = constant($type);
|
||||
$question->save($exercise);
|
||||
$last_question_id = $question->selectId();
|
||||
|
||||
// 3. Create answer
|
||||
$answer = new Answer($last_question_id, $courseId, $exercise, false);
|
||||
$answer->new_nbrAnswers = isset($questionArray['answer']) ? count($questionArray['answer']) : 0;
|
||||
$max_score = 0;
|
||||
|
||||
$scoreFromFile = 0;
|
||||
if (isset($questionArray['score']) && !empty($questionArray['score'])) {
|
||||
$scoreFromFile = $questionArray['score'];
|
||||
}
|
||||
|
||||
foreach ($questionArray['answer'] as $key => $answers) {
|
||||
$key++;
|
||||
$answer->new_answer[$key] = $answers['value'];
|
||||
$answer->new_position[$key] = $key;
|
||||
$answer->new_comment[$key] = '';
|
||||
// Correct answers ...
|
||||
if (isset($questionArray['correct_answers']) &&
|
||||
in_array($key, $questionArray['correct_answers'])
|
||||
) {
|
||||
$answer->new_correct[$key] = 1;
|
||||
if (isset($questionArray['feedback'])) {
|
||||
$answer->new_comment[$key] = $questionArray['feedback'];
|
||||
}
|
||||
} else {
|
||||
$answer->new_correct[$key] = 0;
|
||||
}
|
||||
|
||||
if (isset($questionArray['weighting'][$key - 1])) {
|
||||
$answer->new_weighting[$key] = $questionArray['weighting'][$key - 1];
|
||||
$max_score += $questionArray['weighting'][$key - 1];
|
||||
}
|
||||
|
||||
if (!empty($scoreFromFile) && $answer->new_correct[$key]) {
|
||||
$answer->new_weighting[$key] = $scoreFromFile;
|
||||
}
|
||||
|
||||
$params = [
|
||||
'c_id' => $courseId,
|
||||
'question_id' => $last_question_id,
|
||||
'answer' => $answer->new_answer[$key],
|
||||
'correct' => $answer->new_correct[$key],
|
||||
'comment' => $answer->new_comment[$key],
|
||||
'ponderation' => isset($answer->new_weighting[$key]) ? $answer->new_weighting[$key] : '',
|
||||
'position' => $answer->new_position[$key],
|
||||
'hotspot_coordinates' => '',
|
||||
'hotspot_type' => '',
|
||||
];
|
||||
|
||||
$answerId = Database::insert($tableAnswer, $params);
|
||||
if ($answerId) {
|
||||
$params = [
|
||||
'id_auto' => $answerId,
|
||||
'iid' => $answerId,
|
||||
];
|
||||
Database::update($tableAnswer, $params, ['iid = ?' => [$answerId]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($scoreFromFile)) {
|
||||
$max_score = $scoreFromFile;
|
||||
}
|
||||
$params = ['ponderation' => $max_score];
|
||||
Database::update(
|
||||
$tableQuestion,
|
||||
$params,
|
||||
['iid = ?' => [$last_question_id]]
|
||||
);
|
||||
}
|
||||
|
||||
// Delete the temp dir where the exercise was unzipped
|
||||
if ($fileIsSet) {
|
||||
my_delete($baseWorkDir.$uploadPath);
|
||||
}
|
||||
|
||||
// Invisible by default
|
||||
api_item_property_update(
|
||||
api_get_course_info(),
|
||||
TOOL_QUIZ,
|
||||
$lastExerciseId,
|
||||
'invisible',
|
||||
api_get_user_id()
|
||||
);
|
||||
|
||||
return $lastExerciseId;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exercise information from an aiken text formatted.
|
||||
*/
|
||||
function setExerciseInfoFromAikenText($aikenText, &$exerciseInfo)
|
||||
{
|
||||
$detect = mb_detect_encoding($aikenText, 'ASCII', true);
|
||||
if ('ASCII' === $detect) {
|
||||
$data = explode("\n", $aikenText);
|
||||
} else {
|
||||
if (false !== stripos($aikenText, "\x0D") || false !== stripos($aikenText, "\r\n")) {
|
||||
$text = str_ireplace(["\x0D", "\r\n"], "\n", $aikenText); // Removes ^M char from win files.
|
||||
$data = explode("\n\n", $text);
|
||||
} else {
|
||||
$data = explode("\n", $aikenText);
|
||||
}
|
||||
}
|
||||
|
||||
$questionIndex = 0;
|
||||
$answersArray = [];
|
||||
foreach ($data as $line => $info) {
|
||||
$info = trim($info);
|
||||
if (empty($info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//make sure it is transformed from iso-8859-1 to utf-8 if in that form
|
||||
if (!mb_check_encoding($info, 'utf-8') && mb_check_encoding($info, 'iso-8859-1')) {
|
||||
$info = utf8_encode($info);
|
||||
}
|
||||
$exerciseInfo['question'][$questionIndex]['type'] = 'MCUA';
|
||||
|
||||
if (preg_match('/^([A-Za-z])(\)|\.)\s(.*)/', $info, $matches)) {
|
||||
//adding one of the possible answers
|
||||
$exerciseInfo['question'][$questionIndex]['answer'][]['value'] = $matches[3];
|
||||
$answersArray[] = $matches[1];
|
||||
} elseif (preg_match('/^ANSWER:\s?([A-Z])\s?/', $info, $matches)) {
|
||||
//the correct answers
|
||||
$correctAnswerIndex = array_search($matches[1], $answersArray);
|
||||
$exerciseInfo['question'][$questionIndex]['correct_answers'][] = $correctAnswerIndex + 1;
|
||||
//weight for correct answer
|
||||
$exerciseInfo['question'][$questionIndex]['weighting'][$correctAnswerIndex] = 1;
|
||||
$next = $line + 1;
|
||||
|
||||
if (isset($data[$next]) && false !== strpos($data[$next], 'ANSWER_EXPLANATION:')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($data[$next]) && false !== strpos($data[$next], 'DESCRIPTION:')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if next has score, otherwise loop too next question.
|
||||
if (isset($data[$next]) && false === strpos($data[$next], 'SCORE:')) {
|
||||
$answersArray = [];
|
||||
$questionIndex++;
|
||||
continue;
|
||||
}
|
||||
} elseif (preg_match('/^SCORE:\s?(.*)/', $info, $matches)) {
|
||||
$exerciseInfo['question'][$questionIndex]['score'] = (float) $matches[1];
|
||||
$answersArray = [];
|
||||
$questionIndex++;
|
||||
continue;
|
||||
} elseif (preg_match('/^DESCRIPTION:\s?(.*)/', $info, $matches)) {
|
||||
$exerciseInfo['question'][$questionIndex]['description'] = $matches[1];
|
||||
$next = $line + 1;
|
||||
|
||||
if (isset($data[$next]) && false !== strpos($data[$next], 'ANSWER_EXPLANATION:')) {
|
||||
continue;
|
||||
}
|
||||
// Check if next has score, otherwise loop too next question.
|
||||
if (isset($data[$next]) && false === strpos($data[$next], 'SCORE:')) {
|
||||
$answersArray = [];
|
||||
$questionIndex++;
|
||||
continue;
|
||||
}
|
||||
} elseif (preg_match('/^ANSWER_EXPLANATION:\s?(.*)/', $info, $matches)) {
|
||||
// Comment of correct answer
|
||||
$correctAnswerIndex = array_search($matches[1], $answersArray);
|
||||
$exerciseInfo['question'][$questionIndex]['feedback'] = $matches[1];
|
||||
$next = $line + 1;
|
||||
// Check if next has score, otherwise loop too next question.
|
||||
if (isset($data[$next]) && false === strpos($data[$next], 'SCORE:')) {
|
||||
$answersArray = [];
|
||||
$questionIndex++;
|
||||
continue;
|
||||
}
|
||||
} elseif (preg_match('/^TEXTO_CORRECTA:\s?(.*)/', $info, $matches)) {
|
||||
//Comment of correct answer (Spanish e-ducativa format)
|
||||
$correctAnswerIndex = array_search($matches[1], $answersArray);
|
||||
$exerciseInfo['question'][$questionIndex]['feedback'] = $matches[1];
|
||||
} elseif (preg_match('/^T:\s?(.*)/', $info, $matches)) {
|
||||
//Question Title
|
||||
$correctAnswerIndex = array_search($matches[1], $answersArray);
|
||||
$exerciseInfo['question'][$questionIndex]['title'] = $matches[1];
|
||||
} elseif (preg_match('/^TAGS:\s?([A-Z])\s?/', $info, $matches)) {
|
||||
//TAGS for chamilo >= 1.10
|
||||
$exerciseInfo['question'][$questionIndex]['answer_tags'] = explode(',', $matches[1]);
|
||||
} elseif (preg_match('/^ETIQUETAS:\s?([A-Z])\s?/', $info, $matches)) {
|
||||
//TAGS for chamilo >= 1.10 (Spanish e-ducativa format)
|
||||
$exerciseInfo['question'][$questionIndex]['answer_tags'] = explode(',', $matches[1]);
|
||||
} else {
|
||||
if (empty($exerciseInfo['question'][$questionIndex]['title'])) {
|
||||
$exerciseInfo['question'][$questionIndex]['title'] = $info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$totalQuestions = count($exerciseInfo['question']);
|
||||
$totalWeight = (int) $exerciseInfo['total_weight'];
|
||||
foreach ($exerciseInfo['question'] as $key => $question) {
|
||||
if (!isset($exerciseInfo['question'][$key]['weighting'])) {
|
||||
continue;
|
||||
}
|
||||
$exerciseInfo['question'][$key]['weighting'][current(array_keys($exerciseInfo['question'][$key]['weighting']))] = $totalWeight / $totalQuestions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an Aiken file and builds an array of exercise + questions to be
|
||||
* imported by the import_exercise() function.
|
||||
*
|
||||
* @param array The reference to the array in which to store the questions
|
||||
* @param string Path to the directory with the file to be parsed (without final /)
|
||||
* @param string Name of the last directory part for the file (without /)
|
||||
* @param string Name of the file to be parsed (including extension)
|
||||
* @param string $exercisePath
|
||||
* @param string $file
|
||||
* @param string $questionFile
|
||||
*
|
||||
* @return string|bool True on success, error message on error
|
||||
* @assert ('','','') === false
|
||||
*/
|
||||
function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
|
||||
{
|
||||
$questionTempDir = $exercisePath.'/'.$file.'/';
|
||||
$questionFilePath = $questionTempDir.$questionFile;
|
||||
|
||||
if (!is_file($questionFilePath)) {
|
||||
return 'FileNotFound';
|
||||
}
|
||||
|
||||
$text = file_get_contents($questionFilePath);
|
||||
setExerciseInfoFromAikenText($text, $exercise_info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the zip file.
|
||||
*
|
||||
* @param array $array_file ($_FILES)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function aiken_import_file($array_file)
|
||||
{
|
||||
$unzip = 0;
|
||||
$process = process_uploaded_file($array_file, false);
|
||||
if (preg_match('/\.(zip|txt)$/i', $array_file['name'])) {
|
||||
// if it's a zip, allow zip upload
|
||||
$unzip = 1;
|
||||
}
|
||||
|
||||
if ($process && $unzip == 1) {
|
||||
$imported = aikenImportExercise($array_file['name']);
|
||||
if (is_numeric($imported) && !empty($imported)) {
|
||||
Display::addFlash(Display::return_message(get_lang('Uploaded')));
|
||||
|
||||
return $imported;
|
||||
} else {
|
||||
Display::addFlash(Display::return_message(get_lang($imported), 'error'));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
737
main/exercise/export/exercise_import.inc.php
Normal file
737
main/exercise/export/exercise_import.inc.php
Normal file
@@ -0,0 +1,737 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
/**
|
||||
* @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
|
||||
* @author claro team <cvs@claroline.net>
|
||||
* @author Guillaume Lederer <guillaume@claroline.net>
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unzip the exercise in the temp folder.
|
||||
*
|
||||
* @param string $baseWorkDir The path of the temporary directory where the exercise was uploaded and unzipped
|
||||
* @param string $uploadPath
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)
|
||||
{
|
||||
$_course = api_get_course_info();
|
||||
$_user = api_get_user_info();
|
||||
|
||||
//Check if the file is valid (not to big and exists)
|
||||
if (!isset($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) {
|
||||
// upload failed
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/.zip$/i', $_FILES['userFile']['name'])) {
|
||||
return handle_uploaded_document(
|
||||
$_course,
|
||||
$_FILES['userFile'],
|
||||
$baseWorkDir,
|
||||
$uploadPath,
|
||||
$_user['user_id'],
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an exercise in QTI format if the XML structure can be found in it.
|
||||
*
|
||||
* @param array $file
|
||||
*
|
||||
* @return string|array as a backlog of what was really imported, and error or debug messages to display
|
||||
*/
|
||||
function import_exercise($file)
|
||||
{
|
||||
global $exerciseInfo;
|
||||
global $resourcesLinks;
|
||||
|
||||
$baseWorkDir = api_get_path(SYS_ARCHIVE_PATH).'qti2/';
|
||||
if (!is_dir($baseWorkDir)) {
|
||||
mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true);
|
||||
}
|
||||
|
||||
$uploadPath = api_get_unique_id().'/';
|
||||
|
||||
if (!is_dir($baseWorkDir.$uploadPath)) {
|
||||
mkdir($baseWorkDir.$uploadPath, api_get_permissions_for_new_directories(), true);
|
||||
}
|
||||
|
||||
// set some default values for the new exercise
|
||||
$exerciseInfo = [];
|
||||
$exerciseInfo['name'] = preg_replace('/.zip$/i', '', $file);
|
||||
$exerciseInfo['question'] = [];
|
||||
|
||||
// if file is not a .zip, then we cancel all
|
||||
if (!preg_match('/.zip$/i', $file)) {
|
||||
return 'UplZipCorrupt';
|
||||
}
|
||||
|
||||
// unzip the uploaded file in a tmp directory
|
||||
if (!get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)) {
|
||||
return 'UplZipCorrupt';
|
||||
}
|
||||
|
||||
$baseWorkDir = $baseWorkDir.$uploadPath;
|
||||
|
||||
// find the different manifests for each question and parse them.
|
||||
$exerciseHandle = opendir($baseWorkDir);
|
||||
$fileFound = false;
|
||||
$result = false;
|
||||
$filePath = null;
|
||||
$resourcesLinks = [];
|
||||
|
||||
// parse every subdirectory to search xml question files and other assets to be imported
|
||||
// The assets-related code is a bit fragile as it has to deal with files renamed by Chamilo and it only works if
|
||||
// the imsmanifest.xml file is read.
|
||||
while (false !== ($file = readdir($exerciseHandle))) {
|
||||
if (is_dir($baseWorkDir.'/'.$file) && $file != "." && $file != "..") {
|
||||
// Find each manifest for each question repository found
|
||||
$questionHandle = opendir($baseWorkDir.'/'.$file);
|
||||
// Only analyse one level of subdirectory - no recursivity here
|
||||
while (false !== ($questionFile = readdir($questionHandle))) {
|
||||
if (preg_match('/.xml$/i', $questionFile)) {
|
||||
$isQti = isQtiQuestionBank($baseWorkDir.'/'.$file.'/'.$questionFile);
|
||||
if ($isQti) {
|
||||
$result = qti_parse_file($baseWorkDir, $file, $questionFile);
|
||||
$filePath = $baseWorkDir.$file;
|
||||
$fileFound = true;
|
||||
} else {
|
||||
$isManifest = isQtiManifest($baseWorkDir.'/'.$file.'/'.$questionFile);
|
||||
if ($isManifest) {
|
||||
$resourcesLinks = qtiProcessManifest($baseWorkDir.'/'.$file.'/'.$questionFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (preg_match('/.xml$/i', $file)) {
|
||||
$isQti = isQtiQuestionBank($baseWorkDir.'/'.$file);
|
||||
if ($isQti) {
|
||||
$result = qti_parse_file($baseWorkDir, '', $file);
|
||||
$filePath = $baseWorkDir.'/'.$file;
|
||||
$fileFound = true;
|
||||
} else {
|
||||
$isManifest = isQtiManifest($baseWorkDir.'/'.$file);
|
||||
if ($isManifest) {
|
||||
$resourcesLinks = qtiProcessManifest($baseWorkDir.'/'.$file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$fileFound) {
|
||||
return 'NoXMLFileFoundInTheZip';
|
||||
}
|
||||
|
||||
if ($result == false) {
|
||||
return false;
|
||||
}
|
||||
// 1. Create exercise.
|
||||
$exercise = new Exercise();
|
||||
$exercise->exercise = $exerciseInfo['name'];
|
||||
|
||||
// Random QTI support
|
||||
if (isset($exerciseInfo['order_type'])) {
|
||||
if ($exerciseInfo['order_type'] == 'Random') {
|
||||
$exercise->setQuestionSelectionType(2);
|
||||
$exercise->random = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($exerciseInfo['description'])) {
|
||||
$exercise->updateDescription(formatText(strip_tags($exerciseInfo['description'])));
|
||||
}
|
||||
|
||||
$exercise->save();
|
||||
$last_exercise_id = $exercise->selectId();
|
||||
$courseId = api_get_course_int_id();
|
||||
if (!empty($last_exercise_id)) {
|
||||
// For each question found...
|
||||
foreach ($exerciseInfo['question'] as $question_array) {
|
||||
if (!in_array($question_array['type'], [UNIQUE_ANSWER, MULTIPLE_ANSWER, FREE_ANSWER])) {
|
||||
continue;
|
||||
}
|
||||
//2. Create question
|
||||
$question = new Ims2Question();
|
||||
$question->type = $question_array['type'];
|
||||
if (empty($question->type)) {
|
||||
// If the type was not provided, assume this is a multiple choice, unique answer type (the most basic)
|
||||
$question->type = MCUA;
|
||||
}
|
||||
$question->setAnswer();
|
||||
$description = '';
|
||||
$question->updateTitle(formatText(strip_tags($question_array['title'])));
|
||||
|
||||
if (isset($question_array['category'])) {
|
||||
$category = formatText(strip_tags($question_array['category']));
|
||||
if (!empty($category)) {
|
||||
$categoryId = TestCategory::get_category_id_for_title(
|
||||
$category,
|
||||
$courseId
|
||||
);
|
||||
|
||||
if (empty($categoryId)) {
|
||||
$cat = new TestCategory();
|
||||
$cat->name = $category;
|
||||
$cat->description = '';
|
||||
$categoryId = $cat->save($courseId);
|
||||
if ($categoryId) {
|
||||
$question->category = $categoryId;
|
||||
}
|
||||
} else {
|
||||
$question->category = $categoryId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($question_array['description'])) {
|
||||
$description .= $question_array['description'];
|
||||
}
|
||||
|
||||
$question->updateDescription($description);
|
||||
$question->save($exercise);
|
||||
|
||||
$last_question_id = $question->selectId();
|
||||
//3. Create answer
|
||||
$answer = new Answer($last_question_id);
|
||||
$answerList = $question_array['answer'];
|
||||
$answer->new_nbrAnswers = count($answerList);
|
||||
$totalCorrectWeight = 0;
|
||||
$j = 1;
|
||||
$matchAnswerIds = [];
|
||||
if (!empty($answerList)) {
|
||||
foreach ($answerList as $key => $answers) {
|
||||
if (preg_match('/_/', $key)) {
|
||||
$split = explode('_', $key);
|
||||
$i = $split[1];
|
||||
} else {
|
||||
$i = $j;
|
||||
$j++;
|
||||
$matchAnswerIds[$key] = $j;
|
||||
}
|
||||
|
||||
// Answer
|
||||
$answer->new_answer[$i] = isset($answers['value']) ? formatText($answers['value']) : '';
|
||||
// Comment
|
||||
$answer->new_comment[$i] = isset($answers['feedback']) ? formatText($answers['feedback']) : null;
|
||||
// Position
|
||||
$answer->new_position[$i] = $i;
|
||||
// Correct answers
|
||||
if (in_array($key, $question_array['correct_answers'])) {
|
||||
$answer->new_correct[$i] = 1;
|
||||
} else {
|
||||
$answer->new_correct[$i] = 0;
|
||||
}
|
||||
|
||||
$answer->new_weighting[$i] = 0;
|
||||
if (isset($question_array['weighting'][$key])) {
|
||||
$answer->new_weighting[$i] = $question_array['weighting'][$key];
|
||||
}
|
||||
if ($answer->new_correct[$i]) {
|
||||
$totalCorrectWeight += $answer->new_weighting[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($question->type == FREE_ANSWER) {
|
||||
$totalCorrectWeight = $question_array['weighting'][0];
|
||||
}
|
||||
|
||||
if (!empty($question_array['default_weighting'])) {
|
||||
$totalCorrectWeight = (float) $question_array['default_weighting'];
|
||||
}
|
||||
|
||||
$question->updateWeighting($totalCorrectWeight);
|
||||
$question->save($exercise);
|
||||
$answer->save();
|
||||
}
|
||||
|
||||
// delete the temp dir where the exercise was unzipped
|
||||
my_delete($baseWorkDir.$uploadPath);
|
||||
|
||||
return $last_exercise_id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We assume the file charset is UTF8.
|
||||
*/
|
||||
function formatText($text)
|
||||
{
|
||||
return api_html_entity_decode($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a given XML file and fills global arrays with the elements.
|
||||
*
|
||||
* @param string $exercisePath
|
||||
* @param string $file
|
||||
* @param string $questionFile
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function qti_parse_file($exercisePath, $file, $questionFile)
|
||||
{
|
||||
global $record_item_body;
|
||||
global $questionTempDir;
|
||||
|
||||
$questionTempDir = $exercisePath.'/'.$file.'/';
|
||||
$questionFilePath = $questionTempDir.$questionFile;
|
||||
|
||||
if (!($fp = fopen($questionFilePath, 'r'))) {
|
||||
Display::addFlash(Display::return_message(get_lang('Error opening question\'s XML file'), 'error'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = fread($fp, filesize($questionFilePath));
|
||||
|
||||
//close file
|
||||
fclose($fp);
|
||||
|
||||
//parse XML question file
|
||||
//$data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
|
||||
$data = ChamiloApi::stripGivenTags($data, ['p', 'front']);
|
||||
$qtiVersion = [];
|
||||
$match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data, $qtiVersion);
|
||||
$qtiMainVersion = 2; //by default, assume QTI version 2
|
||||
if ($match) {
|
||||
$qtiMainVersion = $qtiVersion[1];
|
||||
}
|
||||
|
||||
//used global variable start values declaration:
|
||||
$record_item_body = false;
|
||||
|
||||
if ($qtiMainVersion != 2) {
|
||||
Display::addFlash(
|
||||
Display::return_message(
|
||||
get_lang('UnsupportedQtiVersion'),
|
||||
'error'
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
parseQti2($data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to parser a QTI2 xml file.
|
||||
*
|
||||
* @param string $xmlData
|
||||
*/
|
||||
function parseQti2($xmlData)
|
||||
{
|
||||
global $exerciseInfo;
|
||||
global $questionTempDir;
|
||||
global $resourcesLinks;
|
||||
|
||||
$crawler = new Crawler($xmlData);
|
||||
$nodes = $crawler->filter('*');
|
||||
|
||||
$currentQuestionIdent = '';
|
||||
$currentAnswerId = '';
|
||||
$currentQuestionItemBody = '';
|
||||
$cardinality = '';
|
||||
$nonHTMLTagToAvoid = [
|
||||
'prompt',
|
||||
'simpleChoice',
|
||||
'choiceInteraction',
|
||||
'inlineChoiceInteraction',
|
||||
'inlineChoice',
|
||||
'soMPLEMATCHSET',
|
||||
'simpleAssociableChoice',
|
||||
'textEntryInteraction',
|
||||
'feedbackInline',
|
||||
'matchInteraction',
|
||||
'extendedTextInteraction',
|
||||
'itemBody',
|
||||
'br',
|
||||
'img',
|
||||
];
|
||||
$currentMatchSet = null;
|
||||
|
||||
/** @var DOMElement $node */
|
||||
foreach ($nodes as $node) {
|
||||
if ('#text' === $node->nodeName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($node->nodeName) {
|
||||
case 'assessmentItem':
|
||||
$currentQuestionIdent = $node->getAttribute('identifier');
|
||||
|
||||
$exerciseInfo['question'][$currentQuestionIdent] = [
|
||||
'answer' => [],
|
||||
'correct_answers' => [],
|
||||
'title' => $node->getAttribute('title'),
|
||||
'category' => $node->getAttribute('category'),
|
||||
'type' => '',
|
||||
'tempdir' => $questionTempDir,
|
||||
'description' => null,
|
||||
];
|
||||
break;
|
||||
case 'section':
|
||||
$title = $node->getAttribute('title');
|
||||
|
||||
if (!empty($title)) {
|
||||
$exerciseInfo['name'] = $title;
|
||||
}
|
||||
break;
|
||||
case 'responseDeclaration':
|
||||
if ('multiple' === $node->getAttribute('cardinality')) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['type'] = MCMA;
|
||||
$cardinality = 'multiple';
|
||||
}
|
||||
|
||||
if ('single' === $node->getAttribute('cardinality')) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['type'] = MCUA;
|
||||
$cardinality = 'single';
|
||||
}
|
||||
|
||||
$currentAnswerId = $node->getAttribute('identifier');
|
||||
break;
|
||||
case 'inlineChoiceInteraction':
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB;
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'LISTBOX_FILL';
|
||||
$currentAnswerId = $node->getAttribute('responseIdentifier');
|
||||
break;
|
||||
case 'inlineChoice':
|
||||
$answerIdentifier = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId];
|
||||
|
||||
if ($node->getAttribute('identifier') == $answerIdentifier) {
|
||||
$currentQuestionItemBody = str_replace(
|
||||
"**claroline_start**".$currentAnswerId."**claroline_end**",
|
||||
"[".$node->nodeValue."]",
|
||||
$currentQuestionItemBody
|
||||
);
|
||||
} else {
|
||||
if (!isset($exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'])) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'] = [];
|
||||
}
|
||||
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'][] = $node->nodeValue;
|
||||
}
|
||||
break;
|
||||
case 'textEntryInteraction':
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB;
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'TEXTFIELD_FILL';
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody;
|
||||
break;
|
||||
case 'matchInteraction':
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['type'] = MATCHING;
|
||||
break;
|
||||
case 'extendedTextInteraction':
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['type'] = FREE_ANSWER;
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['description'] = $node->nodeValue;
|
||||
break;
|
||||
case 'simpleMatchSet':
|
||||
if (!isset($currentMatchSet)) {
|
||||
$currentMatchSet = 1;
|
||||
} else {
|
||||
$currentMatchSet++;
|
||||
}
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet] = [];
|
||||
break;
|
||||
case 'simpleAssociableChoice':
|
||||
$currentAssociableChoice = $node->getAttribute('identifier');
|
||||
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet][$currentAssociableChoice] = trim($node->nodeValue);
|
||||
break;
|
||||
case 'simpleChoice':
|
||||
$currentAnswerId = $node->getAttribute('identifier');
|
||||
if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId])) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId] = [];
|
||||
}
|
||||
|
||||
//$simpleChoiceValue = $node->nodeValue;
|
||||
$simpleChoiceValue = '';
|
||||
/** @var DOMElement $childNode */
|
||||
foreach ($node->childNodes as $childNode) {
|
||||
if ('feedbackInline' === $childNode->nodeName) {
|
||||
continue;
|
||||
}
|
||||
$simpleChoiceValue .= $childNode->nodeValue;
|
||||
}
|
||||
$simpleChoiceValue = trim($simpleChoiceValue);
|
||||
if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'])) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] = $simpleChoiceValue;
|
||||
} else {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] .= $simpleChoiceValue;
|
||||
}
|
||||
break;
|
||||
case 'mapEntry':
|
||||
if (in_array($node->parentNode->nodeName, ['mapping', 'mapEntry'])) {
|
||||
$answer_id = $node->getAttribute('mapKey');
|
||||
|
||||
if (!isset($exerciseInfo['question'][$currentQuestionIdent]['weighting'])) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['weighting'] = [];
|
||||
}
|
||||
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['weighting'][$answer_id] = $node->getAttribute(
|
||||
'mappedValue'
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'mapping':
|
||||
$defaultValue = $node->getAttribute('defaultValue');
|
||||
if (!empty($defaultValue)) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['default_weighting'] = $defaultValue;
|
||||
}
|
||||
// no break ?
|
||||
case 'itemBody':
|
||||
$nodeValue = $node->nodeValue;
|
||||
$currentQuestionItemBody = '';
|
||||
|
||||
/** @var DOMElement $childNode */
|
||||
foreach ($node->childNodes as $childNode) {
|
||||
if ('#text' === $childNode->nodeName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($childNode->nodeName, $nonHTMLTagToAvoid)) {
|
||||
$currentQuestionItemBody .= '<'.$childNode->nodeName;
|
||||
|
||||
if ($childNode->attributes) {
|
||||
foreach ($childNode->attributes as $attribute) {
|
||||
$currentQuestionItemBody .= ' '.$attribute->nodeName.'="'.$attribute->nodeValue.'"';
|
||||
}
|
||||
}
|
||||
|
||||
$currentQuestionItemBody .= '>'.$childNode->nodeValue.'</'.$node->nodeName.'>';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('inlineChoiceInteraction' === $childNode->nodeName) {
|
||||
$currentQuestionItemBody .= "**claroline_start**".$childNode->attr('responseIdentifier')
|
||||
."**claroline_end**";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('textEntryInteraction' === $childNode->nodeName) {
|
||||
$correct_answer_value = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId];
|
||||
$currentQuestionItemBody .= "[".$correct_answer_value."]";
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('br' === $childNode->nodeName) {
|
||||
$currentQuestionItemBody .= '<br>';
|
||||
}
|
||||
}
|
||||
|
||||
// Replace relative links by links to the documents in the course
|
||||
// $resourcesLinks is only defined by qtiProcessManifest()
|
||||
if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
|
||||
foreach ($resourcesLinks['manifest'] as $key => $value) {
|
||||
$nodeValue = preg_replace('|'.$value.'|', $resourcesLinks['web'][$key], $nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
$currentQuestionItemBody .= $node->firstChild->nodeValue;
|
||||
|
||||
if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FIB) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody;
|
||||
} else {
|
||||
if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FREE_ANSWER) {
|
||||
$currentQuestionItemBody = trim($currentQuestionItemBody);
|
||||
|
||||
if (!empty($currentQuestionItemBody)) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['description'] = $currentQuestionItemBody;
|
||||
}
|
||||
} else {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['statement'] = $currentQuestionItemBody;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'img':
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['attached_file_url'] = $node->getAttribute('src');
|
||||
break;
|
||||
case 'order':
|
||||
$orderType = $node->getAttribute('order_type');
|
||||
|
||||
if (!empty($orderType)) {
|
||||
$exerciseInfo['order_type'] = $orderType;
|
||||
}
|
||||
break;
|
||||
case 'feedbackInline':
|
||||
if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'])) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'] = trim(
|
||||
$node->nodeValue
|
||||
);
|
||||
} else {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'] .= trim(
|
||||
$node->nodeValue
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'value':
|
||||
if ('correctResponse' === $node->parentNode->nodeName) {
|
||||
$nodeValue = trim($node->nodeValue);
|
||||
|
||||
if ('single' === $cardinality) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$nodeValue] = $nodeValue;
|
||||
} else {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][] = $nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
if ('outcomeDeclaration' === $node->parentNode->parentNode->nodeName) {
|
||||
$nodeValue = trim($node->nodeValue);
|
||||
|
||||
if (!empty($nodeValue)) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['weighting'][0] = $nodeValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'mattext':
|
||||
if ('flow_mat' === $node->parentNode->parentNode->nodeName &&
|
||||
('presentation_material' === $node->parentNode->parentNode->parentNode->nodeName ||
|
||||
'section' === $node->parentNode->parentNode->parentNode->nodeName
|
||||
)
|
||||
) {
|
||||
$nodeValue = trim($node->nodeValue);
|
||||
|
||||
if (!empty($nodeValue)) {
|
||||
$exerciseInfo['description'] = $node->nodeValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'prompt':
|
||||
$description = trim($node->nodeValue);
|
||||
$description = htmlspecialchars_decode($description);
|
||||
$description = Security::remove_XSS($description);
|
||||
|
||||
if (!empty($description)) {
|
||||
$exerciseInfo['question'][$currentQuestionIdent]['description'] = $description;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given file is an IMS/QTI question bank file.
|
||||
*
|
||||
* @param string $filePath The absolute filepath
|
||||
*
|
||||
* @return bool Whether it is an IMS/QTI question bank or not
|
||||
*/
|
||||
function isQtiQuestionBank($filePath)
|
||||
{
|
||||
$data = file_get_contents($filePath);
|
||||
if (!empty($data)) {
|
||||
$match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data);
|
||||
// @todo allow other types
|
||||
//$match2 = preg_match('/imsqti_v(\d)p(\d)/', $data);
|
||||
|
||||
if ($match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given file is an IMS/QTI manifest file (listing of extra files).
|
||||
*
|
||||
* @param string $filePath The absolute filepath
|
||||
*
|
||||
* @return bool Whether it is an IMS/QTI manifest file or not
|
||||
*/
|
||||
function isQtiManifest($filePath)
|
||||
{
|
||||
$data = file_get_contents($filePath);
|
||||
if (!empty($data)) {
|
||||
$match = preg_match('/imsccv(\d)p(\d)/', $data);
|
||||
if ($match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an IMS/QTI manifest file: store links to new files
|
||||
* to be able to transform them into the questions text.
|
||||
*
|
||||
* @param string $filePath The absolute filepath
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function qtiProcessManifest($filePath)
|
||||
{
|
||||
$xml = simplexml_load_file($filePath);
|
||||
$course = api_get_course_info();
|
||||
$sessionId = api_get_session_id();
|
||||
$courseDir = $course['path'];
|
||||
$sysPath = api_get_path(SYS_COURSE_PATH);
|
||||
$exercisesSysPath = $sysPath.$courseDir.'/document/';
|
||||
$webPath = api_get_path(WEB_CODE_PATH);
|
||||
$exercisesWebPath = $webPath.'document/document.php?'.api_get_cidreq().'&action=download&id=';
|
||||
$links = [
|
||||
'manifest' => [],
|
||||
'system' => [],
|
||||
'web' => [],
|
||||
];
|
||||
$tableDocuments = Database::get_course_table(TABLE_DOCUMENT);
|
||||
$countResources = count($xml->resources->resource->file);
|
||||
for ($i = 0; $i < $countResources; $i++) {
|
||||
$file = $xml->resources->resource->file[$i];
|
||||
$href = '';
|
||||
foreach ($file->attributes() as $key => $value) {
|
||||
if ('href' == $key) {
|
||||
if ('xml' != substr($value, -3, 3)) {
|
||||
$href = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($href)) {
|
||||
$links['manifest'][] = (string) $href;
|
||||
$links['system'][] = $exercisesSysPath.strtolower($href);
|
||||
$specialHref = Database::escape_string(preg_replace('/_/', '-', strtolower($href)));
|
||||
$specialHref = preg_replace('/(-){2,8}/', '-', $specialHref);
|
||||
|
||||
$sql = "SELECT iid FROM $tableDocuments
|
||||
WHERE
|
||||
c_id = ".$course['real_id']." AND
|
||||
session_id = $sessionId AND
|
||||
path = '/".$specialHref."'";
|
||||
$result = Database::query($sql);
|
||||
$documentId = 0;
|
||||
while ($row = Database::fetch_assoc($result)) {
|
||||
$documentId = $row['iid'];
|
||||
}
|
||||
$links['web'][] = $exercisesWebPath.$documentId;
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
117
main/exercise/export/export_exercise_results.php
Normal file
117
main/exercise/export/export_exercise_results.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* This script exports the PDF reports from a test for several students at once.
|
||||
* This script is the teacher view of a similar script (for admins) at main/admin/export_exercise_results.php.
|
||||
*/
|
||||
require_once __DIR__.'/../../inc/global.inc.php';
|
||||
|
||||
// Setting the section (for the tabs).
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
if (!api_is_allowed_to_edit()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$sessionId = api_get_session_id();
|
||||
$courseId = api_get_course_int_id();
|
||||
$exerciseId = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : null;
|
||||
$exerciseIdChanged = isset($_GET['exercise_id_changed']) ? (int) $_GET['exercise_id_changed'] : null;
|
||||
|
||||
$courseInfo = [];
|
||||
if (empty($courseId)) {
|
||||
$exerciseId = 0;
|
||||
} else {
|
||||
$courseInfo = api_get_course_info_by_id($courseId);
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = ['url' => '../exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
|
||||
|
||||
$confirmYourChoice = addslashes(get_lang('ConfirmYourChoice'));
|
||||
$htmlHeadXtra[] = "
|
||||
<script>
|
||||
function submit_form(obj) {
|
||||
document.export_all_results_form.submit();
|
||||
}
|
||||
|
||||
function mark_exercise_id_changed() {
|
||||
$('#exercise_id_changed').val('0');
|
||||
}
|
||||
|
||||
function confirm_your_choice() {
|
||||
return confirm('$confirmYourChoice');
|
||||
}
|
||||
</script>";
|
||||
|
||||
// Get exercise list for this course
|
||||
$exerciseList = ExerciseLib::get_all_exercises_for_course_id(
|
||||
$courseInfo,
|
||||
$sessionId,
|
||||
$courseId,
|
||||
false
|
||||
);
|
||||
|
||||
$exerciseSelectList = [];
|
||||
$exerciseSelectList = [0 => get_lang('All')];
|
||||
if (is_array($exerciseList)) {
|
||||
foreach ($exerciseList as $row) {
|
||||
$exerciseTitle = $row['title'];
|
||||
$exerciseSelectList[$row['iid']] = $exerciseTitle;
|
||||
}
|
||||
}
|
||||
|
||||
$url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query(
|
||||
[
|
||||
'session_id' => $sessionId,
|
||||
'exerciseId' => $exerciseId,
|
||||
'exercise_id_changed' => $exerciseIdChanged,
|
||||
]
|
||||
);
|
||||
|
||||
// Form
|
||||
$form = new FormValidator('export_all_results_form', 'GET', $url);
|
||||
$form->addHeader(get_lang('ExportExerciseAllResults'));
|
||||
$form
|
||||
->addSelect(
|
||||
'exerciseId',
|
||||
get_lang('Exercise'),
|
||||
$exerciseSelectList
|
||||
)
|
||||
->setSelected($exerciseId);
|
||||
|
||||
$form->addDateTimePicker('start_date', get_lang('StartDate'));
|
||||
$form->addDateTimePicker('end_date', get_lang('EndDate'));
|
||||
$form->addRule('start_date', get_lang('InvalidDate'), 'datetime');
|
||||
$form->addRule('end_date', get_lang('InvalidDate'), 'datetime');
|
||||
|
||||
$form->addRule(
|
||||
['start_date', 'end_date'],
|
||||
get_lang('StartDateShouldBeBeforeEndDate'),
|
||||
'date_compare',
|
||||
'lte'
|
||||
);
|
||||
|
||||
$form->addHidden('exercise_id_changed', '0');
|
||||
$form->addButtonExport(get_lang('Export'), 'name');
|
||||
|
||||
if ($form->validate()) {
|
||||
$values = $form->getSubmitValues();
|
||||
|
||||
$exerciseId = (int) $values['exerciseId'];
|
||||
$filterDates = [
|
||||
'start_date' => (!empty($values['start_date']) ? $values['start_date'] : ''),
|
||||
'end_date' => (!empty($values['end_date']) ? $values['end_date'] : ''),
|
||||
];
|
||||
if ($exerciseId === 0) {
|
||||
ExerciseLib::exportAllExercisesResultsZip($sessionId, $courseId, $filterDates);
|
||||
} else {
|
||||
ExerciseLib::exportExerciseAllResultsZip($sessionId, $courseId, $exerciseId, $filterDates);
|
||||
}
|
||||
}
|
||||
|
||||
Display::display_header(get_lang('ExportExerciseAllResults'));
|
||||
|
||||
echo $form->display();
|
||||
|
||||
Display::display_footer();
|
||||
11
main/exercise/export/index.php
Normal file
11
main/exercise/export/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* @author Claro Team <cvs@claroline.net>
|
||||
*/
|
||||
/**
|
||||
* Redirection.
|
||||
*/
|
||||
header('Location: ../../../');
|
||||
exit();
|
||||
471
main/exercise/export/qti2/qti2_classes.php
Normal file
471
main/exercise/export/qti2/qti2_classes.php
Normal file
@@ -0,0 +1,471 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Interface ImsAnswerInterface.
|
||||
*/
|
||||
interface ImsAnswerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $questionIdent
|
||||
* @param string $questionStatment
|
||||
* @param string $questionDesc
|
||||
* @param string $questionMedia
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '');
|
||||
|
||||
/**
|
||||
* @param $questionIdent
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function imsExportResponsesDeclaration($questionIdent, Question $question = null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Claro Team <cvs@claroline.net>
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com> -
|
||||
* updated ImsAnswerHotspot to match QTI norms
|
||||
*/
|
||||
class Ims2Question extends Question
|
||||
{
|
||||
/**
|
||||
* Include the correct answer class and create answer.
|
||||
*
|
||||
* @return Answer
|
||||
*/
|
||||
public function setAnswer()
|
||||
{
|
||||
switch ($this->type) {
|
||||
case MCUA:
|
||||
$answer = new ImsAnswerMultipleChoice($this->iid);
|
||||
|
||||
return $answer;
|
||||
case MCMA:
|
||||
case MULTIPLE_ANSWER_DROPDOWN:
|
||||
case MULTIPLE_ANSWER_DROPDOWN_COMBINATION:
|
||||
$answer = new ImsAnswerMultipleChoice($this->iid);
|
||||
|
||||
return $answer;
|
||||
case TF:
|
||||
$answer = new ImsAnswerMultipleChoice($this->iid);
|
||||
|
||||
return $answer;
|
||||
case FIB:
|
||||
$answer = new ImsAnswerFillInBlanks($this->iid);
|
||||
|
||||
return $answer;
|
||||
case MATCHING:
|
||||
case MATCHING_DRAGGABLE:
|
||||
$answer = new ImsAnswerMatching($this->iid);
|
||||
|
||||
return $answer;
|
||||
case FREE_ANSWER:
|
||||
$answer = new ImsAnswerFree($this->iid);
|
||||
|
||||
return $answer;
|
||||
case HOT_SPOT:
|
||||
case HOT_SPOT_COMBINATION:
|
||||
$answer = new ImsAnswerHotspot($this->iid);
|
||||
|
||||
return $answer;
|
||||
default:
|
||||
$answer = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return $answer;
|
||||
}
|
||||
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class.
|
||||
*/
|
||||
class ImsAnswerMultipleChoice extends Answer implements ImsAnswerInterface
|
||||
{
|
||||
/**
|
||||
* Return the XML flow for the possible answers.
|
||||
*
|
||||
* @param string $questionIdent
|
||||
* @param string $questionStatment
|
||||
* @param string $questionDesc
|
||||
* @param string $questionMedia
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
|
||||
{
|
||||
// @todo getAnswersList() converts the answers using api_html_entity_decode()
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$out = ' <choiceInteraction responseIdentifier="'.$questionIdent.'" >'."\n";
|
||||
$out .= ' <prompt><![CDATA['.formatExerciseQtiText($questionDesc).']]></prompt>'."\n";
|
||||
if (is_array($this->answerList)) {
|
||||
foreach ($this->answerList as $current_answer) {
|
||||
$out .= '<simpleChoice identifier="answer_'.$current_answer['iid'].'" fixed="false">
|
||||
<![CDATA['.formatExerciseQtiText($current_answer['answer']).']]>';
|
||||
if (isset($current_answer['comment']) && $current_answer['comment'] != '') {
|
||||
$out .= '<feedbackInline identifier="answer_'.$current_answer['iid'].'">
|
||||
<![CDATA['.formatExerciseQtiText($current_answer['comment']).']]>
|
||||
</feedbackInline>';
|
||||
}
|
||||
$out .= '</simpleChoice>'."\n";
|
||||
}
|
||||
}
|
||||
$out .= ' </choiceInteraction>'."\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the XML flow of answer ResponsesDeclaration.
|
||||
*/
|
||||
public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
|
||||
{
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$type = $this->getQuestionType();
|
||||
if (in_array($type, [MCMA, MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION])) {
|
||||
$cardinality = 'multiple';
|
||||
} else {
|
||||
$cardinality = 'single';
|
||||
}
|
||||
|
||||
$out = ' <responseDeclaration identifier="'.$questionIdent.'" cardinality="'.$cardinality.'" baseType="identifier">'."\n";
|
||||
|
||||
// Match the correct answers.
|
||||
if (is_array($this->answerList)) {
|
||||
$out .= ' <correctResponse>'."\n";
|
||||
foreach ($this->answerList as $current_answer) {
|
||||
if ($current_answer['correct']) {
|
||||
$out .= ' <value>answer_'.$current_answer['iid'].'</value>'."\n";
|
||||
}
|
||||
}
|
||||
$out .= ' </correctResponse>'."\n";
|
||||
}
|
||||
|
||||
// Add the grading
|
||||
if (is_array($this->answerList)) {
|
||||
$out .= ' <mapping';
|
||||
|
||||
if (MULTIPLE_ANSWER_DROPDOWN_COMBINATION == $this->getQuestionType()) {
|
||||
$out .= ' defaultValue="'.$question->selectWeighting().'"';
|
||||
}
|
||||
|
||||
$out .= '>'."\n";
|
||||
|
||||
foreach ($this->answerList as $current_answer) {
|
||||
if (isset($current_answer['grade'])) {
|
||||
$out .= ' <mapEntry mapKey="answer_'.$current_answer['iid'].'" mappedValue="'.$current_answer['grade'].'" />'."\n";
|
||||
}
|
||||
}
|
||||
$out .= ' </mapping>'."\n";
|
||||
}
|
||||
|
||||
$out .= ' </responseDeclaration>'."\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*/
|
||||
class ImsAnswerFillInBlanks extends Answer implements ImsAnswerInterface
|
||||
{
|
||||
private $answerList = [];
|
||||
private $gradeList = [];
|
||||
|
||||
/**
|
||||
* Export the text with missing words.
|
||||
*
|
||||
* @param string $questionIdent
|
||||
* @param string $questionStatment
|
||||
* @param string $questionDesc
|
||||
* @param string $questionMedia
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
|
||||
{
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$text = isset($this->answerText) ? $this->answerText : '';
|
||||
if (is_array($this->answerList)) {
|
||||
foreach ($this->answerList as $key => $answer) {
|
||||
$key = $answer['iid'];
|
||||
$answer = $answer['answer'];
|
||||
$len = api_strlen($answer);
|
||||
$text = str_replace('['.$answer.']', '<textEntryInteraction responseIdentifier="fill_'.$key.'" expectedLength="'.api_strlen($answer).'"/>', $text);
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
|
||||
{
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$this->gradeList = $this->getGradesList();
|
||||
$out = '';
|
||||
if (is_array($this->answerList)) {
|
||||
foreach ($this->answerList as $answer) {
|
||||
$answerKey = $answer['iid'];
|
||||
$answer = $answer['answer'];
|
||||
$out .= ' <responseDeclaration identifier="fill_'.$answerKey.'" cardinality="single" baseType="identifier">'."\n";
|
||||
$out .= ' <correctResponse>'."\n";
|
||||
$out .= ' <value><![CDATA['.formatExerciseQtiText($answer).']]></value>'."\n";
|
||||
$out .= ' </correctResponse>'."\n";
|
||||
if (isset($this->gradeList[$answerKey])) {
|
||||
$out .= ' <mapping>'."\n";
|
||||
$out .= ' <mapEntry mapKey="'.$answer.'" mappedValue="'.$this->gradeList[$answerKey].'"/>'."\n";
|
||||
$out .= ' </mapping>'."\n";
|
||||
}
|
||||
|
||||
$out .= ' </responseDeclaration>'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*/
|
||||
class ImsAnswerMatching extends Answer implements ImsAnswerInterface
|
||||
{
|
||||
public $leftList = [];
|
||||
public $rightList = [];
|
||||
private $answerList = [];
|
||||
|
||||
/**
|
||||
* Export the question part as a matrix-choice, with only one possible answer per line.
|
||||
*
|
||||
* @param string $questionIdent
|
||||
* @param string $questionStatment
|
||||
* @param string $questionDesc
|
||||
* @param string $questionMedia
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
|
||||
{
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$maxAssociation = max(count($this->leftList), count($this->rightList));
|
||||
|
||||
$out = '<matchInteraction responseIdentifier="'.$questionIdent.'" maxAssociations="'.$maxAssociation.'">'."\n";
|
||||
$out .= $questionStatment;
|
||||
|
||||
//add left column
|
||||
$out .= ' <simpleMatchSet>'."\n";
|
||||
if (is_array($this->leftList)) {
|
||||
foreach ($this->leftList as $leftKey => $leftElement) {
|
||||
$out .= '
|
||||
<simpleAssociableChoice identifier="left_'.$leftKey.'" >
|
||||
<![CDATA['.formatExerciseQtiText($leftElement['answer']).']]>
|
||||
</simpleAssociableChoice>'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
$out .= ' </simpleMatchSet>'."\n";
|
||||
|
||||
//add right column
|
||||
$out .= ' <simpleMatchSet>'."\n";
|
||||
$i = 0;
|
||||
|
||||
if (is_array($this->rightList)) {
|
||||
foreach ($this->rightList as $rightKey => $rightElement) {
|
||||
$out .= '<simpleAssociableChoice identifier="right_'.$i.'" >
|
||||
<![CDATA['.formatExerciseQtiText($rightElement['answer']).']]>
|
||||
</simpleAssociableChoice>'."\n";
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$out .= ' </simpleMatchSet>'."\n";
|
||||
$out .= '</matchInteraction>'."\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
|
||||
{
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$out = ' <responseDeclaration identifier="'.$questionIdent.'" cardinality="single" baseType="identifier">'."\n";
|
||||
$out .= ' <correctResponse>'."\n";
|
||||
|
||||
$gradeArray = [];
|
||||
if (isset($this->leftList) && is_array($this->leftList)) {
|
||||
foreach ($this->leftList as $leftKey => $leftElement) {
|
||||
$i = 0;
|
||||
foreach ($this->rightList as $rightKey => $rightElement) {
|
||||
if (($leftElement['match'] == $rightElement['code'])) {
|
||||
$out .= ' <value>left_'.$leftKey.' right_'.$i.'</value>'."\n";
|
||||
$gradeArray['left_'.$leftKey.' right_'.$i] = $leftElement['grade'];
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$out .= ' </correctResponse>'."\n";
|
||||
|
||||
if (is_array($gradeArray)) {
|
||||
$out .= ' <mapping>'."\n";
|
||||
foreach ($gradeArray as $gradeKey => $grade) {
|
||||
$out .= ' <mapEntry mapKey="'.$gradeKey.'" mappedValue="'.$grade.'"/>'."\n";
|
||||
}
|
||||
$out .= ' </mapping>'."\n";
|
||||
}
|
||||
|
||||
$out .= ' </responseDeclaration>'."\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*/
|
||||
class ImsAnswerHotspot extends Answer implements ImsAnswerInterface
|
||||
{
|
||||
private $answerList = [];
|
||||
private $gradeList = [];
|
||||
|
||||
/**
|
||||
* @todo update this to match hot spots instead of copying matching
|
||||
* Export the question part as a matrix-choice, with only one possible answer per line.
|
||||
*
|
||||
* @param string $questionIdent
|
||||
* @param string $questionStatment
|
||||
* @param string $questionDesc
|
||||
* @param string $questionMedia
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
|
||||
{
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$mediaFilePath = api_get_course_path().'/document/images/'.$questionMedia;
|
||||
$sysQuestionMediaPath = api_get_path(SYS_COURSE_PATH).$mediaFilePath;
|
||||
$questionMedia = api_get_path(WEB_COURSE_PATH).$mediaFilePath;
|
||||
$mimetype = mime_content_type($sysQuestionMediaPath);
|
||||
if (empty($mimetype)) {
|
||||
$mimetype = 'image/jpeg';
|
||||
}
|
||||
|
||||
$text = ' <p>'.$questionStatment.'</p>'."\n";
|
||||
$text .= ' <graphicOrderInteraction responseIdentifier="hotspot_'.$questionIdent.'">'."\n";
|
||||
$text .= ' <prompt>'.$questionDesc.'</prompt>'."\n";
|
||||
$text .= ' <object type="'.$mimetype.'" width="250" height="230" data="'.$questionMedia.'">-</object>'."\n";
|
||||
if (is_array($this->answerList)) {
|
||||
foreach ($this->answerList as $key => $answer) {
|
||||
$key = $answer['iid'];
|
||||
$answerTxt = $answer['answer'];
|
||||
$len = api_strlen($answerTxt);
|
||||
//coords are transformed according to QTIv2 rules here: http://www.imsproject.org/question/qtiv2p1pd/imsqti_infov2p1pd.html#element10663
|
||||
$coords = '';
|
||||
$type = 'default';
|
||||
switch ($answer['hotspot_type']) {
|
||||
case 'square':
|
||||
$type = 'rect';
|
||||
$res = [];
|
||||
$coords = preg_match('/^\s*(\d+);(\d+)\|(\d+)\|(\d+)\s*$/', $answer['hotspot_coord'], $res);
|
||||
$coords = $res[1].','.$res[2].','.((int) $res[1] + (int) $res[3]).",".((int) $res[2] + (int) $res[4]);
|
||||
break;
|
||||
case 'circle':
|
||||
$type = 'circle';
|
||||
$res = [];
|
||||
$coords = preg_match('/^\s*(\d+);(\d+)\|(\d+)\|(\d+)\s*$/', $answer['hotspot_coord'], $res);
|
||||
$coords = $res[1].','.$res[2].','.sqrt(pow(($res[1] - $res[3]), 2) + pow(($res[2] - $res[4])));
|
||||
break;
|
||||
case 'poly':
|
||||
$type = 'poly';
|
||||
$coords = str_replace([';', '|'], [',', ','], $answer['hotspot_coord']);
|
||||
break;
|
||||
case 'delineation':
|
||||
$type = 'delineation';
|
||||
$coords = str_replace([';', '|'], [',', ','], $answer['hotspot_coord']);
|
||||
break;
|
||||
}
|
||||
$text .= ' <hotspotChoice shape="'.$type.'" coords="'.$coords.'" identifier="'.$key.'"/>'."\n";
|
||||
}
|
||||
}
|
||||
$text .= ' </graphicOrderInteraction>'."\n";
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
|
||||
{
|
||||
$this->answerList = $this->getAnswersList(true);
|
||||
$this->gradeList = $this->getGradesList();
|
||||
$out = ' <responseDeclaration identifier="hotspot_'.$questionIdent.'" cardinality="ordered" baseType="identifier">'."\n";
|
||||
if (is_array($this->answerList)) {
|
||||
$out .= ' <correctResponse>'."\n";
|
||||
foreach ($this->answerList as $answerKey => $answer) {
|
||||
$answerKey = $answer['iid'];
|
||||
$answer = $answer['answer'];
|
||||
$out .= '<value><![CDATA['.formatExerciseQtiText($answerKey).']]></value>';
|
||||
}
|
||||
$out .= ' </correctResponse>'."\n";
|
||||
}
|
||||
$out .= ' </responseDeclaration>'."\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*/
|
||||
class ImsAnswerFree extends Answer implements ImsAnswerInterface
|
||||
{
|
||||
/**
|
||||
* @todo implement
|
||||
* Export the question part as a matrix-choice, with only one possible answer per line.
|
||||
*
|
||||
* @param string $questionIdent
|
||||
* @param string $questionStatment
|
||||
* @param string $questionDesc
|
||||
* @param string $questionMedia
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc = '', $questionMedia = '')
|
||||
{
|
||||
$questionDesc = formatExerciseQtiText($questionDesc);
|
||||
|
||||
return '<extendedTextInteraction responseIdentifier="'.$questionIdent.'" >
|
||||
<prompt>
|
||||
'.$questionDesc.'
|
||||
</prompt>
|
||||
</extendedTextInteraction>';
|
||||
}
|
||||
|
||||
public function imsExportResponsesDeclaration($questionIdent, Question $question = null)
|
||||
{
|
||||
$out = ' <responseDeclaration identifier="'.$questionIdent.'" cardinality="single" baseType="string">';
|
||||
$out .= '<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float">
|
||||
<defaultValue><value>'.$question->weighting.'</value></defaultValue></outcomeDeclaration>';
|
||||
$out .= ' </responseDeclaration>'."\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
515
main/exercise/export/qti2/qti2_export.php
Normal file
515
main/exercise/export/qti2/qti2_export.php
Normal file
@@ -0,0 +1,515 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* @author Claro Team <cvs@claroline.net>
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com>
|
||||
*/
|
||||
require __DIR__.'/qti2_classes.php';
|
||||
|
||||
/**
|
||||
* An IMS/QTI item. It corresponds to a single question.
|
||||
* This class allows export from Claroline to IMS/QTI2.0 XML format of a single question.
|
||||
* It is not usable as-is, but must be subclassed, to support different kinds of questions.
|
||||
*
|
||||
* Every start_*() and corresponding end_*(), as well as export_*() methods return a string.
|
||||
*
|
||||
* note: Attached files are NOT exported.
|
||||
*/
|
||||
class ImsAssessmentItem
|
||||
{
|
||||
/**
|
||||
* @var Ims2Question
|
||||
*/
|
||||
public $question;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $questionIdent;
|
||||
/**
|
||||
* @var ImsAnswerInterface
|
||||
*/
|
||||
public $answer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Ims2Question $question ims2Question object we want to export
|
||||
*/
|
||||
public function __construct($question)
|
||||
{
|
||||
$this->question = $question;
|
||||
$this->answer = $this->question->setAnswer();
|
||||
$this->questionIdent = 'QST_'.$question->iid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the XML flow.
|
||||
*
|
||||
* This opens the <item> block, with correct attributes.
|
||||
*/
|
||||
public function start_item()
|
||||
{
|
||||
$categoryTitle = '';
|
||||
if (!empty($this->question->category)) {
|
||||
$category = new TestCategory();
|
||||
$category = $category->getCategory($this->question->category);
|
||||
if ($category) {
|
||||
$categoryTitle = htmlspecialchars(formatExerciseQtiText($category->name));
|
||||
}
|
||||
}
|
||||
|
||||
return '<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 imsqti_v2p1.xsd"
|
||||
identifier="'.$this->questionIdent.'"
|
||||
title = "'.htmlspecialchars(formatExerciseQtiText($this->question->selectTitle())).'"
|
||||
category = "'.$categoryTitle.'"
|
||||
>'."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* End the XML flow, closing the </item> tag.
|
||||
*/
|
||||
public function end_item()
|
||||
{
|
||||
return "</assessmentItem>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
public function start_item_body()
|
||||
{
|
||||
return ' <itemBody>'."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_item_body()
|
||||
{
|
||||
return " </itemBody>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* add the response processing template used.
|
||||
*/
|
||||
public function add_response_processing()
|
||||
{
|
||||
return ' <responseProcessing template="http://www.imsglobal.org/question/qti_v2p1/rptemplates/map_correct"/>'."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the question as an IMS/QTI Item.
|
||||
*
|
||||
* This is a default behaviour, some classes may want to override this.
|
||||
*
|
||||
* @return string string, the XML flow for an Item
|
||||
*/
|
||||
public function export($standalone = false)
|
||||
{
|
||||
$head = $foot = '';
|
||||
if ($standalone) {
|
||||
$head = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'."\n";
|
||||
}
|
||||
|
||||
//TODO understand why answer might be a non-object sometimes
|
||||
if (!is_object($this->answer)) {
|
||||
return $head;
|
||||
}
|
||||
|
||||
return $head
|
||||
.$this->start_item()
|
||||
.$this->answer->imsExportResponsesDeclaration($this->questionIdent, $this->question)
|
||||
.$this->start_item_body()
|
||||
.$this->answer->imsExportResponses(
|
||||
$this->questionIdent,
|
||||
$this->question->question,
|
||||
$this->question->description,
|
||||
$this->question->getPictureFilename()
|
||||
)
|
||||
.$this->end_item_body()
|
||||
.$this->add_response_processing()
|
||||
.$this->end_item()
|
||||
.$foot;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents an entire exercise to be exported in IMS/QTI.
|
||||
* It will be represented by a single <section> containing several <item>.
|
||||
*
|
||||
* Some properties cannot be exported, as IMS does not support them :
|
||||
* - type (one page or multiple pages)
|
||||
* - start_date and end_date
|
||||
* - max_attempts
|
||||
* - show_answer
|
||||
* - anonymous_attempts
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
class ImsSection
|
||||
{
|
||||
public $exercise;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Exercise $exe The Exercise instance to export
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function __construct($exe)
|
||||
{
|
||||
$this->exercise = $exe;
|
||||
}
|
||||
|
||||
public function start_section()
|
||||
{
|
||||
return '<section
|
||||
ident = "EXO_'.$this->exercise->selectId().'"
|
||||
title = "'.cleanAttribute(formatExerciseQtiDescription($this->exercise->selectTitle())).'"
|
||||
>'."\n";
|
||||
}
|
||||
|
||||
public function end_section()
|
||||
{
|
||||
return "</section>\n";
|
||||
}
|
||||
|
||||
public function export_duration()
|
||||
{
|
||||
if ($max_time = $this->exercise->selectTimeLimit()) {
|
||||
// return exercise duration in ISO8601 format.
|
||||
$minutes = floor($max_time / 60);
|
||||
$seconds = $max_time % 60;
|
||||
|
||||
return '<duration>PT'.$minutes.'M'.$seconds."S</duration>\n";
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the presentation (Exercise's description).
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export_presentation()
|
||||
{
|
||||
return "<presentation_material><flow_mat><material>\n"
|
||||
.' <mattext><![CDATA['.formatExerciseQtiDescription($this->exercise->selectDescription())."]]></mattext>\n"
|
||||
."</material></flow_mat></presentation_material>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the ordering information.
|
||||
* Either sequential, through all questions, or random, with a selected number of questions.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export_ordering()
|
||||
{
|
||||
$out = '';
|
||||
if ($n = $this->exercise->getShuffle()) {
|
||||
$out .= "<selection_ordering>"
|
||||
." <selection>\n"
|
||||
." <selection_number>".$n."</selection_number>\n"
|
||||
." </selection>\n"
|
||||
.' <order order_type="Random" />'
|
||||
."\n</selection_ordering>\n";
|
||||
} else {
|
||||
$out .= '<selection_ordering sequence_type="Normal">'."\n"
|
||||
." <selection />\n"
|
||||
."</selection_ordering>\n";
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the questions, as a succession of <items>.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function exportQuestions()
|
||||
{
|
||||
$out = '';
|
||||
foreach ($this->exercise->selectQuestionList() as $q) {
|
||||
$out .= export_question_qti($q, false);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the exercise in IMS/QTI.
|
||||
*
|
||||
* @param bool $standalone wether it should include XML tag and DTD line
|
||||
*
|
||||
* @return string string containing the XML flow
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export($standalone)
|
||||
{
|
||||
$head = $foot = '';
|
||||
if ($standalone) {
|
||||
$head = '<?xml version = "1.0" encoding = "UTF-8" standalone = "no"?>'."\n"
|
||||
.'<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">'."\n"
|
||||
."<questestinterop>\n";
|
||||
$foot = "</questestinterop>\n";
|
||||
}
|
||||
|
||||
return $head
|
||||
.$this->start_section()
|
||||
.$this->export_duration()
|
||||
.$this->export_presentation()
|
||||
.$this->export_ordering()
|
||||
.$this->exportQuestions()
|
||||
.$this->end_section()
|
||||
.$foot;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Some quick notes on identifiers generation.
|
||||
The IMS format requires some blocks, like items, responses, feedbacks, to be uniquely
|
||||
identified.
|
||||
The unicity is mandatory in a single XML, of course, but it's prefered that the identifier stays
|
||||
coherent for an entire site.
|
||||
|
||||
Here's the method used to generate those identifiers.
|
||||
Question identifier :: "QST_" + <Question Id from the DB> + "_" + <Question numeric type>
|
||||
Response identifier :: <Question identifier> + "_A_" + <Response Id from the DB>
|
||||
Condition identifier :: <Question identifier> + "_C_" + <Response Id from the DB>
|
||||
Feedback identifier :: <Question identifier> + "_F_" + <Response Id from the DB>
|
||||
*/
|
||||
/**
|
||||
* Class ImsItem.
|
||||
*
|
||||
* An IMS/QTI item. It corresponds to a single question.
|
||||
* This class allows export from Claroline to IMS/QTI XML format.
|
||||
* It is not usable as-is, but must be subclassed, to support different kinds of questions.
|
||||
*
|
||||
* Every start_*() and corresponding end_*(), as well as export_*() methods return a string.
|
||||
*
|
||||
* warning: Attached files are NOT exported.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
class ImsItem
|
||||
{
|
||||
public $question;
|
||||
public $questionIdent;
|
||||
public $answer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Question $question the Question object we want to export
|
||||
*
|
||||
* @author Anamd Tihon
|
||||
*/
|
||||
public function __construct($question)
|
||||
{
|
||||
$this->question = $question;
|
||||
$this->answer = $question->answer;
|
||||
$this->questionIdent = 'QST_'.$question->selectId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the XML flow.
|
||||
*
|
||||
* This opens the <item> block, with correct attributes.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function start_item()
|
||||
{
|
||||
return '<item title="'.cleanAttribute(formatExerciseQtiDescription($this->question->selectTitle())).'" ident="'.$this->questionIdent.'">'."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* End the XML flow, closing the </item> tag.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function end_item()
|
||||
{
|
||||
return "</item>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the opening, with the question itself.
|
||||
*
|
||||
* This means it opens the <presentation> but doesn't close it, as this is the role of end_presentation().
|
||||
* In between, the export_responses from the subclass should have been called.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function start_presentation()
|
||||
{
|
||||
return '<presentation label="'.$this->questionIdent.'"><flow>'."\n"
|
||||
.'<material><mattext>'.formatExerciseQtiDescription($this->question->selectDescription())."</mattext></material>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* End the </presentation> part, opened by export_header.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function end_presentation()
|
||||
{
|
||||
return "</flow></presentation>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the response processing, and declare the default variable, SCORE, at 0 in the outcomes.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function start_processing()
|
||||
{
|
||||
return '<resprocessing><outcomes><decvar vartype="Integer" defaultval="0" /></outcomes>'."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* End the response processing part.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function end_processing()
|
||||
{
|
||||
return "</resprocessing>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the question as an IMS/QTI Item.
|
||||
*
|
||||
* This is a default behaviour, some classes may want to override this.
|
||||
*
|
||||
* @param bool $standalone Boolean stating if it should be exported as a stand-alone question
|
||||
*
|
||||
* @return string string, the XML flow for an Item
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export($standalone = false)
|
||||
{
|
||||
global $charset;
|
||||
$head = $foot = '';
|
||||
|
||||
if ($standalone) {
|
||||
$head = '<?xml version = "1.0" encoding = "'.$charset.'" standalone = "no"?>'."\n"
|
||||
.'<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">'."\n"
|
||||
."<questestinterop>\n";
|
||||
$foot = "</questestinterop>\n";
|
||||
}
|
||||
|
||||
return $head
|
||||
.$this->start_item()
|
||||
.$this->start_presentation()
|
||||
.$this->answer->imsExportResponses($this->questionIdent)
|
||||
.$this->end_presentation()
|
||||
.$this->start_processing()
|
||||
.$this->answer->imsExportProcessing($this->questionIdent)
|
||||
.$this->end_processing()
|
||||
.$this->answer->imsExportFeedback($this->questionIdent)
|
||||
.$this->end_item()
|
||||
.$foot;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a complete exercise in IMS/QTI format, from its ID.
|
||||
*
|
||||
* @param int $exerciseId The exercise to export
|
||||
* @param bool $standalone wether it should include XML tag and DTD line
|
||||
*
|
||||
* @return string XML as a string, or an empty string if there's no exercise with given ID
|
||||
*/
|
||||
function export_exercise_to_qti($exerciseId, $standalone = true)
|
||||
{
|
||||
$exercise = new Exercise();
|
||||
if (!$exercise->read($exerciseId)) {
|
||||
return '';
|
||||
}
|
||||
$ims = new ImsSection($exercise);
|
||||
|
||||
return $ims->export($standalone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML flow corresponding to one question.
|
||||
*
|
||||
* @param int $questionId
|
||||
* @param bool $standalone (ie including XML tag, DTD declaration, etc)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function export_question_qti($questionId, $standalone = true)
|
||||
{
|
||||
$question = new Ims2Question();
|
||||
$qst = $question->read($questionId);
|
||||
if (!$qst) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$isValid = $qst instanceof UniqueAnswer
|
||||
|| $qst instanceof MultipleAnswer
|
||||
|| $qst instanceof FreeAnswer
|
||||
|| $qst instanceof MultipleAnswerDropdown
|
||||
|| $qst instanceof MultipleAnswerDropdownCombination
|
||||
;
|
||||
|
||||
if (!$isValid) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$question->iid = $qst->iid;
|
||||
$question->type = $qst->type;
|
||||
$question->question = $qst->question;
|
||||
$question->description = $qst->description;
|
||||
$question->weighting = $qst->weighting;
|
||||
$question->position = $qst->position;
|
||||
$question->picture = $qst->picture;
|
||||
$question->category = $qst->category;
|
||||
$ims = new ImsAssessmentItem($question);
|
||||
|
||||
return $ims->export($standalone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean text like a description.
|
||||
*/
|
||||
function formatExerciseQtiDescription($text)
|
||||
{
|
||||
$entities = api_html_entity_decode($text);
|
||||
|
||||
return htmlspecialchars($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean titles.
|
||||
*
|
||||
* @param $text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function formatExerciseQtiText($text)
|
||||
{
|
||||
return htmlspecialchars($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function cleanAttribute($text)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
79
main/exercise/export/scorm/ScormAnswerFillInBlanks.php
Normal file
79
main/exercise/export/scorm/ScormAnswerFillInBlanks.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This class handles the SCORM export of fill-in-the-blanks questions.
|
||||
*/
|
||||
class ScormAnswerFillInBlanks extends Answer
|
||||
{
|
||||
/**
|
||||
* Export the text with missing words.
|
||||
*
|
||||
* As a side effect, it stores two lists in the class :
|
||||
* the missing words and their respective weightings.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
// get all enclosed answers
|
||||
$blankList = [];
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$blankList[] = '['.$answer.']';
|
||||
}
|
||||
|
||||
// splits text and weightings that are joined with the character '::'
|
||||
$listAnswerInfo = FillBlanks::getAnswerInfo($answer);
|
||||
//$switchableAnswerSet = $listAnswerInfo['switchable'];
|
||||
|
||||
// display empty [input] with the right width for student to fill it
|
||||
$answer = '';
|
||||
$answerList = [];
|
||||
for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) {
|
||||
// display the common words
|
||||
$answer .= $listAnswerInfo['common_words'][$i];
|
||||
// display the blank word
|
||||
$attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px';
|
||||
$answer .= FillBlanks::getFillTheBlankHtml(
|
||||
$this->questionJSId,
|
||||
$this->questionJSId + 1,
|
||||
'',
|
||||
$attributes,
|
||||
$answer,
|
||||
$listAnswerInfo,
|
||||
true,
|
||||
$i,
|
||||
'question_'.$this->questionJSId.'_fib_'.($i + 1)
|
||||
);
|
||||
$answerList[] = $i + 1;
|
||||
}
|
||||
|
||||
// display the last common word
|
||||
$answer .= $listAnswerInfo['common_words'][$i];
|
||||
|
||||
// because [] is parsed here we follow this procedure:
|
||||
// 1. find everything between the [ and ] tags
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
|
||||
foreach ($listAnswerInfo['weighting'] as $key => $weight) {
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.']['.($key + 1).'] = '.$weight.";\n";
|
||||
}
|
||||
|
||||
$wordList = "'".implode("', '", $listAnswerInfo['words'])."'";
|
||||
$answerList = "'".implode("', '", $answerList)."'";
|
||||
|
||||
$html = '<tr><td colspan="2"><table width="100%">';
|
||||
$html .= '<tr>
|
||||
<td>
|
||||
'.$answer.'
|
||||
</td>
|
||||
</tr></table></td></tr>';
|
||||
$js .= 'questions_answers['.$this->questionJSId.'] = new Array('.$answerList.');'."\n";
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = new Array('.$wordList.');'."\n";
|
||||
$js .= 'questions_types['.$this->questionJSId.'] = \'fib\';'."\n";
|
||||
$js .= $jstmpw;
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
98
main/exercise/export/scorm/ScormAnswerMatching.php
Normal file
98
main/exercise/export/scorm/ScormAnswerMatching.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This class handles the SCORM export of matching questions.
|
||||
*/
|
||||
class ScormAnswerMatching extends Answer
|
||||
{
|
||||
/**
|
||||
* Export the question part as a matrix-choice, with only one possible answer per line.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
// prepare list of right proposition to allow
|
||||
// - easiest display
|
||||
// - easiest randomisation if needed one day
|
||||
// (here I use array_values to change array keys from $code1 $code2 ... to 0 1 ...)
|
||||
// get max length of displayed array
|
||||
$nbrAnswers = $this->selectNbrAnswers();
|
||||
$counter = 1;
|
||||
$questionId = $this->questionJSId;
|
||||
$jstmpw = 'questions_answers_ponderation['.$questionId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.'][0] = 0;'."\n";
|
||||
|
||||
// Options (A, B, C, ...) that will be put into the list-box
|
||||
$options = [];
|
||||
$letter = 'A';
|
||||
for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
|
||||
$answerCorrect = $this->isCorrect($answerId);
|
||||
$answer = $this->selectAnswer($answerId);
|
||||
$realAnswerId = $this->selectAutoId($answerId);
|
||||
if (!$answerCorrect) {
|
||||
$options[$realAnswerId]['Lettre'] = $letter;
|
||||
// answers that will be shown at the right side
|
||||
$options[$realAnswerId]['Reponse'] = $answer;
|
||||
$letter++;
|
||||
}
|
||||
}
|
||||
|
||||
$html = [];
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
|
||||
// Answers
|
||||
for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
|
||||
$identifier = 'question_'.$questionId.'_matching_';
|
||||
$answer = $this->selectAnswer($answerId);
|
||||
$answerCorrect = $this->isCorrect($answerId);
|
||||
$weight = $this->selectWeighting($answerId);
|
||||
$jstmp .= $answerId.',';
|
||||
|
||||
if ($answerCorrect) {
|
||||
$html[] = '<tr class="option_row">';
|
||||
$html[] = '<td width="40%" valign="top"> '.$answer.'</td>';
|
||||
$html[] = '<td width="20%" align="center"> ';
|
||||
$html[] = '<select name="'.$identifier.$counter.'" id="'.$identifier.$counter.'">';
|
||||
$html[] = ' <option value="0">--</option>';
|
||||
// fills the list-box
|
||||
foreach ($options as $key => $val) {
|
||||
$html[] = '<option value="'.$key.'">'.$val['Lettre'].'</option>';
|
||||
}
|
||||
|
||||
$html[] = '</select> </td>';
|
||||
$html[] = '<td width="40%" valign="top">';
|
||||
foreach ($options as $key => $val) {
|
||||
$html[] = '<b>'.$val['Lettre'].'.</b> '.$val['Reponse'].'<br />';
|
||||
}
|
||||
$html[] = '</td></tr>';
|
||||
|
||||
$jstmpc .= '['.$answerCorrect.','.$counter.'],';
|
||||
|
||||
$myWeight = explode('@', $weight);
|
||||
if (2 == count($myWeight)) {
|
||||
$weight = $myWeight[0];
|
||||
} else {
|
||||
$weight = $myWeight[0];
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$counter.'] = '.$weight.";\n";
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
$js .= 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');'."\n";
|
||||
$js .= 'questions_answers_correct['.$questionId.'] = new Array('.substr($jstmpc, 0, -1).');'."\n";
|
||||
$js .= 'questions_types['.$questionId.'] = \'matching\';'."\n";
|
||||
$js .= $jstmpw;
|
||||
|
||||
$htmlResult = '<tr><td colspan="2"><table id="question_'.$questionId.'" width="100%">';
|
||||
$htmlResult .= implode("\n", $html);
|
||||
$htmlResult .= '</table></td></tr>'."\n";
|
||||
|
||||
return [$js, $htmlResult];
|
||||
}
|
||||
}
|
||||
118
main/exercise/export/scorm/ScormAnswerMultipleChoice.php
Normal file
118
main/exercise/export/scorm/ScormAnswerMultipleChoice.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This class handles the export to SCORM of a multiple choice question
|
||||
* (be it single answer or multiple answers).
|
||||
*/
|
||||
class ScormAnswerMultipleChoice extends Answer
|
||||
{
|
||||
/**
|
||||
* Return HTML code for possible answers.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = [];
|
||||
$type = $this->getQuestionType();
|
||||
$questionId = $this->questionJSId;
|
||||
$jstmpw = 'questions_answers_ponderation['.$questionId.'] = new Array();';
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.'][0] = 0;';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.'] = new Array();';
|
||||
$html = [];
|
||||
|
||||
//not sure if we are going to export also the MULTIPLE_ANSWER_COMBINATION to SCORM
|
||||
//if ($type == MCMA || $type == MULTIPLE_ANSWER_COMBINATION ) {
|
||||
if (MCMA == $type) {
|
||||
$id = 1;
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$identifier = 'question_'.$questionId.'_multiple_'.$i;
|
||||
$html[] =
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier.'" id="'.$identifier.'" value="'.$i.'" type="checkbox" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier.'">'.Security::remove_XSS($this->answer[$i]).'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
|
||||
$jstmp .= $i.',';
|
||||
if ($this->correct[$i]) {
|
||||
$jstmpc .= $i.',';
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$i.'] = '.$this->weighting[$i].';';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.']['.$i.'] = '.$this->correct[$i].';';
|
||||
$id++;
|
||||
}
|
||||
$js[] = 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');'."\n";
|
||||
$js[] = 'questions_types['.$questionId.'] = \'mcma\';'."\n";
|
||||
$js[] = $jstmpw;
|
||||
} elseif (MULTIPLE_ANSWER_COMBINATION == $type) {
|
||||
$js = [];
|
||||
$id = 1;
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$identifier = 'question_'.$questionId.'_exact_'.$i;
|
||||
$html[] =
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier.'" id="'.$identifier.'" value="'.$i.'" type="checkbox" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier.'">'.Security::remove_XSS($this->answer[$i]).'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
|
||||
$jstmp .= $i.',';
|
||||
if ($this->correct[$i]) {
|
||||
$jstmpc .= $i.',';
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$i.'] = '.$this->weighting[$i].';';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.']['.$i.'] = '.$this->correct[$i].';';
|
||||
$id++;
|
||||
}
|
||||
$js[] = 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');';
|
||||
$js[] = 'questions_types['.$questionId.'] = "exact";';
|
||||
$js[] = $jstmpw;
|
||||
} else {
|
||||
$id = 1;
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$identifier = 'question_'.$questionId.'_unique_'.$i;
|
||||
$identifier_name = 'question_'.$questionId.'_unique_answer';
|
||||
$html[] =
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier_name.'" id="'.$identifier.'" value="'.$i.'" type="checkbox"/>
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier.'">'.Security::remove_XSS($this->answer[$i]).'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
$jstmp .= $i.',';
|
||||
if ($this->correct[$i]) {
|
||||
$jstmpc .= $i;
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$i.'] = '.$this->weighting[$i].';';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.']['.$i.'] = '.$this->correct[$i].';';
|
||||
$id++;
|
||||
}
|
||||
$js[] = 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');';
|
||||
$js[] = 'questions_types['.$questionId.'] = \'mcua\';';
|
||||
$js[] = $jstmpw;
|
||||
}
|
||||
|
||||
$htmlResult = '<tr><td colspan="2"><table id="question_'.$questionId.'" width="100%">';
|
||||
$htmlResult .= implode("\n", $html);
|
||||
$htmlResult .= '</table></td></tr>';
|
||||
|
||||
$js = implode("\n", $js);
|
||||
|
||||
return [$js, $htmlResult];
|
||||
}
|
||||
}
|
||||
55
main/exercise/export/scorm/ScormAnswerTrueFalse.php
Normal file
55
main/exercise/export/scorm/ScormAnswerTrueFalse.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This class handles the SCORM export of true/false questions.
|
||||
*/
|
||||
class ScormAnswerTrueFalse extends Answer
|
||||
{
|
||||
/**
|
||||
* Return the XML flow for the possible answers.
|
||||
* That's one <response_lid>, containing several <flow_label>.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
$html = '<tr><td colspan="2"><table width="100%">';
|
||||
$identifier = 'question_'.$this->questionJSId.'_tf';
|
||||
$identifier_true = $identifier.'_true';
|
||||
$identifier_false = $identifier.'_false';
|
||||
$html .=
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier_true.'" id="'.$identifier_true.'" value="'.$this->trueGrade.'" type="radio" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier_true.'">'.get_lang('True').'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
$html .=
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier_false.'" id="'.$identifier_false.'" value="'.$this->falseGrade.'" type="radio" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier_false.'">'.get_lang('False').'</label>
|
||||
</td>
|
||||
</tr></table></td></tr>';
|
||||
$js .= 'questions_answers['.$this->questionJSId.'] = new Array(\'true\',\'false\');'."\n";
|
||||
$js .= 'questions_types['.$this->questionJSId.'] = \'tf\';'."\n";
|
||||
if ('TRUE' === $this->response) {
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = new Array(\'true\');'."\n";
|
||||
} else {
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = new Array(\'false\');'."\n";
|
||||
}
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][1] = '.$this->weighting[1].";\n";
|
||||
$js .= $jstmpw;
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
159
main/exercise/export/scorm/ScormAssessmentItem.php
Normal file
159
main/exercise/export/scorm/ScormAssessmentItem.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* A SCORM item. It corresponds to a single question.
|
||||
* This class allows export from Chamilo SCORM 1.2 format of a single question.
|
||||
* It is not usable as-is, but must be subclassed, to support different kinds of questions.
|
||||
*
|
||||
* Every start_*() and corresponding end_*(), as well as export_*() methods return a string.
|
||||
*
|
||||
* Attached files are NOT exported.
|
||||
*/
|
||||
class ScormAssessmentItem
|
||||
{
|
||||
public $question;
|
||||
public $question_ident;
|
||||
public $answer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param ScormQuestion $question the Question object we want to export
|
||||
*/
|
||||
public function __construct($question)
|
||||
{
|
||||
$this->question = $question;
|
||||
$this->question->setAnswer();
|
||||
$this->questionIdent = 'QST_'.$question->iid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the XML flow.
|
||||
*
|
||||
* This opens the <item> block, with correct attributes.
|
||||
*/
|
||||
public function start_page()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the XML flow, closing the </item> tag.
|
||||
*/
|
||||
public function end_page()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '</html>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start document header.
|
||||
*/
|
||||
public function start_header()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '<head>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Print CSS inclusion.
|
||||
*/
|
||||
public function css()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End document header.
|
||||
*/
|
||||
public function end_header()
|
||||
{
|
||||
// if ($this->standalone) {
|
||||
// return '</head>';
|
||||
// }
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
public function start_js()
|
||||
{
|
||||
return '<script type="text/javascript" src="assets/api_wrapper.js"></script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_js()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '</script>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
public function start_body()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '<body><form id="dokeos_scorm_form" method="post" action="">';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_body()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '<br /><input class="btn" type="button" id="dokeos_scorm_submit" name="dokeos_scorm_submit" value="OK" /></form></body>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the question as a SCORM Item.
|
||||
* This is a default behaviour, some classes may want to override this.
|
||||
*
|
||||
* @return string|array a string, the XML flow for an Item
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
list($js, $html) = $this->question->export();
|
||||
/*if ($this->standalone) {
|
||||
$res = $this->start_page()
|
||||
.$this->start_header()
|
||||
.$this->css()
|
||||
.$this->start_js()
|
||||
.$this->common_js()
|
||||
.$js
|
||||
.$this->end_js()
|
||||
.$this->end_header()
|
||||
.$this->start_body()
|
||||
.$html
|
||||
.$this->end_body()
|
||||
.$this->end_page();
|
||||
|
||||
return $res;
|
||||
} else {
|
||||
return [$js, $html];
|
||||
}*/
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
216
main/exercise/export/scorm/ScormExercise.php
Normal file
216
main/exercise/export/scorm/ScormExercise.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
/**
|
||||
* This class represents an entire exercise to be exported in SCORM.
|
||||
* It will be represented by a single <section> containing several <item>.
|
||||
*
|
||||
* Some properties cannot be exported, as SCORM does not support them :
|
||||
* - type (one page or multiple pages)
|
||||
* - start_date and end_date
|
||||
* - max_attempts
|
||||
* - show_answer
|
||||
* - anonymous_attempts
|
||||
*
|
||||
* @author Julio Montoya
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
class ScormExercise
|
||||
{
|
||||
public $exercise;
|
||||
public $standalone;
|
||||
|
||||
/**
|
||||
* ScormExercise constructor.
|
||||
*
|
||||
* @param Exercise $exe
|
||||
* @param bool $standalone
|
||||
*/
|
||||
public function __construct($exe, $standalone)
|
||||
{
|
||||
$this->exercise = $exe;
|
||||
$this->standalone = $standalone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the XML flow.
|
||||
*
|
||||
* This opens the <item> block, with correct attributes.
|
||||
*/
|
||||
public function startPage()
|
||||
{
|
||||
$charset = 'UTF-8';
|
||||
|
||||
return '<?xml version="1.0" encoding="'.$charset.'" standalone="no"?><html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the XML flow, closing the </item> tag.
|
||||
*/
|
||||
public function end_page()
|
||||
{
|
||||
return '</html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start document header.
|
||||
*/
|
||||
public function start_header()
|
||||
{
|
||||
return '<head>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Common JS functions.
|
||||
*/
|
||||
public function common_js()
|
||||
{
|
||||
$js = file_get_contents(api_get_path(SYS_CODE_PATH).'exercise/export/scorm/common.js');
|
||||
|
||||
return $js."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_js()
|
||||
{
|
||||
return '</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
public function start_body()
|
||||
{
|
||||
return '<body>'.
|
||||
'<h1>'.$this->exercise->selectTitle().'</h1><p>'.$this->exercise->selectDescription().'</p>'.
|
||||
'<form id="chamilo_scorm_form" method="post" action="">'.
|
||||
'<table width="100%">';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_body()
|
||||
{
|
||||
$button = '<input
|
||||
id="chamilo_scorm_submit"
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
name="chamilo_scorm_submit"
|
||||
value="OK" />';
|
||||
|
||||
return '</table><br />'.$button.'</form></body>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the question as a SCORM Item.
|
||||
*
|
||||
* This is a default behaviour, some classes may want to override this.
|
||||
*
|
||||
* @return string string, the XML flow for an Item
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
global $charset;
|
||||
|
||||
/*$head = '';
|
||||
if ($this->standalone) {
|
||||
$head = '<?xml version = "1.0" encoding = "'.$charset.'" standalone = "no"?>'."\n"
|
||||
.'<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">'."\n";
|
||||
}*/
|
||||
|
||||
list($js, $html) = $this->exportQuestions();
|
||||
|
||||
return $this->startPage()
|
||||
.$this->start_header()
|
||||
.$this->css()
|
||||
.$this->globalAssets()
|
||||
.$this->start_js()
|
||||
.$this->common_js()
|
||||
.$js
|
||||
.$this->end_js()
|
||||
.$this->end_header()
|
||||
.$this->start_body()
|
||||
.$html
|
||||
.$this->end_body()
|
||||
.$this->end_page();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the questions, as a succession of <items>.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function exportQuestions()
|
||||
{
|
||||
$js = $html = '';
|
||||
$encoders = [new JsonEncoder()];
|
||||
$normalizers = [new ObjectNormalizer()];
|
||||
|
||||
$em = Database::getManager();
|
||||
// Export cquiz data
|
||||
/** @var CQuiz $exercise */
|
||||
$exercise = $em->find('ChamiloCourseBundle:CQuiz', $this->exercise->iid);
|
||||
$exercise->setDescription('');
|
||||
$exercise->setTextWhenFinished('');
|
||||
|
||||
$serializer = new Serializer($normalizers, $encoders);
|
||||
$jsonContent = $serializer->serialize($exercise, 'json');
|
||||
$js .= "var exerciseInfo = JSON.parse('".$jsonContent."');\n";
|
||||
|
||||
$counter = 0;
|
||||
$scormQuestion = new ScormQuestion();
|
||||
foreach ($this->exercise->selectQuestionList() as $q) {
|
||||
list($jstmp, $htmltmp) = $scormQuestion->exportQuestionToScorm($q, $counter);
|
||||
$js .= $jstmp."\n";
|
||||
$html .= $htmltmp."\n";
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
|
||||
/**
|
||||
* Print CSS inclusion.
|
||||
*/
|
||||
private function css()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End document header.
|
||||
*/
|
||||
private function end_header()
|
||||
{
|
||||
return '</head>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
private function start_js()
|
||||
{
|
||||
return '<script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function globalAssets()
|
||||
{
|
||||
$assets = '<script type="text/javascript" src="assets/jquery/jquery.min.js"></script>'."\n";
|
||||
$assets .= '<script type="text/javascript" src="assets/api_wrapper.js"></script>'."\n";
|
||||
$assets .= '<link href="assets/bootstrap/bootstrap.min.css" rel="stylesheet" media="screen" type="text/css" />';
|
||||
|
||||
return $assets;
|
||||
}
|
||||
}
|
||||
221
main/exercise/export/scorm/ScormQuestion.php
Normal file
221
main/exercise/export/scorm/ScormQuestion.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* The ScormQuestion class is a gateway to getting the answers exported
|
||||
* (the question is just an HTML text, while the answers are the most important).
|
||||
* It is important to note that the SCORM export process is done in two parts.
|
||||
* First, the HTML part (which is the presentation), and second the JavaScript
|
||||
* part (the process).
|
||||
* The two bits are separate to allow for a one-big-javascript and a one-big-html
|
||||
* files to be built. Each export function thus returns an array of HTML+JS.
|
||||
*
|
||||
* @author Claro Team <cvs@claroline.net>
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com>
|
||||
*/
|
||||
class ScormQuestion extends Question
|
||||
{
|
||||
public $js_id;
|
||||
public $answer;
|
||||
|
||||
/**
|
||||
* ScormQuestion constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML + JS flow corresponding to one question.
|
||||
*
|
||||
* @param int $questionId The question ID
|
||||
* @param int $jsId The JavaScript ID for this question.
|
||||
* Due to the nature of interactions, we must have a natural sequence for
|
||||
* questions in the generated JavaScript.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function exportQuestionToScorm(
|
||||
$questionId,
|
||||
$jsId
|
||||
) {
|
||||
$question = self::read($questionId);
|
||||
if (!$question) {
|
||||
return '';
|
||||
}
|
||||
$this->iid = $question->iid;
|
||||
$this->js_id = $jsId;
|
||||
$this->type = $question->type;
|
||||
$this->question = $question->question;
|
||||
$this->description = $question->description;
|
||||
$this->weighting = $question->weighting;
|
||||
$this->position = $question->position;
|
||||
$this->picture = $question->picture;
|
||||
$assessmentItem = new ScormAssessmentItem($this);
|
||||
|
||||
return $assessmentItem->export();
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the correct answer class and create answer.
|
||||
*/
|
||||
public function setAnswer()
|
||||
{
|
||||
switch ($this->type) {
|
||||
case MCUA:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case MCMA:
|
||||
case GLOBAL_MULTIPLE_ANSWER:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case TF:
|
||||
$this->answer = new ScormAnswerTrueFalse($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case FIB:
|
||||
$this->answer = new ScormAnswerFillInBlanks($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case MATCHING:
|
||||
case MATCHING_DRAGGABLE:
|
||||
case DRAGGABLE:
|
||||
$this->answer = new ScormAnswerMatching($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case ORAL_EXPRESSION:
|
||||
case FREE_ANSWER:
|
||||
$this->answer = new ScormAnswerFree($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case HOT_SPOT:
|
||||
case HOT_SPOT_COMBINATION:
|
||||
$this->answer = new ScormAnswerHotspot($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case MULTIPLE_ANSWER_COMBINATION:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case HOT_SPOT_ORDER:
|
||||
$this->answer = new ScormAnswerHotspot($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case HOT_SPOT_DELINEATION:
|
||||
$this->answer = new ScormAnswerHotspot($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
// not supported
|
||||
case UNIQUE_ANSWER_NO_OPTION:
|
||||
case MULTIPLE_ANSWER_TRUE_FALSE:
|
||||
case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
|
||||
case UNIQUE_ANSWER_IMAGE:
|
||||
case CALCULATED_ANSWER:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
default:
|
||||
$this->answer = new stdClass();
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$html = $this->getQuestionHTML();
|
||||
$js = $this->getQuestionJS();
|
||||
|
||||
if (is_object($this->answer) && $this->answer instanceof Answer) {
|
||||
list($js2, $html2) = $this->answer->export();
|
||||
$js .= $js2;
|
||||
$html .= $html2;
|
||||
} else {
|
||||
throw new \Exception('Question not supported. Exercise: '.$this->selectTitle());
|
||||
}
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML-formatted question.
|
||||
*/
|
||||
public function getQuestionHTML()
|
||||
{
|
||||
$title = $this->selectTitle();
|
||||
$description = $this->selectDescription();
|
||||
$cols = 2;
|
||||
|
||||
return '<tr>
|
||||
<td colspan="'.$cols.'" id="question_'.$this->iid.'_title" valign="middle" style="background-color:#d6d6d6;">
|
||||
'.$title.'
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" colspan="'.$cols.'">
|
||||
<i>'.$description.'</i>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JavaScript code bound to the question.
|
||||
*/
|
||||
public function getQuestionJS()
|
||||
{
|
||||
$weight = $this->selectWeighting();
|
||||
$js = '
|
||||
questions.push('.$this->js_id.');
|
||||
$(function() {
|
||||
if (exerciseInfo.randomAnswers == true) {
|
||||
$("#question_'.$this->js_id.'").shuffleRows();
|
||||
}
|
||||
});';
|
||||
$js .= "\n";
|
||||
|
||||
switch ($this->type) {
|
||||
case ORAL_EXPRESSION:
|
||||
/*$script = file_get_contents(api_get_path(LIBRARY_PATH) . 'javascript/rtc/RecordRTC.js');
|
||||
$script .= file_get_contents(api_get_path(LIBRARY_PATH) . 'wami-recorder/recorder.js');
|
||||
$script .= file_get_contents(api_get_path(LIBRARY_PATH) . 'wami-recorder/gui.js');
|
||||
$js .= $script;*/
|
||||
break;
|
||||
case HOT_SPOT:
|
||||
case HOT_SPOT_COMBINATION:
|
||||
//put the max score to 0 to avoid discounting the points of
|
||||
//non-exported quiz types in the SCORM
|
||||
$weight = 0;
|
||||
break;
|
||||
}
|
||||
$js .= 'questions_score_max['.$this->js_id.'] = '.$weight.';';
|
||||
|
||||
return $js;
|
||||
}
|
||||
}
|
||||
25
main/exercise/export/scorm/common.js
Normal file
25
main/exercise/export/scorm/common.js
Normal file
@@ -0,0 +1,25 @@
|
||||
var questions = new Array();
|
||||
var questions_answers = new Array();
|
||||
var questions_answers_correct = new Array();
|
||||
var questions_types = new Array();
|
||||
var questions_score_max = new Array();
|
||||
var questions_answers_ponderation = new Array();
|
||||
|
||||
/**
|
||||
* Adds the event listener
|
||||
*/
|
||||
function addListeners(e) {
|
||||
loadPage();
|
||||
var myButton = document.getElementById('chamilo_scorm_submit');
|
||||
addEvent(myButton, 'click', doQuit, false);
|
||||
addEvent(myButton, 'click', disableButton, false);
|
||||
addEvent(window, 'unload', unloadPage, false);
|
||||
}
|
||||
|
||||
/** Disables the submit button on SCORM result submission **/
|
||||
function disableButton() {
|
||||
var mybtn = document.getElementById('chamilo_scorm_submit');
|
||||
mybtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
addEvent(window,'load', addListeners, false);
|
||||
144
main/exercise/export/scorm/scorm_classes.php
Normal file
144
main/exercise/export/scorm/scorm_classes.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This class handles the SCORM export of free-answer questions.
|
||||
*/
|
||||
class ScormAnswerFree extends Answer
|
||||
{
|
||||
/**
|
||||
* Export the text with missing words.
|
||||
*
|
||||
* As a side effect, it stores two lists in the class :
|
||||
* the missing words and their respective weightings.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
$identifier = 'question_'.$this->questionJSId.'_free';
|
||||
// currently the free answers cannot be displayed, so ignore the textarea
|
||||
$html = '<tr><td colspan="2">';
|
||||
$type = $this->getQuestionType();
|
||||
|
||||
if (ORAL_EXPRESSION == $type) {
|
||||
/*
|
||||
$template = new Template('');
|
||||
$template->assign('directory', '/tmp/');
|
||||
$template->assign('user_id', api_get_user_id());
|
||||
|
||||
$layout = $template->get_template('document/record_audio.tpl');
|
||||
$html .= $template->fetch($layout);*/
|
||||
|
||||
$html = '<tr><td colspan="2">'.get_lang('ThisItemIsNotExportable').'</td></tr>';
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
|
||||
$html .= '<textarea minlength="20" name="'.$identifier.'" id="'.$identifier.'" ></textarea>';
|
||||
$html .= '</td></tr>';
|
||||
$js .= 'questions_answers['.$this->questionJSId.'] = new Array();';
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = "";';
|
||||
$js .= 'questions_types['.$this->questionJSId.'] = \'free\';';
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = "0";';
|
||||
$js .= $jstmpw;
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class handles the SCORM export of hotpot questions.
|
||||
*/
|
||||
class ScormAnswerHotspot extends Answer
|
||||
{
|
||||
/**
|
||||
* Returns the javascript code that goes with HotSpot exercises.
|
||||
*
|
||||
* @return string The JavaScript code
|
||||
*/
|
||||
public function get_js_header()
|
||||
{
|
||||
$header = '<script>';
|
||||
$header .= file_get_contents(api_get_path(SYS_CODE_PATH).'inc/lib/javascript/hotspot/js/hotspot.js');
|
||||
$header .= '</script>';
|
||||
|
||||
if ($this->standalone) {
|
||||
//because this header closes so many times the <script> tag, we have to reopen our own
|
||||
$header .= '<script>';
|
||||
$header .= 'questions_answers['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_answers_correct['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_types['.$this->questionJSId.'] = \'hotspot\';'."\n";
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][1] = 0;'.";\n";
|
||||
$header .= $jstmpw;
|
||||
} else {
|
||||
$header = '';
|
||||
$header .= 'questions_answers['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_answers_correct['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_types['.$this->questionJSId.'] = \'hotspot\';'."\n";
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][1] = 0;'."\n";
|
||||
$header .= $jstmpw;
|
||||
}
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the text with missing words.
|
||||
*
|
||||
* As a side effect, it stores two lists in the class :
|
||||
* the missing words and their respective weightings.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = $this->get_js_header();
|
||||
$html = '<tr><td colspan="2"><table width="100%">';
|
||||
// some javascript must be added for that kind of questions
|
||||
$html .= '';
|
||||
|
||||
// Get the answers, make a list
|
||||
$nbrAnswers = $this->selectNbrAnswers();
|
||||
|
||||
$answerList = '<div
|
||||
style="padding: 10px;
|
||||
margin-left: -8px;
|
||||
border: 1px solid #4271b5;
|
||||
height: 448px;
|
||||
width: 200px;"><b>'.get_lang('HotspotZones').'</b><ol>';
|
||||
for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
|
||||
$answerList .= '<li>'.$this->selectAnswer($answerId).'</li>';
|
||||
}
|
||||
$answerList .= '</ol></div>';
|
||||
$relPath = api_get_path(REL_PATH);
|
||||
$html .= <<<HTML
|
||||
<tr>
|
||||
<td>
|
||||
<div id="hotspot-{$this->questionJSId}"></div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentListener', function () {
|
||||
new HotspotQuestion({
|
||||
questionId: {$this->questionJSId},
|
||||
selector: '#hotspot-{$this->questionJSId}',
|
||||
for: 'user',
|
||||
relPath: '$relPath'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</td>
|
||||
<td>
|
||||
$answerList
|
||||
</td>
|
||||
<tr>
|
||||
HTML;
|
||||
$html .= '</table></td></tr>';
|
||||
|
||||
// currently the free answers cannot be displayed, so ignore the textarea
|
||||
$html = '<tr><td colspan="2">'.get_lang('ThisItemIsNotExportable').'</td></tr>';
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
1462
main/exercise/fill_blanks.class.php
Normal file
1462
main/exercise/fill_blanks.class.php
Normal file
File diff suppressed because it is too large
Load Diff
68
main/exercise/freeanswer.class.php
Normal file
68
main/exercise/freeanswer.class.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* File containing the FreeAnswer class.
|
||||
* This class allows to instantiate an object of type FREE_ANSWER,
|
||||
* extending the class question.
|
||||
*
|
||||
* @author Eric Marguin
|
||||
*/
|
||||
class FreeAnswer extends Question
|
||||
{
|
||||
public $typePicture = 'open_answer.png';
|
||||
public $explanationLangVar = 'FreeAnswer';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = FREE_ANSWER;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$form->addElement('text', 'weighting', get_lang('Weighting'));
|
||||
global $text;
|
||||
// setting the save button here and not in the question class.php
|
||||
$form->addButtonSave($text, 'submitQuestion');
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults(['weighting' => float_format($this->weighting, 1)]);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults(['weighting' => '10']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$this->weighting = $form->getSubmitValue('weighting');
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$score['revised'] = $this->isQuestionWaitingReview($score);
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'" >
|
||||
<tr>
|
||||
<th>'.get_lang('Answer').'</th>
|
||||
</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
278
main/exercise/global_multiple_answer.class.php
Normal file
278
main/exercise/global_multiple_answer.class.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Class GlobalMultipleAnswer.
|
||||
*/
|
||||
class GlobalMultipleAnswer extends Question
|
||||
{
|
||||
public $typePicture = 'mcmagl.png';
|
||||
public $explanationLangVar = 'GlobalMultipleAnswer';
|
||||
|
||||
/**
|
||||
* GlobalMultipleAnswer constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = GLOBAL_MULTIPLE_ANSWER;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$nb_answers = isset($_POST['nb_answers']) ? $_POST['nb_answers'] : 4;
|
||||
$nb_answers += isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0);
|
||||
|
||||
$obj_ex = Session::read('objExercise');
|
||||
|
||||
$form->addHeader(get_lang('Answers'));
|
||||
/* Mise en variable de Affichage "Reponses" et son icone, "N<>", "Vrai", "Reponse" */
|
||||
$html = '<table class="table table-striped table-hover">
|
||||
<tr>
|
||||
<th width="10px">'.get_lang('Number').'</th>
|
||||
<th width="10px">'.get_lang('True').'</th>
|
||||
<th width="50%">'.get_lang('Answer').'</th>
|
||||
<th width="50%">'.get_lang('Comment').'</th>
|
||||
</tr>
|
||||
';
|
||||
$form->addHtml($html);
|
||||
|
||||
$defaults = [];
|
||||
$correct = 0;
|
||||
$answer = false;
|
||||
if (!empty($this->iid)) {
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) {
|
||||
$nb_answers = $answer->nbrAnswers;
|
||||
}
|
||||
}
|
||||
|
||||
// le nombre de r<>ponses est bien enregistr<74> sous la forme int(nb)
|
||||
/* Ajout mise en forme nb reponse */
|
||||
$form->addElement('hidden', 'nb_answers');
|
||||
$boxes_names = [];
|
||||
|
||||
if ($nb_answers < 1) {
|
||||
$nb_answers = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'), 'normal');
|
||||
}
|
||||
|
||||
//D<>but affichage score global dans la modification d'une question
|
||||
$scoreA = 0; //par reponse
|
||||
$scoreG = 0; //Global
|
||||
|
||||
/* boucle pour sauvegarder les donn<6E>es dans le tableau defaults */
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
/* si la reponse est de type objet */
|
||||
if (is_object($answer)) {
|
||||
$defaults['answer['.$i.']'] = $answer->answer[$i];
|
||||
$defaults['comment['.$i.']'] = $answer->comment[$i];
|
||||
$defaults['correct['.$i.']'] = $answer->correct[$i];
|
||||
|
||||
// start
|
||||
$scoreA = $answer->weighting[$i];
|
||||
}
|
||||
if ($scoreA > 0) {
|
||||
$scoreG = $scoreG + $scoreA;
|
||||
}
|
||||
//------------- Fin
|
||||
//------------- Debut si un des scores par reponse est egal <20> 0 : la coche vaut 1 (coch<63>)
|
||||
if ($scoreA == 0) {
|
||||
$defaults['pts'] = 1;
|
||||
}
|
||||
|
||||
$renderer = &$form->defaultRenderer();
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'correct['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'counter['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'answer['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'comment['.$i.']'
|
||||
);
|
||||
|
||||
$answer_number = $form->addElement(
|
||||
'text',
|
||||
'counter['.$i.']',
|
||||
null,
|
||||
'value="'.$i.'"'
|
||||
);
|
||||
$answer_number->freeze();
|
||||
|
||||
$form->addElement('checkbox', 'correct['.$i.']', null, null, 'class="checkbox"');
|
||||
$boxes_names[] = 'correct['.$i.']';
|
||||
|
||||
$form->addHtmlEditor(
|
||||
"answer[$i]",
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
[
|
||||
'ToolbarSet' => 'TestProposedAnswer',
|
||||
'Width' => '100%',
|
||||
'Height' => '100',
|
||||
]
|
||||
);
|
||||
$form->addHtmlEditor(
|
||||
"comment[$i]",
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
[
|
||||
'ToolbarSet' => 'TestProposedAnswer',
|
||||
'Width' => '100%',
|
||||
'Height' => '100',
|
||||
]
|
||||
);
|
||||
|
||||
$form->addElement('html', '</tr>');
|
||||
}
|
||||
//--------- Mise en variable du score global lors d'une modification de la question/r<>ponse
|
||||
$defaults['weighting[1]'] = (round($scoreG));
|
||||
$form->addElement('html', '</div></div></table>');
|
||||
$form->add_multiple_required_rule(
|
||||
$boxes_names,
|
||||
get_lang('ChooseAtLeastOneCheckbox'),
|
||||
'multiple_required'
|
||||
);
|
||||
|
||||
//only 1 answer the all deal ...
|
||||
$form->addElement('text', 'weighting[1]', get_lang('Score'));
|
||||
|
||||
//--------- Creation coche pour ne pas prendre en compte les n<>gatifs
|
||||
$form->addElement('checkbox', 'pts', '', get_lang('NoNegativeScore'));
|
||||
$form->addElement('html', '<br />');
|
||||
|
||||
// Affiche un message si le score n'est pas renseign<67>
|
||||
$form->addRule('weighting[1]', get_lang('ThisFieldIsRequired'), 'required');
|
||||
|
||||
global $text;
|
||||
|
||||
if ($obj_ex->edit_exercise_in_lp ||
|
||||
(empty($this->exerciseList) && empty($obj_ex->iid))
|
||||
) {
|
||||
// setting the save button here and not in the question class.php
|
||||
$form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers');
|
||||
$form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers');
|
||||
$form->addButtonSave($text, 'submitQuestion');
|
||||
}
|
||||
|
||||
$renderer->setElementTemplate('{element} ', 'lessAnswers');
|
||||
$renderer->setElementTemplate('{element} ', 'submitQuestion');
|
||||
$renderer->setElementTemplate('{element}', 'moreAnswers');
|
||||
|
||||
$form->addElement('html', '</div></div>');
|
||||
|
||||
$defaults['correct'] = $correct;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
$form->setConstants(['nb_answers' => $nb_answers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$objAnswer = new Answer($this->iid);
|
||||
$nb_answers = $form->getSubmitValue('nb_answers');
|
||||
|
||||
// Score total
|
||||
$answer_score = trim($form->getSubmitValue('weighting[1]'));
|
||||
|
||||
// Reponses correctes
|
||||
$nbr_corrects = 0;
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$goodAnswer = trim($form->getSubmitValue('correct['.$i.']'));
|
||||
if ($goodAnswer) {
|
||||
$nbr_corrects++;
|
||||
}
|
||||
}
|
||||
// Set question weighting (score total)
|
||||
$questionWeighting = $answer_score;
|
||||
|
||||
// Set score per answer
|
||||
$nbr_corrects = $nbr_corrects == 0 ? 1 : $nbr_corrects;
|
||||
$answer_score = $nbr_corrects == 0 ? 0 : $answer_score;
|
||||
|
||||
$answer_score = $answer_score / $nbr_corrects;
|
||||
|
||||
//$answer_score <20>quivaut <20> la valeur d'une bonne r<>ponse
|
||||
// cr<63>ation variable pour r<>cuperer la valeur de la coche pour la prise en compte des n<>gatifs
|
||||
$test = $form->getSubmitValue('pts');
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$answer = trim($form->getSubmitValue('answer['.$i.']'));
|
||||
$comment = trim($form->getSubmitValue('comment['.$i.']'));
|
||||
$goodAnswer = trim($form->getSubmitValue('correct['.$i.']'));
|
||||
|
||||
if ($goodAnswer) {
|
||||
$weighting = abs($answer_score);
|
||||
} else {
|
||||
if ($test == 1) {
|
||||
$weighting = 0;
|
||||
} else {
|
||||
$weighting = -abs($answer_score);
|
||||
}
|
||||
}
|
||||
|
||||
$objAnswer->createAnswer($answer, $goodAnswer, $comment, $weighting, $i);
|
||||
}
|
||||
// saves the answers into the data base
|
||||
$objAnswer->save();
|
||||
|
||||
// sets the total weighting of the question --> sert <20> donner le score total pendant l'examen
|
||||
$this->updateWeighting($questionWeighting);
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'"><tr>';
|
||||
|
||||
if (!in_array($exercise->results_disabled, [RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER])) {
|
||||
$header .= '<th>'.get_lang('Choice').'</th>';
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
}
|
||||
|
||||
$header .= '<th>'.get_lang('Answer').'</th>';
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
}
|
||||
if (false === $exercise->hideComment) {
|
||||
$header .= '<th>'.get_lang('Comment').'</th>';
|
||||
}
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
526
main/exercise/hotpotatoes.lib.php
Normal file
526
main/exercise/hotpotatoes.lib.php
Normal file
@@ -0,0 +1,526 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Code library for HotPotatoes integration.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Istvan Mandak (original author)
|
||||
*/
|
||||
|
||||
/* TODO: This is a global variable with too simple name, conflicts are possible.
|
||||
Better eliminate it. Correct the test unit too. */
|
||||
$dbTable = Database::get_course_table(TABLE_DOCUMENT);
|
||||
|
||||
/**
|
||||
* Creates a hotpotato directory.
|
||||
*
|
||||
* If a directory of that name already exists, don't create any.
|
||||
* If a file of that name exists, remove it and create a directory.
|
||||
*
|
||||
* @param string $base_work_dir Wanted path
|
||||
*
|
||||
* @return bool Always true so far
|
||||
*/
|
||||
function hotpotatoes_init($base_work_dir)
|
||||
{
|
||||
//global $_course, $_user;
|
||||
$document_path = $base_work_dir.'/';
|
||||
if (!is_dir($document_path)) {
|
||||
if (is_file($document_path)) {
|
||||
@unlink($document_path);
|
||||
}
|
||||
@mkdir($document_path, api_get_permissions_for_new_directories());
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
//why create a .htaccess here?
|
||||
//if (!is_file($document_path.".htacces"))
|
||||
//{
|
||||
// if (!($fp = fopen($document_path.".htaccess", "w"))) {
|
||||
// }
|
||||
// $str = "order deny,allow\nallow from all";
|
||||
// if (!fwrite($fp,$str)) { }
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of the quiz file given as parameter.
|
||||
*
|
||||
* @param string $fname File name
|
||||
* @param string $fpath File path
|
||||
*
|
||||
* @return string The exercise title
|
||||
*/
|
||||
function GetQuizName($fname, $fpath)
|
||||
{
|
||||
$title = GetComment($fname);
|
||||
if (trim($title) == '') {
|
||||
if (file_exists($fpath.$fname)) {
|
||||
if (!($fp = @fopen($fpath.$fname, 'r'))) {
|
||||
//die('Could not open Quiz input.');
|
||||
return basename($fname);
|
||||
}
|
||||
|
||||
$contents = @fread($fp, filesize($fpath.$fname));
|
||||
@fclose($fp);
|
||||
|
||||
$title = api_get_title_html($contents);
|
||||
}
|
||||
}
|
||||
if ($title == '') {
|
||||
$title = basename($fname);
|
||||
}
|
||||
|
||||
return (string) $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment about a file from the corresponding database record.
|
||||
*
|
||||
* @param string $path File path
|
||||
* @param string $courseCode (if not given, the course will be taken from the context)
|
||||
*
|
||||
* @return string comment from the database record
|
||||
* Added conditional to the table if is empty
|
||||
*/
|
||||
function GetComment($path, $courseCode = '')
|
||||
{
|
||||
$dbTable = Database::get_course_table(TABLE_DOCUMENT);
|
||||
$course_info = api_get_course_info($courseCode);
|
||||
$path = Database::escape_string($path);
|
||||
if (!empty($course_info) && !empty($path)) {
|
||||
$query = "SELECT comment FROM $dbTable WHERE c_id = {$course_info['real_id']}";
|
||||
$result = Database::query($query);
|
||||
while ($row = Database::fetch_array($result)) {
|
||||
return $row[0];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comment in the database for a particular path.
|
||||
*
|
||||
* @param string $path File path
|
||||
* @param string $comment Comment to set
|
||||
*
|
||||
* @return Doctrine\DBAL\Driver\Statement|null
|
||||
* Result of the database operation
|
||||
* (Database::query will output some message directly on error anyway)
|
||||
*/
|
||||
function SetComment($path, $comment)
|
||||
{
|
||||
$dbTable = Database::get_course_table(TABLE_DOCUMENT);
|
||||
$path = Database::escape_string($path);
|
||||
$comment = Database::escape_string($comment);
|
||||
$course_id = api_get_course_int_id();
|
||||
$query = "UPDATE $dbTable SET comment = '$comment'
|
||||
WHERE $course_id AND path = '$path'";
|
||||
$result = Database::query($query);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the file contents into a string.
|
||||
*
|
||||
* @param string $full_file_path Urlencoded path
|
||||
*
|
||||
* @return string The file contents or false on security error
|
||||
*/
|
||||
function ReadFileCont($full_file_path)
|
||||
{
|
||||
if (empty($full_file_path)) {
|
||||
return false;
|
||||
}
|
||||
if (Security::check_abs_path(dirname($full_file_path).'/', api_get_path(SYS_COURSE_PATH))) {
|
||||
if (is_file($full_file_path)) {
|
||||
if (!($fp = fopen(urldecode($full_file_path), 'r'))) {
|
||||
return '';
|
||||
}
|
||||
$contents = fread($fp, filesize($full_file_path));
|
||||
fclose($fp);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the file contents into the given file path.
|
||||
*
|
||||
* @param string $full_file_path Urlencoded path
|
||||
* @param string $content The file contents
|
||||
*
|
||||
* @return bool True on success, false on security error
|
||||
*/
|
||||
function WriteFileCont($full_file_path, $content)
|
||||
{
|
||||
// Check if this is not an attack, trying to get into other directories or something like that.
|
||||
$_course = api_get_course_info();
|
||||
if (Security::check_abs_path(
|
||||
dirname($full_file_path).'/',
|
||||
api_get_path(SYS_COURSE_PATH).$_course['path'].'/'
|
||||
)) {
|
||||
// Check if this is not an attack, trying to upload a php file or something like that.
|
||||
if (basename($full_file_path) != Security::filter_filename(basename($full_file_path))) {
|
||||
return false;
|
||||
}
|
||||
//if (!($fp = fopen(urldecode($full_file_path), 'w'))) {
|
||||
//die('Could not open Quiz input.');
|
||||
//}
|
||||
$fp = fopen(urldecode($full_file_path), 'w');
|
||||
if ($fp !== false) {
|
||||
fwrite($fp, $content);
|
||||
fclose($fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of an img whose path is given (without directories or extensions).
|
||||
*
|
||||
* @param string $imageTag An image tag (<img src="...." ...>)
|
||||
*
|
||||
* @return string The image file name or an empty string
|
||||
*/
|
||||
function GetImgName($imageTag)
|
||||
{
|
||||
// Select src tag from img tag.
|
||||
$match = [];
|
||||
//preg_match('/(src=(["\'])1.*(["\'])1)/i', $imageTag, $match); //src
|
||||
preg_match('/src(\s)*=(\s)*[\'"]([^\'"]*)[\'"]/i', $imageTag, $match); //get the img src as contained between " or '
|
||||
//list($key, $srctag) = each($match);
|
||||
$src = $match[3];
|
||||
//$src = substr($srctag, 5, (strlen($srctag) - 7));
|
||||
if (stristr($src, 'http') === false) {
|
||||
// Valid or invalid image name.
|
||||
if ($src == '') {
|
||||
return '';
|
||||
} else {
|
||||
$tmp_src = basename($src);
|
||||
if ($tmp_src == '') {
|
||||
return $src;
|
||||
} else {
|
||||
return $tmp_src;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The img tag contained "http", which means it is probably external. Ignore it.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source path of an image tag.
|
||||
*
|
||||
* @param string $imageTag An image tag
|
||||
*
|
||||
* @return string The image source or ""
|
||||
*/
|
||||
function GetSrcName($imageTag)
|
||||
{
|
||||
// Select src tag from img tag.
|
||||
$match = [];
|
||||
preg_match("|(src=\".*\" )|U", $imageTag, $match); //src
|
||||
$srctag = reset($match);
|
||||
$src = substr($srctag, 5, (strlen($srctag) - 7));
|
||||
if (stristr($src, 'http') === false) {
|
||||
// valid or invalid image name
|
||||
return $src;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image parameters from an image path.
|
||||
*
|
||||
* @param string $fname File name
|
||||
* @param string $fpath File path
|
||||
* @param array $imgparams Reference to a list of image parameters (emptied, then used to return results)
|
||||
* @param int $imgcount Reference to a counter of images (emptied, then used to return results)
|
||||
*/
|
||||
function GetImgParams($fname, $fpath, &$imgparams, &$imgcount)
|
||||
{
|
||||
// Select img tags from context.
|
||||
$imgparams = [];
|
||||
//phpinfo();
|
||||
$contents = ReadFileCont("$fpath"."$fname");
|
||||
$matches = [];
|
||||
preg_match_all('(<img .*>)', $contents, $matches);
|
||||
$imgcount = 0;
|
||||
foreach ($matches as $match) {
|
||||
// Each match consists of a key and a value.
|
||||
foreach ($match as $imageTag) {
|
||||
$imgname = GetImgName($imageTag);
|
||||
if ($imgname != '' && !in_array($imgname, $imgparams)) {
|
||||
array_push($imgparams, $imgname); // name (+ type) of the images in the html test
|
||||
$imgcount = $imgcount + 1; // number of images in the html test
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of hidden fields with the image params given as parameter to this function.
|
||||
*
|
||||
* @param array $imgparams List of image parameters
|
||||
*
|
||||
* @return string String containing the hidden parameters built from the list given
|
||||
*/
|
||||
function GenerateHiddenList($imgparams)
|
||||
{
|
||||
$list = '';
|
||||
if (is_array($imgparams)) {
|
||||
foreach ($imgparams as $string) {
|
||||
$list .= "<input type=\"hidden\" name=\"imgparams[]\" value=\"$string\" />\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a node in the given array.
|
||||
*
|
||||
* @param array $array Reference to the array to search
|
||||
* @param string $node Node we are looking for in the array
|
||||
*
|
||||
* @return mixed Node name or false if not found
|
||||
*/
|
||||
function myarraysearch(&$array, $node)
|
||||
{
|
||||
$match = false;
|
||||
$tmp_array = [];
|
||||
for ($i = 0; $i < count($array); $i++) {
|
||||
if (!strcmp($array[$i], $node)) {
|
||||
$match = $node;
|
||||
} else {
|
||||
array_push($tmp_array, $array[$i]);
|
||||
}
|
||||
}
|
||||
$array = $tmp_array;
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches an image name into an array.
|
||||
*
|
||||
* @param array $imgparams Reference to an array to search
|
||||
* @param string $string String to look for
|
||||
*
|
||||
* @return mixed String given if found, false otherwise
|
||||
*
|
||||
* @uses \myarraysearch This function is just an additional layer on the myarraysearch() function
|
||||
*/
|
||||
function CheckImageName(&$imgparams, $string)
|
||||
{
|
||||
$checked = myarraysearch($imgparams, $string);
|
||||
|
||||
return $checked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an image tag by ???
|
||||
*
|
||||
* @param string $content The content to replace
|
||||
*
|
||||
* @return string The modified content
|
||||
*/
|
||||
function ReplaceImgTag($content)
|
||||
{
|
||||
$newcontent = $content;
|
||||
$matches = [];
|
||||
preg_match_all('(<img .*>)', $content, $matches);
|
||||
foreach ($matches as $match) {
|
||||
foreach ($match as $imageTag) {
|
||||
$imgname = GetSrcName($imageTag);
|
||||
if ($imgname != '') {
|
||||
$prehref = $imgname;
|
||||
$posthref = basename($imgname);
|
||||
$newcontent = str_replace($prehref, $posthref, $newcontent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $newcontent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the folder name up to a certain length with "0".
|
||||
*
|
||||
* @param string $name Original folder name
|
||||
* @param int $nsize Length to reach
|
||||
*
|
||||
* @return string Modified folder name
|
||||
*/
|
||||
function FillFolderName($name, $nsize)
|
||||
{
|
||||
$str = '';
|
||||
for ($i = 0; $i < $nsize - strlen($name); $i++) {
|
||||
$str .= '0';
|
||||
}
|
||||
$str .= $name;
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the HotPotato folder tree.
|
||||
*
|
||||
* @param string $folder Folder path
|
||||
*
|
||||
* @return string Folder name (modified)
|
||||
*/
|
||||
function GenerateHpFolder($folder)
|
||||
{
|
||||
$filelist = [];
|
||||
if ($dir = @opendir($folder)) {
|
||||
while (($file = readdir($dir)) !== false) {
|
||||
if ($file != '.') {
|
||||
if ($file != '..') {
|
||||
$full_name = $folder.'/'.$file;
|
||||
if (is_dir($full_name)) {
|
||||
$filelist[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$name = '';
|
||||
$w = true;
|
||||
while ($w == true) {
|
||||
$name = FillFolderName(mt_rand(1, 99999), 6);
|
||||
$checked = myarraysearch($filelist, $name);
|
||||
// As long as we find the name in the array, continue looping. As soon as we have a new element, quit.
|
||||
if (!$checked) {
|
||||
$w = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder name (strips down path).
|
||||
*
|
||||
* @param string $fname Path
|
||||
*
|
||||
* @return string Folder name stripped down
|
||||
*/
|
||||
function GetFolderName($fname)
|
||||
{
|
||||
$name = explode('/', $fname);
|
||||
$name = $name[count($name) - 2];
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder path (with out the name of the folder itself) ?
|
||||
*
|
||||
* @param string $fname Path
|
||||
*
|
||||
* @return string Path stripped down
|
||||
*/
|
||||
function GetFolderPath($fname)
|
||||
{
|
||||
$str = '';
|
||||
$name = explode('/', $fname);
|
||||
for ($i = 0; $i < sizeof($name) - 1; $i++) {
|
||||
$str = $str.$name[$i].'/';
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are subfolders.
|
||||
*
|
||||
* @param string $path Path
|
||||
*
|
||||
* @return int 1 if a subfolder was found, 0 otherwise
|
||||
*/
|
||||
function CheckSubFolder($path)
|
||||
{
|
||||
$folder = GetFolderPath($path);
|
||||
$dflag = 0;
|
||||
if ($dir = @opendir($folder)) {
|
||||
while (($file = readdir($dir)) !== false) {
|
||||
if ($file != '.') {
|
||||
if ($file != '..') {
|
||||
$full_name = $folder.'/'.$file;
|
||||
if (is_dir($full_name)) {
|
||||
$dflag = 1; // first directory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dflag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hotpotato Garbage Collector.
|
||||
*
|
||||
* @param string $folder Path
|
||||
* @param int $flag Flag
|
||||
* @param int $user_id User id
|
||||
*/
|
||||
function HotPotGCt($folder, $flag, $user_id)
|
||||
{
|
||||
// Garbage Collector
|
||||
$filelist = [];
|
||||
if ($dir = @opendir($folder)) {
|
||||
while (($file = readdir($dir)) !== false) {
|
||||
if ($file != '.') {
|
||||
if ($file != '..') {
|
||||
$full_name = $folder.'/'.$file;
|
||||
if (is_dir($full_name)) {
|
||||
HotPotGCt($folder.'/'.$file, $flag, $user_id);
|
||||
} else {
|
||||
$filelist[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
|
||||
foreach ($filelist as $val) {
|
||||
if (stristr($val, $user_id.'.t.html')) {
|
||||
if ($flag == 1) {
|
||||
my_delete($folder.'/'.$val);
|
||||
} else {
|
||||
echo $folder.'/'.$val.'<br />';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an attempt from TABLE_STATISTIC_TRACK_E_HOTPOTATOES.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
function deleteAttempt($id)
|
||||
{
|
||||
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
|
||||
$id = intval($id);
|
||||
$sql = "DELETE FROM $table WHERE id = $id";
|
||||
Database::query($sql);
|
||||
}
|
||||
262
main/exercise/hotpotatoes.php
Normal file
262
main/exercise/hotpotatoes.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Code for HotPotatoes integration.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Istvan Mandak (original author)
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
require_once 'hotpotatoes.lib.php';
|
||||
|
||||
// Section (for the tabs).
|
||||
$this_section = SECTION_COURSES;
|
||||
$_course = api_get_course_info();
|
||||
|
||||
// Access restriction: only teachers are allowed here.
|
||||
if (!api_is_allowed_to_edit(null, true)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
if (api_is_in_gradebook()) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => Category::getUrl(),
|
||||
'name' => get_lang('ToolGradebook'),
|
||||
];
|
||||
}
|
||||
// The breadcrumbs.
|
||||
$interbreadcrumb[] = [
|
||||
'url' => api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
|
||||
// Database table definitions.
|
||||
$dbTable = Database::get_course_table(TABLE_DOCUMENT);
|
||||
$course_id = $_course['real_id'];
|
||||
$sessionId = api_get_session_id();
|
||||
|
||||
// Setting some variables.
|
||||
$document_sys_path = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
$uploadPath = '/HotPotatoes_files';
|
||||
$finish = (!empty($_POST['finish']) ? $_POST['finish'] : 0);
|
||||
$imgcount = (!empty($_POST['imgcount']) ? $_POST['imgcount'] : null);
|
||||
$fld = (!empty($_POST['fld']) ? $_POST['fld'] : null);
|
||||
$imgparams = [];
|
||||
$dialogBox = '';
|
||||
|
||||
if ($finish == 2 && isset($_POST['imgparams'])) {
|
||||
$imgparams = $_POST['imgparams'];
|
||||
}
|
||||
|
||||
// If user is allowed to edit...
|
||||
if (api_is_allowed_to_edit(null, true)) {
|
||||
if (hotpotatoes_init($document_sys_path.$uploadPath)) {
|
||||
// If the directory doesn't exist, create the "HotPotatoes" directory.
|
||||
$doc_id = add_document(
|
||||
$_course,
|
||||
'/HotPotatoes_files',
|
||||
'folder',
|
||||
0,
|
||||
get_lang('HotPotatoesFiles')
|
||||
);
|
||||
// Update properties in dbase (in any case).
|
||||
api_item_property_update(
|
||||
$_course,
|
||||
TOOL_DOCUMENT,
|
||||
$doc_id,
|
||||
'FolderCreated',
|
||||
api_get_user_id()
|
||||
);
|
||||
// Make invisible (in any case) - why?
|
||||
api_item_property_update(
|
||||
$_course,
|
||||
TOOL_DOCUMENT,
|
||||
$doc_id,
|
||||
'invisible',
|
||||
api_get_user_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Display */
|
||||
$nameTools = get_lang('HotPotatoesTests');
|
||||
|
||||
$form = new FormValidator(
|
||||
'hotpotatoes',
|
||||
'post',
|
||||
api_get_self()."?".api_get_cidreq(),
|
||||
null,
|
||||
['enctype' => 'multipart/form-data']
|
||||
);
|
||||
$form->addElement('header', $nameTools);
|
||||
$form->addElement('hidden', 'uploadPath');
|
||||
$form->addElement('hidden', 'fld', $fld);
|
||||
$form->addElement('hidden', 'imgcount', $imgcount);
|
||||
$form->addElement('hidden', 'finish', $finish);
|
||||
$form->addElement('html', GenerateHiddenList($imgparams));
|
||||
$form->addElement('label', '', Display::return_icon('hotpotatoes.jpg', 'HotPotatoes'));
|
||||
$label = get_lang('DownloadImg').' : ';
|
||||
if ($finish == 0) {
|
||||
$label = get_lang('DownloadFile').' : ';
|
||||
}
|
||||
|
||||
$form->addElement('file', 'userFile', $label);
|
||||
$form->addButtonSend(get_lang('SendFile'));
|
||||
|
||||
// If finish is set; it's because the user came from this script in the first place (displaying hidden "finish" field).
|
||||
if ((api_is_allowed_to_edit(null, true)) && (($finish == 0) || ($finish == 2))) {
|
||||
// Moved this down here as the upload handling functions give output.
|
||||
if ($form->validate()) {
|
||||
// Initialise $finish
|
||||
if (!isset($finish)) {
|
||||
$finish = 0;
|
||||
}
|
||||
|
||||
//if the size is not defined, it's probably because there has been an error or no file was submitted
|
||||
if (!$_FILES['userFile']['size']) {
|
||||
$dialogBox .= get_lang('SendFileError').'<br />'.get_lang('Notice').' : '.get_lang('MaxFileSize').' '.ini_get('upload_max_filesize');
|
||||
} else {
|
||||
$unzip = 0;
|
||||
if (preg_match('/\.zip$/i', $_FILES['userFile']['name'])) {
|
||||
//if it's a zip, allow zip upload
|
||||
$unzip = 1;
|
||||
}
|
||||
|
||||
$filename = api_replace_dangerous_char(trim($_FILES['userFile']['name']));
|
||||
|
||||
if ($finish == 0) {
|
||||
// Generate new test folder if on first step of file upload.
|
||||
$fld = GenerateHpFolder($document_sys_path.$uploadPath.'/');
|
||||
@mkdir($document_sys_path.$uploadPath.'/'.$fld, api_get_permissions_for_new_directories());
|
||||
$doc_id = add_document($_course, '/HotPotatoes_files/'.$fld, 'folder', 0, $fld);
|
||||
api_item_property_update(
|
||||
$_course,
|
||||
TOOL_DOCUMENT,
|
||||
$doc_id,
|
||||
'FolderCreated',
|
||||
api_get_user_id()
|
||||
);
|
||||
}
|
||||
|
||||
$allow_output_on_success = false;
|
||||
if (handle_uploaded_document(
|
||||
$_course,
|
||||
$_FILES['userFile'],
|
||||
$document_sys_path,
|
||||
$uploadPath.'/'.$fld,
|
||||
api_get_user_id(),
|
||||
null,
|
||||
null,
|
||||
$unzip,
|
||||
'',
|
||||
$allow_output_on_success
|
||||
)) {
|
||||
if ($finish == 2) {
|
||||
$imgparams = $_POST['imgparams'];
|
||||
$checked = CheckImageName($imgparams, $filename);
|
||||
if ($checked) {
|
||||
$imgcount = $imgcount - 1;
|
||||
} else {
|
||||
$dialogBox .= $filename.' '.get_lang('NameNotEqual');
|
||||
my_delete($document_sys_path.$uploadPath.'/'.$fld.'/'.$filename);
|
||||
DocumentManager::updateDbInfo('delete', $uploadPath.'/'.$fld.'/'.$filename);
|
||||
}
|
||||
if ($imgcount == 0) { // all image uploaded
|
||||
$finish = 1;
|
||||
}
|
||||
} else {
|
||||
// If we are (still) on the first step of the upload process.
|
||||
if ($finish == 0) {
|
||||
$finish = 2;
|
||||
// Get number and name of images from the files contents.
|
||||
GetImgParams('/'.$filename, $document_sys_path.$uploadPath.'/'.$fld, $imgparams, $imgcount);
|
||||
if ($imgcount == 0) {
|
||||
// There is no img link, so finish the upload process.
|
||||
$finish = 1;
|
||||
} else {
|
||||
// There is still one or more img missing.
|
||||
$dialogBox .= get_lang('DownloadEnd');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the HotPotatoes title in the document record's "comment" field
|
||||
$title = @htmlspecialchars(
|
||||
GetQuizName(
|
||||
$filename,
|
||||
$document_sys_path.$uploadPath.'/'.$fld.'/'
|
||||
),
|
||||
ENT_COMPAT,
|
||||
api_get_system_encoding()
|
||||
);
|
||||
$sessionAnd = ' AND session_id IS NULL ';
|
||||
if ($sessionId) {
|
||||
$sessionAnd = " AND session_id = $sessionId ";
|
||||
}
|
||||
$path = $uploadPath.'/'.$fld.'/'.$filename;
|
||||
// Find the proper record
|
||||
$select = "SELECT iid FROM $dbTable
|
||||
WHERE c_id = $course_id
|
||||
$sessionAnd
|
||||
AND path = '".Database::escape_string($path)."'";
|
||||
$query = Database::query($select);
|
||||
if (Database::num_rows($query)) {
|
||||
$row = Database::fetch_array($query);
|
||||
// Update the record with the 'comment' (HP title)
|
||||
Database::update(
|
||||
$dbTable,
|
||||
['comment' => $title],
|
||||
['iid = ?' => $row['iid']]
|
||||
);
|
||||
// Mark the addition of the HP quiz in the item_property table
|
||||
api_item_property_update(
|
||||
$_course,
|
||||
TOOL_QUIZ,
|
||||
$row['iid'],
|
||||
'QuizAdded',
|
||||
api_get_user_id()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$finish = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($finish == 1) {
|
||||
/** ok -> send to main exercises page */
|
||||
header('Location: exercise.php?'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
Display::display_header($nameTools, 'Exercise');
|
||||
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="exercise.php?show=test">'.
|
||||
Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).
|
||||
'</a>';
|
||||
echo '</div>';
|
||||
|
||||
if ($finish == 2) {
|
||||
// If we are in the img upload process.
|
||||
$dialogBox .= get_lang('ImgNote_st').$imgcount.get_lang('ImgNote_en').'<br />';
|
||||
foreach ($imgparams as $key => $string) {
|
||||
$dialogBox .= $string.'; ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($dialogBox) {
|
||||
echo Display::return_message($dialogBox, 'normal', false);
|
||||
}
|
||||
|
||||
$form->display();
|
||||
}
|
||||
// Display the footer.
|
||||
Display::display_footer();
|
||||
328
main/exercise/hotpotatoes_exercise_report.php
Normal file
328
main/exercise/hotpotatoes_exercise_report.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Exercise list: This script shows the list of exercises for administrators and students.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author hubert.borderiou
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
// Setting the tabs
|
||||
$this_section = SECTION_COURSES;
|
||||
$htmlHeadXtra[] = api_get_jqgrid_js();
|
||||
$_course = api_get_course_info();
|
||||
|
||||
// Access control
|
||||
api_protect_course_script(true, false, true);
|
||||
|
||||
// including additional libraries
|
||||
require_once 'hotpotatoes.lib.php';
|
||||
|
||||
// document path
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path']."/document";
|
||||
|
||||
/* Constants and variables */
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true) || api_is_drh();
|
||||
$is_tutor = api_is_allowed_to_edit(true);
|
||||
|
||||
$TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
|
||||
$TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
$TBL_TRACK_HOTPOTATOES_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
|
||||
$TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
|
||||
|
||||
$course_id = api_get_course_int_id();
|
||||
$hotpotatoes_path = isset($_REQUEST['path']) ? Security::remove_XSS($_REQUEST['path']) : null;
|
||||
$filter_user = isset($_REQUEST['filter_by_user']) ? intval($_REQUEST['filter_by_user']) : null;
|
||||
|
||||
if (empty($hotpotatoes_path)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
if (!$is_allowedToEdit) {
|
||||
// api_not_allowed();
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['path'])) {
|
||||
$parameters['path'] = Security::remove_XSS($_REQUEST['path']);
|
||||
}
|
||||
|
||||
$origin = isset($origin) ? $origin : null;
|
||||
|
||||
if (!empty($_REQUEST['export_report']) && $_REQUEST['export_report'] == '1') {
|
||||
if (api_is_platform_admin() || api_is_course_admin() || api_is_course_tutor() || api_is_session_general_coach()) {
|
||||
$load_extra_data = false;
|
||||
if (isset($_REQUEST['extra_data']) && $_REQUEST['extra_data'] == 1) {
|
||||
$load_extra_data = true;
|
||||
}
|
||||
|
||||
require_once 'hotpotatoes_exercise_result.class.php';
|
||||
$export = new HotpotatoesExerciseResult();
|
||||
$export->exportCompleteReportCSV($documentPath, $hotpotatoes_path);
|
||||
exit;
|
||||
} else {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
}
|
||||
$actions = null;
|
||||
if ($is_allowedToEdit && $origin != 'learnpath') {
|
||||
// the form
|
||||
if (api_is_platform_admin() || api_is_course_admin() || api_is_course_tutor() || api_is_session_general_coach()) {
|
||||
$actions .= '<a id="export_opener" href="'.api_get_self().'?export_report=1&path='.$hotpotatoes_path.' ">'.
|
||||
Display::return_icon('save.png', get_lang('Export'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
}
|
||||
} else {
|
||||
$actions .= '<a href="exercise.php">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
}
|
||||
|
||||
if ($is_allowedToEdit) {
|
||||
$action = isset($_GET['action']) ? $_GET['action'] : null;
|
||||
switch ($action) {
|
||||
case 'delete':
|
||||
$fileToDelete = isset($_GET['id']) ? $_GET['id'] : null;
|
||||
deleteAttempt($fileToDelete);
|
||||
Display::addFlash(Display::return_message(get_lang('ItemDeleted')));
|
||||
$url = api_get_self().'?'.api_get_cidreq().'&path='.$hotpotatoes_path;
|
||||
header("Location: $url");
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$nameTools = get_lang('Results');
|
||||
|
||||
if ($is_allowedToEdit || $is_tutor) {
|
||||
$nameTools = get_lang('StudentScore');
|
||||
$interbreadcrumb[] = ["url" => "exercise.php?".api_get_cidreq(), "name" => get_lang('Exercises')];
|
||||
$objExerciseTmp = new Exercise();
|
||||
} else {
|
||||
$interbreadcrumb[] = ["url" => "exercise.php?".api_get_cidreq(), "name" => get_lang('Exercises')];
|
||||
$objExerciseTmp = new Exercise();
|
||||
}
|
||||
|
||||
Display::display_header($nameTools);
|
||||
$actions = Display::div($actions, ['class' => 'actions']);
|
||||
|
||||
$extra = '<script>
|
||||
$(function() {
|
||||
$( "#dialog:ui-dialog" ).dialog( "destroy" );
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
autoOpen: false,
|
||||
show: "blind",
|
||||
resizable: false,
|
||||
height:300,
|
||||
modal: true
|
||||
});
|
||||
|
||||
$("#export_opener").click(function() {
|
||||
var targetUrl = $(this).attr("href");
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
width:400,
|
||||
height:300,
|
||||
buttons: {
|
||||
"'.addslashes(get_lang('Download')).'": function() {
|
||||
var export_format = $("input[name=export_format]:checked").val();
|
||||
var extra_data = $("input[name=load_extra_data]:checked").val();
|
||||
location.href = targetUrl+"&export_format="+export_format+"&extra_data="+extra_data;
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
});
|
||||
$( "#dialog-confirm" ).dialog("open");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
|
||||
$extra .= '<div id="dialog-confirm" title="'.get_lang("ConfirmYourChoice").'">';
|
||||
$form = new FormValidator('report', 'post', null, null, ['class' => 'form-vertical']);
|
||||
$form->addElement('radio', 'export_format', null, get_lang('ExportAsCSV'), 'csv', ['id' => 'export_format_csv_label']);
|
||||
//$form->addElement('radio', 'export_format', null, get_lang('ExportAsXLS'), 'xls', array('id' => 'export_format_xls_label'));
|
||||
//$form->addElement('checkbox', 'load_extra_data', null, get_lang('LoadExtraData'), '0', array('id' => 'export_format_xls_label'));
|
||||
$form->setDefaults(['export_format' => 'csv']);
|
||||
$extra .= $form->returnForm();
|
||||
$extra .= '</div>';
|
||||
|
||||
if ($is_allowedToEdit) {
|
||||
echo $extra;
|
||||
}
|
||||
|
||||
echo $actions;
|
||||
|
||||
$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_hotpotatoes_exercise_results&path='.$hotpotatoes_path.'&filter_by_user='.$filter_user;
|
||||
$action_links = '';
|
||||
|
||||
// Generating group list
|
||||
|
||||
$group_list = GroupManager::get_group_list();
|
||||
$group_parameters = ['group_all:'.get_lang('All'), 'group_none:'.get_lang('None')];
|
||||
|
||||
foreach ($group_list as $group) {
|
||||
$group_parameters[] = $group['id'].':'.$group['name'];
|
||||
}
|
||||
if (!empty($group_parameters)) {
|
||||
$group_parameters = implode(';', $group_parameters);
|
||||
}
|
||||
|
||||
if ($is_allowedToEdit || $is_tutor) {
|
||||
// The order is important you need to check the the $column variable in the model.ajax.php file
|
||||
$columns = [
|
||||
get_lang('FirstName'),
|
||||
get_lang('LastName'),
|
||||
get_lang('LoginName'),
|
||||
get_lang('Group'),
|
||||
get_lang('StartDate'),
|
||||
get_lang('Score'),
|
||||
get_lang('Actions'),
|
||||
];
|
||||
|
||||
// Column config
|
||||
// @todo fix search firstname/lastname that doesn't work. rmove search for the moment
|
||||
$column_model = [
|
||||
['name' => 'firstname', 'index' => 'firstname', 'width' => '50', 'align' => 'left', 'search' => 'false'],
|
||||
[
|
||||
'name' => 'lastname',
|
||||
'index' => 'lastname',
|
||||
'width' => '50',
|
||||
'align' => 'left',
|
||||
'formatter' => 'action_formatter',
|
||||
'search' => 'false',
|
||||
],
|
||||
[
|
||||
'name' => 'login',
|
||||
'hidden' => 'true',
|
||||
'index' => 'username',
|
||||
'width' => '40',
|
||||
'align' => 'left',
|
||||
'search' => 'false',
|
||||
],
|
||||
['name' => 'group_name', 'index' => 'group_id', 'width' => '40', 'align' => 'left', 'search' => 'false'],
|
||||
['name' => 'exe_date', 'index' => 'exe_date', 'width' => '60', 'align' => 'left', 'search' => 'false'],
|
||||
['name' => 'score', 'index' => 'exe_result', 'width' => '50', 'align' => 'left', 'search' => 'false'],
|
||||
['name' => 'actions', 'index' => 'actions', 'width' => '60', 'align' => 'left', 'search' => 'false'],
|
||||
];
|
||||
|
||||
$action_links = '
|
||||
// add username as title in lastname filed - ref 4226
|
||||
function action_formatter(cellvalue, options, rowObject) {
|
||||
// rowObject is firstname,lastname,login,... get the third word
|
||||
var loginx = "'.api_htmlentities(sprintf(get_lang("LoginX"), ":::"), ENT_QUOTES).'";
|
||||
var tabLoginx = loginx.split(/:::/);
|
||||
// tabLoginx[0] is before and tabLoginx[1] is after :::
|
||||
// may be empty string but is defined
|
||||
return "<span title=\""+tabLoginx[0]+rowObject[2]+tabLoginx[1]+"\">"+cellvalue+"</span>";
|
||||
}';
|
||||
} else {
|
||||
//The order is important you need to check the the $column variable in the model.ajax.php file
|
||||
$columns = [
|
||||
get_lang('StartDate'),
|
||||
get_lang('Score'),
|
||||
get_lang('Actions'),
|
||||
];
|
||||
|
||||
//Column config
|
||||
// @todo fix search firstname/lastname that doesn't work. rmove search for the moment
|
||||
$column_model = [
|
||||
['name' => 'exe_date', 'index' => 'exe_date', 'width' => '60', 'align' => 'left', 'search' => 'false'],
|
||||
['name' => 'score', 'index' => 'exe_result', 'width' => '50', 'align' => 'left', 'search' => 'false'],
|
||||
['name' => 'actions', 'index' => 'actions', 'width' => '60', 'align' => 'left', 'search' => 'false'],
|
||||
];
|
||||
}
|
||||
|
||||
//Autowidth
|
||||
$extra_params['autowidth'] = 'true';
|
||||
|
||||
//height auto
|
||||
$extra_params['height'] = 'auto';
|
||||
?>
|
||||
<script>
|
||||
function setSearchSelect(columnName) {
|
||||
$("#results").jqGrid('setColProp', columnName,
|
||||
{
|
||||
searchoptions:{
|
||||
dataInit:function(el){
|
||||
$("option[value='1']",el).attr("selected", "selected");
|
||||
setTimeout(function(){
|
||||
$(el).trigger('change');
|
||||
},1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportExcel() {
|
||||
var mya=new Array();
|
||||
mya=$("#results").getDataIDs(); // Get All IDs
|
||||
var data=$("#results").getRowData(mya[0]); // Get First row to get the labels
|
||||
var colNames=new Array();
|
||||
var ii=0;
|
||||
for (var i in data){colNames[ii++]=i;} // capture col names
|
||||
var html="";
|
||||
|
||||
for(i=0;i<mya.length;i++) {
|
||||
data=$("#results").getRowData(mya[i]); // get each row
|
||||
for(j=0;j<colNames.length;j++) {
|
||||
html=html+data[colNames[j]]+","; // output each column as tab delimited
|
||||
}
|
||||
html=html+"\n"; // output each row with end of line
|
||||
}
|
||||
html = html+"\n"; // end of line at the end
|
||||
|
||||
var form = $("#export_report_form");
|
||||
|
||||
$("#csvBuffer").attr('value', html);
|
||||
form.target='_blank';
|
||||
form.submit();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
<?php
|
||||
echo Display::grid_js(
|
||||
'results',
|
||||
$url,
|
||||
$columns,
|
||||
$column_model,
|
||||
$extra_params,
|
||||
[],
|
||||
$action_links,
|
||||
true
|
||||
);
|
||||
if ($is_allowedToEdit || $is_tutor) {
|
||||
?>
|
||||
//setSearchSelect("status");
|
||||
//
|
||||
//view:true, del:false, add:false, edit:false, excel:true}
|
||||
$("#results").jqGrid('navGrid','#results_pager', {view:true, edit:false, add:false, del:false, excel:false},
|
||||
{height:280, reloadAfterSubmit:false}, // view options
|
||||
{height:280, reloadAfterSubmit:false}, // edit options
|
||||
{height:280, reloadAfterSubmit:false}, // add options
|
||||
{reloadAfterSubmit: false}, // del options
|
||||
{width:500} // search options
|
||||
);
|
||||
|
||||
//Adding search options
|
||||
var options = {
|
||||
'stringResult': true,
|
||||
'autosearch' : true,
|
||||
'searchOnEnter':false
|
||||
}
|
||||
jQuery("#results").jqGrid('filterToolbar',options);
|
||||
var sgrid = $("#results")[0];
|
||||
sgrid.triggerToolbar();
|
||||
|
||||
<?php
|
||||
} ?>
|
||||
});
|
||||
</script>
|
||||
<form id="export_report_form" method="post" action="hotpotatoes_exercise_report.php?<?php echo api_get_cidreq(); ?>">
|
||||
<input type="hidden" name="csvBuffer" id="csvBuffer" value="" />
|
||||
<input type="hidden" name="export_report" id="export_report" value="1" />
|
||||
<input type="hidden" name="path" id="path" value="<?php echo $hotpotatoes_path; ?>" />
|
||||
</form>
|
||||
<?php
|
||||
|
||||
echo Display::grid_html('results');
|
||||
Display::display_footer();
|
||||
435
main/exercise/hotpotatoes_exercise_result.class.php
Normal file
435
main/exercise/hotpotatoes_exercise_result.class.php
Normal file
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class HotpotatoesExerciseResult
|
||||
* Allows you to export exercises results in multiple presentation forms.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*/
|
||||
class HotpotatoesExerciseResult
|
||||
{
|
||||
//stores the list of exercises
|
||||
private $exercises_list = [];
|
||||
|
||||
//stores the results
|
||||
private $results = [];
|
||||
|
||||
/**
|
||||
* Gets the results of all students (or just one student if access is limited).
|
||||
*
|
||||
* @param string $document_path The document path (for HotPotatoes retrieval)
|
||||
* @param int User ID. Optional. If no user ID is provided, we take all the results. Defauts to null
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getExercisesReporting($document_path, $hotpotato_name)
|
||||
{
|
||||
$return = [];
|
||||
$TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
|
||||
$TBL_TRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
|
||||
$course_id = api_get_course_int_id();
|
||||
$user_id = null;
|
||||
$session_id_and = ' AND te.session_id = '.api_get_session_id().' ';
|
||||
$hotpotato_name = Database::escape_string($hotpotato_name);
|
||||
|
||||
if (!empty($exercise_id)) {
|
||||
$session_id_and .= " AND exe_exo_id = $exercise_id ";
|
||||
}
|
||||
|
||||
if (empty($user_id)) {
|
||||
$sql = "SELECT firstname as userpart1, lastname as userpart2 ,
|
||||
email,
|
||||
tth.exe_name,
|
||||
tth.exe_result,
|
||||
tth.exe_weighting,
|
||||
tth.exe_date
|
||||
FROM $TBL_TRACK_HOTPOTATOES tth, $TBL_USER tu
|
||||
WHERE tu.user_id=tth.exe_user_id AND
|
||||
tth.c_id = $course_id AND
|
||||
tth.exe_name = '$hotpotato_name'
|
||||
ORDER BY tth.c_id ASC, tth.exe_date ASC";
|
||||
} else {
|
||||
// get only this user's results
|
||||
$sql = "SELECT '', exe_name, exe_result , exe_weighting, exe_date
|
||||
FROM $TBL_TRACK_HOTPOTATOES
|
||||
WHERE
|
||||
exe_user_id = $user_id AND
|
||||
c_id = $course_id AND
|
||||
tth.exe_name = '$hotpotato_name'
|
||||
ORDER BY c_id ASC, exe_date ASC";
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
$resx = Database::query($sql);
|
||||
while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
|
||||
$results[] = $rowx;
|
||||
}
|
||||
|
||||
$hpresults = [];
|
||||
$resx = Database::query($sql);
|
||||
while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
|
||||
$hpresults[] = $rowx;
|
||||
}
|
||||
|
||||
// Print the Result of Hotpotatoes Tests
|
||||
if (is_array($hpresults)) {
|
||||
for ($i = 0; $i < sizeof($hpresults); $i++) {
|
||||
$return[$i] = [];
|
||||
$title = GetQuizName($hpresults[$i]['exe_name'], $document_path);
|
||||
if ($title == '') {
|
||||
$title = basename($hpresults[$i]['exe_name']);
|
||||
}
|
||||
if (empty($user_id)) {
|
||||
$return[$i]['email'] = $hpresults[$i]['email'];
|
||||
$return[$i]['first_name'] = $hpresults[$i]['userpart1'];
|
||||
$return[$i]['last_name'] = $hpresults[$i]['userpart2'];
|
||||
}
|
||||
$return[$i]['title'] = $title;
|
||||
$return[$i]['exe_date'] = $hpresults[$i]['exe_date'];
|
||||
|
||||
$return[$i]['result'] = $hpresults[$i]['exe_result'];
|
||||
$return[$i]['max'] = $hpresults[$i]['exe_weighting'];
|
||||
}
|
||||
}
|
||||
$this->results = $return;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the complete report as a CSV file.
|
||||
*
|
||||
* @param string $document_path Document path inside the document tool
|
||||
* @param string $hotpotato_name
|
||||
*
|
||||
* @return bool False on error
|
||||
*/
|
||||
public function exportCompleteReportCSV($document_path = '', $hotpotato_name = '')
|
||||
{
|
||||
global $charset;
|
||||
$this->getExercisesReporting($document_path, $hotpotato_name);
|
||||
$filename = 'exercise_results_'.date('YmdGis').'.csv';
|
||||
if (!empty($user_id)) {
|
||||
$filename = 'exercise_results_user_'.$user_id.'_'.date('YmdGis').'.csv';
|
||||
}
|
||||
$data = '';
|
||||
|
||||
if (api_is_western_name_order()) {
|
||||
if (!empty($this->results[0]['first_name'])) {
|
||||
$data .= get_lang('FirstName').';';
|
||||
}
|
||||
if (!empty($this->results[0]['last_name'])) {
|
||||
$data .= get_lang('LastName').';';
|
||||
}
|
||||
} else {
|
||||
if (!empty($this->results[0]['last_name'])) {
|
||||
$data .= get_lang('LastName').';';
|
||||
}
|
||||
if (!empty($this->results[0]['first_name'])) {
|
||||
$data .= get_lang('FirstName').';';
|
||||
}
|
||||
}
|
||||
$data .= get_lang('Email').';';
|
||||
$data .= get_lang('Title').';';
|
||||
$data .= get_lang('StartDate').';';
|
||||
$data .= get_lang('Score').';';
|
||||
$data .= get_lang('Total').';';
|
||||
$data .= "\n";
|
||||
|
||||
// Results
|
||||
foreach ($this->results as $row) {
|
||||
if (api_is_western_name_order()) {
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['first_name']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['last_name']), ENT_QUOTES, $charset)).';';
|
||||
} else {
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['last_name']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['first_name']), ENT_QUOTES, $charset)).';';
|
||||
}
|
||||
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['email']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['title']), ENT_QUOTES, $charset)).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['exe_date']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['result']).';';
|
||||
$data .= str_replace("\r\n", ' ', $row['max']).';';
|
||||
$data .= "\n";
|
||||
}
|
||||
|
||||
//output the results
|
||||
$len = strlen($data);
|
||||
header('Content-type: application/octet-stream');
|
||||
header('Content-Type: application/force-download');
|
||||
header('Content-length: '.$len);
|
||||
if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT'])) {
|
||||
header('Content-Disposition: filename= '.$filename);
|
||||
} else {
|
||||
header('Content-Disposition: attachment; filename= '.$filename);
|
||||
}
|
||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
|
||||
header('Pragma: ');
|
||||
header('Cache-Control: ');
|
||||
header('Cache-Control: public'); // IE cannot download from sessions without a cache
|
||||
}
|
||||
header('Content-Description: '.$filename);
|
||||
header('Content-transfer-encoding: binary');
|
||||
// @todo add this utf-8 header for all csv files
|
||||
echo "\xEF\xBB\xBF"; // force utf-8 header of csv file
|
||||
echo $data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the complete report as an XLS file.
|
||||
*
|
||||
* @param string $document_path
|
||||
* @param null $user_id
|
||||
* @param bool $export_user_fields
|
||||
* @param int $export_filter
|
||||
* @param int $exercise_id
|
||||
* @param null $hotpotato_name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exportCompleteReportXLS(
|
||||
$document_path = '',
|
||||
$user_id = null,
|
||||
$export_user_fields = false,
|
||||
$export_filter = 0,
|
||||
$exercise_id = 0,
|
||||
$hotpotato_name = null
|
||||
) {
|
||||
global $charset;
|
||||
$this->getExercisesReporting(
|
||||
$document_path,
|
||||
$user_id,
|
||||
$export_filter,
|
||||
$exercise_id,
|
||||
$hotpotato_name
|
||||
);
|
||||
$filename = 'exercise_results_'.api_get_local_time().'.xls';
|
||||
if (!empty($user_id)) {
|
||||
$filename = 'exercise_results_user_'.$user_id.'_'.api_get_local_time().'.xls';
|
||||
}
|
||||
|
||||
$spreadsheet = new PHPExcel();
|
||||
$spreadsheet->setActiveSheetIndex(0);
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$line = 0;
|
||||
$column = 0; //skip the first column (row titles)
|
||||
|
||||
// check if exists column 'user'
|
||||
$with_column_user = false;
|
||||
foreach ($this->results as $result) {
|
||||
if (!empty($result['last_name']) && !empty($result['first_name'])) {
|
||||
$with_column_user = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($with_column_user) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('Email')
|
||||
);
|
||||
$column++;
|
||||
if (api_is_western_name_order()) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('FirstName')
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('LastName')
|
||||
);
|
||||
$column++;
|
||||
} else {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('LastName')
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('FirstName')
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($export_user_fields) {
|
||||
//show user fields section with a big th colspan that spans over all fields
|
||||
$extra_user_fields = UserManager::get_extra_fields(
|
||||
0,
|
||||
1000,
|
||||
5,
|
||||
'ASC',
|
||||
false,
|
||||
1
|
||||
);
|
||||
|
||||
//show the fields names for user fields
|
||||
foreach ($extra_user_fields as $field) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($field[3]),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
}
|
||||
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('Title')
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('StartDate')
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('EndDate')
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('Duration').' ('.get_lang('MinMinutes').')'
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('Score')
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('Total')
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
get_lang('Status')
|
||||
);
|
||||
$line++;
|
||||
|
||||
foreach ($this->results as $row) {
|
||||
$column = 0;
|
||||
|
||||
if ($with_column_user) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['email']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
|
||||
if (api_is_western_name_order()) {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['first_name']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['last_name']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
} else {
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['last_name']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow(
|
||||
$column,
|
||||
$line,
|
||||
api_html_entity_decode(
|
||||
strip_tags($row['first_name']),
|
||||
ENT_QUOTES,
|
||||
$charset
|
||||
)
|
||||
);
|
||||
$column++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($export_user_fields) {
|
||||
//show user fields data, if any, for this user
|
||||
$user_fields_values = UserManager::get_extra_user_data(
|
||||
$row['user_id'],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
foreach ($user_fields_values as $value) {
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, api_html_entity_decode(strip_tags($value), ENT_QUOTES, $charset));
|
||||
$column++;
|
||||
}
|
||||
}
|
||||
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, api_html_entity_decode(strip_tags($row['title']), ENT_QUOTES, $charset));
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['start_date']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['end_date']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['duration']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['result']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['max']);
|
||||
$column++;
|
||||
$worksheet->setCellValueByColumnAndRow($column, $line, $row['status']);
|
||||
$line++;
|
||||
}
|
||||
|
||||
$file = api_get_path(SYS_ARCHIVE_PATH).api_replace_dangerous_char($filename);
|
||||
$writer = new PHPExcel_Writer_Excel2007($spreadsheet);
|
||||
$writer->save($file);
|
||||
DocumentManager::file_send_for_download($file, true, $filename);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
115
main/exercise/hotspot.class.php
Normal file
115
main/exercise/hotspot.class.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class HotSpot.
|
||||
*
|
||||
* This class allows to instantiate an object of
|
||||
* type HotSpot (MULTIPLE CHOICE, UNIQUE ANSWER)
|
||||
* extending the class question
|
||||
*
|
||||
* @author Eric Marguin
|
||||
*/
|
||||
class HotSpot extends Question
|
||||
{
|
||||
public $typePicture = 'hotspot.png';
|
||||
public $explanationLangVar = 'HotSpot';
|
||||
|
||||
/**
|
||||
* HotSpot constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = HOT_SPOT;
|
||||
}
|
||||
|
||||
public function display()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createForm(&$form, $exercise)
|
||||
{
|
||||
parent::createForm($form, $exercise);
|
||||
|
||||
if (!isset($_GET['editQuestion'])) {
|
||||
$icon = Display::return_icon(
|
||||
'hotspot.png',
|
||||
null,
|
||||
null,
|
||||
ICON_SIZE_BIG,
|
||||
false,
|
||||
true
|
||||
);
|
||||
$form->addElement(
|
||||
'file',
|
||||
'imageUpload',
|
||||
[
|
||||
'<img src="'.$icon.'" />',
|
||||
get_lang('UploadJpgPicture'),
|
||||
]
|
||||
);
|
||||
|
||||
// setting the save button here and not in the question class.php
|
||||
// Saving a question
|
||||
$form->addButtonSave(get_lang('GoToQuestion'), 'submitQuestion');
|
||||
$form->addRule(
|
||||
'imageUpload',
|
||||
get_lang('OnlyImagesAllowed'),
|
||||
'filetype',
|
||||
['jpg', 'jpeg', 'png', 'gif']
|
||||
);
|
||||
$form->addRule('imageUpload', get_lang('NoImage'), 'uploadedfile');
|
||||
} else {
|
||||
// setting the save button here and not in the question class.php
|
||||
// Editing a question
|
||||
$form->addButtonUpdate(get_lang('ModifyQuestion'), 'submitQuestion');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processCreation($form, $exercise)
|
||||
{
|
||||
$fileInfo = $form->getSubmitValue('imageUpload');
|
||||
parent::processCreation($form, $exercise);
|
||||
|
||||
if (!empty($fileInfo['tmp_name'])) {
|
||||
$result = $this->uploadPicture($fileInfo['tmp_name']);
|
||||
if ($result) {
|
||||
$this->save($exercise);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
return parent::return_header($exercise, $counter, $score)
|
||||
.'<table><tr><td><table class="table">';
|
||||
}
|
||||
}
|
||||
28
main/exercise/hotspot.inc.php
Normal file
28
main/exercise/hotspot.inc.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Hotspot languae conversion.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*/
|
||||
/**
|
||||
* Code.
|
||||
*/
|
||||
session_cache_limiter('none');
|
||||
|
||||
$language_file = 'hotspot';
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
|
||||
$file = file(api_get_path(SYS_LANG_PATH).'english/hotspot.inc.php');
|
||||
|
||||
foreach ($file as &$value) {
|
||||
$variable = explode('=', $value, 2);
|
||||
if (count($variable) > 1) {
|
||||
$variable = substr(trim($variable[0]), 1);
|
||||
$variable = '&'.$variable.'='.api_utf8_encode(get_lang($variable)).' ';
|
||||
echo $variable;
|
||||
}
|
||||
}
|
||||
181
main/exercise/hotspot_actionscript.as.php
Normal file
181
main/exercise/hotspot_actionscript.as.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* This file generates the ActionScript variables code used by the HotSpot .swf.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Toon Keppens
|
||||
*
|
||||
* @version $Id: admin.php 10680 2007-01-11 21:26:23Z pcool $
|
||||
*/
|
||||
session_cache_limiter('none');
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$_course = api_get_course_info();
|
||||
require api_get_path(LIBRARY_PATH).'geometry.lib.php';
|
||||
|
||||
// set vars
|
||||
$questionId = intval($_GET['modifyAnswers']);
|
||||
$exerciseId = isset($_GET['exe_id']) ? intval($_GET['exe_id']) : 0;
|
||||
$objQuestion = Question::read($questionId);
|
||||
$answer_type = $objQuestion->selectType(); //very important
|
||||
$TBL_ANSWERS = Database::get_course_table(TABLE_QUIZ_ANSWER);
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
$picturePath = $documentPath.'/images';
|
||||
$pictureName = $objQuestion->getPictureFilename();
|
||||
$pictureSize = getimagesize($picturePath.'/'.$pictureName);
|
||||
$pictureWidth = $pictureSize[0];
|
||||
$pictureHeight = $pictureSize[1];
|
||||
$course_id = api_get_course_int_id();
|
||||
|
||||
// Query db for answers
|
||||
if ($answer_type == HOT_SPOT_DELINEATION) {
|
||||
$sql = "SELECT iid, answer, hotspot_coordinates, hotspot_type, ponderation
|
||||
FROM $TBL_ANSWERS
|
||||
WHERE
|
||||
c_id = $course_id AND
|
||||
question_id = $questionId AND
|
||||
hotspot_type = 'delineation'
|
||||
ORDER BY iid";
|
||||
} else {
|
||||
$sql = "SELECT iid, answer, hotspot_coordinates, hotspot_type, ponderation
|
||||
FROM $TBL_ANSWERS
|
||||
WHERE c_id = $course_id AND question_id = $questionId
|
||||
ORDER BY position";
|
||||
}
|
||||
$result = Database::query($sql);
|
||||
|
||||
$data = [];
|
||||
$data['type'] = 'user';
|
||||
$data['lang'] = [
|
||||
'Square' => get_lang('Square'),
|
||||
'Ellipse' => get_lang('Ellipse'),
|
||||
'Polygon' => get_lang('Polygon'),
|
||||
'HotspotStatus1' => get_lang('HotspotStatus1'),
|
||||
'HotspotStatus2Polygon' => get_lang('HotspotStatus2Polygon'),
|
||||
'HotspotStatus2Other' => get_lang('HotspotStatus2Other'),
|
||||
'HotspotStatus3' => get_lang('HotspotStatus3'),
|
||||
'HotspotShowUserPoints' => get_lang('HotspotShowUserPoints'),
|
||||
'ShowHotspots' => get_lang('ShowHotspots'),
|
||||
'Triesleft' => get_lang('Triesleft'),
|
||||
'HotspotExerciseFinished' => get_lang('HotspotExerciseFinished'),
|
||||
'NextAnswer' => get_lang('NextAnswer'),
|
||||
'Delineation' => get_lang('Delineation'),
|
||||
'CloseDelineation' => get_lang('CloseDelineation'),
|
||||
'Oar' => get_lang('Oar'),
|
||||
'ClosePolygon' => get_lang('ClosePolygon'),
|
||||
'DelineationStatus1' => get_lang('DelineationStatus1'),
|
||||
];
|
||||
$data['image'] = $objQuestion->selectPicturePath();
|
||||
$data['image_width'] = $pictureWidth;
|
||||
$data['image_height'] = $pictureHeight;
|
||||
$data['courseCode'] = $_course['path'];
|
||||
$data['hotspots'] = [];
|
||||
$data['answers'] = [];
|
||||
|
||||
$nmbrTries = 0;
|
||||
|
||||
while ($hotspot = Database::fetch_assoc($result)) {
|
||||
$hotSpot = [];
|
||||
$hotSpot['iid'] = $hotspot['iid'];
|
||||
$hotSpot['answer'] = $hotspot['answer'];
|
||||
|
||||
// Square or rectancle
|
||||
if ($hotspot['hotspot_type'] == 'square') {
|
||||
$hotSpot['type'] = 'square';
|
||||
}
|
||||
// Circle or ovale
|
||||
if ($hotspot['hotspot_type'] == 'circle') {
|
||||
$hotSpot['type'] = 'circle';
|
||||
}
|
||||
// Polygon
|
||||
if ($hotspot['hotspot_type'] == 'poly') {
|
||||
$hotSpot['type'] = 'poly';
|
||||
}
|
||||
// Delineation
|
||||
if ($hotspot['hotspot_type'] == 'delineation') {
|
||||
$hotSpot['type'] = 'delineation';
|
||||
}
|
||||
// No error
|
||||
if ($hotspot['hotspot_type'] == 'noerror') {
|
||||
$hotSpot['type'] = 'noerror';
|
||||
}
|
||||
|
||||
// This is a good answer, count + 1 for nmbr of clicks
|
||||
if ($hotspot['hotspot_type'] > 0) {
|
||||
$nmbrTries++;
|
||||
}
|
||||
|
||||
$hotSpot['coord'] = $hotspot['hotspot_coordinates'];
|
||||
$data['hotspots'][] = $hotSpot;
|
||||
}
|
||||
|
||||
$attemptInfo = Database::select(
|
||||
'exe_id',
|
||||
Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES),
|
||||
[
|
||||
'where' => [
|
||||
'exe_exo_id = ? AND c_id = ? AND exe_user_id = ? AND status = ?' => [
|
||||
(int) $exerciseId,
|
||||
$course_id,
|
||||
api_get_user_id(),
|
||||
'incomplete',
|
||||
],
|
||||
],
|
||||
'order' => 'exe_id DESC',
|
||||
'limit' => 1,
|
||||
],
|
||||
'first'
|
||||
);
|
||||
|
||||
if (empty($attemptInfo)) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$attemptList = Event::getAllExerciseEventByExeId($attemptInfo['exe_id']);
|
||||
|
||||
if (!empty($attemptList)) {
|
||||
if (isset($attemptList[$questionId])) {
|
||||
$questionAttempt = $attemptList[$questionId][0];
|
||||
if (!empty($questionAttempt['answer'])) {
|
||||
$coordinates = explode('|', $questionAttempt['answer']);
|
||||
|
||||
foreach ($coordinates as $coordinate) {
|
||||
$data['answers'][] = Geometry::decodePoint($coordinate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['nmbrTries'] = $nmbrTries;
|
||||
$data['done'] = 'done';
|
||||
|
||||
if (Session::has("hotspot_ordered$questionId")) {
|
||||
$tempHotspots = [];
|
||||
$hotspotOrdered = Session::read("hotspot_ordered$questionId");
|
||||
|
||||
foreach ($hotspotOrdered as $hotspotOrder) {
|
||||
foreach ($data['hotspots'] as $hotspot) {
|
||||
if ($hotspot['iid'] != $hotspotOrder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tempHotspots[] = $hotspot;
|
||||
}
|
||||
}
|
||||
|
||||
$data['hotspots'] = $tempHotspots;
|
||||
|
||||
Session::erase("hotspot_ordered$questionId");
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
echo json_encode($data);
|
||||
116
main/exercise/hotspot_actionscript_admin.as.php
Normal file
116
main/exercise/hotspot_actionscript_admin.as.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* This file generates the ActionScript variables code used by the HotSpot .swf.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Toon Keppens
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(false);
|
||||
|
||||
$isAllowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
|
||||
if (!$isAllowedToEdit) {
|
||||
api_not_allowed(true);
|
||||
exit;
|
||||
}
|
||||
|
||||
$_course = api_get_course_info();
|
||||
$questionId = isset($_GET['modifyAnswers']) ? (int) $_GET['modifyAnswers'] : 0;
|
||||
$objQuestion = Question::read($questionId);
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
$picturePath = $documentPath.'/images';
|
||||
$pictureName = $objQuestion->getPictureFilename();
|
||||
|
||||
$pictureSize = getimagesize($picturePath.'/'.$pictureName);
|
||||
$pictureWidth = $pictureSize[0];
|
||||
$pictureHeight = $pictureSize[1];
|
||||
|
||||
$data = [];
|
||||
$data['type'] = 'admin';
|
||||
$data['lang'] = [
|
||||
'Square' => get_lang('Square'),
|
||||
'Ellipse' => get_lang('Ellipse'),
|
||||
'Polygon' => get_lang('Polygon'),
|
||||
'HotspotStatus1' => get_lang('HotspotStatus1'),
|
||||
'HotspotStatus2Polygon' => get_lang('HotspotStatus2Polygon'),
|
||||
'HotspotStatus2Other' => get_lang('HotspotStatus2Other'),
|
||||
'HotspotStatus3' => get_lang('HotspotStatus3'),
|
||||
'HotspotShowUserPoints' => get_lang('HotspotShowUserPoints'),
|
||||
'ShowHotspots' => get_lang('ShowHotspots'),
|
||||
'Triesleft' => get_lang('Triesleft'),
|
||||
'HotspotExerciseFinished' => get_lang('HotspotExerciseFinished'),
|
||||
'NextAnswer' => get_lang('NextAnswer'),
|
||||
'Delineation' => get_lang('Delineation'),
|
||||
'CloseDelineation' => get_lang('CloseDelineation'),
|
||||
'Oar' => get_lang('Oar'),
|
||||
'ClosePolygon' => get_lang('ClosePolygon'),
|
||||
'DelineationStatus1' => get_lang('DelineationStatus1'),
|
||||
];
|
||||
$data['image'] = $objQuestion->selectPicturePath();
|
||||
$data['image_width'] = $pictureWidth;
|
||||
$data['image_height'] = $pictureHeight;
|
||||
$data['courseCode'] = $_course['path'];
|
||||
$data['hotspots'] = [];
|
||||
|
||||
$i = 0;
|
||||
$nmbrTries = 0;
|
||||
$answer_type = $objQuestion->type;
|
||||
$answers = Session::read('tmp_answers');
|
||||
$nbrAnswers = count($answers['answer']);
|
||||
|
||||
for ($i = 1; $i <= $nbrAnswers; $i++) {
|
||||
$hotSpot = [];
|
||||
$hotSpot['iid'] = null;
|
||||
$hotSpot['answer'] = $answers['answer'][$i];
|
||||
|
||||
if ($answer_type == HOT_SPOT_DELINEATION) {
|
||||
if ($i == 1) {
|
||||
$hotSpot['type'] = 'delineation';
|
||||
} else {
|
||||
$hotSpot['type'] = 'oar';
|
||||
}
|
||||
} else {
|
||||
// Square or rectancle
|
||||
if ($answers['hotspot_type'][$i] == 'square') {
|
||||
$hotSpot['type'] = 'square';
|
||||
}
|
||||
|
||||
// Circle or ovale
|
||||
if ($answers['hotspot_type'][$i] == 'circle') {
|
||||
$hotSpot['type'] = 'circle';
|
||||
}
|
||||
|
||||
// Polygon
|
||||
if ($answers['hotspot_type'][$i] == 'poly') {
|
||||
$hotSpot['type'] = 'poly';
|
||||
}
|
||||
/*// Delineation
|
||||
if ($answers['hotspot_type'][$i] == 'delineation')
|
||||
{
|
||||
$output .= "&hotspot_".$i."_type=delineation";
|
||||
}*/
|
||||
}
|
||||
|
||||
// This is a good answer, count + 1 for nmbr of clicks
|
||||
if ($answers['weighting'][$i] > 0) {
|
||||
$nmbrTries++;
|
||||
}
|
||||
|
||||
$hotSpot['coord'] = $answers['hotspot_coordinates'][$i];
|
||||
$data['hotspots'][] = $hotSpot;
|
||||
}
|
||||
|
||||
// Output
|
||||
$data['nmbrTries'] = $nmbrTries;
|
||||
$data['done'] = 'done';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
echo json_encode($data);
|
||||
1161
main/exercise/hotspot_admin.inc.php
Normal file
1161
main/exercise/hotspot_admin.inc.php
Normal file
File diff suppressed because it is too large
Load Diff
238
main/exercise/hotspot_answers.as.php
Normal file
238
main/exercise/hotspot_answers.as.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEHotspot;
|
||||
use Chamilo\CourseBundle\Entity\CQuizAnswer;
|
||||
|
||||
/**
|
||||
* This file generates a json answer to the question preview.
|
||||
*
|
||||
* @author Toon Keppens, Julio Montoya adding hotspot "medical" support
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
|
||||
$_course = api_get_course_info();
|
||||
$questionId = isset($_GET['modifyAnswers']) ? (int) $_GET['modifyAnswers'] : 0;
|
||||
$exerciseId = isset($_GET['exerciseId']) ? (int) $_GET['exerciseId'] : 0;
|
||||
$exeId = isset($_GET['exeId']) ? (int) $_GET['exeId'] : 0;
|
||||
$userId = api_get_user_id();
|
||||
$courseId = api_get_course_int_id();
|
||||
$objExercise = new Exercise($courseId);
|
||||
$debug = false;
|
||||
|
||||
if ($debug) {
|
||||
error_log("Call to hotspot_answers.as.php");
|
||||
}
|
||||
|
||||
$trackExerciseInfo = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
|
||||
|
||||
// Check if student has access to the hotspot answers
|
||||
if (!api_is_allowed_to_edit(null, true)) {
|
||||
if (empty($exeId)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
if (empty($trackExerciseInfo)) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
// Different exercise
|
||||
if ($exerciseId != $trackExerciseInfo['exe_exo_id']) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
// Different user
|
||||
if ($trackExerciseInfo['exe_user_id'] != $userId) {
|
||||
api_not_allowed();
|
||||
}
|
||||
}
|
||||
|
||||
$objQuestion = Question::read($questionId, $objExercise->course);
|
||||
$objExercise->read($exerciseId);
|
||||
|
||||
if (empty($objQuestion) || empty($objExercise)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
$picturePath = $documentPath.'/images';
|
||||
$pictureName = $objQuestion->getPictureFilename();
|
||||
$pictureSize = getimagesize($picturePath.'/'.$pictureName);
|
||||
$pictureWidth = $pictureSize[0];
|
||||
$pictureHeight = $pictureSize[1];
|
||||
|
||||
$data = [];
|
||||
$data['type'] = 'solution';
|
||||
$data['lang'] = [
|
||||
'Square' => get_lang('Square'),
|
||||
'Ellipse' => get_lang('Ellipse'),
|
||||
'Polygon' => get_lang('Polygon'),
|
||||
'HotspotStatus1' => get_lang('HotspotStatus1'),
|
||||
'HotspotStatus2Polygon' => get_lang('HotspotStatus2Polygon'),
|
||||
'HotspotStatus2Other' => get_lang('HotspotStatus2Other'),
|
||||
'HotspotStatus3' => get_lang('HotspotStatus3'),
|
||||
'HotspotShowUserPoints' => get_lang('HotspotShowUserPoints'),
|
||||
'ShowHotspots' => get_lang('ShowHotspots'),
|
||||
'Triesleft' => get_lang('Triesleft'),
|
||||
'HotspotExerciseFinished' => get_lang('HotspotExerciseFinished'),
|
||||
'NextAnswer' => get_lang('NextAnswer'),
|
||||
'Delineation' => get_lang('Delineation'),
|
||||
'CloseDelineation' => get_lang('CloseDelineation'),
|
||||
'Oar' => get_lang('Oar'),
|
||||
'ClosePolygon' => get_lang('ClosePolygon'),
|
||||
'DelineationStatus1' => get_lang('DelineationStatus1'),
|
||||
];
|
||||
$data['image'] = $objQuestion->selectPicturePath();
|
||||
$data['image_width'] = $pictureWidth;
|
||||
$data['image_height'] = $pictureHeight;
|
||||
$data['courseCode'] = $_course['path'];
|
||||
$data['hotspots'] = [];
|
||||
$resultDisable = $objExercise->selectResultsDisabled();
|
||||
$showTotalScoreAndUserChoicesInLastAttempt = true;
|
||||
if (in_array(
|
||||
$resultDisable, [
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
|
||||
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
|
||||
]
|
||||
)
|
||||
) {
|
||||
$showOnlyScore = true;
|
||||
$showResults = true;
|
||||
$lpId = isset($trackExerciseInfo['orig_lp_id']) ? $trackExerciseInfo['orig_lp_id'] : 0;
|
||||
$lpItemId = isset($trackExerciseInfo['orig_lp_item_id']) ? $trackExerciseInfo['orig_lp_item_id'] : 0;
|
||||
if ($objExercise->attempts > 0) {
|
||||
$attempts = Event::getExerciseResultsByUser(
|
||||
api_get_user_id(),
|
||||
$objExercise->iid,
|
||||
$courseId,
|
||||
api_get_session_id(),
|
||||
$lpId,
|
||||
$lpItemId,
|
||||
'desc'
|
||||
);
|
||||
$numberAttempts = count($attempts);
|
||||
$showTotalScoreAndUserChoicesInLastAttempt = false;
|
||||
|
||||
if ($numberAttempts >= $objExercise->attempts) {
|
||||
$showResults = true;
|
||||
$showOnlyScore = false;
|
||||
$showTotalScoreAndUserChoicesInLastAttempt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hideExpectedAnswer = false;
|
||||
if ($objExercise->getFeedbackType() == 0 &&
|
||||
$resultDisable == RESULT_DISABLE_SHOW_SCORE_ONLY
|
||||
) {
|
||||
$hideExpectedAnswer = true;
|
||||
}
|
||||
|
||||
if (in_array(
|
||||
$resultDisable, [
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
|
||||
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
|
||||
]
|
||||
)
|
||||
) {
|
||||
$hideExpectedAnswer = $showTotalScoreAndUserChoicesInLastAttempt ? false : true;
|
||||
}
|
||||
|
||||
if (in_array(
|
||||
$resultDisable, [
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
|
||||
]
|
||||
)
|
||||
) {
|
||||
$hideExpectedAnswer = false;
|
||||
}
|
||||
|
||||
$hotSpotWithAnswer = [];
|
||||
$data['answers'] = [];
|
||||
$rs = $em
|
||||
->getRepository('ChamiloCoreBundle:TrackEHotspot')
|
||||
->findBy(
|
||||
[
|
||||
'hotspotQuestionId' => $questionId,
|
||||
'cId' => $courseId,
|
||||
'hotspotExeId' => $exeId,
|
||||
],
|
||||
['hotspotAnswerId' => 'ASC']
|
||||
);
|
||||
|
||||
/** @var TrackEHotspot $row */
|
||||
foreach ($rs as $row) {
|
||||
$data['answers'][] = $row->getHotspotCoordinate();
|
||||
|
||||
if ($row->getHotspotCorrect()) {
|
||||
$hotSpotWithAnswer[] = $row->getHotspotAnswerId();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hideExpectedAnswer) {
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb
|
||||
->select('a')
|
||||
->from('ChamiloCourseBundle:CQuizAnswer', 'a');
|
||||
|
||||
if ($objQuestion->selectType() == HOT_SPOT_DELINEATION) {
|
||||
$qb
|
||||
->where($qb->expr()->eq('a.questionId', $questionId))
|
||||
->andWhere("a.hotspotType != 'noerror'")
|
||||
->orderBy('a.iid', 'ASC');
|
||||
} else {
|
||||
$qb
|
||||
->where($qb->expr()->eq('a.questionId', $questionId))
|
||||
->orderBy('a.position', 'ASC');
|
||||
}
|
||||
|
||||
$result = $qb->getQuery()->getResult();
|
||||
/** @var CQuizAnswer $hotSpotAnswer */
|
||||
foreach ($result as $hotSpotAnswer) {
|
||||
if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK == $resultDisable) {
|
||||
if (false === $showTotalScoreAndUserChoicesInLastAttempt) {
|
||||
if (!in_array($hotSpotAnswer->getId(), $hotSpotWithAnswer)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hotSpot = [];
|
||||
$hotSpot['id'] = $hotSpotAnswer->getId();
|
||||
$hotSpot['answer'] = $hotSpotAnswer->getAnswer();
|
||||
|
||||
switch ($hotSpotAnswer->getHotspotType()) {
|
||||
case 'square':
|
||||
$hotSpot['type'] = 'square';
|
||||
break;
|
||||
case 'circle':
|
||||
$hotSpot['type'] = 'circle';
|
||||
break;
|
||||
case 'poly':
|
||||
$hotSpot['type'] = 'poly';
|
||||
break;
|
||||
case 'delineation':
|
||||
$hotSpot['type'] = 'delineation';
|
||||
break;
|
||||
case 'oar':
|
||||
$hotSpot['type'] = 'delineation';
|
||||
break;
|
||||
}
|
||||
$hotSpot['coord'] = $hotSpotAnswer->getHotspotCoordinates();
|
||||
$data['hotspots'][] = $hotSpot;
|
||||
}
|
||||
}
|
||||
|
||||
$data['done'] = 'done';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
echo json_encode($data);
|
||||
|
||||
if ($debug) {
|
||||
error_log("---------- End call to hotspot_answers.as.php------------");
|
||||
}
|
||||
55
main/exercise/hotspot_lang_conversion.php
Normal file
55
main/exercise/hotspot_lang_conversion.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
/**
|
||||
* Hotspot language conversion.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author
|
||||
*
|
||||
* @deprecated ?
|
||||
*
|
||||
* @version $Id: admin.php 10680 2007-01-11 21:26:23Z pcool $
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$hotspot_lang_file = api_get_path(SYS_LANG_PATH);
|
||||
|
||||
if (isset($_GET['lang'])) {
|
||||
//$search = array('../','\\0','\\');
|
||||
$lang = urldecode($_GET['lang']);
|
||||
if (preg_match('/^[a-zA-Z0-9\._-]+$/', $lang)) {
|
||||
//$lang = str_replace($search,$replace,urldecode($_GET['lang']));
|
||||
if (file_exists($hotspot_lang_file.$lang.'/hotspot.inc.php')) {
|
||||
$hotspot_lang_file .= $lang.'/hotspot.inc.php';
|
||||
} else {
|
||||
$hotspot_lang_file .= 'english/hotspot.inc.php';
|
||||
}
|
||||
} else {
|
||||
$hotspot_lang_file .= 'english/hotspot.inc.php';
|
||||
}
|
||||
} else {
|
||||
$hotspot_lang_file .= 'english/hotspot.inc.php';
|
||||
}
|
||||
|
||||
$file = file($hotspot_lang_file);
|
||||
$temp = [];
|
||||
|
||||
foreach ($file as $value) {
|
||||
$explode = explode('=', $value);
|
||||
|
||||
if (count($explode) > 1) {
|
||||
$explode[0] = trim($explode[0]);
|
||||
$explode[0] = '&'.substr($explode[0], 1, strlen($explode[0]));
|
||||
|
||||
$explode[1] = trim($explode[1]);
|
||||
$explode[1] = substr($explode[1], 0, strlen($explode[1]) - 1);
|
||||
$explode[1] = str_replace('"', '', $explode[1]);
|
||||
|
||||
$temp[] = $explode[0].'='.$explode[1];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($temp as $value) {
|
||||
echo $value.' ';
|
||||
}
|
||||
46
main/exercise/hotspot_save.inc.php
Normal file
46
main/exercise/hotspot_save.inc.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Toon Keppens
|
||||
*
|
||||
* @version $Id: admin.php 10680 2007-01-11 21:26:23Z pcool $
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
|
||||
$questionId = intval($_GET['questionId']);
|
||||
$answerId = intval($_GET['answerId']);
|
||||
|
||||
if ($_GET['type'] == "square" || $_GET['type'] == "circle") {
|
||||
$hotspot_type = $_GET['type'];
|
||||
$hotspot_coordinates = $_GET['x'].";".$_GET['y']."|".$_GET['width']."|".$_GET['height'];
|
||||
}
|
||||
if ($_GET['type'] == "poly" || $_GET['type'] == "delineation" || $_GET['type'] == "oar") {
|
||||
$hotspot_type = $_GET['type'];
|
||||
$tmp_coord = explode(",", $_GET['co']);
|
||||
$i = 0;
|
||||
$hotspot_coordinates = "";
|
||||
foreach ($tmp_coord as $coord) {
|
||||
if ($i % 2 == 0) {
|
||||
$delimiter = ";";
|
||||
} else {
|
||||
$delimiter = "|";
|
||||
}
|
||||
$hotspot_coordinates .= $coord.$delimiter;
|
||||
$i++;
|
||||
}
|
||||
$hotspot_coordinates = api_substr($hotspot_coordinates, 0, -2);
|
||||
}
|
||||
$sql = "UPDATE $TBL_ANSWER SET
|
||||
hotspot_coordinates = '".Database::escape_string($hotspot_coordinates)."',
|
||||
hotspot_type = '".Database::escape_string($hotspot_type)."'
|
||||
WHERE
|
||||
iid = ".intval($answerId)."
|
||||
LIMIT 1 ";
|
||||
$result = Database::query($sql);
|
||||
echo "done=done";
|
||||
60
main/exercise/hotspot_savescore.inc.php
Normal file
60
main/exercise/hotspot_savescore.inc.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* This file saves every click in the hotspot tool into track_e_hotspots.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Toon Keppens
|
||||
*
|
||||
* @version $Id: admin.php 10680 2007-01-11 21:26:23Z pcool $
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$courseCode = $_GET['coursecode'];
|
||||
$questionId = $_GET['questionId'];
|
||||
$coordinates = $_GET['coord'];
|
||||
$objExercise = Session::read('objExercise');
|
||||
$exerciseId = $objExercise->selectId();
|
||||
// Save clicking order
|
||||
$answerOrderId = count($_SESSION['exerciseResult'][$questionId]['ids']) + 1;
|
||||
if ($_GET['answerId'] == "0") {
|
||||
// click is NOT on a hotspot
|
||||
$hit = 0;
|
||||
$answerId = null;
|
||||
} else {
|
||||
// user clicked ON a hotspot
|
||||
$hit = 1;
|
||||
$answerId = api_substr($_GET['answerId'], 22, 2);
|
||||
// Save into session
|
||||
$_SESSION['exerciseResult'][$questionId][$answerId] = $hit;
|
||||
}
|
||||
//round-up the coordinates
|
||||
$coords = explode('/', $coordinates);
|
||||
$coordinates = '';
|
||||
foreach ($coords as $coord) {
|
||||
list($x, $y) = explode(';', $coord);
|
||||
$coordinates .= round($x).';'.round($y).'/';
|
||||
}
|
||||
$coordinates = substr($coordinates, 0, -1);
|
||||
|
||||
$TBL_TRACK_E_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
|
||||
// Save into db
|
||||
$params = [
|
||||
'user_id' => api_get_user_id(),
|
||||
'course_id' => $courseCode,
|
||||
'quiz_id' => $exerciseId,
|
||||
'question_id' => $questionId,
|
||||
'answer_id' => $answerId,
|
||||
'correct' => $hit,
|
||||
'coordinate' => $coordinates,
|
||||
];
|
||||
// Save insert id into session if users changes answer.
|
||||
$insert_id = Database::insert($TBL_TRACK_E_HOTSPOT, $params);
|
||||
|
||||
$_SESSION['exerciseResult'][$questionId]['ids'][$answerOrderId] = $insert_id;
|
||||
56
main/exercise/hotspot_updatescore.inc.php
Normal file
56
main/exercise/hotspot_updatescore.inc.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* This file saves every click in the hotspot tool into track_e_hotspots.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Toon Keppens
|
||||
*
|
||||
* @version $Id: admin.php 10680 2007-01-11 21:26:23Z pcool $
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$courseCode = $_GET['coursecode'];
|
||||
$questionId = $_GET['questionId'];
|
||||
$coordinates = $_GET['coord'];
|
||||
$objExercise = Session::read('objExercise');
|
||||
$hotspotId = $_GET['hotspotId'];
|
||||
$exerciseId = $objExercise->selectId();
|
||||
if ($_GET['answerId'] == "0") { // click is NOT on a hotspot
|
||||
$hit = 0;
|
||||
$answerId = $hotspotId;
|
||||
|
||||
// remove from session
|
||||
unset($_SESSION['exerciseResult'][$questionId][$answerId]);
|
||||
} else { // user clicked ON a hotspot
|
||||
$hit = 1;
|
||||
$answerId = $hotspotId;
|
||||
|
||||
// Save into session
|
||||
$_SESSION['exerciseResult'][$questionId][$answerId] = $hit;
|
||||
}
|
||||
|
||||
//round-up the coordinates
|
||||
$coords = explode('/', $coordinates);
|
||||
$coordinates = '';
|
||||
foreach ($coords as $coord) {
|
||||
list($x, $y) = explode(';', $coord);
|
||||
$coordinates .= round($x).';'.round($y).'/';
|
||||
}
|
||||
$coordinates = substr($coordinates, 0, -1);
|
||||
|
||||
$TBL_TRACK_E_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
|
||||
|
||||
// update db
|
||||
$update_id = $_SESSION['exerciseResult'][$questionId]['ids'][$answerId];
|
||||
$sql = "UPDATE $TBL_TRACK_E_HOTSPOT
|
||||
SET coordinate = '".Database::escape_string($coordinates)."'
|
||||
WHERE id = ".intval($update_id)."
|
||||
LIMIT 1";
|
||||
$result = Database::query($sql);
|
||||
7
main/exercise/index.html
Normal file
7
main/exercise/index.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url=exercise.php">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
119
main/exercise/live_stats.php
Normal file
119
main/exercise/live_stats.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/* See license terms in /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
$exercise_id = isset($_GET['exerciseId']) && !empty($_GET['exerciseId']) ? (int) ($_GET['exerciseId']) : 0;
|
||||
|
||||
api_protect_course_script(true);
|
||||
if (!api_is_allowed_to_edit()) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$objExercise = new Exercise();
|
||||
$result = $objExercise->read($exercise_id);
|
||||
|
||||
if (!$result) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
"url" => "exercise.php?".api_get_cidreq(),
|
||||
"name" => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => "admin.php?exerciseId=$exercise_id&".api_get_cidreq(),
|
||||
'name' => $objExercise->selectTitle(true),
|
||||
];
|
||||
|
||||
//Add the JS needed to use the jqgrid
|
||||
$htmlHeadXtra[] = api_get_jqgrid_js();
|
||||
|
||||
// The header.
|
||||
Display::display_header(get_lang('StudentsWhoAreTakingTheExerciseRightNow'));
|
||||
|
||||
//jqgrid will use this URL to do the selects
|
||||
$minutes = 60;
|
||||
$url = api_get_path(WEB_AJAX_PATH).
|
||||
'exercise.ajax.php?'.api_get_cidreq().'&a=get_live_stats&exercise_id='.$objExercise->iid.'&minutes='.$minutes;
|
||||
|
||||
//The order is important you need to check the the $column variable in the model.ajax.php file
|
||||
$columns = [
|
||||
get_lang('FirstName'),
|
||||
get_lang('LastName'),
|
||||
get_lang('Time'),
|
||||
get_lang('QuestionsAlreadyAnswered'),
|
||||
get_lang('Score'),
|
||||
];
|
||||
|
||||
//Column config
|
||||
$column_model = [
|
||||
[
|
||||
'name' => 'firstname',
|
||||
'index' => 'firstname',
|
||||
'width' => '100',
|
||||
'align' => 'left',
|
||||
],
|
||||
[
|
||||
'name' => 'lastname',
|
||||
'index' => 'lastname',
|
||||
'width' => '100',
|
||||
'align' => 'left',
|
||||
],
|
||||
[
|
||||
'name' => 'start_date',
|
||||
'index' => 'start_date',
|
||||
'width' => '100',
|
||||
'align' => 'left',
|
||||
],
|
||||
[
|
||||
'name' => 'question',
|
||||
'index' => 'count_questions',
|
||||
'width' => '60',
|
||||
'align' => 'left',
|
||||
'sortable' => 'false',
|
||||
],
|
||||
[
|
||||
'name' => 'score',
|
||||
'index' => 'score',
|
||||
'width' => '50',
|
||||
'align' => 'left',
|
||||
'sortable' => 'false',
|
||||
],
|
||||
];
|
||||
$extra_params['autowidth'] = 'true';
|
||||
$extra_params['height'] = 'auto';
|
||||
?>
|
||||
<script>
|
||||
|
||||
function refreshGrid() {
|
||||
var grid = $("#live_stats");
|
||||
grid.trigger("reloadGrid");
|
||||
t = setTimeout("refreshGrid()", 10000);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
<?php
|
||||
echo Display::grid_js(
|
||||
'live_stats',
|
||||
$url,
|
||||
$columns,
|
||||
$column_model,
|
||||
$extra_params,
|
||||
[],
|
||||
null,
|
||||
true
|
||||
);
|
||||
?>
|
||||
refreshGrid();
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
|
||||
$actions = '<a href="exercise_report.php?exerciseId='.$exercise_id.'&'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
echo $actions = Display::div($actions, ['class' => 'actions']);
|
||||
echo Display::grid_html('live_stats');
|
||||
Display::display_footer();
|
||||
358
main/exercise/matching.class.php
Normal file
358
main/exercise/matching.class.php
Normal file
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class Matching
|
||||
* Matching questions type class.
|
||||
*
|
||||
* This class allows to instantiate an object of
|
||||
* type MULTIPLE_ANSWER (MULTIPLE CHOICE, MULTIPLE ANSWER)
|
||||
* extending the class question
|
||||
*
|
||||
* @author Eric Marguin
|
||||
*/
|
||||
class Matching extends Question
|
||||
{
|
||||
public $typePicture = 'matching.png';
|
||||
public $explanationLangVar = 'Matching';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MATCHING;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$defaults = [];
|
||||
$nb_matches = $nb_options = 2;
|
||||
$matches = [];
|
||||
$answer = null;
|
||||
$counter = 1;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
if ($answer->nbrAnswers > 0) {
|
||||
for ($i = 1; $i <= $answer->nbrAnswers; $i++) {
|
||||
$correct = $answer->isCorrect($i);
|
||||
if (empty($correct)) {
|
||||
$matches[$answer->selectAutoId($i)] = chr(64 + $counter);
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($form->isSubmitted()) {
|
||||
$nb_matches = $form->getSubmitValue('nb_matches');
|
||||
$nb_options = $form->getSubmitValue('nb_options');
|
||||
if (isset($_POST['lessOptions'])) {
|
||||
$nb_matches--;
|
||||
$nb_options--;
|
||||
}
|
||||
if (isset($_POST['moreOptions'])) {
|
||||
$nb_matches++;
|
||||
$nb_options++;
|
||||
}
|
||||
} elseif (!empty($this->iid)) {
|
||||
if ($answer->nbrAnswers > 0) {
|
||||
$nb_matches = $nb_options = 0;
|
||||
for ($i = 1; $i <= $answer->nbrAnswers; $i++) {
|
||||
if ($answer->isCorrect($i)) {
|
||||
$nb_matches++;
|
||||
$defaults['answer['.$nb_matches.']'] = $answer->selectAnswer($i);
|
||||
$defaults['weighting['.$nb_matches.']'] = float_format($answer->selectWeighting($i));
|
||||
$defaults['matches['.$nb_matches.']'] = $answer->correct[$i];
|
||||
} else {
|
||||
$nb_options++;
|
||||
$defaults['option['.$nb_options.']'] = $answer->selectAnswer($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$defaults['answer[1]'] = get_lang('DefaultMakeCorrespond1');
|
||||
$defaults['answer[2]'] = get_lang('DefaultMakeCorrespond2');
|
||||
$defaults['matches[2]'] = '2';
|
||||
$defaults['option[1]'] = get_lang('DefaultMatchingOptA');
|
||||
$defaults['option[2]'] = get_lang('DefaultMatchingOptB');
|
||||
}
|
||||
|
||||
if (empty($matches)) {
|
||||
for ($i = 1; $i <= $nb_options; $i++) {
|
||||
// fill the array with A, B, C.....
|
||||
$matches[$i] = chr(64 + $i);
|
||||
}
|
||||
} else {
|
||||
for ($i = $counter; $i <= $nb_options; $i++) {
|
||||
// fill the array with A, B, C.....
|
||||
$matches[$i] = chr(64 + $i);
|
||||
}
|
||||
}
|
||||
|
||||
$form->addElement('hidden', 'nb_matches', $nb_matches);
|
||||
$form->addElement('hidden', 'nb_options', $nb_options);
|
||||
|
||||
$thWeighting = '';
|
||||
if (MATCHING === $this->type) {
|
||||
$thWeighting = '<th width="10%">'.get_lang('Weighting').'</th>';
|
||||
}
|
||||
// DISPLAY MATCHES
|
||||
$html = '<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">'.get_lang('Number').'</th>
|
||||
<th width="70%">'.get_lang('Answer').'</th>
|
||||
<th width="15%">'.get_lang('MatchesTo').'</th>
|
||||
'.$thWeighting.'
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
$form->addHeader(get_lang('MakeCorrespond'));
|
||||
$form->addHtml($html);
|
||||
|
||||
if ($nb_matches < 1) {
|
||||
$nb_matches = 1;
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'))
|
||||
);
|
||||
}
|
||||
|
||||
$editorConfig = [
|
||||
'ToolbarSet' => 'TestMatching',
|
||||
'Width' => '100%',
|
||||
'Height' => '125',
|
||||
];
|
||||
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$renderer = &$form->defaultRenderer();
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"answer[$i]"
|
||||
);
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"matches[$i]"
|
||||
);
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"weighting[$i]"
|
||||
);
|
||||
|
||||
$form->addHtml('<tr>');
|
||||
$form->addHtml("<td>$i</td>");
|
||||
$form->addHtmlEditor(
|
||||
"answer[$i]",
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
$editorConfig
|
||||
);
|
||||
$form->addSelect(
|
||||
"matches[$i]",
|
||||
null,
|
||||
$matches,
|
||||
['id' => 'matches_'.$i]
|
||||
);
|
||||
if (MATCHING === $this->type) {
|
||||
$form->addText(
|
||||
"weighting[$i]",
|
||||
null,
|
||||
true,
|
||||
['id' => 'weighting_'.$i, 'value' => 10]
|
||||
);
|
||||
} else {
|
||||
$form->addHidden("weighting[$i]", "0");
|
||||
}
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</tbody></table>');
|
||||
|
||||
// DISPLAY OPTIONS
|
||||
$html = '<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%">'.get_lang('Number').'</th>
|
||||
<th width="85%">'.get_lang('Answer').'</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
$form->addHtml($html);
|
||||
|
||||
if ($nb_options < 1) {
|
||||
$nb_options = 1;
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'))
|
||||
);
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $nb_options; $i++) {
|
||||
$renderer = &$form->defaultRenderer();
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->{element}</td>',
|
||||
"option[$i]"
|
||||
);
|
||||
|
||||
$form->addHtml('<tr>');
|
||||
$form->addHtml('<td>'.chr(64 + $i).'</td>');
|
||||
$form->addHtmlEditor(
|
||||
"option[$i]",
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
$editorConfig
|
||||
);
|
||||
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</table>');
|
||||
|
||||
if (MATCHING_COMBINATION === $this->type) {
|
||||
//only 1 answer the all deal ...
|
||||
$form->addText('questionWeighting', get_lang('Score'), true, ['value' => 10]);
|
||||
if (!empty($this->iid)) {
|
||||
$defaults['questionWeighting'] = $this->weighting;
|
||||
}
|
||||
}
|
||||
|
||||
global $text;
|
||||
$group = [];
|
||||
// setting the save button here and not in the question class.php
|
||||
$group[] = $form->addButtonDelete(get_lang('DelElem'), 'lessOptions', true);
|
||||
$group[] = $form->addButtonCreate(get_lang('AddElem'), 'moreOptions', true);
|
||||
$group[] = $form->addButtonSave($text, 'submitQuestion', true);
|
||||
$form->addGroup($group);
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
|
||||
$form->setConstants(
|
||||
[
|
||||
'nb_matches' => $nb_matches,
|
||||
'nb_options' => $nb_options,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$nb_matches = $form->getSubmitValue('nb_matches');
|
||||
$nb_options = $form->getSubmitValue('nb_options');
|
||||
$this->weighting = 0;
|
||||
$position = 0;
|
||||
$objAnswer = new Answer($this->iid);
|
||||
|
||||
// Insert the options
|
||||
for ($i = 1; $i <= $nb_options; $i++) {
|
||||
$position++;
|
||||
$option = $form->getSubmitValue('option['.$i.']');
|
||||
$objAnswer->createAnswer($option, 0, '', 0, $position);
|
||||
}
|
||||
|
||||
// Insert the answers
|
||||
for ($i = 1; $i <= $nb_matches; $i++) {
|
||||
$position++;
|
||||
$answer = $form->getSubmitValue('answer['.$i.']');
|
||||
$matches = $form->getSubmitValue('matches['.$i.']');
|
||||
$weighting = $form->getSubmitValue('weighting['.$i.']');
|
||||
$this->weighting += $weighting;
|
||||
|
||||
$objAnswer->createAnswer(
|
||||
$answer,
|
||||
$matches,
|
||||
'',
|
||||
$weighting,
|
||||
$position
|
||||
);
|
||||
}
|
||||
|
||||
if (MATCHING_COMBINATION == $this->type) {
|
||||
$this->weighting = $form->getSubmitValue('questionWeighting');
|
||||
}
|
||||
|
||||
$objAnswer->save();
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'">';
|
||||
$header .= '<tr>';
|
||||
|
||||
$header .= '<th>'.get_lang('ElementList').'</th>';
|
||||
if (!in_array($exercise->results_disabled, [
|
||||
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
|
||||
])
|
||||
) {
|
||||
$header .= '<th>'.get_lang('Choice').'</th>';
|
||||
}
|
||||
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
} else {
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('CorrespondsTo').'</th>';
|
||||
}
|
||||
}
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a answer is correct.
|
||||
*
|
||||
* @param int $position
|
||||
* @param int $answer
|
||||
* @param int $questionId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCorrect($position, $answer, $questionId)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
$count = $em
|
||||
->createQuery('
|
||||
SELECT COUNT(a) From ChamiloCourseBundle:CQuizAnswer a
|
||||
WHERE a.iid = :position AND a.correct = :answer AND a.questionId = :question
|
||||
')
|
||||
->setParameters([
|
||||
'position' => $position,
|
||||
'answer' => $answer,
|
||||
'question' => $questionId,
|
||||
])
|
||||
->getSingleScalarResult();
|
||||
|
||||
return $count ? true : false;
|
||||
}
|
||||
}
|
||||
303
main/exercise/multiple_answer.class.php
Normal file
303
main/exercise/multiple_answer.class.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Class MultipleAnswer.
|
||||
*
|
||||
* This class allows to instantiate an object of type MULTIPLE_ANSWER (MULTIPLE CHOICE, MULTIPLE ANSWER),
|
||||
* extending the class question
|
||||
*
|
||||
* @author Eric Marguin
|
||||
*/
|
||||
class MultipleAnswer extends Question
|
||||
{
|
||||
public $typePicture = 'mcma.png';
|
||||
public $explanationLangVar = 'MultipleSelect';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MULTIPLE_ANSWER;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$editorConfig = [
|
||||
'ToolbarSet' => 'TestProposedAnswer',
|
||||
'Width' => '100%',
|
||||
'Height' => '125',
|
||||
];
|
||||
|
||||
// The previous default value was 2. See task #1759.
|
||||
$nb_answers = isset($_POST['nb_answers']) ? $_POST['nb_answers'] : 4;
|
||||
$nb_answers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
|
||||
|
||||
$obj_ex = Session::read('objExercise');
|
||||
|
||||
$form->addHeader(get_lang('Answers'));
|
||||
|
||||
$html = '<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10">'.get_lang('Number').'</th>
|
||||
<th width="10">'.get_lang('True').'</th>
|
||||
<th width="50%">'.get_lang('Answer').'</th>
|
||||
<th width="50%">'.get_lang('Comment').'</th>
|
||||
<th width="10">'.get_lang('Weighting').'</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
$form->addHtml($html);
|
||||
|
||||
$defaults = [];
|
||||
$correct = 0;
|
||||
$answer = false;
|
||||
if (!empty($this->iid)) {
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) {
|
||||
$nb_answers = $answer->nbrAnswers;
|
||||
}
|
||||
}
|
||||
|
||||
$form->addElement('hidden', 'nb_answers');
|
||||
$boxes_names = [];
|
||||
|
||||
if ($nb_answers < 1) {
|
||||
$nb_answers = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'));
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$form->addHtml('<tr>');
|
||||
if (is_object($answer)) {
|
||||
$defaults['answer['.$i.']'] = $answer->answer[$i];
|
||||
$defaults['comment['.$i.']'] = $answer->comment[$i];
|
||||
$defaults['weighting['.$i.']'] = float_format($answer->weighting[$i], 1);
|
||||
$defaults['correct['.$i.']'] = $answer->correct[$i];
|
||||
} else {
|
||||
$defaults['answer[1]'] = get_lang('DefaultMultipleAnswer2');
|
||||
$defaults['comment[1]'] = get_lang('DefaultMultipleComment2');
|
||||
$defaults['correct[1]'] = true;
|
||||
$defaults['weighting[1]'] = 10;
|
||||
|
||||
$defaults['answer[2]'] = get_lang('DefaultMultipleAnswer1');
|
||||
$defaults['comment[2]'] = get_lang('DefaultMultipleComment1');
|
||||
$defaults['correct[2]'] = false;
|
||||
$defaults['weighting[2]'] = -5;
|
||||
}
|
||||
$renderer = &$form->defaultRenderer();
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'correct['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'counter['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'answer['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'comment['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'weighting['.$i.']'
|
||||
);
|
||||
|
||||
$answer_number = $form->addElement('text', 'counter['.$i.']', null, 'value="'.$i.'"');
|
||||
$answer_number->freeze();
|
||||
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'correct['.$i.']',
|
||||
null,
|
||||
null,
|
||||
'class="checkbox" style="margin-left: 0em;"'
|
||||
);
|
||||
$boxes_names[] = 'correct['.$i.']';
|
||||
|
||||
$form->addHtmlEditor("answer[$i]", null, null, false, $editorConfig);
|
||||
$form->addRule('answer['.$i.']', get_lang('ThisFieldIsRequired'), 'required');
|
||||
|
||||
$form->addHtmlEditor("comment[$i]", null, null, false, $editorConfig);
|
||||
|
||||
$form->addElement('text', 'weighting['.$i.']', null, ['style' => "width: 60px;", 'value' => '0']);
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</tbody>');
|
||||
$form->addHtml('</table>');
|
||||
|
||||
$form->add_multiple_required_rule(
|
||||
$boxes_names,
|
||||
get_lang('ChooseAtLeastOneCheckbox'),
|
||||
'multiple_required'
|
||||
);
|
||||
|
||||
$buttonGroup = [];
|
||||
global $text;
|
||||
if ($obj_ex->edit_exercise_in_lp == true ||
|
||||
(empty($this->exerciseList) && empty($obj_ex->iid))
|
||||
) {
|
||||
// setting the save button here and not in the question class.php
|
||||
$buttonGroup[] = $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true);
|
||||
$buttonGroup[] = $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true);
|
||||
$buttonGroup[] = $form->addButton(
|
||||
'submitQuestion',
|
||||
$text,
|
||||
'check',
|
||||
'primary',
|
||||
'default',
|
||||
null,
|
||||
['id' => 'submit-question'],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$form->addGroup($buttonGroup);
|
||||
|
||||
$defaults['correct'] = $correct;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
$form->setConstants(['nb_answers' => $nb_answers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate question answers before saving.
|
||||
*
|
||||
* @param object $form The form object.
|
||||
*
|
||||
* @return array|bool True if valid, or an array with errors if invalid.
|
||||
*/
|
||||
public function validateAnswers($form)
|
||||
{
|
||||
$nb_answers = $form->getSubmitValue('nb_answers');
|
||||
$hasCorrectAnswer = false;
|
||||
$hasValidWeighting = false;
|
||||
$errors = [];
|
||||
$error_fields = [];
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$isCorrect = $form->getSubmitValue("correct[$i]");
|
||||
$weighting = trim($form->getSubmitValue("weighting[$i]") ?? '');
|
||||
|
||||
if ($isCorrect) {
|
||||
$hasCorrectAnswer = true;
|
||||
|
||||
if (is_numeric($weighting) && floatval($weighting) > 0) {
|
||||
$hasValidWeighting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasCorrectAnswer) {
|
||||
$errors[] = get_lang('AtLeastOneCorrectAnswerRequired');
|
||||
$error_fields[] = "correct";
|
||||
}
|
||||
|
||||
if ($hasCorrectAnswer && !$hasValidWeighting) {
|
||||
// Only show if at least one correct answer exists but its weighting is not valid
|
||||
$errors[] = get_lang('AtLeastOneCorrectAnswerMustHaveAPositiveScore');
|
||||
}
|
||||
|
||||
return empty($errors) ? true : ['errors' => $errors, 'error_fields' => $error_fields];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$validationResult = $this->validateAnswers($form);
|
||||
if ($validationResult !== true) {
|
||||
Display::addFlash(Display::return_message(implode("<br>", $validationResult['errors']), 'error'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$questionWeighting = 0;
|
||||
$objAnswer = new Answer($this->iid);
|
||||
$nb_answers = $form->getSubmitValue('nb_answers');
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$answer = trim(str_replace(['<p>', '</p>'], '', $form->getSubmitValue('answer['.$i.']')));
|
||||
$comment = trim(str_replace(['<p>', '</p>'], '', $form->getSubmitValue('comment['.$i.']')));
|
||||
$weighting = trim($form->getSubmitValue('weighting['.$i.']'));
|
||||
$goodAnswer = trim($form->getSubmitValue('correct['.$i.']'));
|
||||
|
||||
if ($goodAnswer) {
|
||||
$weighting = abs($weighting);
|
||||
} else {
|
||||
$weighting = abs($weighting);
|
||||
$weighting = -$weighting;
|
||||
}
|
||||
if ($weighting > 0) {
|
||||
$questionWeighting += $weighting;
|
||||
}
|
||||
$objAnswer->createAnswer(
|
||||
$answer,
|
||||
$goodAnswer,
|
||||
$comment,
|
||||
$weighting,
|
||||
$i
|
||||
);
|
||||
}
|
||||
|
||||
// saves the answers into the data base
|
||||
$objAnswer->save();
|
||||
|
||||
// sets the total weighting of the question
|
||||
$this->updateWeighting($questionWeighting);
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'"><tr>';
|
||||
|
||||
$header .= '<th>'.get_lang('Choice').'</th>';
|
||||
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
|
||||
$header .= '<th>'.get_lang('Answer').'</th>';
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
}
|
||||
|
||||
if (false === $exercise->hideComment) {
|
||||
$header .= '<th>'.get_lang('Comment').'</th>';
|
||||
}
|
||||
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
256
main/exercise/multiple_answer_combination.class.php
Normal file
256
main/exercise/multiple_answer_combination.class.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Class MultipleAnswerCombination.
|
||||
*
|
||||
* This class allows to instantiate an object of type
|
||||
* MULTIPLE_ANSWER (MULTIPLE CHOICE, MULTIPLE ANSWER),
|
||||
* extending the class question
|
||||
*
|
||||
* @author Eric Marguin
|
||||
*/
|
||||
class MultipleAnswerCombination extends Question
|
||||
{
|
||||
public $typePicture = 'mcmac.png';
|
||||
public $explanationLangVar = 'MultipleSelectCombination';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MULTIPLE_ANSWER_COMBINATION;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$nb_answers = isset($_POST['nb_answers']) ? $_POST['nb_answers'] : 2;
|
||||
$nb_answers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
|
||||
$obj_ex = Session::read('objExercise');
|
||||
|
||||
$html = '<table class="table table-striped table-hover">';
|
||||
$html .= '<thead>';
|
||||
$html .= '<tr>';
|
||||
$html .= '<th width="10">'.get_lang('Number').'</th>';
|
||||
$html .= '<th width="10">'.get_lang('True').'</th>';
|
||||
$html .= '<th width="50%">'.get_lang('Answer').'</th>';
|
||||
$html .= '<th width="50%">'.get_lang('Comment').'</th>';
|
||||
$html .= '</tr>';
|
||||
$html .= '</thead>';
|
||||
$html .= '<tbody>';
|
||||
|
||||
$form->addHeader(get_lang('Answers'));
|
||||
$form->addHtml($html);
|
||||
|
||||
$defaults = [];
|
||||
$correct = 0;
|
||||
$answer = false;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) {
|
||||
$nb_answers = $answer->nbrAnswers;
|
||||
}
|
||||
}
|
||||
|
||||
$form->addElement('hidden', 'nb_answers');
|
||||
$boxes_names = [];
|
||||
|
||||
if ($nb_answers < 1) {
|
||||
$nb_answers = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'));
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$form->addHtml('<tr>');
|
||||
|
||||
if (is_object($answer)) {
|
||||
$defaults['answer['.$i.']'] = $answer->answer[$i];
|
||||
$defaults['comment['.$i.']'] = $answer->comment[$i];
|
||||
$defaults['weighting['.$i.']'] = float_format($answer->weighting[$i], 1);
|
||||
$defaults['correct['.$i.']'] = $answer->correct[$i];
|
||||
} else {
|
||||
$defaults['answer[1]'] = get_lang('DefaultMultipleAnswer2');
|
||||
$defaults['comment[1]'] = get_lang('DefaultMultipleComment2');
|
||||
$defaults['correct[1]'] = true;
|
||||
$defaults['weighting[1]'] = 10;
|
||||
|
||||
$defaults['answer[2]'] = get_lang('DefaultMultipleAnswer1');
|
||||
$defaults['comment[2]'] = get_lang('DefaultMultipleComment1');
|
||||
$defaults['correct[2]'] = false;
|
||||
}
|
||||
|
||||
$renderer = &$form->defaultRenderer();
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'correct['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'counter['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'answer['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'comment['.$i.']'
|
||||
);
|
||||
|
||||
$answer_number = $form->addElement('text', 'counter['.$i.']', null, 'value="'.$i.'"');
|
||||
$answer_number->freeze();
|
||||
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'correct['.$i.']',
|
||||
null,
|
||||
null,
|
||||
'class="checkbox" style="margin-left: 0em;"'
|
||||
);
|
||||
$boxes_names[] = 'correct['.$i.']';
|
||||
|
||||
$form->addHtmlEditor(
|
||||
"answer[$i]",
|
||||
'',
|
||||
true,
|
||||
false,
|
||||
['ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100']
|
||||
);
|
||||
|
||||
$form->addHtmlEditor(
|
||||
"comment[$i]",
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
['ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100']
|
||||
);
|
||||
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addElement('html', '</tbody></table>');
|
||||
$form->add_multiple_required_rule(
|
||||
$boxes_names,
|
||||
get_lang('ChooseAtLeastOneCheckbox'),
|
||||
'multiple_required'
|
||||
);
|
||||
|
||||
//only 1 answer the all deal ...
|
||||
$form->addText('weighting[1]', get_lang('Score'), false, ['value' => 10]);
|
||||
|
||||
global $text;
|
||||
if ($obj_ex->edit_exercise_in_lp == true ||
|
||||
(empty($this->exerciseList) && empty($obj_ex->iid))
|
||||
) {
|
||||
// setting the save button here and not in the question class.php
|
||||
$buttonGroup = [
|
||||
$form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true),
|
||||
$form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true),
|
||||
$form->addButtonSave($text, 'submitQuestion', true),
|
||||
];
|
||||
|
||||
$form->addGroup($buttonGroup);
|
||||
}
|
||||
|
||||
$defaults['correct'] = $correct;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults($defaults);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
}
|
||||
|
||||
$form->setConstants(['nb_answers' => $nb_answers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$questionWeighting = 0;
|
||||
$objAnswer = new Answer($this->iid);
|
||||
$nb_answers = $form->getSubmitValue('nb_answers');
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$answer = trim($form->getSubmitValue('answer['.$i.']'));
|
||||
$comment = trim($form->getSubmitValue('comment['.$i.']'));
|
||||
if ($i == 1) {
|
||||
$weighting = trim($form->getSubmitValue('weighting['.$i.']'));
|
||||
} else {
|
||||
$weighting = 0;
|
||||
}
|
||||
$goodAnswer = trim($form->getSubmitValue('correct['.$i.']'));
|
||||
|
||||
if ($goodAnswer) {
|
||||
$weighting = abs($weighting);
|
||||
} else {
|
||||
// $weighting = -$weighting;
|
||||
$weighting = abs($weighting);
|
||||
}
|
||||
if ($weighting > 0) {
|
||||
$questionWeighting += $weighting;
|
||||
}
|
||||
$objAnswer->createAnswer(
|
||||
$answer,
|
||||
$goodAnswer,
|
||||
$comment,
|
||||
$weighting,
|
||||
$i
|
||||
);
|
||||
}
|
||||
|
||||
// saves the answers into the data base
|
||||
$objAnswer->save();
|
||||
|
||||
// sets the total weighting of the question
|
||||
$this->updateWeighting($questionWeighting);
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'"><tr>';
|
||||
|
||||
if (!in_array($exercise->results_disabled, [
|
||||
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
|
||||
])
|
||||
) {
|
||||
$header .= '<th>'.get_lang('Choice').'</th>';
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
}
|
||||
|
||||
$header .= '<th>'.get_lang('Answer').'</th>';
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
}
|
||||
|
||||
if (false === $exercise->hideComment) {
|
||||
$header .= '<th>'.get_lang('Comment').'</th>';
|
||||
}
|
||||
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class MultipleAnswerCombinationTrueFalse.
|
||||
*
|
||||
* This class allows to instantiate an object of type MULTIPLE_ANSWER (MULTIPLE CHOICE, MULTIPLE ANSWER),
|
||||
* extending the class question
|
||||
*
|
||||
* @author Eric Marguin
|
||||
*/
|
||||
class MultipleAnswerCombinationTrueFalse extends MultipleAnswerCombination
|
||||
{
|
||||
public $typePicture = 'mcmaco.png';
|
||||
public $explanationLangVar = 'MultipleAnswerCombinationTrueFalse';
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE;
|
||||
$this->isContent = $this->getIsContent();
|
||||
$this->options = [
|
||||
'1' => get_lang('True'),
|
||||
'0' => get_lang('False'),
|
||||
'2' => get_lang('DontKnow'),
|
||||
];
|
||||
}
|
||||
}
|
||||
219
main/exercise/multiple_answer_dropdown_admin.php
Normal file
219
main/exercise/multiple_answer_dropdown_admin.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
|
||||
$questionId = (int) $_GET['mad_admin'];
|
||||
|
||||
$exerciseId = $exerciseId ?? 0;
|
||||
/** @var Exercise $objExercise */
|
||||
$objExercise = $objExercise ?? null;
|
||||
/** @var Question $objQuestion */
|
||||
$objQuestion = $objQuestion ?? null;
|
||||
|
||||
if (!is_object($objQuestion)) {
|
||||
$objQuestion = Question::read($questionId);
|
||||
}
|
||||
|
||||
$isGlobal = MULTIPLE_ANSWER_DROPDOWN_COMBINATION === (int) $objQuestion->type;
|
||||
|
||||
$objAnswer = new Answer($objQuestion->iid, 0, $objExercise);
|
||||
$options = [];
|
||||
|
||||
for ($i = 1; $i <= $objAnswer->nbrAnswers; $i++) {
|
||||
$options[$objAnswer->iid[$i]] = $objAnswer->answer[$i];
|
||||
}
|
||||
|
||||
$webCodePath = api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'&';
|
||||
$adminUrl = $webCodePath.http_build_query(['exerciseId' => $exerciseId]);
|
||||
|
||||
$httpRequest = HttpRequest::createFromGlobals();
|
||||
$submitAnswers = $httpRequest->request->has('submitAnswers');
|
||||
|
||||
if ($submitAnswers) {
|
||||
$questionAnswers = array_map(
|
||||
'intval',
|
||||
(array) $httpRequest->request->get('answer', [])
|
||||
);
|
||||
|
||||
$tblQuizAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER);
|
||||
|
||||
Database::query(
|
||||
"UPDATE $tblQuizAnswer SET correct = 0, ponderation = 0 WHERE question_id = ".$objQuestion->iid
|
||||
);
|
||||
Database::query(
|
||||
"UPDATE $tblQuizAnswer SET correct = 1
|
||||
WHERE question_id = {$objQuestion->iid} AND iid IN (".implode(', ', $questionAnswers).")"
|
||||
);
|
||||
|
||||
if ($isGlobal) {
|
||||
$questionWeighting = (float) $httpRequest->request->get('weighting', 0);
|
||||
} else {
|
||||
$questionWeighting = 0;
|
||||
$choiceWeighting = array_map(
|
||||
'intval',
|
||||
(array) $httpRequest->request->get('c_weighting', [])
|
||||
);
|
||||
|
||||
foreach ($questionAnswers as $key => $questionAnswer) {
|
||||
if (empty($choiceWeighting[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$questionWeighting += $choiceWeighting[$key];
|
||||
|
||||
Database::query(
|
||||
"UPDATE $tblQuizAnswer SET ponderation = {$choiceWeighting[$key]}
|
||||
WHERE question_id = {$objQuestion->iid} AND iid = $questionAnswer"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$objQuestion->updateWeighting($questionWeighting);
|
||||
$objQuestion->save($objExercise);
|
||||
|
||||
echo '<script type="text/javascript">window.location.href="'.$adminUrl.'&message=ItemUpdated"</script>';
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($questionId) {
|
||||
$answers = [];
|
||||
|
||||
for ($i = 1; $i <= $objAnswer->nbrAnswers; $i++) {
|
||||
if (false === (bool) $objAnswer->correct[$i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$answers[] = [
|
||||
$objAnswer->iid[$i],
|
||||
$objAnswer->answer[$i],
|
||||
$objAnswer->weighting[$i],
|
||||
];
|
||||
}
|
||||
|
||||
$selfUrl = $webCodePath.http_build_query(['mad_admin' => $questionId, 'exerciseId' => $exerciseId]);
|
||||
|
||||
echo Display::page_header(
|
||||
get_lang('Question').': '.$objQuestion->selectTitle()
|
||||
); ?>
|
||||
<form action="<?php echo $selfUrl; ?>" class="form-horizontal" method="post">
|
||||
<div class="form-group">
|
||||
<label for="option" class="col-sm-2 control-label"><?php echo get_lang('Answer'); ?></label>
|
||||
<div class="col-sm-8">
|
||||
<?php echo Display::select(
|
||||
'option',
|
||||
$options,
|
||||
-1,
|
||||
['data-live-search' => 'true', 'class' => 'form-control selectpicker']
|
||||
); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="button" class="btn btn-default" name="add_answers">
|
||||
<em class="fa fa-plus fa-fw" aria-hidden="true"></em>
|
||||
<?php echo get_lang('Add'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend><?php echo get_lang('Answers'); ?></legend>
|
||||
<table class="table table-striped table-hover table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-right"><?php echo get_lang('Number'); ?></th>
|
||||
<th style="width: 85%;"><?php echo get_lang('Answer'); ?></th>
|
||||
<?php if (!$isGlobal) { ?>
|
||||
<th class="text-right"><?php echo get_lang('Weighting'); ?></th>
|
||||
<?php } ?>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
<?php if ($isGlobal) { ?>
|
||||
<div class="form-group">
|
||||
<label for="weighting" class="control-label col-sm-2"><?php echo get_lang('Weighting'); ?></label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" required min="0" class="form-control" step="any" id="weighting" name="weighting"
|
||||
value="<?php echo $objQuestion->weighting; ?>">
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary" name="submitAnswers" value="submitAnswers">
|
||||
<em class="fa fa-save fa-fw" aria-hidden="true"></em>
|
||||
<?php echo get_lang('AddQuestionToExercise'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
$(function () {
|
||||
var lines = <?php echo json_encode($options); ?>;
|
||||
var answers = <?php echo json_encode($answers); ?>;
|
||||
|
||||
var $txtOption = $('#option');
|
||||
var $tBody = $('table tbody');
|
||||
|
||||
$('[name="add_answers"]').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var selected = $txtOption.val();
|
||||
|
||||
if ($txtOption.val() < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
answers.push([selected, lines[selected], 0]);
|
||||
|
||||
$txtOption.val(-1).selectpicker('refresh');
|
||||
|
||||
renderList();
|
||||
});
|
||||
|
||||
$tBody.on('click', '.btn-remove', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var index = $(this).data('index');
|
||||
|
||||
answers.splice(index, 1);
|
||||
|
||||
renderList();
|
||||
});
|
||||
|
||||
function renderList () {
|
||||
var html = '';
|
||||
|
||||
$.each(answers, function (key, line) {
|
||||
var counter = key + 1;
|
||||
|
||||
html += '<tr><td class="text-right">'
|
||||
+ counter + "\n"
|
||||
+ '<input type="hidden" name="counter[]" value="' + counter + '">'
|
||||
+ '</td><td>'
|
||||
+ line[1] + "\n"
|
||||
+ '<input type="hidden" name="answer[]" value="' + line[0] + '">'
|
||||
+ '</td><td class="text-right">'
|
||||
<?php if (!$isGlobal) { ?>
|
||||
+ '<input type="number" required min="0" class="form-control" step="any" name="c_weighting[]" value="' + line[2] + '">'
|
||||
+ '</td><td class="text-right">'
|
||||
<?php } ?>
|
||||
+ '<button type="button" class="btn btn-default btn-remove" data-index="' + key + '" aria-label="<?php echo get_lang('Remove'); ?>">'
|
||||
+ '<?php echo Display::returnFontAwesomeIcon('minus', '', true); ?>'
|
||||
+ '</button>'
|
||||
+ '</td></tr>';
|
||||
});
|
||||
|
||||
$tBody.html(html);
|
||||
}
|
||||
|
||||
renderList();
|
||||
})
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
356
main/exercise/multiple_answer_true_false.class.php
Normal file
356
main/exercise/multiple_answer_true_false.class.php
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Class MultipleAnswerTrueFalse
|
||||
* This class allows to instantiate an object of type MULTIPLE_ANSWER
|
||||
* (MULTIPLE CHOICE, MULTIPLE ANSWER), extending the class question.
|
||||
*
|
||||
* @author Julio Montoya
|
||||
*/
|
||||
class MultipleAnswerTrueFalse extends Question
|
||||
{
|
||||
public $typePicture = 'mcmao.png';
|
||||
public $explanationLangVar = 'MultipleAnswerTrueFalse';
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = MULTIPLE_ANSWER_TRUE_FALSE;
|
||||
$this->isContent = $this->getIsContent();
|
||||
$this->options = [1 => 'True', 2 => 'False', 3 => 'DoubtScore'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$nb_answers = isset($_POST['nb_answers']) ? $_POST['nb_answers'] : 4;
|
||||
// The previous default value was 2. See task #1759.
|
||||
$nb_answers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
|
||||
|
||||
$course_id = api_get_course_int_id();
|
||||
$obj_ex = Session::read('objExercise');
|
||||
$renderer = &$form->defaultRenderer();
|
||||
$defaults = [];
|
||||
|
||||
$html = '<table class="table table-striped table-hover">';
|
||||
$html .= '<thead>';
|
||||
$html .= '<tr>';
|
||||
$html .= '<th width="10px">'.get_lang('Number').'</th>';
|
||||
$html .= '<th width="10px">'.get_lang('True').'</th>';
|
||||
$html .= '<th width="10px">'.get_lang('False').'</th>';
|
||||
$html .= '<th width="50%">'.get_lang('Answer').'</th>';
|
||||
|
||||
// show column comment when feedback is enable
|
||||
if ($obj_ex->getFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
|
||||
$html .= '<th width="50%">'.get_lang('Comment').'</th>';
|
||||
}
|
||||
|
||||
$html .= '</tr>';
|
||||
$html .= '</thead>';
|
||||
$html .= '<tbody>';
|
||||
|
||||
$form->addHeader(get_lang('Answers'));
|
||||
$form->addHtml($html);
|
||||
|
||||
$answer = null;
|
||||
|
||||
if (!empty($this->iid)) {
|
||||
$answer = new Answer($this->iid);
|
||||
$answer->read();
|
||||
|
||||
if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) {
|
||||
$nb_answers = $answer->nbrAnswers;
|
||||
}
|
||||
}
|
||||
|
||||
$form->addElement('hidden', 'nb_answers');
|
||||
if ($nb_answers < 1) {
|
||||
$nb_answers = 1;
|
||||
echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'));
|
||||
}
|
||||
|
||||
// Can be more options
|
||||
$optionData = Question::readQuestionOption($this->iid, $course_id);
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$form->addHtml('<tr>');
|
||||
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'correct['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'counter['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'answer['.$i.']'
|
||||
);
|
||||
$renderer->setElementTemplate(
|
||||
'<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
|
||||
'comment['.$i.']'
|
||||
);
|
||||
|
||||
$answer_number = $form->addElement(
|
||||
'text',
|
||||
'counter['.$i.']',
|
||||
null,
|
||||
'value="'.$i.'"'
|
||||
);
|
||||
|
||||
$answer_number->freeze();
|
||||
|
||||
if (is_object($answer)) {
|
||||
$defaults['answer['.$i.']'] = $answer->answer[$i];
|
||||
$defaults['comment['.$i.']'] = $answer->comment[$i];
|
||||
$correct = $answer->correct[$i];
|
||||
$defaults['correct['.$i.']'] = $correct;
|
||||
|
||||
$j = 1;
|
||||
if (!empty($optionData)) {
|
||||
foreach ($optionData as $id => $data) {
|
||||
$rdoCorrect = $form->addElement('radio', 'correct['.$i.']', null, null, $id);
|
||||
|
||||
if (isset($_POST['correct']) && isset($_POST['correct'][$i]) && $id == $_POST['correct'][$i]) {
|
||||
$rdoCorrect->setValue(Security::remove_XSS($_POST['correct'][$i]));
|
||||
}
|
||||
|
||||
$j++;
|
||||
if ($j == 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$form->addElement('radio', 'correct['.$i.']', null, null, 1);
|
||||
$form->addElement('radio', 'correct['.$i.']', null, null, 2);
|
||||
}
|
||||
|
||||
$form->addHtmlEditor(
|
||||
"answer[$i]",
|
||||
get_lang('ThisFieldIsRequired'),
|
||||
true,
|
||||
false,
|
||||
['ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100']
|
||||
);
|
||||
|
||||
if (isset($_POST['answer']) && isset($_POST['answer'][$i])) {
|
||||
$form->getElement("answer[$i]")->setValue(Security::remove_XSS($_POST['answer'][$i]));
|
||||
}
|
||||
|
||||
// show comment when feedback is enable
|
||||
if ($obj_ex->getFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
|
||||
$txtComment = $form->addElement(
|
||||
'html_editor',
|
||||
'comment['.$i.']',
|
||||
null,
|
||||
[],
|
||||
[
|
||||
'ToolbarSet' => 'TestProposedAnswer',
|
||||
'Width' => '100%',
|
||||
'Height' => '100',
|
||||
]
|
||||
);
|
||||
$form->applyFilter("comment[$i]", 'attr_on_filter');
|
||||
|
||||
if (isset($_POST['comment']) && isset($_POST['comment'][$i])) {
|
||||
$txtComment->setValue(Security::remove_XSS($_POST['comment'][$i]));
|
||||
}
|
||||
}
|
||||
|
||||
$form->addHtml('</tr>');
|
||||
}
|
||||
|
||||
$form->addHtml('</tbody></table>');
|
||||
|
||||
$correctInputTemplate = '<div class="form-group">';
|
||||
$correctInputTemplate .= '<label class="col-sm-2 control-label">';
|
||||
$correctInputTemplate .= '<span class="form_required">*</span>'.get_lang('Score');
|
||||
$correctInputTemplate .= '</label>';
|
||||
$correctInputTemplate .= '<div class="col-sm-8">';
|
||||
$correctInputTemplate .= '<table>';
|
||||
$correctInputTemplate .= '<tr>';
|
||||
$correctInputTemplate .= '<td>';
|
||||
$correctInputTemplate .= get_lang('Correct').'{element}';
|
||||
$correctInputTemplate .= '<!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->';
|
||||
$correctInputTemplate .= '</td>';
|
||||
|
||||
$wrongInputTemplate = '<td>';
|
||||
$wrongInputTemplate .= get_lang('Wrong').'{element}';
|
||||
$wrongInputTemplate .= '<!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->';
|
||||
$wrongInputTemplate .= '</td>';
|
||||
|
||||
$doubtScoreInputTemplate = '<td>'.get_lang('DoubtScore').'<br>{element}';
|
||||
$doubtScoreInputTemplate .= '<!-- BEGIN error --><span class="form_error">{error}</span><!-- END error -->';
|
||||
$doubtScoreInputTemplate .= '</td>';
|
||||
$doubtScoreInputTemplate .= '</tr>';
|
||||
$doubtScoreInputTemplate .= '</table>';
|
||||
$doubtScoreInputTemplate .= '</div>';
|
||||
$doubtScoreInputTemplate .= '</div>';
|
||||
|
||||
$renderer->setElementTemplate($correctInputTemplate, 'option[1]');
|
||||
$renderer->setElementTemplate($wrongInputTemplate, 'option[2]');
|
||||
$renderer->setElementTemplate($doubtScoreInputTemplate, 'option[3]');
|
||||
|
||||
// 3 scores
|
||||
$txtOption1 = $form->addElement('text', 'option[1]', get_lang('Correct'), ['class' => 'span1', 'value' => '1']);
|
||||
$txtOption2 = $form->addElement('text', 'option[2]', get_lang('Wrong'), ['class' => 'span1', 'value' => '-0.5']);
|
||||
$txtOption3 = $form->addElement('text', 'option[3]', get_lang('DoubtScore'), ['class' => 'span1', 'value' => '0']);
|
||||
|
||||
$form->addRule('option[1]', get_lang('ThisFieldIsRequired'), 'required');
|
||||
$form->addRule('option[2]', get_lang('ThisFieldIsRequired'), 'required');
|
||||
$form->addRule('option[3]', get_lang('ThisFieldIsRequired'), 'required');
|
||||
|
||||
$form->addElement('hidden', 'options_count', 3);
|
||||
|
||||
// Extra values True, false, Dont known
|
||||
if (!empty($this->extra)) {
|
||||
$scores = explode(':', $this->extra);
|
||||
|
||||
if (!empty($scores)) {
|
||||
$txtOption1->setValue($scores[0]);
|
||||
$txtOption2->setValue($scores[1]);
|
||||
$txtOption3->setValue($scores[2]);
|
||||
}
|
||||
}
|
||||
|
||||
global $text;
|
||||
if ($obj_ex->edit_exercise_in_lp == true ||
|
||||
(empty($this->exerciseList) && empty($obj_ex->iid))
|
||||
) {
|
||||
// setting the save button here and not in the question class.php
|
||||
$buttonGroup[] = $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true);
|
||||
$buttonGroup[] = $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true);
|
||||
$buttonGroup[] = $form->addButtonSave($text, 'submitQuestion', true);
|
||||
|
||||
$form->addGroup($buttonGroup);
|
||||
}
|
||||
|
||||
if (!empty($this->iid) && !$form->isSubmitted()) {
|
||||
$form->setDefaults($defaults);
|
||||
}
|
||||
$form->setConstants(['nb_answers' => $nb_answers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$questionWeighting = 0;
|
||||
$objAnswer = new Answer($this->iid);
|
||||
$nb_answers = $form->getSubmitValue('nb_answers');
|
||||
$course_id = api_get_course_int_id();
|
||||
|
||||
$correct = [];
|
||||
$options = Question::readQuestionOption($this->iid, $course_id);
|
||||
|
||||
if (!empty($options)) {
|
||||
foreach ($options as $optionData) {
|
||||
$id = $optionData['iid'];
|
||||
unset($optionData['iid']);
|
||||
Question::updateQuestionOption($id, $optionData, $course_id);
|
||||
}
|
||||
} else {
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$last_id = Question::saveQuestionOption(
|
||||
$this->iid,
|
||||
$this->options[$i],
|
||||
$course_id,
|
||||
$i
|
||||
);
|
||||
$correct[$i] = $last_id;
|
||||
}
|
||||
}
|
||||
|
||||
/* Getting quiz_question_options (true, false, doubt) because
|
||||
it's possible that there are more options in the future */
|
||||
$new_options = Question::readQuestionOption($this->iid, $course_id);
|
||||
$sortedByPosition = [];
|
||||
foreach ($new_options as $item) {
|
||||
$sortedByPosition[$item['position']] = $item;
|
||||
}
|
||||
|
||||
/* Saving quiz_question.extra values that has the correct scores of
|
||||
the true, false, doubt options registered in this format
|
||||
XX:YY:ZZZ where XX is a float score value.*/
|
||||
$extra_values = [];
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$score = trim($form->getSubmitValue('option['.$i.']'));
|
||||
$extra_values[] = $score;
|
||||
}
|
||||
$this->setExtra(implode(':', $extra_values));
|
||||
|
||||
for ($i = 1; $i <= $nb_answers; $i++) {
|
||||
$answer = trim($form->getSubmitValue('answer['.$i.']'));
|
||||
$comment = trim($form->getSubmitValue('comment['.$i.']'));
|
||||
$goodAnswer = trim($form->getSubmitValue('correct['.$i.']'));
|
||||
if (empty($options)) {
|
||||
//If this is the first time that the question is created when
|
||||
// change the default values from the form 1 and 2 by the correct "option id" registered
|
||||
$goodAnswer = isset($sortedByPosition[$goodAnswer]) ? $sortedByPosition[$goodAnswer]['iid'] : '';
|
||||
}
|
||||
$questionWeighting += $extra_values[0]; //By default 0 has the correct answers
|
||||
$objAnswer->createAnswer($answer, $goodAnswer, $comment, '', $i);
|
||||
}
|
||||
|
||||
// saves the answers into the database
|
||||
$objAnswer->save();
|
||||
// sets the total weighting of the question
|
||||
$this->updateWeighting($questionWeighting);
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'"><tr>';
|
||||
|
||||
if (!in_array($exercise->results_disabled, [
|
||||
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
|
||||
])
|
||||
) {
|
||||
$header .= '<th>'.get_lang('Choice').'</th>';
|
||||
if ($exercise->showExpectedChoiceColumn()) {
|
||||
$header .= '<th>'.get_lang('ExpectedChoice').'</th>';
|
||||
}
|
||||
}
|
||||
|
||||
$header .= '<th>'.get_lang('Answer').'</th>';
|
||||
|
||||
if ($exercise->showExpectedChoice()) {
|
||||
$header .= '<th>'.get_lang('Status').'</th>';
|
||||
}
|
||||
|
||||
if ($exercise->getFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM ||
|
||||
in_array(
|
||||
$exercise->results_disabled,
|
||||
[
|
||||
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
|
||||
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
|
||||
]
|
||||
)
|
||||
) {
|
||||
if (false === $exercise->hideComment) {
|
||||
$header .= '<th>'.get_lang('Comment').'</th>';
|
||||
}
|
||||
}
|
||||
$header .= '</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
324
main/exercise/oral_expression.class.php
Normal file
324
main/exercise/oral_expression.class.php
Normal file
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class OralExpression
|
||||
* This class allows to instantiate an object of type FREE_ANSWER,
|
||||
* extending the class question.
|
||||
*
|
||||
* @author Eric Marguin
|
||||
*/
|
||||
class OralExpression extends Question
|
||||
{
|
||||
public $typePicture = 'audio_question.png';
|
||||
public $explanationLangVar = 'OralExpression';
|
||||
public $available_extensions = ['wav', 'ogg'];
|
||||
private $sessionId;
|
||||
private $userId;
|
||||
private $exerciseId;
|
||||
private $exeId;
|
||||
private $storePath;
|
||||
private $fileName;
|
||||
private $filePath;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = ORAL_EXPRESSION;
|
||||
$this->isContent = $this->getIsContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
$form->addText(
|
||||
'weighting',
|
||||
get_lang('Weighting'),
|
||||
['class' => 'span1']
|
||||
);
|
||||
global $text;
|
||||
// setting the save button here and not in the question class.php
|
||||
$form->addButtonSave($text, 'submitQuestion');
|
||||
if (!empty($this->iid)) {
|
||||
$form->setDefaults(['weighting' => float_format($this->weighting, 1)]);
|
||||
} else {
|
||||
if ($this->isContent == 1) {
|
||||
$form->setDefaults(['weighting' => '10']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
$this->weighting = $form->getSubmitValue('weighting');
|
||||
$this->save($exercise);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function return_header(Exercise $exercise, $counter = null, $score = [])
|
||||
{
|
||||
$score['revised'] = $this->isQuestionWaitingReview($score);
|
||||
$header = parent::return_header($exercise, $counter, $score);
|
||||
$header .= '<table class="'.$this->question_table_class.'">
|
||||
<tr>
|
||||
<th>'.get_lang("Answer").'</th>
|
||||
</tr>';
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the attributes to generate the file path.
|
||||
*/
|
||||
public function initFile(
|
||||
int $sessionId,
|
||||
int $userId,
|
||||
int $exerciseId,
|
||||
int $exeId,
|
||||
int $courseId = 0
|
||||
): void {
|
||||
if (!empty($courseId)) {
|
||||
$this->course = api_get_course_info_by_id($courseId);
|
||||
}
|
||||
$this->sessionId = $sessionId;
|
||||
$this->userId = $userId;
|
||||
$this->exerciseId = 0;
|
||||
if (!empty($exerciseId)) {
|
||||
$this->exerciseId = $exerciseId;
|
||||
}
|
||||
$this->exeId = $exeId;
|
||||
$this->storePath = $this->generateDirectory();
|
||||
$this->fileName = $this->generateFileName();
|
||||
$this->filePath = $this->storePath.$this->fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTML code to show the RecordRTC/Wami recorder.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function returnRecorder()
|
||||
{
|
||||
$directory = '/..'.$this->generateRelativeDirectory();
|
||||
$recordAudioView = new Template(
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
$recordAudioView->assign('directory', $directory);
|
||||
$recordAudioView->assign('user_id', $this->userId);
|
||||
$recordAudioView->assign('file_name', $this->fileName);
|
||||
$recordAudioView->assign('question_id', $this->iid);
|
||||
|
||||
$template = $recordAudioView->get_template('exercise/oral_expression.tpl');
|
||||
|
||||
return $recordAudioView->fetch($template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute file path. Return null if the file doesn't exists.
|
||||
*
|
||||
* @param bool $loadFromDatabase
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAbsoluteFilePath($loadFromDatabase = false)
|
||||
{
|
||||
$fileName = $this->fileName;
|
||||
|
||||
if ($loadFromDatabase) {
|
||||
$em = Database::getManager();
|
||||
//Load the real filename just if exists
|
||||
if (isset($this->exeId, $this->userId, $this->iid, $this->sessionId, $this->course['real_id'])) {
|
||||
$result = $em
|
||||
->getRepository('ChamiloCoreBundle:TrackEAttempt')
|
||||
->findOneBy([
|
||||
'exeId' => $this->exeId,
|
||||
'userId' => $this->userId,
|
||||
'questionId' => $this->iid,
|
||||
'sessionId' => $this->sessionId,
|
||||
'cId' => $this->course['real_id'],
|
||||
]);
|
||||
|
||||
if (!$result) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$fileName = $result->getFilename();
|
||||
|
||||
if (empty($fileName)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->storePath.$result->getFilename();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->available_extensions as $extension) {
|
||||
$audioFile = $this->storePath.$fileName;
|
||||
$file = "$audioFile.$extension";
|
||||
|
||||
if (is_file($file)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
// Function handle_uploaded_document() adds the session and group id by default.
|
||||
$file = "$audioFile".'__'.$this->sessionId."__0.$extension";
|
||||
|
||||
if (is_file($file)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the audio file. Return null if the file doesn't exists.
|
||||
*
|
||||
* @param bool $loadFromDatabase
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFileUrl($loadFromDatabase = false)
|
||||
{
|
||||
$filePath = $this->getAbsoluteFilePath($loadFromDatabase);
|
||||
|
||||
if (empty($filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
api_get_path(SYS_COURSE_PATH),
|
||||
api_get_path(WEB_COURSE_PATH),
|
||||
$filePath
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tricky stuff to deal with the feedback = 0 in exercises (all question per page).
|
||||
*
|
||||
* @param int $exe_id
|
||||
*/
|
||||
public function replaceWithRealExe($exe_id)
|
||||
{
|
||||
$filename = null;
|
||||
//ugly fix
|
||||
foreach ($this->available_extensions as $extension) {
|
||||
$items = explode('-', $this->fileName);
|
||||
$items[5] = 'temp_exe';
|
||||
$filename = implode('-', $items);
|
||||
|
||||
if (is_file($this->storePath.$filename.'.'.$extension)) {
|
||||
$old_name = $this->storePath.$filename.'.'.$extension;
|
||||
$items = explode('-', $this->fileName);
|
||||
$items[5] = $exe_id;
|
||||
$filename = $filename = implode('-', $items);
|
||||
$new_name = $this->storePath.$filename.'.'.$extension;
|
||||
rename($old_name, $new_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the necessary directory for audios. If them not exists, are created.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generateDirectory()
|
||||
{
|
||||
$this->storePath = api_get_path(SYS_COURSE_PATH).$this->course['path'].'/exercises/';
|
||||
|
||||
if (!is_dir($this->storePath)) {
|
||||
mkdir($this->storePath);
|
||||
}
|
||||
|
||||
if (!is_dir($this->storePath.$this->sessionId)) {
|
||||
mkdir($this->storePath.$this->sessionId);
|
||||
}
|
||||
|
||||
if (!empty($this->exerciseId) && !is_dir($this->storePath.$this->sessionId.'/'.$this->exerciseId)) {
|
||||
mkdir($this->storePath.$this->sessionId.'/'.$this->exerciseId);
|
||||
}
|
||||
|
||||
if (!empty($this->iid) && !is_dir($this->storePath.$this->sessionId.'/'.$this->exerciseId.'/'.$this->iid)) {
|
||||
mkdir($this->storePath.$this->sessionId.'/'.$this->exerciseId.'/'.$this->iid);
|
||||
}
|
||||
|
||||
if (!empty($this->userId) &&
|
||||
!is_dir($this->storePath.$this->sessionId.'/'.$this->exerciseId.'/'.$this->iid.'/'.$this->userId)
|
||||
) {
|
||||
mkdir($this->storePath.$this->sessionId.'/'.$this->exerciseId.'/'.$this->iid.'/'.$this->userId);
|
||||
}
|
||||
|
||||
$params = [
|
||||
$this->sessionId,
|
||||
$this->exerciseId,
|
||||
$this->iid,
|
||||
$this->userId,
|
||||
];
|
||||
|
||||
$this->storePath .= implode('/', $params).'/';
|
||||
|
||||
return $this->storePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the file name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generateFileName()
|
||||
{
|
||||
return implode(
|
||||
'-',
|
||||
[
|
||||
$this->course['real_id'],
|
||||
$this->sessionId,
|
||||
$this->userId,
|
||||
$this->exerciseId,
|
||||
$this->iid,
|
||||
$this->exeId,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a relative directory path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generateRelativeDirectory()
|
||||
{
|
||||
$params = [
|
||||
$this->sessionId,
|
||||
$this->exerciseId,
|
||||
$this->iid,
|
||||
$this->userId,
|
||||
];
|
||||
|
||||
$path = implode('/', $params);
|
||||
$directory = '/exercises/'.$path.'/';
|
||||
|
||||
return $directory;
|
||||
}
|
||||
}
|
||||
611
main/exercise/overview.php
Normal file
611
main/exercise/overview.php
Normal file
@@ -0,0 +1,611 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Exercise preview.
|
||||
*
|
||||
* @author Julio Montoya <gugli100@gmail.com>
|
||||
*/
|
||||
$use_anonymous = true;
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
Exercise::cleanSessionVariables();
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
$js = '<script>'.api_get_language_translate_html().'</script>';
|
||||
$htmlHeadXtra[] = $js;
|
||||
|
||||
// Notice for unauthorized people.
|
||||
api_protect_course_script(true);
|
||||
$sessionId = api_get_session_id();
|
||||
$courseCode = api_get_course_id();
|
||||
$exercise_id = isset($_REQUEST['exerciseId']) ? (int) $_REQUEST['exerciseId'] : 0;
|
||||
|
||||
$objExercise = new Exercise();
|
||||
$result = $objExercise->read($exercise_id, true);
|
||||
|
||||
if (!$result) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
if ('true' === api_get_plugin_setting('positioning', 'tool_enable')) {
|
||||
$plugin = Positioning::create();
|
||||
if ($plugin->blockFinalExercise(api_get_user_id(), $exercise_id, api_get_course_int_id(), $sessionId)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
}
|
||||
|
||||
$learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : null;
|
||||
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : null;
|
||||
$learnpathItemViewId = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : null;
|
||||
$origin = api_get_origin();
|
||||
|
||||
$logInfo = [
|
||||
'tool' => TOOL_QUIZ,
|
||||
'tool_id' => $exercise_id,
|
||||
'action' => $learnpath_id ? 'learnpath_id' : '',
|
||||
'action_details' => $learnpath_id ?: '',
|
||||
];
|
||||
Event::registerLog($logInfo);
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = ['url' => '#', 'name' => Security::remove_XSS($objExercise->selectTitle(true))];
|
||||
|
||||
$time_control = false;
|
||||
$clock_expired_time = ExerciseLib::get_session_time_control_key($objExercise->iid, $learnpath_id, $learnpath_item_id);
|
||||
|
||||
if ($objExercise->expired_time != 0 && !empty($clock_expired_time)) {
|
||||
$time_control = true;
|
||||
}
|
||||
|
||||
$exerciseUrlParams = [
|
||||
'exerciseId' => $objExercise->iid,
|
||||
'learnpath_id' => $learnpath_id,
|
||||
'learnpath_item_id' => $learnpath_item_id,
|
||||
'learnpath_item_view_id' => $learnpathItemViewId,
|
||||
];
|
||||
if (isset($_GET['preview'])) {
|
||||
$exerciseUrlParams['preview'] = 1;
|
||||
}
|
||||
// It is a lti provider
|
||||
if (isset($_GET['lti_launch_id'])) {
|
||||
$exerciseUrlParams['lti_launch_id'] = Security::remove_XSS($_GET['lti_launch_id']);
|
||||
}
|
||||
|
||||
$exercise_url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit.php?'.
|
||||
api_get_cidreq().'&'.http_build_query($exerciseUrlParams);
|
||||
|
||||
if ($time_control) {
|
||||
// Get time left for expiring time
|
||||
$time_left = api_strtotime($clock_expired_time, 'UTC') - time();
|
||||
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/stylesheet/jquery.epiclock.css');
|
||||
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.epiclock.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
|
||||
$htmlHeadXtra[] = $objExercise->showTimeControlJS($time_left, $exercise_url);
|
||||
}
|
||||
|
||||
if (!in_array($origin, ['learnpath', 'embeddable', 'mobileapp', 'iframe'])) {
|
||||
SessionManager::addFlashSessionReadOnly();
|
||||
Display::display_header();
|
||||
} else {
|
||||
$htmlHeadXtra[] = "
|
||||
<style>
|
||||
body { background: none;}
|
||||
</style>
|
||||
";
|
||||
Display::display_reduced_header();
|
||||
}
|
||||
|
||||
if ($origin === 'mobileapp') {
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="javascript:window.history.go(-1);">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), [], 32).'</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
$html = '';
|
||||
$message = '';
|
||||
$html .= '<div class="exercise-overview">';
|
||||
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
|
||||
$hideIp = api_get_configuration_value('exercise_hide_ip');
|
||||
$editLink = '';
|
||||
if ($is_allowed_to_edit) {
|
||||
if ($objExercise->sessionId == $sessionId) {
|
||||
$editLink = Display::url(
|
||||
Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid
|
||||
);
|
||||
}
|
||||
$editLink .= Display::url(
|
||||
Display::return_icon('test_results.png', get_lang('Results'), [], ICON_SIZE_SMALL),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid,
|
||||
['title' => get_lang('Results')]
|
||||
);
|
||||
}
|
||||
|
||||
$iconExercise = Display::return_icon('test-quiz.png', null, [], ICON_SIZE_MEDIUM);
|
||||
|
||||
// Exercise name.
|
||||
if (api_get_configuration_value('save_titles_as_html')) {
|
||||
$html .= Display::div(
|
||||
Security::remove_XSS($objExercise->get_formated_title()).PHP_EOL.$editLink
|
||||
);
|
||||
} else {
|
||||
$html .= Display::page_header(
|
||||
$iconExercise.PHP_EOL.Security::remove_XSS($objExercise->selectTitle()).PHP_EOL.$editLink
|
||||
);
|
||||
}
|
||||
|
||||
// Exercise description.
|
||||
if (!empty($objExercise->description)) {
|
||||
$html .= Display::div(Security::remove_XSS($objExercise->description, COURSEMANAGERLOWSECURITY), ['class' => 'exercise_description']);
|
||||
}
|
||||
|
||||
$exercise_stat_info = $objExercise->get_stat_track_exercise_info(
|
||||
$learnpath_id,
|
||||
$learnpath_item_id,
|
||||
0
|
||||
);
|
||||
|
||||
//1. Check if this is a new attempt or a previous
|
||||
$label = get_lang('StartTest');
|
||||
if ($time_control && !empty($clock_expired_time) || isset($exercise_stat_info['exe_id'])) {
|
||||
$label = get_lang('ContinueTest');
|
||||
}
|
||||
|
||||
if (isset($exercise_stat_info['exe_id'])) {
|
||||
$message = Display::return_message(get_lang('YouTriedToResolveThisExerciseEarlier'));
|
||||
}
|
||||
|
||||
// 2. Exercise button
|
||||
// Notice we not add there the lp_item_view_id because is not already generated
|
||||
$exercise_url_button = Display::url(
|
||||
$label,
|
||||
$exercise_url,
|
||||
['class' => 'btn btn-success btn-large']
|
||||
);
|
||||
|
||||
$btnCheck = '';
|
||||
$quizCheckButtonEnabled = api_get_configuration_value('quiz_check_button_enable');
|
||||
if ($quizCheckButtonEnabled) {
|
||||
$btnCheck = Display::button(
|
||||
'quiz_check_request_button',
|
||||
Display::returnFontAwesomeIcon('spinner', '', true, 'fa-spin hidden').' '.get_lang('TestYourBrowser'),
|
||||
[
|
||||
'type' => 'button',
|
||||
'role' => 'button',
|
||||
'id' => 'quiz-check-request-button',
|
||||
'class' => 'btn btn-default',
|
||||
'data-loading-text' => get_lang('Loading'),
|
||||
'autocomplete' => 'off',
|
||||
]
|
||||
).PHP_EOL.'<strong id="quiz-check-request-text"></strong>';
|
||||
}
|
||||
|
||||
// 3. Checking visibility of the exercise (overwrites the exercise button).
|
||||
$visible_return = $objExercise->is_visible(
|
||||
$learnpath_id,
|
||||
$learnpath_item_id,
|
||||
null,
|
||||
true,
|
||||
$sessionId
|
||||
);
|
||||
|
||||
// Exercise is not visible remove the button
|
||||
if ($visible_return['value'] == false) {
|
||||
if ($is_allowed_to_edit) {
|
||||
$message = Display::return_message(
|
||||
get_lang('ThisItemIsInvisibleForStudentsButYouHaveAccessAsTeacher'),
|
||||
'warning'
|
||||
);
|
||||
} else {
|
||||
$message = $visible_return['message'];
|
||||
$exercise_url_button = null;
|
||||
}
|
||||
}
|
||||
$advancedMessage = RemedialCoursePlugin::create()->getAdvancedCourseList(
|
||||
$objExercise,
|
||||
api_get_user_id(),
|
||||
api_get_session_id(),
|
||||
$learnpath_id ?: 0,
|
||||
$learnpath_item_id ?: 0
|
||||
);
|
||||
if (!empty($advancedMessage)) {
|
||||
$message .= Display::return_message(
|
||||
$advancedMessage,
|
||||
'info',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
$remedialMessage = RemedialCoursePlugin::create()->getRemedialCourseList(
|
||||
$objExercise,
|
||||
api_get_user_id(),
|
||||
api_get_session_id(),
|
||||
false,
|
||||
$learnpath_id ?: 0,
|
||||
$learnpath_item_id ?: 0
|
||||
);
|
||||
|
||||
if (null != $remedialMessage) {
|
||||
$message .= Display::return_message($remedialMessage, 'warning', false);
|
||||
}
|
||||
|
||||
if (!api_is_allowed_to_session_edit()) {
|
||||
$exercise_url_button = null;
|
||||
}
|
||||
|
||||
$attempts = Event::getExerciseResultsByUser(
|
||||
api_get_user_id(),
|
||||
$objExercise->iid,
|
||||
api_get_course_int_id(),
|
||||
api_get_session_id(),
|
||||
$learnpath_id,
|
||||
$learnpath_item_id,
|
||||
'desc'
|
||||
);
|
||||
$counter = count($attempts);
|
||||
$my_attempt_array = [];
|
||||
$table_content = '';
|
||||
|
||||
/* Make a special case for IE, which doesn't seem to be able to handle the
|
||||
* results popup -> send it to the full results page */
|
||||
|
||||
$browser = new Browser();
|
||||
$current_browser = $browser->getBrowser();
|
||||
$url_suffix = '';
|
||||
$btn_class = ' ';
|
||||
if ($current_browser == 'Internet Explorer') {
|
||||
$url_suffix = '&show_headers=1';
|
||||
$btn_class = '';
|
||||
}
|
||||
|
||||
$blockShowAnswers = false;
|
||||
if (in_array(
|
||||
$objExercise->results_disabled,
|
||||
[
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
|
||||
//RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
|
||||
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
|
||||
])
|
||||
) {
|
||||
if (count($attempts) < $objExercise->attempts) {
|
||||
$blockShowAnswers = true;
|
||||
}
|
||||
}
|
||||
|
||||
$certificateBlock = '';
|
||||
if (!empty($attempts)) {
|
||||
$i = $counter;
|
||||
foreach ($attempts as $attempt_result) {
|
||||
if (empty($certificateBlock)) {
|
||||
$certificateBlock = ExerciseLib::generateAndShowCertificateBlock(
|
||||
$attempt_result['exe_result'],
|
||||
$attempt_result['exe_weighting'],
|
||||
$objExercise,
|
||||
$attempt_result['exe_user_id'],
|
||||
$courseCode,
|
||||
$sessionId
|
||||
);
|
||||
}
|
||||
|
||||
$score = ExerciseLib::show_score($attempt_result['exe_result'], $attempt_result['exe_weighting']);
|
||||
$attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?';
|
||||
$attempt_url .= api_get_cidreq().'&show_headers=1&';
|
||||
$attempt_url .= http_build_query(['id' => $attempt_result['exe_id']]);
|
||||
$attempt_url .= $url_suffix;
|
||||
|
||||
$attempt_link = Display::url(
|
||||
get_lang('Show'),
|
||||
$attempt_url,
|
||||
[
|
||||
'class' => $btn_class.'btn btn-default',
|
||||
'data-title' => get_lang('Show'),
|
||||
'data-size' => 'lg',
|
||||
]
|
||||
);
|
||||
|
||||
$teacher_revised = Display::label(get_lang('Validated'), 'success');
|
||||
if ($attempt_result['attempt_revised'] == 0) {
|
||||
$teacher_revised = Display::label(get_lang('NotValidated'), 'info');
|
||||
}
|
||||
$row = [
|
||||
'count' => $i,
|
||||
'date' => api_convert_and_format_date($attempt_result['start_date'], DATE_TIME_FORMAT_LONG),
|
||||
'userIp' => $attempt_result['user_ip'],
|
||||
];
|
||||
if (api_is_anonymous() || $hideIp) {
|
||||
unset($row['userIp']);
|
||||
}
|
||||
$attempt_link .= PHP_EOL.$teacher_revised;
|
||||
|
||||
if (in_array(
|
||||
$objExercise->results_disabled,
|
||||
[
|
||||
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
|
||||
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
|
||||
RESULT_DISABLE_SHOW_SCORE_ONLY,
|
||||
RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
|
||||
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
|
||||
RESULT_DISABLE_RANKING,
|
||||
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
|
||||
]
|
||||
)) {
|
||||
$row['result'] = $score;
|
||||
}
|
||||
|
||||
if (in_array(
|
||||
$objExercise->results_disabled,
|
||||
[
|
||||
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
|
||||
RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
|
||||
RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
|
||||
RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
|
||||
RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
|
||||
RESULT_DISABLE_RANKING,
|
||||
RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
|
||||
]
|
||||
) || (
|
||||
$objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY &&
|
||||
$objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_END
|
||||
)
|
||||
) {
|
||||
if ($blockShowAnswers &&
|
||||
$objExercise->results_disabled != RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK
|
||||
) {
|
||||
$attempt_link = '';
|
||||
}
|
||||
if ($blockShowAnswers == true &&
|
||||
$objExercise->results_disabled == RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK
|
||||
) {
|
||||
if (isset($row['result'])) {
|
||||
unset($row['result']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($objExercise->getResultAccess())) {
|
||||
if (!$objExercise->hasResultsAccess($attempt_result)) {
|
||||
$attempt_link = '';
|
||||
}
|
||||
}
|
||||
$row['attempt_link'] = $attempt_link;
|
||||
}
|
||||
$my_attempt_array[] = $row;
|
||||
$i--;
|
||||
}
|
||||
|
||||
$header_names = [];
|
||||
$table = new HTML_Table(['class' => 'table table-striped table-hover']);
|
||||
// Hiding score and answer.
|
||||
switch ($objExercise->results_disabled) {
|
||||
case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
|
||||
if ($blockShowAnswers) {
|
||||
$header_names = [get_lang('Attempt'), get_lang('StartDate'), get_lang('IP'), get_lang('Details')];
|
||||
} else {
|
||||
$header_names = [
|
||||
get_lang('Attempt'),
|
||||
get_lang('StartDate'),
|
||||
get_lang('IP'),
|
||||
get_lang('Score'),
|
||||
get_lang('Details'),
|
||||
];
|
||||
}
|
||||
break;
|
||||
case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK:
|
||||
case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
|
||||
if ($blockShowAnswers) {
|
||||
$header_names = [get_lang('Attempt'), get_lang('StartDate'), get_lang('IP'), get_lang('Score')];
|
||||
} else {
|
||||
$header_names = [
|
||||
get_lang('Attempt'),
|
||||
get_lang('StartDate'),
|
||||
get_lang('IP'),
|
||||
get_lang('Score'),
|
||||
get_lang('Details'),
|
||||
];
|
||||
}
|
||||
break;
|
||||
case RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS:
|
||||
case RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING:
|
||||
case RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES:
|
||||
case RESULT_DISABLE_RANKING:
|
||||
case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
|
||||
$header_names = [
|
||||
get_lang('Attempt'),
|
||||
get_lang('StartDate'),
|
||||
get_lang('IP'),
|
||||
get_lang('Score'),
|
||||
get_lang('Details'),
|
||||
];
|
||||
break;
|
||||
case RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS:
|
||||
$header_names = [get_lang('Attempt'), get_lang('StartDate'), get_lang('IP')];
|
||||
break;
|
||||
case RESULT_DISABLE_SHOW_SCORE_ONLY:
|
||||
if ($objExercise->getFeedbackType() != EXERCISE_FEEDBACK_TYPE_END) {
|
||||
$header_names = [get_lang('Attempt'), get_lang('StartDate'), get_lang('IP'), get_lang('Score')];
|
||||
} else {
|
||||
$header_names = [
|
||||
get_lang('Attempt'),
|
||||
get_lang('StartDate'),
|
||||
get_lang('IP'),
|
||||
get_lang('Score'),
|
||||
get_lang('Details'),
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (api_is_anonymous() || $hideIp) {
|
||||
unset($header_names[2]); // It removes the 3rd column related to IP
|
||||
}
|
||||
$column = 0;
|
||||
foreach ($header_names as $item) {
|
||||
$table->setHeaderContents(0, $column, $item);
|
||||
$column++;
|
||||
}
|
||||
$row = 1;
|
||||
if (!empty($my_attempt_array)) {
|
||||
foreach ($my_attempt_array as $data) {
|
||||
$column = 0;
|
||||
$table->setCellContents($row, $column, $data);
|
||||
$table->setRowAttributes($row, null, true);
|
||||
$column++;
|
||||
$row++;
|
||||
}
|
||||
}
|
||||
$table_content = $table->toHtml();
|
||||
}
|
||||
|
||||
$selectAttempts = $objExercise->selectAttempts();
|
||||
if ($selectAttempts) {
|
||||
$attempt_message = get_lang('Attempts').' '.$counter.' / '.$selectAttempts;
|
||||
if ($counter == $selectAttempts) {
|
||||
$attempt_message = Display::return_message($attempt_message, 'error');
|
||||
} else {
|
||||
$attempt_message = Display::return_message($attempt_message, 'info');
|
||||
}
|
||||
if ($visible_return['value'] == true) {
|
||||
$message .= $attempt_message;
|
||||
}
|
||||
}
|
||||
|
||||
if ($time_control) {
|
||||
$html .= $objExercise->returnTimeLeftDiv();
|
||||
}
|
||||
|
||||
$html .= $message;
|
||||
|
||||
$disable = api_get_configuration_value('exercises_disable_new_attempts');
|
||||
if ($disable && empty($exercise_stat_info)) {
|
||||
$exercise_url_button = Display::return_message(get_lang('NewExerciseAttemptDisabled'));
|
||||
}
|
||||
|
||||
$isLimitReached = ExerciseLib::isQuestionsLimitPerDayReached(
|
||||
api_get_user_id(),
|
||||
count($objExercise->get_validated_question_list()),
|
||||
api_get_course_int_id(),
|
||||
api_get_session_id()
|
||||
);
|
||||
|
||||
if (!empty($exercise_url_button) && !$isLimitReached) {
|
||||
if ($quizCheckButtonEnabled) {
|
||||
$html .= Display::div(
|
||||
$btnCheck,
|
||||
['class' => 'exercise_overview_options']
|
||||
);
|
||||
$html .= '<br>';
|
||||
}
|
||||
|
||||
$html .= Display::div(
|
||||
Display::div(
|
||||
$exercise_url_button,
|
||||
['class' => 'exercise_overview_options']
|
||||
),
|
||||
['class' => 'options']
|
||||
);
|
||||
}
|
||||
|
||||
if ($isLimitReached) {
|
||||
$maxQuestionsAnswered = (int) api_get_course_setting('quiz_question_limit_per_day');
|
||||
|
||||
$html .= Display::return_message(
|
||||
sprintf(get_lang('QuizQuestionsLimitPerDayXReached'), $maxQuestionsAnswered),
|
||||
'warning',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (0 == $objExercise->getHideAttemptsTableOnStartPage()) {
|
||||
$html .= Display::tag(
|
||||
'div',
|
||||
$table_content,
|
||||
['class' => 'table-responsive']
|
||||
);
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
if ($certificateBlock) {
|
||||
$html .= PHP_EOL.$certificateBlock;
|
||||
}
|
||||
|
||||
if ($quizCheckButtonEnabled) {
|
||||
$quizCheckRequestUrl = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=browser_test';
|
||||
$params = http_build_query(
|
||||
[
|
||||
'exe_id' => 1,
|
||||
'exerciseId' => $exercise_id,
|
||||
'learnpath_id' => $learnpath_id,
|
||||
'learnpath_item_id' => $learnpath_item_id,
|
||||
'learnpath_item_view_id' => $learnpathItemViewId,
|
||||
'reminder' => '0',
|
||||
'type' => 'simple',
|
||||
'question_id' => 23,
|
||||
'choice[23]' => 45,
|
||||
]
|
||||
).'&'.api_get_cidreq();
|
||||
|
||||
$html .= "<script>
|
||||
$(function () {
|
||||
var btnTest = $('#quiz-check-request-button'),
|
||||
iconBtnTest = btnTest.children('.fa.fa-spin');
|
||||
|
||||
btnTest.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
btnTest.prop('disabled', true).removeClass('btn-success btn-danger').addClass('btn-default');
|
||||
iconBtnTest.removeClass('hidden');
|
||||
|
||||
var txtResult = $('#quiz-check-request-text').removeClass('text-success text-danger').hide();
|
||||
|
||||
$
|
||||
.when(
|
||||
$.ajax({
|
||||
url: '$quizCheckRequestUrl',
|
||||
type: 'post',
|
||||
data: '$params'
|
||||
}),
|
||||
$.ajax({
|
||||
url: '$quizCheckRequestUrl',
|
||||
type: 'post',
|
||||
data: '$params&sleep=1'
|
||||
})
|
||||
)
|
||||
.then(
|
||||
function (xhr1, xhr2) {
|
||||
var xhr1IsOk = !!xhr1 && xhr1[1] === 'success' && !!xhr1[0] && 'ok' === xhr1[0];
|
||||
var xhr2IsOk = !!xhr2 && xhr2[1] === 'success' && !!xhr2[0] && 'ok' === xhr2[0];
|
||||
|
||||
if (xhr1IsOk && xhr2IsOk) {
|
||||
btnTest.removeClass('btn-default btn-danger').addClass('btn-success');
|
||||
txtResult.text(\"".get_lang('QuizBrowserCheckOK')."\").addClass('text-success').show();
|
||||
} else {
|
||||
btnTest.removeClass('btn-default btn-success').addClass('btn-danger');
|
||||
txtResult.text(\"".get_lang('QuizBrowserCheckKO')."\").addClass('text-danger').show();
|
||||
}
|
||||
},
|
||||
function () {
|
||||
txtResult.text(\"".get_lang('QuizBrowserCheckKO')."\").addClass('text-danger').show();
|
||||
btnTest.removeClass('btn-default btn-success').addClass('btn-danger');
|
||||
}
|
||||
)
|
||||
.always(function () {
|
||||
btnTest.prop('disabled', false);
|
||||
iconBtnTest.addClass('hidden');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>";
|
||||
}
|
||||
|
||||
echo $html;
|
||||
|
||||
Display::display_footer();
|
||||
580
main/exercise/pending.php
Normal file
580
main/exercise/pending.php
Normal file
@@ -0,0 +1,580 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$cidReset = true;
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
// Setting the tabs
|
||||
$this_section = SECTION_COURSES;
|
||||
$htmlHeadXtra[] = api_get_jqgrid_js();
|
||||
|
||||
$filter_user = isset($_REQUEST['filter_by_user']) ? (int) $_REQUEST['filter_by_user'] : null;
|
||||
$courseId = isset($_REQUEST['course_id']) ? (int) $_REQUEST['course_id'] : 0;
|
||||
$exerciseId = isset($_REQUEST['exercise_id']) ? (int) $_REQUEST['exercise_id'] : 0;
|
||||
$statusId = isset($_REQUEST['status']) ? (int) $_REQUEST['status'] : 0;
|
||||
$questionTypeId = isset($_REQUEST['questionTypeId']) ? (int) $_REQUEST['questionTypeId'] : 0;
|
||||
$exportXls = isset($_REQUEST['export_xls']) && !empty($_REQUEST['export_xls']) ? (int) $_REQUEST['export_xls'] : 0;
|
||||
$action = $_REQUEST['a'] ?? null;
|
||||
|
||||
api_block_anonymous_users();
|
||||
|
||||
// Only teachers.
|
||||
if (false === api_is_teacher() && false === api_is_session_admin()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'get_exercise_by_course':
|
||||
$data = [];
|
||||
$results = ExerciseLib::get_all_exercises_for_course_id(
|
||||
null,
|
||||
0,
|
||||
$courseId,
|
||||
false
|
||||
);
|
||||
if (!empty($results)) {
|
||||
foreach ($results as $exercise) {
|
||||
$data[] = ['id' => $exercise['iid'], 'text' => html_entity_decode($exercise['title'])];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
|
||||
$userId = api_get_user_id();
|
||||
$origin = api_get_origin();
|
||||
|
||||
$TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
$TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
|
||||
$TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
|
||||
$TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
|
||||
$allowCoachFeedbackExercises = api_get_setting('allow_coach_feedback_exercises') === 'true';
|
||||
$documentPath = null;
|
||||
|
||||
if (!empty($_GET['path'])) {
|
||||
$parameters['path'] = Security::remove_XSS($_GET['path']);
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['export_report']) && $_REQUEST['export_report'] == '1') {
|
||||
if (api_is_platform_admin() || api_is_course_admin() ||
|
||||
api_is_course_tutor() || api_is_session_general_coach() ||
|
||||
api_is_session_admin()
|
||||
) {
|
||||
$loadExtraData = false;
|
||||
if (isset($_REQUEST['extra_data']) && $_REQUEST['extra_data'] == 1) {
|
||||
$loadExtraData = true;
|
||||
}
|
||||
|
||||
$includeAllUsers = false;
|
||||
if (isset($_REQUEST['include_all_users']) &&
|
||||
$_REQUEST['include_all_users'] == 1
|
||||
) {
|
||||
$includeAllUsers = true;
|
||||
}
|
||||
|
||||
$onlyBestAttempts = false;
|
||||
if (isset($_REQUEST['only_best_attempts']) &&
|
||||
$_REQUEST['only_best_attempts'] == 1
|
||||
) {
|
||||
$onlyBestAttempts = true;
|
||||
}
|
||||
|
||||
require_once 'exercise_result.class.php';
|
||||
$export = new ExerciseResult();
|
||||
$export->setIncludeAllUsers($includeAllUsers);
|
||||
$export->setOnlyBestAttempts($onlyBestAttempts);
|
||||
|
||||
switch ($_GET['export_format']) {
|
||||
case 'xls':
|
||||
$export->exportCompleteReportXLS(
|
||||
$documentPath,
|
||||
null,
|
||||
$loadExtraData,
|
||||
null,
|
||||
$exerciseId
|
||||
);
|
||||
exit;
|
||||
break;
|
||||
case 'csv':
|
||||
default:
|
||||
$export->exportCompleteReportCSV(
|
||||
$documentPath,
|
||||
null,
|
||||
$loadExtraData,
|
||||
null,
|
||||
$exerciseId
|
||||
);
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
}
|
||||
|
||||
$htmlHeadXtra[] = '<script>
|
||||
$(function() {
|
||||
$("#export-xls").bind("click", function(e) {
|
||||
e.preventDefault();
|
||||
var input = $("<input>", {
|
||||
type: "hidden",
|
||||
name: "export_xls",
|
||||
value: "1"
|
||||
});
|
||||
$("#pending").append(input);
|
||||
$("#pending").submit();
|
||||
});
|
||||
$("#pending_pendingSubmit").bind("click", function(e) {
|
||||
e.preventDefault();
|
||||
if ($("input[name=\"export_xls\"]").length > 0) {
|
||||
$("input[name=\"export_xls\"]").remove();
|
||||
}
|
||||
$("#pending").submit();
|
||||
});
|
||||
|
||||
$("select#course_id").on("change", function () {
|
||||
var courseId = parseInt(this.value, 10);
|
||||
updateExerciseList(courseId);
|
||||
});
|
||||
});
|
||||
function updateExerciseList(courseId) {
|
||||
if (courseId == 0) {
|
||||
return;
|
||||
}
|
||||
var $selectExercise = $("select#exercise_id");
|
||||
$selectExercise.empty();
|
||||
|
||||
$.get("'.api_get_self().'", {
|
||||
a: "get_exercise_by_course",
|
||||
course_id: courseId,
|
||||
}, function (exerciseList) {
|
||||
$("<option>", {
|
||||
value: 0,
|
||||
text: "'.get_lang('All').'"
|
||||
}).appendTo($selectExercise);
|
||||
|
||||
if (exerciseList.length > 0) {
|
||||
$.each(exerciseList, function (index, exercise) {
|
||||
$("<option>", {
|
||||
value: exercise.id,
|
||||
text: exercise.text
|
||||
}).appendTo($selectExercise);
|
||||
});
|
||||
$selectExercise.find("option[value=\''.$exerciseId.'\']").attr("selected",true);
|
||||
}
|
||||
$selectExercise.selectpicker("refresh");
|
||||
}, "json");
|
||||
}
|
||||
</script>';
|
||||
|
||||
if ($exportXls) {
|
||||
ExerciseLib::exportPendingAttemptsToExcel($_REQUEST);
|
||||
}
|
||||
|
||||
Display::display_header(get_lang('PendingAttempts'));
|
||||
$actions = '';
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM),
|
||||
'#',
|
||||
['id' => 'export-xls']
|
||||
);
|
||||
|
||||
echo Display::div($actions, ['class' => 'actions']);
|
||||
$token = Security::get_token();
|
||||
$extra = '<script>
|
||||
$(function() {
|
||||
$( "#dialog:ui-dialog" ).dialog( "destroy" );
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
autoOpen: false,
|
||||
show: "blind",
|
||||
resizable: false,
|
||||
height:300,
|
||||
modal: true
|
||||
});
|
||||
|
||||
$("#export_opener").click(function() {
|
||||
var targetUrl = $(this).attr("href");
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
width:400,
|
||||
height:300,
|
||||
buttons: {
|
||||
"'.addslashes(get_lang('Download')).'": function() {
|
||||
var export_format = $("input[name=export_format]:checked").val();
|
||||
var extra_data = $("input[name=load_extra_data]:checked").val();
|
||||
var includeAllUsers = $("input[name=include_all_users]:checked").val();
|
||||
var attempts = $("input[name=only_best_attempts]:checked").val();
|
||||
location.href = targetUrl+"&export_format="+export_format+"&extra_data="+extra_data+"&include_all_users="+includeAllUsers+"&only_best_attempts="+attempts;
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
});
|
||||
$( "#dialog-confirm" ).dialog("open");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
|
||||
$extra .= '<div id="dialog-confirm" title="'.get_lang('ConfirmYourChoice').'">';
|
||||
$form = new FormValidator(
|
||||
'report',
|
||||
'post',
|
||||
null,
|
||||
null,
|
||||
['class' => 'form-vertical']
|
||||
);
|
||||
$form->addElement(
|
||||
'radio',
|
||||
'export_format',
|
||||
null,
|
||||
get_lang('ExportAsCSV'),
|
||||
'csv',
|
||||
['id' => 'export_format_csv_label']
|
||||
);
|
||||
$form->addElement(
|
||||
'radio',
|
||||
'export_format',
|
||||
null,
|
||||
get_lang('ExportAsXLS'),
|
||||
'xls',
|
||||
['id' => 'export_format_xls_label']
|
||||
);
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'load_extra_data',
|
||||
null,
|
||||
get_lang('LoadExtraData'),
|
||||
'0',
|
||||
['id' => 'export_format_xls_label']
|
||||
);
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'include_all_users',
|
||||
null,
|
||||
get_lang('IncludeAllUsers'),
|
||||
'0'
|
||||
);
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'only_best_attempts',
|
||||
null,
|
||||
get_lang('OnlyBestAttempts'),
|
||||
'0'
|
||||
);
|
||||
$form->setDefaults(['export_format' => 'csv']);
|
||||
$extra .= $form->returnForm();
|
||||
$extra .= '</div>';
|
||||
|
||||
echo $extra;
|
||||
|
||||
$showAttemptsInSessions = api_get_configuration_value('show_exercise_attempts_in_all_user_sessions');
|
||||
$courses = CourseManager::get_courses_list_by_user_id($userId, $showAttemptsInSessions, false, false);
|
||||
|
||||
$form = new FormValidator('pending', 'GET');
|
||||
$courses = array_column($courses, 'title', 'real_id');
|
||||
$form->addSelect('course_id', get_lang('Course'), $courses, ['placeholder' => get_lang('All'), 'id' => 'course_id']);
|
||||
|
||||
$form->addSelect(
|
||||
'exercise_id',
|
||||
get_lang('Exercise'),
|
||||
[],
|
||||
[
|
||||
'placeholder' => get_lang('All'),
|
||||
'id' => 'exercise_id',
|
||||
]
|
||||
);
|
||||
|
||||
$status = [
|
||||
1 => get_lang('All'),
|
||||
2 => get_lang('Validated'),
|
||||
3 => get_lang('NotValidated'),
|
||||
4 => get_lang('Unclosed'),
|
||||
5 => get_lang('Ongoing'),
|
||||
];
|
||||
|
||||
$form->addSelect('status', get_lang('Status'), $status);
|
||||
|
||||
$questionType = [
|
||||
0 => get_lang('All'),
|
||||
1 => get_lang('QuestionsWithNoAutomaticCorrection'),
|
||||
];
|
||||
|
||||
$form->addSelect('questionTypeId', get_lang('QuestionType'), $questionType);
|
||||
|
||||
$form->addButtonSearch(get_lang('Search'), 'pendingSubmit');
|
||||
$content = $form->returnForm();
|
||||
|
||||
echo $content;
|
||||
|
||||
if (empty($statusId)) {
|
||||
Display::display_footer();
|
||||
exit;
|
||||
}
|
||||
|
||||
$url = api_get_path(WEB_AJAX_PATH).
|
||||
'model.ajax.php?a=get_exercise_pending_results&filter_by_user='.$filter_user.
|
||||
'&course_id='.$courseId.'&exercise_id='.$exerciseId.'&status='.$statusId.'&questionType='.$questionTypeId.'&showAttemptsInSessions='.$showAttemptsInSessions;
|
||||
$action_links = '';
|
||||
|
||||
$officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
|
||||
|
||||
// The order is important you need to check the the $column variable in the model.ajax.php file
|
||||
$columns = [
|
||||
get_lang('Course'),
|
||||
get_lang('Exercise'),
|
||||
get_lang('FirstName'),
|
||||
get_lang('LastName'),
|
||||
get_lang('LoginName'),
|
||||
get_lang('Duration').' ('.get_lang('MinMinute').')',
|
||||
get_lang('StartDate'),
|
||||
get_lang('EndDate'),
|
||||
get_lang('Score'),
|
||||
get_lang('IP'),
|
||||
get_lang('Status'),
|
||||
get_lang('Corrector'),
|
||||
get_lang('CorrectionDate'),
|
||||
get_lang('Actions'),
|
||||
];
|
||||
|
||||
if ($officialCodeInList === 'true') {
|
||||
$columns = array_merge([get_lang('OfficialCode')], $columns);
|
||||
}
|
||||
|
||||
// Column config
|
||||
$column_model = [
|
||||
['name' => 'course', 'index' => 'course', 'width' => '50', 'align' => 'left', 'search' => 'false', 'sortable' => 'false'],
|
||||
['name' => 'exercise', 'index' => 'exercise', 'width' => '50', 'align' => 'left', 'search' => 'false', 'sortable' => 'false'],
|
||||
['name' => 'firstname', 'index' => 'firstname', 'width' => '50', 'align' => 'left', 'search' => 'true'],
|
||||
[
|
||||
'name' => 'lastname',
|
||||
'index' => 'lastname',
|
||||
'width' => '50',
|
||||
'align' => 'left',
|
||||
'formatter' => 'action_formatter',
|
||||
'search' => 'true',
|
||||
],
|
||||
[
|
||||
'name' => 'login',
|
||||
'index' => 'username',
|
||||
'width' => '40',
|
||||
'align' => 'left',
|
||||
'search' => 'true',
|
||||
'hidden' => api_get_configuration_value('exercise_attempts_report_show_username') ? 'false' : 'true',
|
||||
],
|
||||
['name' => 'duration', 'index' => 'exe_duration', 'width' => '30', 'align' => 'left', 'search' => 'true'],
|
||||
['name' => 'start_date', 'index' => 'start_date', 'width' => '60', 'align' => 'left', 'search' => 'true'],
|
||||
['name' => 'exe_date', 'index' => 'exe_date', 'width' => '60', 'align' => 'left', 'search' => 'true'],
|
||||
['name' => 'score', 'index' => 'exe_result', 'width' => '50', 'align' => 'center', 'search' => 'true'],
|
||||
['name' => 'ip', 'index' => 'user_ip', 'width' => '40', 'align' => 'center', 'search' => 'true'],
|
||||
[
|
||||
'name' => 'status',
|
||||
'index' => 'revised',
|
||||
'width' => '40',
|
||||
'align' => 'left',
|
||||
'search' => 'false',
|
||||
'sortable' => 'false',
|
||||
//'stype' => 'select',
|
||||
//for the bottom bar
|
||||
/*'searchoptions' => [
|
||||
'defaultValue' => '',
|
||||
'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang('NotValidated'),
|
||||
],*/
|
||||
//for the top bar
|
||||
/*'editoptions' => [
|
||||
'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang('NotValidated'),
|
||||
],*/
|
||||
],
|
||||
[
|
||||
'name' => 'qualificator_fullname',
|
||||
'index' => 'qualificator_fullname',
|
||||
'width' => '20',
|
||||
'align' => 'left',
|
||||
'search' => 'true',
|
||||
],
|
||||
[
|
||||
'name' => 'date_of_qualification',
|
||||
'index' => 'date_of_qualification',
|
||||
'width' => '20',
|
||||
'align' => 'left',
|
||||
'search' => 'true',
|
||||
],
|
||||
[
|
||||
'name' => 'actions',
|
||||
'index' => 'actions',
|
||||
'width' => '60',
|
||||
'align' => 'left',
|
||||
'search' => 'false',
|
||||
'sortable' => 'false',
|
||||
],
|
||||
];
|
||||
|
||||
if ('true' === $officialCodeInList) {
|
||||
$officialCodeRow = [
|
||||
'name' => 'official_code',
|
||||
'index' => 'official_code',
|
||||
'width' => '50',
|
||||
'align' => 'left',
|
||||
'search' => 'true',
|
||||
];
|
||||
$column_model = array_merge([$officialCodeRow], $column_model);
|
||||
}
|
||||
|
||||
$action_links = '
|
||||
// add username as title in lastname filed - ref 4226
|
||||
function action_formatter(cellvalue, options, rowObject) {
|
||||
// rowObject is firstname,lastname,login,... get the third word
|
||||
var loginx = "'.api_htmlentities(sprintf(get_lang('LoginX'), ':::'), ENT_QUOTES).'";
|
||||
var tabLoginx = loginx.split(/:::/);
|
||||
// tabLoginx[0] is before and tabLoginx[1] is after :::
|
||||
// may be empty string but is defined
|
||||
return "<span title=\""+tabLoginx[0]+rowObject[2]+tabLoginx[1]+"\">"+cellvalue+"</span>";
|
||||
}';
|
||||
|
||||
$extra_params['autowidth'] = 'true';
|
||||
$extra_params['height'] = 'auto';
|
||||
$gridJs = Display::grid_js(
|
||||
'results',
|
||||
$url,
|
||||
$columns,
|
||||
$column_model,
|
||||
$extra_params,
|
||||
[],
|
||||
$action_links,
|
||||
true
|
||||
);
|
||||
|
||||
?>
|
||||
<script>
|
||||
function exportExcel()
|
||||
{
|
||||
var mya = $("#results").getDataIDs(); // Get All IDs
|
||||
var data = $("#results").getRowData(mya[0]); // Get First row to get the labels
|
||||
var colNames = new Array();
|
||||
var ii = 0;
|
||||
for (var i in data) {
|
||||
colNames[ii++] = i;
|
||||
}
|
||||
var html = "";
|
||||
for (i = 0; i < mya.length; i++) {
|
||||
data = $("#results").getRowData(mya[i]); // get each row
|
||||
for (j = 0; j < colNames.length; j++) {
|
||||
html = html + data[colNames[j]] + ","; // output each column as tab delimited
|
||||
}
|
||||
html = html + "\n"; // output each row with end of line
|
||||
}
|
||||
html = html + "\n"; // end of line at the end
|
||||
var form = $("#export_report_form");
|
||||
$("#csvBuffer").attr('value', html);
|
||||
form.target='_blank';
|
||||
form.submit();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$("#datepicker_start").datepicker({
|
||||
defaultDate: "",
|
||||
changeMonth: false,
|
||||
numberOfMonths: 1
|
||||
});
|
||||
|
||||
var $selectCourse = $("select#course_id");
|
||||
|
||||
$selectCourse.on("change", function () {
|
||||
var courseId = parseInt(this.value, 10);
|
||||
updateExerciseList(courseId);
|
||||
});
|
||||
|
||||
var courseId = $selectCourse.val() ? $selectCourse.val() : 0;
|
||||
updateExerciseList(courseId);
|
||||
<?php
|
||||
echo $gridJs;
|
||||
?>
|
||||
$("#results").jqGrid(
|
||||
'navGrid',
|
||||
'#results_pager', {
|
||||
view:true, edit:false, add:false, del:false, excel:false
|
||||
},
|
||||
{height:280, reloadAfterSubmit:false}, // view options
|
||||
{height:280, reloadAfterSubmit:false}, // edit options
|
||||
{height:280, reloadAfterSubmit:false}, // add options
|
||||
{reloadAfterSubmit: false}, // del options
|
||||
{width:500}, // search options
|
||||
);
|
||||
|
||||
var sgrid = $("#results")[0];
|
||||
|
||||
// Adding search options
|
||||
var options = {
|
||||
'stringResult': true,
|
||||
'autosearch' : true,
|
||||
'searchOnEnter': false,
|
||||
}
|
||||
jQuery("#results").jqGrid('filterToolbar', options);
|
||||
sgrid.triggerToolbar();
|
||||
$('#results').on('click', 'a.exercise-recalculate', function (e) {
|
||||
e.preventDefault();
|
||||
if (!$(this).data('user') || !$(this).data('exercise') || !$(this).data('id')) {
|
||||
return;
|
||||
}
|
||||
var url = '<?php echo api_get_path(WEB_CODE_PATH); ?>exercise/recalculate.php?<?php echo api_get_cidreq(); ?>';
|
||||
var recalculateXhr = $.post(url, $(this).data());
|
||||
$.when(recalculateXhr).done(function (response) {
|
||||
$('#results').trigger('reloadGrid');
|
||||
});
|
||||
});
|
||||
});
|
||||
// datepicker functions
|
||||
var datapickerInputModified = false;
|
||||
/**
|
||||
* return true if the datepicker input has been modified
|
||||
*/
|
||||
function datepicker_input_changed() {
|
||||
datapickerInputModified = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* disply the datepicker calendar on mouse over the input
|
||||
*/
|
||||
function datepicker_input_mouseover() {
|
||||
$('#datepicker_start').datepicker( "show" );
|
||||
}
|
||||
|
||||
/**
|
||||
* display or hide the datepicker input, calendar and button
|
||||
*/
|
||||
function display_date_picker() {
|
||||
if (!$('#datepicker_span').is(":visible")) {
|
||||
$('#datepicker_span').show();
|
||||
$('#datepicker_start').datepicker( "show" );
|
||||
} else {
|
||||
$('#datepicker_start').datepicker( "hide" );
|
||||
$('#datepicker_span').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* confirm deletion
|
||||
*/
|
||||
function submit_datepicker() {
|
||||
if (datapickerInputModified) {
|
||||
var dateTypeVar = $('#datepicker_start').datepicker('getDate');
|
||||
var dateForBDD = $.datepicker.formatDate('yy-mm-dd', dateTypeVar);
|
||||
// Format the date for confirm box
|
||||
var dateFormat = $( "#datepicker_start" ).datepicker( "option", "dateFormat" );
|
||||
var selectedDate = $.datepicker.formatDate(dateFormat, dateTypeVar);
|
||||
if (confirm("<?php echo convert_double_quote_to_single(get_lang('AreYouSureDeleteTestResultBeforeDateD')).' '; ?>" + selectedDate)) {
|
||||
self.location.href = "exercise_report.php?<?php echo api_get_cidreq(); ?>&exerciseId=<?php echo $exerciseId; ?>&delete_before_date="+dateForBDD+"&sec_token=<?php echo $token; ?>";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<form id="export_report_form" method="post" action="exercise_report.php?<?php echo api_get_cidreq(); ?>">
|
||||
<input type="hidden" name="csvBuffer" id="csvBuffer" value="" />
|
||||
<input type="hidden" name="export_report" id="export_report" value="1" />
|
||||
<input type="hidden" name="exerciseId" id="exerciseId" value="<?php echo $exerciseId; ?>" />
|
||||
</form>
|
||||
<?php
|
||||
|
||||
echo Display::grid_html('results');
|
||||
Display::display_footer();
|
||||
105
main/exercise/qti2.php
Normal file
105
main/exercise/qti2.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Code for Qti2 import integration.
|
||||
*
|
||||
* @author Ronny Velasquez
|
||||
*
|
||||
* @version $Id: qti2.php 2010-03-12 12:14:25Z $
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
// section (for the tabs)
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
// access restriction: only teachers are allowed here
|
||||
if (!api_is_allowed_to_edit(null, true)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
// the breadcrumbs
|
||||
$interbreadcrumb[] = [
|
||||
'url' => api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
|
||||
/**
|
||||
* This function displays the form to import the zip file with qti2.
|
||||
*/
|
||||
function displayForm()
|
||||
{
|
||||
$form = '<div class="actions">';
|
||||
$form .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?show=test&'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
$form .= '</div>';
|
||||
$formValidator = new FormValidator(
|
||||
'qti_upload',
|
||||
'post',
|
||||
api_get_self().'?'.api_get_cidreq(),
|
||||
null,
|
||||
['enctype' => 'multipart/form-data']
|
||||
);
|
||||
$formValidator->addHeader(get_lang('ImportQtiQuiz'));
|
||||
$formValidator->addElement('file', 'userFile', get_lang('DownloadFile'));
|
||||
$formValidator->addButtonImport(get_lang('Upload'));
|
||||
$form .= $formValidator->returnForm();
|
||||
echo $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will import the zip file with the respective qti2.
|
||||
*
|
||||
* @param array $array_file ($_FILES)
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
function importFile($array_file)
|
||||
{
|
||||
$unzip = 0;
|
||||
$process = process_uploaded_file($array_file, false);
|
||||
|
||||
if (preg_match('/\.zip$/i', $array_file['name'])) {
|
||||
// if it's a zip, allow zip upload
|
||||
$unzip = 1;
|
||||
}
|
||||
|
||||
if ($process && $unzip == 1) {
|
||||
$main_path = api_get_path(SYS_CODE_PATH);
|
||||
require_once $main_path.'exercise/export/exercise_import.inc.php';
|
||||
require_once $main_path.'exercise/export/qti2/qti2_classes.php';
|
||||
|
||||
return import_exercise($array_file['name']);
|
||||
}
|
||||
|
||||
return 'FileError';
|
||||
}
|
||||
|
||||
$message = null;
|
||||
|
||||
// import file
|
||||
if (api_is_allowed_to_edit(null, true)) {
|
||||
if (isset($_POST['submit'])) {
|
||||
$imported = importFile($_FILES['userFile']);
|
||||
|
||||
if (is_numeric($imported) && !empty($imported)) {
|
||||
header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'&exerciseId='.$imported);
|
||||
exit;
|
||||
} else {
|
||||
$message = Display::return_message(get_lang($imported));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Display::display_header(get_lang('ImportQtiQuiz'), 'Exercise');
|
||||
|
||||
echo $message;
|
||||
|
||||
// display qti form
|
||||
displayForm();
|
||||
|
||||
Display::display_footer();
|
||||
2866
main/exercise/question.class.php
Normal file
2866
main/exercise/question.class.php
Normal file
File diff suppressed because it is too large
Load Diff
103
main/exercise/question_admin.inc.php
Normal file
103
main/exercise/question_admin.inc.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This script allows to manage the statements of questions.
|
||||
* It is included from the script admin.php.
|
||||
*
|
||||
* @author Olivier Brouckaert
|
||||
*/
|
||||
if (isset($_GET['editQuestion'])) {
|
||||
$objQuestion = Question::read($_GET['editQuestion']);
|
||||
$action = api_get_self().'?'.api_get_cidreq().'&modifyQuestion='.$modifyQuestion.'&editQuestion='.$objQuestion->iid.'&page='.$page;
|
||||
} else {
|
||||
$objQuestion = Question::getInstance($_REQUEST['answerType']);
|
||||
$action = api_get_self().'?'.api_get_cidreq().'&modifyQuestion='.$modifyQuestion.'&newQuestion='.$newQuestion;
|
||||
}
|
||||
|
||||
if (is_object($objQuestion)) {
|
||||
// FORM CREATION
|
||||
$form = new FormValidator('question_admin_form', 'post', $action);
|
||||
if (isset($_GET['editQuestion'])) {
|
||||
$class = 'btn btn-default';
|
||||
$text = get_lang('ModifyQuestion');
|
||||
$type = isset($_GET['type']) ? Security::remove_XSS($_GET['type']) : null;
|
||||
} else {
|
||||
$class = 'btn btn-default';
|
||||
$text = get_lang('AddQuestionToExercise');
|
||||
$type = $_REQUEST['answerType'];
|
||||
}
|
||||
|
||||
$typesInformation = Question::getQuestionTypeList();
|
||||
$form_title_extra = isset($typesInformation[$type][1]) ? get_lang($typesInformation[$type][1]) : null;
|
||||
|
||||
$code = '';
|
||||
if (isset($objQuestion->code) && !empty($objQuestion->code)) {
|
||||
$code = ' ('.$objQuestion->code.')';
|
||||
}
|
||||
|
||||
// form title
|
||||
$form->addHeader($text.': '.$form_title_extra.$code);
|
||||
// question form elements
|
||||
$objQuestion->createForm($form, $objExercise);
|
||||
|
||||
// answer form elements
|
||||
$objQuestion->createAnswersForm($form);
|
||||
|
||||
// this variable $show_quiz_edition comes from admin.php blocks the exercise/quiz modifications
|
||||
if (!empty($objExercise->iid) && $objExercise->edit_exercise_in_lp == false) {
|
||||
$form->freeze();
|
||||
}
|
||||
|
||||
// FORM VALIDATION
|
||||
if (isset($_POST['submitQuestion'])) {
|
||||
$validationResult = true;
|
||||
if (method_exists($objQuestion, 'validateAnswers')) {
|
||||
$validationResult = $objQuestion->validateAnswers($form);
|
||||
}
|
||||
if (is_array($validationResult) && !empty($validationResult['errors'])) {
|
||||
echo Display::return_message(implode("<br>", $validationResult['errors']), 'error', false);
|
||||
} elseif ($form->validate()) {
|
||||
$objQuestion->processCreation($form, $objExercise);
|
||||
$objQuestion->processAnswersCreation($form, $objExercise);
|
||||
if (in_array($objQuestion->type, [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_DELINEATION])) {
|
||||
echo '<script type="text/javascript">window.location.href="admin.php?exerciseId='.$exerciseId.'&page='.$page.'&hotspotadmin='.$objQuestion->iid.'&'.api_get_cidreq().'";</script>';
|
||||
} elseif (in_array($objQuestion->type, [MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION])) {
|
||||
$url = 'admin.php?'.api_get_cidreq().'&'.http_build_query(['exerciseId' => $exerciseId, 'page' => $page, 'mad_admin' => $objQuestion->iid]);
|
||||
echo '<script type="text/javascript">window.location.href="'.$url.'";</script>';
|
||||
} else {
|
||||
if (isset($_GET['editQuestion'])) {
|
||||
if (empty($exerciseId)) {
|
||||
Display::addFlash(Display::return_message(get_lang('ItemUpdated')));
|
||||
$url = 'admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&editQuestion='.$objQuestion->iid;
|
||||
echo '<script type="text/javascript">window.location.href="'.$url.'";</script>';
|
||||
exit;
|
||||
}
|
||||
echo '<script type="text/javascript">window.location.href="admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&page='.$page.'&message=ItemUpdated";</script>';
|
||||
} else {
|
||||
// New question
|
||||
$page = 1;
|
||||
$length = api_get_configuration_value('question_pagination_length');
|
||||
if (!empty($length)) {
|
||||
$page = round($objExercise->getQuestionCount() / $length);
|
||||
}
|
||||
echo '<script type="text/javascript">window.location.href="admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'&page='.$page.'&message=ItemAdded";</script>';
|
||||
}
|
||||
}
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($questionName)) {
|
||||
echo '<h3>'.$questionName.'</h3>';
|
||||
}
|
||||
if (!empty($pictureName)) {
|
||||
echo '<img src="../document/download.php?doc_url=%2Fimages%2F'.$pictureName.'" border="0">';
|
||||
}
|
||||
if (!empty($msgErr)) {
|
||||
echo Display::return_message($msgErr);
|
||||
}
|
||||
|
||||
$form->display();
|
||||
}
|
||||
125
main/exercise/question_create.php
Normal file
125
main/exercise/question_create.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
// the section (tabs)
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
// notice for unauthorized people.
|
||||
api_protect_course_script(true);
|
||||
|
||||
$allow = api_is_allowed_to_edit();
|
||||
|
||||
if (!$allow) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
// breadcrumbs
|
||||
$interbreadcrumb[] = ["url" => "exercise.php", "name" => get_lang('Exercises')];
|
||||
|
||||
// Tool name
|
||||
$nameTools = get_lang('AddQuestionToExercise');
|
||||
|
||||
// The form
|
||||
$form = new FormValidator('add_question', 'post', api_get_self().'?'.api_get_cidreq());
|
||||
// form title
|
||||
$form->addElement('header', '', get_lang('AddQuestionToExercise'));
|
||||
|
||||
$question_list = Question::getQuestionTypeList();
|
||||
$question_list_options = [];
|
||||
foreach ($question_list as $key => $value) {
|
||||
$question_list_options[$key] = addslashes(get_lang($value[1]));
|
||||
}
|
||||
$form->addElement(
|
||||
'select',
|
||||
'question_type_hidden',
|
||||
get_lang('QuestionType'),
|
||||
$question_list_options,
|
||||
['id' => 'question_type_hidden']
|
||||
);
|
||||
|
||||
//session id
|
||||
$session_id = api_get_session_id();
|
||||
|
||||
// the exercises
|
||||
$tbl_exercises = Database::get_course_table(TABLE_QUIZ_TEST);
|
||||
$course_id = api_get_course_int_id();
|
||||
|
||||
$sql = "SELECT iid, title, type, description, results_disabled
|
||||
FROM $tbl_exercises
|
||||
WHERE c_id = $course_id AND active<>'-1' AND session_id = ".$session_id."
|
||||
ORDER BY title ASC";
|
||||
$result = Database::query($sql);
|
||||
$exercises['-'] = '-'.get_lang('SelectExercise').'-';
|
||||
while ($row = Database::fetch_array($result)) {
|
||||
$exercises[$row['iid']] = cut($row['title'], EXERCISE_MAX_NAME_SIZE);
|
||||
}
|
||||
$form->addElement('select', 'exercise', get_lang('Exercise'), $exercises);
|
||||
|
||||
// generate default content
|
||||
$form->addElement(
|
||||
'checkbox',
|
||||
'is_content',
|
||||
null,
|
||||
get_lang('GenerateDefaultContent'),
|
||||
['checked' => true]
|
||||
);
|
||||
|
||||
// the submit button
|
||||
$form->addButtonCreate(get_lang('CreateQuestion'), 'SubmitCreateQuestion');
|
||||
|
||||
// setting the rules
|
||||
$form->addRule('exercise', get_lang('ThisFieldIsRequired'), 'required');
|
||||
$form->addRule('exercise', get_lang('YouHaveToSelectATest'), 'numeric');
|
||||
|
||||
$form->registerRule('validquestiontype', 'callback', 'check_question_type');
|
||||
$form->addRule('question_type_hidden', get_lang('InvalidQuestionType'), 'validquestiontype');
|
||||
|
||||
if ($form->validate()) {
|
||||
$values = $form->exportValues();
|
||||
$answer_type = $values['question_type_hidden'];
|
||||
|
||||
// check feedback_type from current exercise for type of question delineation
|
||||
$exercise_id = (int) $values['exercise'];
|
||||
$sql = "SELECT feedback_type FROM $tbl_exercises WHERE iid = $exercise_id";
|
||||
$rs_feedback_type = Database::query($sql);
|
||||
$row_feedback_type = Database::fetch_row($rs_feedback_type);
|
||||
$feedback_type = $row_feedback_type[0];
|
||||
|
||||
// if question type does not belong to self-evaluation (immediate feedback) it'll send an error
|
||||
if (($answer_type == HOT_SPOT_DELINEATION && $feedback_type != 1) ||
|
||||
($feedback_type == 1 && ($answer_type != HOT_SPOT_DELINEATION && $answer_type != UNIQUE_ANSWER))) {
|
||||
header('Location: question_create.php?'.api_get_cidreq().'&error=true');
|
||||
exit;
|
||||
}
|
||||
header('Location: admin.php?exerciseId='.$values['exercise'].'&newQuestion=yes&isContent='.$values['is_content'].'&answerType='.$answer_type);
|
||||
exit;
|
||||
} else {
|
||||
// header
|
||||
Display::display_header($nameTools);
|
||||
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="exercise.php?show=test">'.Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
echo '</div>';
|
||||
|
||||
// displaying the form
|
||||
$form->display();
|
||||
|
||||
// footer
|
||||
Display::display_footer();
|
||||
}
|
||||
|
||||
function check_question_type($parameter)
|
||||
{
|
||||
$question_list = Question::getQuestionTypeList();
|
||||
foreach ($question_list as $key => $value) {
|
||||
$valid_question_types[] = $key;
|
||||
}
|
||||
if (in_array($parameter, $valid_question_types)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
442
main/exercise/question_list_admin.inc.php
Normal file
442
main/exercise/question_list_admin.inc.php
Normal file
@@ -0,0 +1,442 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* @author Olivier Brouckaert & Julio Montoya & Hubert Borderiou 21-10-2011 (Question by category)
|
||||
* QUESTION LIST ADMINISTRATION
|
||||
*
|
||||
* This script allows to manage the question list
|
||||
* It is included from the script admin.php
|
||||
*/
|
||||
$limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
|
||||
$allowInterCourseLinking = api_get_configuration_value('quiz_question_allow_inter_course_linking');
|
||||
|
||||
// deletes a question from the exercise (not from the data base)
|
||||
if ($deleteQuestion) {
|
||||
if ($limitTeacherAccess && !api_is_platform_admin()) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// if the question exists
|
||||
if ($objQuestionTmp = Question::read($deleteQuestion)) {
|
||||
if ($allowInterCourseLinking === true) {
|
||||
$masterExerciseId = Question::getMasterQuizForQuestion($objQuestionTmp->iid);
|
||||
|
||||
if ($masterExerciseId != $exerciseId) {
|
||||
$objQuestionTmp->delete($exerciseId);
|
||||
} else {
|
||||
$objQuestionTmp->delete();
|
||||
}
|
||||
} else {
|
||||
$objQuestionTmp->delete($exerciseId);
|
||||
}
|
||||
|
||||
// if the question has been removed from the exercise
|
||||
if ($objExercise->removeFromList($deleteQuestion)) {
|
||||
$nbrQuestions--;
|
||||
}
|
||||
}
|
||||
// destruction of the Question object
|
||||
unset($objQuestionTmp);
|
||||
}
|
||||
$ajax_url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&exercise_id='.intval($exerciseId);
|
||||
$addImageUrl = api_get_path(WEB_CODE_PATH).'inc/lib/elfinder/filemanager.php?add_image=1&'.api_get_cidreq();
|
||||
?>
|
||||
<div id="dialog-confirm"
|
||||
title="<?php echo get_lang('ConfirmYourChoice'); ?>"
|
||||
style="display:none;">
|
||||
<p>
|
||||
<?php echo get_lang('AreYouSureToDelete'); ?>
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
function addImageToQuestion(image, questionId) {
|
||||
let url = '<?php echo $ajax_url; ?>' + '&a=save_question_description&question_id=' + questionId;
|
||||
let params = {
|
||||
image: image
|
||||
}
|
||||
$.ajax({
|
||||
url: url,
|
||||
data: params,
|
||||
success: function (data) {
|
||||
window.alert('<?php echo addslashes(get_lang('Updated')); ?>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadEditor(button, questionId) {
|
||||
event.preventDefault();
|
||||
let url = '<?php echo $addImageUrl; ?>' + '&question_id=' + questionId;
|
||||
let w = 850;
|
||||
let h = 500;
|
||||
let left = (screen.width / 2) - (w / 2);
|
||||
let top = (screen.height / 2) - (h / 2);
|
||||
let params = 'height=' + h + ',width=' + w + ',resizable=0,top=' + top + ',left=' + left;
|
||||
|
||||
setTimeout(() => window.open(url, 'editor', params), 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$("#dialog:ui-dialog").dialog("destroy");
|
||||
$("#dialog-confirm").dialog({
|
||||
autoOpen: false,
|
||||
show: "blind",
|
||||
resizable: false,
|
||||
height: 150,
|
||||
modal: false
|
||||
});
|
||||
|
||||
$(".opener").click(function () {
|
||||
var targetUrl = $(this).attr("href");
|
||||
var otherQuizs = $(this).data("otherquizs");
|
||||
|
||||
if (otherQuizs == 1) {
|
||||
$("#dialog-confirm p").text("<?php echo get_lang('QuestionInOtherExercises'); ?>")
|
||||
}
|
||||
|
||||
$("#dialog-confirm").dialog({
|
||||
modal: true,
|
||||
buttons: {
|
||||
"<?php echo get_lang('Yes'); ?>": function () {
|
||||
location.href = targetUrl;
|
||||
$(this).dialog("close");
|
||||
|
||||
},
|
||||
"<?php echo get_lang('No'); ?>": function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#dialog-confirm").dialog("open");
|
||||
return false;
|
||||
});
|
||||
|
||||
var stop = false;
|
||||
$("#question_list h3").click(function (event) {
|
||||
if (stop) {
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
stop = false;
|
||||
}
|
||||
});
|
||||
|
||||
/* We can add links in the accordion header */
|
||||
$(".btn-actions .edition a.btn").click(function () {
|
||||
//Avoid the redirecto when selecting the delete button
|
||||
if (this.id.indexOf('delete') == -1) {
|
||||
newWind = window.open(this.href, "_self");
|
||||
newWind.focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$("#question_list").accordion({
|
||||
icons: null,
|
||||
heightStyle: "content",
|
||||
active: false, // all items closed by default
|
||||
collapsible: true,
|
||||
header: ".header_operations",
|
||||
beforeActivate: function (e, ui) {
|
||||
var data = ui.newHeader.data();
|
||||
if (typeof data === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var exerciseId = data.exercise || 0,
|
||||
questionId = data.question || 0;
|
||||
|
||||
if (!questionId || !exerciseId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $pnlQuestion = $('#pnl-question-' + questionId);
|
||||
|
||||
if ($pnlQuestion.html().trim().length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pnlQuestion.html('<span class="fa fa-spinner fa-spin fa-3x fa-fw" aria-hidden="true"></span>');
|
||||
|
||||
$.get('<?php echo api_get_path(WEB_AJAX_PATH); ?>exercise.ajax.php?<?php echo api_get_cidreq(); ?>', {
|
||||
a: 'show_question',
|
||||
exercise: exerciseId,
|
||||
question: questionId
|
||||
}, function (response) {
|
||||
$pnlQuestion.html(response)
|
||||
});
|
||||
}
|
||||
})
|
||||
.sortable({
|
||||
cursor: "move", // works?
|
||||
update: function (event, ui) {
|
||||
var order = $(this).sortable("serialize") + "&a=update_question_order&exercise_id=<?php echo $exerciseId; ?>";
|
||||
$.post("<?php echo $ajax_url; ?>", order, function (result) {
|
||||
$("#message").html(result);
|
||||
});
|
||||
},
|
||||
axis: "y",
|
||||
placeholder: "ui-state-highlight", //defines the yellow highlight
|
||||
handle: ".moved", //only the class "moved"
|
||||
stop: function () {
|
||||
stop = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
|
||||
// Filter the type of questions we can add
|
||||
Question::displayTypeMenu($objExercise);
|
||||
echo '<div id="message"></div>';
|
||||
$token = Security::get_token();
|
||||
//deletes a session when using don't know question type (ugly fix)
|
||||
Session::erase('less_answer');
|
||||
|
||||
// If we are in a test
|
||||
$inATest = isset($exerciseId) && $exerciseId > 0;
|
||||
if (!$inATest) {
|
||||
echo Display::return_message(get_lang('ChoiceQuestionType'), 'warning');
|
||||
} else {
|
||||
if ($nbrQuestions) {
|
||||
// In the building exercise mode show question list ordered as is.
|
||||
$objExercise->setCategoriesGrouping(false);
|
||||
$originalQuestionSelectType = $objExercise->questionSelectionType;
|
||||
// In building mode show all questions not render by teacher order.
|
||||
$objExercise->questionSelectionType = EX_Q_SELECTION_ORDERED;
|
||||
$allowQuestionOrdering = true;
|
||||
$showPagination = api_get_configuration_value('show_question_pagination');
|
||||
if (!empty($showPagination) && $nbrQuestions > $showPagination) {
|
||||
$length = api_get_configuration_value('question_pagination_length');
|
||||
$url = api_get_self().'?'.api_get_cidreq();
|
||||
// Use pagination for exercise with more than 200 questions.
|
||||
$allowQuestionOrdering = false;
|
||||
$start = ($page - 1) * $length;
|
||||
$questionList = $objExercise->getQuestionForTeacher($start, $length);
|
||||
$paginator = new Knp\Component\Pager\Paginator();
|
||||
$pagination = $paginator->paginate([]);
|
||||
$pagination->setTotalItemCount($nbrQuestions);
|
||||
$pagination->setItemNumberPerPage($length);
|
||||
$pagination->setCurrentPageNumber($page);
|
||||
$pagination->renderer = function ($data) use ($url) {
|
||||
$render = '<ul class="pagination">';
|
||||
for ($i = 1; $i <= $data['pageCount']; $i++) {
|
||||
$pageContent = '<li><a href="'.$url.'&page='.$i.'">'.$i.'</a></li>';
|
||||
if ($data['current'] == $i) {
|
||||
$pageContent = '<li class="active"><a href="#" >'.$i.'</a></li>';
|
||||
}
|
||||
$render .= $pageContent;
|
||||
}
|
||||
$render .= '</ul>';
|
||||
|
||||
return $render;
|
||||
};
|
||||
echo $pagination;
|
||||
} else {
|
||||
// Teacher view, see all questions.
|
||||
//$questionList = $objExercise->selectQuestionList(true, true);
|
||||
$questionList = $objExercise->getQuestionOrderedList(true);
|
||||
}
|
||||
|
||||
// Restore original value
|
||||
$objExercise->questionSelectionType = $originalQuestionSelectType;
|
||||
|
||||
echo '
|
||||
<div class="row hidden-xs">
|
||||
<div class="col-sm-5"><strong>'.get_lang('Questions').'</strong></div>
|
||||
<div class="col-sm-1 text-center"><strong>'.get_lang('Type').'</strong></div>
|
||||
<div class="col-sm-2"><strong>'.get_lang('Category').'</strong></div>
|
||||
<div class="col-sm-1 text-right"><strong>'.get_lang('Difficulty').'</strong></div>
|
||||
<div class="col-sm-1 text-right"><strong>'.get_lang('MaximumScore').'</strong></div>
|
||||
<div class="col-sm-2 text-right"><strong>'.get_lang('Actions').'</strong></div>
|
||||
</div>
|
||||
<div id="question_list">
|
||||
';
|
||||
|
||||
$category_list = TestCategory::getListOfCategoriesNameForTest($objExercise->iid, false);
|
||||
|
||||
if (is_array($questionList)) {
|
||||
foreach ($questionList as $id) {
|
||||
// To avoid warning messages.
|
||||
if (!is_numeric($id)) {
|
||||
continue;
|
||||
}
|
||||
/** @var Question $objQuestionTmp */
|
||||
$objQuestionTmp = Question::read($id);
|
||||
|
||||
if (empty($objQuestionTmp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$addImageLink = '';
|
||||
if (api_get_configuration_value('allow_quick_question_description_popup')) {
|
||||
$addImageLink = Display::url(
|
||||
Display::return_icon(
|
||||
'image.png',
|
||||
get_lang('AddImage'),
|
||||
[],
|
||||
ICON_SIZE_TINY
|
||||
),
|
||||
'javascript:void(0);',
|
||||
['class' => 'btn btn-default btn-sm ajax', 'onclick' => 'loadEditor(this, '.$id.')']
|
||||
);
|
||||
}
|
||||
|
||||
$clone_link = null;
|
||||
if ($objExercise->edit_exercise_in_lp == true) {
|
||||
$clone_link = Display::url(
|
||||
Display::return_icon(
|
||||
'cd.png',
|
||||
get_lang('Copy'),
|
||||
[],
|
||||
ICON_SIZE_TINY
|
||||
),
|
||||
api_get_self().'?'.api_get_cidreq().'&clone_question='.$id.'&page='.$page,
|
||||
['class' => 'btn btn-default btn-sm']
|
||||
);
|
||||
}
|
||||
|
||||
$edit_link = $objQuestionTmp->selectType() == CALCULATED_ANSWER && $objQuestionTmp->isAnswered()
|
||||
? Display::span(
|
||||
Display::return_icon(
|
||||
'edit_na.png',
|
||||
get_lang(
|
||||
'QuestionEditionNotAvailableBecauseItIsAlreadyAnsweredHoweverYouCanCopyItAndModifyTheCopy'
|
||||
),
|
||||
[],
|
||||
ICON_SIZE_TINY
|
||||
),
|
||||
['class' => 'btn btn-default btn-sm']
|
||||
)
|
||||
: Display::url(
|
||||
Display::return_icon(
|
||||
'edit.png',
|
||||
get_lang('Modify'),
|
||||
[],
|
||||
ICON_SIZE_TINY
|
||||
),
|
||||
api_get_self().'?'.api_get_cidreq().'&'
|
||||
.http_build_query([
|
||||
'type' => $objQuestionTmp->selectType(),
|
||||
'editQuestion' => $id,
|
||||
'page' => $page,
|
||||
]),
|
||||
['class' => 'btn btn-default btn-sm']
|
||||
);
|
||||
$delete_link = null;
|
||||
if (!$limitTeacherAccess && api_is_allowed_to_edit() && $objExercise->edit_exercise_in_lp) {
|
||||
$questionInOtherQuizs = 0;
|
||||
$results = Question::countQuizzesUsingQuestion($id);
|
||||
|
||||
if ($results > 1) {
|
||||
$masterExerciseId = Question::getMasterQuizForQuestion($id);
|
||||
if ($masterExerciseId !== $exerciseId) {
|
||||
$questionInOtherQuizs = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$delete_link = Display::url(
|
||||
Display::return_icon(
|
||||
'delete.png',
|
||||
get_lang('RemoveFromTest'),
|
||||
[],
|
||||
ICON_SIZE_TINY
|
||||
),
|
||||
api_get_self().'?'.api_get_cidreq().'&'
|
||||
.http_build_query([
|
||||
'exerciseId' => $exerciseId,
|
||||
'deleteQuestion' => $id,
|
||||
'page' => $page,
|
||||
]),
|
||||
[
|
||||
'id' => "delete_$id",
|
||||
'class' => 'opener btn btn-default btn-sm',
|
||||
'data-otherquizs' => $questionInOtherQuizs,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$btnActions = implode(
|
||||
PHP_EOL,
|
||||
[$edit_link, $clone_link, $addImageLink, $delete_link]
|
||||
);
|
||||
|
||||
$title = Security::remove_XSS(strip_tags($objQuestionTmp->selectTitle()));
|
||||
$move = ' ';
|
||||
if ($allowQuestionOrdering) {
|
||||
$move = Display::returnFontAwesomeIcon('arrows moved', 1, true);
|
||||
}
|
||||
|
||||
// Question name
|
||||
$questionName =
|
||||
'<a href="#" title = "'.$title.'">
|
||||
'.$move.' '.cut($title, 42).'
|
||||
</a>';
|
||||
|
||||
// Question type
|
||||
$typeImg = $objQuestionTmp->getTypePicture();
|
||||
$typeExpl = $objQuestionTmp->getExplanation();
|
||||
$questionType = Display::return_icon($typeImg, $typeExpl);
|
||||
|
||||
// Question category
|
||||
$txtQuestionCat = Security::remove_XSS(
|
||||
TestCategory::getCategoryNameForQuestion($objQuestionTmp->iid)
|
||||
);
|
||||
if (empty($txtQuestionCat)) {
|
||||
$txtQuestionCat = '-';
|
||||
}
|
||||
|
||||
// Question level
|
||||
$txtQuestionLevel = $objQuestionTmp->getLevel();
|
||||
if (empty($objQuestionTmp->level)) {
|
||||
$txtQuestionLevel = '-';
|
||||
}
|
||||
$questionLevel = $txtQuestionLevel;
|
||||
|
||||
// Question score
|
||||
$questionScore = $objQuestionTmp->selectWeighting();
|
||||
|
||||
echo '<div id="question_id_list_'.$id.'">
|
||||
<div class="header_operations" data-exercise="'.$objExercise->selectId().'"
|
||||
data-question="'.$id.'">
|
||||
<div class="row">
|
||||
<div class="question col-sm-5 col-xs-12">'
|
||||
.$questionName.'
|
||||
</div>
|
||||
<div class="type text-center col-sm-1 col-xs-12">
|
||||
<span class="visible-xs-inline">'.get_lang('Type').' </span>'
|
||||
.$questionType.'
|
||||
</div>
|
||||
<div class="category col-sm-2 col-xs-12" title="'.$txtQuestionCat.'">
|
||||
<span class="visible-xs-inline">'.get_lang('Category').' </span>'
|
||||
.cut($txtQuestionCat, 42).'
|
||||
</div>
|
||||
<div class="level text-right col-sm-1 col-xs-6">
|
||||
<span class="visible-xs-inline">'.get_lang('Difficulty').' </span>'
|
||||
.$questionLevel.'
|
||||
</div>
|
||||
<div class="score text-right col-sm-1 col-xs-6">
|
||||
<span class="visible-xs-inline">'.get_lang('Score').' </span>'
|
||||
.$questionScore.'
|
||||
</div>
|
||||
<div class="btn-actions text-right col-sm-2 col-xs-6">
|
||||
<div class="edition">'.$btnActions.'</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-list-description-block" id="pnl-question-'.$id.'">
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
unset($objQuestionTmp);
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>'; //question list div
|
||||
} else {
|
||||
echo Display::return_message(get_lang('NoQuestion'), 'warning');
|
||||
}
|
||||
}
|
||||
1332
main/exercise/question_pool.php
Normal file
1332
main/exercise/question_pool.php
Normal file
File diff suppressed because it is too large
Load Diff
209
main/exercise/question_stats.php
Normal file
209
main/exercise/question_stats.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$isAllowedToEdit = api_is_allowed_to_edit(null, true);
|
||||
|
||||
if (!$isAllowedToEdit) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$exerciseId = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0;
|
||||
$exportXls = isset($_REQUEST['export_xls']) && !empty($_REQUEST['export_xls']) ? (int) $_REQUEST['export_xls'] : 0;
|
||||
$groups = $_REQUEST['groups'] ?? [];
|
||||
$users = $_REQUEST['users'] ?? [];
|
||||
|
||||
if (empty($exerciseId)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$sessionId = api_get_session_id();
|
||||
|
||||
$exercise = new Exercise();
|
||||
$result = $exercise->read($exerciseId);
|
||||
|
||||
if (empty($result)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$nameTools = get_lang('ExerciseManagement');
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'admin.php?exerciseId='.$exercise->iid.'&'.api_get_cidreq(),
|
||||
'name' => $exercise->selectTitle(true),
|
||||
];
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise_report.php?'.api_get_cidreq().'&exerciseId='.$exercise->iid,
|
||||
'name' => get_lang('StudentScore'),
|
||||
];
|
||||
$courseId = api_get_course_int_id();
|
||||
$courseInfo = api_get_course_info();
|
||||
|
||||
$form = new FormValidator('search_form', 'GET', api_get_self().'?id='.$exerciseId.'&'.api_get_cidreq());
|
||||
$form->addCourseHiddenParams();
|
||||
$form->addHidden('id', $exerciseId);
|
||||
|
||||
$courseGroups = GroupManager::get_group_list(null, $courseInfo);
|
||||
|
||||
if (!empty($courseGroups)) {
|
||||
$courseGroups = array_column($courseGroups, 'name', 'iid');
|
||||
$form->addSelect(
|
||||
'groups',
|
||||
get_lang('Groups'),
|
||||
$courseGroups,
|
||||
[
|
||||
'multiple' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$courseUsers = CourseManager::get_user_list_from_course_code($courseInfo['code']);
|
||||
if (!empty($courseUsers)) {
|
||||
array_walk(
|
||||
$courseUsers,
|
||||
function (&$data, $key) {
|
||||
$data = api_get_person_name($data['firstname'], $data['lastname']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$form->addSelect(
|
||||
'users',
|
||||
get_lang('Users'),
|
||||
$courseUsers,
|
||||
[
|
||||
'multiple' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$form->addButtonSearch(get_lang('Search'), 'searchSubmit');
|
||||
|
||||
$formToString = $form->toHtml();
|
||||
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped']);
|
||||
$row = 0;
|
||||
$column = 0;
|
||||
$headers = [
|
||||
get_lang('Question'),
|
||||
get_lang('WrongAnswer').' / '.get_lang('Total'),
|
||||
'%',
|
||||
];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
$table->setHeaderContents($row, $column, $header);
|
||||
$column++;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$tableXls[] = $headers;
|
||||
}
|
||||
$row++;
|
||||
$scoreDisplay = new ScoreDisplay();
|
||||
$orderedData = [];
|
||||
if ($form->validate()) {
|
||||
$questions = ExerciseLib::getWrongQuestionResults($courseId, $exerciseId, $sessionId, $groups, $users);
|
||||
foreach ($questions as $data) {
|
||||
$questionId = (int) $data['question_id'];
|
||||
$total = ExerciseLib::getTotalQuestionAnswered(
|
||||
$courseId,
|
||||
$exerciseId,
|
||||
$questionId,
|
||||
$sessionId,
|
||||
$groups,
|
||||
$users
|
||||
);
|
||||
$ordered = [
|
||||
$data['question'],
|
||||
$data['count'].' / '.$total,
|
||||
$scoreDisplay->display_score([$data['count'], $total], SCORE_AVERAGE),
|
||||
];
|
||||
$orderedData[] = $ordered;
|
||||
if ($exportXls) {
|
||||
$tableXls[] = $ordered;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$questions = ExerciseLib::getWrongQuestionResults($courseId, $exerciseId, $sessionId);
|
||||
foreach ($questions as $data) {
|
||||
$questionId = (int) $data['question_id'];
|
||||
$total = ExerciseLib::getTotalQuestionAnswered($courseId, $exerciseId, $questionId, $sessionId);
|
||||
$ordered = [
|
||||
$data['question'],
|
||||
$data['count'].' / '.$total,
|
||||
$scoreDisplay->display_score([$data['count'], $total], SCORE_AVERAGE),
|
||||
];
|
||||
$orderedData[] = $ordered;
|
||||
}
|
||||
}
|
||||
|
||||
$table = new SortableTableFromArray(
|
||||
$orderedData,
|
||||
0,
|
||||
100,
|
||||
'question_tracking'
|
||||
);
|
||||
|
||||
$table->hideNavigation = true;
|
||||
|
||||
$table->column = 2;
|
||||
$column = 0;
|
||||
foreach ($headers as $header) {
|
||||
$table->set_header($column, $header, false);
|
||||
$column++;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$fileName = get_lang('QuestionStats').'_'.api_get_course_id().'_'.$exerciseId.'_'.api_get_local_time();
|
||||
Export::arrayToXls($tableXls, $fileName);
|
||||
exit;
|
||||
}
|
||||
$htmlHeadXtra[] = '<script>
|
||||
$(function() {
|
||||
$("#export-xls").bind("click", function(e) {
|
||||
e.preventDefault();
|
||||
var input = $("<input>", {
|
||||
type: "hidden",
|
||||
name: "export_xls",
|
||||
value: "1"
|
||||
});
|
||||
$("#search_form").append(input);
|
||||
$("#search_form").submit();
|
||||
});
|
||||
$("#search_form_searchSubmit").bind("click", function(e) {
|
||||
e.preventDefault();
|
||||
if ($("input[name=\"export_xls\"]").length > 0) {
|
||||
$("input[name=\"export_xls\"]").remove();
|
||||
}
|
||||
$("#search_form").submit();
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
|
||||
Display::display_header($nameTools, get_lang('Exercise'));
|
||||
$actions = '<a href="exercise_report.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'">'.
|
||||
Display::return_icon(
|
||||
'back.png',
|
||||
get_lang('GoBackToQuestionList'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
)
|
||||
.'</a>';
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM),
|
||||
'#',
|
||||
['id' => 'export-xls']
|
||||
);
|
||||
|
||||
$actions = Display::div($actions, ['class' => 'actions']);
|
||||
|
||||
echo $actions;
|
||||
echo $formToString;
|
||||
echo $table->return_table();
|
||||
Display::display_footer();
|
||||
BIN
main/exercise/quiz_template.xls
Normal file
BIN
main/exercise/quiz_template.xls
Normal file
Binary file not shown.
35
main/exercise/recalculate.php
Normal file
35
main/exercise/recalculate.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
if (!isset($_REQUEST['user'], $_REQUEST['exercise'], $_REQUEST['id'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$isAllowedToEdit = api_is_allowed_to_edit(true, true);
|
||||
|
||||
if (!$isAllowedToEdit) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$studentId = (int) $_REQUEST['user'];
|
||||
$exerciseId = (int) $_REQUEST['exercise'];
|
||||
$exeId = (int) $_REQUEST['id'];
|
||||
|
||||
/** @var TrackEExercises $trackedExercise */
|
||||
$trackedExercise = ExerciseLib::recalculateResult(
|
||||
$_REQUEST['id'],
|
||||
$_REQUEST['user'],
|
||||
$_REQUEST['exercise']
|
||||
);
|
||||
|
||||
$totalScore = $trackedExercise->getExeResult();
|
||||
$totalWeight = $trackedExercise->getExeWeighting();
|
||||
|
||||
echo $totalScore.'/'.$totalWeight;
|
||||
61
main/exercise/recalculate_all.php
Normal file
61
main/exercise/recalculate_all.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
if (!isset($_REQUEST['exercise'])) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$exerciseId = (int) $_REQUEST['exercise'];
|
||||
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true) ||
|
||||
api_is_drh() ||
|
||||
api_is_student_boss() ||
|
||||
api_is_session_admin();
|
||||
|
||||
if (!$is_allowedToEdit) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$result = ExerciseLib::get_exam_results_data(
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
'asc',
|
||||
$exerciseId,
|
||||
'',
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
foreach ($result as $track) {
|
||||
/** @var TrackEExercises $trackedExercise */
|
||||
$trackedExercise = ExerciseLib::recalculateResult(
|
||||
$track['id'],
|
||||
$track['user_id'],
|
||||
$exerciseId
|
||||
);
|
||||
|
||||
if (!$trackedExercise) {
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('BadFormData').'<br>ID: '.$track['id'], 'warning', false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$url = api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'
|
||||
.api_get_cidreq()
|
||||
."&exerciseId=$exerciseId";
|
||||
|
||||
header("Location: $url");
|
||||
184
main/exercise/result.php
Normal file
184
main/exercise/result.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use ChamiloSession as Session;
|
||||
|
||||
/**
|
||||
* Shows the exercise results.
|
||||
*
|
||||
* @author Julio Montoya - Simple exercise result page
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
$current_course_tool = TOOL_QUIZ;
|
||||
|
||||
$id = isset($_REQUEST['id']) ? (int) $_GET['id'] : 0; // exe id
|
||||
$show_headers = isset($_REQUEST['show_headers']) ? (int) $_REQUEST['show_headers'] : null;
|
||||
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
|
||||
$origin = api_get_origin();
|
||||
|
||||
if (in_array($origin, ['learnpath', 'embeddable', 'mobileapp', 'iframe'])) {
|
||||
$show_headers = false;
|
||||
}
|
||||
|
||||
api_protect_course_script($show_headers);
|
||||
|
||||
if (empty($id)) {
|
||||
api_not_allowed($show_headers);
|
||||
}
|
||||
|
||||
$is_allowedToEdit = api_is_allowed_to_edit(null, true) || api_is_course_tutor();
|
||||
|
||||
// Getting results from the exe_id. This variable also contain all the information about the exercise
|
||||
$track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($id);
|
||||
|
||||
// No track info
|
||||
if (empty($track_exercise_info)) {
|
||||
api_not_allowed($show_headers);
|
||||
}
|
||||
|
||||
$exercise_id = $track_exercise_info['exe_exo_id'];
|
||||
$student_id = (int) $track_exercise_info['exe_user_id'];
|
||||
$current_user_id = api_get_user_id();
|
||||
|
||||
$objExercise = new Exercise();
|
||||
if (!empty($exercise_id)) {
|
||||
$objExercise->read($exercise_id);
|
||||
}
|
||||
|
||||
if (empty($objExercise)) {
|
||||
api_not_allowed($show_headers);
|
||||
}
|
||||
|
||||
// Only users can see their own results
|
||||
if (!$is_allowedToEdit) {
|
||||
if ($student_id != $current_user_id) {
|
||||
api_not_allowed($show_headers);
|
||||
}
|
||||
}
|
||||
|
||||
$allowSignature = false;
|
||||
if ($student_id === $current_user_id && ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise)) {
|
||||
// Check if signature exists.
|
||||
$signature = ExerciseSignaturePlugin::getSignature($current_user_id, $track_exercise_info);
|
||||
if (false === $signature) {
|
||||
$allowSignature = true;
|
||||
}
|
||||
}
|
||||
|
||||
$htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
|
||||
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
|
||||
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
|
||||
if ($allowSignature) {
|
||||
$htmlHeadXtra[] = api_get_asset('signature_pad/signature_pad.umd.js');
|
||||
}
|
||||
|
||||
if (!empty($objExercise->getResultAccess())) {
|
||||
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.dateformat.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/javascript/jquery.epiclock.min.js');
|
||||
$htmlHeadXtra[] = api_get_js('epiclock/renderers/minute/epiclock.minute.js');
|
||||
}
|
||||
|
||||
if ($show_headers) {
|
||||
$interbreadcrumb[] = [
|
||||
'url' => 'exercise.php?'.api_get_cidreq(),
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('Result')];
|
||||
$this_section = SECTION_COURSES;
|
||||
} else {
|
||||
$htmlHeadXtra[] = '<style>
|
||||
body { background: none;}
|
||||
</style>';
|
||||
|
||||
if ($origin === 'mobileapp') {
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="javascript:window.history.go(-1);">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), [], 32).'</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (api_get_configuration_value('allow_skill_rel_items') == true) {
|
||||
$htmlContentExtraClass[] = 'feature-item-user-skill-on';
|
||||
}
|
||||
|
||||
$message = Session::read('attempt_remaining');
|
||||
Session::erase('attempt_remaining');
|
||||
|
||||
$allowExportPdf = api_get_configuration_value('quiz_results_answers_report');
|
||||
|
||||
ob_start();
|
||||
$stats = ExerciseLib::displayQuestionListByAttempt(
|
||||
$objExercise,
|
||||
$id,
|
||||
false,
|
||||
$message,
|
||||
$allowSignature,
|
||||
$allowExportPdf,
|
||||
'export' === $action
|
||||
);
|
||||
$pageContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
switch ($action) {
|
||||
case 'export':
|
||||
if ($allowExportPdf) {
|
||||
$allAnswers = $stats['all_answers_html'];
|
||||
@$pdf = new PDF();
|
||||
$cssFile = api_get_path(SYS_CSS_PATH).'themes/chamilo/default.css';
|
||||
$title = get_lang('ResponseReport');
|
||||
$exerciseTitle = $objExercise->get_formated_title();
|
||||
$studentInfo = api_get_user_info($student_id);
|
||||
$userHeader = $objExercise->showExerciseResultHeader(
|
||||
$studentInfo,
|
||||
$track_exercise_info,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
$filename = get_lang('Exercise').'_'.$exerciseTitle;
|
||||
$pdf->content_to_pdf("
|
||||
<html><body>
|
||||
<h2 style='text-align: center'>$title</h2>
|
||||
$userHeader
|
||||
$allAnswers
|
||||
</body></html>",
|
||||
file_get_contents($cssFile),
|
||||
$filename,
|
||||
api_get_course_id(),
|
||||
'D',
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
|
||||
$lpId = (int) $track_exercise_info['orig_lp_id'];
|
||||
$lpItemId = (int) $track_exercise_info['orig_lp_item_id'];
|
||||
$lpViewId = (int) $track_exercise_info['orig_lp_item_view_id'];
|
||||
|
||||
$pageBottom = '<div class="question-return">';
|
||||
$pageBottom .= Display::url(
|
||||
get_lang('BackToAttemptList'),
|
||||
api_get_path(WEB_CODE_PATH).'exercise/overview.php?exerciseId='.$exercise_id.'&'.api_get_cidreq().
|
||||
"&learnpath_id=$lpId&learnpath_item_id=$lpItemId&learnpath_item_view_id=$lpViewId",
|
||||
['class' => 'btn btn-primary']
|
||||
);
|
||||
$pageBottom .= '</div>';
|
||||
$pageContent .= $pageBottom;
|
||||
|
||||
$template = new Template('', $show_headers, $show_headers);
|
||||
$template->assign('page_content', $pageContent);
|
||||
$template->assign('allow_signature', $allowSignature);
|
||||
$template->assign('exe_id', $id);
|
||||
$layout = $template->fetch($template->get_template('exercise/result.tpl'));
|
||||
$template->assign('content', $layout);
|
||||
$template->display_one_col_template();
|
||||
113
main/exercise/savescores.php
Normal file
113
main/exercise/savescores.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Saving the scores.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$courseInfo = api_get_course_info();
|
||||
$_user = api_get_user_info();
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path']."/document";
|
||||
|
||||
$test = $_REQUEST['test'];
|
||||
$full_file_path = $documentPath.$test;
|
||||
|
||||
my_delete($full_file_path.$_user['user_id'].".t.html");
|
||||
|
||||
$TABLETRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
|
||||
$TABLE_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
|
||||
|
||||
$score = $_REQUEST['score'];
|
||||
$origin = api_get_origin();
|
||||
$learnpath_item_id = intval($_REQUEST['learnpath_item_id']);
|
||||
$lpViewId = isset($_REQUEST['lp_view_id']) ? intval($_REQUEST['lp_view_id']) : null;
|
||||
$course_id = $courseInfo['real_id'];
|
||||
$jscript2run = '';
|
||||
|
||||
/**
|
||||
* Save the score for a HP quiz. Can be used by the learnpath tool as well
|
||||
* for HotPotatoes quizzes. When coming from the learning path, we
|
||||
* use the session variables telling us which item of the learning path has to
|
||||
* be updated (score-wise).
|
||||
*
|
||||
* @param string File is the exercise name (the file name for a HP)
|
||||
* @param int Score to save inside the tracking tables (HP and learnpath)
|
||||
*/
|
||||
function save_scores($file, $score)
|
||||
{
|
||||
global $origin;
|
||||
$TABLETRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
|
||||
$_user = api_get_user_info();
|
||||
// if tracking is disabled record nothing
|
||||
$weighting = 100; // 100%
|
||||
$date = api_get_utc_datetime();
|
||||
$c_id = api_get_course_int_id();
|
||||
|
||||
if ($_user['user_id']) {
|
||||
$user_id = $_user['user_id'];
|
||||
} else {
|
||||
// anonymous
|
||||
$user_id = "NULL";
|
||||
}
|
||||
|
||||
$params = [
|
||||
'exe_name' => $file,
|
||||
'exe_user_id' => $user_id,
|
||||
'exe_date' => $date,
|
||||
'c_id' => $c_id,
|
||||
'exe_result' => $score,
|
||||
'exe_weighting' => $weighting,
|
||||
];
|
||||
Database::insert($TABLETRACK_HOTPOTATOES, $params);
|
||||
|
||||
if ($origin == 'learnpath') {
|
||||
//if we are in a learning path, save the score in the corresponding
|
||||
//table to get tracking in there as well
|
||||
global $jscript2run;
|
||||
//record the results in the learning path, using the SCORM interface (API)
|
||||
$jscript2run .= "<script>
|
||||
$(function() {
|
||||
//API_obj = window.frames.window.content.API;
|
||||
//API_obj = $('content_id').context.defaultView.content.API; //works only in FF
|
||||
//API_obj = window.parent.frames.window.top.API;
|
||||
API_obj = window.top.API;
|
||||
API_obj.void_save_asset('$score', '$weighting', 0, 'completed');
|
||||
});
|
||||
</script>";
|
||||
}
|
||||
}
|
||||
|
||||
// Save the Scores
|
||||
save_scores($test, $score);
|
||||
|
||||
// Back
|
||||
if ($origin != 'learnpath') {
|
||||
$url = "exercise.php"; // back to exercises
|
||||
$jscript2run .= '<script>'."window.open('$url', '_top', '')".'</script>';
|
||||
echo $jscript2run;
|
||||
} else {
|
||||
$htmlHeadXtra[] = $jscript2run;
|
||||
Display::display_reduced_header();
|
||||
if (!empty($course_id) && !empty($learnpath_item_id) && !empty($lpViewId)) {
|
||||
$sql = "UPDATE $TABLE_LP_ITEM_VIEW SET
|
||||
status = 'completed'
|
||||
WHERE
|
||||
c_id = $course_id AND
|
||||
lp_item_id= $learnpath_item_id AND
|
||||
lp_view_id = $lpViewId
|
||||
";
|
||||
Database::query($sql);
|
||||
echo Display::return_message(get_lang('HotPotatoesFinished'), 'confirm');
|
||||
} else {
|
||||
echo Display::return_message(get_lang('Error'), 'error');
|
||||
}
|
||||
|
||||
Display::display_footer();
|
||||
}
|
||||
124
main/exercise/showinframes.php
Normal file
124
main/exercise/showinframes.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Code library for HotPotatoes integration.
|
||||
*
|
||||
* @package chamilo.exercise
|
||||
*
|
||||
* @author Istvan Mandak
|
||||
*/
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
|
||||
$_course = api_get_course_info();
|
||||
|
||||
$time = time();
|
||||
$doc_url = str_replace(['../', '\\', '\\0', '..'], ['', '', '', ''], urldecode($_GET['file']));
|
||||
$cid = api_get_course_id();
|
||||
$document_path = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
|
||||
$document_web_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document';
|
||||
$origin = api_get_origin();
|
||||
$learnpath_id = isset($_REQUEST['learnpath_id']) ? $_REQUEST['learnpath_id'] : null;
|
||||
$learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? $_REQUEST['learnpath_item_id'] : null;
|
||||
$time = isset($_REQUEST['time']) ? $_REQUEST['time'] : null;
|
||||
$lpViewId = isset($_REQUEST['lp_view_id']) ? $_REQUEST['lp_view_id'] : null;
|
||||
|
||||
$user_id = api_get_user_id();
|
||||
$full_file_path = $document_path.$doc_url;
|
||||
my_delete($full_file_path.$user_id.'.t.html');
|
||||
$content = ReadFileCont($full_file_path.$user_id.'.t.html');
|
||||
|
||||
if ($content == '') {
|
||||
$content = ReadFileCont($full_file_path);
|
||||
// Do not move this like:
|
||||
$mit = "function Finish(){";
|
||||
$js_content = "
|
||||
// Code added - start
|
||||
var SaveScoreVariable = 0;
|
||||
function mySaveScore() {
|
||||
if (SaveScoreVariable==0) {
|
||||
SaveScoreVariable = 1;
|
||||
if (C.ie) {
|
||||
document.location.href = '".api_get_path(WEB_CODE_PATH)."exercise/savescores.php?lp_view_id=$lpViewId&origin=$origin&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&time=".Security::remove_XSS($time)."&test=".$doc_url."&uid=".$user_id."&cid=".$cid."&score='+Score;
|
||||
} else {
|
||||
window.location.href = '".api_get_path(WEB_CODE_PATH)."exercise/savescores.php?lp_view_id=$lpViewId&origin=$origin&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&time=".Security::remove_XSS($time)."&test=".$doc_url."&uid=".$user_id."&cid=".$cid."&score='+Score;
|
||||
}
|
||||
}
|
||||
}
|
||||
function Finish() {
|
||||
mySaveScore();
|
||||
// Code added - end
|
||||
";
|
||||
|
||||
$newcontent = str_replace($mit, $js_content, $content);
|
||||
$prehref = "<!-- BeginTopNavButtons -->";
|
||||
$posthref = "<!-- BeginTopNavButtons -->";
|
||||
$newcontent = str_replace($prehref, $posthref, $newcontent);
|
||||
|
||||
if (CheckSubFolder($full_file_path.$user_id.'.t.html') == 0) {
|
||||
$newcontent = ReplaceImgTag($newcontent);
|
||||
}
|
||||
} else {
|
||||
$newcontent = $content;
|
||||
}
|
||||
|
||||
WriteFileCont($full_file_path.$user_id.'.t.html', $newcontent);
|
||||
$doc_url = GetFolderPath($doc_url).urlencode(basename($doc_url));
|
||||
|
||||
$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path']."/document";
|
||||
$my_file = Security::remove_XSS($_GET['file']);
|
||||
$my_file = str_replace(['../', '\\..', '\\0', '..\\'], ['', '', '', ''], urldecode($my_file));
|
||||
|
||||
$title = GetQuizName($my_file, $documentPath);
|
||||
if ($title == '') {
|
||||
$title = basename($my_file);
|
||||
}
|
||||
$nameTools = $title;
|
||||
$htmlHeadXtra[] = <<<HTML
|
||||
<script>
|
||||
function setHeight()
|
||||
{
|
||||
var iframe = document.getElementById('hotpotatoe');
|
||||
iframe.height = 800;
|
||||
var maxheight = $(iframe.contentDocument.body).outerHeight(true);
|
||||
iframe.height = maxheight
|
||||
$(iframe.contentDocument.body).children().each(function() {
|
||||
// If this elements height is bigger than the biggestHeight
|
||||
var tempheight = $(this)["0"].offsetHeight + $(this)["0"].offsetTop;
|
||||
if (tempheight > maxheight) {
|
||||
// Set the maxheight to this Height
|
||||
maxheight = tempheight;
|
||||
}
|
||||
});
|
||||
iframe.height = maxheight;
|
||||
}
|
||||
|
||||
$(function() {
|
||||
var iframe = document.getElementById('hotpotatoe');
|
||||
iframe.onload = function () {
|
||||
// this.height = $(this.contentDocument.body).outerHeight(true)
|
||||
setTimeout(function(){
|
||||
setHeight();
|
||||
}, 1750);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
HTML;
|
||||
|
||||
$interbreadcrumb[] = ["url" => './exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
|
||||
if ($origin == 'learnpath') {
|
||||
Display::display_reduced_header($nameTools, "Exercise");
|
||||
} else {
|
||||
Display::display_header($nameTools, "Exercise");
|
||||
}
|
||||
$url = $document_web_path.$doc_url.$user_id.'.t.html?time='.intval($time);
|
||||
echo '<iframe id="hotpotatoe" name="hotpotatoe" width="100%" height="100%" frameborder="0" src="'.$url.'"></iframe>';
|
||||
|
||||
if ($origin == 'learnpath') {
|
||||
Display::display_reduced_footer();
|
||||
} else {
|
||||
Display::display_footer();
|
||||
}
|
||||
390
main/exercise/stats.php
Normal file
390
main/exercise/stats.php
Normal file
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
/* See license terms in /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true, false, true);
|
||||
|
||||
$showPage = false;
|
||||
|
||||
if (api_is_platform_admin() || api_is_course_admin() ||
|
||||
api_is_course_tutor() || api_is_session_general_coach() || api_is_allowed_to_edit(null, true)
|
||||
) {
|
||||
$showPage = true;
|
||||
}
|
||||
|
||||
if (!$showPage) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$exportXls = isset($_GET['export_xls']) && !empty($_GET['export_xls']) ? (int) $_GET['export_xls'] : 0;
|
||||
$exerciseId = isset($_GET['exerciseId']) && !empty($_GET['exerciseId']) ? (int) $_GET['exerciseId'] : 0;
|
||||
$objExercise = new Exercise();
|
||||
$result = $objExercise->read($exerciseId);
|
||||
|
||||
if (!$result) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$sessionId = api_get_session_id();
|
||||
$courseCode = api_get_course_id();
|
||||
$courseId = api_get_course_int_id();
|
||||
|
||||
if (empty($sessionId)) {
|
||||
$students = CourseManager::get_student_list_from_course_code($courseCode, false);
|
||||
} else {
|
||||
$students = CourseManager::get_student_list_from_course_code($courseCode, true, $sessionId);
|
||||
}
|
||||
$count_students = count($students);
|
||||
$questionList = $objExercise->getQuestionForTeacher(0, $objExercise->getQuestionCount());
|
||||
|
||||
$data = [];
|
||||
// Question title # of students who tool it Lowest score Average Highest score Maximum score
|
||||
$headers = [
|
||||
get_lang('Question'),
|
||||
get_lang('QuestionType'),
|
||||
get_lang('NumberStudentWhoSelectedIt'),
|
||||
get_lang('LowestScore'),
|
||||
get_lang('AverageScore'),
|
||||
get_lang('HighestScore'),
|
||||
get_lang('Weighting'),
|
||||
];
|
||||
|
||||
if (!empty($questionList)) {
|
||||
foreach ($questionList as $question_id) {
|
||||
$questionObj = Question::read($question_id);
|
||||
$exerciseStats = ExerciseLib::get_student_stats_by_question(
|
||||
$question_id,
|
||||
$exerciseId,
|
||||
$courseId,
|
||||
$sessionId,
|
||||
true
|
||||
);
|
||||
|
||||
$count_users = ExerciseLib::get_number_students_question_with_answer_count(
|
||||
$question_id,
|
||||
$exerciseId,
|
||||
$courseCode,
|
||||
$sessionId,
|
||||
$questionObj->type
|
||||
);
|
||||
|
||||
$data[$question_id]['name'] = cut($questionObj->question, 100);
|
||||
$data[$question_id]['type'] = $questionObj->get_question_type_name();
|
||||
$percentage = 0;
|
||||
if ($count_students) {
|
||||
$percentage = $count_users / $count_students * 100;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$data[$question_id]['students_who_try_exercise'] = $count_users.' / '.$count_students.' ('.$percentage.'%)';
|
||||
} else {
|
||||
$data[$question_id]['students_who_try_exercise'] = Display::bar_progress(
|
||||
$percentage,
|
||||
false,
|
||||
$count_users.' / '.$count_students
|
||||
);
|
||||
}
|
||||
$data[$question_id]['lowest_score'] = round($exerciseStats['min'], 2);
|
||||
$data[$question_id]['average_score'] = round($exerciseStats['average'], 2);
|
||||
$data[$question_id]['highest_score'] = round($exerciseStats['max'], 2);
|
||||
$data[$question_id]['max_score'] = round($questionObj->weighting, 2);
|
||||
}
|
||||
}
|
||||
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
|
||||
$row = 0;
|
||||
$column = 0;
|
||||
foreach ($headers as $header) {
|
||||
$table->setHeaderContents($row, $column, $header);
|
||||
$column++;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$tableXls1[] = $headers;
|
||||
}
|
||||
$row++;
|
||||
foreach ($data as $row_table) {
|
||||
$column = 0;
|
||||
foreach ($row_table as $key => $cell) {
|
||||
$table->setCellContents($row, $column, $cell);
|
||||
$table->updateCellAttributes($row, $column, 'align="center"');
|
||||
if ($exportXls) {
|
||||
$row_table[$key] = strip_tags($cell);
|
||||
}
|
||||
$column++;
|
||||
}
|
||||
$table->updateRowAttributes($row, $row % 2 ? 'class="row_even"' : 'class="row_odd"', true);
|
||||
if ($exportXls) {
|
||||
$tableXls1[] = $row_table;
|
||||
}
|
||||
$row++;
|
||||
}
|
||||
$content = $table->toHtml();
|
||||
|
||||
// Format B
|
||||
$headers = [
|
||||
get_lang('Question'),
|
||||
get_lang('Answer'),
|
||||
get_lang('Correct'),
|
||||
get_lang('NumberStudentWhoSelectedIt'),
|
||||
];
|
||||
|
||||
$data = [];
|
||||
|
||||
if (!empty($questionList)) {
|
||||
$id = 0;
|
||||
foreach ($questionList as $question_id) {
|
||||
$questionObj = Question::read($question_id);
|
||||
$exerciseStats = ExerciseLib::get_student_stats_by_question(
|
||||
$question_id,
|
||||
$exerciseId,
|
||||
$courseId,
|
||||
$sessionId,
|
||||
true
|
||||
);
|
||||
|
||||
$answer = new Answer($question_id);
|
||||
$answer_count = $answer->selectNbrAnswers();
|
||||
|
||||
for ($answer_id = 1; $answer_id <= $answer_count; $answer_id++) {
|
||||
$answer_info = $answer->selectAnswer($answer_id);
|
||||
$is_correct = $answer->isCorrect($answer_id);
|
||||
$correct_answer = $is_correct == 1 ? get_lang('Yes') : get_lang('No');
|
||||
$real_answer_id = $answer->selectAutoId($answer_id);
|
||||
|
||||
// Overwriting values depending of the question
|
||||
switch ($questionObj->type) {
|
||||
case FILL_IN_BLANKS:
|
||||
case FILL_IN_BLANKS_COMBINATION:
|
||||
$answer_info_db = $answer_info;
|
||||
$answer_info = substr($answer_info, 0, strpos($answer_info, '::'));
|
||||
$correct_answer = $is_correct;
|
||||
$answers = $objExercise->fill_in_blank_answer_to_array($answer_info);
|
||||
$counter = 0;
|
||||
foreach ($answers as $answer_item) {
|
||||
if (0 == $counter) {
|
||||
$data[$id]['name'] = cut($questionObj->question, 100);
|
||||
} else {
|
||||
$data[$id]['name'] = '-';
|
||||
}
|
||||
$data[$id]['answer'] = $answer_item;
|
||||
|
||||
$answer_item = api_substr($answer_item, 1);
|
||||
$answer_item = api_substr($answer_item, 0, api_strlen($answer_item) - 1);
|
||||
|
||||
$data[$id]['answer'] = $answer_item;
|
||||
$data[$id]['correct'] = '-';
|
||||
|
||||
$count = ExerciseLib::getNumberStudentsFillBlanksAnswerCount($question_id, $exerciseId);
|
||||
$count = isset($count[$counter]) ? $count[$counter] : 0;
|
||||
|
||||
$percentage = 0;
|
||||
if (!empty($count_students)) {
|
||||
$percentage = $count / $count_students * 100;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$data[$id]['attempts'] = $count.' / '.$count_students.' ('.$percentage.'%)';
|
||||
} else {
|
||||
$data[$id]['attempts'] = Display::bar_progress(
|
||||
$percentage,
|
||||
false,
|
||||
$count.' / '.$count_students
|
||||
);
|
||||
}
|
||||
$id++;
|
||||
$counter++;
|
||||
}
|
||||
break;
|
||||
case MATCHING:
|
||||
case MATCHING_DRAGGABLE:
|
||||
if ($is_correct == 0) {
|
||||
if ($answer_id == 1) {
|
||||
$data[$id]['name'] = cut($questionObj->question, 100);
|
||||
} else {
|
||||
$data[$id]['name'] = '-';
|
||||
}
|
||||
$correct = '';
|
||||
for ($i = 1; $i <= $answer_count; $i++) {
|
||||
$is_correct_i = $answer->isCorrect($i);
|
||||
if ($is_correct_i != 0 && $is_correct_i == $answer_id) {
|
||||
$correct = $answer->selectAnswer($i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$data[$id]['answer'] = $correct;
|
||||
$data[$id]['correct'] = $answer_info;
|
||||
|
||||
$count = ExerciseLib::get_number_students_answer_count(
|
||||
$answer_id,
|
||||
$question_id,
|
||||
$exerciseId,
|
||||
$courseId,
|
||||
$sessionId,
|
||||
MATCHING
|
||||
);
|
||||
$percentage = 0;
|
||||
if (!empty($count_students)) {
|
||||
$percentage = $count / $count_students * 100;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$data[$id]['attempts'] = $count.' / '.$count_students.' ('.$percentage.'%)';
|
||||
} else {
|
||||
$data[$id]['attempts'] = Display::bar_progress(
|
||||
$percentage,
|
||||
false,
|
||||
$count.' / '.$count_students
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HOT_SPOT:
|
||||
case HOT_SPOT_COMBINATION:
|
||||
if ($answer_id == 1) {
|
||||
$data[$id]['name'] = cut($questionObj->question, 100);
|
||||
} else {
|
||||
$data[$id]['name'] = '-';
|
||||
}
|
||||
$data[$id]['answer'] = $answer_info;
|
||||
$data[$id]['correct'] = '-';
|
||||
|
||||
$count = ExerciseLib::get_number_students_answer_hotspot_count(
|
||||
$answer_id,
|
||||
$question_id,
|
||||
$exerciseId,
|
||||
$courseCode,
|
||||
$sessionId
|
||||
);
|
||||
$percentage = 0;
|
||||
if (!empty($count_students)) {
|
||||
$percentage = $count / $count_students * 100;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$data[$id]['attempts'] = $count.' / '.$count_students.' ('.$percentage.'%)';
|
||||
} else {
|
||||
$data[$id]['attempts'] = Display::bar_progress(
|
||||
$percentage,
|
||||
false,
|
||||
$count.' / '.$count_students
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ($answer_id == 1) {
|
||||
$data[$id]['name'] = cut($questionObj->question, 100);
|
||||
} else {
|
||||
$data[$id]['name'] = '-';
|
||||
}
|
||||
$data[$id]['answer'] = $answer_info;
|
||||
$data[$id]['correct'] = $correct_answer;
|
||||
|
||||
$count = ExerciseLib::get_number_students_answer_count(
|
||||
$real_answer_id,
|
||||
$question_id,
|
||||
$exerciseId,
|
||||
$courseId,
|
||||
$sessionId
|
||||
);
|
||||
$percentage = 0;
|
||||
if (!empty($count_students)) {
|
||||
$percentage = $count / $count_students * 100;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$data[$id]['attempts'] = $count.' / '.$count_students.' ('.$percentage.'%)';
|
||||
} else {
|
||||
$data[$id]['attempts'] = Display::bar_progress(
|
||||
$percentage,
|
||||
false,
|
||||
$count.' / '.$count_students
|
||||
);
|
||||
}
|
||||
}
|
||||
$id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format A table
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
|
||||
$row = 0;
|
||||
$column = 0;
|
||||
foreach ($headers as $header) {
|
||||
$table->setHeaderContents($row, $column, $header);
|
||||
$column++;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$tableXls1[] = []; // it adds an empty line after the first table
|
||||
$tableXls2[] = $headers;
|
||||
}
|
||||
$row++;
|
||||
foreach ($data as $row_table) {
|
||||
$column = 0;
|
||||
foreach ($row_table as $key => $cell) {
|
||||
$table->setCellContents($row, $column, $cell);
|
||||
$table->updateCellAttributes($row, $column, 'align="center"');
|
||||
if ($exportXls) {
|
||||
$row_table[$key] = strip_tags($cell);
|
||||
}
|
||||
$column++;
|
||||
}
|
||||
$table->updateRowAttributes($row, $row % 2 ? 'class="row_even"' : 'class="row_odd"', true);
|
||||
if ($exportXls) {
|
||||
$tableXls2[] = $row_table;
|
||||
}
|
||||
$row++;
|
||||
}
|
||||
$content .= $table->toHtml();
|
||||
|
||||
$exportPdf = isset($_GET['export_pdf']) && !empty($_GET['export_pdf']) ? (int) $_GET['export_pdf'] : 0;
|
||||
if ($exportPdf) {
|
||||
$fileName = get_lang('Report').'_'.api_get_course_id().'_'.api_get_local_time();
|
||||
$params = [
|
||||
'filename' => $fileName,
|
||||
'pdf_title' => $objExercise->selectTitle(true).'<br>'.get_lang('ReportByQuestion'),
|
||||
'pdf_description' => get_lang('Report'),
|
||||
'format' => 'A4',
|
||||
'orientation' => 'P',
|
||||
];
|
||||
|
||||
Export::export_html_to_pdf($content, $params);
|
||||
exit;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$fileName = get_lang('Report').'_'.api_get_course_id().'_'.api_get_local_time();
|
||||
$tableXls = array_merge($tableXls1, $tableXls2);
|
||||
Export::arrayToXls($tableXls, $fileName);
|
||||
exit;
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
"url" => "exercise.php?".api_get_cidreq(),
|
||||
"name" => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => "admin.php?exerciseId=$exerciseId&".api_get_cidreq(),
|
||||
'name' => $objExercise->selectTitle(true),
|
||||
];
|
||||
|
||||
$tpl = new Template(get_lang('ReportByQuestion'));
|
||||
$actions = '<a href="exercise_report.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'">'.
|
||||
Display::return_icon(
|
||||
'back.png',
|
||||
get_lang('GoBackToQuestionList'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
)
|
||||
.'</a>';
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('pdf.png', get_lang('ExportToPDF'), [], ICON_SIZE_MEDIUM),
|
||||
'stats.php?exerciseId='.$exerciseId.'&export_pdf=1&'.api_get_cidreq()
|
||||
);
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM),
|
||||
'stats.php?exerciseId='.$exerciseId.'&export_xls=1&'.api_get_cidreq()
|
||||
);
|
||||
|
||||
$actions = Display::div($actions, ['class' => 'actions']);
|
||||
$content = $actions.$content;
|
||||
$tpl->assign('content', $content);
|
||||
$tpl->display_one_col_template();
|
||||
100
main/exercise/stats_attempts.php
Normal file
100
main/exercise/stats_attempts.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/* See license terms in /license.txt */
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true, false, true);
|
||||
|
||||
$showPage = false;
|
||||
if (api_is_platform_admin() || api_is_course_admin() ||
|
||||
api_is_course_tutor() || api_is_session_general_coach() || api_is_allowed_to_edit(null, true)
|
||||
) {
|
||||
$showPage = true;
|
||||
}
|
||||
|
||||
if (!$showPage) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$exportXls = isset($_GET['export_xls']) && !empty($_GET['export_xls']) ? (int) $_GET['export_xls'] : 0;
|
||||
$exerciseId = isset($_GET['exerciseId']) && !empty($_GET['exerciseId']) ? (int) $_GET['exerciseId'] : 0;
|
||||
$objExercise = new Exercise();
|
||||
$result = $objExercise->read($exerciseId);
|
||||
|
||||
if (!$result) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$stats = ExerciseLib::getTrackExerciseAttemptsTable($objExercise);
|
||||
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
|
||||
$row = 0;
|
||||
$column = 0;
|
||||
foreach ($stats['headers'] as $header) {
|
||||
$table->setHeaderContents($row, $column, $header);
|
||||
$column++;
|
||||
}
|
||||
if ($exportXls) {
|
||||
$tableXls[] = $stats['headers_xls'];
|
||||
}
|
||||
$row++;
|
||||
foreach ($stats['rows'] as $rowTable) {
|
||||
$column = 0;
|
||||
foreach ($rowTable as $key => $cell) {
|
||||
$table->setCellContents($row, $column, $cell);
|
||||
$table->updateCellAttributes($row, $column, 'align="center"');
|
||||
if ($exportXls) {
|
||||
$rowTable[$key] = strip_tags($cell);
|
||||
}
|
||||
$column++;
|
||||
}
|
||||
$table->updateRowAttributes($row, $row % 2 ? 'class="row_even"' : 'class="row_odd"', true);
|
||||
if ($exportXls) {
|
||||
$tableXls[] = $rowTable;
|
||||
}
|
||||
$row++;
|
||||
}
|
||||
|
||||
$nameTools = get_lang('Results').': '.$objExercise->selectTitle(true);
|
||||
$content = '<h2 class="page-header">'.$nameTools.'</h2>';
|
||||
$content .= '<div style="width: 100%;overflow: auto;">';
|
||||
$content .= $table->toHtml();
|
||||
$content .= '</div>';
|
||||
|
||||
if ($exportXls) {
|
||||
$fileName = get_lang('Report').'_'.api_get_course_id().'_'.api_get_local_time();
|
||||
$tableXls = array_merge($tableXls);
|
||||
Export::arrayToXls($tableXls, $fileName);
|
||||
exit;
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
"url" => "exercise.php?".api_get_cidreq(),
|
||||
"name" => get_lang('Exercises'),
|
||||
];
|
||||
$interbreadcrumb[] = [
|
||||
'url' => "admin.php?exerciseId=$exerciseId&".api_get_cidreq(),
|
||||
'name' => $objExercise->selectTitle(true),
|
||||
];
|
||||
|
||||
$tpl = new Template(get_lang('ReportByAttempts'));
|
||||
$actions = '<a href="exercise_report.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'">'.
|
||||
Display::return_icon(
|
||||
'back.png',
|
||||
get_lang('GoBackToQuestionList'),
|
||||
'',
|
||||
ICON_SIZE_MEDIUM
|
||||
)
|
||||
.'</a>';
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM),
|
||||
'stats_attempts.php?exerciseId='.$exerciseId.'&export_xls=1&'.api_get_cidreq()
|
||||
);
|
||||
|
||||
$actions = Display::div($actions, ['class' => 'actions']);
|
||||
$content = $actions.$content;
|
||||
$tpl->assign('content', $content);
|
||||
|
||||
$tpl->display_one_col_template();
|
||||
272
main/exercise/tests_category.php
Normal file
272
main/exercise/tests_category.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
hubert.borderiou
|
||||
Manage tests category page
|
||||
*/
|
||||
$htmlHeadXtra[] = '
|
||||
<script>
|
||||
function confirmDelete(in_txt, in_id) {
|
||||
var oldbgcolor = document.getElementById(in_id).style.backgroundColor;
|
||||
document.getElementById(in_id).style.backgroundColor="#AAFFB0";
|
||||
if (confirm(in_txt)) {
|
||||
return true;
|
||||
} else {
|
||||
document.getElementById(in_id).style.backgroundColor = oldbgcolor;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>';
|
||||
|
||||
$nameTools = '';
|
||||
|
||||
require_once __DIR__.'/../inc/global.inc.php';
|
||||
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
if (!api_is_allowed_to_edit()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$category = new TestCategory();
|
||||
$courseId = api_get_course_int_id();
|
||||
$sessionId = api_get_session_id();
|
||||
|
||||
// breadcrumbs
|
||||
$interbreadcrumb[] = [
|
||||
"url" => "exercise.php?".api_get_cidreq(),
|
||||
"name" => get_lang('Exercises'),
|
||||
];
|
||||
|
||||
$action = isset($_GET['action']) ? $_GET['action'] : '';
|
||||
$content = '';
|
||||
|
||||
switch ($action) {
|
||||
case 'addcategory':
|
||||
$content = add_category_form('addcategory');
|
||||
break;
|
||||
case 'editcategory':
|
||||
$content = edit_category_form('editcategory');
|
||||
break;
|
||||
case 'deletecategory':
|
||||
delete_category_form();
|
||||
break;
|
||||
case 'export_category':
|
||||
$archiveFile = 'export_exercise_categories_'.api_get_course_id().'_'.api_get_local_time();
|
||||
$categories = $category->getCategories($courseId, $sessionId);
|
||||
$export = [];
|
||||
$export[] = ['title', 'description'];
|
||||
|
||||
if (!empty($categories)) {
|
||||
foreach ($categories as $category) {
|
||||
$export[] = [$category['title'], $category['description']];
|
||||
}
|
||||
}
|
||||
|
||||
Export::arrayToCsv($export, $archiveFile);
|
||||
exit;
|
||||
break;
|
||||
case 'import_category':
|
||||
$form = importCategoryForm();
|
||||
if ($form->validate()) {
|
||||
$categories = Import::csv_reader($_FILES['file']['tmp_name']);
|
||||
if (!empty($categories)) {
|
||||
foreach ($categories as $item) {
|
||||
$cat = new TestCategory();
|
||||
$cat->name = $item['title'];
|
||||
$cat->description = $item['description'];
|
||||
$cat->save();
|
||||
}
|
||||
Display::addFlash(Display::return_message(get_lang('Imported')));
|
||||
}
|
||||
}
|
||||
$content = $form->returnForm();
|
||||
break;
|
||||
}
|
||||
|
||||
Display::display_header(get_lang('Category'));
|
||||
displayActionBar();
|
||||
echo $content;
|
||||
echo $category->displayCategories($courseId, $sessionId);
|
||||
Display::display_footer();
|
||||
|
||||
/**
|
||||
* @return FormValidator
|
||||
*/
|
||||
function importCategoryForm()
|
||||
{
|
||||
$form = new FormValidator('import', 'post', api_get_self().'?action=import_category&'.api_get_cidreq());
|
||||
$form->addElement('file', 'file', get_lang('ImportCSVFileLocation'));
|
||||
$form->addRule('file', get_lang('ThisFieldIsRequired'), 'required');
|
||||
$form->addButtonImport(get_lang('Import'));
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to edit a category.
|
||||
*
|
||||
* @todo move to TestCategory.class.php
|
||||
*
|
||||
* @param string $action
|
||||
*/
|
||||
function edit_category_form($action)
|
||||
{
|
||||
$action = Security::remove_XSS($action);
|
||||
if (isset($_GET['category_id']) && is_numeric($_GET['category_id'])) {
|
||||
$category_id = intval($_GET['category_id']);
|
||||
$objcat = new TestCategory();
|
||||
$objcat = $objcat->getCategory($category_id);
|
||||
$form = new FormValidator(
|
||||
'note',
|
||||
'post',
|
||||
api_get_self().'?action='.$action.'&category_id='.$category_id.'&'.api_get_cidreq()
|
||||
);
|
||||
|
||||
// Setting the form elements
|
||||
$form->addElement('header', get_lang('EditCategory'));
|
||||
$form->addElement('hidden', 'category_id');
|
||||
$form->addElement('text', 'category_name', get_lang('CategoryName'), ['size' => '95']);
|
||||
$form->addHtmlEditor(
|
||||
'category_description',
|
||||
get_lang('CategoryDescription'),
|
||||
false,
|
||||
false,
|
||||
['ToolbarSet' => 'TestQuestionDescription', 'Height' => '200']
|
||||
);
|
||||
$form->addButtonSave(get_lang('ModifyCategory'), 'SubmitNote');
|
||||
|
||||
// setting the defaults
|
||||
$defaults = [];
|
||||
$defaults['category_id'] = $objcat->iid;
|
||||
$defaults['category_name'] = $objcat->name;
|
||||
$defaults['category_description'] = $objcat->description;
|
||||
$form->setDefaults($defaults);
|
||||
|
||||
// setting the rules
|
||||
$form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required');
|
||||
|
||||
// The validation or display
|
||||
if ($form->validate()) {
|
||||
$check = Security::check_token('post');
|
||||
if ($check) {
|
||||
$values = $form->exportValues();
|
||||
$category = new TestCategory();
|
||||
$category = $category->getCategory($values['category_id']);
|
||||
|
||||
if ($category) {
|
||||
$category->name = $values['category_name'];
|
||||
$category->description = $values['category_description'];
|
||||
$category->modifyCategory();
|
||||
Display::addFlash(Display::return_message(get_lang('Updated')));
|
||||
} else {
|
||||
Display::addFlash(Display::return_message(get_lang('ModifyCategoryError'), 'error'));
|
||||
}
|
||||
}
|
||||
Security::clear_token();
|
||||
} else {
|
||||
$token = Security::get_token();
|
||||
$form->addElement('hidden', 'sec_token');
|
||||
$form->setConstants(['sec_token' => $token]);
|
||||
|
||||
return $form->returnForm();
|
||||
}
|
||||
} else {
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('CannotEditCategory'), 'error')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// process to delete a category
|
||||
function delete_category_form()
|
||||
{
|
||||
if (isset($_GET['category_id']) && is_numeric($_GET['category_id'])) {
|
||||
$category = new TestCategory();
|
||||
if ($category->removeCategory($_GET['category_id'])) {
|
||||
Display::addFlash(Display::return_message(get_lang('DeleteCategoryDone')));
|
||||
} else {
|
||||
Display::addFlash(Display::return_message(get_lang('CannotDeleteCategoryError'), 'error'));
|
||||
}
|
||||
} else {
|
||||
Display::addFlash(Display::return_message(get_lang('CannotDeleteCategoryError'), 'error'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* form to add a category.
|
||||
*
|
||||
* @todo move to TestCategory.class.php
|
||||
*
|
||||
* @param string $action
|
||||
*/
|
||||
function add_category_form($action)
|
||||
{
|
||||
$action = Security::remove_XSS($action);
|
||||
// initiate the object
|
||||
$form = new FormValidator('note', 'post', api_get_self().'?action='.$action.'&'.api_get_cidreq());
|
||||
// Setting the form elements
|
||||
$form->addElement('header', get_lang('AddACategory'));
|
||||
$form->addElement('text', 'category_name', get_lang('CategoryName'), ['size' => '95']);
|
||||
$form->addHtmlEditor(
|
||||
'category_description',
|
||||
get_lang('CategoryDescription'),
|
||||
false,
|
||||
false,
|
||||
['ToolbarSet' => 'TestQuestionDescription', 'Height' => '200']
|
||||
);
|
||||
$form->addButtonCreate(get_lang('AddTestCategory'), 'SubmitNote');
|
||||
// setting the rules
|
||||
$form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required');
|
||||
// The validation or display
|
||||
if ($form->validate()) {
|
||||
$check = Security::check_token('post');
|
||||
if ($check) {
|
||||
$values = $form->exportValues();
|
||||
$category = new TestCategory();
|
||||
$category->name = $values['category_name'];
|
||||
$category->description = $values['category_description'];
|
||||
if ($category->save()) {
|
||||
Display::addFlash(Display::return_message(get_lang('AddCategoryDone')));
|
||||
} else {
|
||||
Display::addFlash(Display::return_message(get_lang('AddCategoryNameAlreadyExists'), 'warning'));
|
||||
}
|
||||
}
|
||||
Security::clear_token();
|
||||
} else {
|
||||
$token = Security::get_token();
|
||||
$form->addElement('hidden', 'sec_token');
|
||||
$form->setConstants(['sec_token' => $token]);
|
||||
|
||||
return $form->returnForm();
|
||||
}
|
||||
}
|
||||
|
||||
// Display add category button
|
||||
function displayActionBar()
|
||||
{
|
||||
echo '<div class="actions">';
|
||||
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq().'">'.
|
||||
Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
|
||||
|
||||
echo '<a href="'.api_get_self().'?action=addcategory&'.api_get_cidreq().'">'.
|
||||
Display::return_icon('new_folder.png', get_lang('AddACategory'), null, ICON_SIZE_MEDIUM).'</a>';
|
||||
|
||||
echo Display::url(
|
||||
Display::return_icon('export_csv.png', get_lang('ExportAsCSV'), [], ICON_SIZE_MEDIUM),
|
||||
api_get_self().'?action=export_category&'.api_get_cidreq()
|
||||
);
|
||||
|
||||
echo Display::url(
|
||||
Display::return_icon('import_csv.png', get_lang('ImportAsCSV'), [], ICON_SIZE_MEDIUM),
|
||||
api_get_self().'?action=import_category&'.api_get_cidreq()
|
||||
);
|
||||
|
||||
echo '</div>';
|
||||
echo "<br/>";
|
||||
echo "<fieldset><legend>".get_lang('QuestionCategory')."</legend></fieldset>";
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user