multipleAnswerTrueFalseDegreeCertainty.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class MultipleAnswerTrueFalseDegreeCertainty
  5. * This class allows to instantiate an object of type MULTIPLE_ANSWER
  6. * (MULTIPLE CHOICE, MULTIPLE ANSWER), extending the class question.
  7. *
  8. * @package chamilo.exercise
  9. */
  10. class MultipleAnswerTrueFalseDegreeCertainty extends Question
  11. {
  12. const LEVEL_DARKGREEN = 1;
  13. const LEVEL_LIGHTGREEN = 2;
  14. const LEVEL_WHITE = 3;
  15. const LEVEL_LIGHTRED = 4;
  16. const LEVEL_DARKRED = 5;
  17. public static $typePicture = 'mccert.png';
  18. public static $explanationLangVar = 'MultipleAnswerTrueFalseDegreeCertainty';
  19. public $optionsTitle;
  20. public $options;
  21. /**
  22. * Constructor.
  23. */
  24. public function __construct()
  25. {
  26. parent::__construct();
  27. $this->type = MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY;
  28. $this->isContent = $this->getIsContent();
  29. $this->optionsTitle = [1 => 'Answers', 2 => 'DegreeOfCertaintyThatMyAnswerIsCorrect'];
  30. $this->options = [
  31. 1 => 'True',
  32. 2 => 'False',
  33. 3 => '50%',
  34. 4 => '60%',
  35. 5 => '70%',
  36. 6 => '80%',
  37. 7 => '90%',
  38. 8 => '100%',
  39. ];
  40. }
  41. /**
  42. * Redefines Question::createAnswersForm: creates the HTML form to answer the question
  43. * @param FormValidator $form
  44. * @throws \Exception
  45. * @uses globals $text and $class, defined in the calling script
  46. */
  47. public function createAnswersForm($form)
  48. {
  49. global $text, $class;
  50. $nbAnswers = isset($_POST['nb_answers']) ? (int) $_POST['nb_answers'] : 4;
  51. // The previous default value was 2. See task #1759.
  52. $nbAnswers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
  53. $courseId = api_get_course_int_id();
  54. $objEx = $_SESSION['objExercise'];
  55. $renderer = &$form->defaultRenderer();
  56. $defaults = [];
  57. $html = '<table class="data_table"><tr style="text-align: center;"><th>'
  58. .get_lang('Number')
  59. .'</th><th>'
  60. .get_lang('True')
  61. .'</th><th>'
  62. .get_lang('False')
  63. .'</th><th>'
  64. .get_lang('Answer')
  65. .'</th>';
  66. // show column comment when feedback is enable
  67. if ($objEx->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
  68. $html .= '<th>'.get_lang('Comment').'</th>';
  69. }
  70. $html .= '</tr>';
  71. $form->addElement('label', get_lang('Answers').'<br /> <img src="../img/fill_field.png">', $html);
  72. $correct = 0;
  73. $answer = null;
  74. if (!empty($this->id)) {
  75. $answer = new Answer($this->id);
  76. $answer->read();
  77. if (count($answer->nbrAnswers) > 0 && !$form->isSubmitted()) {
  78. $nbAnswers = $answer->nbrAnswers;
  79. }
  80. }
  81. $form->addElement('hidden', 'nb_answers');
  82. $boxesNames = [];
  83. if ($nbAnswers < 1) {
  84. $nbAnswers = 1;
  85. echo Display::return_message(get_lang('YouHaveToCreateAtLeastOneAnswer'));
  86. }
  87. // Can be more options
  88. $optionData = Question::readQuestionOption($this->id, $courseId);
  89. for ($i = 1; $i <= $nbAnswers; ++$i) {
  90. $renderer->setElementTemplate(
  91. '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
  92. 'correct['.$i.']'
  93. );
  94. $renderer->setElementTemplate(
  95. '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
  96. 'counter['.$i.']'
  97. );
  98. $renderer->setElementTemplate(
  99. '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
  100. 'answer['.$i.']'
  101. );
  102. $renderer->setElementTemplate(
  103. '<td><!-- BEGIN error --><span class="form_error">{error}</span><!-- END error --><br/>{element}</td>',
  104. 'comment['.$i.']'
  105. );
  106. $answerNumber = $form->addElement('text', 'counter['.$i.']', null, 'value="'.$i.'"');
  107. $answerNumber->freeze();
  108. if (is_object($answer)) {
  109. $defaults['answer['.$i.']'] = $answer->answer[$i];
  110. $defaults['comment['.$i.']'] = $answer->comment[$i];
  111. $defaults['weighting['.$i.']'] = float_format($answer->weighting[$i], 1);
  112. $correct = $answer->correct[$i];
  113. $defaults['correct['.$i.']'] = $correct;
  114. $j = 1;
  115. if (!empty($optionData)) {
  116. foreach ($optionData as $id => $data) {
  117. $form->addElement('radio', 'correct['.$i.']', null, null, $id);
  118. $j++;
  119. if ($j == 3) {
  120. break;
  121. }
  122. }
  123. }
  124. } else {
  125. $form->addElement('radio', 'correct['.$i.']', null, null, 1);
  126. $form->addElement('radio', 'correct['.$i.']', null, null, 2);
  127. $defaults['answer['.$i.']'] = '';
  128. $defaults['comment['.$i.']'] = '';
  129. $defaults['correct['.$i.']'] = '';
  130. }
  131. $boxesNames[] = 'correct['.$i.']';
  132. $form->addElement(
  133. 'html_editor',
  134. 'answer['.$i.']',
  135. null,
  136. ['style' => 'vertical-align:middle;'],
  137. ['ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100']);
  138. $form->addRule('answer['.$i.']', get_lang('ThisFieldIsRequired'), 'required');
  139. // show comment when feedback is enable
  140. if ($objEx->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
  141. $form->addElement('html_editor',
  142. 'comment['.$i.']',
  143. null,
  144. ['style' => 'vertical-align:middle;'],
  145. ['ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100']);
  146. }
  147. $form->addElement('html', '</tr>');
  148. }
  149. $form->addElement('html', '</table>');
  150. $form->addElement('html', '<br />');
  151. // 3 scores
  152. $form->addElement('text', 'option[1]', get_lang('Correct'), ['class' => 'span1', 'value' => '1']);
  153. $form->addElement('text', 'option[2]', get_lang('Wrong'), ['class' => 'span1', 'value' => '-0.5']);
  154. $form->addElement('hidden', 'option[3]', 0);
  155. $form->addRule('option[1]', get_lang('ThisFieldIsRequired'), 'required');
  156. $form->addRule('option[2]', get_lang('ThisFieldIsRequired'), 'required');
  157. $form->addElement('html', '</tr><table>');
  158. $form->addElement('hidden', 'options_count', 3);
  159. $form->addElement('html', '</table><br /><br />');
  160. //Extra values True, false, Dont known
  161. if (!empty($this->extra)) {
  162. $scores = explode(':', $this->extra);
  163. if (!empty($scores)) {
  164. for ($i = 1; $i <= 3; $i++) {
  165. $defaults['option['.$i.']'] = $scores[$i - 1];
  166. }
  167. }
  168. }
  169. if ($objEx->edit_exercise_in_lp == true) {
  170. $form->addElement('submit', 'lessAnswers', get_lang('LessAnswer'), 'class="btn minus"');
  171. $form->addElement('submit', 'moreAnswers', get_lang('PlusAnswer'), 'class="btn plus"');
  172. //$text and $class defined in calling script
  173. $form->addElement('submit', 'submitQuestion', $text, 'class="'.$class.'"');
  174. }
  175. $renderer->setElementTemplate('{element}&nbsp;', 'lessAnswers');
  176. $renderer->setElementTemplate('{element}&nbsp;', 'submitQuestion');
  177. $renderer->setElementTemplate('{element}&nbsp;', 'moreAnswers');
  178. $form->addElement('html', '</div></div>');
  179. $defaults['correct'] = $correct;
  180. if (!empty($this->id)) {
  181. $form->setDefaults($defaults);
  182. } else {
  183. //if ($this -> isContent == 1) {
  184. $form->setDefaults($defaults);
  185. //}
  186. }
  187. $form->setConstants(['nb_answers' => $nbAnswers]);
  188. }
  189. /**
  190. * abstract function which creates the form to create / edit the answers of the question.
  191. *
  192. * @param FormValidator $form
  193. * @param Exercise $exercise
  194. */
  195. public function processAnswersCreation($form, $exercise)
  196. {
  197. $questionWeighting = $nbrGoodAnswers = 0;
  198. $objAnswer = new Answer($this->id);
  199. $nbAnswers = $form->getSubmitValue('nb_answers');
  200. $courseId = api_get_course_int_id();
  201. $correct = [];
  202. $options = Question::readQuestionOption($this->id, $courseId);
  203. if (!empty($options)) {
  204. foreach ($options as $optionData) {
  205. $id = $optionData['id'];
  206. unset($optionData['id']);
  207. Question::updateQuestionOption($id, $optionData, $courseId);
  208. }
  209. } else {
  210. for ($i = 1; $i <= 8; $i++) {
  211. $lastId = Question::saveQuestionOption($this->id, $this->options[$i], $courseId, $i);
  212. $correct[$i] = $lastId;
  213. }
  214. }
  215. /* Getting quiz_question_options (true, false, doubt) because
  216. it's possible that there are more options in the future */
  217. $newOptions = Question::readQuestionOption($this->id, $courseId);
  218. $sortedByPosition = [];
  219. foreach ($newOptions as $item) {
  220. $sortedByPosition[$item['position']] = $item;
  221. }
  222. /* Saving quiz_question.extra values that has the correct scores of
  223. the true, false, doubt options registered in this format
  224. XX:YY:ZZZ where XX is a float score value. */
  225. $extraValues = [];
  226. for ($i = 1; $i <= 3; $i++) {
  227. $score = trim($form->getSubmitValue('option['.$i.']'));
  228. $extraValues[] = $score;
  229. }
  230. $this->setExtra(implode(':', $extraValues));
  231. for ($i = 1; $i <= $nbAnswers; $i++) {
  232. $answer = trim($form->getSubmitValue('answer['.$i.']'));
  233. $comment = trim($form->getSubmitValue('comment['.$i.']'));
  234. $goodAnswer = trim($form->getSubmitValue('correct['.$i.']'));
  235. if (empty($options)) {
  236. //If this is the first time that the question is created when change the default values from the form 1 and 2 by the correct "option id" registered
  237. $goodAnswer = $sortedByPosition[$goodAnswer]['id'];
  238. }
  239. $questionWeighting += $extraValues[0]; //By default 0 has the correct answers
  240. $objAnswer->createAnswer($answer, $goodAnswer, $comment, '', $i);
  241. }
  242. // saves the answers into the data base
  243. $objAnswer->save();
  244. // sets the total weighting of the question
  245. $this->updateWeighting($questionWeighting);
  246. $this->save($exercise);
  247. }
  248. /**
  249. * Show result table headers
  250. * @param int $feedbackType
  251. * @param int $counter
  252. * @param float $score
  253. *
  254. * @return null|string
  255. */
  256. public function return_header($feedbackType = null, $counter = null, $score = null)
  257. {
  258. $header = parent::return_header($feedbackType, $counter, $score);
  259. $header .= '<table class="'
  260. .$this->question_table_class
  261. .'"><tr><th>'
  262. .get_lang("Choice")
  263. .'</th><th>'
  264. .get_lang("ExpectedChoice")
  265. .'</th><th>'
  266. .get_lang("Answer")
  267. .'</th><th>'
  268. .get_lang("YourDegreeOfCertainty")
  269. .'</th><th>&nbsp;</th>'
  270. ;
  271. if ($feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
  272. $header .= '<th>'.get_lang("Comment").'</th>';
  273. } else {
  274. $header .= '<th>&nbsp;</th>';
  275. }
  276. $header .= '</tr>';
  277. return $header;
  278. }
  279. /**
  280. * Method to recovery color to show by precision of the student's answer.
  281. *
  282. * @param $studentAnwser
  283. * @param $expectedAnswer
  284. * @param $studentDegreeChoicePosition
  285. *
  286. * @return string
  287. */
  288. public function getColorResponse($studentAnwser, $expectedAnswer, $studentDegreeChoicePosition)
  289. {
  290. $checkResult = ($studentAnwser == $expectedAnswer) ? true : false;
  291. if ($checkResult) {
  292. if ($studentDegreeChoicePosition >= 6) {
  293. return '#088A08';
  294. }
  295. if ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
  296. return '#A9F5A9';
  297. }
  298. if ($studentDegreeChoicePosition == 3) {
  299. return '#FFFFFF';
  300. }
  301. } else {
  302. if ($studentDegreeChoicePosition >= 6) {
  303. return '#FE2E2E';
  304. }
  305. if ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
  306. return '#F6CECE';
  307. }
  308. if ($studentDegreeChoicePosition == 3) {
  309. return '#FFFFFF';
  310. }
  311. }
  312. }
  313. /**
  314. * Return the color code for student answer.
  315. *
  316. * @param $studentAnwser
  317. * @param $expectedAnswer
  318. * @param $studentDegreeChoicePosition
  319. *
  320. * @return int
  321. */
  322. public function getStatusResponse($studentAnwser, $expectedAnswer, $studentDegreeChoicePosition)
  323. {
  324. $checkResult = ($studentAnwser == $expectedAnswer) ? true : false;
  325. if ($checkResult) {
  326. if ($studentDegreeChoicePosition >= 6) {
  327. return self::LEVEL_DARKGREEN;
  328. }
  329. if ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
  330. return self::LEVEL_LIGHTGREEN;
  331. }
  332. if ($studentDegreeChoicePosition == 3) {
  333. return self::LEVEL_WHITE;
  334. }
  335. } else {
  336. if ($studentDegreeChoicePosition >= 6) {
  337. return self::LEVEL_DARKRED;
  338. }
  339. if ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
  340. return self::LEVEL_LIGHTRED;
  341. }
  342. if ($studentDegreeChoicePosition == 3) {
  343. return self::LEVEL_WHITE;
  344. }
  345. }
  346. }
  347. /**
  348. * Method to recover labels for color codes.
  349. *
  350. * @param $studentAnswer
  351. * @param $expectedAnswer
  352. * @param $studentDegreeChoicePosition
  353. *
  354. * @return string
  355. */
  356. public function getCodeResponse($studentAnswer, $expectedAnswer, $studentDegreeChoicePosition)
  357. {
  358. $checkResult = ($studentAnswer == $expectedAnswer) ? true : false;
  359. if ($checkResult) {
  360. if ($studentDegreeChoicePosition >= 6) {
  361. return get_lang('DegreeOfCertaintyVerySure');
  362. }
  363. if ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
  364. return get_lang('DegreeOfCertaintyPrettySure');
  365. }
  366. if ($studentDegreeChoicePosition == 3) {
  367. return get_lang('DegreeOfCertaintyDeclaredIgnorance');
  368. }
  369. } else {
  370. if ($studentDegreeChoicePosition >= 6) {
  371. return get_lang('DegreeOfCertaintyVeryUnsure');
  372. }
  373. if ($studentDegreeChoicePosition >= 4 && $studentDegreeChoicePosition <= 5) {
  374. return get_lang('DegreeOfCertaintyUnsure');
  375. }
  376. if ($studentDegreeChoicePosition == 3) {
  377. return get_lang('DegreeOfCertaintyIgnorance');
  378. }
  379. }
  380. }
  381. /**
  382. * Method to show the code color and his meaning for the test result.
  383. */
  384. public static function showColorCode()
  385. {
  386. ?>
  387. <table class="fc-border-separate" cellspacing="0" style="width:600px;
  388. margin: auto; border: 3px solid #A39E9E;" >
  389. <tr style="border-bottom: 1px solid #A39E9E;">
  390. <td style="width:15%; height:30px; background-color: #088A08; border-right: 1px solid #A39E9E;">
  391. &nbsp;
  392. </td>
  393. <td style="padding-left:10px;">
  394. <b><?php echo get_lang('DegreeOfCertaintyVerySure'); ?> :</b>
  395. <?php echo get_lang('DegreeOfCertaintyVerySureDescription'); ?>
  396. </td>
  397. </tr>
  398. <tr style="border-bottom: 1px solid #A39E9E;">
  399. <td style="width:15%; height:30px; background-color: #A9F5A9; border-right: 1px solid #A39E9E;">
  400. &nbsp;
  401. </td>
  402. <td style="padding-left:10px;">
  403. <b><?php echo get_lang('DegreeOfCertaintyPrettySure'); ?> :</b>
  404. <?php echo get_lang('DegreeOfCertaintyPrettySureDescription'); ?>
  405. </td>
  406. </tr>
  407. <tr style="border: 1px solid #A39E9E;">
  408. <td style="width:15%; height:30px; background-color: #FFFFFF; border-right: 1px solid #A39E9E;">
  409. &nbsp;
  410. </td>
  411. <td style="padding-left:10px;">
  412. <b><?php echo get_lang('DegreeOfCertaintyDeclaredIgnorance'); ?> :</b>
  413. <?php echo get_lang('DegreeOfCertaintyDeclaredIgnoranceDescription'); ?>
  414. </td>
  415. </tr>
  416. <tr style="border: 1px solid #A39E9E;">
  417. <td style="width:15%; height:30px; background-color: #F6CECE; border-right: 1px solid #A39E9E;">
  418. &nbsp;
  419. </td>
  420. <td style="padding-left:10px;">
  421. <b><?php echo get_lang('DegreeOfCertaintyUnsure'); ?> :</b>
  422. <?php echo get_lang('DegreeOfCertaintyUnsureDescription'); ?>
  423. </td>
  424. </tr>
  425. <tr style="border-bottom: 1px solid #A39E9E;">
  426. <td style="width:15%; height:30px; background-color: #FE2E2E; border-right: 1px solid #A39E9E;">
  427. &nbsp;
  428. </td>
  429. <td style="padding-left:10px;">
  430. <b><?php echo get_lang('DegreeOfCertaintyVeryUnsure'); ?> :</b>
  431. <?php echo get_lang('DegreeOfCertaintyVeryUnsureDescription'); ?>
  432. </td>
  433. </tr>
  434. </table><br/>
  435. <?php
  436. }
  437. /**
  438. * Display basic bar charts of results by category of questions
  439. * @param array $scoreListAll
  440. * @param string $title The block title
  441. * @param int $sizeRatio
  442. * @param Exercise $objExercise
  443. * @return string The HTML/CSS code for the charts block
  444. */
  445. public static function displayDegreeChartByCategory($scoreListAll, $title, $sizeRatio = 1, $objExercise)
  446. {
  447. $maxHeight = 0;
  448. if ($objExercise->gather_questions_categories == 1) { // original
  449. $groupCategoriesByBracket = true;
  450. } else {
  451. $groupCategoriesByBracket = false;
  452. }
  453. if ($groupCategoriesByBracket) {
  454. $scoreList = [];
  455. $categoryPrefixList = []; // categoryPrefix['Math'] = firstCategoryId for this prefix
  456. // rebuild $scoreList factorizing data with category prefix
  457. foreach ($scoreListAll as $categoryId => $scoreListForCategory) {
  458. $objCategory = new Testcategory();
  459. $objCategoryNum = $objCategory->getCategory($categoryId);
  460. preg_match("/^\[([^]]+)\]/", $objCategoryNum->name, $matches);
  461. if (count($matches) > 1) {
  462. // check if we have already see this prefix
  463. if (array_key_exists($matches[1], $categoryPrefixList)) {
  464. // add the result color for this entry
  465. $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_DARKGREEN] +=
  466. $scoreListForCategory[self::LEVEL_DARKGREEN];
  467. $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_LIGHTGREEN] +=
  468. $scoreListForCategory[self::LEVEL_LIGHTGREEN];
  469. $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_WHITE] +=
  470. $scoreListForCategory[self::LEVEL_WHITE];
  471. $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_LIGHTRED] +=
  472. $scoreListForCategory[self::LEVEL_LIGHTRED];
  473. $scoreList[$categoryPrefixList[$matches[1]]][self::LEVEL_DARKRED] +=
  474. $scoreListForCategory[self::LEVEL_DARKRED];
  475. } else {
  476. $categoryPrefixList[$matches[1]] = $categoryId;
  477. $scoreList[$categoryId] = $scoreListAll[$categoryId];
  478. }
  479. } else {
  480. // doesn't match the prefix '[math] Math category'
  481. $scoreList[$categoryId] = $scoreListAll[$categoryId];
  482. }
  483. }
  484. } else {
  485. $scoreList = $scoreListAll;
  486. }
  487. // get the max height of item to have each table the same height if displayed side by side
  488. foreach ($scoreList as $categoryId => $scoreListForCategory) {
  489. $testCategory = new TestCategory();
  490. $categoryQuestionName = $testCategory->getCategory($categoryId)->name;
  491. list($null, $height) = self::displayDegreeChart(
  492. $scoreListForCategory,
  493. 300,
  494. '',
  495. 1,
  496. 0,
  497. false,
  498. true,
  499. groupCategoriesByBracket
  500. );
  501. if ($height > $maxHeight) {
  502. $maxHeight = $height;
  503. }
  504. }
  505. if (count($scoreList) > 1) {
  506. $boxWidth = $sizeRatio * 300 * 2 + 54;
  507. } else {
  508. $boxWidth = $sizeRatio * 300 + 54;
  509. }
  510. $html = '<div style="width: '.$boxWidth.'px; margin : auto; padding-left : 15px;">';
  511. $html .= '<h3 style="text-align: center; margin : 10px 0">'.$title.'</h3>';
  512. // get the html of items
  513. $i = 0;
  514. foreach ($scoreList as $categoryId => $scoreListForCategory) {
  515. $oCategory = new Testcategory();
  516. $categoryQuestionName = $oCategory->getCategory($categoryId)->name;
  517. if ($categoryQuestionName == '') {
  518. $categoryName = get_lang('WithoutCategory');
  519. } else {
  520. $categoryName = $oCategory->name;
  521. }
  522. $html .= '<div style="float:left; margin-right: 10px;">';
  523. $html .= self::displayDegreeChart(
  524. $scoreListForCategory,
  525. 300,
  526. $categoryName,
  527. 1,
  528. $maxHeight,
  529. false,
  530. false,
  531. $groupCategoriesByBracket
  532. );
  533. $html .= '</div>';
  534. if ($i == 1) {
  535. $html .= '<div style="clear:both">&nbsp;</div>';
  536. $i = 0;
  537. } else {
  538. $i++;
  539. }
  540. }
  541. return $html.'<div style="clear:both">&nbsp;</div></div>';
  542. }
  543. /**
  544. * Return HTML code for the $scoreList of MultipleAnswerTrueFalseDegreeCertainty questions.
  545. *
  546. * @param $scoreList
  547. * @param int $sizeRatio
  548. *
  549. * @return string
  550. */
  551. public static function displayDegreeChart(
  552. $scoreList,
  553. $widthTable,
  554. $title = '',
  555. $sizeRatio = 1,
  556. $minHeight = 0,
  557. $displayExplanationText = true,
  558. $returnHeight = false,
  559. $groupCategoriesByBracket = false,
  560. $numberOfQuestions = 0
  561. ) {
  562. $topAndBottomMargin = 10;
  563. $colorList = [self::LEVEL_DARKRED,
  564. self::LEVEL_LIGHTRED,
  565. self::LEVEL_WHITE,
  566. self::LEVEL_LIGHTGREEN,
  567. self::LEVEL_DARKGREEN,
  568. ];
  569. // get total attempt number
  570. $highterColorHeight = 0;
  571. foreach ($scoreList as $color => $number) {
  572. if ($number > $highterColorHeight) {
  573. $highterColorHeight = $number;
  574. }
  575. }
  576. $totalAttemptNumber = $numberOfQuestions;
  577. $verticalLineHeight = $highterColorHeight * $sizeRatio * 2 + 122 + $topAndBottomMargin * 2;
  578. if ($verticalLineHeight < $minHeight) {
  579. $minHeightCorrection = $minHeight - $verticalLineHeight;
  580. $verticalLineHeight += $minHeightCorrection;
  581. }
  582. // draw chart
  583. $html = '<style type="text/css">
  584. .certaintyQuizBox {
  585. border : 1px solid black;
  586. margin : auto;
  587. }
  588. .certaintyQuizColumn {
  589. float : left;
  590. }
  591. /* text at the top of the column */
  592. .certaintyQuizDivMiddle {
  593. height : 20px;
  594. margin : 0;
  595. padding : 0;
  596. text-align: center
  597. }
  598. .certaintyQuizDivBottom {
  599. border : 1px solid black;
  600. }
  601. .certaintyVerticalLine {
  602. float: left;
  603. border-left : 1px solid black;
  604. font-size: 0;
  605. }
  606. .certaintyQuizClearer {
  607. clear : both;
  608. font-size: 0;
  609. height:0
  610. }
  611. .certaintyQuizLevel_'.self::LEVEL_DARKGREEN.' {
  612. background-color: #088A08;
  613. }
  614. .certaintyQuizLevel_'.self::LEVEL_LIGHTGREEN.' {
  615. background-color: #A9F5A9;
  616. }
  617. .certaintyQuizLevel_'.self::LEVEL_WHITE.' {
  618. background-color: #FFFFFF;
  619. width:88%
  620. }
  621. .certaintyQuizLevel_'.self::LEVEL_LIGHTRED.' {
  622. background-color: #F6CECE;
  623. }
  624. .certaintyQuizLevel_'.self::LEVEL_DARKRED.' {
  625. background-color: #FE2E2E;
  626. }
  627. table.certaintyTable {
  628. margin : auto;
  629. border: 1px solid #999A9B;
  630. }
  631. table.certaintyTable th {
  632. text-align: center;
  633. border-bottom: 1px solid #999A9B;
  634. background-color: #cdd0d4;
  635. padding : 10px;
  636. }
  637. table.certaintyTable td {
  638. padding : 10px;
  639. }
  640. table.certaintyTable td.borderRight {
  641. border-right: 1px dotted #000000;
  642. }
  643. table.certaintyTable td.firstLine {
  644. vertical-align: top;
  645. text-align: center;
  646. }
  647. table.certaintyTable th.globalChart {
  648. font-size : 18pt;
  649. line-height : 120%;
  650. padding : 20px;
  651. }
  652. table.certaintyTable td.globalChart {
  653. font-weight : bold;
  654. }
  655. </style>';
  656. if ($groupCategoriesByBracket) {
  657. $title = api_preg_replace("/[^]]*$/", "", $title);
  658. $title = ucfirst(api_preg_replace("/[\[\]]/", "", $title));
  659. }
  660. $display = (strpos($title, "ensemble") > 0) ?
  661. $title."<br/>($totalAttemptNumber questions)" :
  662. $title;
  663. $textSize = (
  664. strpos($title, "ensemble") > 0 ||
  665. strpos($title, "votre dernier résultat à ce test") > 0
  666. ) ? 100 : 80;
  667. if ($displayExplanationText) {
  668. // global chart
  669. $classGlobalChart = "globalChart";
  670. } else {
  671. $classGlobalChart = "";
  672. }
  673. $html .= '<table class="certaintyTable" style="height : '
  674. .$verticalLineHeight
  675. .'px; width : '
  676. .$widthTable.'px;">'
  677. ;
  678. $html .= '<tr><th colspan="5" class="'
  679. .$classGlobalChart
  680. .'">'
  681. .$display
  682. .'</th><tr>'
  683. ;
  684. $nbResponsesInc = (isset($scoreList[4]) || isset($scoreList[5])) ? $scoreList[4] + $scoreList[5] : 0;
  685. $nbResponsesIng = (isset($scoreList[3])) ? $scoreList[3] : 0;
  686. $nbResponsesCor = (isset($scoreList[1]) || isset($scoreList[2])) ? $scoreList[1] + $scoreList[2] : 0;
  687. $colWidth = $widthTable / 5;
  688. $html .= '<tr>
  689. <td class="firstLine borderRight '.$classGlobalChart.'"
  690. colspan="2"
  691. style="width:'.($colWidth * 2).'px; font-size:'.$textSize.'%;">'.
  692. sprintf(get_lang('IncorrectAnswersX'), $nbResponsesInc).'
  693. </td>
  694. <td class="firstLine borderRight '.$classGlobalChart.'"
  695. style="width:'.$colWidth.'px; font-size :'.$textSize.'%;">'.
  696. sprintf(get_lang('IgnoranceAnswersX'), $nbResponsesIng).'
  697. </td>
  698. <td class="firstLine '.$classGlobalChart.'"
  699. colspan="2"
  700. style="width:'.($colWidth * 2).'px; font-size:'.$textSize.'%;">'.
  701. sprintf(get_lang('CorrectAnswersX'), $nbResponsesCor).'
  702. </td>
  703. </tr>';
  704. $html .= '<tr>';
  705. foreach ($colorList as $i => $color) {
  706. if (array_key_exists($color, $scoreList)) {
  707. $scoreOnBottom = $scoreList[$color]; // height of the colored area on the bottom
  708. } else {
  709. $scoreOnBottom = 0;
  710. }
  711. $sizeOnBottom = $scoreOnBottom * $sizeRatio * 2;
  712. if ($i == 1 || $i == 2) {
  713. $html .= '<td width="'
  714. .$colWidth
  715. .'px" style="border-right: 1px dotted #000000; vertical-align: bottom;font-size: '
  716. .$textSize
  717. .'%;">'
  718. ;
  719. } else {
  720. $html .= '<td width="'
  721. .$colWidth
  722. .'px" style="vertical-align: bottom;font-size: '
  723. .$textSize
  724. .'%;">'
  725. ;
  726. }
  727. $html .= '<div class="certaintyQuizDivMiddle">'
  728. .$scoreOnBottom
  729. .'</div><div class="certaintyQuizDivBottom certaintyQuizLevel_'
  730. .$color
  731. .'" style="height: '
  732. .$sizeOnBottom
  733. .'px;">&nbsp;</div>'
  734. ;
  735. $html .= '</td>';
  736. }
  737. $html .= '</tr>';
  738. if ($displayExplanationText) {
  739. // Display of histogram text
  740. $explainHistoList = [
  741. 'DegreeOfCertaintyVeryUnsure',
  742. 'DegreeOfCertaintyUnsure',
  743. 'DegreeOfCertaintyIgnorance',
  744. 'DegreeOfCertaintyPrettySure',
  745. 'DegreeOfCertaintyVerySure', ];
  746. $html .= '<tr>';
  747. $i = 0;
  748. foreach ($explainHistoList as $explain) {
  749. if ($i == 1 || $i == 2) {
  750. $class = "borderRight";
  751. } else {
  752. $class = "";
  753. }
  754. $html .= '<td class="firstLine '
  755. .$class
  756. .' '
  757. .$classGlobalChart
  758. .'" style="width="'
  759. .$colWidth
  760. .'px; font-size:'
  761. .$textSize
  762. .'%;">'
  763. ;
  764. $html .= get_lang($explain);
  765. $html .= '</td>';
  766. $i++;
  767. }
  768. $html .= '</tr>';
  769. }
  770. $html .= '</table></center>';
  771. if ($returnHeight) {
  772. return [$html, $verticalLineHeight];
  773. } else {
  774. return $html;
  775. }
  776. }
  777. /**
  778. * return previous attempt id for this test for student, 0 if no previous attempt.
  779. *
  780. * @param $exeId
  781. *
  782. * @return int
  783. */
  784. public static function getPreviousAttemptId($exeId)
  785. {
  786. $tblTrackEExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  787. $sql = "SELECT *
  788. FROM $tblTrackEExercise
  789. WHERE exe_id = ".intval($exeId);
  790. $res = Database::query($sql);
  791. if (Database::num_rows($res) == 0) {
  792. // if we cannot find the exe_id
  793. return 0;
  794. }
  795. $data = Database::fetch_assoc($res);
  796. $courseCode = $data['c_id'];
  797. $exerciseId = $data['exe_exo_id'];
  798. $userId = $data['exe_user_id'];
  799. $attemptDate = $data['exe_date'];
  800. if ($attemptDate == "0000-00-00 00:00:00") {
  801. // incomplete attempt, close it before continue
  802. return 0;
  803. }
  804. // look for previous attempt
  805. $exerciseId = (int) $exerciseId;
  806. $userId = (int) $userId;
  807. $sql = "SELECT *
  808. FROM $tblTrackEExercise
  809. WHERE c_id = '$courseCode'
  810. AND exe_exo_id = $exerciseId
  811. AND exe_user_id = $userId
  812. AND status = ''
  813. AND exe_date > '0000-00-00 00:00:00'
  814. AND exe_date < '$attemptDate'
  815. ORDER BY exe_date DESC";
  816. $res = Database::query($sql);
  817. if (Database::num_rows($res) == 0) {
  818. // no previous attempt
  819. return 0;
  820. }
  821. $data = Database::fetch_assoc($res);
  822. return $data['exe_id'];
  823. }
  824. /**
  825. * return an array of number of answer color for exe attempt
  826. * for question type = MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
  827. * e.g.
  828. * [LEVEL_DARKGREEN => 3, LEVEL_LIGHTGREEN => 0, LEVEL_WHITE => 5, LEVEL_LIGHTRED => 12, LEVEL_DARKTRED => 0].
  829. *
  830. * @param $exeId
  831. *
  832. * @return array
  833. */
  834. public static function getColorNumberListForAttempt($exeId)
  835. {
  836. $result = [self::LEVEL_DARKGREEN => 0,
  837. self::LEVEL_LIGHTGREEN => 0,
  838. self::LEVEL_WHITE => 0,
  839. self::LEVEL_LIGHTRED => 0,
  840. self::LEVEL_DARKRED => 0,
  841. ];
  842. $attemptInfoList = self::getExerciseAttemptInfo($exeId);
  843. foreach ($attemptInfoList as $i => $attemptInfo) {
  844. $oQuestion = new MultipleAnswerTrueFalseDegreeCertainty();
  845. $oQuestion->read($attemptInfo['question_id']);
  846. if ($oQuestion->type == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
  847. $answerColor = self::getAnswerColor($exeId, $attemptInfo['question_id'], $attemptInfo['position']);
  848. if ($answerColor) {
  849. $result[$answerColor]++;
  850. }
  851. }
  852. }
  853. return $result;
  854. }
  855. /**
  856. * return an array of number of color for question type = MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
  857. * for each question category.
  858. *
  859. * e.g.
  860. * [
  861. * (categoryId=)5 => [LEVEL_DARKGREEN => 3, LEVEL_WHITE => 5, LEVEL_LIGHTRED => 12]
  862. * (categoryId=)2 => [LEVEL_DARKGREEN => 8, LEVEL_LIGHTRED => 2, LEVEL_DARKTRED => 8]
  863. * (categoryId=)0 => [LEVEL_DARKGREEN => 1, LEVEL_LIGHTGREEN => 2, LEVEL_WHITE => 6, LEVEL_LIGHTRED => 1, LEVEL_DARKTRED => 9]
  864. * ]
  865. *
  866. * @param $exeId
  867. *
  868. * @return array
  869. */
  870. public static function getColorNumberListForAttemptByCategory($exeId)
  871. {
  872. $result = [];
  873. $attemptInfoList = self::getExerciseAttemptInfo($exeId);
  874. foreach ($attemptInfoList as $i => $attemptInfo) {
  875. $oQuestion = new MultipleAnswerTrueFalseDegreeCertainty();
  876. $oQuestion->read($attemptInfo['question_id']);
  877. if ($oQuestion->type == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
  878. $questionCategory = Testcategory::getCategoryForQuestion($attemptInfo['question_id']);
  879. if (!array_key_exists($questionCategory, $result)) {
  880. $result[$questionCategory] = [];
  881. }
  882. $answerColor = self::getAnswerColor($exeId, $attemptInfo['question_id'], $attemptInfo['position']);
  883. if ($answerColor) {
  884. $result[$questionCategory][$answerColor]++;
  885. }
  886. }
  887. }
  888. return $result;
  889. }
  890. /**
  891. * Return true if answer of $exeId, $questionId, $position is correct, otherwise return false.
  892. *
  893. * @param $exeId
  894. * @param $questionId
  895. * @param $position
  896. *
  897. * @return bool
  898. */
  899. public static function getAnswerColor($exeId, $questionId, $position)
  900. {
  901. $attemptInfoList = self::getExerciseAttemptInfo($exeId, $questionId, $position);
  902. if (count($attemptInfoList) != 1) {
  903. // havent got the answer
  904. return 0;
  905. }
  906. $answerCodes = $attemptInfoList[0]['answer'];
  907. // student answer
  908. $splitAnswer = preg_split("/:/", $answerCodes);
  909. // get correct answer option id
  910. $correctAnswerOptionId = self::getCorrectAnswerOptionId($splitAnswer[0]);
  911. if ($correctAnswerOptionId == 0) {
  912. // error returning the correct answer option id
  913. return 0;
  914. }
  915. // get student answer option id
  916. $studentAnswerOptionId = $splitAnswer[1];
  917. // we got the correct answer option id, let's compare ti with the student answer
  918. $percentage = self::getPercentagePosition($splitAnswer[2]);
  919. if ($studentAnswerOptionId == $correctAnswerOptionId) {
  920. // yeah, student got correct answer
  921. switch ($percentage) {
  922. case 3:
  923. return self::LEVEL_WHITE;
  924. case 4:
  925. case 5:
  926. return self::LEVEL_LIGHTGREEN;
  927. case 6:
  928. case 7:
  929. case 8:
  930. return self::LEVEL_DARKGREEN;
  931. default:
  932. return 0;
  933. }
  934. } else {
  935. // bummer, wrong answer dude
  936. switch ($percentage) {
  937. case 3:
  938. return self::LEVEL_WHITE;
  939. case 4:
  940. case 5:
  941. return self::LEVEL_LIGHTRED;
  942. case 6:
  943. case 7:
  944. case 8:
  945. return self::LEVEL_DARKRED;
  946. default:
  947. return 0;
  948. }
  949. }
  950. }
  951. /**
  952. * Return the position of certitude %age choose by student.
  953. *
  954. * @param $optionId
  955. *
  956. * @return int
  957. */
  958. public static function getPercentagePosition($optionId)
  959. {
  960. $tblAnswerOption = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  961. $courseId = api_get_course_int_id();
  962. $sql = "SELECT position
  963. FROM $tblAnswerOption
  964. WHERE c_id = ".intval($courseId)."
  965. AND id = ".intval($optionId);
  966. $res = Database::query($sql);
  967. if (Database::num_rows($res) == 0) {
  968. return 0;
  969. }
  970. $data = Database::fetch_assoc($res);
  971. return $data['position'];
  972. }
  973. /**
  974. * return the correct id from c_quiz_question_option for question idAuto.
  975. *
  976. * @param $idAuto
  977. *
  978. * @return int
  979. */
  980. public static function getCorrectAnswerOptionId($idAuto)
  981. {
  982. $tblAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER);
  983. $courseId = api_get_course_int_id();
  984. $sql = "SELECT correct
  985. FROM $tblAnswer
  986. WHERE c_id = ".intval($courseId)."
  987. AND id_auto = ".intval($idAuto);
  988. $res = Database::query($sql);
  989. $data = Database::fetch_assoc($res);
  990. if (Database::num_rows($res) > 0) {
  991. return $data['correct'];
  992. } else {
  993. return 0;
  994. }
  995. }
  996. /**
  997. * return an array of exe info from track_e_attempt.
  998. *
  999. * @param $exeId
  1000. * @param int $questionId
  1001. * @param int $position
  1002. *
  1003. * @return array
  1004. */
  1005. public static function getExerciseAttemptInfo($exeId, $questionId = -1, $position = -1)
  1006. {
  1007. $result = [];
  1008. $and = '';
  1009. if ($questionId >= 0) {
  1010. $and .= " AND question_id = ".intval($questionId);
  1011. }
  1012. if ($position >= 0) {
  1013. $and .= " AND position = ".intval($position);
  1014. }
  1015. $tblExeAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
  1016. $cId = api_get_course_int_id();
  1017. $sql = "SELECT * FROM $tblExeAttempt
  1018. WHERE c_id = $cId
  1019. AND exe_id = $exeId
  1020. $and";
  1021. $res = Database::query($sql);
  1022. while ($data = Database::fetch_assoc($res)) {
  1023. $result[] = $data;
  1024. }
  1025. return $result;
  1026. }
  1027. public static function getNumberOfQuestionsForExeId($exeId)
  1028. {
  1029. $tableTrackEExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  1030. $sql = "SELECT exe_exo_id
  1031. FROM $tableTrackEExercise
  1032. WHERE exe_id=".intval($exeId);
  1033. $res = Database::query($sql);
  1034. $data = Database::fetch_assoc($res);
  1035. $exerciseId = $data['exe_exo_id'];
  1036. $objectExercise = new Exercise();
  1037. $objectExercise->read($exerciseId);
  1038. return $objectExercise->get_count_question_list();
  1039. }
  1040. /**
  1041. * Display student chart results for these question types.
  1042. *
  1043. * @param $exeId
  1044. *
  1045. * @return string
  1046. */
  1047. public static function displayStudentsChartResults($exeId, $objExercice)
  1048. {
  1049. $numberOfQuestions = self::getNumberOfQuestionsForExeId($exeId);
  1050. $globalScoreList = MultipleAnswerTrueFalseDegreeCertainty::getColorNumberListForAttempt($exeId);
  1051. $html = MultipleAnswerTrueFalseDegreeCertainty::displayDegreeChart(
  1052. $globalScoreList,
  1053. 600,
  1054. get_lang('YourOverallResultForTheTest'),
  1055. 2,
  1056. 0,
  1057. true,
  1058. false,
  1059. false,
  1060. $numberOfQuestions
  1061. )
  1062. ."<br/>"
  1063. ;
  1064. $previousAttemptId = MultipleAnswerTrueFalseDegreeCertainty::getPreviousAttemptId($exeId);
  1065. if ($previousAttemptId > 0) {
  1066. $previousAttemptScoreList = MultipleAnswerTrueFalseDegreeCertainty::getColorNumberListForAttempt(
  1067. $previousAttemptId
  1068. );
  1069. $html .= MultipleAnswerTrueFalseDegreeCertainty::displayDegreeChart(
  1070. $previousAttemptScoreList,
  1071. 600,
  1072. get_lang('ForComparisonYourLastResultToThisTest'),
  1073. 2
  1074. )
  1075. ."<br/>"
  1076. ;
  1077. }
  1078. $categoryScoreList = MultipleAnswerTrueFalseDegreeCertainty::getColorNumberListForAttemptByCategory($exeId);
  1079. $html .= MultipleAnswerTrueFalseDegreeCertainty::displayDegreeChartByCategory(
  1080. $categoryScoreList,
  1081. get_lang('YourResultsByDiscipline'),
  1082. 1, $objExercice
  1083. )
  1084. ."<br/>"
  1085. ;
  1086. return $html;
  1087. }
  1088. }