Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Definition of the grade_overview_report class
  19   *
  20   * @package gradereport_overview
  21   * @copyright 2007 Nicolas Connault
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  require_once($CFG->dirroot . '/grade/report/lib.php');
  26  require_once($CFG->libdir.'/tablelib.php');
  27  
  28  /**
  29   * Class providing an API for the overview report building and displaying.
  30   * @uses grade_report
  31   * @package gradereport_overview
  32   */
  33  class grade_report_overview extends grade_report {
  34  
  35      /**
  36       * The user's courses
  37       * @var array $courses
  38       */
  39      public $courses;
  40  
  41      /**
  42       * A flexitable to hold the data.
  43       * @var object $table
  44       */
  45      public $table;
  46  
  47      /**
  48       * Show student ranks within each course.
  49       * @var array $showrank
  50       */
  51      public $showrank;
  52  
  53      /**
  54       * An array of course ids that the user is a student in.
  55       * @var array $studentcourseids
  56       */
  57      public $studentcourseids;
  58  
  59      /**
  60       * An array of courses that the user is a teacher in.
  61       * @var array $teachercourses
  62       */
  63      public $teachercourses;
  64  
  65      /**
  66       * Constructor. Sets local copies of user preferences and initialises grade_tree.
  67       * @param int $userid
  68       * @param object $gpr grade plugin return tracking object
  69       * @param string $context
  70       */
  71      public function __construct($userid, $gpr, $context) {
  72          global $CFG, $COURSE, $DB, $USER;
  73          parent::__construct($COURSE->id, $gpr, $context);
  74  
  75          // Get the user (for full name).
  76          $this->user = $DB->get_record('user', array('id' => $userid));
  77  
  78          // Set onlyactive flag to true if the user's viewing his/her report.
  79          $onlyactive = ($this->user->id === $USER->id);
  80  
  81          // Load the user's courses.
  82          $this->courses = enrol_get_users_courses($this->user->id, $onlyactive, 'id, shortname, showgrades');
  83  
  84          $this->showrank = array();
  85          $this->showrank['any'] = false;
  86  
  87          $this->showtotalsifcontainhidden = array();
  88  
  89          $this->studentcourseids = array();
  90          $this->teachercourses = array();
  91          $roleids = explode(',', get_config('moodle', 'gradebookroles'));
  92  
  93          if ($this->courses) {
  94              foreach ($this->courses as $course) {
  95                  $this->showrank[$course->id] = grade_get_setting($course->id, 'report_overview_showrank', !empty($CFG->grade_report_overview_showrank));
  96                  if ($this->showrank[$course->id]) {
  97                      $this->showrank['any'] = true;
  98                  }
  99  
 100                  $this->showtotalsifcontainhidden[$course->id] = grade_get_setting($course->id, 'report_overview_showtotalsifcontainhidden', $CFG->grade_report_overview_showtotalsifcontainhidden);
 101  
 102                  $coursecontext = context_course::instance($course->id);
 103  
 104                  foreach ($roleids as $roleid) {
 105                      if (user_has_role_assignment($userid, $roleid, $coursecontext->id)) {
 106                          $this->studentcourseids[$course->id] = $course->id;
 107                          // We only need to check if one of the roleids has been assigned.
 108                          break;
 109                      }
 110                  }
 111  
 112                  if (has_capability('moodle/grade:viewall', $coursecontext, $userid)) {
 113                      $this->teachercourses[$course->id] = $course;
 114                  }
 115              }
 116          }
 117  
 118  
 119          // base url for sorting by first/last name
 120          $this->baseurl = $CFG->wwwroot.'/grade/overview/index.php?id='.$userid;
 121          $this->pbarurl = $this->baseurl;
 122  
 123          $this->setup_table();
 124      }
 125  
 126      /**
 127       * Regrades all courses if needed.
 128       *
 129       * If $frontend is true, this may show a progress bar and redirect back to the page (possibly
 130       * several times if multiple courses need it). Otherwise, it will not return until all the
 131       * courses have been updated.
 132       *
 133       * @param bool $frontend True if we are running front-end code and can safely redirect back
 134       */
 135      public function regrade_all_courses_if_needed(bool $frontend = false): void {
 136          foreach ($this->courses as $course) {
 137              if ($frontend) {
 138                  grade_regrade_final_grades_if_required($course);
 139              } else {
 140                  grade_regrade_final_grades($course->id);
 141              }
 142          }
 143      }
 144  
 145      /**
 146       * Prepares the headers and attributes of the flexitable.
 147       */
 148      public function setup_table() {
 149          /*
 150           * Table has 3 columns
 151           *| course  | final grade | rank (optional) |
 152           */
 153  
 154          // setting up table headers
 155          if ($this->showrank['any']) {
 156              $tablecolumns = array('coursename', 'grade', 'rank');
 157              $tableheaders = array(grade_helper::get_lang_string('coursename', 'grades'),
 158                  grade_helper::get_lang_string('gradenoun'),
 159                  grade_helper::get_lang_string('rank', 'grades'));
 160          } else {
 161              $tablecolumns = array('coursename', 'grade');
 162              $tableheaders = array(grade_helper::get_lang_string('coursename', 'grades'),
 163                  grade_helper::get_lang_string('gradenoun'));
 164          }
 165          $this->table = new flexible_table('grade-report-overview-'.$this->user->id);
 166  
 167          $this->table->define_columns($tablecolumns);
 168          $this->table->define_headers($tableheaders);
 169          $this->table->define_baseurl($this->baseurl);
 170  
 171          $this->table->set_attribute('cellspacing', '0');
 172          $this->table->set_attribute('id', 'overview-grade');
 173          $this->table->set_attribute('class', 'boxaligncenter generaltable');
 174  
 175          $this->table->setup();
 176      }
 177  
 178      /**
 179       * Set up the courses grades data for the report.
 180       *
 181       * @param bool $studentcoursesonly Only show courses that the user is a student of.
 182       * @return array of course grades information
 183       */
 184      public function setup_courses_data($studentcoursesonly) {
 185          global $USER, $DB;
 186  
 187          $coursesdata = array();
 188          $numusers = $this->get_numusers(false);
 189  
 190          foreach ($this->courses as $course) {
 191              if (!$course->showgrades) {
 192                  continue;
 193              }
 194  
 195              // If we are only showing student courses and this course isn't part of the group, then move on.
 196              if ($studentcoursesonly && !isset($this->studentcourseids[$course->id])) {
 197                  continue;
 198              }
 199  
 200              $coursecontext = context_course::instance($course->id);
 201  
 202              if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
 203                  // The course is hidden and the user isn't allowed to see it.
 204                  continue;
 205              }
 206  
 207              if (!has_capability('moodle/user:viewuseractivitiesreport', context_user::instance($this->user->id)) &&
 208                      ((!has_capability('moodle/grade:view', $coursecontext) || $this->user->id != $USER->id) &&
 209                      !has_capability('moodle/grade:viewall', $coursecontext))) {
 210                  continue;
 211              }
 212  
 213              $coursesdata[$course->id]['course'] = $course;
 214              $coursesdata[$course->id]['context'] = $coursecontext;
 215  
 216              $canviewhidden = has_capability('moodle/grade:viewhidden', $coursecontext);
 217  
 218              // Get course grade_item.
 219              $courseitem = grade_item::fetch_course_item($course->id);
 220  
 221              // Get the stored grade.
 222              $coursegrade = new grade_grade(array('itemid' => $courseitem->id, 'userid' => $this->user->id));
 223              $coursegrade->grade_item =& $courseitem;
 224              $finalgrade = $coursegrade->finalgrade;
 225  
 226              if (!$canviewhidden and !is_null($finalgrade)) {
 227                  if ($coursegrade->is_hidden()) {
 228                      $finalgrade = null;
 229                  } else {
 230                      $adjustedgrade = $this->blank_hidden_total_and_adjust_bounds($course->id,
 231                                                                                   $courseitem,
 232                                                                                   $finalgrade);
 233  
 234                      // We temporarily adjust the view of this grade item - because the min and
 235                      // max are affected by the hidden values in the aggregation.
 236                      $finalgrade = $adjustedgrade['grade'];
 237                      $courseitem->grademax = $adjustedgrade['grademax'];
 238                      $courseitem->grademin = $adjustedgrade['grademin'];
 239                  }
 240              } else {
 241                  // We must use the specific max/min because it can be different for
 242                  // each grade_grade when items are excluded from sum of grades.
 243                  if (!is_null($finalgrade)) {
 244                      $courseitem->grademin = $coursegrade->get_grade_min();
 245                      $courseitem->grademax = $coursegrade->get_grade_max();
 246                  }
 247              }
 248  
 249              $coursesdata[$course->id]['finalgrade'] = $finalgrade;
 250              $coursesdata[$course->id]['courseitem'] = $courseitem;
 251  
 252              if ($this->showrank['any'] && $this->showrank[$course->id] && !is_null($finalgrade)) {
 253                  // Find the number of users with a higher grade.
 254                  // Please note this can not work if hidden grades involved :-( to be fixed in 2.0.
 255                  $params = array($finalgrade, $courseitem->id);
 256                  $sql = "SELECT COUNT(DISTINCT(userid))
 257                            FROM {grade_grades}
 258                           WHERE finalgrade IS NOT NULL AND finalgrade > ?
 259                                 AND itemid = ?";
 260                  $rank = $DB->count_records_sql($sql, $params) + 1;
 261  
 262                  $coursesdata[$course->id]['rank'] = $rank;
 263                  $coursesdata[$course->id]['numusers'] = $numusers;
 264              }
 265          }
 266          return $coursesdata;
 267      }
 268  
 269      /**
 270       * Fill the table for displaying.
 271       *
 272       * @param bool $activitylink If this report link to the activity report or the user report.
 273       * @param bool $studentcoursesonly Only show courses that the user is a student of.
 274       */
 275      public function fill_table($activitylink = false, $studentcoursesonly = false) {
 276          global $CFG, $DB, $OUTPUT, $USER;
 277  
 278          if ($studentcoursesonly && count($this->studentcourseids) == 0) {
 279              return false;
 280          }
 281  
 282          // Only show user's courses instead of all courses.
 283          if ($this->courses) {
 284              $coursesdata = $this->setup_courses_data($studentcoursesonly);
 285  
 286              // Check whether current user can view all grades of this user - parent most probably.
 287              $viewasuser = $this->course->showgrades && has_any_capability([
 288                  'moodle/grade:viewall',
 289                  'moodle/user:viewuseractivitiesreport',
 290              ], context_user::instance($this->user->id));
 291  
 292              foreach ($coursesdata as $coursedata) {
 293  
 294                  $course = $coursedata['course'];
 295                  $coursecontext = $coursedata['context'];
 296                  $finalgrade = $coursedata['finalgrade'];
 297                  $courseitem = $coursedata['courseitem'];
 298  
 299                  $coursenamelink = format_string(get_course_display_name_for_list($course), true, ['context' => $coursecontext]);
 300  
 301                  // Link to the course grade report pages (performing same capability checks as the pages themselves).
 302                  if ($activitylink &&
 303                          (has_capability('gradereport/' . $CFG->grade_profilereport .':view', $coursecontext) || $viewasuser)) {
 304  
 305                      $coursenamelink = html_writer::link(new moodle_url('/course/user.php', [
 306                          'mode' => 'grade',
 307                          'id' => $course->id,
 308                          'user' => $this->user->id,
 309                      ]), $coursenamelink);
 310                  } else if (!$activitylink && (has_capability('gradereport/user:view', $coursecontext) || $viewasuser)) {
 311                      $coursenamelink = html_writer::link(new moodle_url('/grade/report/user/index.php', [
 312                          'id' => $course->id,
 313                          'userid' => $this->user->id,
 314                          'group' => $this->gpr->groupid,
 315                      ]), $coursenamelink);
 316                  }
 317  
 318                  $data = [$coursenamelink, grade_format_gradevalue($finalgrade, $courseitem, true)];
 319  
 320                  if ($this->showrank['any']) {
 321                      if ($this->showrank[$course->id] && !is_null($finalgrade)) {
 322                          $rank = $coursedata['rank'];
 323                          $numusers = $coursedata['numusers'];
 324                          $data[] = "$rank/$numusers";
 325                      } else {
 326                          // No grade, no rank.
 327                          // Or this course wants rank hidden.
 328                          $data[] = '-';
 329                      }
 330                  }
 331  
 332                  $this->table->add_data($data);
 333              }
 334  
 335              return true;
 336          } else {
 337              echo $OUTPUT->notification(get_string('notenrolled', 'grades'), 'notifymessage');
 338              return false;
 339          }
 340      }
 341  
 342      /**
 343       * Prints or returns the HTML from the flexitable.
 344       * @param bool $return Whether or not to return the data instead of printing it directly.
 345       * @return string
 346       */
 347      public function print_table($return=false) {
 348          ob_start();
 349          $this->table->print_html();
 350          $html = ob_get_clean();
 351          if ($return) {
 352              return $html;
 353          } else {
 354              echo $html;
 355          }
 356      }
 357  
 358      /**
 359       * Print a table to show courses that the user is able to grade.
 360       */
 361      public function print_teacher_table() {
 362          $table = new html_table();
 363          $table->head = array(get_string('coursename', 'grades'));
 364          $table->data = null;
 365          foreach ($this->teachercourses as $courseid => $course) {
 366              $coursecontext = context_course::instance($course->id);
 367              $coursenamelink = format_string($course->fullname, true, ['context' => $coursecontext]);
 368              $url = new moodle_url('/grade/report/index.php', array('id' => $courseid));
 369              $table->data[] = array(html_writer::link($url, $coursenamelink));
 370          }
 371          echo html_writer::table($table);
 372      }
 373  
 374      /**
 375       * Processes the data sent by the form (grades and feedbacks).
 376       * @param array $data
 377       * @return bool Success or Failure (array of errors).
 378       */
 379      function process_data($data) {
 380      }
 381      function process_action($target, $action) {
 382      }
 383  
 384      /**
 385       * This report supports being set as the 'grades' report.
 386       */
 387      public static function supports_mygrades() {
 388          return true;
 389      }
 390  
 391      /**
 392       * Check if the user can access the report.
 393       *
 394       * @param  stdClass $systemcontext   system context
 395       * @param  stdClass $context         course context
 396       * @param  stdClass $personalcontext personal context
 397       * @param  stdClass $course          course object
 398       * @param  int $userid               userid
 399       * @return bool true if the user can access the report
 400       * @since  Moodle 3.2
 401       */
 402      public static function check_access($systemcontext, $context, $personalcontext, $course, $userid) {
 403          global $USER;
 404  
 405          $access = false;
 406          if (has_capability('moodle/grade:viewall', $systemcontext)) {
 407              // Ok - can view all course grades.
 408              $access = true;
 409  
 410          } else if (has_capability('moodle/grade:viewall', $context)) {
 411              // Ok - can view any grades in context.
 412              $access = true;
 413  
 414          } else if ($userid == $USER->id and ((has_capability('moodle/grade:view', $context) and $course->showgrades)
 415                  || $course->id == SITEID)) {
 416              // Ok - can view own course grades.
 417              $access = true;
 418  
 419          } else if (has_capability('moodle/grade:viewall', $personalcontext) and $course->showgrades) {
 420              // Ok - can view grades of this user - parent most probably.
 421              $access = true;
 422          } else if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext) and $course->showgrades) {
 423              // Ok - can view grades of this user - parent most probably.
 424              $access = true;
 425          }
 426          return $access;
 427      }
 428  
 429      /**
 430       * Trigger the grade_report_viewed event
 431       *
 432       * @param  stdClass $context  course context
 433       * @param  int $courseid      course id
 434       * @param  int $userid        user id
 435       * @since Moodle 3.2
 436       */
 437      public static function viewed($context, $courseid, $userid) {
 438          $event = \gradereport_overview\event\grade_report_viewed::create(
 439              array(
 440                  'context' => $context,
 441                  'courseid' => $courseid,
 442                  'relateduserid' => $userid,
 443              )
 444          );
 445          $event->trigger();
 446      }
 447  }
 448  
 449  function grade_report_overview_settings_definition(&$mform) {
 450      global $CFG;
 451  
 452      //show rank
 453      $options = array(-1 => get_string('default', 'grades'),
 454                        0 => get_string('hide'),
 455                        1 => get_string('show'));
 456  
 457      if (empty($CFG->grade_report_overview_showrank)) {
 458          $options[-1] = get_string('defaultprev', 'grades', $options[0]);
 459      } else {
 460          $options[-1] = get_string('defaultprev', 'grades', $options[1]);
 461      }
 462  
 463      $mform->addElement('select', 'report_overview_showrank', get_string('showrank', 'grades'), $options);
 464      $mform->addHelpButton('report_overview_showrank', 'showrank', 'grades');
 465  
 466      //showtotalsifcontainhidden
 467      $options = array(-1 => get_string('default', 'grades'),
 468                        GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN => get_string('hide'),
 469                        GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowexhiddenitems', 'grades'),
 470                        GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowinchiddenitems', 'grades') );
 471  
 472      if (!array_key_exists($CFG->grade_report_overview_showtotalsifcontainhidden, $options)) {
 473          $options[-1] = get_string('defaultprev', 'grades', $options[0]);
 474      } else {
 475          $options[-1] = get_string('defaultprev', 'grades', $options[$CFG->grade_report_overview_showtotalsifcontainhidden]);
 476      }
 477  
 478      $mform->addElement('select', 'report_overview_showtotalsifcontainhidden', get_string('hidetotalifhiddenitems', 'grades'), $options);
 479      $mform->addHelpButton('report_overview_showtotalsifcontainhidden', 'hidetotalifhiddenitems', 'grades');
 480  }
 481  
 482  /**
 483   * Add nodes to myprofile page.
 484   *
 485   * @param \core_user\output\myprofile\tree $tree Tree object
 486   * @param stdClass $user user object
 487   * @param bool $iscurrentuser
 488   * @param stdClass $course Course object
 489   */
 490  function gradereport_overview_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $iscurrentuser, $course) {
 491      if (empty($course)) {
 492          // We want to display these reports under the site context.
 493          $course = get_fast_modinfo(SITEID)->get_course();
 494      }
 495      $systemcontext = context_system::instance();
 496      $usercontext = context_user::instance($user->id);
 497      $coursecontext = context_course::instance($course->id);
 498      if (grade_report_overview::check_access($systemcontext, $coursecontext, $usercontext, $course, $user->id)) {
 499          $url = new moodle_url('/grade/report/overview/index.php', array('userid' => $user->id, 'id' => $course->id));
 500          $node = new core_user\output\myprofile\node('reports', 'grades', get_string('gradesoverview', 'gradereport_overview'),
 501                  null, $url);
 502          $tree->add_node($node);
 503      }
 504  }