Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [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   * Abstract class used as a base for the 3 screens.
  19   *
  20   * @package   gradereport_singleview
  21   * @copyright 2014 Moodle Pty Ltd (http://moodle.com)
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace gradereport_singleview\local\screen;
  26  
  27  use context_course;
  28  use grade_report;
  29  use moodle_url;
  30  use html_writer;
  31  use grade_structure;
  32  use grade_grade;
  33  use grade_item;
  34  use stdClass;
  35  
  36  defined('MOODLE_INTERNAL') || die;
  37  require_once($CFG->dirroot . '/grade/report/lib.php');
  38  
  39  /**
  40   * Abstract class used as a base for the 3 screens.
  41   *
  42   * @package   gradereport_singleview
  43   * @copyright 2014 Moodle Pty Ltd (http://moodle.com)
  44   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  abstract class screen {
  47  
  48      /**
  49       * The id of the course
  50       * @var int $courseid
  51       */
  52      protected $courseid;
  53  
  54      /**
  55       * Either a user id or a grade_item id
  56       * @var int|null $itemid
  57       */
  58      protected $itemid;
  59  
  60      /**
  61       * The currently set groupid (if set)
  62       * @var int $groupid
  63       */
  64      protected $groupid;
  65  
  66      /**
  67       * The course context
  68       * @var context_course $context
  69       */
  70      protected $context;
  71  
  72      /**
  73       * The page number
  74       * @var int $page
  75       */
  76      protected $page;
  77  
  78      /**
  79       * Results per page
  80       * @var int $perpage
  81       */
  82      protected $perpage;
  83  
  84      /**
  85       * List of items on the page, they could be users or grade_items
  86       * @var array $items
  87       */
  88      protected $items;
  89  
  90      /** @var int Maximum number of students that can be shown on one page */
  91      protected static $maxperpage = 5000;
  92  
  93      /**
  94       * List of allowed values for 'perpage' setting
  95       * @var array $validperpage
  96       */
  97      protected static $validperpage = [20, 100];
  98  
  99      /**
 100       * To store course data
 101       * @var stdClass
 102       */
 103      protected $course;
 104  
 105      /**
 106       * General structure representing grade items in course
 107       * @var grade_structure
 108       */
 109      protected $structure;
 110  
 111      /**
 112       * Constructor
 113       *
 114       * @param int $courseid The course id
 115       * @param int|null $itemid The item id
 116       * @param int|null $groupid The group id
 117       */
 118      public function __construct(int $courseid, ?int $itemid, ?int $groupid = null) {
 119          global $DB;
 120  
 121          $this->courseid = $courseid;
 122          $this->itemid = $itemid;
 123          $this->groupid = $groupid;
 124  
 125          $this->context = context_course::instance($this->courseid);
 126          $this->course = $DB->get_record('course', ['id' => $courseid]);
 127  
 128          $this->page = optional_param('page', 0, PARAM_INT);
 129  
 130          $cache = \cache::make_from_params(\cache_store::MODE_SESSION, 'gradereport_singleview', 'perpage');
 131          $perpage = optional_param('perpage', null, PARAM_INT);
 132          if (!in_array($perpage, self::$validperpage) && ($perpage !== 0)) {
 133              // Get from cache.
 134              $perpage = $cache->get(get_class($this));
 135          } else {
 136              // Save to cache.
 137              $cache->set(get_class($this), $perpage);
 138          }
 139          if (isset($perpage) && $perpage) {
 140              $this->perpage = $perpage;
 141          } else {
 142              // Get from cache.
 143              $perpage = $cache->get(get_class($this));
 144              $this->perpage = ($perpage === 0) ? $perpage : min(self::$validperpage);
 145          }
 146  
 147          $this->init(empty($itemid));
 148      }
 149  
 150      /**
 151       * Cache the grade_structure class
 152       */
 153      public function setup_structure() {
 154          $this->structure = new grade_structure();
 155          $this->structure->modinfo = get_fast_modinfo($this->course);
 156      }
 157  
 158      /**
 159       * Create a nice link from a thing (user or grade_item).
 160       *
 161       * @param string $screen
 162       * @param int $itemid
 163       * @param bool|null $display Should we wrap this in an anchor ?
 164       * @return string The link
 165       */
 166      public function format_link(string $screen, int $itemid, bool $display = null): string {
 167          $url = new moodle_url('/grade/report/singleview/index.php', [
 168              'id' => $this->courseid,
 169              'item' => $screen,
 170              'itemid' => $itemid,
 171              'group' => $this->groupid,
 172          ]);
 173  
 174          if ($display) {
 175              return html_writer::link($url, $display);
 176          } else {
 177              return $url;
 178          }
 179      }
 180  
 181      /**
 182       * Get the grade_grade
 183       *
 184       * @param grade_item $item The grade_item
 185       * @param int $userid The user id
 186       * @return grade_grade
 187       */
 188      public function fetch_grade_or_default(grade_item $item, int $userid): grade_grade {
 189          $grade = grade_grade::fetch([
 190              'itemid' => $item->id, 'userid' => $userid
 191          ]);
 192  
 193          if (!$grade) {
 194              $default = new stdClass;
 195  
 196              $default->userid = $userid;
 197              $default->itemid = $item->id;
 198              $default->feedback = '';
 199  
 200              $grade = new grade_grade($default, false);
 201          }
 202  
 203          $grade->grade_item = $item;
 204  
 205          return $grade;
 206      }
 207  
 208      /**
 209       * Get the default heading for the screen.
 210       *
 211       * @return string
 212       */
 213      public function heading(): string {
 214          return get_string('entrypage', 'gradereport_singleview');
 215      }
 216  
 217      /**
 218       * Override this to init the screen.
 219       *
 220       * @param boolean $selfitemisempty True if no item has been selected yet.
 221       */
 222      abstract public function init(bool $selfitemisempty = false);
 223  
 224      /**
 225       * Get the type of items in the list.
 226       *
 227       * @return null|string
 228       */
 229      abstract public function item_type(): ?string;
 230  
 231      /**
 232       * Get the entire screen as a string.
 233       *
 234       * @return string
 235       */
 236      abstract public function html(): string;
 237  
 238      /**
 239       * Does this screen support paging?
 240       *
 241       * @return bool
 242       */
 243      public function supports_paging(): bool {
 244          return true;
 245      }
 246  
 247      /**
 248       * Default pager
 249       *
 250       * @return string
 251       */
 252      public function pager(): string {
 253          return '';
 254      }
 255  
 256      /**
 257       * Initialise the js for this screen.
 258       */
 259      public function js() {
 260          global $PAGE;
 261  
 262          $module = [
 263              'name' => 'gradereport_singleview',
 264              'fullpath' => '/grade/report/singleview/js/singleview.js',
 265              'requires' => ['base', 'dom', 'event', 'event-simulate', 'io-base']
 266          ];
 267  
 268          $PAGE->requires->strings_for_js(['overridenoneconfirm', 'removeoverride', 'removeoverridesave'],
 269              'gradereport_singleview');
 270          $PAGE->requires->js_init_call('M.gradereport_singleview.init', [], false, $module);
 271      }
 272  
 273      /**
 274       * Process the data from a form submission.
 275       *
 276       * @param array|object $data
 277       * @return stdClass of warnings
 278       */
 279      public function process($data): stdClass {
 280          $warnings = [];
 281  
 282          $fields = $this->definition();
 283  
 284          // Avoiding execution timeouts when updating
 285          // a large amount of grades.
 286          $progress = 0;
 287          $progressbar = new \core\progress\display_if_slow();
 288          $progressbar->start_html();
 289          $progressbar->start_progress(get_string('savegrades', 'gradereport_singleview'), count((array) $data) - 1);
 290          $changecount = [];
 291          // This array is used to determine if the override should be excluded from being counted as a change.
 292          $ignorevalues = [];
 293  
 294          foreach ($data as $varname => $throw) {
 295              $progressbar->progress($progress);
 296              $progress++;
 297              if (preg_match("/(\w+)_(\d+)_(\d+)/", $varname, $matches)) {
 298                  $itemid = $matches[2];
 299                  $userid = $matches[3];
 300              } else {
 301                  continue;
 302              }
 303  
 304              $gradeitem = grade_item::fetch([
 305                  'id' => $itemid, 'courseid' => $this->courseid
 306              ]);
 307  
 308              if (preg_match('/^old[oe]{1}/', $varname)) {
 309                  $elementname = preg_replace('/^old/', '', $varname);
 310                  if (!isset($data->$elementname)) {
 311                      // Decrease the progress because we've increased the
 312                      // size of the array we are iterating through.
 313                      $progress--;
 314                      $data->$elementname = false;
 315                  }
 316              }
 317  
 318              if (!in_array($matches[1], $fields)) {
 319                  continue;
 320              }
 321  
 322              if (!$gradeitem) {
 323                  continue;
 324              }
 325  
 326              $grade = $this->fetch_grade_or_default($gradeitem, $userid);
 327  
 328              $classname = '\\gradereport_singleview\\local\\ui\\' . $matches[1];
 329              $element = new $classname($grade);
 330  
 331              $name = $element->get_name();
 332              $oldname = "old$name";
 333  
 334              $posted = $data->$name;
 335  
 336              $format = $element->determine_format();
 337  
 338              if ($format->is_textbox() and trim($data->$name) === '') {
 339                  $data->$name = null;
 340              }
 341  
 342              // Same value; skip.
 343              if (isset($data->$oldname) && $data->$oldname == $posted) {
 344                  continue;
 345              }
 346  
 347              // If the user submits Exclude grade elements without the proper.
 348              // permissions then we should refuse to update.
 349              if ($matches[1] === 'exclude' && !has_capability('moodle/grade:manage', $this->context)){
 350                  $warnings[] = get_string('nopermissions', 'error', get_string('grade:manage', 'role'));
 351                  continue;
 352              }
 353  
 354              $msg = $element->set($posted);
 355              // Value to check against our list of matchelements to ignore.
 356              $check = explode('_', $varname, 2);
 357  
 358              // Optional type.
 359              if (!empty($msg)) {
 360                  $warnings[] = $msg;
 361                  if ($element instanceof \gradereport_singleview\local\ui\finalgrade) {
 362                      // Add this value to this list so that the override object that is coming next will also be skipped.
 363                      $ignorevalues[$check[1]] = $check[1];
 364                      // This item wasn't changed so don't add to the changecount.
 365                      continue;
 366                  }
 367              }
 368              // Check to see if this value has already been skipped.
 369              if (array_key_exists($check[1], $ignorevalues)) {
 370                  continue;
 371              }
 372              if (preg_match('/_(\d+)_(\d+)/', $varname, $matchelement)) {
 373                  $changecount[$matchelement[0]] = 1;
 374              }
 375          }
 376  
 377          // Some post-processing.
 378          $eventdata = new stdClass;
 379          $eventdata->warnings = $warnings;
 380          $eventdata->post_data = $data;
 381          $eventdata->instance = $this;
 382          $eventdata->changecount = $changecount;
 383  
 384          $progressbar->end_html();
 385  
 386          return $eventdata;
 387      }
 388  
 389      /**
 390       * By default, there are no options.
 391       * @return array
 392       */
 393      public function options(): array {
 394          return [];
 395      }
 396  
 397      /**
 398       * Should we show the group selector?
 399       * @return bool
 400       */
 401      public function display_group_selector(): bool {
 402          return true;
 403      }
 404  
 405      /**
 406       * Should we show the next prev selector?
 407       * @return bool
 408       */
 409      public function supports_next_prev(): bool {
 410          return true;
 411      }
 412  
 413      /**
 414       * Load a valid list of users for this gradebook as the screen "items".
 415       *
 416       * @deprecated since Moodle 4.3
 417       * @return array A list of enroled users.
 418       */
 419      protected function load_users(): array {
 420          debugging('The function ' . __FUNCTION__ . '() is deprecated. Please use grade_report::get_gradable_users() instead.',
 421              DEBUG_DEVELOPER);
 422  
 423          return grade_report::get_gradable_users($this->courseid, $this->groupid);
 424      }
 425  
 426      /**
 427       * Allow selection of number of items to display per page.
 428       * @return string
 429       */
 430      public function perpage_select(): string {
 431          global $PAGE, $OUTPUT;
 432  
 433          $url = new moodle_url($PAGE->url);
 434          $numusers = count($this->items);
 435          // Print per-page dropdown.
 436          $pagingoptions = self::$validperpage;
 437          if ($this->perpage) {
 438              $pagingoptions[] = $this->perpage; // To make sure the current preference is within the options.
 439          }
 440          $pagingoptions = array_unique($pagingoptions);
 441          sort($pagingoptions);
 442          $pagingoptions = array_combine($pagingoptions, $pagingoptions);
 443          if ($numusers > self::$maxperpage) {
 444              $pagingoptions['0'] = self::$maxperpage;
 445          } else {
 446              $pagingoptions['0'] = get_string('all');
 447          }
 448  
 449          $perpagedata = [
 450              'baseurl' => $url->out(false),
 451              'options' => []
 452          ];
 453          foreach ($pagingoptions as $key => $name) {
 454              $perpagedata['options'][] = [
 455                  'name' => $name,
 456                  'value' => $key,
 457                  'selected' => $key == $this->perpage,
 458              ];
 459          }
 460  
 461          // The number of students per page is always limited even if it is claimed to be unlimited.
 462          $this->perpage = $this->perpage ?: self::$maxperpage;
 463          $perpagedata['pagingbar'] = $this->pager();
 464          return $OUTPUT->render_from_template('gradereport_singleview/perpage', $perpagedata);;
 465      }
 466  }