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 25namespace availability_group; 26 27defined('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 */ 36class 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} 293