Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

   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   * Classes to enforce the various access rules that can apply to a activity.
  19   *
  20   * @package    block_activity_results
  21   * @copyright  2009 Tim Hunt
  22   * @copyright  2015 Stephen Bourget
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once($CFG->dirroot . '/lib/grade/constants.php');
  29  require_once($CFG->dirroot . '/course/lib.php');
  30  
  31  define('B_ACTIVITYRESULTS_NAME_FORMAT_FULL', 1);
  32  define('B_ACTIVITYRESULTS_NAME_FORMAT_ID',   2);
  33  define('B_ACTIVITYRESULTS_NAME_FORMAT_ANON', 3);
  34  define('B_ACTIVITYRESULTS_GRADE_FORMAT_PCT', 1);
  35  define('B_ACTIVITYRESULTS_GRADE_FORMAT_FRA', 2);
  36  define('B_ACTIVITYRESULTS_GRADE_FORMAT_ABS', 3);
  37  define('B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE', 4);
  38  
  39  /**
  40   * Block activity_results class definition.
  41   *
  42   * This block can be added to a course page or a activity page to display of list of
  43   * the best/worst students/groups in a particular activity.
  44   *
  45   * @package    block_activity_results
  46   * @copyright  2009 Tim Hunt
  47   * @copyright  2015 Stephen Bourget
  48   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   */
  50  class block_activity_results extends block_base {
  51  
  52      /**
  53       * Core function used to initialize the block.
  54       */
  55      public function init() {
  56          $this->title = get_string('pluginname', 'block_activity_results');
  57      }
  58  
  59      /**
  60       * Allow the block to have a configuration page
  61       *
  62       * @return boolean
  63       */
  64      public function has_config() {
  65          return true;
  66      }
  67  
  68      /**
  69       * Core function, specifies where the block can be used.
  70       * @return array
  71       */
  72      public function applicable_formats() {
  73          return array('course-view' => true, 'mod' => true);
  74      }
  75  
  76      /**
  77       * If this block belongs to a activity context, then return that activity's id.
  78       * Otherwise, return 0.
  79       * @return stdclass the activity record.
  80       */
  81      public function get_owning_activity() {
  82          global $DB;
  83  
  84          // Set some defaults.
  85          $result = new stdClass();
  86          $result->id = 0;
  87  
  88          if (empty($this->instance->parentcontextid)) {
  89              return $result;
  90          }
  91          $parentcontext = context::instance_by_id($this->instance->parentcontextid);
  92          if ($parentcontext->contextlevel != CONTEXT_MODULE) {
  93              return $result;
  94          }
  95          $cm = get_coursemodule_from_id($this->page->cm->modname, $parentcontext->instanceid);
  96          if (!$cm) {
  97              return $result;
  98          }
  99          // Get the grade_items id.
 100          $rec = $DB->get_record('grade_items', array('iteminstance' => $cm->instance, 'itemmodule' => $this->page->cm->modname));
 101          if (!$rec) {
 102              return $result;
 103          }
 104          // See if it is a gradable activity.
 105          if (($rec->gradetype != GRADE_TYPE_VALUE) && ($rec->gradetype != GRADE_TYPE_SCALE)) {
 106              return $result;
 107          }
 108          return $rec;
 109      }
 110  
 111      /**
 112       * Used to save the form config data
 113       * @param stdclass $data
 114       * @param bool $nolongerused
 115       */
 116      public function instance_config_save($data, $nolongerused = false) {
 117          global $DB;
 118          if (empty($data->activitygradeitemid)) {
 119              // Figure out info about parent module.
 120              $info = $this->get_owning_activity();
 121              $data->activitygradeitemid = $info->id;
 122              if ($info->id < 1) {
 123                  // No activity was selected.
 124                  $info->itemmodule = '';
 125                  $info->iteminstance = '';
 126              } else {
 127                  $data->activityparent = $info->itemmodule;
 128                  $data->activityparentid = $info->iteminstance;
 129              }
 130          } else {
 131              // Lookup info about the parent module (we have the id from mdl_grade_items.
 132              $info = $DB->get_record('grade_items', array('id' => $data->activitygradeitemid));
 133              $data->activityparent = $info->itemmodule;
 134              $data->activityparentid = $info->iteminstance;
 135          }
 136          parent::instance_config_save($data);
 137      }
 138  
 139      /**
 140       * Used to generate the content for the block.
 141       * @return string
 142       */
 143      public function get_content() {
 144          global $USER, $CFG, $DB;
 145  
 146          if ($this->content !== null) {
 147              return $this->content;
 148          }
 149  
 150          $this->content = new stdClass;
 151          $this->content->text = '';
 152          $this->content->footer = '';
 153  
 154          if (empty($this->instance)) {
 155              return $this->content;
 156          }
 157  
 158          // We are configured so use the configuration.
 159          if (!empty($this->config->activitygradeitemid)) {
 160              // We are configured.
 161              $activitygradeitemid = $this->config->activitygradeitemid;
 162  
 163              // Lookup the module in the grade_items table.
 164              $activity = $DB->get_record('grade_items', array('id' => $activitygradeitemid));
 165              if (empty($activity)) {
 166                  // Activity does not exist.
 167                  $this->content->text = get_string('error_emptyactivityrecord', 'block_activity_results');
 168                  return $this->content;
 169              }
 170              $courseid = $activity->courseid;
 171              $inactivity = false;
 172          } else {
 173              // Not configured.
 174              $activitygradeitemid = 0;
 175          }
 176  
 177          // Check to see if we are in the moule we are displaying results for.
 178          if (!empty($this->config->activitygradeitemid)) {
 179              if ($this->get_owning_activity()->id == $this->config->activitygradeitemid) {
 180                  $inactivity = true;
 181              } else {
 182                  $inactivity = false;
 183              }
 184          }
 185  
 186          // Activity ID is missing.
 187          if (empty($activitygradeitemid)) {
 188              $this->content->text = get_string('error_emptyactivityid', 'block_activity_results');
 189              return $this->content;
 190          }
 191  
 192          // Check to see if we are configured.
 193          if (empty($this->config->showbest) && empty($this->config->showworst)) {
 194              $this->content->text = get_string('configuredtoshownothing', 'block_activity_results');
 195              return $this->content;
 196          }
 197  
 198          // Check to see if it is a supported grade type.
 199          if (empty($activity->gradetype) || ($activity->gradetype != GRADE_TYPE_VALUE && $activity->gradetype != GRADE_TYPE_SCALE)) {
 200              $this->content->text = get_string('error_unsupportedgradetype', 'block_activity_results');
 201              return $this->content;
 202          }
 203  
 204          // Get the grades for this activity.
 205          $sql = 'SELECT * FROM {grade_grades}
 206                   WHERE itemid = ? AND finalgrade is not NULL
 207                   ORDER BY finalgrade, timemodified DESC';
 208  
 209          $grades = $DB->get_records_sql($sql, array( $activitygradeitemid));
 210  
 211          if (empty($grades) || $activity->hidden) {
 212              // No grades available, The block will hide itself in this case.
 213              return $this->content;
 214          }
 215  
 216          // Set up results.
 217          $groupmode = NOGROUPS;
 218          $best      = array();
 219          $worst     = array();
 220  
 221          if (!empty($this->config->nameformat)) {
 222              $nameformat = $this->config->nameformat;
 223          } else {
 224              $nameformat = B_ACTIVITYRESULTS_NAME_FORMAT_FULL;
 225          }
 226  
 227          // Get $cm and context.
 228          if ($inactivity) {
 229              $cm = $this->page->cm;
 230              $context = $this->page->context;
 231          } else {
 232              $cm = get_coursemodule_from_instance($activity->itemmodule, $activity->iteminstance, $courseid);
 233              $context = context_module::instance($cm->id);
 234          }
 235  
 236          if (!empty($this->config->usegroups)) {
 237              $groupmode = groups_get_activity_groupmode($cm);
 238  
 239              if ($groupmode == SEPARATEGROUPS && has_capability('moodle/site:accessallgroups', $context)) {
 240                  // If you have the ability to see all groups then lets show them.
 241                  $groupmode = VISIBLEGROUPS;
 242              }
 243          }
 244  
 245          switch ($groupmode) {
 246              case VISIBLEGROUPS:
 247                  // Display group-mode results.
 248                  $groups = groups_get_all_groups($courseid);
 249  
 250                  if (empty($groups)) {
 251                      // No groups exist, sorry.
 252                      $this->content->text = get_string('error_nogroupsexist', 'block_activity_results');
 253                      return $this->content;
 254                  }
 255  
 256                  // Find out all the userids which have a submitted grade.
 257                  $userids = array();
 258                  $gradeforuser = array();
 259                  foreach ($grades as $grade) {
 260                      $userids[] = $grade->userid;
 261                      $gradeforuser[$grade->userid] = (float)$grade->finalgrade;
 262                  }
 263  
 264                  // Now find which groups these users belong in.
 265                  list($usertest, $params) = $DB->get_in_or_equal($userids);
 266                  $params[] = $courseid;
 267                  $usergroups = $DB->get_records_sql('
 268                          SELECT gm.id, gm.userid, gm.groupid, g.name
 269                          FROM {groups} g
 270                          LEFT JOIN {groups_members} gm ON g.id = gm.groupid
 271                          WHERE gm.userid ' . $usertest . ' AND g.courseid = ?', $params);
 272  
 273                  // Now, iterate the grades again and sum them up for each group.
 274                  $groupgrades = array();
 275                  foreach ($usergroups as $usergroup) {
 276                      if (!isset($groupgrades[$usergroup->groupid])) {
 277                          $groupgrades[$usergroup->groupid] = array(
 278                                  'sum' => (float)$gradeforuser[$usergroup->userid],
 279                                  'number' => 1,
 280                                  'group' => $usergroup->name);
 281                      } else {
 282                          $groupgrades[$usergroup->groupid]['sum'] += $gradeforuser[$usergroup->userid];
 283                          $groupgrades[$usergroup->groupid]['number'] += 1;
 284                      }
 285                  }
 286  
 287                  foreach ($groupgrades as $groupid => $groupgrade) {
 288                      $groupgrades[$groupid]['average'] = $groupgrades[$groupid]['sum'] / $groupgrades[$groupid]['number'];
 289                  }
 290  
 291                  // Sort groupgrades according to average grade, ascending.
 292                  uasort($groupgrades, function($a, $b) {
 293                      if ($a["average"] == $b["average"]) {
 294                          return 0;
 295                      }
 296                      return ($a["average"] > $b["average"] ? 1 : -1);
 297                  });
 298  
 299                  // How many groups do we have with graded member submissions to show?
 300                  $numbest  = empty($this->config->showbest) ? 0 : min($this->config->showbest, count($groupgrades));
 301                  $numworst = empty($this->config->showworst) ? 0 : min($this->config->showworst, count($groupgrades) - $numbest);
 302  
 303                  // Collect all the group results we are going to use in $best and $worst.
 304                  $remaining = $numbest;
 305                  $groupgrade = end($groupgrades);
 306                  while ($remaining--) {
 307                      $best[key($groupgrades)] = $groupgrade['average'];
 308                      $groupgrade = prev($groupgrades);
 309                  }
 310  
 311                  $remaining = $numworst;
 312                  $groupgrade = reset($groupgrades);
 313                  while ($remaining--) {
 314                      $worst[key($groupgrades)] = $groupgrade['average'];
 315                      $groupgrade = next($groupgrades);
 316                  }
 317  
 318                  // Ready for output!
 319                  if ($activity->gradetype == GRADE_TYPE_SCALE) {
 320                      // We must display the results using scales.
 321                      $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE;
 322                      // Preload the scale.
 323                      $scale = $this->get_scale($activity->scaleid);
 324                  } else if (intval(empty($this->config->gradeformat))) {
 325                      $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_PCT;
 326                  } else {
 327                      $gradeformat = $this->config->gradeformat;
 328                  }
 329  
 330                  // Generate the header.
 331                  $this->content->text .= $this->activity_link($activity, $cm);
 332  
 333                  if ($nameformat == B_ACTIVITYRESULTS_NAME_FORMAT_FULL) {
 334                      if (has_capability('moodle/course:managegroups', $context)) {
 335                          $grouplink = $CFG->wwwroot.'/group/overview.php?id='.$courseid.'&amp;group=';
 336                      } else if (course_can_view_participants($context)) {
 337                          $grouplink = $CFG->wwwroot.'/user/index.php?id='.$courseid.'&amp;group=';
 338                      } else {
 339                          $grouplink = '';
 340                      }
 341                  }
 342  
 343                  $rank = 0;
 344                  if (!empty($best)) {
 345                      $this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';
 346                      if ($numbest == 1) {
 347                          $this->content->text .= get_string('bestgroupgrade', 'block_activity_results');
 348                      } else {
 349                          $this->content->text .= get_string('bestgroupgrades', 'block_activity_results', $numbest);
 350                      }
 351                      $this->content->text .= '</h6></caption><colgroup class="number" />';
 352                      $this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';
 353                      foreach ($best as $groupid => $averagegrade) {
 354                          switch ($nameformat) {
 355                              case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:
 356                              case B_ACTIVITYRESULTS_NAME_FORMAT_ID:
 357                                  $thisname = get_string('group');
 358                              break;
 359                              default:
 360                              case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:
 361                                  if ($grouplink) {
 362                                      $thisname = '<a href="'.$grouplink.$groupid.'">'.$groupgrades[$groupid]['group'].'</a>';
 363                                  } else {
 364                                      $thisname = $groupgrades[$groupid]['group'];
 365                                  }
 366                              break;
 367                          }
 368                          $this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';
 369                          switch ($gradeformat) {
 370                              case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:
 371                                  // Round answer up and locate appropriate scale.
 372                                  $answer = (round($averagegrade, 0, PHP_ROUND_HALF_UP) - 1);
 373                                  if (isset($scale[$answer])) {
 374                                      $this->content->text .= $scale[$answer];
 375                                  } else {
 376                                      // Value is not in the scale.
 377                                      $this->content->text .= get_string('unknown', 'block_activity_results');
 378                                  }
 379                              break;
 380                              case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:
 381                                  $this->content->text .= $this->activity_format_grade($averagegrade)
 382                                      . '/' . $this->activity_format_grade($activity->grademax);
 383                              break;
 384                              case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:
 385                                  $this->content->text .= $this->activity_format_grade($averagegrade);
 386                              break;
 387                              default:
 388                              case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:
 389                                  $this->content->text .= $this->activity_format_grade((float)$averagegrade /
 390                                          (float)$activity->grademax * 100).'%';
 391                              break;
 392                          }
 393                          $this->content->text .= '</td></tr>';
 394                      }
 395                      $this->content->text .= '</tbody></table>';
 396                  }
 397  
 398                  $rank = 0;
 399                  if (!empty($worst)) {
 400                      $worst = array_reverse($worst, true);
 401                      $this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';
 402                      if ($numworst == 1) {
 403                          $this->content->text .= get_string('worstgroupgrade', 'block_activity_results');
 404                      } else {
 405                          $this->content->text .= get_string('worstgroupgrades', 'block_activity_results', $numworst);
 406                      }
 407                      $this->content->text .= '</h6></caption><colgroup class="number" />';
 408                      $this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';
 409                      foreach ($worst as $groupid => $averagegrade) {
 410                          switch ($nameformat) {
 411                              case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:
 412                              case B_ACTIVITYRESULTS_NAME_FORMAT_ID:
 413                                  $thisname = get_string('group');
 414                              break;
 415                              default:
 416                              case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:
 417                                  if ($grouplink) {
 418                                      $thisname = '<a href="'.$grouplink.$groupid.'">'.$groupgrades[$groupid]['group'].'</a>';
 419                                  } else {
 420                                      $thisname = $groupgrades[$groupid]['group'];
 421                                  }
 422                              break;
 423                          }
 424                          $this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';
 425                          switch ($gradeformat) {
 426                              case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:
 427                                  // Round answer up and locate appropriate scale.
 428                                  $answer = (round($averagegrade, 0, PHP_ROUND_HALF_UP) - 1);
 429                                  if (isset($scale[$answer])) {
 430                                      $this->content->text .= $scale[$answer];
 431                                  } else {
 432                                      // Value is not in the scale.
 433                                      $this->content->text .= get_string('unknown', 'block_activity_results');
 434                                  }
 435                              break;
 436                              case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:
 437                                  $this->content->text .= $this->activity_format_grade($averagegrade)
 438                                      . '/' . $this->activity_format_grade($activity->grademax);
 439                              break;
 440                              case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:
 441                                  $this->content->text .= $this->activity_format_grade($averagegrade);
 442                              break;
 443                              default:
 444                              case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:
 445                                  $this->content->text .= $this->activity_format_grade((float)$averagegrade /
 446                                          (float)$activity->grademax * 100).'%';
 447                              break;
 448                          }
 449                          $this->content->text .= '</td></tr>';
 450                      }
 451                      $this->content->text .= '</tbody></table>';
 452                  }
 453              break;
 454  
 455              case SEPARATEGROUPS:
 456                  // This is going to be just like no-groups mode, only we 'll filter
 457                  // out the grades from people not in our group.
 458                  if (!isloggedin()) {
 459                      // Not logged in, so show nothing.
 460                      return $this->content;
 461                  }
 462  
 463                  $mygroups = groups_get_all_groups($courseid, $USER->id);
 464                  if (empty($mygroups)) {
 465                      // Not member of a group, show nothing.
 466                      return $this->content;
 467                  }
 468  
 469                  // Get users from the same groups as me.
 470                  list($grouptest, $params) = $DB->get_in_or_equal(array_keys($mygroups));
 471                  $mygroupsusers = $DB->get_records_sql_menu(
 472                          'SELECT DISTINCT userid, 1 FROM {groups_members} WHERE groupid ' . $grouptest,
 473                          $params);
 474  
 475                  // Filter out the grades belonging to other users, and proceed as if there were no groups.
 476                  foreach ($grades as $key => $grade) {
 477                      if (!isset($mygroupsusers[$grade->userid])) {
 478                          unset($grades[$key]);
 479                      }
 480                  }
 481  
 482                  // No break, fall through to the default case now we have filtered the $grades array.
 483              default:
 484              case NOGROUPS:
 485                  // Single user mode.
 486                  $numbest  = empty($this->config->showbest) ? 0 : min($this->config->showbest, count($grades));
 487                  $numworst = empty($this->config->showworst) ? 0 : min($this->config->showworst, count($grades) - $numbest);
 488  
 489                  // Collect all the usernames we are going to need.
 490                  $remaining = $numbest;
 491                  $grade = end($grades);
 492                  while ($remaining--) {
 493                      $best[$grade->userid] = $grade->id;
 494                      $grade = prev($grades);
 495                  }
 496  
 497                  $remaining = $numworst;
 498                  $grade = reset($grades);
 499                  while ($remaining--) {
 500                      $worst[$grade->userid] = $grade->id;
 501                      $grade = next($grades);
 502                  }
 503  
 504                  if (empty($best) && empty($worst)) {
 505                      // Nothing to show, for some reason...
 506                      return $this->content;
 507                  }
 508  
 509                  // Now grab all the users from the database.
 510                  $userids = array_merge(array_keys($best), array_keys($worst));
 511                  $fields = array_merge(array('id', 'idnumber'), \core_user\fields::get_name_fields());
 512                  $fields = implode(',', $fields);
 513                  $users = $DB->get_records_list('user', 'id', $userids, '', $fields);
 514  
 515                  // If configured to view user idnumber, ensure current user can see it.
 516                  $extrafields = \core_user\fields::for_identity($this->context)->get_required_fields();
 517                  $canviewidnumber = (array_search('idnumber', $extrafields) !== false);
 518  
 519                  // Ready for output!
 520                  if ($activity->gradetype == GRADE_TYPE_SCALE) {
 521                      // We must display the results using scales.
 522                      $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE;
 523                      // Preload the scale.
 524                      $scale = $this->get_scale($activity->scaleid);
 525                  } else if (intval(empty($this->config->gradeformat))) {
 526                      $gradeformat = B_ACTIVITYRESULTS_GRADE_FORMAT_PCT;
 527                  } else {
 528                      $gradeformat = $this->config->gradeformat;
 529                  }
 530  
 531                  // Generate the header.
 532                  $this->content->text .= $this->activity_link($activity, $cm);
 533  
 534                  $rank = 0;
 535                  if (!empty($best)) {
 536                      $this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';
 537                      if ($numbest == 1) {
 538                          $this->content->text .= get_string('bestgrade', 'block_activity_results');
 539                      } else {
 540                          $this->content->text .= get_string('bestgrades', 'block_activity_results', $numbest);
 541                      }
 542                      $this->content->text .= '</h6></caption><colgroup class="number" />';
 543                      $this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';
 544  
 545                      foreach ($best as $userid => $gradeid) {
 546                          switch ($nameformat) {
 547                              case B_ACTIVITYRESULTS_NAME_FORMAT_ID:
 548                                  $thisname = get_string('user');
 549                                  if ($canviewidnumber) {
 550                                      $thisname .= ' ' . s($users[$userid]->idnumber);
 551                                  }
 552                              break;
 553                              case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:
 554                                  $thisname = get_string('user');
 555                              break;
 556                              default:
 557                              case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:
 558                                  if (has_capability('moodle/user:viewdetails', $context)) {
 559                                      $thisname = html_writer::link(new moodle_url('/user/view.php',
 560                                          array('id' => $userid, 'course' => $courseid)), fullname($users[$userid]));
 561                                  } else {
 562                                      $thisname = fullname($users[$userid]);
 563                                  }
 564                              break;
 565                          }
 566                          $this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';
 567                          switch ($gradeformat) {
 568                              case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:
 569                                  // Round answer up and locate appropriate scale.
 570                                  $answer = (round($grades[$gradeid]->finalgrade, 0, PHP_ROUND_HALF_UP) - 1);
 571                                  if (isset($scale[$answer])) {
 572                                      $this->content->text .= $scale[$answer];
 573                                  } else {
 574                                      // Value is not in the scale.
 575                                      $this->content->text .= get_string('unknown', 'block_activity_results');
 576                                  }
 577                              break;
 578                              case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:
 579                                  $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);
 580                                  $this->content->text .= '/'.$this->activity_format_grade($activity->grademax);
 581                              break;
 582                              case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:
 583                                  $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);
 584                              break;
 585                              default:
 586                              case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:
 587                                  if ($activity->grademax) {
 588                                      $this->content->text .= $this->activity_format_grade((float)$grades[$gradeid]->finalgrade /
 589                                              (float)$activity->grademax * 100).'%';
 590                                  } else {
 591                                      $this->content->text .= '--%';
 592                                  }
 593                              break;
 594                          }
 595                          $this->content->text .= '</td></tr>';
 596                      }
 597                      $this->content->text .= '</tbody></table>';
 598                  }
 599  
 600                  $rank = 0;
 601                  if (!empty($worst)) {
 602                      $worst = array_reverse($worst, true);
 603                      $this->content->text .= '<table class="grades"><caption class="pb-0"><h6>';
 604                      if ($numbest == 1) {
 605                          $this->content->text .= get_string('worstgrade', 'block_activity_results');
 606                      } else {
 607                          $this->content->text .= get_string('worstgrades', 'block_activity_results', $numworst);
 608                      }
 609                      $this->content->text .= '</h6></caption><colgroup class="number" />';
 610                      $this->content->text .= '<colgroup class="name" /><colgroup class="grade" /><tbody>';
 611                      foreach ($worst as $userid => $gradeid) {
 612                          switch ($nameformat) {
 613                              case B_ACTIVITYRESULTS_NAME_FORMAT_ID:
 614                                  $thisname = get_string('user');
 615                                  if ($canviewidnumber) {
 616                                      $thisname .= ' ' . s($users[$userid]->idnumber);
 617                                  };
 618                              break;
 619                              case B_ACTIVITYRESULTS_NAME_FORMAT_ANON:
 620                                  $thisname = get_string('user');
 621                              break;
 622                              default:
 623                              case B_ACTIVITYRESULTS_NAME_FORMAT_FULL:
 624                                  if (has_capability('moodle/user:viewdetails', $context)) {
 625                                      $thisname = html_writer::link(new moodle_url('/user/view.php',
 626                                          array('id' => $userid, 'course' => $courseid)), fullname($users[$userid]));
 627                                  } else {
 628                                      $thisname = fullname($users[$userid]);
 629                                  }
 630                              break;
 631                          }
 632                          $this->content->text .= '<tr><td>'.(++$rank).'.</td><td>'.$thisname.'</td><td>';
 633                          switch ($gradeformat) {
 634                              case B_ACTIVITYRESULTS_GRADE_FORMAT_SCALE:
 635                                  // Round answer up and locate appropriate scale.
 636                                  $answer = (round($grades[$gradeid]->finalgrade, 0, PHP_ROUND_HALF_UP) - 1);
 637                                  if (isset($scale[$answer])) {
 638                                      $this->content->text .= $scale[$answer];
 639                                  } else {
 640                                      // Value is not in the scale.
 641                                      $this->content->text .= get_string('unknown', 'block_activity_results');
 642                                  }
 643                              break;
 644                              case B_ACTIVITYRESULTS_GRADE_FORMAT_FRA:
 645                                  $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);
 646                                  $this->content->text .= '/'.$this->activity_format_grade($activity->grademax);
 647                              break;
 648                              case B_ACTIVITYRESULTS_GRADE_FORMAT_ABS:
 649                                  $this->content->text .= $this->activity_format_grade($grades[$gradeid]->finalgrade);
 650                              break;
 651                              default:
 652                              case B_ACTIVITYRESULTS_GRADE_FORMAT_PCT:
 653                                  if ($activity->grademax) {
 654                                      $this->content->text .= $this->activity_format_grade((float)$grades[$gradeid]->finalgrade /
 655                                              (float)$activity->grademax * 100).'%';
 656                                  } else {
 657                                      $this->content->text .= '--%';
 658                                  }
 659                              break;
 660                          }
 661                          $this->content->text .= '</td></tr>';
 662                      }
 663                      $this->content->text .= '</tbody></table>';
 664                  }
 665              break;
 666          }
 667  
 668          return $this->content;
 669      }
 670  
 671      /**
 672       * Allows the block to be added multiple times to a single page
 673       * @return boolean
 674       */
 675      public function instance_allow_multiple() {
 676          return true;
 677      }
 678  
 679      /**
 680       * Formats the grade to the specified decimal points
 681       * @param float $grade
 682       * @return string
 683       */
 684      private function activity_format_grade($grade) {
 685          if (is_null($grade)) {
 686              return get_string('notyetgraded', 'block_activity_results');
 687          }
 688          return format_float($grade, $this->config->decimalpoints);
 689      }
 690  
 691      /**
 692       * Generates the Link to the activity module when displayed outside of the module.
 693       * @param stdclass $activity
 694       * @param stdclass $cm
 695       * @return string
 696       */
 697      private function activity_link($activity, $cm) {
 698  
 699          $o = html_writer::start_tag('h5');
 700          $o .= html_writer::link(new moodle_url('/mod/'.$activity->itemmodule.'/view.php',
 701          array('id' => $cm->id)), format_string(($activity->itemname), true, ['context' => context_module::instance($cm->id)]));
 702          $o .= html_writer::end_tag('h5');
 703          return $o;
 704      }
 705  
 706      /**
 707       * Generates a numeric array of scale entries
 708       * @param int $scaleid
 709       * @return array
 710       */
 711      private function get_scale($scaleid) {
 712          global $DB;
 713          $scaletext = $DB->get_field('scale', 'scale', array('id' => $scaleid), IGNORE_MISSING);
 714          $scale = explode ( ',', $scaletext);
 715          return $scale;
 716  
 717      }
 718  
 719      /**
 720       * Return the plugin config settings for external functions.
 721       *
 722       * @return stdClass the configs for both the block instance and plugin
 723       * @since Moodle 3.8
 724       */
 725      public function get_config_for_external() {
 726          // Return all settings for all users since it is safe (no private keys, etc..).
 727          $instanceconfigs = !empty($this->config) ? $this->config : new stdClass();
 728          $pluginconfigs = get_config('block_activity_results');
 729  
 730          return (object) [
 731              'instance' => $instanceconfigs,
 732              'plugin' => $pluginconfigs,
 733          ];
 734      }
 735  }