Browse Source

Minor - partial merge from 1.11.x

Julio Montoya 6 years ago
parent
commit
fc7cbbe51d
41 changed files with 814 additions and 594 deletions
  1. 2 2
      main/create_course/add_course.php
  2. 7 13
      main/document/create_draw.php
  3. 22 24
      main/document/document.php
  4. 2 2
      main/document/document_quota.php
  5. 1 0
      main/document/download.php
  6. 1 0
      main/document/download_scorm.php
  7. 1 0
      main/document/record_audio.php
  8. 2 1
      main/exercise/Annotation.php
  9. 1 1
      main/exercise/Draggable.php
  10. 2 2
      main/exercise/TestCategory.php
  11. 0 14
      main/exercise/admin.php
  12. 8 6
      main/exercise/answer.class.php
  13. 233 126
      main/exercise/exercise.class.php
  14. 10 7
      main/exercise/exercise.php
  15. 1 2
      main/exercise/exercise_history.php
  16. 6 3
      main/exercise/exercise_report.php
  17. 80 47
      main/exercise/exercise_show.php
  18. 67 35
      main/exercise/exercise_submit.php
  19. 7 1
      main/exercise/export/qti2/qti2_classes.php
  20. 2 2
      main/exercise/export/qti2/qti2_export.php
  21. 46 42
      main/exercise/fill_blanks.class.php
  22. 2 1
      main/exercise/hotpotatoes.php
  23. 9 0
      main/exercise/hotspot_answers.as.php
  24. 25 15
      main/exercise/question.class.php
  25. 2 2
      main/exercise/question_admin.inc.php
  26. 9 10
      main/exercise/question_list_admin.inc.php
  27. 0 3
      main/forum/editthread.php
  28. 8 7
      main/forum/forumfunction.inc.php
  29. 0 2
      main/forum/forumqualify.php
  30. 1 1
      main/forum/index.php
  31. 3 3
      main/forum/viewforumcategory.php
  32. 16 10
      main/gradebook/gradebook_display_certificate.php
  33. 1 2
      main/gradebook/gradebook_display_summary.php
  34. 65 68
      main/gradebook/index.php
  35. 6 0
      main/gradebook/lib/be/abstractlink.class.php
  36. 7 7
      main/gradebook/lib/be/attendancelink.class.php
  37. 26 11
      main/gradebook/lib/be/exerciselink.class.php
  38. 12 1
      main/gradebook/lib/fe/displaygradebook.php
  39. 2 0
      main/gradebook/lib/fe/flatviewtable.class.php
  40. 97 95
      main/gradebook/lib/fe/gradebooktable.class.php
  41. 22 26
      main/gradebook/lib/gradebook_data_generator.class.php

+ 2 - 2
main/create_course/add_course.php

