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