upload_exercise.php 23 KB

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