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 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [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   * Class for rendering user filters on the course participants page.
  19   *
  20   * @package    core_user
  21   * @copyright  2020 Michael Hawkins <michaelh@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace core_user\output;
  25  
  26  use context_course;
  27  use core_user\fields;
  28  use renderable;
  29  use renderer_base;
  30  use stdClass;
  31  use templatable;
  32  
  33  /**
  34   * Class for rendering user filters on the course participants page.
  35   *
  36   * @copyright  2020 Michael Hawkins <michaelh@moodle.com>
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class participants_filter implements renderable, templatable {
  40  
  41      /** @var context_course $context The context where the filters are being rendered. */
  42      protected $context;
  43  
  44      /** @var string $tableregionid The table to be updated by this filter */
  45      protected $tableregionid;
  46  
  47      /** @var stdClass $course The course shown */
  48      protected $course;
  49  
  50      /**
  51       * Participants filter constructor.
  52       *
  53       * @param context_course $context The context where the filters are being rendered.
  54       * @param string $tableregionid The table to be updated by this filter
  55       */
  56      public function __construct(context_course $context, string $tableregionid) {
  57          $this->context = $context;
  58          $this->tableregionid = $tableregionid;
  59  
  60          $this->course = get_course($context->instanceid);
  61      }
  62  
  63      /**
  64       * Get data for all filter types.
  65       *
  66       * @return array
  67       */
  68      protected function get_filtertypes(): array {
  69          $filtertypes = [];
  70  
  71          $filtertypes[] = $this->get_keyword_filter();
  72  
  73          if ($filtertype = $this->get_enrolmentstatus_filter()) {
  74              $filtertypes[] = $filtertype;
  75          }
  76  
  77          if ($filtertype = $this->get_roles_filter()) {
  78              $filtertypes[] = $filtertype;
  79          }
  80  
  81          if ($filtertype = $this->get_enrolments_filter()) {
  82              $filtertypes[] = $filtertype;
  83          }
  84  
  85          if ($filtertype = $this->get_groups_filter()) {
  86              $filtertypes[] = $filtertype;
  87          }
  88  
  89          if ($filtertype = $this->get_accesssince_filter()) {
  90              $filtertypes[] = $filtertype;
  91          }
  92  
  93          if ($filtertype = $this->get_country_filter()) {
  94              $filtertypes[] = $filtertype;
  95          }
  96  
  97          return $filtertypes;
  98      }
  99  
 100      /**
 101       * Get data for the enrolment status filter.
 102       *
 103       * @return stdClass|null
 104       */
 105      protected function get_enrolmentstatus_filter(): ?stdClass {
 106          if (!has_capability('moodle/course:enrolreview', $this->context)) {
 107              return null;
 108          }
 109  
 110          return $this->get_filter_object(
 111              'status',
 112              get_string('participationstatus', 'core_enrol'),
 113              false,
 114              true,
 115              null,
 116              [
 117                  (object) [
 118                      'value' => ENROL_USER_ACTIVE,
 119                      'title' => get_string('active'),
 120                  ],
 121                  (object) [
 122                      'value' => ENROL_USER_SUSPENDED,
 123                      'title'  => get_string('inactive'),
 124                  ],
 125              ]
 126          );
 127      }
 128  
 129      /**
 130       * Get data for the roles filter.
 131       *
 132       * @return stdClass|null
 133       */
 134      protected function get_roles_filter(): ?stdClass {
 135          $roles = [];
 136          $roles += [-1 => get_string('noroles', 'role')];
 137          $roles += get_viewable_roles($this->context, null, ROLENAME_BOTH);
 138  
 139          if (has_capability('moodle/role:assign', $this->context)) {
 140              $roles += get_assignable_roles($this->context, ROLENAME_BOTH);
 141          }
 142  
 143          return $this->get_filter_object(
 144              'roles',
 145              get_string('roles', 'core_role'),
 146              false,
 147              true,
 148              null,
 149              array_map(function($id, $title) {
 150                  return (object) [
 151                      'value' => $id,
 152                      'title' => $title,
 153                  ];
 154              }, array_keys($roles), array_values($roles))
 155          );
 156      }
 157  
 158      /**
 159       * Get data for the roles filter.
 160       *
 161       * @return stdClass|null
 162       */
 163      protected function get_enrolments_filter(): ?stdClass {
 164          if (!has_capability('moodle/course:enrolreview', $this->context)) {
 165              return null;
 166          }
 167  
 168          if ($this->course->id == SITEID) {
 169              // No enrolment methods for the site.
 170              return null;
 171          }
 172  
 173          $instances = enrol_get_instances($this->course->id, true);
 174          $plugins = enrol_get_plugins(false);
 175  
 176          return $this->get_filter_object(
 177              'enrolments',
 178              get_string('enrolmentinstances', 'core_enrol'),
 179              false,
 180              true,
 181              null,
 182              array_filter(array_map(function($instance) use ($plugins): ?stdClass {
 183                  if (!array_key_exists($instance->enrol, $plugins)) {
 184                      return null;
 185                  }
 186  
 187                  return (object) [
 188                      'value' => $instance->id,
 189                      'title' => $plugins[$instance->enrol]->get_instance_name($instance),
 190                  ];
 191              }, array_values($instances)))
 192          );
 193      }
 194  
 195      /**
 196       * Get data for the groups filter.
 197       *
 198       * @return stdClass|null
 199       */
 200      protected function get_groups_filter(): ?stdClass {
 201          global $USER;
 202  
 203          // Filter options for groups, if available.
 204          $seeallgroups = has_capability('moodle/site:accessallgroups', $this->context);
 205          $seeallgroups = $seeallgroups || ($this->course->groupmode != SEPARATEGROUPS);
 206          if ($seeallgroups) {
 207              $groups = [];
 208              $groups += [USERSWITHOUTGROUP => (object) [
 209                      'id' => USERSWITHOUTGROUP,
 210                      'name' => get_string('nogroup', 'group'),
 211                  ]];
 212              $groups += groups_get_all_groups($this->course->id);
 213          } else {
 214              // Otherwise, just list the groups the user belongs to.
 215              $groups = groups_get_all_groups($this->course->id, $USER->id);
 216          }
 217  
 218          // Return no data if no groups found (which includes if the only value is 'No group').
 219          if (empty($groups) || (count($groups) === 1 && array_key_exists(-1, $groups))) {
 220              return null;
 221          }
 222  
 223          return $this->get_filter_object(
 224              'groups',
 225              get_string('groups', 'core_group'),
 226              false,
 227              true,
 228              null,
 229              array_map(function($group) {
 230                  return (object) [
 231                      'value' => $group->id,
 232                      'title' => format_string($group->name, true, ['context' => $this->context]),
 233                  ];
 234              }, array_values($groups))
 235          );
 236      }
 237  
 238      /**
 239       * Get data for the accesssince filter.
 240       *
 241       * @return stdClass|null
 242       */
 243      protected function get_accesssince_filter(): ?stdClass {
 244          global $CFG, $DB;
 245  
 246          $hiddenfields = [];
 247          if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
 248              $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
 249          }
 250  
 251          if (array_key_exists('lastaccess', $hiddenfields)) {
 252              return null;
 253          }
 254  
 255          // Get minimum lastaccess for this course and display a dropbox to filter by lastaccess going back this far.
 256          // We need to make it diferently for normal courses and site course.
 257          if (!$this->course->id == SITEID) {
 258              // Regular course.
 259              $params = [
 260                  'courseid' => $this->course->id,
 261                  'timeaccess' => 0,
 262              ];
 263              $select = 'courseid = :courseid AND timeaccess != :timeaccess';
 264              $minlastaccess = $DB->get_field_select('user_lastaccess', 'MIN(timeaccess)', $select, $params);
 265              $lastaccess0exists = $DB->record_exists('user_lastaccess', $params);
 266          } else {
 267              // Front page.
 268              $params = ['lastaccess' => 0];
 269              $select = 'lastaccess != :lastaccess';
 270              $minlastaccess = $DB->get_field_select('user', 'MIN(lastaccess)', $select, $params);
 271              $lastaccess0exists = $DB->record_exists('user', $params);
 272          }
 273  
 274          $now = usergetmidnight(time());
 275          $timeoptions = [];
 276          $criteria = get_string('usersnoaccesssince');
 277  
 278          $getoptions = function(int $count, string $singletype, string $type) use ($now, $minlastaccess): array {
 279              $values = [];
 280              for ($i = 1; $i <= $count; $i++) {
 281                  $timestamp = strtotime("-{$i} {$type}", $now);
 282                  if ($timestamp < $minlastaccess) {
 283                      break;
 284                  }
 285  
 286                  if ($i === 1) {
 287                      $title = get_string("num{$singletype}", 'moodle', $i);
 288                  } else {
 289                      $title = get_string("num{$type}", 'moodle', $i);
 290                  }
 291  
 292                  $values[] = [
 293                      'value' => $timestamp,
 294                      'title' => $title,
 295                  ];
 296              }
 297  
 298              return $values;
 299          };
 300  
 301          $values = array_merge(
 302              $getoptions(6, 'day', 'days'),
 303              $getoptions(10, 'week', 'weeks'),
 304              $getoptions(11, 'month', 'months'),
 305              $getoptions(1, 'year', 'years')
 306          );
 307  
 308          if ($lastaccess0exists) {
 309              $values[] = [
 310                  'value' => time(),
 311                  'title' => get_string('never', 'moodle'),
 312              ];
 313          }
 314  
 315          if (count($values) <= 1) {
 316              // Nothing to show.
 317              return null;
 318          }
 319  
 320          return $this->get_filter_object(
 321              'accesssince',
 322              get_string('usersnoaccesssince'),
 323              false,
 324              false,
 325              null,
 326              $values
 327          );
 328      }
 329  
 330      /**
 331       * Get data for the country filter
 332       *
 333       * @return stdClass|null
 334       */
 335      protected function get_country_filter(): ?stdClass {
 336          $extrauserfields = fields::get_identity_fields($this->context, false);
 337          if (array_search('country', $extrauserfields) === false) {
 338              return null;
 339          }
 340  
 341          $countries = get_string_manager()->get_list_of_countries(true);
 342  
 343          return $this->get_filter_object(
 344              'country',
 345              get_string('country'),
 346              false,
 347              true,
 348              'core_user/local/participantsfilter/filtertypes/country',
 349              array_map(function(string $code, string $name): stdClass {
 350                  return (object) [
 351                      'value' => $code,
 352                      'title' => $name,
 353                  ];
 354              }, array_keys($countries), array_values($countries))
 355          );
 356      }
 357  
 358      /**
 359       * Get data for the keywords filter.
 360       *
 361       * @return stdClass|null
 362       */
 363      protected function get_keyword_filter(): ?stdClass {
 364          return $this->get_filter_object(
 365              'keywords',
 366              get_string('filterbykeyword', 'core_user'),
 367              true,
 368              true,
 369              'core_user/local/participantsfilter/filtertypes/keyword',
 370              [],
 371              true
 372          );
 373      }
 374  
 375      /**
 376       * Export the renderer data in a mustache template friendly format.
 377       *
 378       * @param renderer_base $output Unused.
 379       * @return stdClass Data in a format compatible with a mustache template.
 380       */
 381      public function export_for_template(renderer_base $output): stdClass {
 382          return (object) [
 383              'tableregionid' => $this->tableregionid,
 384              'courseid' => $this->context->instanceid,
 385              'filtertypes' => $this->get_filtertypes(),
 386              'rownumber' => 1,
 387          ];
 388  
 389          return $data;
 390      }
 391  
 392      /**
 393       * Get a standardised filter object.
 394       *
 395       * @param string $name
 396       * @param string $title
 397       * @param bool $custom
 398       * @param bool $multiple
 399       * @param string|null $filterclass
 400       * @param array $values
 401       * @param bool $allowempty
 402       * @return stdClass|null
 403       */
 404      protected function get_filter_object(
 405          string $name,
 406          string $title,
 407          bool $custom,
 408          bool $multiple,
 409          ?string $filterclass,
 410          array $values,
 411          bool $allowempty = false
 412      ): ?stdClass {
 413  
 414          if (!$allowempty && empty($values)) {
 415              // Do not show empty filters.
 416              return null;
 417          }
 418  
 419          return (object) [
 420              'name' => $name,
 421              'title' => $title,
 422              'allowcustom' => $custom,
 423              'allowmultiple' => $multiple,
 424              'filtertypeclass' => $filterclass,
 425              'values' => $values,
 426          ];
 427      }
 428  }