Browse Source

Minor - Update from 1.11.x

Julio 6 years ago
parent
commit
f41061624a
79 changed files with 1351 additions and 719 deletions
  1. 1 1
      main/admin/course_user_import.php
  2. 1 1
      main/admin/gradebook_list.php
  3. 0 8
      main/admin/user_edit.php
  4. 1 1
      main/attendance/attendance_sheet.php
  5. 1 7
      main/auth/inscription.php
  6. 2 10
      main/auth/profile.php
  7. 1 1
      main/badge/assign.php
  8. 6 10
      main/course_description/course_description_controller.php
  9. 1 0
      main/course_description/index.php
  10. 0 7
      main/course_description/listing.php
  11. 1 1
      main/course_home/course_home.php
  12. 1 1
      main/course_info/tools.php
  13. 1 1
      main/course_progress/index.php
  14. 0 1
      main/course_progress/thematic.php
  15. 1 1
      main/cron/check_lp_total_time.php
  16. 1 1
      main/document/create_audio.php
  17. 1 1
      main/document/document_quota.php
  18. 1 1
      main/document/edit_document.php
  19. 1 0
      main/document/show_content.php
  20. 1 1
      main/dropbox/dropbox_functions.inc.php
  21. 4 3
      main/exercise/MatchingDraggable.php
  22. 1 0
      main/exercise/MultipleAnswerTrueFalseDegreeCertainty.php
  23. 3 7
      main/exercise/admin.php
  24. 0 2
      main/exercise/answer.class.php
  25. 171 25
      main/exercise/exercise.class.php
  26. 1 8
      main/exercise/exercise.php
  27. 1 1
      main/exercise/exercise_admin.php
  28. 21 6
      main/exercise/exercise_report.php
  29. 16 15
      main/exercise/exercise_show.php
  30. 1 1
      main/exercise/exercise_submit_modal.php
  31. 1 9
      main/exercise/export/aiken/aiken_import.inc.php
  32. 1 1
      main/exercise/export/scorm/ScormQuestion.php
  33. 9 8
      main/exercise/fill_blanks.class.php
  34. 10 7
      main/exercise/global_multiple_answer.class.php
  35. 1 2
      main/exercise/hotpotatoes_exercise_report.php
  36. 6 3
      main/exercise/matching.class.php
  37. 6 2
      main/exercise/multiple_answer.class.php
  38. 8 4
      main/exercise/multiple_answer_combination.class.php
  39. 10 6
      main/exercise/multiple_answer_true_false.class.php
  40. 1 1
      main/exercise/overview.php
  41. 3 3
      main/exercise/question.class.php
  42. 1 1
      main/exercise/question_list_admin.inc.php
  43. 57 41
      main/exercise/recalculate.php
  44. 1 1
      main/exercise/savescores.php
  45. 1 1
      main/exercise/showinframes.php
  46. 6 3
      main/exercise/unique_answer.class.php
  47. 5 2
      main/exercise/unique_answer_no_option.class.php
  48. 13 28
      main/exercise/upload_exercise.php
  49. 1 1
      main/forum/editpost.php
  50. 1 1
      main/forum/editthread.php
  51. 4 6
      main/forum/forumfunction.inc.php
  52. 1 1
      main/forum/forumqualify.php
  53. 3 2
      main/forum/index.php
  54. 1 1
      main/forum/newthread.php
  55. 1 1
      main/forum/reply.php
  56. 1 1
      main/forum/viewforumcategory.php
  57. 1 1
      main/glossary/index.php
  58. 1 1
      main/gradebook/get_badges.php
  59. 3 3
      main/gradebook/gradebook.php
  60. 4 6
      main/gradebook/gradebook_add_eval.php
  61. 1 1
      main/gradebook/gradebook_add_link.php
  62. 10 3
      main/gradebook/gradebook_add_result.php
  63. 76 17
      main/gradebook/gradebook_display_summary.php
  64. 1 1
      main/gradebook/gradebook_edit_eval.php
  65. 1 1
      main/gradebook/gradebook_edit_link.php
  66. 11 0
      main/gradebook/gradebook_edit_result.php
  67. 4 15
      main/gradebook/gradebook_view_result.php
  68. 74 73
      main/gradebook/index.php
  69. 29 70
      main/gradebook/lib/GradebookUtils.php
  70. 51 1
      main/gradebook/lib/be/abstractlink.class.php
  71. 18 5
      main/gradebook/lib/be/category.class.php
  72. 113 0
      main/gradebook/lib/be/evaluation.class.php
  73. 70 1
      main/gradebook/lib/be/exerciselink.class.php
  74. 2 2
      main/gradebook/lib/be/linkfactory.class.php
  75. 4 1
      main/gradebook/lib/be/result.class.php
  76. 10 7
      main/gradebook/lib/fe/dataform.class.php
  77. 4 9
      main/gradebook/lib/fe/displaygradebook.php
  78. 254 154
      main/gradebook/lib/fe/gradebooktable.class.php
  79. 214 87
      main/gradebook/lib/gradebook_data_generator.class.php

+ 1 - 1
main/admin/course_user_import.php

@@ -142,7 +142,7 @@ function save_data($users_courses)
  */
 function parse_csv_data($file)
 {
-    $courses = Import :: csvToArray($file);
+    $courses = Import::csvToArray($file);
 
     return $courses;
 }

+ 1 - 1
main/admin/gradebook_list.php

