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   * The user screen.
  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 grade_seq;
  28  use gradereport_singleview;
  29  use moodle_url;
  30  use pix_icon;
  31  use html_writer;
  32  use gradereport_singleview\local\ui\range;
  33  use gradereport_singleview\local\ui\bulk_insert;
  34  use grade_item;
  35  use grade_grade;
  36  use stdClass;
  37  
  38  defined('MOODLE_INTERNAL') || die;
  39  
  40  /**
  41   * The user screen.
  42   *
  43   * @package   gradereport_singleview
  44   * @copyright 2014 Moodle Pty Ltd (http://moodle.com)
  45   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  46   */
  47  class user extends tablelike implements selectable_items {
  48  
  49      /** @var array $categories A cache for grade_item categories */
  50      private $categories = [];
  51  
  52      /** @var int $requirespaging Do we have more items than the paging limit? */
  53      private $requirespaging = true;
  54  
  55      /** @var array get a valid user.  */
  56      public $item = [];
  57  
  58      /**
  59       * Get the label for the select box that chooses items for this page.
  60       * @return string
  61       */
  62      public function select_label(): string {
  63          return get_string('selectgrade', 'gradereport_singleview');
  64      }
  65  
  66      /**
  67       * Get the description for the screen.
  68       *
  69       * @return string
  70       */
  71      public function description(): string {
  72          return get_string('gradeitems', 'grades');
  73      }
  74  
  75      /**
  76       * Convert the list of items to a list of options.
  77       *
  78       * @return array
  79       */
  80      public function options(): array {
  81          $result = [];
  82          foreach ($this->items as $itemid => $item) {
  83              $result[$itemid] = $item->get_name();
  84          }
  85          return $result;
  86      }
  87  
  88      /**
  89       * Get the type of items on this screen.
  90       *
  91       * @return string
  92       */
  93      public function item_type(): string {
  94          return 'grade';
  95      }
  96  
  97      /**
  98       * Init the screen
  99       *
 100       * @param bool $selfitemisempty Have we selected an item yet?
 101       */
 102      public function init($selfitemisempty = false) {
 103  
 104          if (!$selfitemisempty) {
 105              $validusers = \grade_report::get_gradable_users($this->courseid, $this->groupid);
 106              if (!isset($validusers[$this->itemid])) {
 107                  // If the passed user id is not valid, show the first user from the list instead.
 108                  $this->item = reset($validusers);
 109                  $this->itemid = $this->item->id;
 110              } else {
 111                  $this->item = $validusers[$this->itemid];
 112              }
 113          }
 114  
 115          $seq = new grade_seq($this->courseid, true);
 116  
 117          $this->items = [];
 118          foreach ($seq->items as $itemid => $item) {
 119              if (grade::filter($item)) {
 120                  $this->items[$itemid] = $item;
 121              }
 122          }
 123  
 124          // If we change perpage on pagination we might end up with a page that doesn't exist.
 125          if ($this->perpage) {
 126              $numpages = intval(count($this->items) / $this->perpage) + 1;
 127              if ($numpages <= $this->page) {
 128                  $this->page = 0;
 129              }
 130          } else {
 131              $this->page = 0;
 132          }
 133  
 134          $this->requirespaging = count($this->items) > $this->perpage;
 135  
 136          $this->setup_structure();
 137  
 138          $this->definition = [
 139              'finalgrade', 'feedback', 'override', 'exclude'
 140          ];
 141          $this->set_headers($this->original_headers());
 142      }
 143  
 144      /**
 145       * Get the list of headers for the table.
 146       *
 147       * @return array List of headers
 148       */
 149      public function original_headers(): array {
 150          return [
 151              get_string('assessmentname', 'gradereport_singleview'),
 152              '', // For filter icon.
 153              get_string('gradecategory', 'grades'),
 154              get_string('grade', 'grades'),
 155              get_string('range', 'grades'),
 156              get_string('feedback', 'grades'),
 157              get_string('override', 'gradereport_singleview'),
 158              get_string('exclude', 'gradereport_singleview'),
 159          ];
 160      }
 161  
 162      /**
 163       * Format each row of the table.
 164       *
 165       * @param grade_item $item
 166       * @return array
 167       */
 168      public function format_line($item): array {
 169          global $OUTPUT;
 170  
 171          $grade = $this->fetch_grade_or_default($item, $this->item->id);
 172          $gradestatus = '';
 173  
 174          $context = [
 175              'hidden' => $grade->is_hidden(),
 176              'locked' => $grade->is_locked(),
 177          ];
 178  
 179          if (in_array(true, $context)) {
 180              $context['classes'] = 'gradestatus';
 181              $gradestatus = $OUTPUT->render_from_template('core_grades/status_icons', $context);
 182          }
 183  
 184          // Create a fake gradetreeitem so we can call get_element_header().
 185          // The type logic below is from grade_category->_get_children_recursion().
 186          $gradetreeitem = [];
 187  
 188          $type = in_array($item->itemtype, ['course', 'category']) ? "{$item->itemtype}item" : 'item';
 189          $gradetreeitem['type'] = $type;
 190          $gradetreeitem['object'] = $item;
 191          $gradetreeitem['userid'] = $this->item->id;
 192  
 193          $itemname = $this->structure->get_element_header($gradetreeitem, true, false, false, false, true);
 194          $grade->label = $item->get_name();
 195  
 196          $formatteddefinition = $this->format_definition($grade);
 197  
 198          $itemicon = html_writer::div($this->format_icon($item), 'mr-1');
 199          $itemtype = \html_writer::span($this->structure->get_element_type_string($gradetreeitem),
 200              'd-block text-uppercase small dimmed_text');
 201          // If a behat test site is running avoid outputting the information about the type of the grade item.
 202          // This additional information currently causes issues in behat particularly with the existing xpath used to
 203          // interact with table elements.
 204          if (!defined('BEHAT_SITE_RUNNING')) {
 205              $itemcontent = html_writer::div($itemtype . $itemname);
 206          } else {
 207              $itemcontent = html_writer::div($itemname);
 208          }
 209  
 210          $line = [
 211              html_writer::div($itemicon . $itemcontent, "{$type} d-flex align-items-center"),
 212              $this->get_item_action_menu($item),
 213              $this->category($item),
 214              $formatteddefinition['finalgrade'] . $gradestatus,
 215              new range($item),
 216              $formatteddefinition['feedback'],
 217              $formatteddefinition['override'],
 218              $formatteddefinition['exclude'],
 219          ];
 220          $lineclasses = [
 221              'gradeitem',
 222              'action',
 223              'category',
 224              'grade',
 225              'range',
 226          ];
 227  
 228          $outputline = [];
 229          $i = 0;
 230          foreach ($line as $key => $value) {
 231              $cell = new \html_table_cell($value);
 232              if ($isheader = $i == 0) {
 233                  $cell->header = $isheader;
 234                  $cell->scope = "row";
 235              }
 236              if (array_key_exists($key, $lineclasses)) {
 237                  $cell->attributes['class'] = $lineclasses[$key];
 238              }
 239              $outputline[] = $cell;
 240              $i++;
 241          }
 242  
 243          return $outputline;
 244      }
 245  
 246      /**
 247       * Helper to get the icon for an item.
 248       *
 249       * @param grade_item $item
 250       * @return string
 251       */
 252      private function format_icon($item): string {
 253          $element = ['type' => 'item', 'object' => $item];
 254          return $this->structure->get_element_icon($element);
 255      }
 256  
 257      /**
 258       * Return the action menu HTML for the grade item.
 259       *
 260       * @param grade_item $item
 261       * @return mixed
 262       */
 263      private function get_item_action_menu(grade_item $item) {
 264          global $OUTPUT;
 265  
 266          $menuitems = [];
 267          $url = new moodle_url($this->format_link('grade', $item->id));
 268          $title = get_string('showallgrades', 'core_grades');
 269          $menuitems[] = new \action_menu_link_secondary($url, null, $title);
 270          $menu = new \action_menu($menuitems);
 271          $icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
 272          $extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
 273          $menu->set_menu_trigger($icon, $extraclasses);
 274          $menu->set_menu_left();
 275          $menu->set_boundary('window');
 276  
 277          return $OUTPUT->render($menu);
 278      }
 279  
 280      /**
 281       * Helper to get the category for an item.
 282       *
 283       * @param grade_item $item
 284       * @return string
 285       */
 286      private function category(grade_item $item): string {
 287          global $DB;
 288  
 289          if (empty($item->categoryid)) {
 290  
 291              if ($item->itemtype == 'course') {
 292                  return $this->course->fullname;
 293              }
 294  
 295              $params = ['id' => $item->iteminstance];
 296              $elem = $DB->get_record('grade_categories', $params);
 297  
 298              return $elem->fullname;
 299          }
 300  
 301          if (!isset($this->categories[$item->categoryid])) {
 302              $category = $item->get_parent_category();
 303  
 304              $this->categories[$category->id] = $category;
 305          }
 306  
 307          return $this->categories[$item->categoryid]->get_name();
 308      }
 309  
 310      /**
 311       * Get the heading for the page.
 312       *
 313       * @return string
 314       */
 315      public function heading(): string {
 316          global $PAGE;
 317          $headinglangstring = $PAGE->user_is_editing() ? 'gradeuseredit' : 'gradeuser';
 318          return get_string($headinglangstring, 'gradereport_singleview', fullname($this->item));
 319      }
 320  
 321      /**
 322       * Get the summary for this table.
 323       *
 324       * @return string
 325       */
 326      public function summary(): string {
 327          return get_string('summaryuser', 'gradereport_singleview');
 328      }
 329  
 330      /**
 331       * Default pager
 332       *
 333       * @return string
 334       */
 335      public function pager(): string {
 336          global $OUTPUT;
 337  
 338          if (!$this->supports_paging()) {
 339              return '';
 340          }
 341  
 342          return $OUTPUT->paging_bar(
 343              count($this->items), $this->page, $this->perpage,
 344              new moodle_url('/grade/report/singleview/index.php', [
 345                  'perpage' => $this->perpage,
 346                  'id' => $this->courseid,
 347                  'group' => $this->groupid,
 348                  'itemid' => $this->itemid,
 349                  'item' => 'user'
 350              ])
 351          );
 352      }
 353  
 354      /**
 355       * Does this page require paging?
 356       *
 357       * @return bool
 358       */
 359      public function supports_paging(): bool {
 360          return $this->requirespaging;
 361      }
 362  
 363  
 364      /**
 365       * Process the data from the form.
 366       *
 367       * @param array $data
 368       * @return stdClass of warnings
 369       */
 370      public function process($data): stdClass {
 371          $bulk = new bulk_insert($this->item);
 372          // Bulk insert messages the data to be passed in
 373          // ie: for all grades of empty grades apply the specified value.
 374          if ($bulk->is_applied($data)) {
 375              $filter = $bulk->get_type($data);
 376              $insertvalue = $bulk->get_insert_value($data);
 377  
 378              $userid = $this->item->id;
 379              foreach ($this->items as $gradeitemid => $gradeitem) {
 380                  $null = $gradeitem->gradetype == GRADE_TYPE_SCALE ? -1 : '';
 381                  $field = "finalgrade_{$gradeitem->id}_{$this->itemid}";
 382                  if (isset($data->$field)) {
 383                      continue;
 384                  }
 385  
 386                  $oldfinalgradefield = "oldfinalgrade_{$gradeitem->id}_{$this->itemid}";
 387                  // Bulk grade changes for all grades need to be processed and shouldn't be skipped if they had a previous grade.
 388                  if ($gradeitem->is_course_item() || ($filter != 'all' && !empty($data->$oldfinalgradefield))) {
 389                      if ($gradeitem->is_course_item()) {
 390                          // The course total should not be overridden.
 391                          unset($data->$field);
 392                          unset($data->oldfinalgradefield);
 393                          $oldoverride = "oldoverride_{$gradeitem->id}_{$this->itemid}";
 394                          unset($data->$oldoverride);
 395                          $oldfeedback = "oldfeedback_{$gradeitem->id}_{$this->itemid}";
 396                          unset($data->$oldfeedback);
 397                      }
 398                      continue;
 399                  }
 400                  $grade = grade_grade::fetch([
 401                      'itemid' => $gradeitemid,
 402                      'userid' => $userid
 403                  ]);
 404  
 405                  $data->$field = empty($grade) ? $null : $grade->finalgrade;
 406                  $data->{"old$field"} = $data->$field;
 407              }
 408  
 409              foreach ($data as $varname => $value) {
 410                  if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
 411                      // If we've selected overriding all grades.
 412                      if ($filter == 'all') {
 413                          $override = "override_{$matches[1]}_{$matches[2]}";
 414                          $data->$override = '1';
 415                      }
 416                  }
 417                  if (!preg_match('/^finalgrade_(\d+)_(\d+)/', $varname, $matches)) {
 418                      continue;
 419                  }
 420  
 421                  $gradeitem = grade_item::fetch([
 422                      'courseid' => $this->courseid,
 423                      'id' => $matches[1],
 424                  ]);
 425  
 426                  $isscale = ($gradeitem->gradetype == GRADE_TYPE_SCALE);
 427  
 428                  $empties = (trim($value ?? '') === '' || ($isscale && $value == -1));
 429  
 430                  if ($filter == 'all' || $empties) {
 431                      $data->$varname = ($isscale && empty($insertvalue)) ? -1 : $insertvalue;
 432                  }
 433              }
 434          }
 435          return parent::process($data);
 436      }
 437  }