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] [Versions 401 and 402] [Versions 401 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      /**
  91       * List of allowed values for 'perpage' setting
  92       * @var array $validperpage
  93       */
  94      protected static $validperpage = [20, 50, 100, 200, 400, 1000, 5000];
  95  
  96      /**
  97       * Constructor
  98       *
  99       * @param int $courseid The course id
 100       * @param int|null $itemid The item id
 101       * @param int|null $groupid The group id
 102       */
 103      public function __construct(int $courseid, ?int $itemid, ?int $groupid = null) {
 104          global $DB;
 105  
 106          $this->courseid = $courseid;
 107          $this->itemid = $itemid;
 108          $this->groupid = $groupid;
 109  
 110          $this->context = context_course::instance($this->courseid);
 111          $this->course = $DB->get_record('course', ['id' => $courseid]);
 112  
 113          $this->page = optional_param('page', 0, PARAM_INT);
 114  
 115          $cache = \cache::make_from_params(\cache_store::MODE_SESSION, 'gradereport_singleview', 'perpage');
 116          $perpage = optional_param('perpage', null, PARAM_INT);
 117          if (!in_array($perpage, self::$validperpage)) {
 118              // Get from cache.
 119              $perpage = $cache->get(get_class($this));
 120          } else {
 121              // Save to cache.
 122              $cache->set(get_class($this), $perpage);
 123          }
 124          if ($perpage) {
 125              $this->perpage = $perpage;
 126          } else {
 127              $this->perpage = 100;
 128          }
 129  
 130          $this->init(empty($itemid));
 131      }
 132  
 133      /**
 134       * Cache the grade_structure class
 135       */
 136      public function setup_structure() {
 137          $this->structure = new grade_structure();
 138          $this->structure->modinfo = get_fast_modinfo($this->course);
 139      }
 140  
 141      /**
 142       * Create a nice link from a thing (user or grade_item).
 143       *
 144       * @param string $screen
 145       * @param int $itemid
 146       * @param bool|null $display Should we wrap this in an anchor ?
 147       * @return string The link
 148       */
 149      public function format_link(string $screen, int $itemid, bool $display = null): string {
 150          $url = new moodle_url('/grade/report/singleview/index.php', [
 151              'id' => $this->courseid,
 152              'item' => $screen,
 153              'itemid' => $itemid,
 154              'group' => $this->groupid,
 155          ]);
 156  
 157          if ($display) {
 158              return html_writer::link($url, $display);
 159          } else {
 160              return $url;
 161          }
 162      }
 163  
 164      /**
 165       * Get the grade_grade
 166       *
 167       * @param grade_item $item The grade_item
 168       * @param int $userid The user id
 169       * @return grade_grade
 170       */
 171      public function fetch_grade_or_default(grade_item $item, int $userid): grade_grade {
 172          $grade = grade_grade::fetch([
 173              'itemid' => $item->id, 'userid' => $userid
 174          ]);
 175  
 176          if (!$grade) {
 177              $default = new stdClass;
 178  
 179              $default->userid = $userid;
 180              $default->itemid = $item->id;
 181              $default->feedback = '';
 182  
 183              $grade = new grade_grade($default, false);
 184          }
 185  
 186          $grade->grade_item = $item;
 187  
 188          return $grade;
 189      }
 190  
 191      /**
 192       * Get the default heading for the screen.
 193       *
 194       * @return string
 195       */
 196      public function heading(): string {
 197          return get_string('entrypage', 'gradereport_singleview');
 198      }
 199  
 200      /**
 201       * Override this to init the screen.
 202       *
 203       * @param boolean $selfitemisempty True if no item has been selected yet.
 204       */
 205      abstract public function init(bool $selfitemisempty = false);
 206  
 207      /**
 208       * Get the type of items in the list.
 209       *
 210       * @return null|string
 211       */
 212      abstract public function item_type(): ?string;
 213  
 214      /**
 215       * Get the entire screen as a string.
 216       *
 217       * @return string
 218       */
 219      abstract public function html(): string;
 220  
 221      /**
 222       * Does this screen support paging?
 223       *
 224       * @return bool
 225       */
 226      public function supports_paging(): bool {
 227          return true;
 228      }
 229  
 230      /**
 231       * Default pager
 232       *
 233       * @return string
 234       */
 235      public function pager(): string {
 236          return '';
 237      }
 238  
 239      /**
 240       * Initialise the js for this screen.
 241       */
 242      public function js() {
 243          global $PAGE;
 244  
 245          $module = [
 246              'name' => 'gradereport_singleview',
 247              'fullpath' => '/grade/report/singleview/js/singleview.js',
 248              'requires' => ['base', 'dom', 'event', 'event-simulate', 'io-base']
 249          ];
 250  
 251          $PAGE->requires->strings_for_js(['overridenoneconfirm', 'removeoverride', 'removeoverridesave'],
 252              'gradereport_singleview');
 253          $PAGE->requires->js_init_call('M.gradereport_singleview.init', [], false, $module);
 254      }
 255  
 256      /**
 257       * Process the data from a form submission.
 258       *
 259       * @param array|object $data
 260       * @return stdClass of warnings
 261       */
 262      public function process($data): stdClass {
 263          $warnings = [];
 264  
 265          $fields = $this->definition();
 266  
 267          // Avoiding execution timeouts when updating
 268          // a large amount of grades.
 269          $progress = 0;
 270          $progressbar = new \core\progress\display_if_slow();
 271          $progressbar->start_html();
 272          $progressbar->start_progress(get_string('savegrades', 'gradereport_singleview'), count((array) $data) - 1);
 273          $changecount = [];
 274          // This array is used to determine if the override should be excluded from being counted as a change.
 275          $ignorevalues = [];
 276  
 277          foreach ($data as $varname => $throw) {
 278              $progressbar->progress($progress);
 279              $progress++;
 280              if (preg_match("/(\w+)_(\d+)_(\d+)/", $varname, $matches)) {
 281                  $itemid = $matches[2];
 282                  $userid = $matches[3];
 283              } else {
 284                  continue;
 285              }
 286  
 287              $gradeitem = grade_item::fetch([
 288                  'id' => $itemid, 'courseid' => $this->courseid
 289              ]);
 290  
 291              if (preg_match('/^old[oe]{1}/', $varname)) {
 292                  $elementname = preg_replace('/^old/', '', $varname);
 293                  if (!isset($data->$elementname)) {
 294                      // Decrease the progress because we've increased the
 295                      // size of the array we are iterating through.
 296                      $progress--;
 297                      $data->$elementname = false;
 298                  }
 299              }
 300  
 301              if (!in_array($matches[1], $fields)) {
 302                  continue;
 303              }
 304  
 305              if (!$gradeitem) {
 306                  continue;
 307              }
 308  
 309              $grade = $this->fetch_grade_or_default($gradeitem, $userid);
 310  
 311              $classname = '\\gradereport_singleview\\local\\ui\\' . $matches[1];
 312              $element = new $classname($grade);
 313  
 314              $name = $element->get_name();
 315              $oldname = "old$name";
 316  
 317              $posted = $data->$name;
 318  
 319              $format = $element->determine_format();
 320  
 321              if ($format->is_textbox() and trim($data->$name) === '') {
 322                  $data->$name = null;
 323              }
 324  
 325              // Same value; skip.
 326              if (isset($data->$oldname) && $data->$oldname == $posted) {
 327                  continue;
 328              }
 329  
 330              // If the user submits Exclude grade elements without the proper.
 331              // permissions then we should refuse to update.
 332              if ($matches[1] === 'exclude' && !has_capability('moodle/grade:manage', $this->context)){
 333                  $warnings[] = get_string('nopermissions', 'error', get_string('grade:manage', 'role'));
 334                  continue;
 335              }
 336  
 337              $msg = $element->set($posted);
 338              // Value to check against our list of matchelements to ignore.
 339              $check = explode('_', $varname, 2);
 340  
 341              // Optional type.
 342              if (!empty($msg)) {
 343                  $warnings[] = $msg;
 344                  if ($element instanceof \gradereport_singleview\local\ui\finalgrade) {
 345                      // Add this value to this list so that the override object that is coming next will also be skipped.
 346                      $ignorevalues[$check[1]] = $check[1];
 347                      // This item wasn't changed so don't add to the changecount.
 348                      continue;
 349                  }
 350              }
 351              // Check to see if this value has already been skipped.
 352              if (array_key_exists($check[1], $ignorevalues)) {
 353                  continue;
 354              }
 355              if (preg_match('/_(\d+)_(\d+)/', $varname, $matchelement)) {
 356                  $changecount[$matchelement[0]] = 1;
 357              }
 358          }
 359  
 360          // Some post-processing.
 361          $eventdata = new stdClass;
 362          $eventdata->warnings = $warnings;
 363          $eventdata->post_data = $data;
 364          $eventdata->instance = $this;
 365          $eventdata->changecount = $changecount;
 366  
 367          $progressbar->end_html();
 368  
 369          return $eventdata;
 370      }
 371  
 372      /**
 373       * By default, there are no options.
 374       * @return array
 375       */
 376      public function options(): array {
 377          return [];
 378      }
 379  
 380      /**
 381       * Should we show the group selector?
 382       * @return bool
 383       */
 384      public function display_group_selector(): bool {
 385          return true;
 386      }
 387  
 388      /**
 389       * Should we show the next prev selector?
 390       * @return bool
 391       */
 392      public function supports_next_prev(): bool {
 393          return true;
 394      }
 395  
 396      /**
 397       * Load a valid list of users for this gradebook as the screen "items".
 398       * @return array $users A list of enroled users.
 399       */
 400      protected function load_users(): array {
 401          global $CFG;
 402  
 403          // Create a graded_users_iterator because it will properly check the groups etc.
 404          $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
 405          $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
 406          $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $this->context);
 407  
 408          require_once($CFG->dirroot.'/grade/lib.php');
 409          $gui = new \graded_users_iterator($this->course, null, $this->groupid);
 410          $gui->require_active_enrolment($showonlyactiveenrol);
 411          $gui->init();
 412  
 413          // Flatten the users.
 414          $users = [];
 415          while ($user = $gui->next_user()) {
 416              $users[$user->user->id] = $user->user;
 417          }
 418          $gui->close();
 419          return $users;
 420      }
 421  
 422      /**
 423       * Allow selection of number of items to display per page.
 424       * @return string
 425       */
 426      public function perpage_select(): string {
 427          global $PAGE, $OUTPUT;
 428  
 429          $options = array_combine(self::$validperpage, self::$validperpage);
 430  
 431          $url = new moodle_url($PAGE->url);
 432          $url->remove_params(['page', 'perpage']);
 433  
 434          $out = '';
 435          $select = new \single_select($url, 'perpage', $options, $this->perpage, null, 'perpagechanger');
 436          $select->label = get_string('itemsperpage', 'gradereport_singleview');
 437          $out .= $OUTPUT->render($select);
 438  
 439          return $out;
 440      }
 441  }