answer.class.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class Answer
  5. * Allows to instantiate an object of type Answer
  6. * 5 arrays are created to receive the attributes of each answer belonging to a specified question
  7. * @package chamilo.exercise
  8. * @author Olivier Brouckaert
  9. * @package chamilo.exercise
  10. */
  11. class Answer
  12. {
  13. public $questionId;
  14. // these are arrays
  15. public $answer;
  16. public $correct;
  17. public $comment;
  18. public $weighting;
  19. public $position;
  20. public $hotspot_coordinates;
  21. public $hotspot_type;
  22. public $destination;
  23. // these arrays are used to save temporarily new answers
  24. // then they are moved into the arrays above or deleted in the event of cancellation
  25. public $new_answer;
  26. public $new_correct;
  27. public $new_comment;
  28. public $new_weighting;
  29. public $new_position;
  30. public $new_hotspot_coordinates;
  31. public $new_hotspot_type;
  32. public $nbrAnswers;
  33. public $new_nbrAnswers;
  34. public $new_destination; // id of the next question if feedback option is set to Directfeedback
  35. public $course; //Course information
  36. /**
  37. * constructor of the class
  38. *
  39. * @author Olivier Brouckaert
  40. * @param integer Question ID that answers belong to
  41. */
  42. function Answer($questionId, $course_id = null)
  43. {
  44. $this->questionId = intval($questionId);
  45. $this->answer = array();
  46. $this->correct = array();
  47. $this->comment = array();
  48. $this->weighting = array();
  49. $this->position = array();
  50. $this->hotspot_coordinates = array();
  51. $this->hotspot_type = array();
  52. $this->destination = array();
  53. // clears $new_* arrays
  54. $this->cancel();
  55. if (!empty($course_id)) {
  56. $course_info = api_get_course_info_by_id($course_id);
  57. } else {
  58. $course_info = api_get_course_info();
  59. }
  60. $this->course = $course_info;
  61. $this->course_id = $course_info['real_id'];
  62. // fills arrays
  63. $objExercise = new Exercise($this->course_id);
  64. $exerciseId = isset($_REQUEST['exerciseId']) ? $_REQUEST['exerciseId'] : null;
  65. $objExercise->read($exerciseId);
  66. if ($objExercise->random_answers == '1') {
  67. $this->readOrderedBy('rand()', '');// randomize answers
  68. } else {
  69. $this->read(); // natural order
  70. }
  71. }
  72. /**
  73. * Clears $new_* arrays
  74. *
  75. * @author Olivier Brouckaert
  76. */
  77. function cancel() {
  78. $this->new_answer = array();
  79. $this->new_correct = array();
  80. $this->new_comment = array();
  81. $this->new_weighting = array();
  82. $this->new_position = array();
  83. $this->new_hotspot_coordinates = array();
  84. $this->new_hotspot_type = array();
  85. $this->new_nbrAnswers = 0;
  86. $this->new_destination = array();
  87. }
  88. /**
  89. * Reads answer information from the database
  90. *
  91. * @author Olivier Brouckaert
  92. */
  93. public function read()
  94. {
  95. $TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
  96. $questionId = $this->questionId;
  97. $sql = "SELECT * FROM $TBL_ANSWER
  98. WHERE
  99. c_id = {$this->course_id} AND
  100. question_id ='".$questionId."'
  101. ORDER BY position";
  102. $result = Database::query($sql);
  103. $i=1;
  104. // while a record is found
  105. while ($object = Database::fetch_object($result)) {
  106. $this->id[$i] = $object->id;
  107. $this->answer[$i] = $object->answer;
  108. $this->correct[$i] = $object->correct;
  109. $this->comment[$i] = $object->comment;
  110. $this->weighting[$i] = $object->ponderation;
  111. $this->position[$i] = $object->position;
  112. $this->hotspot_coordinates[$i] = $object->hotspot_coordinates;
  113. $this->hotspot_type[$i] = $object->hotspot_type;
  114. $this->destination[$i] = $object->destination;
  115. $this->autoId[$i] = $object->id_auto;
  116. $i++;
  117. }
  118. $this->nbrAnswers = $i-1;
  119. }
  120. /**
  121. * returns all answer ids from this question Id
  122. *
  123. * @author Yoselyn Castillo
  124. * @return array - $id (answer ids)
  125. */
  126. public function selectAnswerId()
  127. {
  128. $TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
  129. $questionId = $this->questionId;
  130. $sql="SELECT id FROM
  131. $TBL_ANSWER WHERE c_id = {$this->course_id} AND question_id ='".$questionId."'";
  132. $result = Database::query($sql);
  133. $id = array();
  134. // while a record is found
  135. if (Database::num_rows($result) > 0) {
  136. while ($object = Database::fetch_array($result)) {
  137. $id[] = $object['id'];
  138. }
  139. }
  140. return $id;
  141. }
  142. /**
  143. * Reads answer information from the data base ordered by parameter
  144. * @param string Field we want to order by
  145. * @param string DESC or ASC
  146. * @author Frederic Vauthier
  147. */
  148. function readOrderedBy($field, $order='ASC')
  149. {
  150. $field = Database::escape_string($field);
  151. if (empty($field)) {
  152. $field = 'position';
  153. }
  154. if ($order != 'ASC' && $order!='DESC') {
  155. $order = 'ASC';
  156. }
  157. $TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
  158. $TBL_QUIZ = Database::get_course_table(TABLE_QUIZ_QUESTION);
  159. $questionId=intval($this->questionId);
  160. $sql = "SELECT type FROM $TBL_QUIZ WHERE c_id = {$this->course_id} AND id = $questionId";
  161. $result_question = Database::query($sql);
  162. $question_type = Database::fetch_array($result_question);
  163. $sql = "SELECT answer,correct,comment,ponderation,position, hotspot_coordinates, hotspot_type, destination, id_auto " .
  164. "FROM $TBL_ANSWER WHERE c_id = {$this->course_id} AND question_id='".$questionId."' " .
  165. "ORDER BY $field $order";
  166. $result=Database::query($sql);
  167. $i = 1;
  168. // while a record is found
  169. $doubt_data = null;
  170. while($object = Database::fetch_object($result)) {
  171. if ($question_type['type'] == UNIQUE_ANSWER_NO_OPTION && $object->position == 666) {
  172. $doubt_data = $object;
  173. continue;
  174. }
  175. $this->answer[$i] = $object->answer;
  176. $this->correct[$i] = $object->correct;
  177. $this->comment[$i] = $object->comment;
  178. $this->weighting[$i] = $object->ponderation;
  179. $this->position[$i] = $object->position;
  180. $this->destination[$i] = $object->destination;
  181. $this->autoId[$i] = $object->id_auto;
  182. $i++;
  183. }
  184. if ($question_type['type'] == UNIQUE_ANSWER_NO_OPTION && !empty($doubt_data)) {
  185. $this->answer[$i] = $doubt_data->answer;
  186. $this->correct[$i] = $doubt_data->correct;
  187. $this->comment[$i] = $doubt_data->comment;
  188. $this->weighting[$i] = $doubt_data->ponderation;
  189. $this->position[$i] = $doubt_data->position;
  190. $this->destination[$i] = $doubt_data->destination;
  191. $this->autoId[$i] = $doubt_data->id_auto;
  192. $i++;
  193. }
  194. $this->nbrAnswers = $i-1;
  195. }
  196. /**
  197. * returns the autoincrement id identificator
  198. *
  199. * @author Juan Carlos Ra�a
  200. * @return integer - answer num
  201. */
  202. function selectAutoId($id)
  203. {
  204. return isset($this->autoId[$id]) ? $this->autoId[$id] : null;
  205. }
  206. /**
  207. * returns the number of answers in this question
  208. *
  209. * @author Olivier Brouckaert
  210. * @return integer - number of answers
  211. */
  212. function selectNbrAnswers()
  213. {
  214. return $this->nbrAnswers;
  215. }
  216. /**
  217. * returns the question ID which the answers belong to
  218. *
  219. * @author Olivier Brouckaert
  220. * @return integer - the question ID
  221. */
  222. function selectQuestionId()
  223. {
  224. return $this->questionId;
  225. }
  226. /**
  227. * returns the question ID of the destination question
  228. *
  229. * @author Julio Montoya
  230. * @return integer - the question ID
  231. */
  232. function selectDestination($id)
  233. {
  234. return isset($this->destination[$id]) ? $this->destination[$id] : null;
  235. }
  236. /**
  237. * returns the answer title
  238. *
  239. * @author Olivier Brouckaert
  240. * @param - integer $id - answer ID
  241. * @return string - answer title
  242. */
  243. function selectAnswer($id)
  244. {
  245. return isset($this->answer[$id]) ? $this->answer[$id] : null;
  246. }
  247. /**
  248. * return array answer by id else return a bool
  249. */
  250. function selectAnswerByAutoId($auto_id) {
  251. $TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
  252. $auto_id = intval($auto_id);
  253. $sql="SELECT id, answer, id_auto FROM $TBL_ANSWER WHERE c_id = {$this->course_id} AND id_auto='$auto_id'";
  254. $rs = Database::query($sql);
  255. if (Database::num_rows($rs) > 0) {
  256. $row = Database::fetch_array($rs);
  257. return $row;
  258. }
  259. return false;
  260. }
  261. /**
  262. * returns the answer title from an answer's position
  263. *
  264. * @author Yannick Warnier
  265. * @param - integer $id - answer ID
  266. * @return bool - answer title
  267. */
  268. function selectAnswerIdByPosition($pos) {
  269. foreach ($this->position as $k => $v) {
  270. if ($v != $pos) { continue; }
  271. return $k;
  272. }
  273. return false;
  274. }
  275. /**
  276. * Returns a list of answers
  277. * @author Yannick Warnier <ywarnier@beeznest.org>
  278. * @return array List of answers where each answer is an array of (id, answer, comment, grade) and grade=weighting
  279. */
  280. function getAnswersList($decode = false)
  281. {
  282. $list = array();
  283. for($i = 1; $i<=$this->nbrAnswers;$i++){
  284. if(!empty($this->answer[$i])){
  285. //Avoid problems when parsing elements with accents
  286. if ($decode) {
  287. $this->answer[$i] = api_html_entity_decode($this->answer[$i], ENT_QUOTES, api_get_system_encoding());
  288. $this->comment[$i] = api_html_entity_decode($this->comment[$i], ENT_QUOTES, api_get_system_encoding());
  289. }
  290. $list[] = array(
  291. 'id' => $i,
  292. 'answer' => $this->answer[$i],
  293. 'comment' => $this->comment[$i],
  294. 'grade' => $this->weighting[$i],
  295. 'hotspot_coord' => $this->hotspot_coordinates[$i],
  296. 'hotspot_type' => $this->hotspot_type[$i],
  297. 'correct' => $this->correct[$i],
  298. 'destination' => $this->destination[$i]
  299. );
  300. }
  301. }
  302. return $list;
  303. }
  304. /**
  305. * Returns a list of grades
  306. * @author Yannick Warnier <ywarnier@beeznest.org>
  307. * @return array List of grades where grade=weighting (?)
  308. */
  309. function getGradesList() {
  310. $list = array();
  311. for($i = 0; $i<$this->nbrAnswers;$i++){
  312. if(!empty($this->answer[$i])){
  313. $list[$i] = $this->weighting[$i];
  314. }
  315. }
  316. return $list;
  317. }
  318. /**
  319. * Returns the question type
  320. * @author Yannick Warnier <ywarnier@beeznest.org>
  321. * @return integer The type of the question this answer is bound to
  322. */
  323. function getQuestionType() {
  324. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  325. $sql = "SELECT type FROM $TBL_QUESTIONS WHERE c_id = {$this->course_id} AND id = '".$this->questionId."'";
  326. $res = Database::query($sql);
  327. if(Database::num_rows($res)<=0){
  328. return null;
  329. }
  330. $row = Database::fetch_array($res);
  331. return $row['type'];
  332. }
  333. /**
  334. * tells if answer is correct or not
  335. *
  336. * @author Olivier Brouckaert
  337. * @param - integer $id - answer ID
  338. * @return integer - 0 if bad answer, not 0 if good answer
  339. */
  340. function isCorrect($id)
  341. {
  342. return isset($this->correct[$id]) ? $this->correct[$id] : null;
  343. }
  344. /**
  345. * returns answer comment
  346. *
  347. * @author Olivier Brouckaert
  348. * @param - integer $id - answer ID
  349. * @return string - answer comment
  350. */
  351. function selectComment($id)
  352. {
  353. return isset($this->comment[$id]) ? $this->comment[$id] : null;
  354. }
  355. /**
  356. * returns answer weighting
  357. *
  358. * @author Olivier Brouckaert
  359. * @param - integer $id - answer ID
  360. * @return integer - answer weighting
  361. */
  362. function selectWeighting($id)
  363. {
  364. return isset($this->weighting[$id]) ? $this->weighting[$id] : null;
  365. }
  366. /**
  367. * returns answer position
  368. *
  369. * @author Olivier Brouckaert
  370. * @param - integer $id - answer ID
  371. * @return integer - answer position
  372. */
  373. function selectPosition($id)
  374. {
  375. return isset($this->position[$id]) ? $this->position[$id] : null;
  376. }
  377. /**
  378. * returns answer hotspot coordinates
  379. *
  380. * @author Olivier Brouckaert
  381. * @param integer Answer ID
  382. * @return integer Answer position
  383. */
  384. function selectHotspotCoordinates($id)
  385. {
  386. return isset($this->hotspot_coordinates[$id]) ? $this->hotspot_coordinates[$id] : null;
  387. }
  388. /**
  389. * returns answer hotspot type
  390. *
  391. * @author Toon Keppens
  392. * @param integer Answer ID
  393. * @return integer Answer position
  394. */
  395. function selectHotspotType($id)
  396. {
  397. return isset($this->hotspot_type[$id]) ? $this->hotspot_type[$id] : null;
  398. }
  399. /**
  400. * Creates a new answer
  401. *
  402. * @author Olivier Brouckaert
  403. * @param string answer title
  404. * @param integer 0 if bad answer, not 0 if good answer
  405. * @param string answer comment
  406. * @param integer answer weighting
  407. * @param integer answer position
  408. * @param coordinates Coordinates for hotspot exercises (optional)
  409. * @param integer Type for hotspot exercises (optional)
  410. */
  411. function createAnswer($answer,$correct,$comment,$weighting, $position, $new_hotspot_coordinates = null, $new_hotspot_type = null, $destination='')
  412. {
  413. $this->new_nbrAnswers++;
  414. $id=$this->new_nbrAnswers;
  415. $this->new_answer[$id]=$answer;
  416. $this->new_correct[$id]=$correct;
  417. $this->new_comment[$id]=$comment;
  418. $this->new_weighting[$id]=$weighting;
  419. $this->new_position[$id]=$position;
  420. $this->new_hotspot_coordinates[$id]=$new_hotspot_coordinates;
  421. $this->new_hotspot_type[$id]=$new_hotspot_type;
  422. $this->new_destination[$id] = $destination;
  423. }
  424. /**
  425. * Updates an answer
  426. *
  427. * @author Toon Keppens
  428. *
  429. * @param string $answer
  430. * @param string $comment
  431. * @param string $correct
  432. * @param string $weighting
  433. * @param string $position
  434. * @param string $destination
  435. * @param string $hotspot_coordinates
  436. * @param string $hotspot_type
  437. */
  438. function updateAnswers($answer, $comment, $correct, $weighting, $position, $destination, $hotspot_coordinates, $hotspot_type)
  439. {
  440. $TBL_REPONSES = Database :: get_course_table(TABLE_QUIZ_ANSWER);
  441. $idAnswer = $this->selectAnswerId();
  442. $id = $this->getQuestionType() == 3 ? $idAnswer[0] : Database::escape_string($position);
  443. $questionId=$this->questionId;
  444. $sql = "UPDATE $TBL_REPONSES SET
  445. answer = '".Database::escape_string($answer)."',
  446. comment = '".Database::escape_string($comment)."',
  447. correct = '".Database::escape_string($correct)."',
  448. ponderation = '".Database::escape_string($weighting)."',
  449. position = '".Database::escape_string($position)."',
  450. destination = '".Database::escape_string($destination)."',
  451. hotspot_coordinates = '".Database::escape_string($hotspot_coordinates)."',
  452. hotspot_type = '".Database::escape_string($hotspot_type)."'
  453. WHERE c_id = {$this->course_id} AND id = '$id'
  454. AND question_id = ".intval($questionId)."";
  455. Database::query($sql);
  456. }
  457. /**
  458. * Records answers into the data base
  459. *
  460. * @author Olivier Brouckaert
  461. */
  462. function save()
  463. {
  464. $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
  465. $questionId = intval($this->questionId);
  466. $c_id = $this->course['real_id'];
  467. // inserts new answers into data base
  468. $flag = 0;
  469. $sql = "INSERT INTO $TBL_REPONSES (c_id, id, question_id, answer, correct, comment, ponderation, position, hotspot_coordinates, hotspot_type, destination) VALUES ";
  470. for ($i=1;$i <= $this->new_nbrAnswers; $i++) {
  471. $answer = Database::escape_string($this->new_answer[$i]);
  472. $correct = Database::escape_string($this->new_correct[$i]);
  473. $comment = Database::escape_string($this->new_comment[$i]);
  474. $weighting = Database::escape_string($this->new_weighting[$i]);
  475. $position = Database::escape_string($this->new_position[$i]);
  476. $hotspot_coordinates = Database::escape_string($this->new_hotspot_coordinates[$i]);
  477. $hotspot_type = Database::escape_string($this->new_hotspot_type[$i]);
  478. $destination = Database::escape_string($this->new_destination[$i]);
  479. if (!(isset($this->position[$i]))) {
  480. $flag = 1;
  481. $sql.="($c_id, '$i','$questionId','$answer','$correct','$comment','$weighting','$position','$hotspot_coordinates','$hotspot_type','$destination'),";
  482. } else {
  483. // https://support.chamilo.org/issues/6558
  484. // function updateAnswers already escape_string, error if we do it twice.
  485. // Feed function updateAnswers with none escaped strings
  486. $this->updateAnswers(
  487. $this->new_answer[$i],
  488. $this->new_comment[$i],
  489. $this->new_correct[$i],
  490. $this->new_weighting[$i],
  491. $this->new_position[$i],
  492. $this->new_destination[$i],
  493. $this->new_hotspot_coordinates[$i],
  494. $this->new_hotspot_type[$i]
  495. );
  496. }
  497. }
  498. if ($flag == 1) {
  499. $sql = api_substr($sql,0,-1);
  500. Database::query($sql);
  501. }
  502. if (count($this->position) > $this->new_nbrAnswers) {
  503. $i = $this->new_nbrAnswers+1;
  504. while ($this->position[$i]) {
  505. $position = $this->position[$i];
  506. $sql = "DELETE FROM $TBL_REPONSES WHERE c_id = {$this->course_id} AND question_id = '".($questionId)."' AND position ='$position'";
  507. Database::query($sql);
  508. $i++;
  509. }
  510. }
  511. // moves $new_* arrays
  512. $this->answer=$this->new_answer;
  513. $this->correct=$this->new_correct;
  514. $this->comment=$this->new_comment;
  515. $this->weighting=$this->new_weighting;
  516. $this->position=$this->new_position;
  517. $this->hotspot_coordinates=$this->new_hotspot_coordinates;
  518. $this->hotspot_type=$this->new_hotspot_type;
  519. $this->nbrAnswers=$this->new_nbrAnswers;
  520. $this->destination=$this->new_destination;
  521. // clears $new_* arrays
  522. $this->cancel();
  523. }
  524. /**
  525. * Duplicates answers by copying them into another question
  526. *
  527. * @author Olivier Brouckaert
  528. * @param int question id
  529. * @param array destination course info (result of the function api_get_course_info() )
  530. */
  531. function duplicate($newQuestionId, $course_info = null) {
  532. if (empty($course_info)) {
  533. $course_info = $this->course;
  534. } else {
  535. $course_info = $course_info;
  536. }
  537. $TBL_REPONSES = Database :: get_course_table(TABLE_QUIZ_ANSWER);
  538. if (self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE || self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE) {
  539. //Selecting origin options
  540. $origin_options = Question::readQuestionOption($this->selectQuestionId(), $this->course['real_id']);
  541. if (!empty($origin_options)) {
  542. foreach($origin_options as $item) {
  543. $new_option_list[]=$item['id'];
  544. }
  545. }
  546. $destination_options = Question::readQuestionOption($newQuestionId, $course_info['real_id']);
  547. $i=0;
  548. $fixed_list = array();
  549. if (!empty($destination_options)) {
  550. foreach($destination_options as $item) {
  551. $fixed_list[$new_option_list[$i]] = $item['id'];
  552. $i++;
  553. }
  554. }
  555. }
  556. // if at least one answer
  557. if ($this->nbrAnswers) {
  558. // inserts new answers into data base
  559. $sql = "INSERT INTO $TBL_REPONSES (c_id, id,question_id,answer,correct,comment, ponderation,position,hotspot_coordinates,hotspot_type,destination) VALUES";
  560. $c_id = $course_info['real_id'];
  561. for ($i=1;$i <= $this->nbrAnswers;$i++) {
  562. if ($this->course['id'] != $course_info['id']) {
  563. $this->answer[$i] = DocumentManager::replace_urls_inside_content_html_from_copy_course($this->answer[$i],$this->course['id'], $course_info['id']) ;
  564. $this->comment[$i] = DocumentManager::replace_urls_inside_content_html_from_copy_course($this->comment[$i],$this->course['id'], $course_info['id']) ;
  565. }
  566. $answer = Database::escape_string($this->answer[$i]);
  567. $correct = Database::escape_string($this->correct[$i]);
  568. if (self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE || self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE ) {
  569. $correct = $fixed_list[intval($correct)];
  570. }
  571. $comment = Database::escape_string($this->comment[$i]);
  572. $weighting = Database::escape_string($this->weighting[$i]);
  573. $position = Database::escape_string($this->position[$i]);
  574. $hotspot_coordinates = Database::escape_string($this->hotspot_coordinates[$i]);
  575. $hotspot_type = Database::escape_string($this->hotspot_type[$i]);
  576. $destination = Database::escape_string($this->destination[$i]);
  577. $sql.="($c_id, '$i','$newQuestionId','$answer','$correct','$comment'," .
  578. "'$weighting','$position','$hotspot_coordinates','$hotspot_type','$destination'),";
  579. }
  580. $sql = api_substr($sql,0,-1);
  581. Database::query($sql);
  582. }
  583. }
  584. }