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