admin.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use ChamiloSession as Session;
  4. /**
  5. * Exercise administration
  6. * This script allows to manage (create, modify) an exercise and its questions.
  7. *
  8. * Following scripts are includes for a best code understanding :
  9. *
  10. * - exercise.class.php : for the creation of an Exercise object
  11. * - question.class.php : for the creation of a Question object
  12. * - answer.class.php : for the creation of an Answer object
  13. * - exercise.lib.php : functions used in the exercise tool
  14. * - exercise_admin.inc.php : management of the exercise
  15. * - question_admin.inc.php : management of a question (statement & answers)
  16. * - statement_admin.inc.php : management of a statement
  17. * - question_list_admin.inc.php : management of the question list
  18. *
  19. * Main variables used in this script :
  20. *
  21. * - $is_allowedToEdit : set to 1 if the user is allowed to manage the exercise
  22. * - $objExercise : exercise object
  23. * - $objQuestion : question object
  24. * - $objAnswer : answer object
  25. * - $exerciseId : the exercise ID
  26. * - $picturePath : the path of question pictures
  27. * - $newQuestion : ask to create a new question
  28. * - $modifyQuestion : ID of the question to modify
  29. * - $editQuestion : ID of the question to edit
  30. * - $submitQuestion : ask to save question modifications
  31. * - $cancelQuestion : ask to cancel question modifications
  32. * - $deleteQuestion : ID of the question to delete
  33. * - $moveUp : ID of the question to move up
  34. * - $moveDown : ID of the question to move down
  35. * - $modifyExercise : ID of the exercise to modify
  36. * - $submitExercise : ask to save exercise modifications
  37. * - $cancelExercise : ask to cancel exercise modifications
  38. * - $modifyAnswers : ID of the question which we want to modify answers for
  39. * - $cancelAnswers : ask to cancel answer modifications
  40. * - $buttonBack : ask to go back to the previous page in answers of type "Fill in blanks"
  41. *
  42. * @package chamilo.exercise
  43. *
  44. * @author Olivier Brouckaert
  45. * Modified by Hubert Borderiou 21-10-2011 Question by category
  46. */
  47. require_once __DIR__.'/../inc/global.inc.php';
  48. $current_course_tool = TOOL_QUIZ;
  49. $this_section = SECTION_COURSES;
  50. // Access control
  51. api_protect_course_script(true);
  52. $is_allowedToEdit = api_is_allowed_to_edit(null, true, false, false);
  53. $sessionId = api_get_session_id();
  54. $studentViewActive = api_is_student_view_active();
  55. $showPagination = api_get_configuration_value('show_question_pagination');
  56. if (!$is_allowedToEdit) {
  57. api_not_allowed(true);
  58. }
  59. $exerciseId = isset($_GET['exerciseId']) ? (int) $_GET['exerciseId'] : 0;
  60. /* stripslashes POST data */
  61. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  62. foreach ($_POST as $key => $val) {
  63. if (is_string($val)) {
  64. $_POST[$key] = stripslashes($val);
  65. } elseif (is_array($val)) {
  66. foreach ($val as $key2 => $val2) {
  67. $_POST[$key][$key2] = stripslashes($val2);
  68. }
  69. }
  70. $GLOBALS[$key] = $_POST[$key];
  71. }
  72. }
  73. $newQuestion = isset($_GET['newQuestion']) ? $_GET['newQuestion'] : 0;
  74. $modifyAnswers = isset($_GET['modifyAnswers']) ? $_GET['modifyAnswers'] : 0;
  75. $editQuestion = isset($_GET['editQuestion']) ? $_GET['editQuestion'] : 0;
  76. $page = isset($_GET['page']) && !empty($_GET['page']) ? (int) $_GET['page'] : 1;
  77. $modifyQuestion = isset($_GET['modifyQuestion']) ? $_GET['modifyQuestion'] : 0;
  78. $deleteQuestion = isset($_GET['deleteQuestion']) ? $_GET['deleteQuestion'] : 0;
  79. $clone_question = isset($_REQUEST['clone_question']) ? $_REQUEST['clone_question'] : 0;
  80. if (empty($questionId)) {
  81. $questionId = Session::read('questionId');
  82. }
  83. if (empty($modifyExercise)) {
  84. $modifyExercise = isset($_GET['modifyExercise']) ? $_GET['modifyExercise'] : null;
  85. }
  86. $fromExercise = isset($fromExercise) ? $fromExercise : null;
  87. $cancelExercise = isset($cancelExercise) ? $cancelExercise : null;
  88. $cancelAnswers = isset($cancelAnswers) ? $cancelAnswers : null;
  89. $modifyIn = isset($modifyIn) ? $modifyIn : null;
  90. $cancelQuestion = isset($cancelQuestion) ? $cancelQuestion : null;
  91. /* Cleaning all incomplete attempts of the admin/teacher to avoid weird problems
  92. when changing the exercise settings, number of questions, etc */
  93. Event::delete_all_incomplete_attempts(
  94. api_get_user_id(),
  95. $exerciseId,
  96. api_get_course_int_id(),
  97. api_get_session_id()
  98. );
  99. // get from session
  100. $objExercise = Session::read('objExercise');
  101. $objQuestion = Session::read('objQuestion');
  102. if (isset($_REQUEST['convertAnswer'])) {
  103. $objQuestion = $objQuestion->swapSimpleAnswerTypes();
  104. Session::write('objQuestion', $objQuestion);
  105. }
  106. $objAnswer = Session::read('objAnswer');
  107. $_course = api_get_course_info();
  108. // document path
  109. $documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
  110. // picture path
  111. $picturePath = $documentPath.'/images';
  112. // audio path
  113. $audioPath = $documentPath.'/audio';
  114. // tables used in the exercise tool
  115. if (!empty($_GET['action']) && $_GET['action'] == 'exportqti2' && !empty($_GET['questionId'])) {
  116. require_once 'export/qti2/qti2_export.php';
  117. $export = export_question_qti($_GET['questionId'], true);
  118. $qid = (int) $_GET['questionId'];
  119. $zip = api_create_zip($name);
  120. $zip->addFile("qti2export_$qid.xml", $export);
  121. $zip->finish();
  122. exit;
  123. }
  124. // Exercise object creation.
  125. if (!is_object($objExercise)) {
  126. // construction of the Exercise object
  127. $objExercise = new Exercise();
  128. // creation of a new exercise if wrong or not specified exercise ID
  129. if ($exerciseId) {
  130. $parseQuestionList = $showPagination > 0 ? false : true;
  131. if ($editQuestion) {
  132. $parseQuestionList = false;
  133. $showPagination = true;
  134. }
  135. $objExercise->read($exerciseId, $parseQuestionList);
  136. }
  137. // saves the object into the session
  138. Session::write('objExercise', $objExercise);
  139. }
  140. // Exercise can be edited in their course.
  141. if ($objExercise->sessionId != $sessionId) {
  142. api_not_allowed(true);
  143. }
  144. // doesn't select the exercise ID if we come from the question pool
  145. if (!$fromExercise) {
  146. // gets the right exercise ID, and if 0 creates a new exercise
  147. if (!$exerciseId = $objExercise->selectId()) {
  148. $modifyExercise = 'yes';
  149. }
  150. }
  151. $nbrQuestions = $objExercise->getQuestionCount();
  152. // Question object creation.
  153. if ($editQuestion || $newQuestion || $modifyQuestion || $modifyAnswers) {
  154. if ($editQuestion || $newQuestion) {
  155. // reads question data
  156. if ($editQuestion) {
  157. // question not found
  158. if (!$objQuestion = Question::read($editQuestion)) {
  159. api_not_allowed(true);
  160. }
  161. // saves the object into the session
  162. Session::write('objQuestion', $objQuestion);
  163. }
  164. }
  165. // checks if the object exists
  166. if (is_object($objQuestion)) {
  167. // gets the question ID
  168. $questionId = $objQuestion->selectId();
  169. }
  170. }
  171. // if cancelling an exercise
  172. if ($cancelExercise) {
  173. // existing exercise
  174. if ($exerciseId) {
  175. unset($modifyExercise);
  176. } else {
  177. // new exercise
  178. // goes back to the exercise list
  179. header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
  180. exit();
  181. }
  182. }
  183. // if cancelling question creation/modification
  184. if ($cancelQuestion) {
  185. // if we are creating a new question from the question pool
  186. if (!$exerciseId && !$questionId) {
  187. // goes back to the question pool
  188. header('Location: question_pool.php?'.api_get_cidreq());
  189. exit();
  190. } else {
  191. // goes back to the question viewing
  192. $editQuestion = $modifyQuestion;
  193. unset($newQuestion, $modifyQuestion);
  194. }
  195. }
  196. if (!empty($clone_question) && !empty($objExercise->id)) {
  197. $old_question_obj = Question::read($clone_question);
  198. $old_question_obj->question = $old_question_obj->question.' - '.get_lang('Copy');
  199. $new_id = $old_question_obj->duplicate(api_get_course_info());
  200. $new_question_obj = Question::read($new_id);
  201. $new_question_obj->addToList($exerciseId);
  202. // Save category to the destination course
  203. if (!empty($old_question_obj->category)) {
  204. $new_question_obj->saveCategory($old_question_obj->category, api_get_course_int_id());
  205. }
  206. // This should be moved to the duplicate function
  207. $new_answer_obj = new Answer($clone_question);
  208. $new_answer_obj->read();
  209. $new_answer_obj->duplicate($new_question_obj);
  210. // Reloading tne $objExercise obj
  211. $objExercise->read($objExercise->id, false);
  212. Display::addFlash(Display::return_message(get_lang('Item copied')));
  213. header('Location: admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id.'&page='.$page);
  214. exit;
  215. }
  216. // if cancelling answer creation/modification
  217. if ($cancelAnswers) {
  218. // goes back to the question viewing
  219. $editQuestion = $modifyAnswers;
  220. unset($modifyAnswers);
  221. }
  222. $nameTools = '';
  223. // modifies the query string that is used in the link of tool name
  224. if ($editQuestion || $modifyQuestion || $newQuestion || $modifyAnswers) {
  225. $nameTools = get_lang('Question / Answer management');
  226. }
  227. if (api_is_in_gradebook()) {
  228. $interbreadcrumb[] = [
  229. 'url' => Category::getUrl(),
  230. 'name' => get_lang('Assessments'),
  231. ];
  232. }
  233. $interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Tests')];
  234. if (isset($_GET['newQuestion']) || isset($_GET['editQuestion'])) {
  235. $interbreadcrumb[] = [
  236. 'url' => 'admin.php?exerciseId='.$objExercise->id.'&'.api_get_cidreq(),
  237. 'name' => $objExercise->selectTitle(true),
  238. ];
  239. } else {
  240. $interbreadcrumb[] = [
  241. 'url' => '#',
  242. 'name' => $objExercise->selectTitle(true),
  243. ];
  244. }
  245. // shows a link to go back to the question pool
  246. if (!$exerciseId && $nameTools != get_lang('Tests management')) {
  247. $interbreadcrumb[] = [
  248. 'url' => api_get_path(WEB_CODE_PATH)."exercise/question_pool.php?fromExercise=$fromExercise&".api_get_cidreq(),
  249. 'name' => get_lang('Recycle existing questions'),
  250. ];
  251. }
  252. // if the question is duplicated, disable the link of tool name
  253. if ($modifyIn === 'thisExercise') {
  254. if ($buttonBack) {
  255. $modifyIn = 'allExercises';
  256. }
  257. }
  258. //$htmlHeadXtra[] = api_get_js('jqueryui-touch-punch/jquery.ui.touch-punch.min.js');
  259. $htmlHeadXtra[] = api_get_js('jquery.jsPlumb.all.js');
  260. $template = new Template();
  261. $templateName = $template->get_template('exercise/submit.js.tpl');
  262. $htmlHeadXtra[] = $template->fetch($templateName);
  263. //$htmlHeadXtra[] = api_get_js('d3/jquery.xcolor.js');
  264. $htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
  265. $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
  266. if (isset($_GET['message'])) {
  267. if (in_array($_GET['message'], ['ExerciseStored', 'ItemUpdated', 'ItemAdded'])) {
  268. Display::addFlash(Display::return_message(get_lang($_GET['message']), 'confirmation'));
  269. }
  270. }
  271. Display::display_header($nameTools, 'Exercise');
  272. // If we are in a test
  273. $inATest = isset($exerciseId) && $exerciseId > 0;
  274. if ($inATest) {
  275. echo '<div class="actions">';
  276. if (isset($_GET['hotspotadmin']) || isset($_GET['newQuestion'])) {
  277. echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/admin.php?exerciseId='.$exerciseId.'&'.api_get_cidreq().'">'.
  278. Display::return_icon('back.png', get_lang('Go back to the questions list'), '', ICON_SIZE_MEDIUM).'</a>';
  279. }
  280. if (!isset($_GET['hotspotadmin']) && !isset($_GET['newQuestion']) && !isset($_GET['editQuestion'])) {
  281. echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq().'">'.
  282. Display::return_icon('back.png', get_lang('BackToTestsList'), '', ICON_SIZE_MEDIUM).'</a>';
  283. }
  284. echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id.'&preview=1">'.
  285. Display::return_icon('preview_view.png', get_lang('Preview'), '', ICON_SIZE_MEDIUM).'</a>';
  286. echo Display::url(
  287. Display::return_icon('test_results.png', get_lang('Results and feedback'), '', ICON_SIZE_MEDIUM),
  288. api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id
  289. );
  290. echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->id.'">'.
  291. Display::return_icon('settings.png', get_lang('Edit test name and settings'), '', ICON_SIZE_MEDIUM).'</a>';
  292. $maxScoreAllQuestions = 0;
  293. if ($showPagination === false) {
  294. $questionList = $objExercise->selectQuestionList(true, true);
  295. if (!empty($questionList)) {
  296. foreach ($questionList as $questionItemId) {
  297. $question = Question::read($questionItemId);
  298. if ($question) {
  299. $maxScoreAllQuestions += $question->selectWeighting();
  300. }
  301. }
  302. }
  303. }
  304. echo '</div>';
  305. if ($objExercise->added_in_lp()) {
  306. 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');
  307. }
  308. if ($editQuestion && $objQuestion->existsInAnotherExercise()) {
  309. echo Display::return_message(
  310. Display::returnFontAwesomeIcon('exclamation-triangle"')
  311. .get_lang('ThisQuestionExistsInAnotherTestsWarning'),
  312. 'warning',
  313. false
  314. );
  315. }
  316. $alert = '';
  317. if ($showPagination === false) {
  318. $alert .= sprintf(
  319. get_lang('%d questions, for a total score (all questions) of %s.'),
  320. $nbrQuestions,
  321. $maxScoreAllQuestions
  322. );
  323. }
  324. if ($objExercise->random > 0) {
  325. $alert .= '<br />'.sprintf(get_lang('Only %s questions will be picked randomly following the quiz configuration.'), $objExercise->random);
  326. }
  327. echo Display::return_message($alert, 'normal', false);
  328. } elseif (isset($_GET['newQuestion'])) {
  329. // we are in create a new question from question pool not in a test
  330. echo '<div class="actions">';
  331. echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'">'.
  332. Display::return_icon('back.png', get_lang('Go back to the questions list'), '', ICON_SIZE_MEDIUM).'</a>';
  333. echo '</div>';
  334. } else {
  335. // If we are in question_pool but not in an test, go back to question create in pool
  336. echo '<div class="actions">';
  337. echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/question_pool.php?'.api_get_cidreq().'">'.
  338. Display::return_icon('back.png', get_lang('Go back to the questions list'), '', ICON_SIZE_MEDIUM).
  339. '</a>';
  340. echo '</div>';
  341. }
  342. if ($newQuestion || $editQuestion) {
  343. // Question management
  344. $type = isset($_REQUEST['answerType']) ? Security::remove_XSS($_REQUEST['answerType']) : null;
  345. echo '<input type="hidden" name="Type" value="'.$type.'" />';
  346. if ($newQuestion === 'yes') {
  347. $objExercise->edit_exercise_in_lp = true;
  348. require 'question_admin.inc.php';
  349. }
  350. if ($editQuestion) {
  351. // Question preview if teacher clicked the "switch to student"
  352. if ($studentViewActive && $is_allowedToEdit) {
  353. echo '<div class="main-question">';
  354. echo Display::div($objQuestion->selectTitle(), ['class' => 'question_title']);
  355. ExerciseLib::showQuestion(
  356. $objExercise,
  357. $editQuestion,
  358. false,
  359. null,
  360. null,
  361. false,
  362. true,
  363. false,
  364. true,
  365. true
  366. );
  367. echo '</div>';
  368. } else {
  369. require 'question_admin.inc.php';
  370. }
  371. }
  372. }
  373. if (isset($_GET['hotspotadmin'])) {
  374. if (!is_object($objQuestion)) {
  375. $objQuestion = Question::read($_GET['hotspotadmin']);
  376. }
  377. if (!$objQuestion) {
  378. api_not_allowed();
  379. }
  380. require 'hotspot_admin.inc.php';
  381. }
  382. if (!$newQuestion && !$modifyQuestion && !$editQuestion && !isset($_GET['hotspotadmin'])) {
  383. // question list management
  384. require 'question_list_admin.inc.php';
  385. }
  386. // if we are in question authoring, display warning to user is feedback not shown at the end of the test -ref #6619
  387. // this test to display only message in the question authoring page and not in the question list page too
  388. if ($objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_EXAM) {
  389. 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');
  390. }
  391. Session::write('objExercise', $objExercise);
  392. Session::write('objQuestion', $objQuestion);
  393. Session::write('objAnswer', $objAnswer);
  394. Display::display_footer();