upload_exercise.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use ChamiloSession as Session;
  4. /**
  5. * Upload quiz: This script shows the upload quiz feature.
  6. *
  7. * @package chamilo.exercise
  8. */
  9. // setting the help
  10. $help_content = 'exercise_upload';
  11. require_once __DIR__.'/../inc/global.inc.php';
  12. api_protect_course_script(true);
  13. $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
  14. $debug = false;
  15. $origin = api_get_origin();
  16. if (!$is_allowed_to_edit) {
  17. api_not_allowed(true);
  18. }
  19. $this_section = SECTION_COURSES;
  20. $htmlHeadXtra[] = "<script>
  21. $(function(){
  22. $('#user_custom_score').click(function() {
  23. $('#options').toggle();
  24. });
  25. });
  26. </script>";
  27. // Action handling
  28. lp_upload_quiz_action_handling();
  29. $interbreadcrumb[] = [
  30. 'url' => 'exercise.php?'.api_get_cidreq(),
  31. 'name' => get_lang('Exercises'),
  32. ];
  33. // Display the header
  34. Display :: display_header(get_lang('ImportExcelQuiz'), 'Exercises');
  35. // display the actions
  36. echo '<div class="actions">';
  37. echo lp_upload_quiz_actions();
  38. echo '</div>';
  39. // the main content
  40. lp_upload_quiz_main();
  41. function lp_upload_quiz_actions()
  42. {
  43. $return = '<a href="exercise.php?'.api_get_cidreq().'">'.
  44. Display::return_icon(
  45. 'back.png',
  46. get_lang('BackToExercisesList'),
  47. '',
  48. ICON_SIZE_MEDIUM
  49. ).'</a>';
  50. return $return;
  51. }
  52. function lp_upload_quiz_main()
  53. {
  54. $lp_id = isset($_GET['lp_id']) ? (int) $_GET['lp_id'] : null;
  55. $form = new FormValidator(
  56. 'upload',
  57. 'POST',
  58. api_get_self().'?'.api_get_cidreq().'&lp_id='.$lp_id,
  59. '',
  60. ['enctype' => 'multipart/form-data']
  61. );
  62. $form->addElement('header', get_lang('ImportExcelQuiz'));
  63. $form->addElement('file', 'user_upload_quiz', get_lang('FileUpload'));
  64. $link = '<a href="../exercise/quiz_template.xls">'.
  65. Display::return_icon('export_excel.png', get_lang('DownloadExcelTemplate')).get_lang('DownloadExcelTemplate').'</a>';
  66. $form->addElement('label', '', $link);
  67. $table = new HTML_Table(['class' => 'table']);
  68. $tableList = [
  69. UNIQUE_ANSWER => get_lang('UniqueSelect'),
  70. MULTIPLE_ANSWER => get_lang('MultipleSelect'),
  71. FILL_IN_BLANKS => get_lang('FillBlanks'),
  72. MATCHING => get_lang('Matching'),
  73. FREE_ANSWER => get_lang('FreeAnswer'),
  74. GLOBAL_MULTIPLE_ANSWER => get_lang('GlobalMultipleAnswer'),
  75. ];
  76. $table->setHeaderContents(0, 0, get_lang('QuestionType'));
  77. $table->setHeaderContents(0, 1, '#');
  78. $row = 1;
  79. foreach ($tableList as $key => $label) {
  80. $table->setCellContents($row, 0, $label);
  81. $table->setCellContents($row, 1, $key);
  82. $row++;
  83. }
  84. $table = $table->toHtml();
  85. $form->addElement('label', get_lang('QuestionType'), $table);
  86. $form->addElement(
  87. 'checkbox',
  88. 'user_custom_score',
  89. null,
  90. get_lang('UseCustomScoreForAllQuestions'),
  91. ['id' => 'user_custom_score']
  92. );
  93. $form->addElement('html', '<div id="options" style="display:none">');
  94. $form->addElement('text', 'correct_score', get_lang('CorrectScore'));
  95. $form->addElement('text', 'incorrect_score', get_lang('IncorrectScore'));
  96. $form->addElement('html', '</div>');
  97. $form->addRule('user_upload_quiz', get_lang('ThisFieldIsRequired'), 'required');
  98. $form->addProgress();
  99. $form->addButtonUpload(get_lang('Upload'), 'submit_upload_quiz');
  100. $form->display();
  101. }
  102. /**
  103. * Handles a given Excel spreadsheets as in the template provided.
  104. */
  105. function lp_upload_quiz_action_handling()
  106. {
  107. if (!isset($_POST['submit_upload_quiz'])) {
  108. return;
  109. }
  110. $_course = api_get_course_info();
  111. if (empty($_course)) {
  112. return false;
  113. }
  114. $courseId = $_course['real_id'];
  115. // Get the extension of the document.
  116. $path_info = pathinfo($_FILES['user_upload_quiz']['name']);
  117. // Check if the document is an Excel document
  118. if (!in_array($path_info['extension'], ['xls', 'xlsx'])) {
  119. return;
  120. }
  121. // Variables
  122. $numberQuestions = 0;
  123. $question = [];
  124. $scoreList = [];
  125. $feedbackTrueList = [];
  126. $feedbackFalseList = [];
  127. $questionDescriptionList = [];
  128. $noNegativeScoreList = [];
  129. $questionTypeList = [];
  130. $answerList = [];
  131. $quizTitle = '';
  132. $objPHPExcel = PHPExcel_IOFactory::load($_FILES['user_upload_quiz']['tmp_name']);
  133. $objPHPExcel->setActiveSheetIndex(0);
  134. $worksheet = $objPHPExcel->getActiveSheet();
  135. $highestRow = $worksheet->getHighestRow(); // e.g. 10
  136. $highestColumn = $worksheet->getHighestColumn(); // e.g 'F'
  137. $correctScore = isset($_POST['correct_score']) ? $_POST['correct_score'] : null;
  138. $incorrectScore = isset($_POST['incorrect_score']) ? $_POST['incorrect_score'] : null;
  139. $useCustomScore = isset($_POST['user_custom_score']) ? true : false;
  140. for ($row = 1; $row <= $highestRow; $row++) {
  141. $cellTitleInfo = $worksheet->getCellByColumnAndRow(0, $row);
  142. $cellDataInfo = $worksheet->getCellByColumnAndRow(1, $row);
  143. $cellScoreInfo = $worksheet->getCellByColumnAndRow(2, $row);
  144. $title = $cellTitleInfo->getValue();
  145. switch ($title) {
  146. case 'Quiz':
  147. $quizTitle = $cellDataInfo->getValue();
  148. break;
  149. case 'Question':
  150. $question[] = $cellDataInfo->getValue();
  151. // Search cell with Answer title
  152. $answerRow = $row;
  153. $continue = true;
  154. $answerIndex = 0;
  155. while ($continue) {
  156. $answerRow++;
  157. $answerInfoTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
  158. $answerInfoData = $worksheet->getCellByColumnAndRow(1, $answerRow);
  159. $answerInfoExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
  160. $answerInfoTitle = $answerInfoTitle->getValue();
  161. if (strpos($answerInfoTitle, 'Answer') !== false) {
  162. $answerList[$numberQuestions][$answerIndex]['data'] = $answerInfoData->getValue();
  163. $answerList[$numberQuestions][$answerIndex]['extra'] = $answerInfoExtra->getValue();
  164. } else {
  165. $continue = false;
  166. }
  167. $answerIndex++;
  168. // To avoid loops
  169. if ($answerIndex > 60) {
  170. $continue = false;
  171. }
  172. }
  173. // Search cell with question type
  174. $answerRow = $row;
  175. $continue = true;
  176. $questionTypeIndex = 0;
  177. while ($continue) {
  178. $answerRow++;
  179. $questionTypeTitle = $worksheet->getCellByColumnAndRow(0, $answerRow);
  180. $questionTypeExtra = $worksheet->getCellByColumnAndRow(2, $answerRow);
  181. $title = $questionTypeTitle->getValue();
  182. if ($title == 'QuestionType') {
  183. $questionTypeList[$numberQuestions] = $questionTypeExtra->getValue();
  184. $continue = false;
  185. }
  186. if ($title == 'Question') {
  187. $continue = false;
  188. }
  189. // To avoid loops
  190. if ($questionTypeIndex > 60) {
  191. $continue = false;
  192. }
  193. $questionTypeIndex++;
  194. }
  195. // Detect answers
  196. $numberQuestions++;
  197. break;
  198. case 'Score':
  199. $scoreList[] = $cellScoreInfo->getValue();
  200. break;
  201. case 'NoNegativeScore':
  202. $noNegativeScoreList[] = $cellScoreInfo->getValue();
  203. break;
  204. case 'Category':
  205. $categoryList[] = $cellDataInfo->getValue();
  206. break;
  207. case 'FeedbackTrue':
  208. $feedbackTrueList[] = $cellDataInfo->getValue();
  209. break;
  210. case 'FeedbackFalse':
  211. $feedbackFalseList[] = $cellDataInfo->getValue();
  212. break;
  213. case 'EnrichQuestion':
  214. $questionDescriptionList[] = $cellDataInfo->getValue();
  215. break;
  216. }
  217. }
  218. $propagateNegative = 0;
  219. if ($useCustomScore && !empty($incorrectScore)) {
  220. if ($incorrectScore < 0) {
  221. $propagateNegative = 1;
  222. }
  223. }
  224. if ($quizTitle != '') {
  225. // Variables
  226. $type = 2;
  227. $random = $active = $results = $max_attempt = $expired_time = 0;
  228. // Make sure feedback is enabled (3 to disable), otherwise the fields
  229. // added to the XLS are not shown, which is confusing
  230. $feedback = 0;
  231. // Quiz object
  232. $exercise = new Exercise();
  233. $exercise->updateTitle($quizTitle);
  234. $exercise->updateExpiredTime($expired_time);
  235. $exercise->updateType($type);
  236. $exercise->setRandom($random);
  237. $exercise->active = $active;
  238. $exercise->updateResultsDisabled($results);
  239. $exercise->updateAttempts($max_attempt);
  240. $exercise->updateFeedbackType($feedback);
  241. $exercise->updatePropagateNegative($propagateNegative);
  242. $quiz_id = $exercise->save();
  243. if ($quiz_id) {
  244. // Import questions.
  245. for ($i = 0; $i < $numberQuestions; $i++) {
  246. // Question name
  247. $questionTitle = $question[$i];
  248. $myAnswerList = isset($answerList[$i]) ? $answerList[$i] : [];
  249. $description = isset($questionDescriptionList[$i]) ? $questionDescriptionList[$i] : '';
  250. $categoryId = null;
  251. if (isset($categoryList[$i]) && !empty($categoryList[$i])) {
  252. $categoryName = $categoryList[$i];
  253. $categoryId = TestCategory::get_category_id_for_title($categoryName, $courseId);
  254. if (empty($categoryId)) {
  255. $category = new TestCategory();
  256. $category->name = $categoryName;
  257. $categoryId = $category->save();
  258. }
  259. }
  260. $question_description_text = '<p></p>';
  261. if (!empty($description)) {
  262. // Question description.
  263. $question_description_text = "<p>$description</p>";
  264. }
  265. // Unique answers are the only question types available for now
  266. // through xls-format import
  267. $question_id = null;
  268. if (isset($questionTypeList[$i]) && $questionTypeList[$i] != '') {
  269. $detectQuestionType = (int) $questionTypeList[$i];
  270. } else {
  271. $detectQuestionType = detectQuestionType($myAnswerList);
  272. }
  273. /** @var Question $answer */
  274. switch ($detectQuestionType) {
  275. case FREE_ANSWER:
  276. $answer = new FreeAnswer();
  277. break;
  278. case GLOBAL_MULTIPLE_ANSWER:
  279. $answer = new GlobalMultipleAnswer();
  280. break;
  281. case MULTIPLE_ANSWER:
  282. $answer = new MultipleAnswer();
  283. break;
  284. case FILL_IN_BLANKS:
  285. $answer = new FillBlanks();
  286. $question_description_text = '';
  287. break;
  288. case MATCHING:
  289. $answer = new Matching();
  290. break;
  291. case UNIQUE_ANSWER:
  292. default:
  293. $answer = new UniqueAnswer();
  294. break;
  295. }
  296. if ($questionTitle != '') {
  297. $question_id = $answer->create_question(
  298. $quiz_id,
  299. $questionTitle,
  300. $question_description_text,
  301. 0, // max score
  302. $answer->type
  303. );
  304. if (!empty($categoryId)) {
  305. TestCategory::addCategoryToQuestion(
  306. $categoryId,
  307. $question_id,
  308. $courseId
  309. );
  310. }
  311. }
  312. switch ($detectQuestionType) {
  313. case GLOBAL_MULTIPLE_ANSWER:
  314. case MULTIPLE_ANSWER:
  315. case UNIQUE_ANSWER:
  316. $total = 0;
  317. if (is_array($myAnswerList) && !empty($myAnswerList) && !empty($question_id)) {
  318. $id = 1;
  319. $objAnswer = new Answer($question_id, $courseId);
  320. $globalScore = isset($scoreList[$i]) ? $scoreList[$i] : null;
  321. // Calculate the number of correct answers to divide the
  322. // score between them when importing from CSV
  323. $numberRightAnswers = 0;
  324. foreach ($myAnswerList as $answer_data) {
  325. if (strtolower($answer_data['extra']) == 'x') {
  326. $numberRightAnswers++;
  327. }
  328. }
  329. foreach ($myAnswerList as $answer_data) {
  330. $answerValue = $answer_data['data'];
  331. $correct = 0;
  332. $score = 0;
  333. if (strtolower($answer_data['extra']) == 'x') {
  334. $correct = 1;
  335. $score = isset($scoreList[$i]) ? $scoreList[$i] : 0;
  336. $comment = isset($feedbackTrueList[$i]) ? $feedbackTrueList[$i] : '';
  337. } else {
  338. $comment = isset($feedbackFalseList[$i]) ? $feedbackFalseList[$i] : '';
  339. $floatVal = (float) $answer_data['extra'];
  340. if (is_numeric($floatVal)) {
  341. $score = $answer_data['extra'];
  342. }
  343. }
  344. if ($useCustomScore) {
  345. if ($correct) {
  346. $score = $correctScore;
  347. } else {
  348. $score = $incorrectScore;
  349. }
  350. }
  351. // Fixing scores:
  352. switch ($detectQuestionType) {
  353. case GLOBAL_MULTIPLE_ANSWER:
  354. if ($correct) {
  355. $score = abs($scoreList[$i]);
  356. } else {
  357. if (isset($noNegativeScoreList[$i]) && $noNegativeScoreList[$i] == 'x') {
  358. $score = 0;
  359. } else {
  360. $score = -abs($scoreList[$i]);
  361. }
  362. }
  363. $score /= $numberRightAnswers;
  364. break;
  365. case UNIQUE_ANSWER:
  366. break;
  367. case MULTIPLE_ANSWER:
  368. if (!$correct) {
  369. //$total = $total - $score;
  370. }
  371. break;
  372. }
  373. $objAnswer->createAnswer(
  374. $answerValue,
  375. $correct,
  376. $comment,
  377. $score,
  378. $id
  379. );
  380. $total += (float) $score;
  381. $id++;
  382. }
  383. $objAnswer->save();
  384. $questionObj = Question::read(
  385. $question_id,
  386. $_course
  387. );
  388. if ($questionObj) {
  389. switch ($detectQuestionType) {
  390. case GLOBAL_MULTIPLE_ANSWER:
  391. $questionObj->updateWeighting($globalScore);
  392. break;
  393. case UNIQUE_ANSWER:
  394. case MULTIPLE_ANSWER:
  395. default:
  396. $questionObj->updateWeighting($total);
  397. break;
  398. }
  399. $questionObj->save($exercise);
  400. }
  401. }
  402. break;
  403. case FREE_ANSWER:
  404. $globalScore = isset($scoreList[$i]) ? $scoreList[$i] : null;
  405. $questionObj = Question::read($question_id, $_course);
  406. if ($questionObj) {
  407. $questionObj->updateWeighting($globalScore);
  408. $questionObj->save($exercise);
  409. }
  410. break;
  411. case FILL_IN_BLANKS:
  412. $fillInScoreList = [];
  413. $size = [];
  414. $globalScore = 0;
  415. foreach ($myAnswerList as $data) {
  416. $score = isset($data['extra']) ? $data['extra'] : 0;
  417. $globalScore += $score;
  418. $fillInScoreList[] = $score;
  419. $size[] = 200;
  420. }
  421. $scoreToString = implode(',', $fillInScoreList);
  422. $sizeToString = implode(',', $size);
  423. //<p>Texte long avec les [mots] à [remplir] mis entre [crochets]</p>::10,10,10:200.36363999999998,200,200:0@'
  424. $answerValue = $description.'::'.$scoreToString.':'.$sizeToString.':0@';
  425. $objAnswer = new Answer($question_id, $courseId);
  426. $objAnswer->createAnswer(
  427. $answerValue,
  428. '', //$correct,
  429. '', //$comment,
  430. $globalScore,
  431. 1
  432. );
  433. $objAnswer->save();
  434. $questionObj = Question::read($question_id, $_course);
  435. if ($questionObj) {
  436. $questionObj->updateWeighting($globalScore);
  437. $questionObj->save($exercise);
  438. }
  439. break;
  440. case MATCHING:
  441. $globalScore = isset($scoreList[$i]) ? $scoreList[$i] : null;
  442. $position = 1;
  443. $objAnswer = new Answer($question_id, $courseId);
  444. foreach ($myAnswerList as $data) {
  445. $option = isset($data['extra']) ? $data['extra'] : '';
  446. $objAnswer->createAnswer($option, 0, '', 0, $position);
  447. $position++;
  448. }
  449. $counter = 1;
  450. foreach ($myAnswerList as $data) {
  451. $value = isset($data['data']) ? $data['data'] : '';
  452. $position++;
  453. $objAnswer->createAnswer(
  454. $value,
  455. $counter,
  456. ' ',
  457. $globalScore,
  458. $position
  459. );
  460. $counter++;
  461. }
  462. $objAnswer->save();
  463. $questionObj = Question::read($question_id, $_course);
  464. if ($questionObj) {
  465. $questionObj->updateWeighting($globalScore);
  466. $questionObj->save($exercise);
  467. }
  468. break;
  469. }
  470. }
  471. }
  472. $lpObject = Session::read('lpobject');
  473. if (!empty($lpObject)) {
  474. /** @var learnpath $oLP */
  475. $oLP = UnserializeApi::unserialize('lp', $lpObject);
  476. if (is_object($oLP)) {
  477. if ((empty($oLP->cc)) || $oLP->cc != api_get_course_id()) {
  478. $oLP = null;
  479. Session::erase('oLP');
  480. Session::erase('lpobject');
  481. } else {
  482. Session::write('oLP', $oLP);
  483. }
  484. }
  485. }
  486. if (isset($_SESSION['oLP']) && isset($_GET['lp_id'])) {
  487. $previous = $_SESSION['oLP']->select_previous_item_id();
  488. $parent = 0;
  489. // Add a Quiz as Lp Item
  490. $_SESSION['oLP']->add_item(
  491. $parent,
  492. $previous,
  493. TOOL_QUIZ,
  494. $quiz_id,
  495. $quizTitle,
  496. ''
  497. );
  498. // Redirect to home page for add more content
  499. header('Location: ../lp/lp_controller.php?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.intval($_GET['lp_id']));
  500. exit;
  501. } else {
  502. // header('location: exercise.php?' . api_get_cidreq());
  503. echo '<script>window.location.href = "'.api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'&exerciseId='.$quiz_id.'&session_id='.api_get_session_id().'"</script>';
  504. }
  505. }
  506. }
  507. /**
  508. * @param array $answers_data
  509. *
  510. * @return int
  511. */
  512. function detectQuestionType($answers_data)
  513. {
  514. $correct = 0;
  515. $isNumeric = false;
  516. if (empty($answers_data)) {
  517. return FREE_ANSWER;
  518. }
  519. foreach ($answers_data as $answer_data) {
  520. if (strtolower($answer_data['extra']) == 'x') {
  521. $correct++;
  522. } else {
  523. if (is_numeric($answer_data['extra'])) {
  524. $isNumeric = true;
  525. }
  526. }
  527. }
  528. if ($correct == 1) {
  529. $type = UNIQUE_ANSWER;
  530. } else {
  531. if ($correct > 1) {
  532. $type = MULTIPLE_ANSWER;
  533. } else {
  534. $type = FREE_ANSWER;
  535. }
  536. }
  537. if ($type == MULTIPLE_ANSWER) {
  538. if ($isNumeric) {
  539. $type = MULTIPLE_ANSWER;
  540. } else {
  541. $type = GLOBAL_MULTIPLE_ANSWER;
  542. }
  543. }
  544. return $type;
  545. }
  546. if ($origin != 'learnpath') {
  547. //so we are not in learnpath tool
  548. Display::display_footer();
  549. }