upload_exercise.php 23 KB

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