@@ -180,9 +180,9 @@ if ($course_validation_feature) {
 
 // Course language.
 $languages = api_get_languages();
-if (count($languages) === 1) {
+if (count($languages['name']) === 1) {
     // If there's only one language available, there's no point in asking
-    $form->addElement('hidden', 'course_language', $languages[0]);
+    $form->addElement('hidden', 'course_language', $languages['folder'][0]);
 } else {
     $form->addSelectLanguage(
         'course_language',

+ 7 - 13
main/document/create_draw.php

@@ -46,9 +46,7 @@ if (empty($document_data)) {
 $document_id = $document_data['id'];
 $dir = $document_data['path'];
 
-/*	Constants and variables */
-
-//path for svg-edit save
+// path for svg-edit save
 Session::write('draw_dir', Security::remove_XSS($dir));
 if ($dir == '/') {
     Session::write('draw_dir', '');
@@ -58,7 +56,6 @@ $dir = isset($dir) ? Security::remove_XSS($dir) : (isset($_POST['dir']) ? Securi
 $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
 
 // Please, do not modify this dirname formatting
-
 if (strstr($dir, '..')) {
     $dir = '/';
 }
@@ -76,14 +73,12 @@ if ($dir[strlen($dir) - 1] != '/') {
 }
 
 $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
-
 if (!is_dir($filepath)) {
     $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
     $dir = '/';
 }
 
 $groupId = api_get_group_id();
-
 if (!empty($groupId)) {
     $interbreadcrumb[] = [
         "url" => "../group/group_space.php?".api_get_cidreq(),
@@ -115,7 +110,6 @@ if (!($is_allowed_to_edit || $groupRights ||
     api_not_allowed(true);
 }
 
-/*	Header */
 Event::event_access_tool(TOOL_DOCUMENT);
 $display_dir = $dir;
 if (isset($group)) {
@@ -149,17 +143,17 @@ echo '<a href="document.php?id='.$document_id.'">'.
 echo '</div>';
 
 if (api_browser_support('svg')) {
-    //automatic loading the course language
-    $svgedit_code_translation_table = ['' => 'en', 'pt' => 'pt-Pt', 'sr' => 'sr_latn'];
+    // Automatic loading the course language
+    $translationList = ['' => 'en', 'pt' => 'pt-Pt', 'sr' => 'sr_latn'];
     $langsvgedit = api_get_language_isocode();
-    $langsvgedit = isset($svgedit_code_translation_table[$langsvgedit]) ? $svgedit_code_translation_table[$langsvgedit] : $langsvgedit;
+    $langsvgedit = isset($translationList[$langsvgedit]) ? $translationList[$langsvgedit] : $langsvgedit;
     $langsvgedit = file_exists(api_get_path(LIBRARY_PATH).'javascript/svgedit/locale/lang.'.$langsvgedit.'.js') ? $langsvgedit : 'en';
-    $svg_url = api_get_path(WEB_LIBRARY_PATH).'javascript/svgedit/svg-editor.php?lang='.$langsvgedit; ?>
+    $svg_url = api_get_path(WEB_LIBRARY_PATH).'javascript/svgedit/svg-editor.php?'.api_get_cidreq().'&lang='.$langsvgedit; ?>
     <script>
-        document.write ('<iframe id="frame" frameborder="0" scrolling="no" src="<?php echo  $svg_url; ?>" width="100%" height="100%"><noframes><p>Sorry, your browser does not handle frames</p></noframes></iframe>');
+        document.write('<iframe id="frame" frameborder="0" scrolling="no" src="<?php echo $svg_url; ?>" width="100%" height="100%"><noframes><p>Sorry, your browser does not handle frames</p></noframes></iframe>');
         function resizeIframe() {
             var height = window.innerHeight -50;
-            //max lower size
+            // max lower size
             if (height<550) {
                 height=550;
             }

+ 22 - 24
main/document/document.php

@@ -744,8 +744,8 @@ if (isset($_GET['curdirpath']) && $_GET['curdirpath'] == '/certificates' &&
 
         // Remove media=screen to be available when printing a document
         $new_content_html = str_replace(
-            api_get_path(WEB_CSS_PATH).'editor.css" media="screen"',
-            api_get_path(WEB_CSS_PATH).'editor.css" ',
+            ' media="screen"',
+            '',
             $new_content_html
         );
 
@@ -2162,31 +2162,30 @@ $table->display();
 $ajaxURL = api_get_path(WEB_AJAX_PATH).'document.ajax.php?a=get_document_quota&'.api_get_cidreq();
 
 if (count($documentAndFolders) > 1) {
-    if ($isAllowedToEdit || $groupMemberWithUploadRights) {
-        echo '<script>
-        $(document).ready(function() {
+    echo '<script>
+    $(document).ready(function() {
+        $.ajax({
+            url:"'.$ajaxURL.'",
+            success:function(data){
+                $("#course_quota").html(data);
+            }
+        });
+        
+        $(".document_size").each(function(i, obj) {
+            var path = obj.getAttribute("data-path");
+                            
             $.ajax({
-                url:"'.$ajaxURL.'",
+                url:"'.$getSizeURL.'&path="+path,
                 success:function(data){
-                    $("#course_quota").html(data);
+                    $(obj).html(data);
                 }
-            });
-            
-            $(".document_size").each(function(i, obj) {
-                var path = obj.getAttribute("data-path");
-                                
-                $.ajax({
-                    url:"'.$getSizeURL.'&path="+path,
-                    success:function(data){
-                        $(obj).html(data);
-                    }
-                });            
-            });    
-        });
-        </script>';
-        echo '<span id="course_quota"></span>';
-    }
+            });            
+        });    
+    });
+    </script>';
+    echo '<span id="course_quota"></span>';
 }
+
 if (!empty($table_footer)) {
     echo Display::return_message($table_footer, 'warning');
 }
@@ -2232,5 +2231,4 @@ echo '
     </div>
 ';
 
-// Footer
 Display::display_footer();

+ 2 - 2
main/document/document_quota.php

@@ -47,7 +47,7 @@ $used_quota_bytes = $quota_bytes;
 if (!empty($session_list)) {
     foreach ($session_list as $session_data) {
         $quota_percentage = 0;
-        $quota_bytes = intval(DocumentManager::documents_total_space($course_id, null, $session_data['id']));
+        $quota_bytes = DocumentManager::documents_total_space($course_id, null, $session_data['id']);
         if (!empty($quota_bytes)) {
             $quota_percentage = round($quota_bytes / $total_quota_bytes, 2) * 100;
         }
@@ -67,7 +67,7 @@ if (!empty($group_list)) {
     foreach ($group_list as $group_data) {
         $quota_percentage = 0;
         $my_group_id = $group_data['id'];
-        $quota_bytes = intval(DocumentManager::documents_total_space($course_id, $my_group_id, 0));
+        $quota_bytes = DocumentManager::documents_total_space($course_id, $my_group_id, 0);
         if (!empty($quota_bytes)) {
             $quota_percentage = round($quota_bytes / $total_quota_bytes, 2) * 100;
         }

+ 1 - 0
main/document/download.php

@@ -103,6 +103,7 @@ if (Security::check_abs_path($sys_course_path.$doc_url, $sys_course_path.'/')) {
     // Launch event
     Event::event_download($doc_url);
     $download = !empty($_GET['dl']) ? true : false;
+
     $result = DocumentManager::file_send_for_download($fullFileName, $download);
     if ($result === false) {
         api_not_allowed(true);

+ 1 - 0
main/document/download_scorm.php

@@ -21,6 +21,7 @@ if (!isset($_course)) {
     api_not_allowed(true);
 }
 
+/** @var learnpath $obj */
 $obj = Session::read('oLP');
 // If LP obj exists
 if (empty($obj)) {

+ 1 - 0
main/document/record_audio.php

@@ -153,6 +153,7 @@ $actions = Display::toolbarButton(
 $template = new Template($nameTools);
 $template->assign('directory', $wamidir);
 $template->assign('user_id', api_get_user_id());
+$template->assign('reload_page', 1);
 
 $layout = $template->get_template('document/record_audio.tpl');
 $content = $template->fetch($layout);

+ 2 - 1
main/exercise/Annotation.php

@@ -50,8 +50,9 @@ class Annotation extends Question
             }
         }
 
+        global $text;
         if (isset($_GET['editQuestion'])) {
-            $form->addButtonUpdate(get_lang('ModifyExercise'), 'submitQuestion');
+            $form->addButtonUpdate($text, 'submitQuestion');
 
             return;
         }

+ 1 - 1
main/exercise/Draggable.php

@@ -88,7 +88,7 @@ class Draggable extends Question
 
         $form->addRadio(
             'orientation',
-            get_lang('Orientation'),
+            get_lang('ChooseOrientation'),
             ['h' => get_lang('Horizontal'), 'v' => get_lang('Vertical')]
         );
 

+ 2 - 2
main/exercise/TestCategory.php

@@ -35,7 +35,7 @@ class TestCategory
     public function getCategory($id, $courseId = 0)
     {
         $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
-        $id = intval($id);
+        $id = (int) $id;
         $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
         $sql = "SELECT * FROM $table
                 WHERE id = $id AND c_id = ".$courseId;
@@ -801,7 +801,7 @@ class TestCategory
         }
         $category_name_list = self::getListOfCategoriesNameForTest($exerciseId);
 
-        $table = new HTML_Table(['class' => 'data_table']);
+        $table = new HTML_Table(['class' => 'table table-bordered']);
         $table->setHeaderContents(0, 0, get_lang('Categories'));
         $table->setHeaderContents(0, 1, get_lang('AbsoluteScore'));
         $table->setHeaderContents(0, 2, get_lang('RelativeScore'));

+ 0 - 14
main/exercise/admin.php

@@ -60,20 +60,6 @@ if (!$is_allowedToEdit) {
     api_not_allowed(true);
 }
 
-/*  stripslashes POST data  */
-if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-    foreach ($_POST as $key => $val) {
-        if (is_string($val)) {
-            $_POST[$key] = stripslashes($val);
-        } elseif (is_array($val)) {
-            foreach ($val as $key2 => $val2) {
-                $_POST[$key][$key2] = stripslashes($val2);
-            }
-        }
-        $GLOBALS[$key] = $_POST[$key];
-    }
-}
-
 if (empty($exerciseId)) {
     $exerciseId = isset($_GET['exerciseId']) ? intval($_GET['exerciseId']) : '0';
 }

+ 8 - 6
main/exercise/answer.class.php

@@ -55,7 +55,7 @@ class Answer
      */
     public function __construct($questionId, $course_id = 0, $exercise = null)
     {
-        $this->questionId = intval($questionId);
+        $this->questionId = (int) $questionId;
         $this->answer = [];
         $this->correct = [];
         $this->comment = [];
@@ -220,7 +220,7 @@ class Answer
 
         $TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
         $TBL_QUIZ = Database::get_course_table(TABLE_QUIZ_QUESTION);
-        $questionId = intval($this->questionId);
+        $questionId = (int) $this->questionId;
 
         $sql = "SELECT type FROM $TBL_QUIZ
                 WHERE c_id = {$this->course_id} AND id = $questionId";
@@ -365,7 +365,7 @@ class Answer
     public function selectAnswerByAutoId($auto_id)
     {
         $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
-        $auto_id = intval($auto_id);
+        $auto_id = (int) $auto_id;
         $sql = "SELECT id, answer, id_auto FROM $table
                 WHERE c_id = {$this->course_id} AND id_auto='$auto_id'";
         $rs = Database::query($sql);
@@ -483,7 +483,7 @@ class Answer
         }
         $row = Database::fetch_array($res);
 
-        return $row['type'];
+        return (int) $row['type'];
     }
 
     /**
@@ -845,7 +845,8 @@ class Answer
         $tableAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER);
 
         if (self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE ||
-            self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE
+            self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE ||
+            self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
         ) {
             // Selecting origin options
             $origin_options = Question::readQuestionOption(
@@ -964,7 +965,8 @@ class Answer
 
                     $correct = $this->correct[$i];
                     if ($newQuestion->type == MULTIPLE_ANSWER_TRUE_FALSE ||
-                        $newQuestion->type == MULTIPLE_ANSWER_TRUE_FALSE
+                        $newQuestion->type == MULTIPLE_ANSWER_TRUE_FALSE ||
+                        $newQuestion->type == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
                     ) {
                         $correct = $fixed_list[intval($correct)];
                     }

+ 233 - 126
main/exercise/exercise.class.php

@@ -2834,7 +2834,7 @@ class Exercise
 
         // if we want to delete results from LP too
         if ($cleanLpTests) {
-            $sql_where = "";
+            $sql_where = '';
         }
 
         // if we want to delete attempts before date $cleanResultBeforeDate
@@ -2850,12 +2850,12 @@ class Exercise
         }
 
         $sql = "SELECT exe_id
-                FROM $table_track_e_exercises
-                WHERE
-                    c_id = ".api_get_course_int_id()." AND
-                    exe_exo_id = ".$this->id." AND
-                    session_id = ".api_get_session_id()." ".
-                    $sql_where;
+            FROM $table_track_e_exercises
+            WHERE
+                c_id = ".api_get_course_int_id()." AND
+                exe_exo_id = ".$this->id." AND
+                session_id = ".api_get_session_id()." ".
+                $sql_where;
 
         $result = Database::query($sql);
         $exe_list = Database::store_result($result);
@@ -2875,10 +2875,10 @@ class Exercise
         $session_id = api_get_session_id();
         // delete TRACK_E_EXERCISES table
         $sql = "DELETE FROM $table_track_e_exercises
-                WHERE c_id = ".api_get_course_int_id()."
-                AND exe_exo_id = ".$this->id."
-                $sql_where
-                AND session_id = ".$session_id;
+                WHERE 
+                  c_id = ".api_get_course_int_id()." AND 
+                  exe_exo_id = ".$this->id." $sql_where AND 
+                  session_id = ".$session_id;
         Database::query($sql);
 
         Event::addEvent(
@@ -3232,26 +3232,7 @@ class Exercise
         $time_left = intval($time_left);
 
         return "<script>
-
-            function get_expired_date_string(expired_time) {
-                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
-                var day, month, year, hours, minutes, seconds, date_string;
-                var obj_date = new Date(expired_time);
-                day     = obj_date.getDate();
-                if (day < 10) day = '0' + day;
-                    month   = obj_date.getMonth();
-                    year    = obj_date.getFullYear();
-                    hours   = obj_date.getHours();
-                if (hours < 10) hours = '0' + hours;
-                minutes = obj_date.getMinutes();
-                if (minutes < 10) minutes = '0' + minutes;
-                seconds = obj_date.getSeconds();
-                if (seconds < 10) seconds = '0' + seconds;
-                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
-                return date_string;
-            }
-
-            function open_clock_warning() {
+            function openClockWarning() {
                 $('#clock_warning').dialog({
                     modal:true,
                     height:250,
@@ -3267,7 +3248,6 @@ class Exercise
                     }
                 });
                 $('#clock_warning').dialog('open');
-
                 $('#counter_to_redirect').epiclock({
                     mode: $.epiclock.modes.countdown,
                     offset: {seconds: 5},
@@ -3279,7 +3259,7 @@ class Exercise
 
             function send_form() {
                 if ($('#exercise_form').length) {
-                    $('#exercise_form').submit();
+                    save_now_all('validate');
                 } else {
                     // In exercise_reminder.php
                     final_submit();
@@ -3288,21 +3268,15 @@ class Exercise
 
             function onExpiredTimeExercise() {
                 $('#wrapper-clock').hide();
-                //$('#exercise_form').hide();
                 $('#expired-message-id').show();
-
                 //Fixes bug #5263
                 $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
-                open_clock_warning();
+                openClockWarning();
             }
 
 			$(document).ready(function() {
-				var current_time = new Date().getTime();
 				// time in seconds when using minutes there are some seconds lost
-                var time_left    = parseInt(".$time_left."); 
-				var expired_time = current_time + (time_left*1000);
-				var expired_date = get_expired_date_string(expired_time);
-
+                var time_left = parseInt(".$time_left.");
                 $('#exercise_clock_warning').epiclock({
                     mode: $.epiclock.modes.countdown,
                     offset: {seconds: time_left},
@@ -3321,7 +3295,7 @@ class Exercise
      *
      * @param int    $exeId
      * @param int    $questionId
-     * @param int    $choice                                    the user selected
+     * @param mixed  $choice                                    the user-selected option
      * @param string $from                                      function is called from 'exercise_show' or 'exercise_result'
      * @param array  $exerciseResultCoordinates                 the hotspot coordinates $hotspot[$question_id] = coordinates
      * @param bool   $saved_results                             save results in the DB or just show the reponse
@@ -3382,8 +3356,12 @@ class Exercise
         $threadhold3 = 0;
         $arrques = null;
         $arrans = null;
-        $questionId = intval($questionId);
-        $exeId = intval($exeId);
+        $studentChoice = null;
+        $expectedAnswer = '';
+        $calculatedChoice = '';
+        $calculatedStatus = '';
+        $questionId = (int) $questionId;
+        $exeId = (int) $exeId;
         $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
         $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
 
@@ -3405,12 +3383,18 @@ class Exercise
         $totalScore = 0;
 
         // Extra information of the question
-        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE && !empty($extra)) {
+        if ((
+            $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
+            $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
+            )
+            && !empty($extra)
+        ) {
             $extra = explode(':', $extra);
             if ($debug) {
                 error_log(print_r($extra, 1));
             }
             // Fixes problems with negatives values using intval
+
             $true_score = floatval(trim($extra[0]));
             $false_score = floatval(trim($extra[1]));
             $doubt_score = floatval(trim($extra[2]));
@@ -3425,6 +3409,12 @@ class Exercise
             error_log('$answerType: '.$answerType);
         }
 
+        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+            $choiceTmp = $choice;
+            $choice = $choiceTmp["choice"];
+            $choiceDegreeCertainty = $choiceTmp["choiceDegreeCertainty"];
+        }
+
         if ($answerType == FREE_ANSWER ||
             $answerType == ORAL_EXPRESSION ||
             $answerType == CALCULATED_ANSWER ||
@@ -3508,7 +3498,6 @@ class Exercise
             $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
 
             switch ($answerType) {
-                // for unique answer
                 case UNIQUE_ANSWER:
                 case UNIQUE_ANSWER_IMAGE:
                 case UNIQUE_ANSWER_NO_OPTION:
@@ -3534,7 +3523,6 @@ class Exercise
                         }
                     }
                     break;
-                // for multiple answers
                 case MULTIPLE_ANSWER_TRUE_FALSE:
                     if ($from_database) {
                         $choice = [];
@@ -3572,6 +3560,55 @@ class Exercise
                     }
                     $totalScore = $questionScore;
                     break;
+                case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
+                    if ($from_database) {
+                        $choice = [];
+                        $choiceDegreeCertainty = [];
+                        $sql = "SELECT answer 
+                            FROM $TBL_TRACK_ATTEMPT
+                            WHERE 
+                            exe_id = $exeId AND question_id = $questionId";
+
+                        $result = Database::query($sql);
+                        while ($row = Database::fetch_array($result)) {
+                            $ind = $row['answer'];
+                            $values = explode(':', $ind);
+                            $myAnswerId = $values[0];
+                            $option = $values[1];
+                            $percent = $values[2];
+                            $choice[$myAnswerId] = $option;
+                            $choiceDegreeCertainty[$myAnswerId] = $percent;
+                        }
+                    }
+
+                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
+                    $studentChoiceDegree = isset($choiceDegreeCertainty[$answerAutoId]) ?
+                        $choiceDegreeCertainty[$answerAutoId] : null;
+
+                    // student score update
+                    if (!empty($studentChoice)) {
+                        if ($studentChoice == $answerCorrect) {
+                            // correct answer and student is Unsure or PrettySur
+                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
+                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
+                                $questionScore += $true_score;
+                            } else {
+                                // student ignore correct answer
+                                $questionScore += $doubt_score;
+                            }
+                        } else {
+                            // false answer and student is Unsure or PrettySur
+                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
+                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
+                                $questionScore += $false_score;
+                            } else {
+                                // student ignore correct answer
+                                $questionScore += $doubt_score;
+                            }
+                        }
+                    }
+                    $totalScore = $questionScore;
+                    break;
                 case MULTIPLE_ANSWER: //2
                     if ($from_database) {
                         $choice = [];
@@ -3797,6 +3834,7 @@ class Exercise
                         $answer = '';
                         $real_correct_tags = $correct_tags;
                         $chosen_list = [];
+
                         for ($i = 0; $i < count($real_correct_tags); $i++) {
                             if ($i == 0) {
                                 $answer .= $real_text[0];
@@ -3852,10 +3890,7 @@ class Exercise
                         // insert the student result in the track_e_attempt table, field answer
                         // $answer is the answer like in the c_quiz_answer table for the question
                         // student data are choice[]
-                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
-                            $answer
-                        );
-
+                        $listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
                         $switchableAnswerSet = $listCorrectAnswers['switchable'];
                         $answerWeighting = $listCorrectAnswers['weighting'];
                         // user choices is an array $choice
@@ -3919,7 +3954,6 @@ class Exercise
                                         }
                                     }
                                 }
-
                                 $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
                                 $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
                             }
@@ -3976,9 +4010,7 @@ class Exercise
                                 }
                             }
                         }
-                        $answer = FillBlanks::getAnswerInStudentAttempt(
-                            $listCorrectAnswers
-                        );
+                        $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
                     }
                     break;
                 case CALCULATED_ANSWER:
@@ -4027,7 +4059,6 @@ class Exercise
                                         question_id = ".intval($questionId);
                                 $result = Database::query($sql);
                                 $str = Database::result($result, 0, 'answer');
-
                                 api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
                                 $str = str_replace('\r\n', '', $str);
                                 $choice = $arr[1];
@@ -4499,9 +4530,9 @@ class Exercise
                         }
                     }
                     break;
-                // @todo never added to chamilo
-                //for hotspot with fixed order
                 case HOT_SPOT_ORDER:
+                    // @todo never added to chamilo
+                    // for hotspot with fixed order
                     $studentChoice = $choice['order'][$answerId];
                     if ($studentChoice == $answerId) {
                         $questionScore += $answerWeighting;
@@ -4511,8 +4542,8 @@ class Exercise
                         $studentChoice = false;
                     }
                     break;
-                // for hotspot with delineation
                 case HOT_SPOT_DELINEATION:
+                    // for hotspot with delineation
                     if ($from_database) {
                         // getting the user answer
                         $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
@@ -4558,9 +4589,9 @@ class Exercise
                 case ANNOTATION:
                     if ($from_database) {
                         $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
-                                 WHERE 
-                                    exe_id = $exeId AND 
-                                    question_id= ".$questionId;
+                                WHERE 
+                                  exe_id = $exeId AND 
+                                  question_id= ".$questionId;
                         $resq = Database::query($sql);
                         $data = Database::fetch_array($resq);
 
@@ -4570,9 +4601,7 @@ class Exercise
                         $arrques = $questionName;
                         break;
                     }
-
                     $studentChoice = $choice;
-
                     if ($studentChoice) {
                         $questionScore = 0;
                         $totalScore += 0;
@@ -4631,6 +4660,17 @@ class Exercise
                                 $results_disabled,
                                 $showTotalScoreAndUserChoicesInLastAttempt
                             );
+                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+                            ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
+                                $feedback_type,
+                                $studentChoice,
+                                $studentChoiceDegree,
+                                $answer,
+                                $answerComment,
+                                $answerCorrect,
+                                $questionId,
+                                $results_disabled
+                            );
                         } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
                             ExerciseShowFunctions::display_multiple_answer_combination_true_false(
                                 $this,
@@ -5012,6 +5052,31 @@ class Exercise
                                 );
                             }
                             break;
+                        case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
+                            if ($answerId == 1) {
+                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
+                                    $feedback_type,
+                                    $studentChoice,
+                                    $studentChoiceDegree,
+                                    $answer,
+                                    $answerComment,
+                                    $answerCorrect,
+                                    $questionId,
+                                    $results_disabled
+                                );
+                            } else {
+                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
+                                    $feedback_type,
+                                    $studentChoice,
+                                    $studentChoiceDegree,
+                                    $answer,
+                                    $answerComment,
+                                    $answerCorrect,
+                                    $questionId,
+                                    $results_disabled
+                                );
+                            }
+                            break;
                         case FILL_IN_BLANKS:
                             ExerciseShowFunctions::display_fill_in_blanks_answer(
                                 $feedback_type,
@@ -5388,19 +5453,19 @@ class Exercise
                                     <td><b>'.get_lang('Overlap').'</b></td>
                                     <td>'.get_lang('Min').' '.$threadhold1.'</td>
                                     <td><div style="color:'.$overlap_color.'">'
-                                        .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
+                            .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
                                 </tr>
                                 <tr>
                                     <td><b>'.get_lang('Excess').'</b></td>
                                     <td>'.get_lang('Max').' '.$threadhold2.'</td>
                                     <td><div style="color:'.$excess_color.'">'
-                                        .(($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
+                            .(($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
                                 </tr>
                                 <tr class="row_even">
                                     <td><b>'.get_lang('Missing').'</b></td>
                                     <td>'.get_lang('Max').' '.$threadhold3.'</td>
                                     <td><div style="color:'.$missing_color.'">'
-                                        .(($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
+                            .(($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
                                 </tr>
                             </table>';
                         if ($next == 0) {
@@ -5529,9 +5594,11 @@ class Exercise
                 }
             }
 
+            //if ($origin != 'learnpath') {
             if ($show_result && $answerType != ANNOTATION) {
                 echo '</table>';
             }
+            //	}
         }
         unset($objAnswerTmp);
 
@@ -5542,29 +5609,51 @@ class Exercise
         if ($saved_results) {
             if ($debug) {
                 error_log("Save question results $saved_results");
-            }
-            if ($debug) {
+                error_log('choice: ');
                 error_log(print_r($choice, 1));
             }
 
             if (empty($choice)) {
                 $choice = 0;
             }
-            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
+            // with certainty degree
+            if (empty($choiceDegreeCertainty)) {
+                $choiceDegreeCertainty = 0;
+            }
+            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
+                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ||
+                $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
+            ) {
                 if ($choice != 0) {
                     $reply = array_keys($choice);
                     for ($i = 0; $i < sizeof($reply); $i++) {
-                        $ans = $reply[$i];
-                        Event::saveQuestionAttempt(
-                            $questionScore,
-                            $ans.':'.$choice[$ans],
-                            $quesId,
-                            $exeId,
-                            $i,
-                            $this->id
-                        );
+                        $chosenAnswer = $reply[$i];
+                        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+                            if ($choiceDegreeCertainty != 0) {
+                                $replyDegreeCertainty = array_keys($choiceDegreeCertainty);
+                                $answerDegreeCertainty = $replyDegreeCertainty[$i];
+                                Event::saveQuestionAttempt(
+                                    $questionScore,
+                                    $chosenAnswer.':'.$choice[$chosenAnswer].':'.
+                                        $choiceDegreeCertainty[$answerDegreeCertainty],
+                                    $quesId,
+                                    $exeId,
+                                    $i,
+                                    $this->id
+                                );
+                            }
+                        } else {
+                            Event::saveQuestionAttempt(
+                                $questionScore,
+                                $chosenAnswer.':'.$choice[$chosenAnswer],
+                                $quesId,
+                                $exeId,
+                                $i,
+                                $this->id
+                            );
+                        }
                         if ($debug) {
-                            error_log('result =>'.$questionScore.' '.$ans.':'.$choice[$ans]);
+                            error_log('result =>'.$questionScore.' '.$chosenAnswer.':'.$choice[$chosenAnswer]);
                         }
                     }
                 } else {
@@ -5651,10 +5740,12 @@ class Exercise
             ) {
                 $answer = $choice;
                 Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
-            //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
             } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
                 $answer = [];
                 if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
+                    if ($debug) {
+                        error_log('Checking result coordinates');
+                    }
                     Database::delete(
                         Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
                         [
@@ -5669,6 +5760,9 @@ class Exercise
                     foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
                         $answer[] = $val;
                         $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
+                        if ($debug) {
+                            error_log('Hotspot value: '.$hotspotValue);
+                        }
                         Event::saveExerciseAttemptHotspot(
                             $exeId,
                             $quesId,
@@ -5679,6 +5773,10 @@ class Exercise
                             $this->id
                         );
                     }
+                } else {
+                    if ($debug) {
+                        error_log("Empty: exerciseResultCoordinates");
+                    }
                 }
                 Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
             } else {
@@ -5691,14 +5789,17 @@ class Exercise
         }
 
         if ($saved_results) {
-            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
-            $sql = 'UPDATE '.$table.' SET
-                        exe_result = exe_result + '.floatval($questionScore).'
-                    WHERE exe_id = '.$exeId;
+            $statsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
+            $sql = "UPDATE $statsTable SET
+                        exe_result = exe_result + ".floatval($questionScore)."
+                    WHERE exe_id = $exeId";
             Database::query($sql);
+            if ($debug) {
+                error_log($sql);
+            }
         }
 
-        $return = [
+        $return_array = [
             'score' => $questionScore,
             'weight' => $questionWeighting,
             'extra' => $extra_data,
@@ -5708,7 +5809,7 @@ class Exercise
             'generated_oral_file' => $generatedFile,
         ];
 
-        return $return;
+        return $return_array;
     }
 
     /**
@@ -5785,7 +5886,8 @@ class Exercise
         }
 
         $user_info = api_get_user_info(api_get_user_id());
-        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.api_get_cidreq().'&id='.$exe_id.'&action=qualify';
+        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.
+            api_get_cidreq(true, true, 'qualify').'&id='.$exe_id.'&action=qualify';
 
         if (!empty($sessionId)) {
             $addGeneralCoach = true;
@@ -5873,12 +5975,13 @@ class Exercise
             '#student_complete_name#' => $user_info['complete_name'],
             '#course#' => $courseInfo['title'],
         ];
-        if ($origin != 'learnpath' && $sendEnd) {
+
+        if ($sendEnd) {
             $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
             $variables['#url#'] = $url;
         }
 
-        $mail_content = str_replace(array_keys($variables), array_values($variables), $msg);
+        $content = str_replace(array_keys($variables), array_values($variables), $msg);
 
         if ($sendEnd) {
             $subject = get_lang('ExerciseAttempted');
@@ -5891,7 +5994,7 @@ class Exercise
                 MessageManager::send_message_simple(
                     $user_id,
                     $subject,
-                    $mail_content
+                    $content
                 );
             }
         }
@@ -5916,9 +6019,13 @@ class Exercise
 
         $array = [];
         if (!empty($user_data)) {
+            $userFullName = $user_data['complete_name'];
+            if (api_is_teacher() || api_is_platform_admin(true, true)) {
+                $userFullName = '<a href="'.$user_data['profile_url'].'" title="'.get_lang('GoToStudentDetails').'">'.$user_data['complete_name'].'</a>';
+            }
             $array[] = [
                 'title' => get_lang('Name'),
-                'content' => $user_data['complete_name'],
+                'content' => $userFullName,
             ];
             $array[] = [
                 'title' => get_lang('Username'),
@@ -7874,6 +7981,16 @@ class Exercise
         Database::query($sql);
     }
 
+    /**
+     * Get the title without HTML tags.
+     *
+     * @return string
+     */
+    public function getUnformattedTitle()
+    {
+        return strip_tags(api_html_entity_decode($this->title));
+    }
+
     /**
      * Gets the question list ordered by the question_order setting (drag and drop).
      *
@@ -8040,25 +8157,25 @@ class Exercise
         $courseInfo = api_get_course_info($courseCode);
 
         $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
-                    .get_lang('AttemptDetails').' : <br /><br />'
-                    .'<table>'
-                        .'<tr>'
-                            .'<td><em>'.get_lang('CourseName').'</em></td>'
-                            .'<td>&nbsp;<b>#course#</b></td>'
-                        .'</tr>'
-                        .'<tr>'
-                            .'<td>'.get_lang('TestAttempted').'</td>'
-                            .'<td>&nbsp;#exercise#</td>'
-                        .'</tr>'
-                        .'<tr>'
-                            .'<td>'.get_lang('StudentName').'</td>'
-                            .'<td>&nbsp;#firstName# #lastName#</td>'
-                        .'</tr>'
-                        .'<tr>'
-                            .'<td>'.get_lang('StudentEmail').'</td>'
-                            .'<td>&nbsp;#mail#</td>'
-                        .'</tr>'
-                    .'</table>';
+            .get_lang('AttemptDetails').' : <br /><br />'
+            .'<table>'
+            .'<tr>'
+            .'<td><em>'.get_lang('CourseName').'</em></td>'
+            .'<td>&nbsp;<b>#course#</b></td>'
+            .'</tr>'
+            .'<tr>'
+            .'<td>'.get_lang('TestAttempted').'</td>'
+            .'<td>&nbsp;#exercise#</td>'
+            .'</tr>'
+            .'<tr>'
+            .'<td>'.get_lang('StudentName').'</td>'
+            .'<td>&nbsp;#firstName# #lastName#</td>'
+            .'</tr>'
+            .'<tr>'
+            .'<td>'.get_lang('StudentEmail').'</td>'
+            .'<td>&nbsp;#mail#</td>'
+            .'</tr>'
+            .'</table>';
         $open_question_list = null;
         foreach ($question_list_answers as $item) {
             $question = $item['question'];
@@ -8068,19 +8185,19 @@ class Exercise
             if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
                 $open_question_list .=
                     '<tr>'
-                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
-                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
+                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
+                    .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
                     .'</tr>'
                     .'<tr>'
-                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
-                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
+                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
+                    .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
                     .'</tr>';
             }
         }
 
         if (!empty($open_question_list)) {
             $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
-                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
+                '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
             $msg .= $open_question_list;
             $msg .= '</table><br />';
             $msg1 = str_replace("#exercise#", $this->exercise, $msg);
@@ -8237,14 +8354,4 @@ class Exercise
         }
         $this->mediaList = $mediaList;
     }
-
-    /**
-     * Get the title without HTML tags.
-     *
-     * @return string
-     */
-    private function getUnformattedTitle()
-    {
-        return strip_tags(api_html_entity_decode($this->title));
-    }
 }

+ 10 - 7
main/exercise/exercise.php

@@ -486,6 +486,7 @@ if ($is_allowedToEdit && $origin != 'learnpath') {
     echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/qti2.php?'.api_get_cidreq().'">'.Display::return_icon('import_qti2.png', get_lang('ImportQtiQuiz'), '', ICON_SIZE_MEDIUM).'</a>';
     echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/aiken.php?'.api_get_cidreq().'">'.Display::return_icon('import_aiken.png', get_lang('ImportAikenQuiz'), '', ICON_SIZE_MEDIUM).'</a>';
     echo '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/upload_exercise.php?'.api_get_cidreq().'">'.Display::return_icon('import_excel.png', get_lang('ImportExcelQuiz'), '', ICON_SIZE_MEDIUM).'</a>';
+
     echo Display::url(
         Display::return_icon(
             'clean_all.png',
@@ -493,10 +494,12 @@ if ($is_allowedToEdit && $origin != 'learnpath') {
             '',
             ICON_SIZE_MEDIUM
         ),
-        '',
+        '#',
         [
-            'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToEmptyAllTestResults'), ENT_QUOTES, $charset))."')) return false;",
-            'href' => api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq().'&choice=clean_all_test&sec_token='.$token,
+            'data-item-question' => addslashes(get_lang('AreYouSureToEmptyAllTestResults')),
+            'data-href' => api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq().'&choice=clean_all_test&sec_token='.$token,
+            'data-toggle' => 'modal',
+            'data-target' => '#confirm-delete',
         ]
     );
 }
@@ -672,7 +675,7 @@ if (!empty($exerciseList)) {
             $cut_title = $exercise->getCutTitle();
             $alt_title = '';
             if ($cut_title != $row['title']) {
-                $alt_title = ' title = "'.$row['title'].'" ';
+                $alt_title = ' title = "'.$exercise->getUnformattedTitle().'" ';
             }
 
             // Teacher only
@@ -936,7 +939,7 @@ if (!empty($exerciseList)) {
                             ),
                             '',
                             [
-                                'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;",
+                                'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset))." ".addslashes($exercise->getUnformattedTitle())."?"."')) return false;",
                                 'href' => 'exercise.php?'.api_get_cidreq().'&choice=delete&sec_token='.$token.'&exerciseId='.$row['id'],
                             ]
                         );
@@ -1184,9 +1187,9 @@ while ($row = Database :: fetch_array($result, 'ASSOC')) {
 $nbrActiveTests = 0;
 if (isset($attribute['path']) && is_array($attribute['path'])) {
     $hotpotatoes_exist = true;
-    while (list($key, $path) = each($attribute['path'])) {
+    foreach ($attribute['path'] as $key => $path) {
         $item = '';
-        list($a, $vis) = each($attribute['visibility']);
+        $vis = $attribute['visibility'][$key];
         $active = !empty($vis);
         $title = GetQuizName($path, $documentPath);
         if ($title == '') {

+ 1 - 2
main/exercise/exercise_history.php

@@ -14,8 +14,7 @@ require_once __DIR__.'/../inc/global.inc.php';
 $this_section = SECTION_COURSES;
 api_protect_course_script(true);
 
-// moved down to fix bug: http://www.dokeos.com/forum/viewtopic.php?p=18609#18609
-$show = (isset($_GET['show']) && $_GET['show'] == 'result') ? 'result' : 'test';
+$show = isset($_GET['show']) && $_GET['show'] === 'result' ? 'result' : 'test';
 
 /* 	Constants and variables */
 $is_allowedToEdit = api_is_allowed_to_edit(null, true);

+ 6 - 3
main/exercise/exercise_report.php

@@ -43,7 +43,10 @@ $path = isset($_GET['path']) ? Security::remove_XSS($_GET['path']) : null;
 
 /* Constants and variables */
 
-$is_allowedToEdit = api_is_allowed_to_edit(null, true) || api_is_drh() || api_is_student_boss() || api_is_session_admin();
+$is_allowedToEdit = api_is_allowed_to_edit(null, true) ||
+    api_is_drh() ||
+    api_is_student_boss() ||
+    api_is_session_admin();
 $is_tutor = api_is_allowed_to_edit(true);
 
 $TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
@@ -336,7 +339,7 @@ if ($is_allowedToEdit && $origin != 'learnpath') {
     if (api_is_platform_admin() || api_is_course_admin() ||
         api_is_course_tutor() || api_is_session_general_coach()
     ) {
-        $actions .= '<a href="admin.php?exerciseId='.intval($_GET['exerciseId']).'&'.api_get_cidreq().'">'.
+        $actions .= '<a href="exercise.php?'.api_get_cidreq().'">'.
             Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
         $actions .= '<a href="live_stats.php?'.api_get_cidreq().'&exerciseId='.$exercise_id.'">'.
             Display::return_icon('activity_monitor.png', get_lang('LiveResults'), '', ICON_SIZE_MEDIUM).'</a>';
@@ -420,7 +423,7 @@ if ($is_allowedToEdit || $is_tutor) {
     $nameTools = get_lang('StudentScore');
     if ($exerciseExists) {
         $interbreadcrumb[] = [
-            "url" => "admin.php?exerciseId=".$exercise_id.'&'.api_get_cidreq(),
+            "url" => '#',
             "name" => $objExerciseTmp->selectTitle(true),
         ];
     }

+ 80 - 47
main/exercise/exercise_show.php

@@ -19,7 +19,7 @@ require_once __DIR__.'/../inc/global.inc.php';
 $debug = false;
 $origin = api_get_origin();
 $currentUserId = api_get_user_id();
-$printHeaders = $origin == 'learnpath';
+$printHeaders = $origin === 'learnpath';
 $id = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0; //exe id
 
 if (empty($id)) {
@@ -64,6 +64,9 @@ if (empty($exerciseResult)) {
     $exerciseResult = Session::read('exerciseResult');
 }
 
+if (empty($choiceDegreeCertainty)) {
+    $choiceDegreeCertainty = isset($_REQUEST['choiceDegreeCertainty']) ? $_REQUEST['choiceDegreeCertainty'] : null;
+}
 $questionId = isset($_REQUEST['questionId']) ? $_REQUEST['questionId'] : null;
 
 if (empty($choice)) {
@@ -81,7 +84,6 @@ if (empty($questionList)) {
 if (empty($objExercise)) {
     $objExercise = Session::read('objExercise');
 }
-
 $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : null;
 
 $courseInfo = api_get_course_info();
@@ -105,7 +107,7 @@ if (!empty($sessionId) && !$is_allowedToEdit) {
 }
 
 $allowCoachFeedbackExercises = api_get_setting('allow_coach_feedback_exercises') === 'true';
-$maxEditors = intval(api_get_setting('exercise_max_ckeditors_in_page'));
+$maxEditors = (int) api_get_setting('exercise_max_ckeditors_in_page');
 $isCoachAllowedToEdit = api_is_allowed_to_edit(false, true);
 $isFeedbackAllowed = false;
 
@@ -138,16 +140,17 @@ if (api_is_in_gradebook()) {
 }
 
 $interbreadcrumb[] = [
-    "url" => "exercise.php?".api_get_cidreq(),
+    'url' => 'exercise.php?'.api_get_cidreq(),
     'name' => get_lang('Exercises'),
 ];
 $interbreadcrumb[] = [
-    "url" => "overview.php?exerciseId=".$exercise_id.'&'.api_get_cidreq(),
-    "name" => $objExercise->selectTitle(true),
+    'url' => 'overview.php?exerciseId='.$exercise_id.'&'.api_get_cidreq(),
+    'name' => $objExercise->selectTitle(true),
 ];
 $interbreadcrumb[] = ['url' => '#', 'name' => get_lang('Result')];
 
 $this_section = SECTION_COURSES;
+
 $htmlHeadXtra[] = '<link rel="stylesheet" href="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/css/hotspot.css">';
 $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>';
 $htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'annotation/js/annotation.js"></script>';
@@ -165,11 +168,7 @@ if ($action != 'export') {
     if ($origin != 'learnpath') {
         Display::display_header('');
     } else {
-        $htmlHeadXtra[] = "
-    <style>
-    body { background: none;}
-    </style>
-    ";
+        $htmlHeadXtra[] = "<style>body { background: none; } </style>";
         Display::display_reduced_header();
     }
 
@@ -181,8 +180,8 @@ if ($action != 'export') {
     ]); ?>
     <script>
         <?php echo $scoreJsCode; ?>
+        var maxEditors = <?php echo $maxEditors; ?>;
 
-        var maxEditors = <?php echo intval($maxEditors); ?>;
         function showfck(sid, marksid) {
             $('#' + sid).toggleClass('hidden');
             $('#' + marksid).toggleClass('hidden');
@@ -194,7 +193,7 @@ if ($action != 'export') {
         }
 
         function getFCK(vals, marksid) {
-            var f = document.getElementById('myform');
+            var f = document.getElementById('form-email');
 
             var m_id = marksid.split(',');
             for (var i = 0; i < m_id.length; i++) {
@@ -321,11 +320,10 @@ $sql = "SELECT attempts.question_id, answer
             attempts.exe_id = ".intval($id)." $user_restriction
 		GROUP BY quizz_rel_questions.question_order, attempts.question_id";
 $result = Database::query($sql);
-$questionListFromDatabase = [];
+$question_list_from_database = [];
 $exerciseResult = [];
-
 while ($row = Database::fetch_array($result)) {
-    $questionListFromDatabase[] = $row['question_id'];
+    $question_list_from_database[] = $row['question_id'];
     $exerciseResult[$row['question_id']] = $row['answer'];
 }
 
@@ -339,16 +337,16 @@ if (!empty($track_exercise_info['data_tracking'])) {
     }
     // If for some reason data_tracking is empty we select the question list from db
     if (empty($questionList)) {
-        $questionList = $questionListFromDatabase;
+        $questionList = $question_list_from_database;
     }
 } else {
-    $questionList = $questionListFromDatabase;
+    $questionList = $question_list_from_database;
 }
 
 // Display the text when finished message if we are on a LP #4227
-$endOfMessage = $objExercise->selectTextWhenFinished();
-if (!empty($endOfMessage) && ($origin == 'learnpath')) {
-    echo Display::return_message($endOfMessage, 'normal', false);
+$end_of_message = $objExercise->selectTextWhenFinished();
+if (!empty($end_of_message) && ($origin === 'learnpath')) {
+    echo Display::return_message($end_of_message, 'normal', false);
     echo "<div class='clear'>&nbsp;</div>";
 }
 
@@ -365,6 +363,7 @@ $counter = 1;
 $exercise_content = '';
 $category_list = [];
 $useAdvancedEditor = true;
+
 if (!empty($maxEditors) && count($questionList) > $maxEditors) {
     $useAdvancedEditor = false;
 }
@@ -428,10 +427,29 @@ foreach ($questionList as $questionId) {
             $questionScore = $question_result['score'];
             $totalScore += $question_result['score'];
             break;
+        case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
+            $choiceTmp = [];
+            $choiceTmp['choice'] = $choice;
+            $choiceTmp['choiceDegreeCertainty'] = $choiceDegreeCertainty;
+
+            $questionResult = $objExercise->manage_answer(
+                $id,
+                $questionId,
+                $choiceTmp,
+                'exercise_show',
+                [],
+                false,
+                true,
+                $show_results,
+                $objExercise->selectPropagateNeg()
+            );
+            $questionScore = $questionResult['score'];
+            $totalScore += $questionResult['score'];
+            break;
         case HOT_SPOT:
             if ($show_results || $showTotalScoreAndUserChoicesInLastAttempt) {
                 echo '<table width="500" border="0"><tr>
-                    <td valign="top" align="center" style="padding-left:0px;" >
+                        <td valign="top" align="center" style="padding-left:0px;" >
                         <table border="1" bordercolor="#A4A4A4" style="border-collapse: collapse;" width="552">';
             }
             $question_result = $objExercise->manage_answer(
@@ -698,8 +716,8 @@ foreach ($questionList as $questionId) {
 
         $marksname = '';
         if ($isFeedbackAllowed && $action != 'export') {
-            $name = "fckdiv".$questionId;
-            $marksname = "marksName".$questionId;
+            $name = 'fckdiv'.$questionId;
+            $marksname = 'marksName'.$questionId;
             if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION])) {
                 $url_name = get_lang('EditCommentsAndMarks');
             } else {
@@ -757,6 +775,7 @@ foreach ($questionList as $questionId) {
             }
             $feedback_form->setDefaults($default);
             $feedback_form->display();
+
             echo '</div>';
 
             if ($allowRecordAudio && $allowTeacherCommentAudio) {
@@ -764,7 +783,6 @@ foreach ($questionList as $questionId) {
                 echo ExerciseLib::getOralFeedbackForm($id, $questionId, $student_id);
                 echo '</div>';
             }
-
             echo '</div>';
         } else {
             $comnt = Event::get_comments($id, $questionId);
@@ -923,31 +941,46 @@ foreach ($questionList as $questionId) {
     $exercise_content .= Display::panel($question_content);
 } // end of large foreach on questions
 
-$total_score_text = '';
+$totalScoreText = '';
 
 //Total score
+$myTotalScoreTemp = $totalScore;
 if ($origin != 'learnpath' || ($origin == 'learnpath' && isset($_GET['fb_type']))) {
     if ($show_results || $show_only_total_score || $showTotalScoreAndUserChoicesInLastAttempt) {
-        $total_score_text .= '<div class="question_row">';
-        $my_total_score_temp = $totalScore;
-        if ($objExercise->selectPropagateNeg() == 0 && $my_total_score_temp < 0) {
-            $my_total_score_temp = 0;
+        $totalScoreText .= '<div class="question_row">';
+        if ($objExercise->selectPropagateNeg() == 0 && $myTotalScoreTemp < 0) {
+            $myTotalScoreTemp = 0;
         }
-        $total_score_text .= ExerciseLib::getTotalScoreRibbon(
-            $objExercise,
-            $my_total_score_temp,
-            $totalWeighting,
-            true,
-            $countPendingQuestions
-        );
-        $total_score_text .= '</div>';
+
+        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+            $totalScoreText .= ExerciseLib::getQuestionDiagnosisRibbon(
+                $objExercise,
+                $myTotalScoreTemp,
+                $totalWeighting,
+                true
+            );
+        } else {
+            $totalScoreText .= ExerciseLib::getTotalScoreRibbon(
+                $objExercise,
+                $myTotalScoreTemp,
+                $totalWeighting,
+                true,
+                $countPendingQuestions
+            );
+        }
+
+        $totalScoreText .= '</div>';
     }
 }
+if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+    $chartMultiAnswer = MultipleAnswerTrueFalseDegreeCertainty::displayStudentsChartResults($id, $objExercise);
+    echo $chartMultiAnswer;
+}
 
 if (!empty($category_list) && ($show_results || $show_only_total_score || $showTotalScoreAndUserChoicesInLastAttempt)) {
     // Adding total
     $category_list['total'] = [
-        'score' => $my_total_score_temp,
+        'score' => $myTotalScoreTemp,
         'total' => $totalWeighting,
     ];
     echo TestCategory::get_stats_table_by_attempt(
@@ -956,12 +989,12 @@ if (!empty($category_list) && ($show_results || $show_only_total_score || $showT
     );
 }
 
-echo $total_score_text;
+echo $totalScoreText;
 echo $exercise_content;
 
 // only show "score" in bottom of page if there's exercise content
 if ($show_results) {
-    echo $total_score_text;
+    echo $totalScoreText;
 }
 
 if ($action == 'export') {
@@ -1008,7 +1041,7 @@ if ($isFeedbackAllowed && $origin != 'learnpath' && $origin != 'student_progress
             'course' => Security::remove_XSS($_GET['cidReq']),
         ]);
 
-        $emailForm = new FormValidator('myform', 'post', $formUrl, '', ['id' => 'myform']);
+        $emailForm = new FormValidator('form-email', 'post', $formUrl, '', ['id' => 'form-email']);
         $emailForm->addHidden('lp_item_id', $learnpath_id);
         $emailForm->addHidden('lp_item_view_id', $lp_item_view_id);
         $emailForm->addHidden('student_id', $student_id);
@@ -1024,11 +1057,11 @@ if ($isFeedbackAllowed && $origin != 'learnpath' && $origin != 'student_progress
         ]);
 
         $emailForm = new FormValidator(
-            'myform',
+            'form-email',
             'post',
             $formUrl,
             '',
-            ['id' => 'myform']
+            ['id' => 'form-email']
         );
     }
 
@@ -1038,13 +1071,13 @@ if ($isFeedbackAllowed && $origin != 'learnpath' && $origin != 'student_progress
         get_lang('SendEmail'),
         ['onclick' => 'openEmailWrapper();']
     );
-    $emailForm->addHtml('<span id="email_content_wrapper" style="display:none">');
+    $emailForm->addHtml('<div id="email_content_wrapper" style="display:none; margin-bottom: 20px;">');
     $emailForm->addHtmlEditor(
         'notification_content',
         get_lang('Content'),
         false
     );
-    $emailForm->addHtml('</span>');
+    $emailForm->addHtml('</div>');
 
     if (empty($track_exercise_info['orig_lp_id']) || empty($track_exercise_info['orig_lp_item_id'])) {
         // Default url
@@ -1106,7 +1139,7 @@ if ($origin != 'learnpath') {
     if (!isset($_GET['fb_type'])) {
         $lp_mode = Session::read('lp_mode');
         $url = '../lp/lp_controller.php?'.api_get_cidreq().'&';
-        $url .= http_build_url([
+        $url .= http_build_query([
             'action' => 'view',
             'lp_id' => $learnpath_id,
             'lp_item_id' => $learnpath_item_id,

+ 67 - 35
main/exercise/exercise_submit.php

@@ -791,7 +791,28 @@ if ($question_count != 0) {
                     header('Location: exercise_reminder.php?'.$params);
                     exit;
                 } else {
-                    header("Location: exercise_result.php?".api_get_cidreq()."&exe_id=$exe_id&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id");
+                    $certaintyQuestionPresent = false;
+                    foreach ($questionList as $questionId) {
+                        $question = Question::read($questionId);
+                        if ($question->type == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+                            $certaintyQuestionPresent = true;
+                            break;
+                        }
+                    }
+                    if ($certaintyQuestionPresent) {
+                        // Certainty grade question
+                        // We send an email to the student before redirection to the result page
+                        MultipleAnswerTrueFalseDegreeCertainty::sendQuestionCertaintyNotification(
+                            $user_id, $objExercise, $exe_id
+                        );
+                    }
+
+                    header("Location: exercise_result.php?"
+                        .api_get_cidreq()
+                        ."&exe_id=$exe_id&learnpath_id=$learnpath_id&learnpath_item_id="
+                        .$learnpath_item_id
+                        ."&learnpath_item_view_id=$learnpath_item_view_id"
+                    );
                     exit;
                 }
             }
@@ -827,11 +848,7 @@ if ($origin != 'learnpath') { //so we are not in learnpath tool
 
     Display::display_header(null, 'Exercises');
 } else {
-    $htmlHeadXtra[] = "
-    <style>
-    body { background: none;}
-    </style>
-    ";
+    $htmlHeadXtra[] = "<style> body { background: none;} </style> ";
     Display::display_reduced_header();
     echo '<div style="height:10px">&nbsp;</div>';
 }
@@ -842,9 +859,11 @@ $show_quiz_edition = $objExercise->added_in_lp();
 if (api_is_course_admin() && $origin != 'learnpath') {
     echo '<div class="actions">';
     if ($show_quiz_edition == false) {
-        echo '<a href="exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->id.'">'.Display::return_icon('settings.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
+        echo '<a href="exercise_admin.php?'.api_get_cidreq().'&modifyExercise=yes&exerciseId='.$objExercise->id.'">'.
+            Display::return_icon('settings.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
     } else {
-        echo '<a href="#">'.Display::return_icon('settings_na.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
+        echo '<a href="#">'.
+            Display::return_icon('settings_na.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).'</a>';
     }
     echo '</div>';
 }
@@ -934,7 +953,8 @@ if (isset($_custom['exercises_hidden_when_no_start_date']) &&
 // Timer control
 if ($time_control) {
     echo $objExercise->return_time_left_div();
-    echo '<div style="display:none" class="warning-message" id="expired-message-id">'.get_lang('ExerciseExpiredTimeMessage').'</div>';
+    echo '<div style="display:none" class="warning-message" id="expired-message-id">'.
+        get_lang('ExerciseExpiredTimeMessage').'</div>';
 }
 
 if ($origin != 'learnpath') {
@@ -1003,7 +1023,10 @@ if (!empty($error)) {
     echo Display::return_message($error, 'error', false);
 } else {
     if (!empty($exercise_sound)) {
-        echo "<a href=\"../document/download.php?doc_url=%2Faudio%2F".Security::remove_XSS($exercise_sound)."\" target=\"_blank\">", "<img src=\"../img/sound.gif\" border=\"0\" align=\"absmiddle\" alt=", get_lang('Sound')."\" /></a>";
+        echo "<a 
+            href=\"../document/download.php?doc_url=%2Faudio%2F".Security::remove_XSS($exercise_sound)."\" 
+            target=\"_blank\">";
+        echo "<img src=\"../img/sound.gif\" border=\"0\" align=\"absmiddle\" alt=", get_lang('Sound')."\" /></a>";
     }
     // Get number of hotspot questions for javascript validation
     $number_of_hotspot_questions = 0;
@@ -1037,10 +1060,6 @@ if (!empty($error)) {
         }
     }
 
-    if ($number_of_hotspot_questions > 0) {
-        $onsubmit = "onsubmit=\"return validateFlashVar('".$number_of_hotspot_questions."', '".get_lang('HotspotValidateError1')."', '".get_lang('HotspotValidateError2')."');\"";
-    }
-
     $saveIcon = Display::return_icon(
         'save.png',
         get_lang('Saved'),
@@ -1063,8 +1082,7 @@ if (!empty($error)) {
             return;
         }
         
-        var calledUpdateDuration = false;
-        
+        var calledUpdateDuration = false;        
         function updateDuration() {
             if (calledUpdateDuration === false) {
                 var saveDurationUrl = "'.$saveDurationUrl.'";
@@ -1103,6 +1121,7 @@ if (!empty($error)) {
 
             $(".no_remind_highlight").hide();
 
+            // if the users validates the form using return key, 
             // if the users validates the form using return key, prevent form action and simulates click on validation button
             /*$("#exercise_form").submit(function(){
                 $(".question-validate-btn").first().trigger("click");
@@ -1111,7 +1130,7 @@ if (!empty($error)) {
 
             $("form#exercise_form").prepend($("#exercise-description"));
         
-            $(\'button[name="previous_question_and_save"]\').on("click", function (e) {
+            $(\'button[name="previous_question_and_save"]\').on("touchstart click", function (e) {
                 e.preventDefault();
                 e.stopPropagation();    
                 var
@@ -1122,7 +1141,7 @@ if (!empty($error)) {
                 previous_question_and_save(previousId, questionId);
             });
 
-            $(\'button[name="save_question_list"]\').on(\'click\', function (e) {
+            $(\'button[name="save_question_list"]\').on(\'touchstart click\', function (e) {
                 e.preventDefault();
                 e.stopPropagation();
                 var $this = $(this);
@@ -1131,9 +1150,10 @@ if (!empty($error)) {
                 save_question_list(questionList);
             });
 
-            $(\'button[name="save_now"]\').on(\'click\', function (e) {
+            $(\'button[name="save_now"]\').on(\'touchstart click\', function (e) {
                 e.preventDefault();
-                e.stopPropagation();                
+                e.stopPropagation();
+                
                 var
                     $this = $(this),
                     questionId = parseInt($this.data(\'question\')) || 0,
@@ -1142,9 +1162,10 @@ if (!empty($error)) {
                 save_now(questionId, urlExtra);
             });
 
-            $(\'button[name="validate_all"]\').on(\'click\', function (e) {
+            $(\'button[name="validate_all"]\').on(\'touchstart click\', function (e) {
                 e.preventDefault();
-                e.stopPropagation();                
+                e.stopPropagation();
+                
                 validate_all();
             });
             
@@ -1190,6 +1211,9 @@ if (!empty($error)) {
 
             //3. Hotspots
             var hotspot = $(\'*[name*="hotspot[\'+question_id+\']"]\').serialize();
+            
+            //4. choice for degree of certainty
+            var my_choiceDc = $(\'*[name*="choiceDegreeCertainty[\'+question_id+\']"]\').serialize();
 
             // Checking FCK
             if (question_id) {
@@ -1200,7 +1224,7 @@ if (!empty($error)) {
                     my_choice = $.param(my_choice);
                 }
             }
-
+            
             if ($(\'input[name="remind_list[\'+question_id+\']"]\').is(\':checked\')) {
                 $("#question_div_"+question_id).addClass("remind_highlight");
             } else {
@@ -1208,26 +1232,33 @@ if (!empty($error)) {
             }
 
             // Only for the first time
-
-            $("#save_for_now_"+question_id).html(\''.Display::returnFontAwesomeIcon('spinner', null, true, 'fa-spin').'\');
+            var dataparam = "'.$params.'&type=simple&question_id="+question_id;
+            dataparam += "&"+my_choice+"&"+hotspot+"&"+remind_list+"&"+my_choiceDc;
+            
+            $("#save_for_now_"+question_id).html(\''.
+                Display::returnFontAwesomeIcon('spinner', null, true, 'fa-spin').'\');
             $.ajax({
                 type:"post",
                 async: false,
                 url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=save_exercise_by_now",
-                data: "'.$params.'&type=simple&question_id="+question_id+"&"+my_choice+"&"+hotspot+"&"+remind_list,
+                data: dataparam,
                 success: function(return_value) {
                     if (return_value == "ok") {
-                        $("#save_for_now_"+question_id).html(\''.Display::return_icon('save.png', get_lang('Saved'), [], ICON_SIZE_SMALL).'\');
+                        $("#save_for_now_"+question_id).html(\''.
+                            Display::return_icon('save.png', get_lang('Saved'), [], ICON_SIZE_SMALL).'\');
                     } else if (return_value == "error") {
-                        $("#save_for_now_"+question_id).html(\''.Display::return_icon('error.png', get_lang('Error'), [], ICON_SIZE_SMALL).'\');
+                        $("#save_for_now_"+question_id).html(\''.
+                            Display::return_icon('error.png', get_lang('Error'), [], ICON_SIZE_SMALL).'\');
                     } else if (return_value == "one_per_page") {
                         var url = "";
                         if ('.$reminder.' == 1 ) {
                             url = "exercise_reminder.php?'.$params.'&num='.$current_question.'";
                         } else if ('.$reminder.' == 2 ) {
-                            url = "exercise_submit.php?'.$params.'&num='.$current_question.'&remind_question_id='.$remind_question_id.'&reminder=2";
+                            url = "exercise_submit.php?'.$params.'&num='.$current_question.
+                                '&remind_question_id='.$remind_question_id.'&reminder=2";
                         } else {
-                            url = "exercise_submit.php?'.$params.'&num='.$current_question.'&remind_question_id='.$remind_question_id.'";
+                            url = "exercise_submit.php?'.$params.'&num='.$current_question.
+                                '&remind_question_id='.$remind_question_id.'";
                         }
 
                         if (url_extra) {
@@ -1240,7 +1271,8 @@ if (!empty($error)) {
                     }
                 },
                 error: function() {
-                    $("#save_for_now_"+question_id).html(\''.Display::return_icon('error.png', get_lang('Error'), [], ICON_SIZE_SMALL).'\');
+                    $("#save_for_now_"+question_id).html(\''.
+                        Display::return_icon('error.png', get_lang('Error'), [], ICON_SIZE_SMALL).'\');
                 }
             });
         }
@@ -1254,12 +1286,10 @@ if (!empty($error)) {
 
             // 3. Hotspots.
             var hotspot = $(\'*[name*="hotspot"]\').serialize();
-
+            
             // Question list.
             var question_list = ['.implode(',', $questionList).'];
-
             var free_answers = {};
-
             $.each(question_list, function(index, my_question_id) {
                 // Checking FCK
                 if (my_question_id) {
@@ -1299,7 +1329,9 @@ if (!empty($error)) {
         }
     </script>';
 
-    echo '<form id="exercise_form" method="post" action="'.api_get_self().'?'.api_get_cidreq().'&reminder='.$reminder.'&autocomplete=off&exerciseId='.$exerciseId.'" name="frm_exercise" '.$onsubmit.'>
+    echo '<form id="exercise_form" method="post" action="'.
+            api_get_self().'?'.api_get_cidreq().'&reminder='.$reminder.
+            '&autocomplete=off&exerciseId='.$exerciseId.'" name="frm_exercise" '.$onsubmit.'>
          <input type="hidden" name="formSent" value="1" />
          <input type="hidden" name="exerciseId" value="'.$exerciseId.'" />
          <input type="hidden" name="num" value="'.$current_question.'" id="num_current_id" />

+ 7 - 1
main/exercise/export/qti2/qti2_classes.php

@@ -17,10 +17,15 @@ class Ims2Question extends Question
      */
     public function setAnswer()
     {
-        $answer = null;
         switch ($this->type) {
             case MCUA:
+                $answer = new ImsAnswerMultipleChoice($this->id);
+
+                return $answer;
             case MCMA:
+                $answer = new ImsAnswerMultipleChoice($this->id);
+
+                return $answer;
             case TF:
                 $answer = new ImsAnswerMultipleChoice($this->id);
 
@@ -43,6 +48,7 @@ class Ims2Question extends Question
 
                 return $answer;
             default:
+                $answer = null;
                 break;
         }
 

+ 2 - 2
main/exercise/export/qti2/qti2_export.php

@@ -240,7 +240,7 @@ class ImsSection
      *
      * @author Amand Tihon <amand@alrj.org>
      */
-    public function export_questions()
+    public function exportQuestions()
     {
         $out = '';
         foreach ($this->exercise->selectQuestionList() as $q) {
@@ -273,7 +273,7 @@ class ImsSection
              .$this->export_duration()
              .$this->export_presentation()
              .$this->export_ordering()
-             .$this->export_questions()
+             .$this->exportQuestions()
              .$this->end_section()
              .$foot;
 

+ 46 - 42
main/exercise/fill_blanks.class.php

@@ -80,7 +80,7 @@ class FillBlanks extends Question
                         updateBlanks();
                     });
                 }
-            });                        
+            });
             
             function updateBlanks()
             {                
@@ -91,7 +91,7 @@ class FillBlanks extends Question
                 } else {
                     answer = CKEDITOR.instances["answer"].getData();
                 }
-                                
+                
                 // disable the save button, if not blanks have been created
                 $("button").attr("disabled", "disabled");
                 $("#defineoneblank").show();      
@@ -101,9 +101,9 @@ class FillBlanks extends Question
                 fields += "<label class=\"col-sm-2 control-label\"></label>";
                 fields += "<div class=\"col-sm-8\">";
                 fields += "<table class=\"data_table\">";
-                fields += "<tr><th style=\"width:220px\">'.get_lang("WordTofind").'</th>";
-                fields += "<th style=\"width:50px\">'.get_lang("QuestionWeighting").'</th>";
-                fields += "<th>'.get_lang("BlankInputSize").'</th></tr>";
+                fields += "<tr><th style=\"width:220px\">'.get_lang('WordTofind').'</th>";
+                fields += "<th style=\"width:50px\">'.get_lang('QuestionWeighting').'</th>";
+                fields += "<th>'.get_lang('BlankInputSize').'</th></tr>";
 
                 if (blanks != null) {
                     for (var i=0; i < blanks.length; i++) {
@@ -163,8 +163,7 @@ class FillBlanks extends Question
                         if (id) {
                             var sizeInputId = id.replace("samplesize", "sizeofinput");                            
                             var sizeInputId = sizeInputId.replace("[", "\\\[");
-                            var sizeInputId = sizeInputId.replace("]", "\\\]");                       
-                                                         
+                            var sizeInputId = sizeInputId.replace("]", "\\\]");                                                         
                             $("#"+sizeInputId).val(data.width);                        
                             d.outerWidth(data.width+"px");
                         }
@@ -179,8 +178,7 @@ class FillBlanks extends Question
                 }
             }
 
-            window.onload = updateBlanks;
-            
+            window.onload = updateBlanks;            
             String.prototype.hashCode = function() {
                 var hash = 0, i, chr, len;
                 if (this.length === 0) return hash;
@@ -250,6 +248,7 @@ class FillBlanks extends Question
                 outTxt = outTxt.replace(/&nbsp;/g, " ");
                 outTxt = outTxt.replace(/^ +/, "");
                 outTxt = outTxt.replace(/ +$/, "");
+                
                 return outTxt;
             }
 
@@ -441,7 +440,7 @@ class FillBlanks extends Question
                 $answer .= $form->getSubmitValue('weighting['.$i.']');
                 // not the last word, add ","
                 if ($i != $nb - 1) {
-                    $answer .= ",";
+                    $answer .= ',';
                 }
                 // calculate the global weighting for the question
                 $this->weighting += (float) $form->getSubmitValue('weighting['.$i.']');
@@ -454,7 +453,7 @@ class FillBlanks extends Question
                 $answer .= $form->getSubmitValue('sizeofinput['.$i.']');
                 // not the last word, add ","
                 if ($i != $nb - 1) {
-                    $answer .= ",";
+                    $answer .= ',';
                 }
             }
         }
@@ -470,7 +469,7 @@ class FillBlanks extends Question
             5 %...%
             6 $...$
          */
-        $answer .= ":".$form->getSubmitValue('select_separator');
+        $answer .= ':'.$form->getSubmitValue('select_separator');
 
         // Allow answers order switches
         $is_multiple = $form->getSubmitValue('multiple_answer');
@@ -490,7 +489,7 @@ class FillBlanks extends Question
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="'.$this->question_table_class.'">
             <tr>
-                <th>'.get_lang("Answer").'</th>
+                <th>'.get_lang('Answer').'</th>
             </tr>';
 
         return $header;
@@ -505,6 +504,7 @@ class FillBlanks extends Question
      * @param array  $listAnswersInfo
      * @param bool   $displayForStudent
      * @param int    $inBlankNumber
+     * @param string $labelId
      *
      * @return string
      */
@@ -516,12 +516,16 @@ class FillBlanks extends Question
         $answer,
         $listAnswersInfo,
         $displayForStudent,
-        $inBlankNumber
+        $inBlankNumber,
+        $labelId = ''
     ) {
         $inTabTeacherSolution = $listAnswersInfo['words'];
         $inTeacherSolution = $inTabTeacherSolution[$inBlankNumber];
 
-        $labelId = 'choice_id_'.$currentQuestion.'_'.$inBlankNumber;
+        if (empty($labelId)) {
+            $labelId = 'choice_id_'.$currentQuestion.'_'.$inBlankNumber;
+        }
+
         switch (self::getFillTheBlankAnswerType($inTeacherSolution)) {
             case self::FILL_THE_BLANK_MENU:
                 $selected = '';
@@ -749,7 +753,7 @@ class FillBlanks extends Question
         }
 
         // Take the complete string except after the last '::'
-        $listDetails = explode(":", $listArobaseSplit[0]);
+        $listDetails = explode(':', $listArobaseSplit[0]);
 
         // < number of item after the ::[score]:[size]:[separator_id]@ , here there are 3
         if (count($listDetails) < 3) {
@@ -832,9 +836,9 @@ class FillBlanks extends Question
             $listAnswerResults['words_with_bracket'] = $listBrackets;
 
             // if we are in student view, we've got 3 times :::::: for common words
-            $commonWords = api_preg_replace("/::::::/", "::", $commonWords);
+            $commonWords = api_preg_replace("/::::::/", '::', $commonWords);
         }
-        $listAnswerResults['common_words'] = explode("::", $commonWords);
+        $listAnswerResults['common_words'] = explode('::', $commonWords);
 
         return $listAnswerResults;
     }
@@ -845,7 +849,7 @@ class FillBlanks extends Question
      * -2  : didn't answer
      * -1  : student answer is wrong
      *  0  : student answer is correct
-     * >0  : choice menu, is the index of the student answer (right answer index is 0).
+     * >0  : fill the blank question with choice menu, is the index of the student answer (right answer index is 0).
      *
      * @param int $testId
      * @param int $questionId
@@ -864,7 +868,7 @@ class FillBlanks extends Question
      *               )
      *               )
      */
-    public static function getFillTheBlankTabResult(
+    public static function getFillTheBlankResult(
         $testId,
         $questionId,
         $studentsIdList,
@@ -899,52 +903,52 @@ class FillBlanks extends Question
         ';
 
         $res = Database::query($sql);
-        $tabUserResult = [];
+        $userResult = [];
         // foreach attempts for all students starting with his older attempt
         while ($data = Database::fetch_array($res)) {
-            $tabAnswer = self::getAnswerInfo($data['answer'], true);
+            $answer = self::getAnswerInfo($data['answer'], true);
 
             // for each bracket to find in this question
-            foreach ($tabAnswer['student_answer'] as $bracketNumber => $studentAnswer) {
-                if ($tabAnswer['student_answer'][$bracketNumber] != '') {
+            foreach ($answer['student_answer'] as $bracketNumber => $studentAnswer) {
+                if ($answer['student_answer'][$bracketNumber] != '') {
                     // student has answered this bracket, cool
-                    switch (self::getFillTheBlankAnswerType($tabAnswer['words'][$bracketNumber])) {
+                    switch (self::getFillTheBlankAnswerType($answer['words'][$bracketNumber])) {
                         case self::FILL_THE_BLANK_MENU:
                             // get the indice of the choosen answer in the menu
                             // we know that the right answer is the first entry of the menu, ie 0
                             // (remember, menu entries are shuffled when taking the test)
-                            $tabUserResult[$data['user_id']][$bracketNumber] = self::getFillTheBlankMenuAnswerNum(
-                                $tabAnswer['words'][$bracketNumber],
-                                $tabAnswer['student_answer'][$bracketNumber]
+                            $userResult[$data['user_id']][$bracketNumber] = self::getFillTheBlankMenuAnswerNum(
+                                $answer['words'][$bracketNumber],
+                                $answer['student_answer'][$bracketNumber]
                             );
                             break;
                         default:
                             if (self::isStudentAnswerGood(
-                                $tabAnswer['student_answer'][$bracketNumber],
-                                $tabAnswer['words'][$bracketNumber]
+                                $answer['student_answer'][$bracketNumber],
+                                $answer['words'][$bracketNumber]
                             )
                             ) {
-                                $tabUserResult[$data['user_id']][$bracketNumber] = 0; //  right answer
+                                $userResult[$data['user_id']][$bracketNumber] = 0; //  right answer
                             } else {
-                                $tabUserResult[$data['user_id']][$bracketNumber] = -1; // wrong answer
+                                $userResult[$data['user_id']][$bracketNumber] = -1; // wrong answer
                             }
                     }
                 } else {
                     // student didn't answer this bracket
                     if ($useLastAnsweredAttempt) {
                         // if we take into account the last answered attempt
-                        if (!isset($tabUserResult[$data['user_id']][$bracketNumber])) {
-                            $tabUserResult[$data['user_id']][$bracketNumber] = -2; // not answered
+                        if (!isset($userResult[$data['user_id']][$bracketNumber])) {
+                            $userResult[$data['user_id']][$bracketNumber] = -2; // not answered
                         }
                     } else {
                         // we take the last attempt, even if the student answer the question before
-                        $tabUserResult[$data['user_id']][$bracketNumber] = -2; // not answered
+                        $userResult[$data['user_id']][$bracketNumber] = -2; // not answered
                     }
                 }
             }
         }
 
-        return $tabUserResult;
+        return $userResult;
     }
 
     /**
@@ -992,7 +996,7 @@ class FillBlanks extends Question
             $result .= $separatorStart.$listWithStudentAnswer['student_score'][$i].$separatorEnd;
         }
         $result .= $listWithStudentAnswer['common_words'][$i];
-        $result .= "::";
+        $result .= '::';
         // add the system string
         $result .= $listWithStudentAnswer['system_string'];
 
@@ -1133,7 +1137,7 @@ class FillBlanks extends Question
         $listResults = [];
         $allowedSeparator = self::getAllowedSeparator();
         foreach ($allowedSeparator as $part) {
-            $listResults[] = $part[0]."...".$part[1];
+            $listResults[] = $part[0].'...'.$part[1];
         }
 
         return $listResults;
@@ -1267,11 +1271,11 @@ class FillBlanks extends Question
             case self::FILL_THE_BLANK_MENU:
                 $listPossibleAnswers = self::getFillTheBlankMenuAnswers($correct, false);
                 $correctAnswerHtml .= "<span class='correct-answer'><strong>".$listPossibleAnswers[0]."</strong>";
-                $correctAnswerHtml .= " (";
+                $correctAnswerHtml .= ' (';
                 for ($i = 1; $i < count($listPossibleAnswers); $i++) {
                     $correctAnswerHtml .= $listPossibleAnswers[$i];
                     if ($i != count($listPossibleAnswers) - 1) {
-                        $correctAnswerHtml .= " | ";
+                        $correctAnswerHtml .= ' | ';
                     }
                 }
                 $correctAnswerHtml .= ")</span>";
@@ -1299,7 +1303,7 @@ class FillBlanks extends Question
         $result .= $iconAnswer."<span class='$style'>".$answer."</span>";
         $result .= "<span class='feedback-separator'>|</span>";
         $result .= $correctAnswerHtml;
-        $result .= "</span>";
+        $result .= '</span>';
 
         return $result;
     }
@@ -1409,7 +1413,7 @@ class FillBlanks extends Question
     private static function trimOption($text)
     {
         $text = trim($text);
-        $text = preg_replace("/\s+/", " ", $text);
+        $text = preg_replace("/\s+/", ' ', $text);
 
         return $text;
     }

+ 2 - 1
main/exercise/hotpotatoes.php

@@ -36,7 +36,8 @@ $is_allowedToEdit = api_is_allowed_to_edit(null, true);
 
 // Database table definitions.
 $dbTable = Database::get_course_table(TABLE_DOCUMENT);
-$course_id = api_get_course_int_id();
+$course_id = $_course['real_id'];
+$sessionId = api_get_session_id();
 
 // Setting some variables.
 $document_sys_path = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';

+ 9 - 0
main/exercise/hotspot_answers.as.php

@@ -21,6 +21,11 @@ $exeId = isset($_GET['exeId']) ? (int) $_GET['exeId'] : 0;
 $userId = api_get_user_id();
 $courseId = api_get_course_int_id();
 $objExercise = new Exercise($courseId);
+$debug = false;
+
+if ($debug) {
+    error_log("Call to hotspot_answers.as.php");
+}
 
 // Check if student has access to the hotspot answers
 if (!api_is_allowed_to_edit(null, true)) {
@@ -191,3 +196,7 @@ $data['done'] = 'done';
 header('Content-Type: application/json');
 
 echo json_encode($data);
+
+if ($debug) {
+    error_log("---------- End call to hotspot_answers.as.php------------");
+}

+ 25 - 15
main/exercise/question.class.php

@@ -50,6 +50,10 @@ abstract class Question
         MULTIPLE_ANSWER_COMBINATION => ['multiple_answer_combination.class.php', 'MultipleAnswerCombination'],
         UNIQUE_ANSWER_NO_OPTION => ['unique_answer_no_option.class.php', 'UniqueAnswerNoOption'],
         MULTIPLE_ANSWER_TRUE_FALSE => ['multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'],
+        MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY => [
+            'MultipleAnswerTrueFalseDegreeCertainty.php',
+            'MultipleAnswerTrueFalseDegreeCertainty',
+        ],
         MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => [
             'multiple_answer_combination_true_false.class.php',
             'MultipleAnswerCombinationTrueFalse',
@@ -124,7 +128,7 @@ abstract class Question
      */
     public static function read($id, $course_id = null)
     {
-        $id = intval($id);
+        $id = (int) $id;
         if (!empty($course_id)) {
             $course_info = api_get_course_info_by_id($course_id);
         } else {
@@ -494,8 +498,8 @@ abstract class Question
 
             // update or add category for a question
             foreach ($category_list as $category_id) {
-                $category_id = intval($category_id);
-                $question_id = intval($this->id);
+                $category_id = (int) $category_id;
+                $question_id = (int) $this->id;
                 $sql = "SELECT count(*) AS nb
                         FROM $table
                         WHERE
@@ -1325,14 +1329,14 @@ abstract class Question
         $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
         $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
 
-        $id = intval($this->id);
+        $id = (int) $this->id;
 
         // if the question must be removed from all exercises
         if (!$deleteFromEx) {
             //update the question_order of each question to avoid inconsistencies
             $sql = "SELECT exercice_id, question_order 
                     FROM $TBL_EXERCISE_QUESTION
-                    WHERE c_id = $course_id AND question_id = ".intval($id)."";
+                    WHERE c_id = $course_id AND question_id = ".intval($id);
 
             $res = Database::query($sql);
             if (Database::num_rows($res) > 0) {
@@ -1532,12 +1536,12 @@ abstract class Question
     public static function getInstance($type)
     {
         if (!is_null($type)) {
-            list($file_name, $class_name) = self::get_question_type($type);
-            if (!empty($file_name)) {
-                if (class_exists($class_name)) {
-                    return new $class_name();
+            list($fileName, $className) = self::get_question_type($type);
+            if (!empty($fileName)) {
+                if (class_exists($className)) {
+                    return new $className();
                 } else {
-                    echo 'Can\'t instanciate class '.$class_name.' of type '.$type;
+                    echo 'Can\'t instanciate class '.$className.' of type '.$type;
                 }
             }
         }
@@ -1791,7 +1795,8 @@ abstract class Question
             unset($question_type_custom_list[HOT_SPOT_DELINEATION]);
         }
 
-        echo '<div class="well">';
+        echo '<div class="panel panel-default">';
+        echo '<div class="panel-body">';
         echo '<ul class="question_menu">';
         foreach ($question_type_custom_list as $i => $a_type) {
             // @todo remove require_once classes are already loaded using composer
@@ -1845,6 +1850,7 @@ abstract class Question
         echo '</div></li>';
         echo '</ul>';
         echo '</div>';
+        echo '</div>';
     }
 
     /**
@@ -1858,7 +1864,7 @@ abstract class Question
     public static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
     {
         $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
-        $params['question_id'] = intval($question_id);
+        $params['question_id'] = (int) $question_id;
         $params['name'] = $name;
         $params['position'] = $position;
         $params['c_id'] = $course_id;
@@ -1948,7 +1954,7 @@ abstract class Question
     {
         $counterLabel = '';
         if (!empty($counter)) {
-            $counterLabel = intval($counter);
+            $counterLabel = (int) $counter;
         }
         $score_label = get_lang('Wrong');
         $class = 'error';
@@ -1969,7 +1975,7 @@ abstract class Question
                 $score['result'] = " ? / ".$weight;
                 $model = ExerciseLib::getCourseScoreModel();
                 if (!empty($model)) {
-                    $score['result'] = " ? ";
+                    $score['result'] = ' ? ';
                 }
 
                 $hide = api_get_configuration_value('hide_free_question_score');
@@ -1993,7 +1999,11 @@ abstract class Question
             'missing' => $score['weight'],
         ];
         $header .= Display::page_subheader2($counterLabel.'. '.$this->question);
-        $header .= $exercise->getQuestionRibbon($class, $score_label, $score['result'], $scoreCurrent);
+        // dont display score for certainty degree questions
+        if ($this->type != MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+            $header .= $exercise->getQuestionRibbon($class, $score_label, $score['result'], $scoreCurrent);
+        }
+
         if ($this->type != READING_COMPREHENSION) {
             // Do not show the description (the text to read) if the question is of type READING_COMPREHENSION
             $header .= Display::div(

+ 2 - 2
main/exercise/question_admin.inc.php

@@ -23,11 +23,11 @@ if (is_object($objQuestion)) {
     // FORM CREATION
     $form = new FormValidator('question_admin_form', 'post', $action);
     if (isset($_GET['editQuestion'])) {
-        $class = "btn btn-default";
+        $class = 'btn btn-default';
         $text = get_lang('ModifyQuestion');
         $type = isset($_GET['type']) ? Security::remove_XSS($_GET['type']) : null;
     } else {
-        $class = "btn btn-default";
+        $class = 'btn btn-default';
         $text = get_lang('AddQuestionToExercise');
         $type = $_REQUEST['answerType'];
     }

+ 9 - 10
main/exercise/question_list_admin.inc.php

@@ -163,11 +163,11 @@ if (!$inATest) {
     echo '
         <div class="row hidden-xs">
             <div class="col-sm-5"><strong>'.get_lang('Questions').'</strong></div>
-            <div class="col-sm-1"><strong>'.get_lang('Type').'</strong></div>
+            <div class="col-sm-1 text-center"><strong>'.get_lang('Type').'</strong></div>
             <div class="col-sm-2"><strong>'.get_lang('Category').'</strong></div>
-            <div class="col-sm-1"><strong>'.get_lang('Difficulty').'</strong></div>
-            <div class="col-sm-1"><strong>'.get_lang('MaximumScore').'</strong></div>
-            <div class="col-sm-2"><strong>'.get_lang('Actions').'</strong></div>
+            <div class="col-sm-1 text-right"><strong>'.get_lang('Difficulty').'</strong></div>
+            <div class="col-sm-1 text-right"><strong>'.get_lang('MaximumScore').'</strong></div>
+            <div class="col-sm-2 text-right"><strong>'.get_lang('Actions').'</strong></div>
         </div>
         <div id="question_list">
     ';
@@ -213,8 +213,7 @@ if (!$inATest) {
                     ['class' => 'btn btn-default btn-sm']
                 );
                 $edit_link = ($objQuestionTmp->type == CALCULATED_ANSWER && $objQuestionTmp->isAnswered())
-                    ? Display::button(
-                        'edit',
+                    ? Display::span(
                         Display::return_icon(
                             'edit_na.png',
                             get_lang('QuestionEditionNotAvailableBecauseItIsAlreadyAnsweredHoweverYouCanCopyItAndModifyTheCopy'),
@@ -305,7 +304,7 @@ if (!$inATest) {
                                 <div class="question col-sm-5 col-xs-12">'
                                     .$questionName.'
                                 </div>
-                                <div class="type col-sm-1 col-xs-12">
+                                <div class="type text-center col-sm-1 col-xs-12">
                                     <span class="visible-xs-inline">'.get_lang('Type').' </span>'
                                     .$questionType.'
                                 </div>
@@ -313,15 +312,15 @@ if (!$inATest) {
                                     <span class="visible-xs-inline">'.get_lang('Category').' </span>'
                                     .cut($txtQuestionCat, 42).'
                                 </div>
-                                <div class="level col-sm-1 col-xs-6">
+                                <div class="level text-right col-sm-1 col-xs-6">
                                     <span class="visible-xs-inline">'.get_lang('Difficulty').' </span>'
                                     .$questionLevel.'
                                 </div>
-                                <div class="score col-sm-1 col-xs-6">
+                                <div class="score text-right col-sm-1 col-xs-6">
                                     <span class="visible-xs-inline">'.get_lang('Score').' </span>'
                                     .$questionScore.'
                                 </div>
-                                <div class="btn-actions col-sm-2 col-xs-6">
+                                <div class="btn-actions text-right col-sm-2 col-xs-6">
                                     <div class="edition">'.$btnActions.'</div>
                                 </div>
                             </div>

+ 0 - 3
main/forum/editthread.php

@@ -253,9 +253,7 @@ if ($form->validate()) {
         $values = $form->exportValues();
         Security::clear_token();
         updateThread($values);
-
         Skill::saveSkills($form, ITEM_TYPE_FORUM_THREAD, $threadId);
-
         header('Location: '.$redirectUrl);
         exit;
     }
@@ -265,7 +263,6 @@ $form->setDefaults(isset($defaults) ? $defaults : null);
 $token = Security::get_token();
 $form->addElement('hidden', 'sec_token');
 $form->setConstants(['sec_token' => $token]);
-
 $originIsLearnPath = $origin == 'learnpath';
 
 $view = new Template(

+ 8 - 7
main/forum/forumfunction.inc.php

@@ -958,8 +958,10 @@ function deleteForumCategoryThread($content, $id)
 }
 
 /**
- * This function deletes a forum post. This separate function is needed because forum posts do not appear in the item_property table (yet)
- * and because deleting a post also has consequence on the posts that have this post as parent_id (they are also deleted).
+ * This function deletes a forum post. This separate function is needed because forum posts do not appear
+ * in the item_property table (yet)
+ * and because deleting a post also has consequence on the posts that have this post as parent_id
+ * (they are also deleted).
  * an alternative would be to store the posts also in item_property and mark this post as deleted (visibility = 2).
  * We also have to decrease the number of replies in the thread table.
  *
@@ -1066,7 +1068,7 @@ function check_if_last_post_of_thread($thread_id)
 }
 
 /**
- * @param string $content                   what is it that we want to make (in)visible: forum category, forum, thread, post
+ * @param string $content                   Type of content forum category, forum, thread, post
  * @param int    $id                        the id of the content we want to make invisible
  * @param int    $current_visibility_status what is the current status of the visibility (0 = invisible, 1 = visible)
  * @param array  $additional_url_parameters
@@ -1161,9 +1163,9 @@ function return_lock_unlock_icon($content, $id, $current_lock_status, $additiona
  *
  * @param string $content what is it that we want to make (in)visible: forum category, forum, thread, post
  * @param int    $id      is the id of the item we want to display the icons for
- * @param $list is an array of all the items. All items in this list should have
- * an up and down icon except for the first (no up icon) and the last (no down icon)
- *          The key of this $list array is the id of the item.
+ * @param array  $list    is an array of all the items. All items in this list should have
+ *                        an up and down icon except for the first (no up icon) and the last (no down icon)
+ *                        The key of this $list array is the id of the item.
  *
  * @return string HTML
  */
@@ -1377,7 +1379,6 @@ function move_up_down($content, $direction, $id)
     $result = Database::query($sql);
     $found = false;
     while ($row = Database::fetch_array($result)) {
-        //echo $row[$id_column].'-';
         if ($found) {
             $next_id = $row[$id_column];
             $next_sort = $row[$sort_column];

+ 0 - 2
main/forum/forumqualify.php

@@ -267,9 +267,7 @@ if (isset($rows)) {
 
         echo "<tr>";
         echo "<td rowspan=\"3\" class=\"$leftclass\">";
-
         echo '<br /><b>'.api_convert_and_format_date($row['post_date'], DATE_TIME_FORMAT_LONG).'</b><br />';
-
         echo "</td>";
 
         // The post title

+ 1 - 1
main/forum/index.php

@@ -519,7 +519,7 @@ if (is_array($forumCategories)) {
 }
 
 $tpl = new Template($nameTools);
-$tpl->assign('introduction_section', $introduction);
+$tpl->assign('introduction', $introduction);
 $tpl->assign('actions', $actions);
 $tpl->assign('data', $listForumCategory);
 $tpl->assign('form_content', $formContent);

+ 3 - 3
main/forum/viewforumcategory.php

@@ -164,14 +164,14 @@ if ($action != 'add') {
     $html = '';
     $html .= '<div class="category-forum">';
 
+    $session_displayed = '';
     if ((!isset($sessionId) || $sessionId == 0) &&
         !empty($forum_category['session_name'])
     ) {
         $session_displayed = ' ('.Security::remove_XSS($forum_category['session_name']).')';
-    } else {
-        $session_displayed = '';
     }
-    $forum_categories_list = '';
+
+    $forum_categories_list = [];
     $forumId = $forum_category['cat_id'];
     $forumTitle = $forum_category['cat_title'];
     $linkForumCategory = 'viewforumcategory.php?'.api_get_cidreq().'&forumcategory='.strval(intval($forumId));

+ 16 - 10
main/gradebook/gradebook_display_certificate.php

@@ -13,8 +13,7 @@ if (!api_is_student_boss()) {
     api_protect_course_script(true);
 }
 
-set_time_limit(0);
-ini_set('max_execution_time', 0);
+api_set_more_memory_and_time_limits();
 
 //extra javascript functions for in html head:
 $htmlHeadXtra[] = "<script>
@@ -128,17 +127,24 @@ switch ($action) {
         $content = $form->returnForm();
         break;
     case 'export_all_certificates':
-        if (api_is_student_boss()) {
-            $userGroup = new UserGroup();
-            $userList = $userGroup->getGroupUsersByUser(api_get_user_id());
+        $courseCode = api_get_course_id();
+        if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') == 'true' &&
+            api_get_course_setting('customcertificate_course_enable', $courseCode) == 1
+        ) {
+            $url = api_get_path(WEB_PLUGIN_PATH).'customcertificate/src/print_certificate.php?export_all=1';
         } else {
-            $userList = [];
-            if (!empty($filterOfficialCodeGet)) {
-                $userList = UserManager::getUsersByOfficialCode($filterOfficialCodeGet);
+            if (api_is_student_boss()) {
+                $userGroup = new UserGroup();
+                $userList = $userGroup->getGroupUsersByUser(api_get_user_id());
+            } else {
+                $userList = [];
+                if (!empty($filterOfficialCodeGet)) {
+                    $userList = UserManager::getUsersByOfficialCode($filterOfficialCodeGet);
+                }
             }
-        }
 
-        Category::exportAllCertificates($categoryId, $userList);
+            Category::exportAllCertificates($categoryId, $userList);
+        }
         header('Location: '.$url);
         exit;
         break;

+ 1 - 2
main/gradebook/gradebook_display_summary.php

@@ -12,8 +12,7 @@ require_once __DIR__.'/../inc/global.inc.php';
 $current_course_tool = TOOL_GRADEBOOK;
 
 api_protect_course_script();
-set_time_limit(0);
-ini_set('max_execution_time', 0);
+api_set_more_memory_and_time_limits();
 api_block_anonymous_users();
 
 GradebookUtils::block_students();

+ 65 - 68
main/gradebook/index.php

@@ -14,13 +14,15 @@ $_in_course = true;
 require_once __DIR__.'/../inc/global.inc.php';
 $current_course_tool = TOOL_GRADEBOOK;
 
-ob_start();
-
+api_block_anonymous_users();
 api_protect_course_script(true);
 
+ob_start();
+
 $course_code = api_get_course_id();
 $stud_id = api_get_user_id();
 $session_id = api_get_session_id();
+$course_id = api_get_course_int_id();
 
 //make sure the destination for scripts is index.php instead of gradebook.php
 Category::setUrl('index.php');
@@ -31,6 +33,14 @@ $htmlHeadXtra[] = '<script>
 var show_icon = "'.Display::returnIconPath('view_more_stats.gif').'";
 var hide_icon = "'.Display::returnIconPath('view_less_stats.gif').'";
 
+function confirmation() {
+	if (confirm("'.get_lang('DeleteAll').'?")) {
+		return true;
+	} else {
+		return false;
+	}
+}
+
 $(document).ready(function() {
     $("body").on("click", ".view_children", function() {
         var id = $(this).attr("data-cat-id");
@@ -47,29 +57,7 @@ $(document).ready(function() {
         $(this).addClass("view_children");
         $(this).find("img").attr("src", show_icon);
     });
-/*
-  var s1 = [["a",25]];
-  var s2 = [["a", 0], ["a", 10], ["a", 10], ["a", 5]];
-
-  var plot3 = $.jqplot("chart3", [s1, s2], {
-  colors: ["#000", "#fff"],
-    seriesDefaults: {
-      // make this a donut chart.
-      renderer:$.jqplot.DonutRenderer,
-      rendererOptions:{
-        // Donuts can be cut into slices like pies.
-        sliceMargin: 3 ,
-        // Pies and donuts can start at any arbitrary angle.
-        startAngle: -90,
-        showDataLabels: true,
-        // By default, data labels show the percentage of the donut/pie.
-        // You can show the data "value" or data "label" instead.
-        dataLabels: "value"
-
-      }
-    }
-  });*/
-
+    
 	for (i=0;i<$(".actions").length;i++) {
 		if ($(".actions:eq("+i+")").html()=="<table border=\"0\"></table>" || $(".actions:eq("+i+")").html()=="" || $(".actions:eq("+i+")").html()==null || $(".actions:eq("+i+")").html().split("<TBODY></TBODY>").length==2) {
 			$(".actions:eq("+i+")").hide();
@@ -77,16 +65,6 @@ $(document).ready(function() {
 	}
 });
 </script>';
-api_block_anonymous_users();
-$htmlHeadXtra[] = '<script type="text/javascript">
-function confirmation() {
-	if (confirm("'.get_lang('DeleteAll').'?")) {
-		return true;
-	} else {
-		return false;
-	}
-}
-</script>';
 
 $tbl_forum_thread = Database::get_course_table(TABLE_FORUM_THREAD);
 $tbl_attendance = Database::get_course_table(TABLE_ATTENDANCE);
@@ -242,7 +220,6 @@ if (isset($_GET['movelink'])) {
     if ($move_form->validate()) {
         $targetcat = Category::load($move_form->exportValue('move_cat'));
         $link[0]->move_to_cat($targetcat[0]);
-        unset($link);
         header('Location: '.api_get_self().'?linkmoved=&selectcat='.$selectCat.'&'.api_get_cidreq());
         exit;
     }
@@ -270,6 +247,7 @@ if (isset($_GET['visiblecat'])) {
         $filter_confirm_msg = false;
     }
 }
+
 if (isset($_GET['deletecat'])) {
     GradebookUtils::block_students();
     $cats = Category::load($_GET['deletecat']);
@@ -359,8 +337,6 @@ if (isset($_GET['visiblelink'])) {
     }
 }
 
-$course_id = api_get_course_int_id();
-
 if (isset($_GET['deletelink'])) {
     GradebookUtils::block_students();
     $get_delete_link = intval($_GET['deletelink']);
@@ -402,7 +378,7 @@ if (!empty($course_to_crsind) && !isset($_GET['confirm'])) {
     }
     $button = '<form name="confirm" method="post" action="'.api_get_self().'?confirm='
         .(isset($_GET['movecat']) ? '&movecat='.intval($_GET['movecat'])
-            : '&moveeval='.Security::remove_XSS($_GET['moveeval'])).'&selectcat='.$selectCat.'&targetcat='.Security::remove_XSS($_GET['targetcat']).'">
+            : '&moveeval='.intval($_GET['moveeval'])).'&selectcat='.$selectCat.'&targetcat='.intval($_GET['targetcat']).'">
 			   <input type="submit" value="'.get_lang('Ok').'">
 			   </form>';
     $warning_message = get_lang('MoveWarning').'<br><br>'.$button;
@@ -425,6 +401,11 @@ switch ($action) {
         }
         break;
     case 'export_table':
+        $hidePdfReport = api_get_configuration_value('gradebook_hide_pdf_report_button');
+        if ($hidePdfReport) {
+            api_not_allowed(true);
+        }
+
         //table will be export below
         ob_start();
         break;
@@ -472,7 +453,12 @@ if (isset($_POST['action'])) {
                         }
                     }
                 }
-                $confirmation_message = get_lang('DeletedCategories').' : <b>'.$number_of_deleted_categories.'</b><br />'.get_lang('DeletedEvaluations').' : <b>'.$number_of_deleted_evaluations.'</b><br />'.get_lang('DeletedLinks').' : <b>'.$number_of_deleted_links.'</b><br /><br />'.get_lang('TotalItems').' : <b>'.$number_of_selected_items.'</b>';
+
+                $confirmation_message =
+                    get_lang('DeletedCategories').' : <b>'.$number_of_deleted_categories.'</b><br />'.
+                    get_lang('DeletedEvaluations').' : <b>'.$number_of_deleted_evaluations.'</b><br />'.
+                    get_lang('DeletedLinks').' : <b>'.$number_of_deleted_links.'</b><br /><br />'.
+                    get_lang('TotalItems').' : <b>'.$number_of_selected_items.'</b>';
                 $filter_confirm_msg = false;
                 break;
             case 'setvisible':
@@ -596,7 +582,6 @@ if (!isset($_GET['exportpdf'])) {
         $viewTitle = get_lang('ToolGradebook');
     }
 }
-
 // LOAD DATA & DISPLAY TABLE
 
 $is_platform_admin = api_is_platform_admin();
@@ -633,7 +618,14 @@ if (isset($_GET['studentoverview'])) {
         $pdf->selectFont(api_get_path(LIBRARY_PATH).'ezpdf/fonts/Courier.afm');
         $pdf->ezSetMargins(30, 30, 50, 30);
         $pdf->ezSetY(810);
-        $pdf->ezText(get_lang('FlatView').' ('.api_convert_and_format_date(null, DATE_FORMAT_SHORT).' '.api_convert_and_format_date(null, TIME_NO_SEC_FORMAT).')', 12, ['justification' => 'center']);
+        $pdf->ezText(
+            get_lang('FlatView').' ('.api_convert_and_format_date(
+                null,
+                DATE_FORMAT_SHORT
+            ).' '.api_convert_and_format_date(null, TIME_NO_SEC_FORMAT).')',
+            12,
+            ['justification' => 'center']
+        );
         $pdf->line(50, 790, 550, 790);
         $pdf->line(50, 40, 550, 40);
         $pdf->ezSetY(750);
@@ -767,11 +759,14 @@ if (!empty($selectCat)) {
 }
 
 if (!api_is_allowed_to_edit(null, true)) {
-    $actionsLeft .= Display::url(
-        Display::returnFontAwesomeIcon('file-pdf-o').get_lang('DownloadReportPdf'),
-        api_get_self().'?action=export_table',
-        ['class' => 'btn btn-default']
-    );
+    $allowButton = api_get_configuration_value('gradebook_hide_pdf_report_button') === false;
+    if ($allowButton) {
+        $actionsLeft .= Display::url(
+            Display::returnFontAwesomeIcon('file-pdf-o').get_lang('DownloadReportPdf'),
+            api_get_self().'?action=export_table&'.api_get_cidreq(),
+            ['class' => 'btn btn-default']
+        );
+    }
 }
 
 if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)) {
@@ -922,11 +917,17 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
                     $exportToPdf = true;
                 }
 
+                $loadStats = [];
                 $teacher = api_is_allowed_to_edit(null, true);
-                if ($teacher) {
-                    $loadStats = false;
-                } else {
-                    $loadStats = api_get_configuration_value('disable_gradebook_stats') === false;
+
+                if (!$teacher) {
+                    if (api_get_setting('gradebook_detailed_admin_view') === 'true') {
+                        $loadStats = [1, 2, 3];
+                    } else {
+                        if (api_get_configuration_value('gradebook_enable_best_score') !== false) {
+                            $loadStats = [2];
+                        }
+                    }
                 }
 
                 $gradebookTable = new GradebookTable(
@@ -948,22 +949,13 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
                     ];
                 } else {
                     if (empty($model)) {
-                        if ($loadStats) {
-                            $gradebookTable->td_attributes = [
-                                3 => 'class="text-right"',
-                                4 => 'class="text-center"',
-                                5 => 'class="text-center"',
-                                6 => 'class="text-center"',
-                                7 => 'class="text-center"',
-                            ];
-                        } else {
-                            $gradebookTable->td_attributes = [
-                                3 => 'class="text-right"',
-                                4 => 'class="text-center"',
-                                5 => 'class="text-center"',
-                                //6 => 'class="text-center"',
-                                //7 => 'class="text-center"',
-                            ];
+                        $gradebookTable->td_attributes = [
+                            3 => 'class="text-right"',
+                            4 => 'class="text-center"',
+                        ];
+
+                        for ($z = 5; $z < count($loadStats); $z++) {
+                            $gradebookTable->td_attributes[$z] = 'class="text-center"';
                         }
                     } else {
                         $gradebookTable->td_attributes = [
@@ -978,7 +970,12 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
                 }
 
                 $table = $gradebookTable->return_table();
-                $graph = $gradebookTable->getGraph();
+
+                $allowGraph = api_get_configuration_value('gradebook_hide_graph') === false;
+                $graph = '';
+                if ($allowGraph) {
+                    $graph = $gradebookTable->getGraph();
+                }
 
                 if ($action == 'export_table') {
                     ob_clean();

+ 6 - 0
main/gradebook/lib/be/abstractlink.class.php

@@ -212,6 +212,9 @@ abstract class AbstractLink implements GradebookItem
         }
     }
 
+    /**
+     * @return array
+     */
     public function getStudentList()
     {
         if (empty($this->studentList)) {
@@ -221,6 +224,9 @@ abstract class AbstractLink implements GradebookItem
         return $this->studentList;
     }
 
+    /**
+     * @param array $list
+     */
     public function setStudentList($list)
     {
         $this->studentList = $list;

+ 7 - 7
main/gradebook/lib/be/attendancelink.class.php

@@ -82,7 +82,7 @@ class AttendanceLink extends AbstractLink
                 WHERE
                     session_id = $session_id AND
                     c_id = '.$this->course_id.' AND
-                    attendance_id = '".intval($this->get_ref_id())."'";
+                    attendance_id = '".$this->get_ref_id()."'";
         $result = Database::query($sql);
         $number = Database::fetch_row($result);
 
@@ -104,15 +104,15 @@ class AttendanceLink extends AbstractLink
                 FROM '.$this->get_attendance_table().' att
                 WHERE
                     att.c_id = '.$this->course_id.' AND
-                    att.id = '.intval($this->get_ref_id()).' AND
-                    att.session_id='.intval($session_id).'';
+                    att.id = '.$this->get_ref_id().' AND
+                    att.session_id='.$session_id;
         $query = Database::query($sql);
         $attendance = Database::fetch_array($query, 'ASSOC');
 
         // Get results
         $sql = 'SELECT *
                 FROM '.$tbl_attendance_result.'
-                WHERE c_id = '.$this->course_id.' AND attendance_id = '.intval($this->get_ref_id());
+                WHERE c_id = '.$this->course_id.' AND attendance_id = '.$this->get_ref_id();
         if (isset($stud_id)) {
             $sql .= ' AND user_id = '.intval($stud_id);
         }
@@ -218,7 +218,7 @@ class AttendanceLink extends AbstractLink
     {
         $session_id = api_get_session_id();
         $sql = 'SELECT count(att.id) FROM '.$this->get_attendance_table().' att
-                 WHERE att.c_id = '.$this->course_id.' AND att.id = '.intval($this->get_ref_id()).' ';
+                 WHERE att.c_id = '.$this->course_id.' AND att.id = '.$this->get_ref_id();
         $result = Database::query($sql);
         $number = Database::fetch_row($result);
 
@@ -230,7 +230,7 @@ class AttendanceLink extends AbstractLink
         //it was extracts the attendance id
         $session_id = api_get_session_id();
         $sql = 'SELECT * FROM '.$this->get_attendance_table().' att
-                WHERE att.c_id = '.$this->course_id.' AND att.id = '.intval($this->get_ref_id()).' ';
+                WHERE att.c_id = '.$this->course_id.' AND att.id = '.$this->get_ref_id();
         $result = Database::query($sql);
         $row = Database::fetch_array($result, 'ASSOC');
         $attendance_id = $row['id'];
@@ -268,7 +268,7 @@ class AttendanceLink extends AbstractLink
             return false;
         } elseif (!isset($this->attendance_data)) {
             $sql = 'SELECT * FROM '.$this->get_attendance_table().' att
-                    WHERE att.c_id = '.$this->course_id.' AND att.id = '.intval($this->get_ref_id()).' ';
+                    WHERE att.c_id = '.$this->course_id.' AND att.id = '.$this->get_ref_id();
             $query = Database::query($sql);
             $this->attendance_data = Database::fetch_array($query);
         }

+ 26 - 11
main/gradebook/lib/be/exerciselink.class.php

@@ -166,8 +166,9 @@ class ExerciseLink extends AbstractLink
     /**
      * Get the score of this exercise. Only the first attempts are taken into account.
      *
-     * @param int $stud_id student id (default: all students who have results -
-     *                     then the average is returned)
+     * @param int    $stud_id student id (default: all students who have results -
+     *                        then the average is returned)
+     * @param string $type
      *
      * @return array (score, max) if student is given
      *               array (sum of scores, number of scores) otherwise
@@ -260,7 +261,20 @@ class ExerciseLink extends AbstractLink
             $weight = 0;
             $sumResult = 0;
 
+            $studentList = $this->getStudentList();
+            $studentIdList = [];
+            if (!empty($studentList)) {
+                $studentIdList = array_column($studentList, 'user_id');
+            }
+
             while ($data = Database::fetch_array($scores, 'ASSOC')) {
+                // Only take into account users in the current student list.
+                if (!empty($studentIdList)) {
+                    if (!in_array($data['exe_user_id'], $studentIdList)) {
+                        continue;
+                    }
+                }
+
                 if (!isset($students[$data['exe_user_id']])) {
                     if ($data['exe_weighting'] != 0) {
                         $students[$data['exe_user_id']] = $data['exe_result'];
@@ -333,7 +347,7 @@ class ExerciseLink extends AbstractLink
      */
     public function get_name()
     {
-        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code."/document";
+        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
         require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
         $data = $this->get_exercise_data();
 
@@ -436,18 +450,18 @@ class ExerciseLink extends AbstractLink
     {
         $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
         if ($this->is_hp == 1) {
-            $tbl_exercise = Database::get_course_table(TABLE_DOCUMENT);
+            $table = Database::get_course_table(TABLE_DOCUMENT);
         } else {
-            $tbl_exercise = $this->get_exercise_table();
+            $table = $this->get_exercise_table();
         }
 
         $exerciseId = $this->get_ref_id();
 
-        if ($tbl_exercise == '') {
+        if ($table == '') {
             return false;
         } elseif (empty($this->exercise_data)) {
             if ($this->is_hp == 1) {
-                $sql = "SELECT * FROM $tbl_exercise ex
+                $sql = "SELECT * FROM $table ex
                         INNER JOIN $tableItemProperty ip
                         ON (ip.ref = ex.id AND ip.c_id = ex.c_id)
                         WHERE
@@ -461,10 +475,11 @@ class ExerciseLink extends AbstractLink
                 $result = Database::query($sql);
                 $this->exercise_data = Database::fetch_array($result);
             } else {
-                $sql = 'SELECT * FROM '.$tbl_exercise.'
+                // Try with iid
+                $sql = 'SELECT * FROM '.$table.'
                         WHERE
                             c_id = '.$this->course_id.' AND
-                            id = '.$exerciseId;
+                            iid = '.$exerciseId;
                 $result = Database::query($sql);
                 $rows = Database::num_rows($result);
 
@@ -472,10 +487,10 @@ class ExerciseLink extends AbstractLink
                     $this->exercise_data = Database::fetch_array($result);
                 } else {
                     // Try wit iid
-                    $sql = 'SELECT * FROM '.$tbl_exercise.'
+                    $sql = 'SELECT * FROM '.$table.'
                             WHERE
                                 c_id = '.$this->course_id.' AND
-                                iid = '.$exerciseId;
+                                id = '.$exerciseId;
                     $result = Database::query($sql);
                     $this->exercise_data = Database::fetch_array($result);
                 }

+ 12 - 1
main/gradebook/lib/fe/displaygradebook.php

@@ -463,8 +463,19 @@ class DisplayGradebook
                     // Right icons
                     $actionsRight = '<a href="gradebook_edit_cat.php?editcat='.$catobj->get_id().'&cidReq='.$catobj->get_course_code().'&id_session='.$catobj->get_session_id().'">'.
                         Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_MEDIUM).'</a>';
-                    $actionsRight .= '<a href="../document/document.php?curdirpath=/certificates&'.$my_api_cidreq.'&origin=gradebook&selectcat='.$catobj->get_id().'">'.
+                    $courseCode = api_get_course_id();
+                    if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') == 'true' &&
+                        api_get_course_setting('customcertificate_course_enable', $courseCode) == 1
+                    ) {
+                        $actionsRight .= '<a href="'.api_get_path(WEB_PLUGIN_PATH).'customcertificate/src/index.php?'.
+                            $my_api_cidreq.'&origin=gradebook&selectcat='.$catobj->get_id().'">'.
                             Display::return_icon('certificate.png', get_lang('AttachCertificate'), '', ICON_SIZE_MEDIUM).'</a>';
+                    } else {
+                        $actionsRight .= '<a href="'.api_get_path(WEB_CODE_PATH).
+                            'document/document.php?curdirpath=/certificates&'.
+                            $my_api_cidreq.'&origin=gradebook&selectcat='.$catobj->get_id().'">'.
+                            Display::return_icon('certificate.png', get_lang('AttachCertificate'), '', ICON_SIZE_MEDIUM).'</a>';
+                    }
 
                     if (empty($categories)) {
                         $actionsRight .= '<a href="gradebook_edit_all.php?id_session='.api_get_session_id().'&'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.

+ 2 - 0
main/gradebook/lib/fe/flatviewtable.class.php

@@ -117,6 +117,8 @@ class FlatViewTable extends SortableTable
         array_shift($headerName);
         // Removing last name
         array_shift($headerName);
+        // Removing username
+        array_shift($headerName);
 
         $pre_result = $new_result = [];
         foreach ($user_results as $result) {

+ 97 - 95
main/gradebook/lib/fe/gradebooktable.class.php

@@ -20,12 +20,19 @@ class GradebookTable extends SortableTable
     public $exportToPdf;
     public $teacherView;
     public $userId;
-    public $studentList;
+    public $studentList = [];
     private $currentcat;
     private $datagen;
     private $evals_links;
     private $dataForGraph;
-    private $loadStats = true;
+    /**
+     * @var array Indicates which columns should be shown in gradebook
+     *
+     * @example [1] For add Ranking column
+     *          [2] For add Best Score column
+     *          [3] For add Average column
+     */
+    private $loadStats = [];
 
     /**
      * GradebookTable constructor.
@@ -39,6 +46,7 @@ class GradebookTable extends SortableTable
      * @param null     $showTeacherView
      * @param int      $userId
      * @param array    $studentList
+     * @param array    $loadStats
      */
     public function __construct(
         $currentcat,
@@ -50,7 +58,7 @@ class GradebookTable extends SortableTable
         $showTeacherView = null,
         $userId = null,
         $studentList = [],
-        $loadStats = true
+        array $loadStats = []
     ) {
         $this->teacherView = is_null($showTeacherView) ? api_is_allowed_to_edit(null, true) : $showTeacherView;
         $this->userId = is_null($userId) ? api_get_user_id() : $userId;
@@ -107,11 +115,13 @@ class GradebookTable extends SortableTable
             $this->set_header($column++, get_lang('Weight'), false);
             $this->set_header($column++, get_lang('Result'), false);
             if (empty($model)) {
-                if ($this->loadStats) {
+                if (in_array(1, $this->loadStats)) {
                     $this->set_header($column++, get_lang('Ranking'), false);
                 }
-                $this->set_header($column++, get_lang('BestScore'), false);
-                if ($this->loadStats) {
+                if (in_array(2, $this->loadStats)) {
+                    $this->set_header($column++, get_lang('BestScore'), false);
+                }
+                if (in_array(3, $this->loadStats)) {
                     $this->set_header($column++, get_lang('Average'), false);
                 }
             }
@@ -183,8 +193,10 @@ class GradebookTable extends SortableTable
     {
         //variables load in index.php
         global $certificate_min_score;
+
+        $isAllowedToEdit = api_is_allowed_to_edit();
         // determine sorting type
-        $col_adjust = api_is_allowed_to_edit() ? 1 : 0;
+        $col_adjust = $isAllowedToEdit ? 1 : 0;
         // By id
         $this->column = 5;
 
@@ -284,8 +296,8 @@ class GradebookTable extends SortableTable
                 $item = $mainCategory = $data[0];
 
                 //if the item is invisible, wrap it in a span with class invisible
-                $invisibility_span_open = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '<span class="text-muted">' : '';
-                $invisibility_span_close = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '</span>' : '';
+                $invisibility_span_open = $isAllowedToEdit && $item->is_visible() == '0' ? '<span class="text-muted">' : '';
+                $invisibility_span_close = $isAllowedToEdit && $item->is_visible() == '0' ? '</span>' : '';
 
                 // Id
                 if ($this->teacherView) {
@@ -413,15 +425,17 @@ class GradebookTable extends SortableTable
 
                         if (empty($model)) {
                             // Ranking
-                            if ($this->loadStats) {
+                            if (in_array(1, $this->loadStats)) {
                                 $row[] = $ranking;
                             }
 
                             // Best
-                            $row[] = $best;
+                            if (in_array(2, $this->loadStats)) {
+                                $row[] = $best;
+                            }
 
                             // Average
-                            if ($this->loadStats) {
+                            if (in_array(3, $this->loadStats)) {
                                 $row[] = $average;
                             }
                         }
@@ -460,9 +474,11 @@ class GradebookTable extends SortableTable
                     );
 
                     if (isset($cats[0])) {
-                        $allcat = $cats[0]->get_subcategories($this->userId, $course_code, $session_id);
-                        $alleval = $cats[0]->get_evaluations($this->userId);
-                        $alllink = $cats[0]->get_links($this->userId);
+                        /** @var Category $subCategory */
+                        $subCategory = $cats[0];
+                        $allcat = $subCategory->get_subcategories($this->userId, $course_code, $session_id);
+                        $alleval = $subCategory->get_evaluations($this->userId);
+                        $alllink = $subCategory->get_links($this->userId);
 
                         $sub_cat_info = new GradebookDataGenerator($allcat, $alleval, $alllink);
                         $sub_cat_info->userId = $user_id;
@@ -481,8 +497,8 @@ class GradebookTable extends SortableTable
                             $item = $data[0];
 
                             //if the item is invisible, wrap it in a span with class invisible
-                            $invisibility_span_open = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '<span class="text-muted">' : '';
-                            $invisibility_span_close = api_is_allowed_to_edit() && $item->is_visible() == '0' ? '</span>' : '';
+                            $invisibility_span_open = $isAllowedToEdit && $item->is_visible() == '0' ? '<span class="text-muted">' : '';
+                            $invisibility_span_close = $isAllowedToEdit && $item->is_visible() == '0' ? '</span>' : '';
 
                             if (isset($item)) {
                                 $main_categories[$parent_id]['children'][$item->get_id()]['name'] = $item->get_name();
@@ -545,16 +561,18 @@ class GradebookTable extends SortableTable
                                         $average = isset($data['average']) ? $data['average'] : null;
                                         $ranking = isset($data['ranking']) ? $data['ranking'] : null;
 
-                                        if ($this->loadStats) {
+                                        if (in_array(1, $this->loadStats)) {
                                             // Ranking
                                             $row[] = $ranking;
                                         }
 
-                                        // Best
-                                        $row[] = $best;
+                                        if (in_array(2, $this->loadStats)) {
+                                            // Best
+                                            $row[] = $best;
+                                        }
 
                                         // Average
-                                        if ($this->loadStats) {
+                                        if (in_array(3, $this->loadStats)) {
                                             $row[] = $average;
                                         }
                                     }
@@ -665,86 +683,71 @@ class GradebookTable extends SortableTable
                     SCORE_DIV
                 );
 
-                $totalRanking = [];
-                $invalidateRanking = true;
-                $average = 0;
-                foreach ($this->studentList as $student) {
-                    $score = $main_cat[0]->calc_score($student['user_id']);
-                    if (!empty($score[0])) {
-                        $invalidateRanking = false;
-                    }
-                    $totalRanking[$student['user_id']] = $score[0];
-                    $average += $score[0];
-                }
-
-                $totalRanking = AbstractLink::getCurrentUserRanking($user_id, $totalRanking);
-
-                $totalRanking = $scoredisplay->display_score(
-                    $totalRanking,
-                    SCORE_DIV,
-                    SCORE_BOTH,
-                    true
-                );
+                $row = [
+                        null,
+                        '<h3>'.get_lang('Total').'</h3>',
+                    ];
 
-                if ($invalidateRanking) {
-                    $totalRanking = null;
+                if (!$this->exportToPdf) {
+                    $row[] = null;
                 }
 
-                // Overwrite main weight
-                $totalBest[1] = $main_weight;
+                $row[] = $main_weight;
+                $row[] = $totalResult;
+
+                if (in_array(1, $this->loadStats)) {
+                    $totalRanking = [];
+                    $invalidateRanking = true;
+                    $average = 0;
+                    foreach ($this->studentList as $student) {
+                        $score = $main_cat[0]->calc_score($student['user_id']);
+                        if (!empty($score[0])) {
+                            $invalidateRanking = false;
+                        }
+                        $totalRanking[$student['user_id']] = $score[0];
+                        $average += $score[0];
+                    }
 
-                $totalBest = $scoredisplay->display_score(
-                    $totalBest,
-                    SCORE_DIV,
-                    SCORE_BOTH,
-                    true
-                );
+                    $totalRanking = AbstractLink::getCurrentUserRanking($user_id, $totalRanking);
 
-                // Overwrite main weight
-                $totalAverage[0] = $average / count($this->studentList);
-                $totalAverage[1] = $main_weight;
+                    $totalRanking = $scoredisplay->display_score(
+                            $totalRanking,
+                            SCORE_DIV,
+                            SCORE_BOTH,
+                            true
+                        );
 
-                $totalAverage = $scoredisplay->display_score(
-                    $totalAverage,
-                    SCORE_DIV,
-                    SCORE_BOTH,
-                    true
-                );
+                    if ($invalidateRanking) {
+                        $totalRanking = null;
+                    }
 
-                if ($this->exportToPdf) {
-                    $row = [
-                        null,
-                        '<h3>'.get_lang('Total').'</h3>',
-                        $main_weight,
-                        $totalResult,
-                        $totalRanking,
-                        $totalBest,
-                        $totalAverage,
-                    ];
-                } else {
-                    if ($this->loadStats) {
-                        $row = [
-                            null,
-                            '<h3>'.get_lang('Total').'</h3>',
-                            null,
-                            $main_weight,
-                            $totalResult,
-                            $totalRanking,
+                    $row[] = $totalRanking;
+                }
+                if (in_array(2, $this->loadStats)) {
+                    // Overwrite main weight
+                    $totalBest[1] = $main_weight;
+
+                    $totalBest = $scoredisplay->display_score(
                             $totalBest,
+                            SCORE_DIV,
+                            SCORE_BOTH,
+                            true
+                        );
+                    $row[] = $totalBest;
+                }
+                if (in_array(3, $this->loadStats)) {
+                    // Overwrite main weight
+                    $totalAverage[0] = $average / count($this->studentList);
+                    $totalAverage[1] = $main_weight;
+
+                    $totalAverage = $scoredisplay->display_score(
                             $totalAverage,
-                        ];
-                    } else {
-                        $row = [
-                            null,
-                            '<h3>'.get_lang('Total').'</h3>',
-                            null,
-                            $main_weight,
-                            $totalResult,
-                            //$totalRanking,
-                            $totalBest,
-                            //$totalAverage,
-                        ];
-                    }
+                            SCORE_DIV,
+                            SCORE_BOTH,
+                            true
+                        );
+
+                    $row[] = $totalAverage;
                 }
 
                 $sortable_data[] = $row;
@@ -849,9 +852,8 @@ class GradebookTable extends SortableTable
                 $totalUserResult,
                 SCORE_DIV_PERCENT_WITH_CUSTOM
             );
-            $rowTotal[] = ' ';
-            if ($this->loadStats) {
-                $rowTotal[] = ' ';
+
+            foreach ($this->loadStats as $col) {
                 $rowTotal[] = ' ';
             }
 

+ 22 - 26
main/gradebook/lib/gradebook_data_generator.class.php

@@ -130,34 +130,31 @@ class GradebookDataGenerator
             //if (count($this->evals_links) > 0) {
             if (get_class($item) == 'Evaluation') {
                 // Items inside a category.
-                if (1) {
-                    $resultColumn = $this->build_result_column(
-                        $userId,
-                        $item,
-                        $ignore_score_color
-                    );
-
-                    $row[] = $resultColumn['display'];
-                    $row['result_score'] = $resultColumn['score'];
-                    $row['result_score_weight'] = $resultColumn['score_weight'];
+                $resultColumn = $this->build_result_column(
+                    $userId,
+                    $item,
+                    $ignore_score_color
+                );
 
-                    // Best
-                    $best = $this->buildBestResultColumn($item);
-                    $row['best'] = $best['display'];
-                    $row['best_score'] = $best['score'];
+                $row[] = $resultColumn['display'];
+                $row['result_score'] = $resultColumn['score'];
+                $row['result_score_weight'] = $resultColumn['score_weight'];
 
-                    // Average
-                    $average = $this->buildAverageResultColumn($item);
-                    $row['average'] = $average['display'];
-                    $row['average_score'] = $average['score'];
+                // Best
+                $best = $this->buildBestResultColumn($item);
+                $row['best'] = $best['display'];
+                $row['best_score'] = $best['score'];
 
-                    // Ranking
-                    $ranking = $this->buildRankingColumn($item, $userId, $userCount);
-                    $row['ranking'] = $ranking['display'];
-                    $row['ranking_score'] = $ranking['score'];
+                // Average
+                $average = $this->buildAverageResultColumn($item);
+                $row['average'] = $average['display'];
+                $row['average_score'] = $average['score'];
 
-                    $row[] = $item;
-                }
+                // Ranking
+                $ranking = $this->buildRankingColumn($item, $userId, $userCount);
+                $row['ranking'] = $ranking['display'];
+                $row['ranking_score'] = $ranking['score'];
+                $row[] = $item;
             } else {
                 // Category.
                 $result = $this->build_result_column(
@@ -391,8 +388,7 @@ class GradebookDataGenerator
             true
         );
         $type = $item->get_item_type();
-
-        if ($type == 'L' && get_class($item) == 'ExerciseLink') {
+        if ($type === 'L' && get_class($item) === 'ExerciseLink') {
             $display = ExerciseLib::show_score($score[0], $score[1], false);
         }