MultipleAnswerTrueFalseDegreeCertainty.php 48 KB

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