@@ -189,7 +189,7 @@ switch ($action) {
 
             $options = [];
             if (!empty($categoryData['depends'])) {
-                $list = unserialize($categoryData['depends']);
+                $list = UnserializeApi::unserialize('not_allowed_classes', $categoryData['depends']);
                 foreach ($list as $itemId) {
                     $courseInfo = api_get_course_info_by_id($itemId);
                     $options[$itemId] = $courseInfo['name'];

+ 0 - 8
main/admin/user_edit.php

@@ -65,14 +65,6 @@ function confirmation(name) {
 }
 </script>';
 
-$gMapsPlugin = GoogleMapsPlugin::create();
-$geolocalization = $gMapsPlugin->get('enable_api') === 'true';
-
-if ($geolocalization) {
-    $gmapsApiKey = $gMapsPlugin->get('api_key');
-    $htmlHeadXtra[] = '<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?sensor=true&key='.$gmapsApiKey.'" ></script>';
-}
-
 //$htmlHeadXtra[] = api_get_css_asset('cropper/dist/cropper.min.css');
 //$htmlHeadXtra[] = api_get_asset('cropper/dist/cropper.min.js');
 $tool_name = get_lang('ModifyUserInfo');

+ 1 - 1
main/attendance/attendance_sheet.php

@@ -182,7 +182,7 @@ if (api_is_allowed_to_edit(null, true) ||
                 });
             }
 
-            $(document).ready(function() {
+            $(function() {
                 $("table.tableWithFloatingHeader").each(function() {
                     $(this).wrap("<div class=\"divTableWithFloatingHeader\" style=\"position:relative\"></div>");
 

+ 1 - 7
main/auth/inscription.php

@@ -32,12 +32,6 @@ if ($allowedFieldsConfiguration !== false) {
     $allowedFields['extra_fields'] = isset($allowedFieldsConfiguration['extra_fields']) ? $allowedFieldsConfiguration['extra_fields'] : [];
 }
 
-$gMapsPlugin = GoogleMapsPlugin::create();
-if ($gMapsPlugin->get('enable_api') === 'true') {
-    $key = $gMapsPlugin->get('api_key');
-    $htmlHeadXtra[] = '<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?sensor=true&key='.$key.'" ></script>';
-}
-
 $extraFieldsLoaded = false;
 $htmlHeadXtra[] = api_get_password_checker_js('#username', '#pass1');
 // User is not allowed if Terms and Conditions are disabled and
@@ -578,7 +572,7 @@ $allowDoubleValidation = api_get_configuration_value('allow_double_validation_in
 $formContainsSendButton = false;
 if ($allowDoubleValidation && $showTerms == false) {
     $htmlHeadXtra[] = '<script>
-        $(document).ready(function() {
+        $(function() {
             $("#pre_validation").click(function() {
                 $(this).hide();
                 $("#final_button").show();

+ 2 - 10
main/auth/profile.php

@@ -39,19 +39,11 @@ if (!(isset($_user['user_id']) && $_user['user_id']) || api_is_anonymous($_user[
     api_not_allowed(true);
 }
 
-$gMapsPlugin = GoogleMapsPlugin::create();
-$geolocalization = $gMapsPlugin->get('enable_api') === 'true';
-
-if ($geolocalization) {
-    $gmapsApiKey = $gMapsPlugin->get('api_key');
-    $htmlHeadXtra[] = '<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?sensor=true&key='.$gmapsApiKey.'" ></script>';
-}
-
 $htmlHeadXtra[] = api_get_password_checker_js('#username', '#password1');
 //$htmlHeadXtra[] = api_get_css_asset('cropper/dist/cropper.min.css');
 //$htmlHeadXtra[] = api_get_asset('cropper/dist/cropper.min.js');
 $htmlHeadXtra[] = '<script>
-$(document).ready(function() {
+$(function() {
     $("#id_generate_api_key").on("click", function (e) {
         e.preventDefault();
 
@@ -332,7 +324,7 @@ $jquery_ready_content = $return['jquery_ready_content'];
 // the $jquery_ready_content variable collects all functions that
 // will be load in the $(document).ready javascript function
 $htmlHeadXtra[] = '<script>
-$(document).ready(function(){
+$(function() {
     '.$jquery_ready_content.'
 });
 </script>';

+ 1 - 1
main/badge/assign.php

@@ -418,7 +418,7 @@ if ($disableList) {
 }
 
 $htmlHeadXtra[] = '<script>
-$(document).ready(function() {
+$(function() {
     $("#skill").on("change", function() {
         $(location).attr("href", "'.$url.'&id="+$(this).val());
     });

+ 6 - 10
main/course_description/course_description_controller.php

@@ -114,19 +114,15 @@ class CourseDescriptionController
             $actions = Display::toolbarAction('toolbar', [0 => $actionLeft]);
         }
 
-        $tpl = new Template(null);
+        $tpl = new Template(get_lang('CourseProgram'));
         $tpl->assign('listing', $data);
         $tpl->assign('is_allowed_to_edit', $is_allowed_to_edit);
+        $tpl->assign('actions', $actions);
         $tpl->assign('session_id', $session_id);
-        $tpl->assign('c_id', api_get_course_int_id());
-        $templateName = $tpl->get_template('course_description/index.html.twig');
-        $data['toolbar'] = $actions;
-        $data['descriptions'] = $tpl->fetch($templateName);
-
-        $this->view->set_data($data);
-        $this->view->set_layout('layout');
-        $this->view->set_template('listing');
-        $this->view->render();
+        $templateName = $tpl->get_template('course_description/index.tpl');
+        $content = $tpl->fetch($templateName);
+        $tpl->assign('content', $content);
+        $tpl->display_one_col_template();
     }
 
     /**

+ 1 - 0
main/course_description/index.php

@@ -19,6 +19,7 @@ define('ADD_BLOCK', 8);
 $this_section = SECTION_COURSES;
 
 $action = !empty($_GET['action']) ? Security::remove_XSS($_GET['action']) : 'listing';
+
 $logInfo = [
     'tool' => TOOL_COURSE_DESCRIPTION,
     'tool_id' => 0,

+ 0 - 7
main/course_description/listing.php

@@ -1,7 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-api_protect_course_script(true);
-
-echo $toolbar;
-echo $descriptions;

+ 1 - 1
main/course_home/course_home.php

@@ -34,7 +34,7 @@ require_once __DIR__.'/../inc/global.inc.php';
 
 $htmlHeadXtra[] = '<script>
 /* option show/hide thematic-block */
-$(document).ready(function(){
+$(function() {
     $("#thematic-show").click(function(){
         $(".btn-hide-thematic").hide();
         $(".btn-show-thematic").show(); //show using class

+ 1 - 1
main/course_info/tools.php

@@ -22,7 +22,6 @@ $action = isset($_GET['action']) ? $_GET['action'] : '';
 $id = isset($_GET['id']) ? intval($_GET['id']) : '';
 
 $toolName = get_lang('CustomizeIcons');
-$tpl = new Template($toolName);
 
 switch ($action) {
     case 'delete_icon':
@@ -164,5 +163,6 @@ switch ($action) {
         break;
 }
 
+$tpl = new Template($toolName);
 $tpl->assign('content', $content);
 $tpl->display_one_col_template();

+ 1 - 1
main/course_progress/index.php

@@ -89,7 +89,7 @@ $default_thematic_plan_title = $thematic->get_default_thematic_plan_title();
 // Only when I see the 3 columns. Avoids double or triple click binding for onclick event
 
 $htmlHeadXtra[] = '<script>
-$(document).ready(function() {
+$(function() {
     $(".thematic_advance_actions, .thematic_tools ").hide();
 	$(".thematic_content").mouseover(function() {
 		var id = parseInt(this.id.split("_")[3]);

+ 0 - 1
main/course_progress/thematic.php

@@ -289,7 +289,6 @@ if ($action == 'thematic_list') {
     $html = $form->returnForm();
 }
 $tpl->assign('actions', $toolbar);
-
 if (!empty($html)) {
     $tpl->assign('content', $html);
     $thematicLayout = $tpl->get_template('course_progress/layout.tpl');

+ 1 - 1
main/cron/check_lp_total_time.php

@@ -19,7 +19,7 @@ $max = 10;
 $counter = 0;
 // Check Sessions
 $_configuration['access_url'] = 6;
-$sessions = SessionManager::get_sessions_admin();
+$sessions = SessionManager::formatSessionsAdminForGrid();
 foreach ($sessions as $session) {
     $sessionId = $session['id'];
     if (!empty($testSessionId)) {

+ 1 - 1
main/document/create_audio.php

@@ -163,7 +163,7 @@ echo '</div>';
     <!-- javascript and styles for textareaCounter-->
     <script>
         var info;
-        $(document).ready(function () {
+        $(function() {
             var options = {
                 'maxCharacterSize': 100,
                 'originalStyle': 'originalTextareaInfo',

+ 1 - 1
main/document/document_quota.php

@@ -121,7 +121,7 @@ $session[] = [
 $quota_data = json_encode($session);
 
 $htmlHeadXtra[] = "<script>
-$(document).ready(function() {
+$(function() {
     var data = ".$quota_data.";
     var plot1 = jQuery.jqplot('chart1', [data], {
         seriesDefaults: {

+ 1 - 1
main/document/edit_document.php

@@ -523,7 +523,7 @@ if ($owner_id == api_get_user_id() ||
     } else {
         // Add tooltip and correctly parse its inner HTML
         echo '<script>
-        $(document).ready(function() {
+        $(function() {
             $("[data-toggle=\'tooltip\']").tooltip(
                 {
                     content: 

+ 1 - 0
main/document/show_content.php

@@ -32,6 +32,7 @@ $document_data = DocumentManager::get_document_data_by_id(
     true,
     $session_id
 );
+
 if ($session_id != 0 && !$document_data) {
     $document_data = DocumentManager::get_document_data_by_id(
         $document_id,

+ 1 - 1
main/dropbox/dropbox_functions.inc.php

@@ -16,7 +16,7 @@ $htmlHeadXtra[] = '<script>
 function setFocus(){
     $("#category_title").focus();
 }
-$(document).ready(function () {
+$(function() {
     setFocus();
 });
 </script>';

+ 4 - 3
main/exercise/MatchingDraggable.php

@@ -264,14 +264,15 @@ class MatchingDraggable extends Question
     {
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="matching '.$this->question_table_class.'"><tr>';
-
         $header .= '<th>'.get_lang('ElementList').'</th>';
-        if ($exercise->showExpectedChoice()) {
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
             $header .= '<th>'.get_lang('YourChoice').'</th>';
+        }
+
+        if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
             $header .= '<th>'.get_lang('Status').'</th>';
         } else {
-            $header .= '<th>'.get_lang('YourChoice').'</th>';
             $header .= '<th>'.get_lang('CorrespondsTo').'</th>';
         }
         $header .= '</tr>';

+ 1 - 0
main/exercise/MultipleAnswerTrueFalseDegreeCertainty.php

@@ -146,6 +146,7 @@ class MultipleAnswerTrueFalseDegreeCertainty extends Question
                 if (isset($_POST['correct']) && isset($_POST['correct'][$i])) {
                     $defaults['correct['.$i.']'] = Security::remove_XSS($_POST['correct'][$i]);
                 }
+
                 $j = 1;
                 if (!empty($optionData)) {
                     foreach ($optionData as $id => $data) {

+ 3 - 7
main/exercise/admin.php

@@ -81,13 +81,9 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
 $newQuestion = isset($_GET['newQuestion']) ? $_GET['newQuestion'] : 0;
 $modifyAnswers = isset($_GET['modifyAnswers']) ? $_GET['modifyAnswers'] : 0;
 $editQuestion = isset($_GET['editQuestion']) ? $_GET['editQuestion'] : 0;
-
-if (empty($modifyQuestion)) {
-    $modifyQuestion = isset($_GET['modifyQuestion']) ? $_GET['modifyQuestion'] : 0;
-}
-if (empty($deleteQuestion)) {
-    $deleteQuestion = isset($_GET['deleteQuestion']) ? $_GET['deleteQuestion'] : 0;
-}
+$page = isset($_GET['page']) && !empty($_GET['page']) ? (int) $_GET['page'] : 1;
+$modifyQuestion = isset($_GET['modifyQuestion']) ? $_GET['modifyQuestion'] : 0;
+$deleteQuestion = isset($_GET['deleteQuestion']) ? $_GET['deleteQuestion'] : 0;
 $clone_question = isset($_REQUEST['clone_question']) ? $_REQUEST['clone_question'] : 0;
 if (empty($questionId)) {
     $questionId = Session::read('questionId');

+ 0 - 2
main/exercise/answer.class.php

@@ -845,7 +845,6 @@ 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_DEGREE_CERTAINTY
         ) {
             // Selecting origin options
@@ -965,7 +964,6 @@ 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_DEGREE_CERTAINTY
                     ) {
                         $correct = $fixed_list[intval($correct)];

+ 171 - 25
main/exercise/exercise.class.php

@@ -1122,7 +1122,7 @@ class Exercise
         $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
 
         // Random with limit
-        $randomLimit = "ORDER BY RAND() LIMIT $random";
+        $randomLimit = " ORDER BY RAND() LIMIT $random";
 
         // Random with no limit
         if ($random == -1) {
@@ -2112,7 +2112,7 @@ class Exercise
                 '<div id="hidden_random" style="display:'.$displayRandom.'">'
             );
             // Number of random question.
-            $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
+            $max = ($this->id > 0) ? $this->getQuestionCount() : 10;
             $option = range(0, $max);
             $option[0] = get_lang('No');
             $option[-1] = get_lang('AllQuestionsShort');
@@ -2373,11 +2373,11 @@ class Exercise
         // defaults
         if ($type == 'full') {
             if ($this->id > 0) {
-                if ($this->random > $this->selectNbrQuestions()) {
-                    $defaults['randomQuestions'] = $this->selectNbrQuestions();
-                } else {
-                    $defaults['randomQuestions'] = $this->random;
-                }
+                //if ($this->random > $this->selectNbrQuestions()) {
+                //    $defaults['randomQuestions'] = $this->selectNbrQuestions();
+                //} else {
+                $defaults['randomQuestions'] = $this->random;
+                //}
 
                 $defaults['randomAnswers'] = $this->getRandomAnswers();
                 $defaults['exerciseType'] = $this->selectType();
@@ -2756,6 +2756,7 @@ class Exercise
      */
     public function cleanResults($cleanLpTests = false, $cleanResultBeforeDate = null)
     {
+        $sessionId = api_get_session_id();
         $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
         $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
 
@@ -2785,7 +2786,7 @@ class Exercise
             WHERE
                 c_id = ".api_get_course_int_id()." AND
                 exe_exo_id = ".$this->id." AND
-                session_id = ".api_get_session_id()." ".
+                session_id = ".$sessionId." ".
                 $sql_where;
 
         $result = Database::query($sql);
@@ -2803,15 +2804,16 @@ 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;
+                  session_id = ".$sessionId;
         Database::query($sql);
 
+        $this->generateStats($this->id, api_get_course_info(), $sessionId);
+
         Event::addEvent(
             LOG_EXERCISE_RESULT_DELETE,
             LOG_EXERCISE_ID,
@@ -2819,7 +2821,7 @@ class Exercise
             null,
             null,
             api_get_course_int_id(),
-            $session_id
+            $sessionId
         );
 
         return $i;
@@ -3210,7 +3212,7 @@ class Exercise
                 openClockWarning();
             }
 
-			$(document).ready(function() {
+			$(function() {
 				// time in seconds when using minutes there are some seconds lost
                 var time_left = parseInt(".$time_left.");
                 $('#exercise_clock_warning').epiclock({
@@ -4176,6 +4178,7 @@ class Exercise
                             $s_answer_label = $a_answers['answer']; // your daddy - your mother
                             $i_answer_correct_answer = $a_answers['correct']; //1 - 2
                             $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
+
                             $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
                                     WHERE
                                         exe_id = '$exeId' AND
@@ -4190,6 +4193,7 @@ class Exercise
                             $i_answerWeighting = $a_answers['ponderation'];
                             $user_answer = '';
                             $status = Display::label(get_lang('Incorrect'), 'danger');
+
                             if (!empty($s_user_answer)) {
                                 if ($answerType == DRAGGABLE) {
                                     if ($s_user_answer == $i_answer_correct_answer) {
@@ -4264,12 +4268,6 @@ class Exercise
                                 }
                             }
 
-                            if ($results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
-                                if ($s_user_answer != $i_answer_correct_answer) {
-                                    continue;
-                                }
-                            }
-
                             if ($show_result) {
                                 if ($this->showExpectedChoice() == false &&
                                     $showTotalScoreAndUserChoicesInLastAttempt === false
@@ -4280,9 +4278,15 @@ class Exercise
                                     case MATCHING:
                                     case MATCHING_DRAGGABLE:
                                         echo '<tr>';
-                                        if ($this->showExpectedChoice()) {
+                                        if ($this->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
                                             echo '<td>'.$s_answer_label.'</td>';
                                             echo '<td>'.$user_answer.'</td>';
+                                        } else {
+                                            echo '<td>'.$s_answer_label.'</td>';
+                                            $status = Display::label(get_lang('Correct'), 'success');
+                                        }
+
+                                        if ($this->showExpectedChoice()) {
                                             echo '<td>';
                                             if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
                                                 if (isset($real_list[$i_answer_correct_answer]) &&
@@ -4296,8 +4300,6 @@ class Exercise
                                             echo '</td>';
                                             echo '<td>'.$status.'</td>';
                                         } else {
-                                            echo '<td>'.$s_answer_label.'</td>';
-                                            echo '<td>'.$user_answer.'</td>';
                                             echo '<td>';
                                             if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
                                                 if (isset($real_list[$i_answer_correct_answer]) &&
@@ -4319,7 +4321,11 @@ class Exercise
                                         }
                                         echo '<tr>';
                                         if ($this->showExpectedChoice()) {
-                                            echo '<td>'.$user_answer.'</td>';
+                                            if ($this->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+                                                echo '<td>'.$user_answer.'</td>';
+                                            } else {
+                                                $status = Display::label(get_lang('Correct'), 'success');
+                                            }
                                             echo '<td>'.$s_answer_label.'</td>';
                                             echo '<td>'.$status.'</td>';
                                         } else {
@@ -7832,7 +7838,7 @@ class Exercise
                         <h3>'.$scoreLabel.'</h3>
                       </div>';
             if (!empty($result)) {
-                $label .= '<h4>'.get_lang('Score').': '.$result['html'].'</h4>';
+                $label .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
             }
             if ($hideLabel === true) {
                 $answerUsed = (int) $array['used'];
@@ -7847,7 +7853,7 @@ class Exercise
                         Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL).
                         '</span>';
                 }
-                $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result['html'].'</div>';
+                $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result.'</div>';
                 $label .= '<div class="score-limits">';
                 $label .= $html;
                 $label .= '</div>';
@@ -7863,7 +7869,7 @@ class Exercise
                             <h3>'.$scoreLabel.'</h3>
                         </div>';
             if (!empty($result)) {
-                $html .= '<h4>'.get_lang('Score').': '.$result['html'].'</h4>';
+                $html .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
             }
             $html .= '</div>';
 
@@ -7947,6 +7953,146 @@ class Exercise
         return $questionList;
     }
 
+    /**
+     * @param int   $exerciseId
+     * @param array $courseInfo
+     * @param int   $sessionId
+     *
+     * @throws \Doctrine\ORM\OptimisticLockException
+     *
+     * @return bool
+     */
+    public function generateStats($exerciseId, $courseInfo, $sessionId)
+    {
+        $allowStats = api_get_configuration_value('allow_gradebook_stats');
+        if (!$allowStats) {
+            return false;
+        }
+
+        if (empty($courseInfo)) {
+            return false;
+        }
+
+        $courseId = $courseInfo['real_id'];
+
+        $sessionId = (int) $sessionId;
+        $result = $this->read($exerciseId);
+
+        if (empty($result)) {
+            api_not_allowed(true);
+        }
+
+        $statusToFilter = empty($sessionId) ? STUDENT : 0;
+
+        $studentList = CourseManager::get_user_list_from_course_code(
+            api_get_course_id(),
+            $sessionId,
+            null,
+            null,
+            $statusToFilter
+        );
+
+        if (empty($studentList)) {
+            Display::addFlash(Display::return_message(get_lang('NoUsersInCourse')));
+            header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
+            exit;
+        }
+
+        $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
+
+        $studentIdList = [];
+        if (!empty($studentList)) {
+            $studentIdList = array_column($studentList, 'user_id');
+        }
+
+        if ($this->exercise_was_added_in_lp == false) {
+            $sql = "SELECT * FROM $tblStats
+                        WHERE
+                            exe_exo_id = $exerciseId AND
+                            orig_lp_id = 0 AND
+                            orig_lp_item_id = 0 AND
+                            status <> 'incomplete' AND
+                            session_id = $sessionId AND
+                            c_id = $courseId
+                        ";
+        } else {
+            $lpId = null;
+            if (!empty($this->lpList)) {
+                // Taking only the first LP
+                $lpId = current($this->lpList);
+                $lpId = $lpId['lp_id'];
+            }
+
+            $sql = "SELECT * 
+                        FROM $tblStats
+                        WHERE
+                            exe_exo_id = $exerciseId AND
+                            orig_lp_id = $lpId AND
+                            status <> 'incomplete' AND
+                            session_id = $sessionId AND
+                            c_id = $courseId ";
+        }
+
+        $sql .= ' ORDER BY exe_id DESC';
+
+        $studentCount = 0;
+        $sum = 0;
+        $bestResult = 0;
+        $weight = 0;
+        $sumResult = 0;
+        $result = Database::query($sql);
+        while ($data = Database::fetch_array($result, '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'];
+                    $studentCount++;
+                    if ($data['exe_result'] > $bestResult) {
+                        $bestResult = $data['exe_result'];
+                    }
+                    $sum += $data['exe_result'] / $data['exe_weighting'];
+                    $sumResult += $data['exe_result'];
+                    $weight = $data['exe_weighting'];
+                }
+            }
+        }
+
+        $count = count($studentList);
+        $average = $sumResult / $count;
+        $em = Database::getManager();
+
+        $links = AbstractLink::getGradebookLinksFromItem(
+            $this->selectId(),
+            LINK_EXERCISE,
+            api_get_course_id(),
+            api_get_session_id()
+        );
+
+        $repo = $em->getRepository('ChamiloCoreBundle:GradebookLink');
+
+        foreach ($links as $link) {
+            $linkId = $link['id'];
+            /** @var \Chamilo\CoreBundle\Entity\GradebookLink $exerciseLink */
+            $exerciseLink = $repo->find($linkId);
+            if ($exerciseLink) {
+                $exerciseLink
+                    ->setUserScoreList($students)
+                    ->setBestScore($bestResult)
+                    ->setAverageScore($average)
+                    ->setScoreWeight($this->get_max_score())
+                ;
+                $em->persist($exerciseLink);
+                $em->flush();
+            }
+        }
+    }
+
     /**
      * Gets the question list ordered by the question_order setting (drag and drop).
      *

+ 1 - 8
main/exercise/exercise.php

@@ -347,6 +347,7 @@ if ($is_allowedToEdit) {
                         }
                         break;
                     case 'copy_exercise': //copy an exercise
+                        api_set_more_memory_and_time_limits();
                         $objExerciseTmp->copyExercise();
                         echo Display::return_message(
                             get_lang('ExerciseCopied'),
@@ -735,12 +736,6 @@ if (!empty($exerciseList)) {
             }
 
             // Teacher only
-            // Count number exercise - teacher
-            $sql = "SELECT count(*) count FROM $TBL_EXERCISE_QUESTION
-                        WHERE c_id = $courseId AND exercice_id = $my_exercise_id";
-            $sqlresult = Database::query($sql);
-            $rowi = intval(Database :: result($sqlresult, 0, 0));
-
             if ($is_allowedToEdit) {
                 $lp_blocked = null;
                 if ($exercise->exercise_was_added_in_lp == true) {
@@ -791,7 +786,6 @@ if (!empty($exerciseList)) {
                     $sessionId
                 );
 
-
                 $move = Display::return_icon(
                     'all_directions.png',
                     get_lang('Move'),
@@ -1066,7 +1060,6 @@ if (!empty($exerciseList)) {
                 $actions .= $delete;
 
                 // Number of questions
-                
                 $random_label = null;
                 if ($row['random'] > 0 || $row['random'] == -1) {
                     // if random == -1 means use random questions with all questions

+ 1 - 1
main/exercise/exercise_admin.php

@@ -119,7 +119,7 @@ $htmlHeadXtra[] = '<script>
 function setFocus(){
     $("#exercise_title").focus();
 }
-$(document).ready(function () {
+$(function() {
     setFocus();
 });
 </script>';

+ 21 - 6
main/exercise/exercise_report.php

@@ -233,13 +233,28 @@ if (isset($_REQUEST['comments']) &&
         Database::insert($TBL_TRACK_ATTEMPT_RECORDING, $params);
     }
 
-    $qry = 'SELECT DISTINCT question_id, marks
+    $useEvaluationPlugin = false;
+    $pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
+
+    if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
+        $formula = $pluginEvaluation->getFormulaForExercise($exerciseId);
+
+        if (!empty($formula)) {
+            $useEvaluationPlugin = true;
+        }
+    }
+
+    if (!$useEvaluationPlugin) {
+        $qry = 'SELECT DISTINCT question_id, marks
             FROM '.$TBL_TRACK_ATTEMPT.' WHERE exe_id = '.$id.'
             GROUP BY question_id';
-    $res = Database::query($qry);
-    $tot = 0;
-    while ($row = Database :: fetch_array($res, 'ASSOC')) {
-        $tot += $row['marks'];
+        $res = Database::query($qry);
+        $tot = 0;
+        while ($row = Database :: fetch_array($res, 'ASSOC')) {
+            $tot += $row['marks'];
+        }
+    } else {
+        $tot = $pluginEvaluation->getResultWithFormula($id, $formula);
     }
 
     $sql = "UPDATE $TBL_TRACK_EXERCISES
@@ -482,7 +497,7 @@ $token = Security::get_token();
 $actions = Display::div($actions, ['class' => 'actions']);
 
 $extra = '<script>
-    $(document).ready(function() {
+    $(function() {
         $( "#dialog:ui-dialog" ).dialog( "destroy" );
         $( "#dialog-confirm" ).dialog({
                 autoOpen: false,

+ 16 - 15
main/exercise/exercise_show.php

@@ -288,7 +288,7 @@ if ($origin == 'learnpath' && !isset($_GET['fb_type'])) {
     $show_results = false;
 }
 
-if ($is_allowedToEdit && in_array($action, ['qualify', 'edit'])) {
+if ($is_allowedToEdit && in_array($action, ['qualify', 'edit', 'export'])) {
     $show_results = true;
 }
 
@@ -727,7 +727,6 @@ foreach ($questionList as $questionId) {
         if ($isBossOfStudent) {
             $isFeedbackAllowed = false;
         }
-
         $marksname = '';
         if ($isFeedbackAllowed && $action != 'export') {
             $name = 'fckdiv'.$questionId;
@@ -1093,19 +1092,21 @@ if ($isFeedbackAllowed && $origin != 'learnpath' && $origin != 'student_progress
         );
     }
 
-    $emailForm->addCheckBox(
-        'send_notification',
-        get_lang('SendEmail'),
-        get_lang('SendEmail'),
-        ['onclick' => 'openEmailWrapper();']
-    );
-    $emailForm->addHtml('<div id="email_content_wrapper" style="display:none; margin-bottom: 20px;">');
-    $emailForm->addHtmlEditor(
-        'notification_content',
-        get_lang('Content'),
-        false
-    );
-    $emailForm->addHtml('</div>');
+    if ($objExercise->results_disabled != RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS) {
+        $emailForm->addCheckBox(
+            'send_notification',
+            get_lang('SendEmail'),
+            get_lang('SendEmail'),
+            ['onclick' => 'openEmailWrapper();']
+        );
+        $emailForm->addHtml('<div id="email_content_wrapper" style="display:none; margin-bottom: 20px;">');
+        $emailForm->addHtmlEditor(
+            'notification_content',
+            get_lang('Content'),
+            false
+        );
+        $emailForm->addHtml('</div>');
+    }
 
     if (empty($track_exercise_info['orig_lp_id']) || empty($track_exercise_info['orig_lp_item_id'])) {
         // Default url

+ 1 - 1
main/exercise/exercise_submit_modal.php

@@ -621,4 +621,4 @@ if ($links != '') {
 
 echo '</div>';
 
-Display::display_footer();
+//Display::display_footer();

+ 1 - 9
main/exercise/export/aiken/aiken_import.inc.php

@@ -338,15 +338,7 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
             $new_question = true;
         } else {
             if (empty($exercise_info['question'][$question_index]['title'])) {
-                if (strlen($info) < 100) {
-                    $exercise_info['question'][$question_index]['title'] = $info;
-                } else {
-                    //Question itself (use a 100-chars long title and a larger description)
-                    $exercise_info['question'][$question_index]['title'] = trim(substr($info, 0, 100)).'...';
-                    //$exercise_info['question'][$question_index]['description'] = $info;
-                }
-            } else {
-                //$exercise_info['question'][$question_index]['description'] = $info;
+                $exercise_info['question'][$question_index]['title'] = $info;
             }
         }
     }

+ 1 - 1
main/exercise/export/scorm/ScormQuestion.php

@@ -196,7 +196,7 @@ class ScormQuestion extends Question
         $weight = $this->selectWeighting();
         $js = '
             questions.push('.$this->js_id.');
-            $(document).ready(function() {         
+            $(function() {     
                 if (exerciseInfo.randomAnswers == true) {
                     $("#question_'.$this->js_id.'").shuffleRows();                    
                 } 

+ 9 - 8
main/exercise/fill_blanks.class.php

@@ -1232,14 +1232,9 @@ class FillBlanks extends Question
 
         // rebuild the sentence with student answer inserted
         for ($i = 0; $i < count($listStudentAnswerInfo['common_words']); $i++) {
-            if ($resultsDisabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
-                if (isset($listStudentAnswerInfo['student_score'][$i]) &&
-                    $listStudentAnswerInfo['student_score'][$i] != 1) {
-                    continue;
-                }
-            }
             $result .= isset($listStudentAnswerInfo['common_words'][$i]) ? $listStudentAnswerInfo['common_words'][$i] : '';
-            $result .= isset($listStudentAnswerInfo['student_answer'][$i]) ? $listStudentAnswerInfo['student_answer'][$i] : '';
+            $studentLabel = isset($listStudentAnswerInfo['student_answer'][$i]) ? $listStudentAnswerInfo['student_answer'][$i] : '';
+            $result .= $studentLabel;
         }
 
         // the last common word (should be </p>)
@@ -1269,7 +1264,11 @@ class FillBlanks extends Question
         $showTotalScoreAndUserChoices = false
     ) {
         $hideExpectedAnswer = false;
+        $hideUserSelection = false;
         switch ($resultsDisabled) {
+            case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
+                $hideUserSelection = true;
+                break;
             case RESULT_DISABLE_SHOW_SCORE_ONLY:
                 if ($feedbackType == 0) {
                     $hideExpectedAnswer = true;
@@ -1326,7 +1325,9 @@ class FillBlanks extends Question
         }
 
         $result = "<span class='feedback-question'>";
-        $result .= $iconAnswer."<span class='$style'>".$answer."</span>";
+        if ($hideUserSelection === false) {
+            $result .= $iconAnswer."<span class='$style'>".$answer."</span>";
+        }
         $result .= "<span class='feedback-separator'>|</span>";
         $result .= $correctAnswerHtml;
         $result .= '</span>';

+ 10 - 7
main/exercise/global_multiple_answer.class.php

@@ -27,7 +27,7 @@ class GlobalMultipleAnswer extends Question
     public function createAnswersForm($form)
     {
         $nb_answers = isset($_POST['nb_answers']) ? $_POST['nb_answers'] : 4;
-        $nb_answers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0));
+        $nb_answers += isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0);
 
         $obj_ex = Session::read('objExercise');
 
@@ -74,8 +74,8 @@ class GlobalMultipleAnswer extends Question
         }
 
         //D�but affichage score global dans la modification d'une question
-        $scoreA = "0"; //par reponse
-        $scoreG = "0"; //Global
+        $scoreA = 0; //par reponse
+        $scoreG = 0; //Global
 
         /* boucle pour sauvegarder les donn�es dans le tableau defaults */
         for ($i = 1; $i <= $nb_answers; $i++) {
@@ -174,7 +174,7 @@ class GlobalMultipleAnswer extends Question
 
         global $text;
 
-        if ($obj_ex->edit_exercise_in_lp == true) {
+        if ($obj_ex->edit_exercise_in_lp) {
             $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers');
             $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers');
             $form->addButtonSave($text, 'submitQuestion');
@@ -225,7 +225,7 @@ class GlobalMultipleAnswer extends Question
         $nbr_corrects = $nbr_corrects == 0 ? 1 : $nbr_corrects;
         $answer_score = $nbr_corrects == 0 ? 0 : $answer_score;
 
-        $answer_score = ($answer_score / $nbr_corrects);
+        $answer_score = $answer_score / $nbr_corrects;
 
         //$answer_score �quivaut � la valeur d'une bonne r�ponse
         // cr�ation variable pour r�cuperer la valeur de la coche pour la prise en compte des n�gatifs
@@ -267,8 +267,11 @@ class GlobalMultipleAnswer extends Question
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="'.$this->question_table_class.'"><tr>';
 
-        $header .= '<th>'.get_lang('Choice').'</th>';
-        $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $header .= '<th>'.get_lang('Choice').'</th>';
+            $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        }
+
         $header .= '<th>'.get_lang('Answer').'</th>';
         if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('Status').'</th>';

+ 1 - 2
main/exercise/hotpotatoes_exercise_report.php

@@ -107,8 +107,7 @@ Display :: display_header($nameTools);
 $actions = Display::div($actions, ['class' => 'actions']);
 
 $extra = '<script>
-$(document).ready(function() {
-
+$(function() {
     $( "#dialog:ui-dialog" ).dialog( "destroy" );
     $( "#dialog-confirm" ).dialog({
         autoOpen: false,

+ 6 - 3
main/exercise/matching.class.php

@@ -285,13 +285,16 @@ class Matching extends Question
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="'.$this->question_table_class.'">';
         $header .= '<tr>';
+
         $header .= '<th>'.get_lang('ElementList').'</th>';
-        if ($exercise->showExpectedChoice()) {
-            $header .= '<th>'.get_lang('YourChoice').'</th>';
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $header .= '<th>'.get_lang('Choice').'</th>';
             $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        }
+
+        if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('Status').'</th>';
         } else {
-            $header .= '<th>'.get_lang('YourChoice').'</th>';
             $header .= '<th>'.get_lang('CorrespondsTo').'</th>';
         }
         $header .= '</tr>';

+ 6 - 2
main/exercise/multiple_answer.class.php

@@ -231,8 +231,12 @@ class MultipleAnswer extends Question
     {
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="'.$this->question_table_class.'"><tr>';
-        $header .= '<th>'.get_lang('Choice').'</th>';
-        $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $header .= '<th>'.get_lang('Choice').'</th>';
+            $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        }
+
         $header .= '<th>'.get_lang('Answer').'</th>';
         if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('Status').'</th>';

+ 8 - 4
main/exercise/multiple_answer_combination.class.php

@@ -228,10 +228,14 @@ class MultipleAnswerCombination extends Question
     public function return_header($exercise, $counter = null, $score = null)
     {
         $header = parent::return_header($exercise, $counter, $score);
-        $header .= '<table class="'.$this->question_table_class.'"><tr>
-                <th>'.get_lang('Choice').'</th>
-                <th>'.get_lang('ExpectedChoice').'</th>
-                <th>'.get_lang('Answer').'</i></th>';
+        $header .= '<table class="'.$this->question_table_class.'"><tr>';
+
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $header .= '<th>'.get_lang('Choice').'</th>';
+            $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        }
+
+        $header .= '<th>'.get_lang('Answer').'</th>';
         if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('Status').'</th>';
         }

+ 10 - 6
main/exercise/multiple_answer_true_false.class.php

@@ -310,16 +310,20 @@ class MultipleAnswerTrueFalse extends Question
     {
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="'.$this->question_table_class.'"><tr>';
-        $header .= '<th>'.get_lang('Choice').'</th>
-            <th>'.get_lang('ExpectedChoice').'</th>
-            <th>'.get_lang('Answer').'</th>';
+
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $header .= '<th>'.get_lang('Choice').'</th>';
+            $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        }
+
+        $header .= '<th>'.get_lang('Answer').'</th>';
+
         if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('Status').'</th>';
         }
-        if ($exercise->feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+        if ($exercise->feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM ||
+            $exercise->results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
             $header .= '<th>'.get_lang('Comment').'</th>';
-        } else {
-            $header .= '<th>&nbsp;</th>';
         }
         $header .= '</tr>';
 

+ 1 - 1
main/exercise/overview.php

@@ -28,7 +28,7 @@ $sessionId = api_get_session_id();
 $exercise_id = isset($_REQUEST['exerciseId']) ? intval($_REQUEST['exerciseId']) : 0;
 
 $objExercise = new Exercise();
-$result = $objExercise->read($exercise_id);
+$result = $objExercise->read($exercise_id, false);
 
 if (!$result) {
     api_not_allowed(true);

+ 3 - 3
main/exercise/question.class.php

@@ -1268,9 +1268,9 @@ abstract class Question
         // checks if the exercise ID is not in the list
         if (!in_array($exerciseId, $this->exerciseList)) {
             $this->exerciseList[] = $exerciseId;
-            $new_exercise = new Exercise();
-            $new_exercise->read($exerciseId);
-            $count = $new_exercise->selectNbrQuestions();
+            $newExercise = new Exercise();
+            $newExercise->read($exerciseId, false);
+            $count = $newExercise->getQuestionCount();
             $count++;
             $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order)
                     VALUES ({$this->course['real_id']}, ".intval($id).", ".intval($exerciseId).", '$count')";

+ 1 - 1
main/exercise/question_list_admin.inc.php

@@ -34,7 +34,7 @@ if ($deleteQuestion) {
     // destruction of the Question object
     unset($objQuestionTmp);
 }
-$ajax_url = api_get_path(WEB_AJAX_PATH)."exercise.ajax.php?".api_get_cidreq()."&exercise_id=".intval($exerciseId);
+$ajax_url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&exercise_id='.intval($exerciseId);
 ?>
 <div id="dialog-confirm"
      title="<?php echo get_lang('ConfirmYourChoice'); ?>"

+ 57 - 41
main/exercise/recalculate.php

@@ -49,50 +49,66 @@ $exercise->read($exerciseId);
 $totalScore = 0;
 $totalWeight = 0;
 
-foreach ($questionList as $questionId) {
-    $question = Question::read($questionId, $courseId);
-    $totalWeight += $question->selectWeighting();
-
-    // We're inside *one* question. Go through each possible answer for this question
-    if ($question->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
-        $result = $exercise->manage_answer(
-            $exeId,
-            $questionId,
-            [],
-            'exercise_result',
-            [],
-            false,
-            true,
-            false,
-            $exercise->selectPropagateNeg(),
-            [],
-            [],
-            true
-        );
-    } else {
-        $result = $exercise->manage_answer(
-            $exeId,
-            $questionId,
-            [],
-            'exercise_result',
-            [],
-            false,
-            true,
-            false,
-            $exercise->selectPropagateNeg(),
-            [],
-            [],
-            true
-        );
-    }
+$useEvaluationPlugin = false;
+$pluginEvaluation = QuestionOptionsEvaluationPlugin::create();
+
+if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) {
+    $formula = $pluginEvaluation->getFormulaForExercise($exerciseId);
 
-    //  Adding the new score.
-    $totalScore += $result['score'];
+    if (!empty($formula)) {
+        $useEvaluationPlugin = true;
+    }
 }
 
-$remindList = $trackedExercise->getQuestionsToCheck();
-if (!empty($remindList)) {
-    $remindList = explode(',', $remindList);
+if (!$useEvaluationPlugin) {
+    foreach ($questionList as $questionId) {
+        $question = Question::read($questionId, $courseId);
+        $totalWeight += $question->selectWeighting();
+
+        // We're inside *one* question. Go through each possible answer for this question
+        if ($question->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
+            $result = $exercise->manage_answer(
+                $exeId,
+                $questionId,
+                [],
+                'exercise_result',
+                [],
+                false,
+                true,
+                false,
+                $exercise->selectPropagateNeg(),
+                [],
+                [],
+                true
+            );
+        } else {
+            $result = $exercise->manage_answer(
+                $exeId,
+                $questionId,
+                [],
+                'exercise_result',
+                [],
+                false,
+                true,
+                false,
+                $exercise->selectPropagateNeg(),
+                [],
+                [],
+                true
+            );
+        }
+
+        //  Adding the new score.
+        $totalScore += $result['score'];
+    }
+
+    $remindList = $trackedExercise->getQuestionsToCheck();
+    if (!empty($remindList)) {
+        $remindList = explode(',', $remindList);
+    }
+} else {
+    $totalScore = $pluginEvaluation->getResultWithFormula($exeId, $formula);
+    $totalWeight = $pluginEvaluation->getMaxScore();
 }
 
 $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);

+ 1 - 1
main/exercise/savescores.php

@@ -70,7 +70,7 @@ function save_scores($file, $score)
         global $jscript2run;
         //record the results in the learning path, using the SCORM interface (API)
         $jscript2run .= "<script>
-            $(document).ready(function() {
+            $(function() {
                 //API_obj = window.frames.window.content.API;
                 //API_obj = $('content_id').context.defaultView.content.API; //works only in FF
                 //API_obj = window.parent.frames.window.top.API;

+ 1 - 1
main/exercise/showinframes.php

@@ -93,7 +93,7 @@ $htmlHeadXtra[] = <<<HTML
             iframe.height = maxheight;
         }
         
-        $(document).on('ready', function () {
+        $(function() {
             var iframe = document.getElementById('hotpotatoe');
             iframe.onload = function () {
                 // this.height = $(this.contentDocument.body).outerHeight(true)

+ 6 - 3
main/exercise/unique_answer.class.php

@@ -122,7 +122,6 @@ class UniqueAnswer extends Question
         }
 
         $temp_scenario = [];
-
         if ($nb_answers < 1) {
             $nb_answers = 1;
             echo Display::return_message(
@@ -416,8 +415,12 @@ class UniqueAnswer extends Question
     ) {
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="'.$this->question_table_class.'"><tr>';
-        $header .= '<th>'.get_lang('Choice').'</th>';
-        $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $header .= '<th>'.get_lang('Choice').'</th>';
+            $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        }
+
         $header .= '<th>'.get_lang('Answer').'</th>';
         if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('Status').'</th>';

+ 5 - 2
main/exercise/unique_answer_no_option.class.php

@@ -401,8 +401,11 @@ class UniqueAnswerNoOption extends Question
     {
         $header = parent::return_header($exercise, $counter, $score);
         $header .= '<table class="'.$this->question_table_class.'"><tr>';
-        $header .= '<th>'.get_lang('Choice').'</th>';
-        $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+
+        if ($exercise->results_disabled != RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $header .= '<th>'.get_lang('Choice').'</th>';
+            $header .= '<th>'.get_lang('ExpectedChoice').'</th>';
+        }
         $header .= '<th>'.get_lang('Answer').'</th>';
         if ($exercise->showExpectedChoice()) {
             $header .= '<th>'.get_lang('Status').'</th>';

+ 13 - 28
main/exercise/upload_exercise.php

@@ -23,7 +23,7 @@ if (!$is_allowed_to_edit) {
 
 $this_section = SECTION_COURSES;
 $htmlHeadXtra[] = "<script>
-$(document).ready( function(){
+$(function(){
     $('#user_custom_score').click(function() {
         $('#options').toggle();
     });
@@ -34,8 +34,8 @@ $(document).ready( function(){
 lp_upload_quiz_action_handling();
 
 $interbreadcrumb[] = [
-    "url" => "exercise.php?".api_get_cidreq(),
-    "name" => get_lang('Exercises'),
+    'url' => 'exercise.php?'.api_get_cidreq(),
+    'name' => get_lang('Exercises'),
 ];
 
 // Display the header
@@ -64,7 +64,7 @@ function lp_upload_quiz_actions()
 
 function lp_upload_quiz_main()
 {
-    $lp_id = isset($_GET['lp_id']) ? intval($_GET['lp_id']) : null;
+    $lp_id = isset($_GET['lp_id']) ? (int) $_GET['lp_id'] : null;
 
     $form = new FormValidator(
         'upload',
@@ -119,8 +119,6 @@ function lp_upload_quiz_main()
 
     $form->addProgress();
     $form->addButtonUpload(get_lang('Upload'), 'submit_upload_quiz');
-
-    // Display the upload field
     $form->display();
 }
 
@@ -237,7 +235,7 @@ function lp_upload_quiz_action_handling()
                 $scoreList[] = $cellScoreInfo->getValue();
                 break;
             case 'NoNegativeScore':
-                $noNegativeScoreList[] = $cellDataInfo->getValue();
+                $noNegativeScoreList[] = $cellScoreInfo->getValue();
                 break;
             case 'Category':
                 $categoryList[] = $cellDataInfo->getValue();
@@ -283,15 +281,6 @@ function lp_upload_quiz_action_handling()
         $quiz_id = $exercise->save();
 
         if ($quiz_id) {
-            // insert into the item_property table
-            api_item_property_update(
-                $_course,
-                TOOL_QUIZ,
-                $quiz_id,
-                'QuizAdded',
-                api_get_user_id()
-            );
-
             // Import questions.
             for ($i = 0; $i < $numberQuestions; $i++) {
                 // Question name
@@ -365,7 +354,6 @@ function lp_upload_quiz_action_handling()
                         );
                     }
                 }
-
                 switch ($detectQuestionType) {
                     case GLOBAL_MULTIPLE_ANSWER:
                     case MULTIPLE_ANSWER:
@@ -412,18 +400,15 @@ function lp_upload_quiz_action_handling()
                                 // Fixing scores:
                                 switch ($detectQuestionType) {
                                     case GLOBAL_MULTIPLE_ANSWER:
-                                        if (!$correct) {
-                                            if (isset($noNegativeScoreList[$i])) {
-                                                if (strtolower($noNegativeScoreList[$i]) == 'x') {
-                                                    $score = 0;
-                                                } else {
-                                                    $score = $scoreList[$i] * -1;
-                                                }
-                                            }
+                                        if ($correct) {
+                                            $score = abs($scoreList[$i]);
                                         } else {
-                                            $score = $scoreList[$i];
+                                            if (isset($noNegativeScoreList[$i]) && $noNegativeScoreList[$i] == 'x') {
+                                                $score = 0;
+                                            } else {
+                                                $score = -abs($scoreList[$i]);
+                                            }
                                         }
-
                                         $score /= $numberRightAnswers;
                                         break;
                                     case UNIQUE_ANSWER:
@@ -630,5 +615,5 @@ function detectQuestionType($answers_data)
 
 if ($origin != 'learnpath') {
     //so we are not in learnpath tool
-    Display :: display_footer();
+    Display::display_footer();
 }

+ 1 - 1
main/forum/editpost.php

@@ -110,7 +110,7 @@ $table_link = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
 /* Header */
 $htmlHeadXtra[] = <<<JS
     <script>
-    $(document).on('ready', function() {
+    $(function() {
         $('#reply-add-attachment').on('click', function(e) {
             e.preventDefault();
 

+ 1 - 1
main/forum/editthread.php

@@ -123,7 +123,7 @@ $tableLink = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
 /* Header */
 $htmlHeadXtra[] = <<<JS
     <script>
-    $(document).on('ready', function() {
+    $(function() {
         $('[name="thread_qualify_gradebook"]:checkbox').change(function () {
             if (this.checked) {
                 $('#options_field').show();

+ 4 - 6
main/forum/forumfunction.inc.php

@@ -5790,7 +5790,7 @@ function send_notifications($forum_id = 0, $thread_id = 0, $post_id = 0)
  * @param int  $user_id the user_id of a user (default = 0 => the current user)
  * @param bool $force   force get the notification subscriptions (even if the information is already in the session
  *
- * @return array returns
+ * @return array
  *
  * @author Patrick Cool <patrick.cool@UGent.be>, Ghent University, Belgium
  *
@@ -5806,18 +5806,16 @@ function getNotificationsPerUser($user_id = 0, $force = false, $course_id = 0)
     if (empty($course_id) || $course_id == -1) {
         return null;
     }
-    if ($user_id == 0) {
-        $user_id = api_get_user_id();
-    }
+
+    $user_id = empty($user_id) ? api_get_user_id() : (int) $user_id;
 
     if (!isset($_SESSION['forum_notification']) ||
         $_SESSION['forum_notification']['course'] != $course_id ||
         $force == true
     ) {
         $_SESSION['forum_notification']['course'] = $course_id;
-
         $sql = "SELECT * FROM $table_notification
-                WHERE c_id = $course_id AND user_id='".intval($user_id)."'";
+                WHERE c_id = $course_id AND user_id='".$user_id."'";
 
         $result = Database::query($sql);
         while ($row = Database::fetch_array($result)) {

+ 1 - 1
main/forum/forumqualify.php

@@ -77,7 +77,7 @@ if (isset($_POST['idtextqualify'])) {
 
 /*     Including necessary files */
 $htmlHeadXtra[] = '<script>
-    $(document).ready(function(){
+    $(function() {
         $(\'.hide-me\').slideUp()
     });
 

+ 3 - 2
main/forum/index.php

@@ -30,7 +30,7 @@ api_protect_course_script(true);
 
 $current_course_tool = TOOL_FORUM;
 $htmlHeadXtra[] = '<script>
-$(document).ready(function() {
+$(function() {
     $(\'.hide-me\').slideUp();
 });
 
@@ -49,6 +49,7 @@ $_user = api_get_user_info();
 
 $hideNotifications = api_get_course_setting('hide_forum_notifications');
 $hideNotifications = $hideNotifications == 1;
+
 require_once 'forumfunction.inc.php';
 
 if (api_is_in_gradebook()) {
@@ -391,7 +392,7 @@ if (is_array($forumCategories)) {
                             );
                         }
                     }
-                    //var_dump($forum);
+
                     if ($show_forum) {
                         $form_count++;
                         $mywhatsnew_post_info = isset($whatsnew_post_info[$forum['forum_id']])

+ 1 - 1
main/forum/newthread.php

@@ -138,7 +138,7 @@ if (!empty($groupId)) {
 
 $htmlHeadXtra[] = "
     <script>
-        $(document).on('ready', function() {
+        $(function() {
             $('#reply-add-attachment').on('click', function(e) {
                 e.preventDefault();
     

+ 1 - 1
main/forum/reply.php

@@ -132,7 +132,7 @@ if (!empty($groupId)) {
 /* Header */
 $htmlHeadXtra[] = <<<JS
     <script>
-    $(document).on('ready', function() {
+    $(function() {
         $('#reply-add-attachment').on('click', function(e) {
             e.preventDefault();
 

+ 1 - 1
main/forum/viewforumcategory.php

@@ -32,7 +32,7 @@ Session::erase('_gid');
 api_protect_course_script(true);
 
 $htmlHeadXtra[] = '<script>
-$(document).ready(function(){
+$(function() {
     $(\'.hide-me\').slideUp()
 });
 

+ 1 - 1
main/glossary/index.php

@@ -26,7 +26,7 @@ function setFocus(){
     $("#glossary_title").focus();
 }
 
-$(document).ready(function () {
+$(function() {
     setFocus();
     $( "#dialog:ui-dialog" ).dialog( "destroy" );
     $( "#dialog-confirm" ).dialog({

+ 1 - 1
main/gradebook/get_badges.php

@@ -56,7 +56,7 @@ $tpl = new Template(get_lang('Badges'), false, false);
 $tpl->assign(
     'content',
     "<script>
-    $(document).on('ready', function (){ 
+    $(function() {
         OpenBadges.issue_no_modal(".json_encode($assertions)."); 
     });
     </script>"

+ 3 - 3
main/gradebook/gradebook.php

@@ -25,14 +25,14 @@ if (!empty($_GET['course'])) {
 $selectcat = isset($_GET['selectcat']) ? (int) $_GET['selectcat'] : 0;
 
 $htmlHeadXtra[] = '<script>
-$(document).ready( function() {
+$(function() {
 	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+")").hide();
 		}
 	}
- } );
- </script>';
+});
+</script>';
 api_block_anonymous_users();
 
 $htmlHeadXtra[] = '<script>

+ 4 - 6
main/gradebook/gradebook_add_eval.php

@@ -22,7 +22,7 @@ $evaladd = new Evaluation();
 $evaladd->set_user_id($userId);
 if (!empty($select_cat)) {
     $evaladd->set_category_id($_GET['selectcat']);
-    $cat = Category :: load($_GET['selectcat']);
+    $cat = Category::load($_GET['selectcat']);
     $evaladd->set_course_code($cat[0]->get_course_code());
 } else {
     $evaladd->set_category_id(0);
@@ -52,18 +52,16 @@ if ($form->validate()) {
     $eval->set_course_code(api_get_course_id());
     $eval->set_category_id($values['hid_category_id']);
 
-    $parent_cat = Category :: load($values['hid_category_id']);
+    $parent_cat = Category::load($values['hid_category_id']);
     $global_weight = $cat[0]->get_weight();
     //$values['weight'] = $values['weight_mask']/$global_weight*$parent_cat[0]->get_weight();
     $values['weight'] = $values['weight_mask'];
 
     $eval->set_weight($values['weight']);
     $eval->set_max($values['max']);
-
+    $visible = 1;
     if (empty($values['visible'])) {
         $visible = 0;
-    } else {
-        $visible = 1;
     }
     $eval->set_visible($visible);
     $eval->add();
@@ -114,7 +112,7 @@ $interbreadcrumb[] = [
 $this_section = SECTION_COURSES;
 
 $htmlHeadXtra[] = '<script>
-$(document).ready( function() {
+$(function() {
     $("#hid_category_id").change(function() {
        $("#hid_category_id option:selected").each(function () {
            var cat_id = $(this).val();

+ 1 - 1
main/gradebook/gradebook_add_link.php

@@ -175,7 +175,7 @@ $interbreadcrumb[] = [
 $this_section = SECTION_COURSES;
 
 $htmlHeadXtra[] = '<script>
-$(document).ready( function() {
+$(function() {
     $("#hide_category_id").change(function() {
        $("#hide_category_id option:selected").each(function () {
            var cat_id = $(this).val();

+ 10 - 3
main/gradebook/gradebook_add_result.php

@@ -37,18 +37,25 @@ if ($add_result_form->validate()) {
         header('Location: gradebook_view_result.php?addresultnostudents=&selecteval='.$selectEval.'&'.api_get_cidreq());
         exit;
     }
-    $scores = ($values['score']);
-    foreach ($scores as $row) {
+
+    $scores = $values['score'];
+    $sumResult = 0;
+    $bestResult = 0;
+    $studentScoreList = [];
+    foreach ($scores as $userId => $row) {
         $res = new Result();
         $res->set_evaluation_id($values['evaluation_id']);
         $res->set_user_id(key($scores));
         //if no scores are given, don't set the score
-        if ((!empty($row)) || ($row == '0')) {
+        if (!empty($row) || $row == '0') {
             $res->set_score($row);
         }
         $res->add();
         next($scores);
     }
+
+    Evaluation::generateStats($values['evaluation_id']);
+
     Display::addFlash(Display::return_message(get_lang('ResultAdded'), 'confirmation', false));
     header('Location: gradebook_view_result.php?addresult=&selecteval='.$selectEval.'&'.api_get_cidreq());
     exit;

+ 76 - 17
main/gradebook/gradebook_display_summary.php

@@ -9,7 +9,7 @@ use ChamiloSession as Session;
 require_once __DIR__.'/../inc/global.inc.php';
 $current_course_tool = TOOL_GRADEBOOK;
 
-api_protect_course_script();
+api_protect_course_script(true);
 api_set_more_memory_and_time_limits();
 api_block_anonymous_users();
 GradebookUtils::block_students();
@@ -18,6 +18,7 @@ $cat_id = isset($_GET['selectcat']) ? (int) $_GET['selectcat'] : null;
 $action = isset($_GET['action']) && $_GET['action'] ? $_GET['action'] : null;
 
 $sessionId = api_get_session_id();
+$courseInfo = api_get_course_info();
 $statusToFilter = empty($sessionId) ? STUDENT : 0;
 
 $userList = CourseManager::get_user_list_from_course_code(
@@ -28,19 +29,68 @@ $userList = CourseManager::get_user_list_from_course_code(
     $statusToFilter
 );
 
+$loadStats = [];
+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];
+    }
+}
+
+/*Session::write('use_gradebook_cache', false);
+$useCache = api_get_configuration_value('gradebook_use_apcu_cache');
+$cacheAvailable = api_get_configuration_value('apc') && $useCache;
+
+if ($cacheAvailable) {
+    $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
+    $cacheDriver->deleteAll();
+    $cacheDriver->flushAll();
+}*/
+
 switch ($action) {
     case 'export_all':
+        //Session::write('use_gradebook_cache', true);
         $cats = Category::load($cat_id, null, null, null, null, null, false);
-        $studentList = CourseManager::get_user_list_from_course_code(
+        /** @var Category $cat */
+        $cat = $cats[0];
+        $allcat = $cat->get_subcategories(
+            null,
+            api_get_course_id(),
+            api_get_session_id()
+        );
+        $alleval = $cat->get_evaluations(
+            null,
+            true,
             api_get_course_id(),
-            $sessionId,
+            api_get_session_id()
+        );
+        $alllink = $cat->get_links(
             null,
+            true,
+            api_get_course_id(),
+            api_get_session_id()
+        );
+
+        $gradebooktable = new GradebookTable(
+            $cat,
+            $allcat,
+            $alleval,
+            $alllink,
+            null, // params
+            true, // $exportToPdf
+            false, // showteacher
             null,
-            $statusToFilter
+            $userList,
+            $loadStats
         );
 
+        $key = $gradebooktable->getPreloadDataKey();
+        // preloads data
+        Session::erase($key);
+        $defaultData = $gradebooktable->preloadData();
+
         $tpl = new Template('', false, false, false);
-        $courseInfo = api_get_course_info();
         $params = [
             'pdf_title' => sprintf(get_lang('GradeFromX'), $courseInfo['name']),
             'session_info' => '',
@@ -54,44 +104,53 @@ switch ($action) {
             'orientation' => 'P',
         ];
         $pdf = new PDF('A4', $params['orientation'], $params, $tpl);
+        $counter = 0;
         $htmlList = [];
         foreach ($userList as $index => $value) {
             $htmlList[] = GradebookUtils::generateTable(
+                $courseInfo,
                 $value['user_id'],
                 $cats,
+                false,
                 true,
-                true,
-                $studentList,
+                $userList,
                 $pdf
             );
+            $counter++;
         }
 
         if (!empty($htmlList)) {
-            // Print certificates (without the common header/footer/watermark
-            //  stuff) and return as one multiple-pages PDF
-            /*$address = api_get_setting('institution_address');
-            $phone = api_get_setting('administratorTelephone');
-            $address = str_replace('\n', '<br />', $address);
-            $pdf->custom_header = array('html' => "<h5 align='right'>$address <br />$phone</h5>");*/
-            //  stuff) and return as one multiple-pages PDF
+            $counter = 0;
+            //error_log('Loading html list');
+            $content = '';
+            foreach ($htmlList as $value) {
+                $content .= $value.'<pagebreak>';
+                //error_log('Loading html: '.$counter);
+                $counter++;
+            }
+
+            $tempFile = api_get_path(SYS_ARCHIVE_PATH).uniqid('gradebook_export_all').'.html';
+            file_put_contents($tempFile, $content);
+            //error_log('generating pdf');
             $pdf->html_to_pdf(
-                $htmlList,
+                $tempFile,
                 null,
                 null,
                 false,
                 true,
                 true
             );
+            //error_log('End generating');
         }
 
         // Delete calc_score session data
         Session::erase('calc_score');
-
         break;
     case 'download':
+        //Session::write('use_gradebook_cache', true);
         $userId = isset($_GET['user_id']) && $_GET['user_id'] ? $_GET['user_id'] : null;
         $cats = Category::load($cat_id, null, null, null, null, null, false);
-        GradebookUtils::generateTable($userId, $cats);
+        GradebookUtils::generateTable($courseInfo, $userId, $cats);
         break;
 }
 

+ 1 - 1
main/gradebook/gradebook_edit_eval.php

@@ -64,7 +64,7 @@ $interbreadcrumb[] = [
 ];
 
 $htmlHeadXtra[] = '<script>
-$(document).ready( function() {
+$(function() {
     $("#hid_category_id").change(function() {
        $("#hid_category_id option:selected").each(function () {
            var cat_id = $(this).val();

+ 1 - 1
main/gradebook/gradebook_edit_link.php

@@ -124,7 +124,7 @@ $interbreadcrumb[] = [
 ];
 
 $htmlHeadXtra[] = '<script>
-$(document).ready( function() {
+$(function() {
     $("#hide_category_id").change(function() {
        $("#hide_category_id option:selected").each(function () {
            var cat_id = $(this).val();

+ 11 - 0
main/gradebook/gradebook_edit_result.php

@@ -17,6 +17,9 @@ if (empty($select_eval)) {
 $resultedit = Result::load(null, null, $select_eval);
 $evaluation = Evaluation::load($select_eval);
 $evaluation[0]->check_lock_permissions();
+$courseInfo = api_get_course_info();
+$sessionId = api_get_session_id();
+
 $edit_result_form = new EvalForm(
     EvalForm::TYPE_ALL_RESULTS_EDIT,
     $evaluation[0],
@@ -28,6 +31,8 @@ $edit_result_form = new EvalForm(
 if ($edit_result_form->validate()) {
     $values = $edit_result_form->exportValues();
     $scores = $values['score'];
+    $bestResult = 0;
+    $scoreFinalList = [];
     foreach ($scores as $userId => $score) {
         /** @var array $resultedit */
         $resultedit = Result::load($userId);
@@ -37,6 +42,12 @@ if ($edit_result_form->validate()) {
         if (empty($score)) {
             $score = 0;
         }
+
+        $scoreFinalList[$result->get_user_id()] = $score;
+
+        if ($score > $bestResult) {
+            $bestResult = $score;
+        }
         $score = api_number_format($score, api_get_setting('gradebook_number_decimals'));
         $result->set_score($score);
         $result->save();

+ 4 - 15
main/gradebook/gradebook_view_result.php

@@ -25,13 +25,13 @@ $interbreadcrumb[] = [
 ];
 
 //load the evaluation & category
-$select_eval = intval($_GET['selecteval']);
+$select_eval = (int) $_GET['selecteval'];
 if (empty($select_eval)) {
     api_not_allowed();
 }
 
 $displayscore = ScoreDisplay::instance();
-$eval = Evaluation :: load($select_eval);
+$eval = Evaluation::load($select_eval);
 $overwritescore = 0;
 if ($eval[0]->get_category_id() < 0) {
     // if category id is negative, then the evaluation's origin is a link
@@ -43,7 +43,7 @@ if ($eval[0]->get_category_id() < 0) {
 
 //load the result with the evaluation id
 if (isset($_GET['delete_mark'])) {
-    $result = Result :: load($_GET['delete_mark']);
+    $result = Result::load($_GET['delete_mark']);
     if (!empty($result[0])) {
         $result[0]->delete();
     }
@@ -116,13 +116,6 @@ if (isset($_GET['action'])) {
                 );
 
                 $form->addTextarea('comment', get_lang('Comment'));
-
-                /*$form->addRule(
-                    'score',
-                    get_lang('ValueTooBig'),
-                    'max_numeric_length',
-                    $evaluation->get_max()
-                );*/
                 $form->addButtonSave(get_lang('Save'));
                 $attemptList = ResultTable::getResultAttemptTable($result, $url);
                 $form->addLabel(get_lang('Attempts'), $attemptList);
@@ -130,7 +123,6 @@ if (isset($_GET['action'])) {
                 if ($form->validate()) {
                     $values = $form->getSubmitValues();
                     $newScore = $values['score'];
-
                     $newScore = api_number_format(
                         $newScore,
                         api_get_setting('gradebook_number_decimals')
@@ -155,7 +147,6 @@ if (isset($_GET['action'])) {
                 }
 
                 Display::display_header();
-
                 $items[] = [
                     'url' => $backUrl,
                     'content' => Display::return_icon(
@@ -165,7 +156,6 @@ if (isset($_GET['action'])) {
                         ICON_SIZE_MEDIUM
                     ),
                 ];
-
                 echo Display::actions($items);
                 $form->display();
                 Display::display_footer();
@@ -215,7 +205,6 @@ if (isset($_GET['editres'])) {
                 'created_at' => $now,
                 'updated_at' => $now,
             ];
-
             Database::insert($table, $params);
         }
 
@@ -651,7 +640,7 @@ if (!isset($_GET['export']) && (!isset($_GET['import']))) {
         'url' => api_get_path(WEB_CODE_PATH).'gradebook/gradebook_view_result.php?selecteval='.$select_eval.'&'.api_get_cidreq(),
         'name' => get_lang('ViewResult'),
     ];
-    Display :: display_header('');
+    Display::display_header();
 }
 
 if (isset($_GET['adduser'])) {

+ 74 - 73
main/gradebook/index.php

@@ -17,12 +17,65 @@ $current_course_tool = TOOL_GRADEBOOK;
 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();
+$courseInfo = api_get_course_info();
+
+$action = isset($_GET['action']) ? $_GET['action'] : null;
+$itemId = isset($_GET['itemId']) ? $_GET['itemId'] : 0;
+
+switch ($action) {
+    case 'generate_eval_stats':
+        if (!empty($itemId)) {
+            Evaluation::generateStats($itemId);
+            Display::addFlash(Display::return_message(get_lang('Updated')));
+        }
+        header('Location: '.api_get_self().'?'.api_get_cidreq());
+        exit;
+        break;
+    case 'generate_link_stats':
+        if (!empty($itemId)) {
+            $link = LinkFactory::create(LINK_EXERCISE);
+            $links = $link::load($itemId);
+            /** @var ExerciseLink $link */
+            foreach ($links as $link) {
+                $exercise = new Exercise(api_get_course_int_id());
+                $exercise->read($link->get_ref_id());
+                $exercise->generateStats($link->get_ref_id(), api_get_course_info(), api_get_session_id());
+            }
+            Display::addFlash(Display::return_message(get_lang('Updated')));
+        }
+        header('Location: '.api_get_self().'?'.api_get_cidreq());
+        exit;
+        break;
+    case 'lock':
+        $category_to_lock = Category::load($_GET['category_id']);
+        $category_to_lock[0]->lockAllItems(1);
+        $confirmation_message = get_lang('GradebookLockedAlert');
+        break;
+    case 'unlock':
+        if (api_is_platform_admin()) {
+            $category_to_lock = Category::load($_GET['category_id']);
+            $category_to_lock[0]->lockAllItems(0);
+            $confirmation_message = get_lang('EvaluationHasBeenUnLocked');
+        }
+        break;
+    case 'export_table':
+        $hidePdfReport = api_get_configuration_value('gradebook_hide_pdf_report_button');
+        if ($hidePdfReport) {
+            api_not_allowed(true);
+        }
+        if (isset($_GET['category_id'])) {
+            $cats = Category::load($_GET['category_id'], null, null, null, null, null, false);
+            GradebookUtils::generateTable($courseInfo, api_get_user_id(), $cats);
+            exit;
+        }
+        break;
+}
+
+ob_start();
 
 // Make sure the destination for scripts is index.php instead of gradebook.php
 Category::setUrl('index.php');
@@ -41,7 +94,7 @@ function confirmation() {
 	}
 }
 
-$(document).ready(function() {
+$(function() {
     $("body").on("click", ".view_children", function() {
         var id = $(this).attr("data-cat-id");
         $(".hidden_"+id).removeClass("hidden");
@@ -262,15 +315,16 @@ if (isset($_GET['moveeval'])) {
 
 //move a link
 if (isset($_GET['movelink'])) {
+    $moveLink = (int) $_GET['movelink'];
     GradebookUtils::block_students();
-    $link = LinkFactory::load($_GET['movelink']);
+    $link = LinkFactory::load($moveLink);
     $move_form = new LinkForm(
         LinkForm::TYPE_MOVE,
         null,
         $link[0],
         'move_link_form',
         null,
-        api_get_self().'?movelink='.$_GET['movelink'].'&selectcat='.$selectCat.'&'.api_get_cidreq()
+        api_get_self().'?movelink='.$moveLink.'&selectcat='.$selectCat.'&'.api_get_cidreq()
     );
 
     if ($move_form->validate()) {
@@ -323,10 +377,9 @@ if (isset($_GET['deletecat'])) {
 // Parameters for evaluations.
 if (isset($_GET['visibleeval'])) {
     GradebookUtils::block_students();
+    $visibility_command = 0;
     if (isset($_GET['set_visible'])) {
         $visibility_command = 1;
-    } else {
-        $visibility_command = 0;
     }
     $eval = Evaluation::load($_GET['visibleeval']);
     $eval[0]->set_visible($visibility_command);
@@ -344,7 +397,7 @@ if (isset($_GET['visibleeval'])) {
 // Parameters for evaluations.
 if (isset($_GET['lockedeval'])) {
     GradebookUtils::block_students();
-    $locked = Security::remove_XSS($_GET['lockedeval']);
+    $locked = (int) $_GET['lockedeval'];
     if (isset($_GET['typelocked']) && api_is_platform_admin()) {
         $type_locked = 0;
         $confirmation_message = get_lang('EvaluationHasBeenUnLocked');
@@ -373,10 +426,9 @@ if (isset($_GET['deleteeval'])) {
 // Parameters for links.
 if (isset($_GET['visiblelink'])) {
     GradebookUtils::block_students();
+    $visibility_command = 0;
     if (isset($_GET['set_visible'])) {
         $visibility_command = 1;
-    } else {
-        $visibility_command = 0;
     }
     $link = LinkFactory::load($_GET['visiblelink']);
     if (isset($link) && isset($link[0])) {
@@ -395,7 +447,7 @@ if (isset($_GET['visiblelink'])) {
 
 if (isset($_GET['deletelink'])) {
     GradebookUtils::block_students();
-    $get_delete_link = intval($_GET['deletelink']);
+    $get_delete_link = (int) $_GET['deletelink'];
     //fixing #5229
     if (!empty($get_delete_link)) {
         $link = LinkFactory::load($get_delete_link);
@@ -441,38 +493,12 @@ if (!empty($course_to_crsind) && !isset($_GET['confirm'])) {
     $filter_warning_msg = false;
 }
 
-$action = isset($_GET['action']) ? $_GET['action'] : null;
-
-switch ($action) {
-    case 'lock':
-        $category_to_lock = Category::load($_GET['category_id']);
-        $category_to_lock[0]->lockAllItems(1);
-        $confirmation_message = get_lang('GradebookLockedAlert');
-        break;
-    case 'unlock':
-        if (api_is_platform_admin()) {
-            $category_to_lock = Category::load($_GET['category_id']);
-            $category_to_lock[0]->lockAllItems(0);
-            $confirmation_message = get_lang('EvaluationHasBeenUnLocked');
-        }
-        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;
-}
-
 // Actions on the sortabletable.
 if (isset($_POST['action'])) {
     GradebookUtils::block_students();
     $number_of_selected_items = count($_POST['id']);
 
-    if ($number_of_selected_items == '0') {
+    if ($number_of_selected_items == 0) {
         $warning_message = get_lang('NoItemsSelected');
         $filter_warning_msg = false;
     } else {
@@ -751,11 +777,9 @@ if (isset($_GET['studentoverview'])) {
     }
     $cats = Category::load($selectCat, null, null, null, null, null, false);
 
-    //with this fix the teacher only can view 1 gradebook
+    // With this fix the teacher only can view 1 gradebook
     if (api_is_platform_admin()) {
         $stud_id = (api_is_allowed_to_edit() ? null : api_get_user_id());
-    } else {
-        $stud_id = $stud_id;
     }
 
     $allcat = $cats[0]->get_subcategories($stud_id, $course_code, $session_id);
@@ -787,7 +811,6 @@ if (!empty($selectCat)) {
     $cat = new Category();
     $course_id = CourseManager::get_course_by_category($selectCat);
     $show_message = $cat->show_message_resource_delete($course_id);
-
     if ($show_message == '') {
         // Student
         if (!api_is_allowed_to_edit() && !api_is_excluded_user_type()) {
@@ -819,7 +842,7 @@ if (!api_is_allowed_to_edit(null, true)) {
     if ($allowButton) {
         $actionsLeft .= Display::url(
             Display::returnFontAwesomeIcon('file-pdf-o').get_lang('DownloadReportPdf'),
-            api_get_self().'?action=export_table&'.api_get_cidreq(),
+            api_get_self().'?action=export_table&'.api_get_cidreq().'&category_id='.$selectCat,
             ['class' => 'btn btn-default']
         );
     }
@@ -828,6 +851,12 @@ if (!api_is_allowed_to_edit(null, true)) {
 if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)) {
     echo '<meta http-equiv="refresh" content="0;url='.api_get_self().'?'.api_get_cidreq().'" />';
 } else {
+    // Tool introduction
+    Display::display_introduction_section(
+        TOOL_GRADEBOOK,
+        ['ToolbarSet' => 'AssessmentsIntroduction']
+    );
+
     if (!empty($actionsLeft)) {
         echo $toolbar = Display::toolbarAction(
             'gradebook-student-actions',
@@ -836,12 +865,6 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
     }
 
     if (api_is_allowed_to_edit(null, true)) {
-        // Tool introduction
-        Display::display_introduction_section(
-            TOOL_GRADEBOOK,
-            ['ToolbarSet' => 'AssessmentsIntroduction']
-        );
-
         if (((empty($selectCat)) || (isset($_GET['cidReq']) && $_GET['cidReq'] !== '')) ||
             (isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'false')
         ) {
@@ -960,7 +983,7 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
                 if (api_is_allowed_to_edit(null, true) &&
                     api_get_setting('gradebook_enable_grade_model') === 'true'
                 ) {
-                    //Showing the grading system
+                    // Showing the grading system
                     if (!empty($grade_models[$grade_model_id])) {
                         echo Display::return_message(
                             get_lang('GradeModel').': '.$grade_models[$grade_model_id]['name']
@@ -1002,28 +1025,6 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
                     $gradebookTable->td_attributes = [
                         4 => 'class="text-center"',
                     ];
-                } else {
-                    /*if (empty($model)) {
-                        $gradebookTable->td_attributes = [
-                            3 => 'class="text-right"',
-                            4 => 'class="text-center"',
-                        ];
-
-                        if (!empty($loadStats)) {
-                            for ($z = 5; $z < count($loadStats); $z++) {
-                                $gradebookTable->td_attributes[$z] = 'class="text-center"';
-                            }
-                        }
-                    } else {
-                        $gradebookTable->td_attributes = [
-                            3 => 'class="text-right"',
-                            4 => 'class="text-center"',
-                        ];
-                    }
-
-                    if ($action == 'export_table') {
-                        unset($gradebookTable->td_attributes[7]);
-                    }*/
                 }
 
                 $table = $gradebookTable->return_table();
@@ -1034,7 +1035,7 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)
                     $graph = $gradebookTable->getGraph();
                 }
 
-                if ($action == 'export_table') {
+                if ($action === 'export_table') {
                     ob_clean();
                     $params = [
                         'pdf_title' => sprintf(get_lang('GradeFromX'), $courseInfo['name']),

+ 29 - 70
main/gradebook/lib/GradebookUtils.php

@@ -379,6 +379,7 @@ class GradebookUtils
                     ICON_SIZE_SMALL
                 ).
                 '</a>';
+
             if (api_is_allowed_to_edit(null, true)) {
                 $modify_icons .= '&nbsp;<a href="gradebook_showlog_eval.php?visiblelog='.$eval->get_id().'&selectcat='.$selectcat.' &'.$courseParams.'">'.
                     Display::return_icon(
@@ -388,6 +389,14 @@ class GradebookUtils
                         ICON_SIZE_SMALL
                     ).
                     '</a>';
+
+                $allowStats = api_get_configuration_value('allow_gradebook_stats');
+                if ($allowStats) {
+                    $modify_icons .= Display::url(
+                        Display::return_icon('reload.png', get_lang('GenerateStats')),
+                        api_get_self().'?itemId='.$eval->get_id().'&action=generate_eval_stats&selectcat='.$selectcat.'&'.$courseParams
+                    );
+                }
             }
 
             if ($is_locked && !api_is_platform_admin()) {
@@ -467,6 +476,7 @@ class GradebookUtils
                     ICON_SIZE_SMALL
                 ).
                 '</a>';
+
             $modify_icons .= '&nbsp;<a href="gradebook_showlog_link.php?visiblelink='.$link->get_id().'&selectcat='.$selectcat.'&'.$courseParams.'">'.
                 Display::return_icon(
                     'history.png',
@@ -476,6 +486,14 @@ class GradebookUtils
                 ).
                 '</a>';
 
+            $allowStats = api_get_configuration_value('allow_gradebook_stats');
+            if ($allowStats && $link->get_type() == LINK_EXERCISE) {
+                $modify_icons .= Display::url(
+                    Display::return_icon('reload.png', get_lang('GenerateStats')),
+                    api_get_self().'?itemId='.$link->get_id().'&action=generate_link_stats&selectcat='.$selectcat.'&'.$courseParams
+                );
+            }
+
             //If a work is added in a gradebook you can only delete the link in the work tool
             if ($is_locked && !api_is_platform_admin()) {
                 $modify_icons .= '&nbsp;'.
@@ -656,40 +674,6 @@ class GradebookUtils
         $current_value = $data;
     }
 
-    /**
-     * XML-parser: handle end of element.
-     */
-    public static function element_end($parser, $data)
-    {
-        global $user;
-        global $users;
-        global $current_value;
-        switch ($data) {
-            case 'Result':
-                $users[] = $user;
-                break;
-            default:
-                $user[$data] = $current_value;
-                break;
-        }
-    }
-
-    /**
-     * XML-parser: handle start of element.
-     */
-    public static function element_start($parser, $data)
-    {
-        global $user;
-        global $current_tag;
-        switch ($data) {
-            case 'Result':
-                $user = [];
-                break;
-            default:
-                $current_tag = $data;
-        }
-    }
-
     public static function overwritescore($resid, $importscore, $eval_max)
     {
         $result = Result::load($resid);
@@ -702,30 +686,6 @@ class GradebookUtils
         unset($result);
     }
 
-    /**
-     * Read the XML-file.
-     *
-     * @param string $file Path to the XML-file
-     *
-     * @return array All user information read from the file
-     */
-    public static function parse_xml_data($file)
-    {
-        global $current_tag;
-        global $current_value;
-        global $user;
-        global $users;
-        $users = [];
-        $parser = xml_parser_create();
-        xml_set_element_handler($parser, 'element_start', 'element_end');
-        xml_set_character_data_handler($parser, "character_data");
-        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
-        xml_parse($parser, file_get_contents($file));
-        xml_parser_free($parser);
-
-        return $users;
-    }
-
     /**
      * register user info about certificate.
      *
@@ -1589,16 +1549,19 @@ class GradebookUtils
     }
 
     /**
-     * @param int   $userId
-     * @param array $cats
-     * @param bool  $saveToFile
-     * @param bool  $saveToHtmlFile
-     * @param array $studentList
-     * @param PDF   $pdf
+     * @param GradebookTable $gradebooktable
+     * @param array          $courseInfo
+     * @param int            $userId
+     * @param array          $cats
+     * @param bool           $saveToFile
+     * @param bool           $saveToHtmlFile
+     * @param array          $studentList
+     * @param PDF            $pdf
      *
      * @return string
      */
     public static function generateTable(
+        $courseInfo,
         $userId,
         $cats,
         $saveToFile = false,
@@ -1606,9 +1569,7 @@ class GradebookUtils
         $studentList = [],
         $pdf = null
     ) {
-        $courseInfo = api_get_course_info();
         $userInfo = api_get_user_info($userId);
-
         $cat = $cats[0];
         $allcat = $cats[0]->get_subcategories(
             $userId,
@@ -1626,6 +1587,7 @@ class GradebookUtils
                 $loadStats = [2];
             }
         }
+
         $gradebooktable = new GradebookTable(
             $cat,
             $allcat,
@@ -1642,9 +1604,6 @@ class GradebookUtils
         $gradebooktable->userId = $userId;
 
         if (api_is_allowed_to_edit(null, true)) {
-            /*$gradebooktable->td_attributes = [
-                4 => 'class=centered',
-            ];*/
         } else {
             $gradebooktable->td_attributes = [
                 3 => 'class=centered',
@@ -1654,7 +1613,6 @@ class GradebookUtils
                 7 => 'class=centered',
             ];
         }
-
         $table = $gradebooktable->return_table();
         $graph = $gradebooktable->getGraph();
 
@@ -1691,6 +1649,7 @@ class GradebookUtils
         );
 
         if ($saveToHtmlFile) {
+            return $result;
             file_put_contents($file, $result);
 
             return $file;

+ 51 - 1
main/gradebook/lib/be/abstractlink.class.php

@@ -17,6 +17,8 @@ abstract class AbstractLink implements GradebookItem
 {
     public $course_id;
     public $studentList;
+    /** @var \Chamilo\CoreBundle\Entity\GradebookLink */
+    public $entity;
     protected $id;
     protected $type;
     protected $ref_id;
@@ -697,6 +699,45 @@ abstract class AbstractLink implements GradebookItem
         return $skillToString;
     }
 
+    /**
+     * @param int    $itemId
+     * @param int    $linkType
+     * @param string $courseCode
+     * @param int    $sessionId
+     *
+     * @return array|bool|\Doctrine\DBAL\Driver\Statement
+     */
+    public static function getGradebookLinksFromItem($itemId, $linkType, $courseCode, $sessionId = 0)
+    {
+        if (empty($courseCode) || empty($itemId) || empty($linkType)) {
+            return false;
+        }
+        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
+        $tableCategory = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
+        $itemId = (int) $itemId;
+        $linkType = (int) $linkType;
+        $sessionId = (int) $sessionId;
+        $courseCode = Database::escape_string($courseCode);
+
+        $sql = "SELECT DISTINCT l.* 
+                FROM $table l INNER JOIN $tableCategory c 
+                ON (c.course_code = l.course_code AND c.id = l.category_id)
+                WHERE 
+                    ref_id = $itemId AND 
+                    type = $linkType AND 
+                    l.course_code = '$courseCode' AND 
+                    c.session_id = $sessionId";
+
+        $result = Database::query($sql);
+        if (Database::num_rows($result)) {
+            $result = Database::store_result($result);
+
+            return $result;
+        }
+
+        return false;
+    }
+
     /**
      * @param Doctrine\DBAL\Driver\Statement|null $result
      *
@@ -705,6 +746,12 @@ abstract class AbstractLink implements GradebookItem
     private static function create_objects_from_sql_result($result)
     {
         $links = [];
+        $allow = api_get_configuration_value('allow_gradebook_stats');
+        if ($allow) {
+            $em = Database::getManager();
+            $repo = $em->getRepository('ChamiloCoreBundle:GradebookLink');
+        }
+
         while ($data = Database::fetch_array($result)) {
             $link = LinkFactory::create($data['type']);
             $link->set_id($data['id']);
@@ -720,8 +767,11 @@ abstract class AbstractLink implements GradebookItem
 
             //session id should depend of the category --> $data['category_id']
             $session_id = api_get_session_id();
-
             $link->set_session_id($session_id);
+
+            if ($allow) {
+                $link->entity = $repo->find($data['id']);
+            }
             $links[] = $link;
         }
 

+ 18 - 5
main/gradebook/lib/be/category.class.php

@@ -16,6 +16,8 @@ class Category implements GradebookItem
     public $evaluations;
     public $links;
     public $subCategories;
+    /** @var GradebookCategory */
+    public $entity;
     private $id;
     private $name;
     private $description;
@@ -266,12 +268,13 @@ class Category implements GradebookItem
      */
     public function setCourseListDependency($value)
     {
-        $result = [];
-        if (@unserialize($value) !== false) {
-            $result = unserialize($value);
-        }
+        $this->courseDependency = [];
+
+        $unserialized = UnserializeApi::unserialize('not_allowed_classes', $value, true);
 
-        $this->courseDependency = $result;
+        if (false !== $unserialized) {
+            $this->courseDependency = $unserialized;
+        }
     }
 
     /**
@@ -2603,6 +2606,12 @@ class Category implements GradebookItem
     private static function create_category_objects_from_sql_result($result)
     {
         $categories = [];
+        $allow = api_get_configuration_value('allow_gradebook_stats');
+        if ($allow) {
+            $em = Database::getManager();
+            $repo = $em->getRepository('ChamiloCoreBundle:GradebookCategory');
+        }
+
         while ($data = Database::fetch_array($result)) {
             $cat = new Category();
             $cat->set_id($data['id']);
@@ -2625,6 +2634,10 @@ class Category implements GradebookItem
             $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
             $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
 
+            if ($allow) {
+                $cat->entity = $repo->find($data['id']);
+            }
+
             $categories[] = $cat;
         }
 

+ 113 - 0
main/gradebook/lib/be/evaluation.class.php

@@ -11,6 +11,8 @@ use ChamiloSession as Session;
 class Evaluation implements GradebookItem
 {
     public $studentList;
+    /** @var \Chamilo\CoreBundle\Entity\GradebookEvaluation */
+    public $entity;
     private $id;
     private $name;
     private $description;
@@ -535,6 +537,58 @@ class Evaluation implements GradebookItem
      */
     public function calc_score($stud_id = null, $type = null)
     {
+        $allowStats = api_get_configuration_value('allow_gradebook_stats');
+        if ($allowStats) {
+            $evaluation = $this->entity;
+            if (!empty($evaluation)) {
+                $weight = $evaluation->getMax();
+                switch ($type) {
+                    case 'best':
+                        $bestResult = $evaluation->getBestScore();
+                        $result = [$bestResult, $weight];
+
+                        return $result;
+                        break;
+                    case 'average':
+                        $count = count($evaluation->getUserScoreList());
+                        if (empty($count)) {
+                            $result = [0, $weight];
+
+                            return $result;
+                        }
+
+                        $sumResult = array_sum($evaluation->getUserScoreList());
+                        $result = [$sumResult / $count, $weight];
+
+                        return $result;
+                        break;
+                    case 'ranking':
+                        $ranking = AbstractLink::getCurrentUserRanking($stud_id, $evaluation->getUserScoreList());
+
+                        return $ranking;
+                        break;
+                    default:
+                        $weight = $evaluation->getMax();
+                        if (!empty($stud_id)) {
+                            $scoreList = $evaluation->getUserScoreList();
+                            $result = [0, $weight];
+                            if (isset($scoreList[$stud_id])) {
+                                $result = [$scoreList[$stud_id], $weight];
+                            }
+
+                            return $result;
+                        } else {
+                            $studentCount = count($evaluation->getUserScoreList());
+                            $sumResult = array_sum($evaluation->getUserScoreList());
+                            $result = [$sumResult, $studentCount];
+                        }
+
+                        return $result;
+                        break;
+                }
+            }
+        }
+
         $useSession = true;
         if (isset($stud_id) && empty($type)) {
             $key = 'result_score_student_list_'.api_get_course_int_id().'_'.api_get_session_id().'_'.$this->id.'_'.$stud_id;
@@ -800,16 +854,65 @@ class Evaluation implements GradebookItem
     {
     }
 
+    /**
+     * @return mixed
+     */
     public function getStudentList()
     {
         return $this->studentList;
     }
 
+    /**
+     * @param $list
+     */
     public function setStudentList($list)
     {
         $this->studentList = $list;
     }
 
+    /**
+     * @param int $evaluationId
+     */
+    public static function generateStats($evaluationId)
+    {
+        $allowStats = api_get_configuration_value('allow_gradebook_stats');
+        if ($allowStats) {
+            $evaluation = self::load($evaluationId);
+
+            $results = Result::load(null, null, $evaluationId);
+            $sumResult = 0;
+            $bestResult = 0;
+            $average = 0;
+            $scoreList = [];
+
+            if (!empty($results)) {
+                /** @var Result $result */
+                foreach ($results as $result) {
+                    $score = $result->get_score();
+                    $scoreList[$result->get_user_id()] = $score;
+                    $sumResult += $score;
+                    if ($score > $bestResult) {
+                        $bestResult = $score;
+                    }
+                }
+                $average = $sumResult / count($results);
+            }
+
+            /** @var Evaluation $evaluation */
+            $evaluation = $evaluation[0];
+            $evaluation = $evaluation->entity;
+            $evaluation
+                ->setBestScore($bestResult)
+                ->setAverageScore($average)
+                ->setUserScoreList($scoreList)
+            ;
+
+            $em = Database::getManager();
+            $em->persist($evaluation);
+            $em->flush();
+        }
+    }
+
     /**
      * @param int $courseId
      *
@@ -830,6 +933,12 @@ class Evaluation implements GradebookItem
     private static function create_evaluation_objects_from_sql_result($result)
     {
         $alleval = [];
+        $allow = api_get_configuration_value('allow_gradebook_stats');
+        if ($allow) {
+            $em = Database::getManager();
+            $repo = $em->getRepository('ChamiloCoreBundle:GradebookEvaluation');
+        }
+
         if (Database::num_rows($result)) {
             while ($data = Database::fetch_array($result)) {
                 $eval = new Evaluation();
@@ -849,6 +958,10 @@ class Evaluation implements GradebookItem
                 $eval->set_locked($data['locked']);
                 $eval->setSessionId(api_get_session_id());
 
+                if ($allow) {
+                    $eval->entity = $repo->find($data['id']);
+                }
+
                 $alleval[] = $eval;
             }
         }

+ 70 - 1
main/gradebook/lib/be/exerciselink.class.php

@@ -176,6 +176,60 @@ class ExerciseLink extends AbstractLink
      */
     public function calc_score($stud_id = null, $type = null)
     {
+        $allowStats = api_get_configuration_value('allow_gradebook_stats');
+
+        if ($allowStats) {
+            $link = $this->entity;
+            if (!empty($link)) {
+                $weight = $link->getScoreWeight();
+
+                switch ($type) {
+                    case 'best':
+                        $bestResult = $link->getBestScore();
+                        $result = [$bestResult, $weight];
+
+                        return $result;
+                        break;
+                    case 'average':
+                        $count = count($this->getStudentList());
+                        if (empty($count)) {
+                            $result = [0, $weight];
+
+                            return $result;
+                        }
+                        $sumResult = array_sum($link->getUserScoreList());
+                        $result = [$sumResult / $count, $weight];
+
+                        return $result;
+                        break;
+                    case 'ranking':
+                        return '';
+                        /*
+                        $newList = [];
+                        $ranking = AbstractLink::getCurrentUserRanking($stud_id, $link->getUserScoreList());
+                        return $ranking;*/
+                        break;
+                    default:
+                        if (!empty($stud_id)) {
+                            $scoreList = $link->getUserScoreList();
+                            $result = [0, $weight];
+                            if (isset($scoreList[$stud_id])) {
+                                $result = [$scoreList[$stud_id], $weight];
+                            }
+
+                            return $result;
+                        } else {
+                            $studentCount = count($this->getStudentList());
+                            $sumResult = array_sum($link->getUserScoreList());
+                            $result = [$sumResult, $studentCount];
+                        }
+
+                        return $result;
+                        break;
+                }
+            }
+        }
+
         $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
         $tblHp = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
         $tblDoc = Database::get_course_table(TABLE_DOCUMENT);
@@ -185,6 +239,7 @@ class ExerciseLink extends AbstractLink
         $sessionId = $this->get_session_id();
         $courseId = $this->getCourseId();
         $exerciseData = $this->get_exercise_data();
+
         $exerciseId = isset($exerciseData['id']) ? $exerciseData['id'] : 0;
         $stud_id = (int) $stud_id;
 
@@ -493,6 +548,20 @@ class ExerciseLink extends AbstractLink
         $this->hp = $hp;
     }
 
+    public function getBestScore()
+    {
+        return $this->getStats('best');
+    }
+
+    public function getStats($type)
+    {
+        switch ($type) {
+            case 'best':
+
+                break;
+        }
+    }
+
     /**
      * Lazy load function to get the database table of the exercise.
      */
@@ -546,7 +615,7 @@ class ExerciseLink extends AbstractLink
                 if (!empty($rows)) {
                     $this->exercise_data = Database::fetch_array($result);
                 } else {
-                    // Try wit iid
+                    // Try wit id
                     $sql = 'SELECT * FROM '.$table.'
                             WHERE
                                 c_id = '.$this->course_id.' AND

+ 2 - 2
main/gradebook/lib/be/linkfactory.class.php

@@ -74,11 +74,11 @@ class LinkFactory
     /**
      * Static method to create specific link objects.
      *
-     * @param $type link type
+     * @param int $type link type
      */
     public static function create($type)
     {
-        $type = intval($type);
+        $type = (int) $type;
         switch ($type) {
             case LINK_EXERCISE:
                 return new ExerciseLink();

+ 4 - 1
main/gradebook/lib/be/result.class.php

@@ -179,7 +179,6 @@ class Result
                 $sql .= ' WHERE';
             }
             $sql .= ' gr.evaluation_id = '.intval($evaluation_id);
-            $paramcount++;
         }
         $sql .= ' ORDER BY u.lastname, u.firstname';
 
@@ -272,6 +271,8 @@ class Result
         $sql .= ' WHERE id = '.$this->id;
         // no need to update creation date
         Database::query($sql);
+
+        Evaluation::generateStats($this->get_evaluation_id());
     }
 
     /**
@@ -288,6 +289,8 @@ class Result
             $sql = "DELETE FROM $table WHERE result_id = ".$this->id;
             Database::query($sql);
         }
+
+        Evaluation::generateStats($this->get_evaluation_id());
     }
 
     /**

+ 10 - 7
main/gradebook/lib/fe/dataform.class.php

@@ -107,13 +107,16 @@ class DataForm extends FormValidator
         $this->addElement('hidden', 'formSent');
         $this->addElement('header', get_lang('ImportFileLocation'));
         $this->addElement('file', 'import_file', get_lang('Location'));
-        $allowed_file_types = [
-            'xml',
-            'csv',
-        ];
-        //$this->addRule('file', get_lang('InvalidExtension') . ' (' . implode(',', $allowed_file_types) . ')', 'filetype', $allowed_file_types);
-        $this->addElement('radio', 'file_type', get_lang('FileType'), 'CSV (<a href="docs/example_csv.html" target="_blank">'.get_lang('ExampleCSVFile').'</a>)', 'csv');
-        $this->addElement('radio', 'file_type', null, 'XML (<a href="docs/example_xml.html" target="_blank">'.get_lang('ExampleXMLFile').'</a>)', 'xml');
+        $this->addElement(
+            'radio',
+            'file_type',
+            get_lang('FileType'),
+            'CSV (<a href="docs/example_csv.html" target="_blank" download>'
+                .get_lang('ExampleCSVFile')
+                .'</a>)',
+            'csv'
+        );
+        //$this->addElement('radio', 'file_type', null, 'XML (<a href="docs/example_xml.html" target="_blank" download>'.get_lang('ExampleXMLFile').'</a>)', 'xml');
         $this->addElement('checkbox', 'overwrite', null, get_lang('OverwriteScores'));
         $this->addElement('checkbox', 'ignoreerrors', null, get_lang('IgnoreErrors'));
         $this->addButtonImport(get_lang('Ok'));

+ 4 - 9
main/gradebook/lib/fe/displaygradebook.php

@@ -23,7 +23,8 @@ class DisplayGradebook
             $header = '<div class="actions">';
             if ($page != 'statistics') {
                 $header .= '<a href="'.Category::getUrl().'selectcat='.$selectcat.'">'.
-                    Display::return_icon(('back.png'), get_lang('FolderView'), '', ICON_SIZE_MEDIUM).'</a>';
+                    Display::return_icon('back.png', get_lang('FolderView'), '', ICON_SIZE_MEDIUM)
+                    .'</a>';
                 if (($evalobj->get_course_code() != null) && !$evalobj->has_results()) {
                     $header .= '<a href="gradebook_add_result.php?'.api_get_cidreq().'&selectcat='.$selectcat.'&selecteval='.$evalobj->get_id().'">
     				'.Display::return_icon('evaluation_rate.png', get_lang('AddResult'), '', ICON_SIZE_MEDIUM).'</a>';
@@ -45,22 +46,15 @@ class DisplayGradebook
                             Display::return_icon('delete.png', get_lang('DeleteResult'), '', ICON_SIZE_MEDIUM).'</a>';
                     }
                 }
-
                 $header .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&print=&selecteval='.$evalobj->get_id().'" target="_blank">'.
                     Display::return_icon('printer.png', get_lang('Print'), '', ICON_SIZE_MEDIUM).'</a>';
             } else {
                 $header .= '<a href="gradebook_view_result.php?'.api_get_cidreq().'&selecteval='.Security::remove_XSS($_GET['selecteval']).'"> '.
-                    Display::return_icon(('back.png'), get_lang('FolderView'), '', ICON_SIZE_MEDIUM).'</a>';
+                    Display::return_icon('back.png', get_lang('FolderView'), '', ICON_SIZE_MEDIUM).'</a>';
             }
             $header .= '</div>';
         }
 
-        if ($evalobj->is_visible() == '1') {
-            $visible = get_lang('Yes');
-        } else {
-            $visible = get_lang('No');
-        }
-
         $scoredisplay = ScoreDisplay::instance();
         $student_score = '';
         $average = '';
@@ -87,6 +81,7 @@ class DisplayGradebook
                 }
             }
         }
+
         $description = '';
         if (!$evalobj->get_description() == '') {
             $description = get_lang('Description').' :<b> '.$evalobj->get_description().'</b><br>';

+ 254 - 154
main/gradebook/lib/fe/gradebooktable.class.php

@@ -1,6 +1,7 @@
 <?php
 /* For licensing terms, see license.txt */
 
+use ChamiloSession as Session;
 use CpChart\Cache as pCache;
 use CpChart\Data as pData;
 use CpChart\Image as pImage;
@@ -63,6 +64,7 @@ class GradebookTable extends SortableTable
         $this->teacherView = is_null($showTeacherView) ? api_is_allowed_to_edit(null, true) : $showTeacherView;
         $this->userId = is_null($userId) ? api_get_user_id() : $userId;
         $this->exportToPdf = $exportToPdf;
+        $this->studentList = $studentList;
 
         parent::__construct(
             'gradebooklist',
@@ -79,6 +81,8 @@ class GradebookTable extends SortableTable
         $this->cats = $cats;
         $this->loadStats = $loadStats;
         $this->datagen = new GradebookDataGenerator($cats, $evals, $links);
+        $this->datagen->preLoadDataKey = $this->getPreloadDataKey();
+
         $this->datagen->hidePercentage = api_get_configuration_value('hide_gradebook_percentage_user_result');
 
         if (!empty($userId)) {
@@ -182,6 +186,105 @@ class GradebookTable extends SortableTable
         return $this->datagen->get_total_items_count();
     }
 
+    /**
+     * @return string
+     */
+    public function getPreloadDataKey()
+    {
+        return 'default_data_'.api_get_course_id().'_'.api_get_session_id();
+    }
+
+    public function preloadData()
+    {
+        $allitems = $this->datagen->items;
+        usort($allitems, ['GradebookDataGenerator', 'sort_by_name']);
+        //$visibleItems = $this->datagen->items;
+        $visibleItems = array_merge($this->datagen->items, $this->evals_links);
+
+        //Session::erase($this->getPreloadDataKey());
+        $defaultDataFromSession = Session::read($this->getPreloadDataKey());
+        if (empty($defaultDataFromSession)) {
+            $defaultData = [];
+            /** @var GradebookItem $item */
+            foreach ($visibleItems as $item) {
+                $item->setStudentList($this->studentList);
+                $itemType = get_class($item);
+                switch ($itemType) {
+                    case 'Evaluation':
+                        // Best
+                        $best = $this->datagen->buildBestResultColumn($item);
+                        $defaultData[$item->get_id()]['best'] = $best;
+                        // Average
+                        $average = $this->datagen->buildAverageResultColumn($item);
+                        $defaultData[$item->get_id()]['average'] = $average;
+                        break;
+                    case 'ExerciseLink':
+                        /** @var ExerciseLink $item */
+                        // Best
+                        $best = $this->datagen->buildBestResultColumn($item);
+                        $defaultData[$item->get_id()]['best'] = $best;
+                        // Average
+                        $average = $this->datagen->buildAverageResultColumn($item);
+                        $defaultData[$item->get_id()]['average'] = $average;
+
+                        // Ranking
+                        /*if (!empty($this->studentList)) {
+                            $invalidateRanking = true;
+                            foreach ($this->studentList as $user) {
+                                $score = $this->datagen->build_result_column(
+                                    $user['user_id'],
+                                    $item,
+                                    false,
+                                    true
+                                );
+                                if (!empty($score['score'])) {
+                                    $invalidateRanking = false;
+                                }
+                                $rankingStudentList[$user['user_id']] = $score['score'][0];
+                                $defaultData[$item->get_id()]['ranking'] = $rankingStudentList;
+                                $defaultData[$item->get_id()]['ranking_invalidate'] = $invalidateRanking;
+                            }
+                        }*/
+                        break;
+                    default:
+                        // Best
+                        $best = $this->datagen->buildBestResultColumn($item);
+                        $defaultData[$item->get_id()]['best'] = $best;
+
+                        // Average
+                        $average = $this->datagen->buildAverageResultColumn($item);
+                        $defaultData[$item->get_id()]['average'] = $average;
+
+                        // Ranking
+                        if (!empty($this->studentList)) {
+                            $invalidateRanking = true;
+                            foreach ($this->studentList as $user) {
+                                $score = $this->datagen->build_result_column(
+                                    $user['user_id'],
+                                    $item,
+                                    false,
+                                    true
+                                );
+                                if (!empty($score['score'])) {
+                                    $invalidateRanking = false;
+                                }
+                                $rankingStudentList[$user['user_id']] = $score['score'][0];
+                                $defaultData[$item->get_id()]['ranking'] = $rankingStudentList;
+                                $defaultData[$item->get_id()]['ranking_invalidate'] = $invalidateRanking;
+                            }
+                            //exit;
+                        }
+                        break;
+                }
+            }
+            Session::write($this->getPreloadDataKey(), $defaultData);
+        } else {
+            $defaultData = $defaultDataFromSession;
+        }
+
+        return $defaultData;
+    }
+
     /**
      * Function used by SortableTable to generate the data to display.
      *
@@ -292,16 +395,20 @@ class GradebookTable extends SortableTable
         $userExerciseScoreInCategory = api_get_configuration_value(
             'gradebook_use_exercise_score_settings_in_categories'
         );
+
+        $course_code = api_get_course_id();
+        $session_id = api_get_session_id();
+        $defaultData = Session::read($this->getPreloadDataKey());
+
         // Categories.
         if (!empty($data_array)) {
             foreach ($data_array as $data) {
                 // list of items inside the gradebook (exercises, lps, forums, etc)
                 $row = [];
-
                 /** @var AbstractLink $item */
                 $item = $mainCategory = $data[0];
 
-                //if the item is invisible, wrap it in a span with class invisible
+                // If the item is invisible, wrap it in a span with class invisible
                 $invisibility_span_open = $isAllowedToEdit && $item->is_visible() == '0' ? '<span class="text-muted">' : '';
                 $invisibility_span_close = $isAllowedToEdit && $item->is_visible() == '0' ? '</span>' : '';
 
@@ -316,7 +423,7 @@ class GradebookTable extends SortableTable
                 $row[] = $this->build_type_column($item);
 
                 // Name.
-                if (get_class($item) == 'Category') {
+                if (get_class($item) === 'Category') {
                     $row[] = $invisibility_span_open.'<strong>'.$item->get_name().'</strong>'.$invisibility_span_close;
                     $main_categories[$item->get_id()]['name'] = $item->get_name();
                 } else {
@@ -383,94 +490,86 @@ class GradebookTable extends SortableTable
                     }
 
                     // Students get the results and certificates columns
-                    if (1) {
-                        $value_data = isset($data[4]) ? $data[4] : null;
-                        $best = isset($data['best']) ? $data['best'] : null;
-                        $average = isset($data['average']) ? $data['average'] : null;
-                        $ranking = isset($data['ranking']) ? $data['ranking'] : null;
 
-                        $totalResult = [
-                            $data['result_score'][0],
-                            $data['result_score'][1],
-                        ];
-
-                        $totalUserResult[0] += $totalResult[0] / ($totalResult[1] ?: 1) * $data[3];
-                        $totalUserResult[1] += $data[3];
-
-                        $totalBest = [
-                            $scoredisplay->format_score($totalBest[0] + $data['best_score'][0]),
-                            $scoredisplay->format_score($totalBest[1] + $data['best_score'][1]),
-                        ];
-
-                        $totalAverage = [
-                            $data['average_score'][0],
-                            $data['average_score'][1],
-                        ];
-
-                        // Student result
-                        if (empty($model)) {
-                            $row[] = $value_data;
-                        } else {
-                            $row[] = ExerciseLib::show_score(
-                                $data['result_score'][0],
-                                $data['result_score'][1]
-                            );
-                        }
+                    $value_data = isset($data[4]) ? $data[4] : null;
+                    $best = isset($data['best']) ? $data['best'] : null;
+                    $average = isset($data['average']) ? $data['average'] : null;
+                    $ranking = isset($data['ranking']) ? $data['ranking'] : null;
 
-                        $mode = SCORE_AVERAGE;
-                        if ($userExerciseScoreInCategory) {
-                            $mode = SCORE_SIMPLE;
+                    $totalResult = [
+                        $data['result_score'][0],
+                        $data['result_score'][1],
+                    ];
 
-                            $result = ExerciseLib::convertScoreToPlatformSetting($totalAverage[0], $totalAverage[1]);
-                            $totalAverage[0] = $result['score'];
-                            $totalAverage[1] = $result['weight'];
+                    $totalUserResult[0] += $totalResult[0] / ($totalResult[1] ?: 1) * $data[3];
+                    $totalUserResult[1] += $data[3];
 
-                            $result = ExerciseLib::convertScoreToPlatformSetting($totalResult[0], $totalResult[1]);
-                            $totalResult[0] = $result['score'];
-                            $totalResult[1] = $result['weight'];
+                    $totalBest = [
+                        $scoredisplay->format_score($totalBest[0] + $data['best_score'][0]),
+                        $scoredisplay->format_score($totalBest[1] + $data['best_score'][1]),
+                    ];
+
+                    $totalAverage = [
+                        $data['average_score'][0],
+                        $data['average_score'][1],
+                    ];
 
-                            $result = ExerciseLib::convertScoreToPlatformSetting(
-                                $data['result_score'][0],
-                                $data['result_score'][1]
+                    // Student result
+                    if (empty($model)) {
+                        $row[] = $value_data;
+                    } else {
+                        $row[] = ExerciseLib::show_score(
+                            $data['result_score'][0],
+                            $data['result_score'][1]
                         );
-                            $data['my_result_no_float'][0] = $result['score'];
-                        }
+                    }
 
-                        $totalResultAverageValue = strip_tags($scoredisplay->display_score($totalResult, $mode));
-                        $totalAverageValue = strip_tags($scoredisplay->display_score($totalAverage, $mode));
+                    $mode = SCORE_AVERAGE;
+                    if ($userExerciseScoreInCategory) {
+                        $mode = SCORE_SIMPLE;
 
-                        $this->dataForGraph['my_result'][] = floatval($totalResultAverageValue);
-                        $this->dataForGraph['average'][] = floatval($totalAverageValue);
-                        $this->dataForGraph['my_result_no_float'][] = $data['result_score'][0];
+                        $result = ExerciseLib::convertScoreToPlatformSetting($totalAverage[0], $totalAverage[1]);
+                        $totalAverage[0] = $result['score'];
+                        $totalAverage[1] = $result['weight'];
 
-                        if (empty($model)) {
-                            // Ranking
-                            if (in_array(1, $this->loadStats)) {
-                                $row[] = $ranking;
-                            }
+                        $result = ExerciseLib::convertScoreToPlatformSetting($totalResult[0], $totalResult[1]);
+                        $totalResult[0] = $result['score'];
+                        $totalResult[1] = $result['weight'];
 
-                            // Best
-                            if (in_array(2, $this->loadStats)) {
-                                $row[] = $best;
-                            }
+                        $result = ExerciseLib::convertScoreToPlatformSetting(
+                            $data['result_score'][0],
+                            $data['result_score'][1]
+                        );
+                        $data['my_result_no_float'][0] = $result['score'];
+                    }
 
-                            // Average
-                            if (in_array(3, $this->loadStats)) {
-                                $row[] = $average;
-                            }
+                    $totalResultAverageValue = strip_tags($scoredisplay->display_score($totalResult, $mode));
+                    $totalAverageValue = strip_tags($scoredisplay->display_score($totalAverage, $mode));
+
+                    $this->dataForGraph['my_result'][] = floatval($totalResultAverageValue);
+                    $this->dataForGraph['average'][] = floatval($totalAverageValue);
+                    $this->dataForGraph['my_result_no_float'][] = $data['result_score'][0];
+
+                    if (empty($model)) {
+                        // Ranking
+                        if (in_array(1, $this->loadStats)) {
+                            $row[] = $ranking;
                         }
 
-                        if (get_class($item) == 'Category') {
-                            if ($this->exportToPdf == false) {
-                                $row[] = $this->build_edit_column($item);
-                            }
+                        // Best
+                        if (in_array(2, $this->loadStats)) {
+                            $row[] = $best;
                         }
-                    } else {
-                        $row[] = $scoreToDisplay;
-                        if (!empty($this->cats)) {
-                            if ($this->exportToPdf == false) {
-                                $row[] = $this->build_edit_column($item);
-                            }
+
+                        // Average
+                        if (in_array(3, $this->loadStats)) {
+                            $row[] = $average;
+                        }
+                    }
+
+                    if (get_class($item) === 'Category') {
+                        if ($this->exportToPdf == false) {
+                            $row[] = $this->build_edit_column($item);
                         }
                     }
                 }
@@ -479,10 +578,9 @@ class GradebookTable extends SortableTable
                 $sortable_data[] = $row;
 
                 // Loading children
-                if (get_class($item) == 'Category') {
-                    $course_code = api_get_course_id();
-                    $session_id = api_get_session_id();
+                if (get_class($item) === 'Category') {
                     $parent_id = $item->get_id();
+
                     $cats = Category::load(
                         $parent_id,
                         null,
@@ -500,6 +598,7 @@ class GradebookTable extends SortableTable
                         $alllink = $subCategory->get_links($this->userId);
 
                         $sub_cat_info = new GradebookDataGenerator($allcat, $alleval, $alllink);
+                        $sub_cat_info->preLoadDataKey = $this->getPreloadDataKey();
                         $sub_cat_info->userId = $user_id;
                         $data_array2 = $sub_cat_info->get_data(
                             $sorting,
@@ -534,7 +633,7 @@ class GradebookTable extends SortableTable
                             $row[] = $this->build_type_column($item, ['style' => 'padding-left:5px']);
 
                             // Name.
-                            $row[] = $invisibility_span_open."&nbsp;&nbsp;&nbsp; ".
+                            $row[] = $invisibility_span_open.'&nbsp;&nbsp;&nbsp; '.
                                 $this->build_name_link($item, $type).$invisibility_span_close;
 
                             // Description.
@@ -652,7 +751,7 @@ class GradebookTable extends SortableTable
             foreach ($main_cat as $myCat) {
                 $myParentId = $myCat->get_parent_id();
                 if ($myParentId == 0) {
-                    $main_weight = intval($myCat->get_weight());
+                    $main_weight = (int) $myCat->get_weight();
                 }
             }
         }
@@ -682,31 +781,27 @@ class GradebookTable extends SortableTable
         } else {
             // Total for student.
             if (count($main_cat) > 1) {
-                $weights = [];
-                foreach ($main_categories as $cat) {
-                    $weights[] = $cat['weight'];
-                }
-                $main_weight = intval($main_cat[0]->get_weight());
+                $main_weight = (int) $main_cat[0]->get_weight();
                 $global = null;
                 $average = null;
                 $myTotal = 0;
-
                 foreach ($this->dataForGraph['my_result_no_float'] as $result) {
-                    $myTotal += $scoredisplay->format_score($result);
+                    $myTotal += $result;
                 }
 
                 $totalResult[0] = $myTotal;
                 // Overwrite main weight
                 $totalResult[1] = $main_weight;
+
                 $totalResult = $scoredisplay->display_score(
                     $totalResult,
                     SCORE_DIV
                 );
 
                 $row = [
-                        null,
-                        '<strong>'.get_lang('Total').'</strong>',
-                    ];
+                    null,
+                    '<strong>'.get_lang('Total').'</strong>',
+                ];
 
                 if (!$this->exportToPdf) {
                     $row[] = null;
@@ -714,34 +809,43 @@ class GradebookTable extends SortableTable
 
                 $row[] = $main_weight;
                 $row[] = $totalResult;
+                $categoryId = $main_cat[0]->get_id();
 
                 if (in_array(1, $this->loadStats)) {
-                    $totalRanking = [];
-                    $invalidateRanking = true;
-                    $average = 0;
-                    $main_cat[0]->setStudentList($this->studentList);
-                    foreach ($this->studentList as $student) {
-                        $score = $main_cat[0]->calc_score(
-                            $student['user_id'],
-                            null,
-                            api_get_course_id(),
-                            api_get_session_id()
-                        );
-                        if (!empty($score[0])) {
-                            $invalidateRanking = false;
+                    if (isset($defaultData[$categoryId]) && isset($defaultData[$categoryId]['ranking'])) {
+                        $totalRanking = $defaultData[$categoryId]['ranking'];
+                        $invalidateRanking = $defaultData[$categoryId]['ranking_invalidate'];
+                    } else {
+                        $totalRanking = [];
+                        $invalidateRanking = true;
+                        $average = 0;
+                        $main_cat[0]->setStudentList($this->studentList);
+                        foreach ($this->studentList as $student) {
+                            $score = $main_cat[0]->calc_score(
+                                $student['user_id'],
+                                null,
+                                api_get_course_id(),
+                                api_get_session_id()
+                            );
+                            if (!empty($score[0])) {
+                                $invalidateRanking = false;
+                            }
+                            $totalRanking[$student['user_id']] = $score[0];
+                            $average += $score[0];
                         }
-                        $totalRanking[$student['user_id']] = $score[0];
-                        $average += $score[0];
+                        $defaultData[$categoryId]['ranking'] = $totalRanking;
+                        $defaultData[$categoryId]['ranking_invalidate'] = $invalidateRanking;
+                        Session::write($this->getPreloadDataKey(), $defaultData);
                     }
 
                     $totalRanking = AbstractLink::getCurrentUserRanking($user_id, $totalRanking);
                     $totalRanking = $scoredisplay->display_score(
-                            $totalRanking,
-                            SCORE_DIV,
-                            SCORE_BOTH,
-                            true,
-                            true
-                        );
+                        $totalRanking,
+                        SCORE_DIV,
+                        SCORE_BOTH,
+                        true,
+                        true
+                    );
 
                     if ($invalidateRanking) {
                         $totalRanking = null;
@@ -750,46 +854,60 @@ class GradebookTable extends SortableTable
                 }
 
                 if (in_array(2, $this->loadStats)) {
-                    // Overwrite main weight
-                    $totalBest[1] = $main_weight;
+                    if (isset($defaultData[$categoryId]) && isset($defaultData[$categoryId]['best'])) {
+                        $totalBest = $defaultData[$categoryId]['best'];
+                    } else {
+                        // Overwrite main weight
+                        $totalBest[1] = $main_weight;
+                        $defaultData[$categoryId]['best'] = $totalBest;
+                    }
                     $totalBest = $scoredisplay->display_score(
-                            $totalBest,
-                            SCORE_DIV,
-                            SCORE_BOTH,
-                            true
-                        );
+                        $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;
+                    if (isset($defaultData[$categoryId]) && isset($defaultData[$categoryId]['average'])) {
+                        $totalAverage = $defaultData[$categoryId]['average'];
+                    } else {
+                        // Overwrite main weight
+                        $totalAverage[0] = $average / count($this->studentList);
+                        $totalAverage[1] = $main_weight;
+                        $defaultData[$categoryId]['average'] = $totalBest;
+                    }
+
                     $totalAverage = $scoredisplay->display_score(
-                            $totalAverage,
-                            SCORE_DIV,
-                            SCORE_BOTH,
-                            true
-                        );
+                        $totalAverage,
+                        SCORE_DIV,
+                        SCORE_BOTH,
+                        true
+                    );
 
                     $row[] = $totalAverage;
                 }
+
                 if (!empty($row)) {
                     $sortable_data[] = $row;
                 }
             }
         }
 
+        Session::write('default_data', $defaultData);
+
         // Warning messages
         $view = isset($_GET['view']) ? $_GET['view'] : null;
-
         if ($this->teacherView) {
             if (isset($_GET['selectcat']) &&
                 $_GET['selectcat'] > 0 &&
                 $view != 'presence'
             ) {
-                $id_cat = intval($_GET['selectcat']);
+                $id_cat = (int) $_GET['selectcat'];
                 $category = Category::load($id_cat);
-                $weight_category = intval($this->build_weight($category[0]));
+                $weight_category = (int) $this->build_weight($category[0]);
                 $course_code = $this->build_course_code($category[0]);
                 $weight_total_links = round($weight_total_links);
 
@@ -798,7 +916,8 @@ class GradebookTable extends SortableTable
                     $weight_total_links > $weight_category
                 ) {
                     $warning_message = sprintf(get_lang('TotalWeightMustBeX'), $weight_category);
-                    $modify_icons = '<a href="gradebook_edit_cat.php?editcat='.$id_cat.'&cidReq='.$course_code.'&id_session='.api_get_session_id().'">'.
+                    $modify_icons =
+                        '<a href="gradebook_edit_cat.php?editcat='.$id_cat.'&cidReq='.$course_code.'&id_session='.api_get_session_id().'">'.
                         Display::return_icon('edit.png', $warning_message, [], ICON_SIZE_SMALL).'</a>';
                     $warning_message .= $modify_icons;
                     echo Display::return_message($warning_message, 'warning', false);
@@ -853,7 +972,9 @@ class GradebookTable extends SortableTable
                         if (empty($certificate_min_score) ||
                             ($certificate_min_score > $weight_category)
                         ) {
-                            $warning_message .= $course_code.'&nbsp;-&nbsp;'.get_lang('CertificateMinimunScoreIsRequiredAndMustNotBeMoreThan').'&nbsp;'.$weight_category.'<br />';
+                            $warning_message .= $course_code.
+                                '&nbsp;-&nbsp;'.get_lang('CertificateMinimunScoreIsRequiredAndMustNotBeMoreThan').
+                                '&nbsp;'.$weight_category.'<br />';
                         }
                     }
 
@@ -864,27 +985,6 @@ class GradebookTable extends SortableTable
             }
         }
 
-        if (!$this->teacherView) {
-            /*$rowTotal = [];
-            $rowTotal[] = ' ';
-            $rowTotal[] = '<strong>'.get_lang('FinalScore').'</strong>';
-
-            if (!$this->exportToPdf) {
-                $rowTotal[] = ' ';
-            }
-            $rowTotal[] = ' ';
-            $rowTotal[] = $scoredisplay->display_score(
-                $totalUserResult,
-                SCORE_DIV_PERCENT_WITH_CUSTOM
-            );
-
-            foreach ($this->loadStats as $col) {
-                $rowTotal[] = ' ';
-            }
-
-            $sortable_data[] = $rowTotal;*/
-        }
-
         return $sortable_data;
     }
 

+ 214 - 87
main/gradebook/lib/gradebook_data_generator.class.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use ChamiloSession as Session;
+
 /**
  * Class GradebookDataGenerator
  * Class to select, sort and transform object data into array data,
@@ -24,7 +26,8 @@ class GradebookDataGenerator
     public $userId;
     public $hidePercentage = false;
 
-    private $items;
+    public $items;
+    public $preLoadDataKey;
     private $evals_links;
 
     /**
@@ -113,7 +116,9 @@ class GradebookDataGenerator
 
         // Generate the data to display
         $data = [];
-        $totalWeight = 0;
+        $allowStats = api_get_configuration_value('allow_gradebook_stats');
+        $scoreDisplay = ScoreDisplay::instance();
+        $defaultData = Session::read($this->preLoadDataKey);
 
         /** @var GradebookItem $item */
         foreach ($visibleItems as $item) {
@@ -124,91 +129,214 @@ class GradebookDataGenerator
             // on mouseover (https://support.chamilo.org/issues/6588)
             $row[] = '<span title="'.api_remove_tags_with_space($item->get_description()).'">'.
                 api_get_short_text_from_html($item->get_description(), 160).'</span>';
-            $totalWeight += $item->get_weight();
             $row[] = $item->get_weight();
             $item->setStudentList($studentList);
+            $itemType = get_class($item);
+
+            switch ($itemType) {
+                case 'Evaluation':
+                    // Items inside a category.
+                    $resultColumn = $this->build_result_column(
+                        $userId,
+                        $item,
+                        $ignore_score_color
+                    );
 
-            if (get_class($item) == 'Evaluation') {
-                // Items inside a category.
-                $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'];
-
-                // Best
-                $best = $this->buildBestResultColumn($item);
-
-                $row['best'] = $best['display'];
-                $row['best_score'] = $best['score'];
-
-                // Average
-                $average = $this->buildAverageResultColumn($item);
-                $row['average'] = $average['display'];
-                $row['average_score'] = $average['score'];
-
-                // 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(
-                    $userId,
-                    $item,
-                    $ignore_score_color,
-                    true
-                );
-                $row[] = $result['display'];
-                $row['result_score'] = $result['score'];
-                $row['result_score_weight'] = $result['score'];
-
-                // Best
-                $best = $this->buildBestResultColumn($item);
-                $row['best'] = $best['display'];
-                $row['best_score'] = $best['score'];
-
-                $rankingStudentList = [];
-                $invalidateResults = true;
-
-                // Average
-                $average = $this->buildAverageResultColumn($item);
-                $row['average'] = $average['display'];
-                $row['average_score'] = $average['score'];
-
-                // Ranking
-                if (!empty($studentList)) {
-                    foreach ($studentList as $user) {
-                        $score = $this->build_result_column(
-                            $user['user_id'],
-                            $item,
-                            $ignore_score_color,
-                            true
-                        );
-                        if (!empty($score['score'][0])) {
-                            $invalidateResults = false;
+                    $row[] = $resultColumn['display'];
+                    $row['result_score'] = $resultColumn['score'];
+                    $row['result_score_weight'] = $resultColumn['score_weight'];
+
+                    // Best
+                    if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['best'])) {
+                        $best = $defaultData[$item->get_id()]['best'];
+                    } else {
+                        $best = $this->buildBestResultColumn($item);
+                    }
+
+                    $row['best'] = $best['display'];
+                    $row['best_score'] = $best['score'];
+
+                    // Average
+                    if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['average'])) {
+                        $average = $defaultData[$item->get_id()]['average'];
+                    } else {
+                        $average = $this->buildBestResultColumn($item);
+                    }
+
+                    $row['average'] = $average['display'];
+                    $row['average_score'] = $average['score'];
+
+                    // Ranking
+                    $ranking = $this->buildRankingColumn($item, $userId, $userCount);
+                    $row['ranking'] = $ranking['display'];
+                    $row['ranking_score'] = $ranking['score'];
+                    $row[] = $item;
+                    break;
+                case 'ExerciseLink':
+                    /** @var ExerciseLink $item */
+                    // Category.
+                    $result = $this->build_result_column(
+                        $userId,
+                        $item,
+                        $ignore_score_color,
+                        true
+                    );
+                    $row[] = $result['display'];
+                    $row['result_score'] = $result['score'];
+                    $row['result_score_weight'] = $result['score'];
+
+                    // Best
+                    if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['best'])) {
+                        $best = $defaultData[$item->get_id()]['best'];
+                    } else {
+                        $best = $this->buildBestResultColumn($item);
+                    }
+
+                    $row['best'] = $best['display'];
+                    $row['best_score'] = $best['score'];
+
+                    $rankingStudentList = [];
+                    $invalidateResults = false;
+
+                    $debug = $item->get_id() == 1177;
+
+                    // Average
+                    if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['average'])) {
+                        $average = $defaultData[$item->get_id()]['average'];
+                    } else {
+                        $average = $this->buildAverageResultColumn($item);
+                    }
+
+                    $row['average'] = $average['display'];
+                    $row['average_score'] = $average['score'];
+
+                    // Ranking
+                    if ($allowStats) {
+                        // Ranking
+                        if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['ranking'])) {
+                            $rankingStudentList = $defaultData[$item->get_id()]['ranking'];
+                            $invalidateResults = $defaultData[$item->get_id()]['ranking_invalidate'];
+                            $score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
+                        } else {
+                            if (!empty($studentList)) {
+                                foreach ($studentList as $user) {
+                                    $score = $this->build_result_column(
+                                        $user['user_id'],
+                                        $item,
+                                        $ignore_score_color,
+                                        true
+                                    );
+                                    if (!empty($score['score'][0])) {
+                                        $invalidateResults = false;
+                                    }
+                                    $rankingStudentList[$user['user_id']] = $score['score'][0];
+                                }
+                                $defaultData[$item->get_id()]['ranking'] = $rankingStudentList;
+                                $defaultData[$item->get_id()]['ranking_invalidate'] = $invalidateResults;
+                                Session::write($this->preLoadDataKey, $defaultData);
+                            }
+                            $score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
                         }
-                        $rankingStudentList[$user['user_id']] = $score['score'][0];
+                    } else {
+                        if (!empty($studentList)) {
+                            foreach ($studentList as $user) {
+                                $score = $this->build_result_column(
+                                    $user['user_id'],
+                                    $item,
+                                    $ignore_score_color,
+                                    true
+                                );
+                                if (!empty($score['score'][0])) {
+                                    $invalidateResults = false;
+                                }
+                                $rankingStudentList[$user['user_id']] = $score['score'][0];
+                            }
+                        }
+                        $score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
                     }
-                }
 
-                $scoreDisplay = ScoreDisplay::instance();
-                $score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
-                $row['ranking'] = $scoreDisplay->display_score(
-                    $score,
-                    SCORE_DIV,
-                    SCORE_BOTH,
-                    true,
-                    true
-                );
-                if ($invalidateResults) {
-                    $row['ranking'] = null;
-                }
+                    $row['ranking'] = $scoreDisplay->display_score(
+                        $score,
+                        SCORE_DIV,
+                        SCORE_BOTH,
+                        true,
+                        true
+                    );
+                    if ($invalidateResults) {
+                        $row['ranking'] = null;
+                    }
+                    break;
+                default:
+                    // Category.
+                    $result = $this->build_result_column(
+                        $userId,
+                        $item,
+                        $ignore_score_color,
+                        true
+                    );
+                    $row[] = $result['display'];
+                    $row['result_score'] = $result['score'];
+                    $row['result_score_weight'] = $result['score'];
+
+                    // Best
+                    if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['best'])) {
+                        $best = $defaultData[$item->get_id()]['best'];
+                    } else {
+                        $best = $this->buildBestResultColumn($item);
+                    }
+
+                    $row['best'] = $best['display'];
+                    $row['best_score'] = $best['score'];
+
+                    $rankingStudentList = [];
+                    $invalidateResults = true;
+
+                    // Average
+                    if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['average'])) {
+                        $average = $defaultData[$item->get_id()]['average'];
+                    } else {
+                        $average = $this->buildAverageResultColumn($item);
+                    }
+                    $row['average'] = $average['display'];
+                    $row['average_score'] = $average['score'];
+
+                    // Ranking
+                    if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['ranking'])) {
+                        $rankingStudentList = $defaultData[$item->get_id()]['ranking'];
+                        $invalidateResults = $defaultData[$item->get_id()]['ranking_invalidate'];
+                        $invalidateResults = false;
+                        $score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
+                    } else {
+                        if (!empty($studentList)) {
+                            foreach ($studentList as $user) {
+                                $score = $this->build_result_column(
+                                    $user['user_id'],
+                                    $item,
+                                    $ignore_score_color,
+                                    true
+                                );
+                                if (!empty($score['score'][0])) {
+                                    $invalidateResults = false;
+                                }
+                                $rankingStudentList[$user['user_id']] = $score['score'][0];
+                            }
+                        }
+                        error_log('loading not cACHE');
+                        $score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
+                    }
+
+                    $row['ranking'] = $scoreDisplay->display_score(
+                        $score,
+                        SCORE_DIV,
+                        SCORE_BOTH,
+                        true,
+                        true
+                    );
+
+                    if ($invalidateResults) {
+                        $row['ranking'] = null;
+                    }
+                    break;
             }
             $data[] = $row;
         }
@@ -244,7 +372,7 @@ class GradebookDataGenerator
      *
      * @return int
      */
-    public function sort_by_name($item1, $item2)
+    public static function sort_by_name($item1, $item2)
     {
         return api_strnatcmp($item1->get_name(), $item2->get_name());
     }
@@ -345,7 +473,7 @@ class GradebookDataGenerator
      *
      * @return array
      */
-    private function buildBestResultColumn(GradebookItem $item)
+    public function buildBestResultColumn(GradebookItem $item)
     {
         $score = $item->calc_score(
             null,
@@ -382,7 +510,7 @@ class GradebookDataGenerator
      *
      * @return array
      */
-    private function buildAverageResultColumn(GradebookItem $item)
+    public function buildAverageResultColumn(GradebookItem $item)
     {
         $score = $item->calc_score(null, 'average');
         $scoreDisplay = ScoreDisplay::instance();
@@ -421,7 +549,7 @@ class GradebookDataGenerator
      *
      * @return array
      */
-    private function buildRankingColumn(GradebookItem $item, $userId = null, $userCount = 0)
+    public function buildRankingColumn(GradebookItem $item, $userId = null, $userCount = 0)
     {
         $score = $item->calc_score($userId, 'ranking');
         $score[1] = $userCount;
@@ -450,7 +578,7 @@ class GradebookDataGenerator
      *
      * @return string|null
      */
-    private function build_result_column(
+    public function build_result_column(
         $userId,
         $item,
         $ignore_score_color,
@@ -491,7 +619,6 @@ class GradebookDataGenerator
                         ];
                     }
                     break;
-                // evaluation and link
                 case 'E':
                 case 'L':
                     //if ($parentId == 0) {