Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

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