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   * 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          $this->requirespaging = count($this->items) > $this->perpage;
 125  
 126          $this->setup_structure();
 127  
 128          $this->definition = [
 129              'finalgrade', 'feedback', 'override', 'exclude'
 130          ];
 131          $this->set_headers($this->original_headers());
 132      }
 133  
 134      /**
 135       * Get the list of headers for the table.
 136       *
 137       * @return array List of headers
 138       */
 139      public function original_headers(): array {
 140          return [
 141              get_string('assessmentname', 'gradereport_singleview'),
 142              '', // For filter icon.
 143              get_string('gradecategory', 'grades'),
 144              get_string('grade', 'grades'),
 145              get_string('range', 'grades'),
 146              get_string('feedback', 'grades'),
 147              get_string('override', 'gradereport_singleview'),
 148              get_string('exclude', 'gradereport_singleview'),
 149          ];
 150      }
 151  
 152      /**
 153       * Format each row of the table.
 154       *
 155       * @param grade_item $item
 156       * @return array
 157       */
 158      public function format_line($item): array {
 159          global $OUTPUT;
 160  
 161          $grade = $this->fetch_grade_or_default($item, $this->item->id);
 162          $lockicon = '';
 163  
 164          $lockeditem = $lockeditemgrade = 0;
 165          if (!empty($grade->locked)) {
 166              $lockeditem = 1;
 167          }
 168          if (!empty($grade->grade_item->locked)) {
 169              $lockeditemgrade = 1;
 170          }
 171          // Check both grade and grade item.
 172          if ($lockeditem || $lockeditemgrade) {
 173               $lockicon = $OUTPUT->pix_icon('t/locked', 'grade is locked', 'moodle', ['class' => 'ml-3']);
 174          }
 175  
 176          // Create a fake gradetreeitem so we can call get_element_header().
 177          // The type logic below is from grade_category->_get_children_recursion().
 178          $gradetreeitem = [];
 179  
 180          $type = in_array($item->itemtype, ['course', 'category']) ? "{$item->itemtype}item" : 'item';
 181          $gradetreeitem['type'] = $type;
 182          $gradetreeitem['object'] = $item;
 183          $gradetreeitem['userid'] = $this->item->id;
 184  
 185          $itemname = $this->structure->get_element_header($gradetreeitem, true, false, false, false, true);
 186          $grade->label = $item->get_name();
 187  
 188          $formatteddefinition = $this->format_definition($grade);
 189  
 190          $itemicon = html_writer::div($this->format_icon($item), 'mr-1');
 191          $itemtype = \html_writer::span($this->structure->get_element_type_string($gradetreeitem),
 192              'd-block text-uppercase small dimmed_text');
 193          // If a behat test site is running avoid outputting the information about the type of the grade item.
 194          // This additional information currently causes issues in behat particularly with the existing xpath used to
 195          // interact with table elements.
 196          if (!defined('BEHAT_SITE_RUNNING')) {
 197              $itemcontent = html_writer::div($itemtype . $itemname);
 198          } else {
 199              $itemcontent = html_writer::div($itemname);
 200          }
 201  
 202          $line = [
 203              html_writer::div($itemicon . $itemcontent .  $lockicon, "{$type} d-flex align-items-center"),
 204              $this->get_item_action_menu($item),
 205              $this->category($item),
 206              $formatteddefinition['finalgrade'],
 207              new range($item),
 208              $formatteddefinition['feedback'],
 209              $formatteddefinition['override'],
 210              $formatteddefinition['exclude'],
 211          ];
 212          $lineclasses = [
 213              'gradeitem',
 214              'action',
 215              'category',
 216              'grade',
 217              'range',
 218          ];
 219  
 220          $outputline = [];
 221          $i = 0;
 222          foreach ($line as $key => $value) {
 223              $cell = new \html_table_cell($value);
 224              if ($isheader = $i == 0) {
 225                  $cell->header = $isheader;
 226                  $cell->scope = "row";
 227              }
 228              if (array_key_exists($key, $lineclasses)) {
 229                  $cell->attributes['class'] = $lineclasses[$key];
 230              }
 231              $outputline[] = $cell;
 232              $i++;
 233          }
 234  
 235          return $outputline;
 236      }
 237  
 238      /**
 239       * Helper to get the icon for an item.
 240       *
 241       * @param grade_item $item
 242       * @return string
 243       */
 244      private function format_icon($item): string {
 245          $element = ['type' => 'item', 'object' => $item];
 246          return $this->structure->get_element_icon($element);
 247      }
 248  
 249      /**
 250       * Return the action menu HTML for the grade item.
 251       *
 252       * @param grade_item $item
 253       * @return mixed
 254       */
 255      private function get_item_action_menu(grade_item $item) {
 256          global $OUTPUT;
 257  
 258          $menuitems = [];
 259          $url = new moodle_url($this->format_link('grade', $item->id));
 260          $title = get_string('showallgrades', 'core_grades');
 261          $menuitems[] = new \action_menu_link_secondary($url, null, $title);
 262          $menu = new \action_menu($menuitems);
 263          $icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
 264          $extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
 265          $menu->set_menu_trigger($icon, $extraclasses);
 266          $menu->set_menu_left();
 267          $menu->set_boundary('window');
 268  
 269          return $OUTPUT->render($menu);
 270      }
 271  
 272      /**
 273       * Helper to get the category for an item.
 274       *
 275       * @param grade_item $item
 276       * @return string
 277       */
 278      private function category(grade_item $item): string {
 279          global $DB;
 280  
 281          if (empty($item->categoryid)) {
 282  
 283              if ($item->itemtype == 'course') {
 284                  return $this->course->fullname;
 285              }
 286  
 287              $params = ['id' => $item->iteminstance];
 288              $elem = $DB->get_record('grade_categories', $params);
 289  
 290              return $elem->fullname;
 291          }
 292  
 293          if (!isset($this->categories[$item->categoryid])) {
 294              $category = $item->get_parent_category();
 295  
 296              $this->categories[$category->id] = $category;
 297          }
 298  
 299          return $this->categories[$item->categoryid]->get_name();
 300      }
 301  
 302      /**
 303       * Get the heading for the page.
 304       *
 305       * @return string
 306       */
 307      public function heading(): string {
 308          global $PAGE;
 309          $headinglangstring = $PAGE->user_is_editing() ? 'gradeuseredit' : 'gradeuser';
 310          return get_string($headinglangstring, 'gradereport_singleview', fullname($this->item));
 311      }
 312  
 313      /**
 314       * Get the summary for this table.
 315       *
 316       * @return string
 317       */
 318      public function summary(): string {
 319          return get_string('summaryuser', 'gradereport_singleview');
 320      }
 321  
 322      /**
 323       * Default pager
 324       *
 325       * @return string
 326       */
 327      public function pager(): string {
 328          global $OUTPUT;
 329  
 330          if (!$this->supports_paging()) {
 331              return '';
 332          }
 333  
 334          return $OUTPUT->paging_bar(
 335              count($this->items), $this->page, $this->perpage,
 336              new moodle_url('/grade/report/singleview/index.php', [
 337                  'perpage' => $this->perpage,
 338                  'id' => $this->courseid,
 339                  'group' => $this->groupid,
 340                  'itemid' => $this->itemid,
 341                  'item' => 'user'
 342              ])
 343          );
 344      }
 345  
 346      /**
 347       * Does this page require paging?
 348       *
 349       * @return bool
 350       */
 351      public function supports_paging(): bool {
 352          return $this->requirespaging;
 353      }
 354  
 355  
 356      /**
 357       * Process the data from the form.
 358       *
 359       * @param array $data
 360       * @return stdClass of warnings
 361       */
 362      public function process($data): stdClass {
 363          $bulk = new bulk_insert($this->item);
 364          // Bulk insert messages the data to be passed in
 365          // ie: for all grades of empty grades apply the specified value.
 366          if ($bulk->is_applied($data)) {
 367              $filter = $bulk->get_type($data);
 368              $insertvalue = $bulk->get_insert_value($data);
 369  
 370              $userid = $this->item->id;
 371              foreach ($this->items as $gradeitemid => $gradeitem) {
 372                  $null = $gradeitem->gradetype == GRADE_TYPE_SCALE ? -1 : '';
 373                  $field = "finalgrade_{$gradeitem->id}_{$this->itemid}";
 374                  if (isset($data->$field)) {
 375                      continue;
 376                  }
 377  
 378                  $oldfinalgradefield = "oldfinalgrade_{$gradeitem->id}_{$this->itemid}";
 379                  // Bulk grade changes for all grades need to be processed and shouldn't be skipped if they had a previous grade.
 380                  if ($gradeitem->is_course_item() || ($filter != 'all' && !empty($data->$oldfinalgradefield))) {
 381                      if ($gradeitem->is_course_item()) {
 382                          // The course total should not be overridden.
 383                          unset($data->$field);
 384                          unset($data->oldfinalgradefield);
 385                          $oldoverride = "oldoverride_{$gradeitem->id}_{$this->itemid}";
 386                          unset($data->$oldoverride);
 387                          $oldfeedback = "oldfeedback_{$gradeitem->id}_{$this->itemid}";
 388                          unset($data->$oldfeedback);
 389                      }
 390                      continue;
 391                  }
 392                  $grade = grade_grade::fetch([
 393                      'itemid' => $gradeitemid,
 394                      'userid' => $userid
 395                  ]);
 396  
 397                  $data->$field = empty($grade) ? $null : $grade->finalgrade;
 398                  $data->{"old$field"} = $data->$field;
 399              }
 400  
 401              foreach ($data as $varname => $value) {
 402                  if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
 403                      // If we've selected overriding all grades.
 404                      if ($filter == 'all') {
 405                          $override = "override_{$matches[1]}_{$matches[2]}";
 406                          $data->$override = '1';
 407                      }
 408                  }
 409                  if (!preg_match('/^finalgrade_(\d+)_(\d+)/', $varname, $matches)) {
 410                      continue;
 411                  }
 412  
 413                  $gradeitem = grade_item::fetch([
 414                      'courseid' => $this->courseid,
 415                      'id' => $matches[1],
 416                  ]);
 417  
 418                  $isscale = ($gradeitem->gradetype == GRADE_TYPE_SCALE);
 419  
 420                  $empties = (trim($value ?? '') === '' || ($isscale && $value == -1));
 421  
 422                  if ($filter == 'all' || $empties) {
 423                      $data->$varname = ($isscale && empty($insertvalue)) ? -1 : $insertvalue;
 424                  }
 425              }
 426          }
 427          return parent::process($data);
 428      }
 429  }