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