Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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   * Condition main class.
  19   *
  20   * @package availability_group
  21   * @copyright 2014 The Open University
  22   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace availability_group;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Condition main class.
  31   *
  32   * @package availability_group
  33   * @copyright 2014 The Open University
  34   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class condition extends \core_availability\condition {
  37      /** @var array Array from group id => name */
  38      protected static $groupnames = array();
  39  
  40      /** @var int ID of group that this condition requires, or 0 = any group */
  41      protected $groupid;
  42  
  43      /**
  44       * Constructor.
  45       *
  46       * @param \stdClass $structure Data structure from JSON decode
  47       * @throws \coding_exception If invalid data structure.
  48       */
  49      public function __construct($structure) {
  50          // Get group id.
  51          if (!property_exists($structure, 'id')) {
  52              $this->groupid = 0;
  53          } else if (is_int($structure->id)) {
  54              $this->groupid = $structure->id;
  55          } else {
  56              throw new \coding_exception('Invalid ->id for group condition');
  57          }
  58      }
  59  
  60      public function save() {
  61          $result = (object)array('type' => 'group');
  62          if ($this->groupid) {
  63              $result->id = $this->groupid;
  64          }
  65          return $result;
  66      }
  67  
  68      public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
  69          $course = $info->get_course();
  70          $context = \context_course::instance($course->id);
  71          $allow = true;
  72          if (!has_capability('moodle/site:accessallgroups', $context, $userid)) {
  73              // Get all groups the user belongs to.
  74              $groups = $info->get_modinfo()->get_groups();
  75              if ($this->groupid) {
  76                  $allow = in_array($this->groupid, $groups);
  77              } else {
  78                  // No specific group. Allow if they belong to any group at all.
  79                  $allow = $groups ? true : false;
  80              }
  81  
  82              // The NOT condition applies before accessallgroups (i.e. if you
  83              // set something to be available to those NOT in group X,
  84              // people with accessallgroups can still access it even if
  85              // they are in group X).
  86              if ($not) {
  87                  $allow = !$allow;
  88              }
  89          }
  90          return $allow;
  91      }
  92  
  93      public function get_description($full, $not, \core_availability\info $info) {
  94          global $DB;
  95  
  96          if ($this->groupid) {
  97              // Need to get the name for the group. Unfortunately this requires
  98              // a database query. To save queries, get all groups for course at
  99              // once in a static cache.
 100              $course = $info->get_course();
 101              if (!array_key_exists($this->groupid, self::$groupnames)) {
 102                  $coursegroups = $DB->get_records(
 103                          'groups', array('courseid' => $course->id), '', 'id, name');
 104                  foreach ($coursegroups as $rec) {
 105                      self::$groupnames[$rec->id] = $rec->name;
 106                  }
 107              }
 108  
 109              // If it still doesn't exist, it must have been misplaced.
 110              if (!array_key_exists($this->groupid, self::$groupnames)) {
 111                  $name = get_string('missing', 'availability_group');
 112              } else {
 113                  $context = \context_course::instance($course->id);
 114                  $name = format_string(self::$groupnames[$this->groupid], true,
 115                          array('context' => $context));
 116              }
 117          } else {
 118              return get_string($not ? 'requires_notanygroup' : 'requires_anygroup',
 119                      'availability_group');
 120          }
 121  
 122          return get_string($not ? 'requires_notgroup' : 'requires_group',
 123                  'availability_group', $name);
 124      }
 125  
 126      protected function get_debug_string() {
 127          return $this->groupid ? '#' . $this->groupid : 'any';
 128      }
 129  
 130      /**
 131       * Include this condition only if we are including groups in restore, or
 132       * if it's a generic 'same activity' one.
 133       *
 134       * @param int $restoreid The restore Id.
 135       * @param int $courseid The ID of the course.
 136       * @param base_logger $logger The logger being used.
 137       * @param string $name Name of item being restored.
 138       * @param base_task $task The task being performed.
 139       *
 140       * @return Integer groupid
 141       */
 142      public function include_after_restore($restoreid, $courseid, \base_logger $logger,
 143              $name, \base_task $task) {
 144          return !$this->groupid || $task->get_setting_value('groups');
 145      }
 146  
 147      public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name) {
 148          global $DB;
 149          if (!$this->groupid) {
 150              return false;
 151          }
 152          $rec = \restore_dbops::get_backup_ids_record($restoreid, 'group', $this->groupid);
 153          if (!$rec || !$rec->newitemid) {
 154              // If we are on the same course (e.g. duplicate) then we can just
 155              // use the existing one.
 156              if ($DB->record_exists('groups',
 157                      array('id' => $this->groupid, 'courseid' => $courseid))) {
 158                  return false;
 159              }
 160              // Otherwise it's a warning.
 161              $this->groupid = -1;
 162              $logger->process('Restored item (' . $name .
 163                      ') has availability condition on group that was not restored',
 164                      \backup::LOG_WARNING);
 165          } else {
 166              $this->groupid = (int)$rec->newitemid;
 167          }
 168          return true;
 169      }
 170  
 171      public function update_dependency_id($table, $oldid, $newid) {
 172          if ($table === 'groups' && (int)$this->groupid === (int)$oldid) {
 173              $this->groupid = $newid;
 174              return true;
 175          } else {
 176              return false;
 177          }
 178      }
 179  
 180      /**
 181       * Wipes the static cache used to store grouping names.
 182       */
 183      public static function wipe_static_cache() {
 184          self::$groupnames = array();
 185      }
 186  
 187      public function is_applied_to_user_lists() {
 188          // Group conditions are assumed to be 'permanent', so they affect the
 189          // display of user lists for activities.
 190          return true;
 191      }
 192  
 193      public function filter_user_list(array $users, $not, \core_availability\info $info,
 194              \core_availability\capability_checker $checker) {
 195          global $CFG, $DB;
 196  
 197          // If the array is empty already, just return it.
 198          if (!$users) {
 199              return $users;
 200          }
 201  
 202          require_once($CFG->libdir . '/grouplib.php');
 203          $course = $info->get_course();
 204  
 205          // List users for this course who match the condition.
 206          if ($this->groupid) {
 207              $groupusers = groups_get_members($this->groupid, 'u.id', 'u.id ASC');
 208          } else {
 209              $groupusers = $DB->get_records_sql("
 210                      SELECT DISTINCT gm.userid
 211                        FROM {groups} g
 212                        JOIN {groups_members} gm ON gm.groupid = g.id
 213                       WHERE g.courseid = ?", array($course->id));
 214          }
 215  
 216          // List users who have access all groups.
 217          $aagusers = $checker->get_users_by_capability('moodle/site:accessallgroups');
 218  
 219          // Filter the user list.
 220          $result = array();
 221          foreach ($users as $id => $user) {
 222              // Always include users with access all groups.
 223              if (array_key_exists($id, $aagusers)) {
 224                  $result[$id] = $user;
 225                  continue;
 226              }
 227              // Other users are included or not based on group membership.
 228              $allow = array_key_exists($id, $groupusers);
 229              if ($not) {
 230                  $allow = !$allow;
 231              }
 232              if ($allow) {
 233                  $result[$id] = $user;
 234              }
 235          }
 236          return $result;
 237      }
 238  
 239      /**
 240       * Returns a JSON object which corresponds to a condition of this type.
 241       *
 242       * Intended for unit testing, as normally the JSON values are constructed
 243       * by JavaScript code.
 244       *
 245       * @param int $groupid Required group id (0 = any group)
 246       * @return stdClass Object representing condition
 247       */
 248      public static function get_json($groupid = 0) {
 249          $result = (object)array('type' => 'group');
 250          // Id is only included if set.
 251          if ($groupid) {
 252              $result->id = (int)$groupid;
 253          }
 254          return $result;
 255      }
 256  
 257      public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
 258          global $DB;
 259  
 260          // Get enrolled users with access all groups. These always are allowed.
 261          list($aagsql, $aagparams) = get_enrolled_sql(
 262                  $info->get_context(), 'moodle/site:accessallgroups', 0, $onlyactive);
 263  
 264          // Get all enrolled users.
 265          list ($enrolsql, $enrolparams) =
 266                  get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
 267  
 268          // Condition for specified or any group.
 269          $matchparams = array();
 270          if ($this->groupid) {
 271              $matchsql = "SELECT 1
 272                             FROM {groups_members} gm
 273                            WHERE gm.userid = userids.id
 274                                  AND gm.groupid = " .
 275                      self::unique_sql_parameter($matchparams, $this->groupid);
 276          } else {
 277              $matchsql = "SELECT 1
 278                             FROM {groups_members} gm
 279                             JOIN {groups} g ON g.id = gm.groupid
 280                            WHERE gm.userid = userids.id
 281                                  AND g.courseid = " .
 282                      self::unique_sql_parameter($matchparams, $info->get_course()->id);
 283          }
 284  
 285          // Overall query combines all this.
 286          $condition = $not ? 'NOT' : '';
 287          $sql = "SELECT userids.id
 288                    FROM ($enrolsql) userids
 289                   WHERE (userids.id IN ($aagsql)) OR $condition EXISTS ($matchsql)";
 290          return array($sql, array_merge($enrolparams, $aagparams, $matchparams));
 291      }
 292  }