Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 == '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::table($table);
 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->wrap = array('nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap');
 231          $stattable->attributes['class'] = 'standardtable generaltable';
 232          $stattable->data[] = array($data->avescore, $data->avetime, $data->highscore, $data->lowscore, $data->hightime, $data->lowtime);
 233  
 234      } else {
 235          // Display simple stats for the lesson.
 236          echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
 237          $stattable = new html_table();
 238          $stattable->head = array(get_string('averagetime', 'lesson'), get_string('hightime', 'lesson'),
 239                                  get_string('lowtime', 'lesson'));
 240          $stattable->align = array('center', 'center', 'center');
 241          $stattable->wrap = array('nowrap', 'nowrap', 'nowrap');
 242          $stattable->attributes['class'] = 'standardtable generaltable';
 243          $stattable->data[] = array($data->avetime, $data->hightime, $data->lowtime);
 244      }
 245  
 246      echo html_writer::table($stattable);
 247  } else if ($action === 'reportdetail') {
 248      /**************************************************************************
 249      this action is for a student detailed view and for the general detailed view
 250  
 251      General flow of this section of the code
 252      1.  Generate a object which holds values for the statistics for each question/answer
 253      2.  Cycle through all the pages to create a object.  Foreach page, see if the student actually answered
 254          the page.  Then process the page appropriatly.  Display all info about the question,
 255          Highlight correct answers, show how the user answered the question, and display statistics
 256          about each page
 257      3.  Print out info about the try (if needed)
 258      4.  Print out the object which contains all the try info
 259  
 260  **************************************************************************/
 261      echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('detailedstats', 'lesson'));
 262      groups_print_activity_menu($cm, $url);
 263  
 264      $course_context = context_course::instance($course->id);
 265      if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
 266          $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
 267          $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
 268          echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
 269      }
 270  
 271      $formattextdefoptions = new stdClass;
 272      $formattextdefoptions->para = false;  //I'll use it widely in this page
 273      $formattextdefoptions->overflowdiv = true;
 274  
 275      $userid = optional_param('userid', null, PARAM_INT); // if empty, then will display the general detailed view
 276      $try    = optional_param('try', null, PARAM_INT);
 277  
 278      list($answerpages, $userstats) = lesson_get_user_detailed_report_data($lesson, $userid, $try);
 279  
 280      /// actually start printing something
 281      $table = new html_table();
 282      $table->wrap = array();
 283      $table->width = "60%";
 284      if (!empty($userid)) {
 285          // if looking at a students try, print out some basic stats at the top
 286  
 287              // print out users name
 288              //$headingobject->lastname = $students[$userid]->lastname;
 289              //$headingobject->firstname = $students[$userid]->firstname;
 290              //$headingobject->attempt = $try + 1;
 291              //print_heading(get_string("studentattemptlesson", "lesson", $headingobject));
 292          echo $OUTPUT->heading(get_string('attempt', 'lesson', $try+1), 3);
 293  
 294          $table->head = array();
 295          $table->align = array('right', 'left');
 296          $table->attributes['class'] = 'generaltable';
 297  
 298          if (empty($userstats->gradeinfo)) {
 299              $table->align = array("center");
 300  
 301              $table->data[] = array(get_string("notcompleted", "lesson"));
 302          } else {
 303              $user = $DB->get_record('user', array('id' => $userid));
 304  
 305              $gradeinfo = lesson_grade($lesson, $try, $user->id);
 306  
 307              $table->data[] = array(get_string('name').':', $OUTPUT->user_picture($user, array('courseid'=>$course->id)).fullname($user, true));
 308              $table->data[] = array(get_string("timetaken", "lesson").":", format_time($userstats->timetotake));
 309              $table->data[] = array(get_string("completed", "lesson").":", userdate($userstats->completed));
 310              $table->data[] = array(get_string('rawgrade', 'lesson').':', $userstats->gradeinfo->earned.'/'.$userstats->gradeinfo->total);
 311              $table->data[] = array(get_string("grade", "lesson").":", $userstats->grade."%");
 312          }
 313          echo html_writer::table($table);
 314  
 315          // Don't want this class for later tables
 316          $table->attributes['class'] = '';
 317      }
 318  
 319      foreach ($answerpages as $page) {
 320          $table->align = array('left', 'left');
 321          $table->size = array('70%', null);
 322          $table->attributes['class'] = 'generaltable';
 323          unset($table->data);
 324          if ($page->grayout) { // set the color of text
 325              $fontstart = html_writer::start_tag('span', array('class' => 'dimmed_text'));
 326              $fontend = html_writer::end_tag('span');
 327              $fontstart2 = $fontstart;
 328              $fontend2 = $fontend;
 329          } else {
 330              $fontstart = '';
 331              $fontend = '';
 332              $fontstart2 = '';
 333              $fontend2 = '';
 334          }
 335  
 336          $table->head = array($fontstart2.$page->qtype.": ".format_string($page->title).$fontend2, $fontstart2.get_string("classstats", "lesson").$fontend2);
 337          $table->data[] = array($fontstart.get_string("question", "lesson").": <br />".$fontend.$fontstart2.$page->contents.$fontend2, " ");
 338          $table->data[] = array($fontstart.get_string("answer", "lesson").":".$fontend, ' ');
 339          // apply the font to each answer
 340          if (!empty($page->answerdata) && !empty($page->answerdata->answers)) {
 341              foreach ($page->answerdata->answers as $answer){
 342                  $modified = array();
 343                  foreach ($answer as $single) {
 344                      // need to apply a font to each one
 345                      $modified[] = $fontstart2.$single.$fontend2;
 346                  }
 347                  $table->data[] = $modified;
 348              }
 349              if (isset($page->answerdata->response)) {
 350                  $table->data[] = array($fontstart.get_string("response", "lesson").": <br />".$fontend
 351                          .$fontstart2.$page->answerdata->response.$fontend2, " ");
 352              }
 353              $table->data[] = array($page->answerdata->score, " ");
 354          } else {
 355              $table->data[] = array(get_string('didnotanswerquestion', 'lesson'), " ");
 356          }
 357          echo html_writer::start_tag('div', array('class' => 'no-overflow'));
 358          echo html_writer::table($table);
 359          echo html_writer::end_tag('div');
 360      }
 361  } else {
 362      print_error('unknowaction');
 363  }
 364  
 365  /// Finish the page
 366  echo $OUTPUT->footer();