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 39 and 401]

   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                  // Not safe to call format_string here; use the special function to call it later.
 114                  $name = self::description_format_string(self::$groupnames[$this->groupid]);
 115              }
 116          } else {
 117              return get_string($not ? 'requires_notanygroup' : 'requires_anygroup',
 118                      'availability_group');
 119          }
 120  
 121          return get_string($not ? 'requires_notgroup' : 'requires_group',
 122                  'availability_group', $name);
 123      }
 124  
 125      protected function get_debug_string() {
 126          return $this->groupid ? '#' . $this->groupid : 'any';
 127      }
 128  
 129      /**
 130       * Include this condition only if we are including groups in restore, or
 131       * if it's a generic 'same activity' one.
 132       *
 133       * @param int $restoreid The restore Id.
 134       * @param int $courseid The ID of the course.
 135       * @param base_logger $logger The logger being used.
 136       * @param string $name Name of item being restored.
 137       * @param base_task $task The task being performed.
 138       *
 139       * @return Integer groupid
 140       */
 141      public function include_after_restore($restoreid, $courseid, \base_logger $logger,
 142              $name, \base_task $task) {
 143          return !$this->groupid || $task->get_setting_value('groups');
 144      }
 145  
 146      public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name) {
 147          global $DB;
 148          if (!$this->groupid) {
 149              return false;
 150          }
 151          $rec = \restore_dbops::get_backup_ids_record($restoreid, 'group', $this->groupid);
 152          if (!$rec || !$rec->newitemid) {
 153              // If we are on the same course (e.g. duplicate) then we can just
 154              // use the existing one.
 155              if ($DB->record_exists('groups',
 156                      array('id' => $this->groupid, 'courseid' => $courseid))) {
 157                  return false;
 158              }
 159              // Otherwise it's a warning.
 160              $this->groupid = -1;
 161              $logger->process('Restored item (' . $name .
 162                      ') has availability condition on group that was not restored',
 163                      \backup::LOG_WARNING);
 164          } else {
 165              $this->groupid = (int)$rec->newitemid;
 166          }
 167          return true;
 168      }
 169  
 170      public function update_dependency_id($table, $oldid, $newid) {
 171          if ($table === 'groups' && (int)$this->groupid === (int)$oldid) {
 172              $this->groupid = $newid;
 173              return true;
 174          } else {
 175              return false;
 176          }
 177      }
 178  
 179      /**
 180       * Wipes the static cache used to store grouping names.
 181       */
 182      public static function wipe_static_cache() {
 183          self::$groupnames = array();
 184      }
 185  
 186      public function is_applied_to_user_lists() {
 187          // Group conditions are assumed to be 'permanent', so they affect the
 188          // display of user lists for activities.
 189          return true;
 190      }
 191  
 192      public function filter_user_list(array $users, $not, \core_availability\info $info,
 193              \core_availability\capability_checker $checker) {
 194          global $CFG, $DB;
 195  
 196          // If the array is empty already, just return it.
 197          if (!$users) {
 198              return $users;
 199          }
 200  
 201          require_once($CFG->libdir . '/grouplib.php');
 202          $course = $info->get_course();
 203  
 204          // List users for this course who match the condition.
 205          if ($this->groupid) {
 206              $groupusers = groups_get_members($this->groupid, 'u.id', 'u.id ASC');
 207          } else {
 208              $groupusers = $DB->get_records_sql("
 209                      SELECT DISTINCT gm.userid
 210                        FROM {groups} g
 211                        JOIN {groups_members} gm ON gm.groupid = g.id
 212                       WHERE g.courseid = ?", array($course->id));
 213          }
 214  
 215          // List users who have access all groups.
 216          $aagusers = $checker->get_users_by_capability('moodle/site:accessallgroups');
 217  
 218          // Filter the user list.
 219          $result = array();
 220          foreach ($users as $id => $user) {
 221              // Always include users with access all groups.
 222              if (array_key_exists($id, $aagusers)) {
 223                  $result[$id] = $user;
 224                  continue;
 225              }
 226              // Other users are included or not based on group membership.
 227              $allow = array_key_exists($id, $groupusers);
 228              if ($not) {
 229                  $allow = !$allow;
 230              }
 231              if ($allow) {
 232                  $result[$id] = $user;
 233              }
 234          }
 235          return $result;
 236      }
 237  
 238      /**
 239       * Returns a JSON object which corresponds to a condition of this type.
 240       *
 241       * Intended for unit testing, as normally the JSON values are constructed
 242       * by JavaScript code.
 243       *
 244       * @param int $groupid Required group id (0 = any group)
 245       * @return stdClass Object representing condition
 246       */
 247      public static function get_json($groupid = 0) {
 248          $result = (object)array('type' => 'group');
 249          // Id is only included if set.
 250          if ($groupid) {
 251              $result->id = (int)$groupid;
 252          }
 253          return $result;
 254      }
 255  
 256      public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
 257          global $DB;
 258  
 259          // Get enrolled users with access all groups. These always are allowed.
 260          list($aagsql, $aagparams) = get_enrolled_sql(
 261                  $info->get_context(), 'moodle/site:accessallgroups', 0, $onlyactive);
 262  
 263          // Get all enrolled users.
 264          list ($enrolsql, $enrolparams) =
 265                  get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
 266  
 267          // Condition for specified or any group.
 268          $matchparams = array();
 269          if ($this->groupid) {
 270              $matchsql = "SELECT 1
 271                             FROM {groups_members} gm
 272                            WHERE gm.userid = userids.id
 273                                  AND gm.groupid = " .
 274                      self::unique_sql_parameter($matchparams, $this->groupid);
 275          } else {
 276              $matchsql = "SELECT 1
 277                             FROM {groups_members} gm
 278                             JOIN {groups} g ON g.id = gm.groupid
 279                            WHERE gm.userid = userids.id
 280                                  AND g.courseid = " .
 281                      self::unique_sql_parameter($matchparams, $info->get_course()->id);
 282          }
 283  
 284          // Overall query combines all this.
 285          $condition = $not ? 'NOT' : '';
 286          $sql = "SELECT userids.id
 287                    FROM ($enrolsql) userids
 288                   WHERE (userids.id IN ($aagsql)) OR $condition EXISTS ($matchsql)";
 289          return array($sql, array_merge($enrolparams, $aagparams, $matchparams));
 290      }
 291  }