You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
467 lines
18 KiB
467 lines
18 KiB
<?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.
|
|
*
|
|
* Following scripts are includes for a best code understanding :
|
|
*
|
|
* - 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);
|
|
}
|
|
|
|
$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'])) {
|
|
$objQuestion = $objQuestion->swapSimpleAnswerTypes();
|
|
Session::write('objQuestion', $objQuestion);
|
|
}
|
|
$objAnswer = Session::read('objAnswer');
|
|
$_course = api_get_course_info();
|
|
|
|
// tables used in the exercise tool
|
|
if (!empty($_GET['action']) && 'exportqti2' == $_GET['action'] && !empty($_GET['questionId'])) {
|
|
require_once 'export/qti2/qti2_export.php';
|
|
$export = export_question_qti($_GET['questionId'], true);
|
|
$qid = (int) $_GET['questionId'];
|
|
|
|
$zip = api_create_zip($name);
|
|
$zip->addFile("qti2export_$qid.xml", $export);
|
|
$zip->finish();
|
|
exit;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
// Exercise can be edited in their course.
|
|
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);
|
|
}
|
|
|
|
// doesn't 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->getId()) {
|
|
$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->getId();
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
if (!empty($clone_question) && !empty($objExercise->getId())) {
|
|
$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->getId(), false);
|
|
|
|
Display::addFlash(Display::return_message(get_lang('Item copied')));
|
|
|
|
header('Location: admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->getId().'&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('Question / Answer management');
|
|
}
|
|
|
|
if (api_is_in_gradebook()) {
|
|
$interbreadcrumb[] = [
|
|
'url' => Category::getUrl(),
|
|
'name' => get_lang('Assessments'),
|
|
];
|
|
}
|
|
|
|
$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Tests')];
|
|
if (isset($_GET['newQuestion']) || isset($_GET['editQuestion'])) {
|
|
$interbreadcrumb[] = [
|
|
'url' => 'admin.php?exerciseId='.$objExercise->getId().'&'.api_get_cidreq(),
|
|
'name' => $objExercise->selectTitle(true),
|
|
];
|
|
} else {
|
|
$interbreadcrumb[] = [
|
|
'url' => '#',
|
|
'name' => $objExercise->selectTitle(true),
|
|
];
|
|
}
|
|
|
|
// shows a link to go back to the question pool
|
|
if (!$exerciseId && $nameTools != get_lang('Tests management')) {
|
|
$interbreadcrumb[] = [
|
|
'url' => api_get_path(WEB_CODE_PATH)."exercise/question_pool.php?fromExercise=$fromExercise&".api_get_cidreq(),
|
|
'name' => get_lang('Recycle existing questions'),
|
|
];
|
|
}
|
|
|
|
// if the question is duplicated, disable the link of tool name
|
|
if ('thisExercise' === $modifyIn) {
|
|
if ($buttonBack) {
|
|
$modifyIn = 'allExercises';
|
|
}
|
|
}
|
|
|
|
//$htmlHeadXtra[] = api_get_js('jqueryui-touch-punch/jquery.ui.touch-punch.min.js');
|
|
//$htmlHeadXtra[] = api_get_js('jquery.jsPlumb.all.js');
|
|
$htmlHeadXtra[] = api_get_build_js('exercise.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['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('Go back to the questions list'), '', ICON_SIZE_MEDIUM).'</a>';
|
|
}
|
|
|
|
if (!isset($_GET['hotspotadmin']) && !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('BackToTestsList'), '', ICON_SIZE_MEDIUM).'</a>';
|
|
}
|
|
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->getId().'&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 and feedback'), '', ICON_SIZE_MEDIUM),
|
|
api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'.api_get_cidreq().'&exerciseId='.$objExercise->getId()
|
|
);
|
|
|
|
echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->getId().'">'.
|
|
Display::return_icon('settings.png', get_lang('Edit test name and settings'), '', ICON_SIZE_MEDIUM).'</a>';
|
|
|
|
$maxScoreAllQuestions = 0;
|
|
if (false === $showPagination) {
|
|
$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(
|
|
'This exercise has been included in a learning path, so it cannot be accessed by students directly from here. If you want to put the same exercise available through the exercises tool, please make a copy of the current exercise using the copy icon.'
|
|
),
|
|
'warning'
|
|
);
|
|
}
|
|
if ($editQuestion && $objQuestion->existsInAnotherExercise()) {
|
|
echo Display::return_message(
|
|
Display::returnFontAwesomeIcon('exclamation-triangle"')
|
|
.get_lang('ThisQuestionExistsInAnotherTestsWarning'),
|
|
'warning',
|
|
false
|
|
);
|
|
}
|
|
|
|
$alert = '';
|
|
if ($showPagination === false) {
|
|
$originalSelectionType = $objExercise->questionSelectionType;
|
|
$objExercise->questionSelectionType = EX_Q_SELECTION_ORDERED;
|
|
|
|
$fullQuestionsScore = 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('%d questions, for a total score (all questions) of %s.'),
|
|
$nbrQuestions,
|
|
$fullQuestionsScore
|
|
);
|
|
}
|
|
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('Go back to the questions list'), '', 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('Go back to the questions list'), '', 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 ('yes' === $newQuestion) {
|
|
$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($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->getId());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($_GET['hotspotadmin'])) {
|
|
if (!is_object($objQuestion)) {
|
|
$objQuestion = Question::read($_GET['hotspotadmin']);
|
|
}
|
|
if (!$objQuestion) {
|
|
api_not_allowed();
|
|
}
|
|
require 'hotspot_admin.inc.php';
|
|
}
|
|
|
|
if (!$newQuestion && !$modifyQuestion && !$editQuestion && !isset($_GET['hotspotadmin'])) {
|
|
// 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 (EXERCISE_FEEDBACK_TYPE_EXAM == $objExercise->getFeedbackType()) {
|
|
echo Display::return_message(get_lang('This test is configured not to display feedback to learners. Comments will not be seen at the end of the test, but may be useful for you, as teacher, when reviewing the question details.'), 'normal');
|
|
}
|
|
|
|
Session::write('objQuestion', $objQuestion);
|
|
Session::write('objAnswer', $objAnswer);
|
|
Display::display_footer();
|
|
|