Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Displays the lesson statistics.
  20   *
  21   * @package mod_lesson
  22   * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
  24   **/
  25  
  26  require_once('../../config.php');
  27  require_once($CFG->dirroot.'/mod/lesson/locallib.php');
  28  
  29  $id     = required_param('id', PARAM_INT);    // Course Module ID
  30  $pageid = optional_param('pageid', null, PARAM_INT);    // Lesson Page ID
  31  $action = optional_param('action', 'reportoverview', PARAM_ALPHA);  // action to take
  32  $nothingtodisplay = false;
  33  
  34  $cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);
  35  $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
  36  $lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST));
  37  
  38  require_login($course, false, $cm);
  39  
  40  $currentgroup = groups_get_activity_group($cm, true);
  41  
  42  $context = context_module::instance($cm->id);
  43  require_capability('mod/lesson:viewreports', $context);
  44  
  45  $url = new moodle_url('/mod/lesson/report.php', array('id'=>$id));
  46  $url->param('action', $action);
  47  if ($pageid !== null) {
  48      $url->param('pageid', $pageid);
  49  }
  50  $PAGE->set_url($url);
  51  if ($action == 'reportoverview') {
  52      $PAGE->navbar->add(get_string('reports', 'lesson'));
  53      $PAGE->navbar->add(get_string('overview', 'lesson'));
  54  }
  55  
  56  $lessonoutput = $PAGE->get_renderer('mod_lesson');
  57  
  58  if ($action === 'delete') {
  59      /// Process any form data before fetching attempts, grades and times
  60      if (has_capability('mod/lesson:edit', $context) and $form = data_submitted() and confirm_sesskey()) {
  61      /// Cycle through array of userids with nested arrays of tries
  62          if (!empty($form->attempts)) {
  63              foreach ($form->attempts as $userid => $tries) {
  64                  // Modifier IS VERY IMPORTANT!  What does it do?
  65                  //      Well, it is for when you delete multiple attempts for the same user.
  66                  //      If you delete try 1 and 3 for a user, then after deleting try 1, try 3 then
  67                  //      becomes try 2 (because try 1 is gone and all tries after try 1 get decremented).
  68                  //      So, the modifier makes sure that the submitted try refers to the current try in the
  69                  //      database - hope this all makes sense :)
  70                  $modifier = 0;
  71  
  72                  foreach ($tries as $try => $junk) {
  73                      $try -= $modifier;
  74  
  75                  /// Clean up the timer table by removing using the order - this is silly, it should be linked to specific attempt (skodak)
  76                      $timers = $lesson->get_user_timers($userid, 'starttime', 'id', $try, 1);
  77                      if ($timers) {
  78                          $timer = reset($timers);
  79                          $DB->delete_records('lesson_timer', array('id' => $timer->id));
  80                      }
  81  
  82                      $params = array ("userid" => $userid, "lessonid" => $lesson->id);
  83                      // Remove the grade from the grades tables - this is silly, it should be linked to specific attempt (skodak).
  84                      $grades = $DB->get_records_sql("SELECT id FROM {lesson_grades}
  85                                                       WHERE userid = :userid AND lessonid = :lessonid
  86                                                    ORDER BY completed", $params, $try, 1);
  87  
  88                      if ($grades) {
  89                          $grade = reset($grades);
  90                          $DB->delete_records('lesson_grades', array('id' => $grade->id));
  91                      }
  92  
  93                  /// Remove attempts and update the retry number
  94                      $DB->delete_records('lesson_attempts', array('userid' => $userid, 'lessonid' => $lesson->id, 'retry' => $try));
  95                      $DB->execute("UPDATE {lesson_attempts} SET retry = retry - 1 WHERE userid = ? AND lessonid = ? AND retry > ?", array($userid, $lesson->id, $try));
  96  
  97                  /// Remove seen branches and update the retry number
  98                      $DB->delete_records('lesson_branch', array('userid' => $userid, 'lessonid' => $lesson->id, 'retry' => $try));
  99                      $DB->execute("UPDATE {lesson_branch} SET retry = retry - 1 WHERE userid = ? AND lessonid = ? AND retry > ?", array($userid, $lesson->id, $try));
 100  
 101                  /// update central gradebook
 102                      lesson_update_grades($lesson, $userid);
 103  
 104                      $modifier++;
 105                  }
 106              }
 107          }
 108      }
 109      redirect(new moodle_url($PAGE->url, array('action'=>'reportoverview')));
 110  
 111  } else if ($action === 'reportoverview') {
 112      /**************************************************************************
 113      this action is for default view and overview view
 114      **************************************************************************/
 115  
 116      // Get the table and data for build statistics.
 117      list($table, $data) = lesson_get_overview_report_table_and_data($lesson, $currentgroup);
 118  
 119      if ($table === false) {
 120          echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('nolessonattempts', 'lesson'));
 121          if (!empty($currentgroup)) {
 122              $groupname = groups_get_group_name($currentgroup);
 123              echo $OUTPUT->notification(get_string('nolessonattemptsgroup', 'lesson', $groupname));
 124          } else {
 125              echo $OUTPUT->notification(get_string('nolessonattempts', 'lesson'));
 126          }
 127          groups_print_activity_menu($cm, $url);
 128          echo $OUTPUT->footer();
 129          exit();
 130      }
 131  
 132      echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('overview', 'lesson'));
 133      groups_print_activity_menu($cm, $url);
 134  
 135      $course_context = context_course::instance($course->id);
 136      if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
 137          $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
 138          $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
 139          echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
 140      }
 141  
 142      // The attempts table.
 143      $attemptstable = html_writer::div(html_writer::table($table), 'table-responsive');
 144  
 145      // The HTML that we will be displaying which includes the attempts table and bulk actions menu, if necessary.
 146      $attemptshtml = $attemptstable;
 147  
 148      // Show bulk actions when user has capability to edit the lesson.
 149      if (has_capability('mod/lesson:edit', $context)) {
 150          $reporturl = new moodle_url('/mod/lesson/report.php');
 151          $formid  = 'mod-lesson-report-form';
 152  
 153          // Sesskey hidden input.
 154          $formcontents = html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]);
 155  
 156          // CMID hidden input.
 157          $formcontents .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'id', 'value' => $cm->id]);
 158  
 159          // Attempts table.
 160          $formcontents .= $attemptstable;
 161  
 162          // Bulk actions menu.
 163          $attemptsactions = [
 164              'delete' => get_string('deleteselected')
 165          ];
 166          $bulkactions = new single_select($reporturl, 'action', $attemptsactions, '', ['' => 'choosedots'], $formid);
 167          $bulkactions->set_label(get_string('withselectedattempts', 'lesson'));
 168          $bulkactions->disabled = true;
 169          $bulkactions->attributes = [
 170              'data-action' => 'toggle',
 171              'data-togglegroup' => 'lesson-attempts',
 172              'data-toggle' => 'action',
 173          ];
 174          $bulkactionshtml = $OUTPUT->render($bulkactions);
 175          $formcontents .= $OUTPUT->box($bulkactionshtml, 'center');
 176  
 177          // Build the attempts form.
 178          $formattributes = [
 179              'id' => $formid,
 180              'method' => 'post',
 181          ];
 182          $attemptshtml = html_writer::tag('form', $formcontents, $formattributes);
 183      }
 184  
 185      // Show the attempts HTML.
 186      echo $attemptshtml;
 187  
 188      // Calculate the Statistics.
 189      if ($data->avetime == null) {
 190          $data->avetime = get_string("notcompleted", "lesson");
 191      } else {
 192          $data->avetime = format_float($data->avetime / $data->numofattempts, 0);
 193          $data->avetime = format_time($data->avetime);
 194      }
 195      if ($data->hightime == null) {
 196          $data->hightime = get_string("notcompleted", "lesson");
 197      } else {
 198          $data->hightime = format_time($data->hightime);
 199      }
 200      if ($data->lowtime == null) {
 201          $data->lowtime = get_string("notcompleted", "lesson");
 202      } else {
 203          $data->lowtime = format_time($data->lowtime);
 204      }
 205  
 206      if ($data->lessonscored) {
 207          if ($data->numofattempts == 0) {
 208              $data->avescore = get_string("notcompleted", "lesson");
 209          } else {
 210              $data->avescore = format_float($data->avescore, 2) . '%';
 211          }
 212          if ($data->highscore === null) {
 213              $data->highscore = get_string("notcompleted", "lesson");
 214          } else {
 215              $data->highscore .= '%';
 216          }
 217          if ($data->lowscore === null) {
 218              $data->lowscore = get_string("notcompleted", "lesson");
 219          } else {
 220              $data->lowscore .= '%';
 221          }
 222  
 223          // Display the full stats for the lesson.
 224          echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
 225          $stattable = new html_table();
 226          $stattable->head = array(get_string('averagescore', 'lesson'), get_string('averagetime', 'lesson'),
 227                                  get_string('highscore', 'lesson'), get_string('lowscore', 'lesson'),
 228                                  get_string('hightime', 'lesson'), get_string('lowtime', 'lesson'));
 229          $stattable->align = array('center', 'center', 'center', 'center', 'center', 'center');
 230          $stattable->attributes['class'] = 'standardtable generaltable';
 231          $stattable->data[] = array($data->avescore, $data->avetime, $data->highscore, $data->lowscore, $data->hightime, $data->lowtime);
 232  
 233      } else {
 234          // Display simple stats for the lesson.
 235          echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
 236          $stattable = new html_table();
 237          $stattable->head = array(get_string('averagetime', 'lesson'), get_string('hightime', 'lesson'),
 238                                  get_string('lowtime', 'lesson'));
 239          $stattable->align = array('center', 'center', 'center');
 240          $stattable->attributes['class'] = 'standardtable generaltable';
 241          $stattable->data[] = array($data->avetime, $data->hightime, $data->lowtime);
 242      }
 243  
 244      echo html_writer::table($stattable);
 245  } else if ($action === 'reportdetail') {
 246      /**************************************************************************
 247      this action is for a student detailed view and for the general detailed view
 248  
 249      General flow of this section of the code
 250      1.  Generate a object which holds values for the statistics for each question/answer
 251      2.  Cycle through all the pages to create a object.  Foreach page, see if the student actually answered
 252          the page.  Then process the page appropriatly.  Display all info about the question,
 253          Highlight correct answers, show how the user answered the question, and display statistics
 254          about each page
 255      3.  Print out info about the try (if needed)
 256      4.  Print out the object which contains all the try info
 257  
 258  **************************************************************************/
 259      echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('detailedstats', 'lesson'));
 260      groups_print_activity_menu($cm, $url);
 261  
 262      $course_context = context_course::instance($course->id);
 263      if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
 264          $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
 265          $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
 266          echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
 267      }
 268  
 269      $formattextdefoptions = new stdClass;
 270      $formattextdefoptions->para = false;  //I'll use it widely in this page
 271      $formattextdefoptions->overflowdiv = true;
 272  
 273      $userid = optional_param('userid', null, PARAM_INT); // if empty, then will display the general detailed view
 274      $try    = optional_param('try', null, PARAM_INT);
 275  
 276      list($answerpages, $userstats) = lesson_get_user_detailed_report_data($lesson, $userid, $try);
 277  
 278      /// actually start printing something
 279      $table = new html_table();
 280      $table->wrap = array();
 281      $table->width = "60%";
 282      if (!empty($userid)) {
 283          // if looking at a students try, print out some basic stats at the top
 284  
 285              // print out users name
 286              //$headingobject->lastname = $students[$userid]->lastname;
 287              //$headingobject->firstname = $students[$userid]->firstname;
 288              //$headingobject->attempt = $try + 1;
 289              //print_heading(get_string("studentattemptlesson", "lesson", $headingobject));
 290          echo $OUTPUT->heading(get_string('attempt', 'lesson', $try+1), 3);
 291  
 292          $table->head = array();
 293          $table->align = array('right', 'left');
 294          $table->attributes['class'] = 'table table-striped';
 295  
 296          if (empty($userstats->gradeinfo)) {
 297              $table->align = array("center");
 298  
 299              $table->data[] = array(get_string("notcompleted", "lesson"));
 300          } else {
 301              $user = $DB->get_record('user', array('id' => $userid));
 302  
 303              $gradeinfo = lesson_grade($lesson, $try, $user->id);
 304  
 305              $table->data[] = array(get_string('name').':', $OUTPUT->user_picture($user, array('courseid'=>$course->id)).fullname($user, true));
 306              $table->data[] = array(get_string("timetaken", "lesson").":", format_time($userstats->timetotake));
 307              $table->data[] = array(get_string("completed", "lesson").":", userdate($userstats->completed));
 308              $table->data[] = array(get_string('rawgrade', 'lesson').':', $userstats->gradeinfo->earned.'/'.$userstats->gradeinfo->total);
 309              $table->data[] = array(get_string("grade", "lesson").":", $userstats->grade."%");
 310          }
 311          echo html_writer::table($table);
 312  
 313          // Don't want this class for later tables
 314          $table->attributes['class'] = '';
 315      }
 316  
 317      foreach ($answerpages as $page) {
 318          $table->align = array('left', 'left');
 319          $table->size = array('70%', null);
 320          $table->attributes['class'] = 'table table-striped';
 321          unset($table->data);
 322          if ($page->grayout) { // set the color of text
 323              $fontstart = html_writer::start_tag('span', array('class' => 'dimmed_text'));
 324              $fontend = html_writer::end_tag('span');
 325              $fontstart2 = $fontstart;
 326              $fontend2 = $fontend;
 327          } else {
 328              $fontstart = '';
 329              $fontend = '';
 330              $fontstart2 = '';
 331              $fontend2 = '';
 332          }
 333  
 334          $table->head = array($fontstart2.$page->qtype.": ".format_string($page->title).$fontend2, $fontstart2.get_string("classstats", "lesson").$fontend2);
 335          $table->data[] = array($fontstart.get_string("question", "lesson").": <br />".$fontend.$fontstart2.$page->contents.$fontend2, " ");
 336          $table->data[] = array($fontstart.get_string("answer", "lesson").":".$fontend, ' ');
 337          // apply the font to each answer
 338          if (!empty($page->answerdata) && !empty($page->answerdata->answers)) {
 339              foreach ($page->answerdata->answers as $answer){
 340                  $modified = array();
 341                  foreach ($answer as $single) {
 342                      // need to apply a font to each one
 343                      $modified[] = $fontstart2.$single.$fontend2;
 344                  }
 345                  $table->data[] = $modified;
 346              }
 347              if (isset($page->answerdata->response)) {
 348                  $table->data[] = array($fontstart.get_string("response", "lesson").": <br />".$fontend
 349                          .$fontstart2.$page->answerdata->response.$fontend2, " ");
 350              }
 351              $table->data[] = array($page->answerdata->score, " ");
 352          } else {
 353              $table->data[] = array(get_string('didnotanswerquestion', 'lesson'), " ");
 354          }
 355          echo html_writer::start_tag('div', ['class' => 'no-overflow table-responsive']);
 356          echo html_writer::table($table);
 357          echo html_writer::end_tag('div');
 358      }
 359  } else {
 360      print_error('unknowaction');
 361  }
 362  
 363  /// Finish the page
 364  echo $OUTPUT->footer();