1 <?php 2 // This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>. 16 17 namespace core\context; 18 19 use core\context; 20 use stdClass; 21 use coding_exception, moodle_url; 22 23 /** 24 * Course module context class 25 * 26 * @package core_access 27 * @category access 28 * @copyright Petr Skoda 29 * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 * @since Moodle 4.2 31 */ 32 class module extends context { 33 /** @var int numeric context level value matching legacy CONTEXT_MODULE */ 34 public const LEVEL = 70; 35 36 /** 37 * Please use \core\context\module::instance($cmid) if you need the instance of context. 38 * Alternatively if you know only the context id use \core\context::instance_by_id($contextid) 39 * 40 * @param stdClass $record 41 */ 42 protected function __construct(stdClass $record) { 43 parent::__construct($record); 44 if ($record->contextlevel != self::LEVEL) { 45 throw new coding_exception('Invalid $record->contextlevel in core\context\module constructor.'); 46 } 47 } 48 49 /** 50 * Returns short context name. 51 * 52 * @since Moodle 4.2 53 * 54 * @return string 55 */ 56 public static function get_short_name(): string { 57 return 'module'; 58 } 59 60 /** 61 * Returns human readable context level name. 62 * 63 * @return string the human readable context level name. 64 */ 65 public static function get_level_name() { 66 return get_string('activitymodule'); 67 } 68 69 /** 70 * Returns human readable context identifier. 71 * 72 * @param boolean $withprefix whether to prefix the name of the context with the 73 * module name, e.g. Forum, Glossary, etc. 74 * @param boolean $short does not apply to module context 75 * @param boolean $escape Whether the returned name of the context is to be HTML escaped or not. 76 * @return string the human readable context name. 77 */ 78 public function get_context_name($withprefix = true, $short = false, $escape = true) { 79 global $DB; 80 81 $name = ''; 82 if ($cm = $DB->get_record_sql("SELECT cm.*, md.name AS modname 83 FROM {course_modules} cm 84 JOIN {modules} md ON md.id = cm.module 85 WHERE cm.id = ?", array($this->_instanceid))) { 86 if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) { 87 if ($withprefix) { 88 $name = get_string('modulename', $cm->modname).': '; 89 } 90 if (!$escape) { 91 $name .= format_string($mod->name, true, array('context' => $this, 'escape' => false)); 92 } else { 93 $name .= format_string($mod->name, true, array('context' => $this)); 94 } 95 } 96 } 97 return $name; 98 } 99 100 /** 101 * Returns the most relevant URL for this context. 102 * 103 * @return moodle_url 104 */ 105 public function get_url() { 106 global $DB; 107 108 if ($modname = $DB->get_field_sql("SELECT md.name AS modname 109 FROM {course_modules} cm 110 JOIN {modules} md ON md.id = cm.module 111 WHERE cm.id = ?", array($this->_instanceid))) { 112 return new moodle_url('/mod/' . $modname . '/view.php', array('id' => $this->_instanceid)); 113 } 114 115 return new moodle_url('/'); 116 } 117 118 /** 119 * Returns context instance database name. 120 * 121 * @return string|null table name for all levels except system. 122 */ 123 protected static function get_instance_table(): ?string { 124 return 'course_modules'; 125 } 126 127 /** 128 * Returns list of columns that can be used from behat 129 * to look up context by reference. 130 * 131 * @return array list of column names from instance table 132 */ 133 protected static function get_behat_reference_columns(): array { 134 return ['idnumber']; 135 } 136 137 /** 138 * Returns list of all role archetypes that are compatible 139 * with role assignments in context level. 140 * @since Moodle 4.2 141 * 142 * @return int[] 143 */ 144 protected static function get_compatible_role_archetypes(): array { 145 return ['editingteacher', 'teacher', 'student']; 146 } 147 148 /** 149 * Returns list of all possible parent context levels. 150 * @since Moodle 4.2 151 * 152 * @return int[] 153 */ 154 public static function get_possible_parent_levels(): array { 155 return [course::LEVEL]; 156 } 157 158 /** 159 * Returns array of relevant context capability records. 160 * 161 * @param string $sort 162 * @return array 163 */ 164 public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) { 165 global $DB, $CFG; 166 167 $cm = $DB->get_record('course_modules', array('id' => $this->_instanceid)); 168 $module = $DB->get_record('modules', array('id' => $cm->module)); 169 170 $subcaps = array(); 171 172 $modulepath = "{$CFG->dirroot}/mod/{$module->name}"; 173 if (file_exists("{$modulepath}/db/subplugins.json")) { 174 $subplugins = (array) json_decode(file_get_contents("{$modulepath}/db/subplugins.json"))->plugintypes; 175 } else if (file_exists("{$modulepath}/db/subplugins.php")) { 176 debugging('Use of subplugins.php has been deprecated. ' . 177 'Please update your plugin to provide a subplugins.json file instead.', 178 DEBUG_DEVELOPER); 179 $subplugins = array(); // Should be redefined in the file. 180 include("{$modulepath}/db/subplugins.php"); 181 } 182 183 if (!empty($subplugins)) { 184 foreach (array_keys($subplugins) as $subplugintype) { 185 foreach (array_keys(\core_component::get_plugin_list($subplugintype)) as $subpluginname) { 186 $subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname))); 187 } 188 } 189 } 190 191 $modfile = "{$modulepath}/lib.php"; 192 $extracaps = array(); 193 if (file_exists($modfile)) { 194 include_once($modfile); 195 $modfunction = $module->name.'_get_extra_capabilities'; 196 if (function_exists($modfunction)) { 197 $extracaps = $modfunction(); 198 } 199 } 200 201 $extracaps = array_merge($subcaps, $extracaps); 202 $extra = ''; 203 list($extra, $params) = $DB->get_in_or_equal( 204 $extracaps, SQL_PARAMS_NAMED, 'cap0', true, ''); 205 if (!empty($extra)) { 206 $extra = "OR name $extra"; 207 } 208 209 // Fetch the list of modules, and remove this one. 210 $components = \core_component::get_component_list(); 211 $componentnames = $components['mod']; 212 unset($componentnames["mod_{$module->name}"]); 213 $componentnames = array_keys($componentnames); 214 215 // Exclude all other modules. 216 list($notcompsql, $notcompparams) = $DB->get_in_or_equal($componentnames, SQL_PARAMS_NAMED, 'notcomp', false); 217 $params = array_merge($params, $notcompparams); 218 219 // Exclude other component submodules. 220 $i = 0; 221 $ignorecomponents = []; 222 foreach ($componentnames as $mod) { 223 if ($subplugins = \core_component::get_subplugins($mod)) { 224 foreach (array_keys($subplugins) as $subplugintype) { 225 $paramname = "notlike{$i}"; 226 $ignorecomponents[] = $DB->sql_like('component', ":{$paramname}", true, true, true); 227 $params[$paramname] = "{$subplugintype}_%"; 228 $i++; 229 } 230 } 231 } 232 $notlikesql = "(" . implode(' AND ', $ignorecomponents) . ")"; 233 234 $sql = "SELECT * 235 FROM {capabilities} 236 WHERE (contextlevel = ".self::LEVEL." 237 AND component {$notcompsql} 238 AND {$notlikesql}) 239 $extra 240 ORDER BY $sort"; 241 242 return $DB->get_records_sql($sql, $params); 243 } 244 245 /** 246 * Is this context part of any course? If yes return course context. 247 * 248 * @param bool $strict true means throw exception if not found, false means return false if not found 249 * @return course|false context of the enclosing course, null if not found or exception 250 */ 251 public function get_course_context($strict = true) { 252 return $this->get_parent_context(); 253 } 254 255 /** 256 * Returns module context instance. 257 * 258 * @param int $cmid id of the record from {course_modules} table; pass cmid there, NOT id in the instance column 259 * @param int $strictness 260 * @return module|false context instance 261 */ 262 public static function instance($cmid, $strictness = MUST_EXIST) { 263 global $DB; 264 265 if ($context = context::cache_get(self::LEVEL, $cmid)) { 266 return $context; 267 } 268 269 if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $cmid))) { 270 if ($cm = $DB->get_record('course_modules', array('id' => $cmid), 'id,course', $strictness)) { 271 $parentcontext = course::instance($cm->course); 272 $record = context::insert_context_record(self::LEVEL, $cm->id, $parentcontext->path); 273 } 274 } 275 276 if ($record) { 277 $context = new module($record); 278 context::cache_add($context); 279 return $context; 280 } 281 282 return false; 283 } 284 285 /** 286 * Create missing context instances at module context level 287 */ 288 protected static function create_level_instances() { 289 global $DB; 290 291 $sql = "SELECT " . self::LEVEL . ", cm.id 292 FROM {course_modules} cm 293 WHERE NOT EXISTS (SELECT 'x' 294 FROM {context} cx 295 WHERE cm.id = cx.instanceid AND cx.contextlevel=" . self::LEVEL . ")"; 296 $contextdata = $DB->get_recordset_sql($sql); 297 foreach ($contextdata as $context) { 298 context::insert_context_record(self::LEVEL, $context->id, null); 299 } 300 $contextdata->close(); 301 } 302 303 /** 304 * Returns sql necessary for purging of stale context instances. 305 * 306 * @return string cleanup SQL 307 */ 308 protected static function get_cleanup_sql() { 309 $sql = " 310 SELECT c.* 311 FROM {context} c 312 LEFT OUTER JOIN {course_modules} cm ON c.instanceid = cm.id 313 WHERE cm.id IS NULL AND c.contextlevel = " . self::LEVEL . " 314 "; 315 316 return $sql; 317 } 318 319 /** 320 * Rebuild context paths and depths at module context level. 321 * 322 * @param bool $force 323 */ 324 protected static function build_paths($force) { 325 global $DB; 326 327 if ($force || $DB->record_exists_select('context', "contextlevel = " . self::LEVEL . " AND (depth = 0 OR path IS NULL)")) { 328 if ($force) { 329 $ctxemptyclause = ''; 330 } else { 331 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 332 } 333 334 $sql = "INSERT INTO {context_temp} (id, path, depth, locked) 335 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked 336 FROM {context} ctx 337 JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = " . self::LEVEL . ") 338 JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = " . course::LEVEL . ") 339 WHERE pctx.path IS NOT NULL AND pctx.depth > 0 340 $ctxemptyclause"; 341 $trans = $DB->start_delegated_transaction(); 342 $DB->delete_records('context_temp'); 343 $DB->execute($sql); 344 context::merge_context_temp_table(); 345 $DB->delete_records('context_temp'); 346 $trans->allow_commit(); 347 } 348 } 349 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body