Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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

   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          if ($PAGE->has_secondary_navigation()) {
 124              echo $reportactionarea;
 125          }
 126          if (!empty($currentgroup)) {
 127              $groupname = groups_get_group_name($currentgroup);
 128              echo $OUTPUT->notification(get_string('nolessonattemptsgroup', 'lesson', $groupname));
 129          } else {
 130              echo $OUTPUT->notification(get_string('nolessonattempts', 'lesson'));
 131          }
 132          groups_print_activity_menu($cm, $url);
 133          echo $OUTPUT->footer();
 134          exit();
 135      }
 136  
 137      echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('overview', 'lesson'));
 138      if ($PAGE->has_secondary_navigation()) {
 139          echo $reportactionarea;
 140      }
 141      groups_print_activity_menu($cm, $url);
 142  
 143      $course_context = context_course::instance($course->id);
 144      if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
 145          $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
 146          $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
 147          echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
 148      }
 149  
 150      // The attempts table.
 151      $attemptstable = html_writer::table($table);
 152  
 153      // The HTML that we will be displaying which includes the attempts table and bulk actions menu, if necessary.
 154      $attemptshtml = $attemptstable;
 155  
 156      // Show bulk actions when user has capability to edit the lesson.
 157      if (has_capability('mod/lesson:edit', $context)) {
 158          $reporturl = new moodle_url('/mod/lesson/report.php');
 159          $formid  = 'mod-lesson-report-form';
 160  
 161          // Sesskey hidden input.
 162          $formcontents = html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]);
 163  
 164          // CMID hidden input.
 165          $formcontents .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'id', 'value' => $cm->id]);
 166  
 167          // Attempts table.
 168          $formcontents .= $attemptstable;
 169  
 170          // Bulk actions menu.
 171          $attemptsactions = [
 172              'delete' => get_string('deleteselected')
 173          ];
 174          $bulkactions = new single_select($reporturl, 'action', $attemptsactions, '', ['' => 'choosedots'], $formid);
 175          $bulkactions->set_label(get_string('withselectedattempts', 'lesson'));
 176          $bulkactions->disabled = true;
 177          $bulkactions->attributes = [
 178              'data-action' => 'toggle',
 179              'data-togglegroup' => 'lesson-attempts',
 180              'data-toggle' => 'action',
 181          ];
 182          $bulkactionshtml = $OUTPUT->render($bulkactions);
 183          $formcontents .= $OUTPUT->box($bulkactionshtml, 'center');
 184  
 185          // Build the attempts form.
 186          $formattributes = [
 187              'id' => $formid,
 188              'method' => 'post',
 189          ];
 190          $attemptshtml = html_writer::tag('form', $formcontents, $formattributes);
 191      }
 192  
 193      // Show the attempts HTML.
 194      echo $attemptshtml;
 195  
 196      // Calculate the Statistics.
 197      if ($data->avetime == null) {
 198          $data->avetime = get_string("notcompleted", "lesson");
 199      } else {
 200          $data->avetime = format_float($data->avetime / $data->numofattempts, 0);
 201          $data->avetime = format_time($data->avetime);
 202      }
 203      if ($data->hightime == null) {
 204          $data->hightime = get_string("notcompleted", "lesson");
 205      } else {
 206          $data->hightime = format_time($data->hightime);
 207      }
 208      if ($data->lowtime == null) {
 209          $data->lowtime = get_string("notcompleted", "lesson");
 210      } else {
 211          $data->lowtime = format_time($data->lowtime);
 212      }
 213  
 214      if ($data->lessonscored) {
 215          if ($data->numofattempts == 0) {
 216              $data->avescore = get_string("notcompleted", "lesson");
 217          } else {
 218              $data->avescore = format_float($data->avescore, 2) . '%';
 219          }
 220          if ($data->highscore === null) {
 221              $data->highscore = get_string("notcompleted", "lesson");
 222          } else {
 223              $data->highscore .= '%';
 224          }
 225          if ($data->lowscore === null) {
 226              $data->lowscore = get_string("notcompleted", "lesson");
 227          } else {
 228              $data->lowscore .= '%';
 229          }
 230  
 231          // Display the full stats for the lesson.
 232          echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
 233          $stattable = new html_table();
 234          $stattable->head = array(get_string('averagescore', 'lesson'), get_string('averagetime', 'lesson'),
 235                                  get_string('highscore', 'lesson'), get_string('lowscore', 'lesson'),
 236                                  get_string('hightime', 'lesson'), get_string('lowtime', 'lesson'));
 237          $stattable->align = array('center', 'center', 'center', 'center', 'center', 'center');
 238          $stattable->attributes['class'] = 'standardtable generaltable';
 239          $stattable->data[] = array($data->avescore, $data->avetime, $data->highscore, $data->lowscore, $data->hightime, $data->lowtime);
 240  
 241      } else {
 242          // Display simple stats for the lesson.
 243          echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
 244          $stattable = new html_table();
 245          $stattable->head = array(get_string('averagetime', 'lesson'), get_string('hightime', 'lesson'),
 246                                  get_string('lowtime', 'lesson'));
 247          $stattable->align = array('center', 'center', 'center');
 248          $stattable->attributes['class'] = 'standardtable generaltable';
 249          $stattable->data[] = array($data->avetime, $data->hightime, $data->lowtime);
 250      }
 251  
 252      echo html_writer::table($stattable);
 253  } else if ($action === 'reportdetail') {
 254      /**************************************************************************
 255      this action is for a student detailed view and for the general detailed view
 256  
 257      General flow of this section of the code
 258      1.  Generate a object which holds values for the statistics for each question/answer
 259      2.  Cycle through all the pages to create a object.  Foreach page, see if the student actually answered
 260          the page.  Then process the page appropriatly.  Display all info about the question,
 261          Highlight correct answers, show how the user answered the question, and display statistics
 262          about each page
 263      3.  Print out info about the try (if needed)
 264      4.  Print out the object which contains all the try info
 265  
 266  **************************************************************************/
 267      echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('detailedstats', 'lesson'));
 268      if ($PAGE->has_secondary_navigation()) {
 269          echo $reportactionarea;
 270      }
 271      groups_print_activity_menu($cm, $url);
 272  
 273      $course_context = context_course::instance($course->id);
 274      if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
 275          $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
 276          $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
 277          echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
 278      }
 279  
 280      $formattextdefoptions = new stdClass;
 281      $formattextdefoptions->para = false;  //I'll use it widely in this page
 282      $formattextdefoptions->overflowdiv = true;
 283  
 284      $userid = optional_param('userid', null, PARAM_INT); // if empty, then will display the general detailed view
 285      $try    = optional_param('try', null, PARAM_INT);
 286  
 287      list($answerpages, $userstats) = lesson_get_user_detailed_report_data($lesson, $userid, $try);
 288  
 289      /// actually start printing something
 290      $table = new html_table();
 291      $table->wrap = array();
 292      $table->width = "60%";
 293      if (!empty($userid)) {
 294          // if looking at a students try, print out some basic stats at the top
 295  
 296              // print out users name
 297              //$headingobject->lastname = $students[$userid]->lastname;
 298              //$headingobject->firstname = $students[$userid]->firstname;
 299              //$headingobject->attempt = $try + 1;
 300              //print_heading(get_string("studentattemptlesson", "lesson", $headingobject));
 301          echo $OUTPUT->heading(get_string('attempt', 'lesson', $try+1), 3);
 302  
 303          $table->head = array();
 304          $table->align = array('right', 'left');
 305          $table->attributes['class'] = 'table table-striped';
 306  
 307          if (empty($userstats->gradeinfo)) {
 308              $table->align = array("center");
 309  
 310              $table->data[] = array(get_string("notcompleted", "lesson"));
 311          } else {
 312              $user = $DB->get_record('user', array('id' => $userid));
 313  
 314              $gradeinfo = lesson_grade($lesson, $try, $user->id);
 315  
 316              $table->data[] = array(get_string('name').':', $OUTPUT->user_picture($user, array('courseid'=>$course->id)).fullname($user, true));
 317              $table->data[] = array(get_string("timetaken", "lesson").":", format_time($userstats->timetotake));
 318              $table->data[] = array(get_string("completed", "lesson").":", userdate($userstats->completed));
 319              $table->data[] = array(get_string('rawgrade', 'lesson').':', $userstats->gradeinfo->earned.'/'.$userstats->gradeinfo->total);
 320              $table->data[] = array(get_string("grade", "lesson").":", $userstats->grade."%");
 321          }
 322          echo html_writer::table($table);
 323  
 324          // Don't want this class for later tables
 325          $table->attributes['class'] = '';
 326      }
 327  
 328      foreach ($answerpages as $page) {
 329          $table->align = array('left', 'left');
 330          $table->size = array('70%', null);
 331          $table->attributes['class'] = 'table table-striped';
 332          unset($table->data);
 333          if ($page->grayout) { // set the color of text
 334              $fontstart = html_writer::start_tag('span', array('class' => 'dimmed_text'));
 335              $fontend = html_writer::end_tag('span');
 336              $fontstart2 = $fontstart;
 337              $fontend2 = $fontend;
 338          } else {
 339              $fontstart = '';
 340              $fontend = '';
 341              $fontstart2 = '';
 342              $fontend2 = '';
 343          }
 344  
 345          $table->head = array($fontstart2.$page->qtype.": ".format_string($page->title).$fontend2, $fontstart2.get_string("classstats", "lesson").$fontend2);
 346          $table->data[] = array($fontstart.get_string("question", "lesson").": <br />".$fontend.$fontstart2.$page->contents.$fontend2, " ");
 347          $table->data[] = array($fontstart.get_string("answer", "lesson").":".$fontend, ' ');
 348          // apply the font to each answer
 349          if (!empty($page->answerdata) && !empty($page->answerdata->answers)) {
 350              foreach ($page->answerdata->answers as $answer){
 351                  $modified = array();
 352                  foreach ($answer as $single) {
 353                      // need to apply a font to each one
 354                      $modified[] = $fontstart2.$single.$fontend2;
 355                  }
 356                  $table->data[] = $modified;
 357              }
 358              if (isset($page->answerdata->response)) {
 359                  $table->data[] = array($fontstart.get_string("response", "lesson").": <br />".$fontend
 360                          .$fontstart2.$page->answerdata->response.$fontend2, " ");
 361              }
 362              $table->data[] = array($page->answerdata->score, " ");
 363          } else {
 364              $table->data[] = array(get_string('didnotanswerquestion', 'lesson'), " ");
 365          }
 366          echo html_writer::table($table);
 367      }
 368  } else {
 369      print_error('unknowaction');
 370  }
 371  
 372  /// Finish the page
 373  echo $OUTPUT->footer();