upload_exercise.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Upload quiz: This script shows the upload quiz feature
  5. * Initial work by Isaac flores on Nov 4 of 2010
  6. * Encoding fixes Julio Montoya
  7. * @package chamilo.exercise
  8. */
  9. use \ChamiloSession as Session;
  10. // setting the help
  11. $help_content = 'exercise_upload';
  12. // including the global Dokeos file
  13. require_once '../inc/global.inc.php';
  14. require_once api_get_path(LIBRARY_PATH) . 'pear/excelreader/reader.php';
  15. // Security check
  16. $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
  17. if (!$is_allowed_to_edit) {
  18. api_not_allowed(true);
  19. }
  20. // setting the tabs
  21. $this_section = SECTION_COURSES;
  22. $htmlHeadXtra[] = "<script>
  23. $(document).ready( function(){
  24. $('#user_custom_score').click(function() {
  25. $('#options').toggle();
  26. });
  27. });
  28. </script>";
  29. // Action handling
  30. lp_upload_quiz_action_handling();
  31. $interbreadcrumb[]= array ("url"=>"exercise.php", "name"=> get_lang('Exercises'));
  32. Display::display_header(get_lang('ImportExcelQuiz'), 'Exercises');
  33. if (isset($_GET['message'])) {
  34. if (in_array($_GET['message'], array('ExerciseEdited'))) {
  35. Display :: display_confirmation_message(get_lang($_GET['message']));
  36. }
  37. }
  38. // display the actions
  39. echo '<div class="actions">';
  40. echo lp_upload_quiz_actions();
  41. echo '</div>';
  42. // the main content
  43. lp_upload_quiz_main();
  44. function lp_upload_quiz_actions() {
  45. $return = '<a href="exercise.php?'.api_get_cidReq().'">'.
  46. Display::return_icon('back.png', get_lang('BackToExercisesList'),'',ICON_SIZE_MEDIUM).'</a>';
  47. return $return;
  48. }
  49. function lp_upload_quiz_secondary_actions()
  50. {
  51. $return = '<a href="exercise_report.php?' . api_get_cidreq() . '">' .
  52. Display :: return_icon('reporting32.png', get_lang('Tracking')) . get_lang('Tracking') . '</a>';
  53. return $return;
  54. }
  55. function lp_upload_quiz_main() {
  56. // variable initialisation
  57. $lp_id = isset($_GET['lp_id']) ? intval($_GET['lp_id']) : null;
  58. $form = new FormValidator(
  59. 'upload',
  60. 'POST',
  61. api_get_self().'?'.api_get_cidreq().'&lp_id='.$lp_id,
  62. '',
  63. array('enctype' => 'multipart/form-data')
  64. );
  65. $form->addElement('header', get_lang('ImportExcelQuiz'));
  66. $form->addElement('file', 'user_upload_quiz', get_lang('FileUpload'));
  67. $link = '<a href="../exercice/quiz_template.xls">'.
  68. Display::return_icon('export_excel.png', get_lang('DownloadExcelTemplate')).get_lang('DownloadExcelTemplate').'</a>';
  69. $form->addElement('label', '', $link);
  70. $form->addElement('checkbox', 'user_custom_score', null, get_lang('UseCustomScoreForAllQuestions'), array('id'=> 'user_custom_score'));
  71. $form->addElement('html', '<div id="options" style="display:none">');
  72. $form->addElement('text', 'correct_score', get_lang('CorrectScore'));
  73. $form->addElement('text', 'incorrect_score', get_lang('IncorrectScore'));
  74. $form->addElement('html', '</div>');
  75. $form->addRule('user_upload_quiz', get_lang('ThisFieldIsRequired'), 'required');
  76. $form->add_progress_bar();
  77. $form->addButtonUpload(get_lang('Send'), 'submit_upload_quiz');
  78. // Display the upload field
  79. $form->display();
  80. }
  81. /**
  82. * Handles a given Excel spreadsheets as in the template provided
  83. */
  84. function lp_upload_quiz_action_handling() {
  85. global $debug;
  86. $_course = api_get_course_info();
  87. $courseId = $_course['real_id'];
  88. if (!isset($_POST['submit_upload_quiz'])) {
  89. return;
  90. }
  91. // Get the extension of the document.
  92. $path_info = pathinfo($_FILES['user_upload_quiz']['name']);
  93. // Check if the document is an Excel document
  94. if ($path_info['extension'] != 'xls') {
  95. return;
  96. }
  97. // Read the Excel document
  98. $data = new Spreadsheet_Excel_Reader();
  99. // Set output Encoding.
  100. $data->setOutputEncoding(api_get_system_encoding());
  101. // Reading the xls document.
  102. $data->read($_FILES['user_upload_quiz']['tmp_name']);
  103. $correctScore = isset($_POST['correct_score']) ? $_POST['correct_score'] : null;
  104. $incorrectScore = isset($_POST['incorrect_score']) ? $_POST['incorrect_score'] : null;
  105. $useCustomScore = isset($_POST['user_custom_score']) ? true : false;
  106. $propagateNegative = 0;
  107. if ($useCustomScore && !empty($incorrectScore)) {
  108. if ($incorrectScore < 0) {
  109. $propagateNegative = 1;
  110. }
  111. }
  112. // Variables
  113. $quiz_index = 0;
  114. $question_title_index = array();
  115. $question_name_index_init = array();
  116. $question_name_index_end = array();
  117. $score_index = array();
  118. $feedback_true_index = array();
  119. $feedback_false_index = array();
  120. $number_questions = 0;
  121. $question_description_index = array();
  122. $noNegativeScoreIndex = array();
  123. $questionTypeList = array();
  124. $questionTypeIndex = array();
  125. // Reading all the first column items sequentially to create breakpoints
  126. for ($i = 1; $i <= $data->sheets[0]['numRows']; $i++) {
  127. if ($data->sheets[0]['cells'][$i][1] == 'Quiz' && $i == 1) {
  128. $quiz_index = $i; // Quiz title position, only occurs once
  129. } elseif ($data->sheets[0]['cells'][$i][1] == 'Question') {
  130. $question_title_index[] = $i; // Question title position line
  131. $question_name_index_init[] = $i + 1; // Questions name 1st position line
  132. $number_questions++;
  133. } elseif ($data->sheets[0]['cells'][$i][1] == 'Score') {
  134. $question_name_index_end[] = $i - 1; // Question name position
  135. $score_index[] = $i; // Question score position
  136. } elseif ($data->sheets[0]['cells'][$i][1] == 'FeedbackTrue') {
  137. $feedback_true_index[] = $i; // FeedbackTrue position (line)
  138. } elseif ($data->sheets[0]['cells'][$i][1] == 'FeedbackFalse') {
  139. $feedback_false_index[] = $i; // FeedbackFalse position (line)
  140. } elseif ($data->sheets[0]['cells'][$i][1] == 'EnrichQuestion') {
  141. $question_description_index[] = $i;
  142. } elseif ($data->sheets[0]['cells'][$i][1] == 'NoNegativeScore') {
  143. $noNegativeScoreIndex[] = $i;
  144. } elseif ($data->sheets[0]['cells'][$i][1] == 'QuestionType') {
  145. $questionTypeIndex[] = $i;
  146. }
  147. }
  148. // Variables
  149. $quiz = array();
  150. $question = array();
  151. $new_answer = array();
  152. $score_list = array();
  153. $feedback_true_list = array();
  154. $feedback_false_list = array();
  155. $question_description = array();
  156. $noNegativeScoreList = array();
  157. // Getting questions.
  158. $k = $z = $q = $l = $m = $n = 0;
  159. for ($i = 1; $i <= $data->sheets[0]['numRows']; $i++) {
  160. if (is_array($data->sheets[0]['cells'][$i])) {
  161. $column_data = $data->sheets[0]['cells'][$i];
  162. // Fill all column with data to have a full array
  163. for ($x = 1; $x <= $data->sheets[0]['numCols']; $x++) {
  164. if (empty($column_data[$x])) {
  165. $data->sheets[0]['cells'][$i][$x] = '';
  166. }
  167. }
  168. // Array filled with data
  169. $column_data = $data->sheets[0]['cells'][$i];
  170. } else {
  171. $column_data = '';
  172. }
  173. // Fill quiz data
  174. if ($quiz_index == $i) {
  175. // The title always in the first position
  176. $quiz = $column_data;
  177. } elseif (in_array($i, $question_title_index)) {
  178. //a complete line where 1st column is 'Question'
  179. $question[$k] = $column_data;
  180. for ($counter = 0; $counter < 12; $counter++) {
  181. $myData = isset($data->sheets[0]['cells'][$i + $counter]) ? $data->sheets[0]['cells'][$i + $counter] : null;
  182. if (isset($myData[1]) && $myData[1] == 'QuestionType') {
  183. $questionTypeList[$k] = $myData[3];
  184. }
  185. }
  186. if (!isset($questionTypeList[$k])) {
  187. $questionTypeList[$k] = null;
  188. }
  189. $k++;
  190. } elseif (in_array($i, $score_index)) {
  191. //a complete line where 1st column is 'Score'
  192. $score_list[$z] = $column_data;
  193. $z++;
  194. } elseif (in_array($i, $feedback_true_index)) {
  195. //a complete line where 1st column is 'FeedbackTrue'
  196. $feedback_true_list[$q] = $column_data;
  197. $q++;
  198. } elseif (in_array($i, $feedback_false_index)) {
  199. //a complete line where 1st column is 'FeedbackFalse' for wrong answers
  200. $feedback_false_list[$l] = $column_data;
  201. $l++;
  202. } elseif (in_array($i, $question_description_index)) {
  203. //a complete line where 1st column is 'EnrichQuestion'
  204. $question_description[$m] = $column_data;
  205. $m++;
  206. } elseif (in_array($i, $noNegativeScoreIndex)) {
  207. //a complete line where 1st column is 'NoNegativeScore'
  208. $noNegativeScoreList[$z-1] = $column_data;
  209. }
  210. }
  211. // Get answers
  212. for ($i = 0; $i < count($question_name_index_init); $i++) {
  213. for ($j = $question_name_index_init[$i]; $j <= $question_name_index_end[$i]; $j++) {
  214. if (is_array($data->sheets[0]['cells'][$j])) {
  215. $column_data = $data->sheets[0]['cells'][$j];
  216. // Fill all column with data
  217. for ($x = 1; $x <= $data->sheets[0]['numCols']; $x++) {
  218. if (empty($column_data[$x])) {
  219. $data->sheets[0]['cells'][$j][$x] = '';
  220. }
  221. }
  222. $column_data = $data->sheets[0]['cells'][$j];
  223. // Array filled of data
  224. if (is_array($column_data) && count($column_data) > 0) {
  225. $new_answer[$i][$j] = $column_data;
  226. }
  227. }
  228. }
  229. }
  230. // Quiz title.
  231. $quiz_title = $quiz[2];
  232. if ($quiz_title != '') {
  233. // Variables
  234. $type = 2;
  235. $random = $active = $results = $max_attempt = $expired_time = 0;
  236. // Make sure feedback is enabled (3 to disable), otherwise the fields
  237. // added to the XLS are not shown, which is confusing
  238. $feedback = 0;
  239. // Quiz object
  240. $exercise = new Exercise();
  241. //
  242. $quiz_id = $exercise->createExercise(
  243. $quiz_title,
  244. $expired_time,
  245. $type,
  246. $random,
  247. $active,
  248. $results,
  249. $max_attempt,
  250. $feedback,
  251. $propagateNegative
  252. );
  253. if ($quiz_id) {
  254. // insert into the item_property table
  255. api_item_property_update(
  256. $_course,
  257. TOOL_QUIZ,
  258. $quiz_id,
  259. 'QuizAdded',
  260. api_get_user_id()
  261. );
  262. // Import questions.
  263. for ($i = 0; $i < $number_questions; $i++) {
  264. // Question name
  265. $question_title = $question[$i][2];
  266. $description = isset($question_description[$i][2]) ? $question_description[$i][2] : '';
  267. $question_description_text = "<p></p>";
  268. if (!empty($description)) {
  269. // Question description.
  270. $question_description_text = "<p>".$description."</p>";
  271. }
  272. // Unique answers are the only question types available for now
  273. // through xls-format import
  274. $answerList = isset($new_answer[$i]) ? $new_answer[$i] : '';
  275. $question_id = null;
  276. if (isset($questionTypeList[$i])) {
  277. $detectQuestionType = intval($questionTypeList[$i]);
  278. } else {
  279. $detectQuestionType = detectQuestionType(
  280. $answerList,
  281. $score_list
  282. );
  283. }
  284. /** @var Question $answer */
  285. switch ($detectQuestionType) {
  286. case FREE_ANSWER:
  287. $answer = new FreeAnswer();
  288. break;
  289. case GLOBAL_MULTIPLE_ANSWER:
  290. $answer = new GlobalMultipleAnswer();
  291. break;
  292. case MULTIPLE_ANSWER:
  293. $answer = new MultipleAnswer();
  294. break;
  295. case FILL_IN_BLANKS:
  296. $answer = new FillBlanks();
  297. $question_description_text = '';
  298. break;
  299. case MATCHING:
  300. $answer = new Matching();
  301. break;
  302. case UNIQUE_ANSWER:
  303. default:
  304. $answer = new UniqueAnswer();
  305. break;
  306. }
  307. if ($question_title != '') {
  308. $question_id = $answer->create_question(
  309. $quiz_id,
  310. $question_title,
  311. $question_description_text,
  312. 0, // max score
  313. $answer->type
  314. );
  315. }
  316. switch ($detectQuestionType) {
  317. case GLOBAL_MULTIPLE_ANSWER:
  318. case MULTIPLE_ANSWER:
  319. case UNIQUE_ANSWER:
  320. $total = 0;
  321. if (is_array($answerList) && !empty($question_id)) {
  322. $id = 1;
  323. $globalScore = null;
  324. $objAnswer = new Answer($question_id, $courseId);
  325. $globalScore = $score_list[$i][3];
  326. // Calculate the number of correct answers to divide the
  327. // score between them when importing from CSV
  328. $numberRightAnswers = 0;
  329. foreach ($answerList as $answer_data) {
  330. if (strtolower($answer_data[3]) == 'x') {
  331. $numberRightAnswers++;
  332. }
  333. }
  334. foreach ($answerList as $answer_data) {
  335. $answerValue = $answer_data[2];
  336. $correct = 0;
  337. $score = 0;
  338. if (strtolower($answer_data[3]) == 'x') {
  339. $correct = 1;
  340. $score = $score_list[$i][3];
  341. $comment = $feedback_true_list[$i][2];
  342. } else {
  343. $comment = $feedback_false_list[$i][2];
  344. $floatVal = (float)$answer_data[3];
  345. if (is_numeric($floatVal)) {
  346. $score = $answer_data[3];
  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 (isset($noNegativeScoreList[$i][3])) {
  360. if (!(strtolower($noNegativeScoreList[$i][3]) == 'x') &&
  361. !$correct
  362. ) {
  363. $score = $score_list[$i][3] * -1;
  364. }
  365. }
  366. $score /= $numberRightAnswers;
  367. break;
  368. case UNIQUE_ANSWER:
  369. break;
  370. case MULTIPLE_ANSWER:
  371. if (!$correct) {
  372. //$total = $total - $score;
  373. }
  374. break;
  375. }
  376. $objAnswer->createAnswer(
  377. $answerValue,
  378. $correct,
  379. $comment,
  380. $score,
  381. $id
  382. );
  383. $total += $score;
  384. $id++;
  385. }
  386. $objAnswer->save();
  387. $questionObj = Question::read(
  388. $question_id,
  389. $courseId
  390. );
  391. switch ($detectQuestionType) {
  392. case GLOBAL_MULTIPLE_ANSWER:
  393. $questionObj->updateWeighting($globalScore);
  394. break;
  395. case UNIQUE_ANSWER:
  396. case MULTIPLE_ANSWER:
  397. default:
  398. $questionObj->updateWeighting($total);
  399. break;
  400. }
  401. $questionObj->save();
  402. }
  403. break;
  404. case FREE_ANSWER:
  405. $questionObj = Question::read($question_id, $courseId);
  406. $globalScore = $score_list[$i][3];
  407. $questionObj->updateWeighting($globalScore);
  408. $questionObj->save();
  409. break;
  410. case FILL_IN_BLANKS:
  411. $scoreList = array();
  412. $size = array();
  413. $globalScore = 0;
  414. foreach($answerList as $data) {
  415. $score = isset($data[3]) ? $data[3] : 0;
  416. $globalScore += $score;
  417. $scoreList[] = $score;
  418. $size[] = 200;
  419. }
  420. $scoreToString = implode(',', $scoreList);
  421. $sizeToString = implode(',', $size);
  422. //<p>Texte long avec les [mots] à [remplir] mis entre [crochets]</p>::10,10,10:200.36363999999998,200,200:0@'
  423. $answerValue = $description.'::'.$scoreToString.':'.$sizeToString.':0@';
  424. $objAnswer = new Answer($question_id, $courseId);
  425. $objAnswer->createAnswer(
  426. $answerValue,
  427. '', //$correct,
  428. '', //$comment,
  429. $globalScore,
  430. 1
  431. );
  432. $objAnswer->save();
  433. $questionObj = Question::read($question_id, $courseId);
  434. $questionObj->updateWeighting($globalScore);
  435. $questionObj->save();
  436. break;
  437. case MATCHING:
  438. $globalScore = $score_list[$i][3];
  439. $position = 1;
  440. $objAnswer = new Answer($question_id, $courseId);
  441. foreach ($answerList as $data) {
  442. $option = isset($data[3]) ? $data[3] : '';
  443. $objAnswer->createAnswer($option, 0, '', 0, $position);
  444. $position++;
  445. }
  446. $counter = 1;
  447. foreach ($answerList as $data) {
  448. $value = isset($data[2]) ? $data[2] : '';
  449. $position++;
  450. $objAnswer->createAnswer(
  451. $value,
  452. $counter,
  453. ' ',
  454. $globalScore,
  455. $position
  456. );
  457. $counter++;
  458. }
  459. $objAnswer->save();
  460. $questionObj = Question::read($question_id, $courseId);
  461. $questionObj->updateWeighting($globalScore);
  462. $questionObj->save();
  463. break;
  464. }
  465. }
  466. }
  467. if (isset($_SESSION['lpobject'])) {
  468. if ($debug > 0) {
  469. error_log('New LP - SESSION[lpobject] is defined', 0);
  470. }
  471. $oLP = unserialize($_SESSION['lpobject']);
  472. if (is_object($oLP)) {
  473. if ($debug > 0) {
  474. error_log('New LP - oLP is object', 0);
  475. }
  476. if ((empty($oLP->cc)) OR $oLP->cc != api_get_course_id()) {
  477. if ($debug > 0) {
  478. error_log('New LP - Course has changed, discard lp object', 0);
  479. }
  480. $oLP = null;
  481. Session::erase('oLP');
  482. Session::erase('lpobject');
  483. } else {
  484. $_SESSION['oLP'] = $oLP;
  485. }
  486. }
  487. }
  488. if (isset($_SESSION['oLP']) && isset($_GET['lp_id'])) {
  489. $previous = $_SESSION['oLP']->select_previous_item_id();
  490. $parent = 0;
  491. // Add a Quiz as Lp Item
  492. $_SESSION['oLP']->add_item($parent, $previous, TOOL_QUIZ, $quiz_id, $quiz_title, '');
  493. // Redirect to home page for add more content
  494. header('location: ../newscorm/lp_controller.php?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.Security::remove_XSS($_GET['lp_id']));
  495. exit;
  496. } else {
  497. // header('location: exercise.php?' . api_get_cidreq());
  498. echo '<script>window.location.href = "'.api_get_path(WEB_CODE_PATH).'exercice/admin.php?'.api_get_cidReq().'&exerciseId='.$quiz_id.'&session_id='.api_get_session_id().'"</script>';
  499. }
  500. }
  501. }
  502. /**
  503. * @param array $answers_data
  504. * @return int
  505. */
  506. function detectQuestionType($answers_data) {
  507. $correct = 0;
  508. $isNumeric = false;
  509. if (empty($answers_data)) {
  510. return FREE_ANSWER;
  511. }
  512. foreach ($answers_data as $answer_data) {
  513. if (strtolower($answer_data[3]) == 'x') {
  514. $correct++;
  515. } else {
  516. if (is_numeric($answer_data[3])) {
  517. $isNumeric = true;
  518. }
  519. }
  520. }
  521. if ($correct == 1) {
  522. $type = UNIQUE_ANSWER;
  523. } else if ($correct > 1) {
  524. $type = MULTIPLE_ANSWER;
  525. } else {
  526. $type = FREE_ANSWER;
  527. }
  528. if ($type == MULTIPLE_ANSWER) {
  529. if ($isNumeric) {
  530. $type = MULTIPLE_ANSWER;
  531. } else {
  532. $type = GLOBAL_MULTIPLE_ANSWER;
  533. }
  534. }
  535. return $type;
  536. }
  537. if (!isset($origin) || isset($origin) && $origin != 'learnpath') {
  538. //so we are not in learnpath tool
  539. Display :: display_footer();
  540. }