| 1 | efrain | 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;
 | 
        
           |  |  | 18 |   | 
        
           |  |  | 19 | use stdClass, IteratorAggregate, ArrayIterator;
 | 
        
           |  |  | 20 | use coding_exception, moodle_url;
 | 
        
           |  |  | 21 |   | 
        
           |  |  | 22 | /**
 | 
        
           |  |  | 23 |  * Basic moodle context abstraction class.
 | 
        
           |  |  | 24 |  *
 | 
        
           |  |  | 25 |  * Google confirms that no other important framework is using "context" class,
 | 
        
           |  |  | 26 |  * we could use something else like mcontext or moodle_context, but we need to type
 | 
        
           |  |  | 27 |  * this very often which would be annoying and it would take too much space...
 | 
        
           |  |  | 28 |  *
 | 
        
           |  |  | 29 |  * This class is derived from stdClass for backwards compatibility with
 | 
        
           |  |  | 30 |  * odl $context record that was returned from DML $DB->get_record()
 | 
        
           |  |  | 31 |  *
 | 
        
           |  |  | 32 |  * @package   core_access
 | 
        
           |  |  | 33 |  * @category  access
 | 
        
           |  |  | 34 |  * @copyright Petr Skoda
 | 
        
           |  |  | 35 |  * @license   https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 36 |  * @since     Moodle 4.2
 | 
        
           |  |  | 37 |  *
 | 
        
           |  |  | 38 |  * @property-read int $id context id
 | 
        
           |  |  | 39 |  * @property-read int $contextlevel CONTEXT_SYSTEM, CONTEXT_COURSE, etc.
 | 
        
           |  |  | 40 |  * @property-read int $instanceid id of related instance in each context
 | 
        
           |  |  | 41 |  * @property-read string $path path to context, starts with system context
 | 
        
           |  |  | 42 |  * @property-read int $depth
 | 
        
           |  |  | 43 |  * @property-read bool $locked true means write capabilities are ignored in this context or parents
 | 
        
           |  |  | 44 |  */
 | 
        
           |  |  | 45 | abstract class context extends stdClass implements IteratorAggregate {
 | 
        
           |  |  | 46 |   | 
        
           |  |  | 47 |     /** @var string Default sorting of capabilities in {@see get_capabilities} */
 | 
        
           |  |  | 48 |     protected const DEFAULT_CAPABILITY_SORT = 'contextlevel, component, name';
 | 
        
           |  |  | 49 |   | 
        
           |  |  | 50 |     /**
 | 
        
           |  |  | 51 |      * The context id
 | 
        
           |  |  | 52 |      * Can be accessed publicly through $context->id
 | 
        
           |  |  | 53 |      * @var int
 | 
        
           |  |  | 54 |      */
 | 
        
           |  |  | 55 |     protected $_id;
 | 
        
           |  |  | 56 |   | 
        
           |  |  | 57 |     /**
 | 
        
           |  |  | 58 |      * The context level
 | 
        
           |  |  | 59 |      * Can be accessed publicly through $context->contextlevel
 | 
        
           |  |  | 60 |      * @var int One of CONTEXT_* e.g. CONTEXT_COURSE, CONTEXT_MODULE
 | 
        
           |  |  | 61 |      */
 | 
        
           |  |  | 62 |     protected $_contextlevel;
 | 
        
           |  |  | 63 |   | 
        
           |  |  | 64 |     /**
 | 
        
           |  |  | 65 |      * Id of the item this context is related to e.g. COURSE_CONTEXT => course.id
 | 
        
           |  |  | 66 |      * Can be accessed publicly through $context->instanceid
 | 
        
           |  |  | 67 |      * @var int
 | 
        
           |  |  | 68 |      */
 | 
        
           |  |  | 69 |     protected $_instanceid;
 | 
        
           |  |  | 70 |   | 
        
           |  |  | 71 |     /**
 | 
        
           |  |  | 72 |      * The path to the context always starting from the system context
 | 
        
           |  |  | 73 |      * Can be accessed publicly through $context->path
 | 
        
           |  |  | 74 |      * @var string
 | 
        
           |  |  | 75 |      */
 | 
        
           |  |  | 76 |     protected $_path;
 | 
        
           |  |  | 77 |   | 
        
           |  |  | 78 |     /**
 | 
        
           |  |  | 79 |      * The depth of the context in relation to parent contexts
 | 
        
           |  |  | 80 |      * Can be accessed publicly through $context->depth
 | 
        
           |  |  | 81 |      * @var int
 | 
        
           |  |  | 82 |      */
 | 
        
           |  |  | 83 |     protected $_depth;
 | 
        
           |  |  | 84 |   | 
        
           |  |  | 85 |     /**
 | 
        
           |  |  | 86 |      * Whether this context is locked or not.
 | 
        
           |  |  | 87 |      *
 | 
        
           |  |  | 88 |      * Can be accessed publicly through $context->locked.
 | 
        
           |  |  | 89 |      *
 | 
        
           |  |  | 90 |      * @var int
 | 
        
           |  |  | 91 |      */
 | 
        
           |  |  | 92 |     protected $_locked;
 | 
        
           |  |  | 93 |   | 
        
           |  |  | 94 |     /**
 | 
        
           |  |  | 95 |      * @var array Context caching info
 | 
        
           |  |  | 96 |      */
 | 
        
           |  |  | 97 |     private static $cache_contextsbyid = array();
 | 
        
           |  |  | 98 |   | 
        
           |  |  | 99 |     /**
 | 
        
           |  |  | 100 |      * @var array Context caching info
 | 
        
           |  |  | 101 |      */
 | 
        
           |  |  | 102 |     private static $cache_contexts = array();
 | 
        
           |  |  | 103 |   | 
        
           |  |  | 104 |     /**
 | 
        
           |  |  | 105 |      * Context count
 | 
        
           |  |  | 106 |      * Why do we do count contexts? Because count($array) is horribly slow for large arrays
 | 
        
           |  |  | 107 |      * @var int
 | 
        
           |  |  | 108 |      */
 | 
        
           |  |  | 109 |     protected static $cache_count = 0;
 | 
        
           |  |  | 110 |   | 
        
           |  |  | 111 |     /**
 | 
        
           |  |  | 112 |      * @var array Context caching info
 | 
        
           |  |  | 113 |      */
 | 
        
           |  |  | 114 |     protected static $cache_preloaded = array();
 | 
        
           |  |  | 115 |   | 
        
           |  |  | 116 |     /**
 | 
        
           |  |  | 117 |      * @var context\system The system context once initialised
 | 
        
           |  |  | 118 |      */
 | 
        
           |  |  | 119 |     protected static $systemcontext = null;
 | 
        
           |  |  | 120 |   | 
        
           |  |  | 121 |     /**
 | 
        
           |  |  | 122 |      * Returns short context name.
 | 
        
           |  |  | 123 |      *
 | 
        
           |  |  | 124 |      * @since Moodle 4.2
 | 
        
           |  |  | 125 |      *
 | 
        
           |  |  | 126 |      * @return string
 | 
        
           |  |  | 127 |      */
 | 
        
           |  |  | 128 |     public static function get_short_name(): string {
 | 
        
           |  |  | 129 |         // NOTE: it would be more correct to make this abstract,
 | 
        
           |  |  | 130 |         // unfortunately there are tests that attempt to mock context classes.
 | 
        
           |  |  | 131 |         throw new \coding_exception('get_short_name() method must be overridden in custom context levels');
 | 
        
           |  |  | 132 |     }
 | 
        
           |  |  | 133 |   | 
        
           |  |  | 134 |     /**
 | 
        
           |  |  | 135 |      * Resets the cache to remove all data.
 | 
        
           |  |  | 136 |      */
 | 
        
           |  |  | 137 |     protected static function reset_caches() {
 | 
        
           |  |  | 138 |         self::$cache_contextsbyid = array();
 | 
        
           |  |  | 139 |         self::$cache_contexts = array();
 | 
        
           |  |  | 140 |         self::$cache_count = 0;
 | 
        
           |  |  | 141 |         self::$cache_preloaded = array();
 | 
        
           |  |  | 142 |   | 
        
           |  |  | 143 |         self::$systemcontext = null;
 | 
        
           |  |  | 144 |     }
 | 
        
           |  |  | 145 |   | 
        
           |  |  | 146 |     /**
 | 
        
           |  |  | 147 |      * Adds a context to the cache. If the cache is full, discards a batch of
 | 
        
           |  |  | 148 |      * older entries.
 | 
        
           |  |  | 149 |      *
 | 
        
           |  |  | 150 |      * @param context $context New context to add
 | 
        
           |  |  | 151 |      * @return void
 | 
        
           |  |  | 152 |      */
 | 
        
           |  |  | 153 |     protected static function cache_add(context $context) {
 | 
        
           |  |  | 154 |         if (isset(self::$cache_contextsbyid[$context->id])) {
 | 
        
           |  |  | 155 |             // Already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow.
 | 
        
           |  |  | 156 |             return;
 | 
        
           |  |  | 157 |         }
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |         if (self::$cache_count >= CONTEXT_CACHE_MAX_SIZE) {
 | 
        
           |  |  | 160 |             $i = 0;
 | 
        
           |  |  | 161 |             foreach (self::$cache_contextsbyid as $ctx) {
 | 
        
           |  |  | 162 |                 $i++;
 | 
        
           |  |  | 163 |                 if ($i <= 100) {
 | 
        
           |  |  | 164 |                     // We want to keep the first contexts to be loaded on this page, hopefully they will be needed again later.
 | 
        
           |  |  | 165 |                     continue;
 | 
        
           |  |  | 166 |                 }
 | 
        
           |  |  | 167 |                 if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) {
 | 
        
           |  |  | 168 |                     // We remove oldest third of the contexts to make room for more contexts.
 | 
        
           |  |  | 169 |                     break;
 | 
        
           |  |  | 170 |                 }
 | 
        
           |  |  | 171 |                 unset(self::$cache_contextsbyid[$ctx->id]);
 | 
        
           |  |  | 172 |                 unset(self::$cache_contexts[$ctx->contextlevel][$ctx->instanceid]);
 | 
        
           |  |  | 173 |                 self::$cache_count--;
 | 
        
           |  |  | 174 |             }
 | 
        
           |  |  | 175 |         }
 | 
        
           |  |  | 176 |   | 
        
           |  |  | 177 |         self::$cache_contexts[$context->contextlevel][$context->instanceid] = $context;
 | 
        
           |  |  | 178 |         self::$cache_contextsbyid[$context->id] = $context;
 | 
        
           |  |  | 179 |         self::$cache_count++;
 | 
        
           |  |  | 180 |     }
 | 
        
           |  |  | 181 |   | 
        
           |  |  | 182 |     /**
 | 
        
           |  |  | 183 |      * Removes a context from the cache.
 | 
        
           |  |  | 184 |      *
 | 
        
           |  |  | 185 |      * @param context $context Context object to remove
 | 
        
           |  |  | 186 |      * @return void
 | 
        
           |  |  | 187 |      */
 | 
        
           |  |  | 188 |     protected static function cache_remove(context $context) {
 | 
        
           |  |  | 189 |         if (!isset(self::$cache_contextsbyid[$context->id])) {
 | 
        
           |  |  | 190 |             // Not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow.
 | 
        
           |  |  | 191 |             return;
 | 
        
           |  |  | 192 |         }
 | 
        
           |  |  | 193 |         unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]);
 | 
        
           |  |  | 194 |         unset(self::$cache_contextsbyid[$context->id]);
 | 
        
           |  |  | 195 |   | 
        
           |  |  | 196 |         self::$cache_count--;
 | 
        
           |  |  | 197 |   | 
        
           |  |  | 198 |         if (self::$cache_count < 0) {
 | 
        
           |  |  | 199 |             self::$cache_count = 0;
 | 
        
           |  |  | 200 |         }
 | 
        
           |  |  | 201 |     }
 | 
        
           |  |  | 202 |   | 
        
           |  |  | 203 |     /**
 | 
        
           |  |  | 204 |      * Gets a context from the cache.
 | 
        
           |  |  | 205 |      *
 | 
        
           |  |  | 206 |      * @param int $contextlevel Context level
 | 
        
           |  |  | 207 |      * @param int $instance Instance ID
 | 
        
           |  |  | 208 |      * @return context|bool Context or false if not in cache
 | 
        
           |  |  | 209 |      */
 | 
        
           |  |  | 210 |     protected static function cache_get($contextlevel, $instance) {
 | 
        
           |  |  | 211 |         if (isset(self::$cache_contexts[$contextlevel][$instance])) {
 | 
        
           |  |  | 212 |             return self::$cache_contexts[$contextlevel][$instance];
 | 
        
           |  |  | 213 |         }
 | 
        
           |  |  | 214 |         return false;
 | 
        
           |  |  | 215 |     }
 | 
        
           |  |  | 216 |   | 
        
           |  |  | 217 |     /**
 | 
        
           |  |  | 218 |      * Gets a context from the cache based on its id.
 | 
        
           |  |  | 219 |      *
 | 
        
           |  |  | 220 |      * @param int $id Context ID
 | 
        
           |  |  | 221 |      * @return context|bool Context or false if not in cache
 | 
        
           |  |  | 222 |      */
 | 
        
           |  |  | 223 |     protected static function cache_get_by_id($id) {
 | 
        
           |  |  | 224 |         if (isset(self::$cache_contextsbyid[$id])) {
 | 
        
           |  |  | 225 |             return self::$cache_contextsbyid[$id];
 | 
        
           |  |  | 226 |         }
 | 
        
           |  |  | 227 |         return false;
 | 
        
           |  |  | 228 |     }
 | 
        
           |  |  | 229 |   | 
        
           |  |  | 230 |     /**
 | 
        
           |  |  | 231 |      * Preloads context information from db record and strips the cached info.
 | 
        
           |  |  | 232 |      *
 | 
        
           |  |  | 233 |      * @param stdClass $rec
 | 
        
           |  |  | 234 |      * @return context|null (modifies $rec)
 | 
        
           |  |  | 235 |      */
 | 
        
           |  |  | 236 |     protected static function preload_from_record(stdClass $rec) {
 | 
        
           |  |  | 237 |         $notenoughdata = false;
 | 
        
           |  |  | 238 |         $notenoughdata = $notenoughdata || empty($rec->ctxid);
 | 
        
           |  |  | 239 |         $notenoughdata = $notenoughdata || empty($rec->ctxlevel);
 | 
        
           |  |  | 240 |         $notenoughdata = $notenoughdata || !isset($rec->ctxinstance);
 | 
        
           |  |  | 241 |         $notenoughdata = $notenoughdata || empty($rec->ctxpath);
 | 
        
           |  |  | 242 |         $notenoughdata = $notenoughdata || empty($rec->ctxdepth);
 | 
        
           |  |  | 243 |         $notenoughdata = $notenoughdata || !isset($rec->ctxlocked);
 | 
        
           |  |  | 244 |         if ($notenoughdata) {
 | 
        
           |  |  | 245 |             // The record does not have enough data, passed here repeatedly or context does not exist yet.
 | 
        
           |  |  | 246 |             if (isset($rec->ctxid) && !isset($rec->ctxlocked)) {
 | 
        
           |  |  | 247 |                 debugging('Locked value missing. Code is possibly not usings the getter properly.', DEBUG_DEVELOPER);
 | 
        
           |  |  | 248 |             }
 | 
        
           |  |  | 249 |             return null;
 | 
        
           |  |  | 250 |         }
 | 
        
           |  |  | 251 |   | 
        
           |  |  | 252 |         $record = (object) [
 | 
        
           |  |  | 253 |             'id' => $rec->ctxid,
 | 
        
           |  |  | 254 |             'contextlevel' => $rec->ctxlevel,
 | 
        
           |  |  | 255 |             'instanceid' => $rec->ctxinstance,
 | 
        
           |  |  | 256 |             'path' => $rec->ctxpath,
 | 
        
           |  |  | 257 |             'depth' => $rec->ctxdepth,
 | 
        
           |  |  | 258 |             'locked' => $rec->ctxlocked,
 | 
        
           |  |  | 259 |         ];
 | 
        
           |  |  | 260 |   | 
        
           |  |  | 261 |         unset($rec->ctxid);
 | 
        
           |  |  | 262 |         unset($rec->ctxlevel);
 | 
        
           |  |  | 263 |         unset($rec->ctxinstance);
 | 
        
           |  |  | 264 |         unset($rec->ctxpath);
 | 
        
           |  |  | 265 |         unset($rec->ctxdepth);
 | 
        
           |  |  | 266 |         unset($rec->ctxlocked);
 | 
        
           |  |  | 267 |   | 
        
           |  |  | 268 |         return self::create_instance_from_record($record);
 | 
        
           |  |  | 269 |     }
 | 
        
           |  |  | 270 |   | 
        
           |  |  | 271 |   | 
        
           |  |  | 272 |     /* ====== magic methods ======= */
 | 
        
           |  |  | 273 |   | 
        
           |  |  | 274 |     /**
 | 
        
           |  |  | 275 |      * Magic setter method, we do not want anybody to modify properties from the outside
 | 
        
           |  |  | 276 |      * @param string $name
 | 
        
           |  |  | 277 |      * @param mixed $value
 | 
        
           |  |  | 278 |      */
 | 
        
           |  |  | 279 |     public function __set($name, $value) {
 | 
        
           |  |  | 280 |         debugging('Can not change context instance properties!');
 | 
        
           |  |  | 281 |     }
 | 
        
           |  |  | 282 |   | 
        
           |  |  | 283 |     /**
 | 
        
           |  |  | 284 |      * Magic method getter, redirects to read only values.
 | 
        
           |  |  | 285 |      * @param string $name
 | 
        
           |  |  | 286 |      * @return mixed
 | 
        
           |  |  | 287 |      */
 | 
        
           |  |  | 288 |     public function __get($name) {
 | 
        
           |  |  | 289 |         switch ($name) {
 | 
        
           |  |  | 290 |             case 'id':
 | 
        
           |  |  | 291 |                 return $this->_id;
 | 
        
           |  |  | 292 |             case 'contextlevel':
 | 
        
           |  |  | 293 |                 return $this->_contextlevel;
 | 
        
           |  |  | 294 |             case 'instanceid':
 | 
        
           |  |  | 295 |                 return $this->_instanceid;
 | 
        
           |  |  | 296 |             case 'path':
 | 
        
           |  |  | 297 |                 return $this->_path;
 | 
        
           |  |  | 298 |             case 'depth':
 | 
        
           |  |  | 299 |                 return $this->_depth;
 | 
        
           |  |  | 300 |             case 'locked':
 | 
        
           |  |  | 301 |                 return $this->is_locked();
 | 
        
           |  |  | 302 |   | 
        
           |  |  | 303 |             default:
 | 
        
           |  |  | 304 |                 debugging('Invalid context property accessed! '.$name);
 | 
        
           |  |  | 305 |                 return null;
 | 
        
           |  |  | 306 |         }
 | 
        
           |  |  | 307 |     }
 | 
        
           |  |  | 308 |   | 
        
           |  |  | 309 |     /**
 | 
        
           |  |  | 310 |      * Full support for isset on our magic read only properties.
 | 
        
           |  |  | 311 |      * @param string $name
 | 
        
           |  |  | 312 |      * @return bool
 | 
        
           |  |  | 313 |      */
 | 
        
           |  |  | 314 |     public function __isset($name) {
 | 
        
           |  |  | 315 |         switch ($name) {
 | 
        
           |  |  | 316 |             case 'id':
 | 
        
           |  |  | 317 |                 return isset($this->_id);
 | 
        
           |  |  | 318 |             case 'contextlevel':
 | 
        
           |  |  | 319 |                 return isset($this->_contextlevel);
 | 
        
           |  |  | 320 |             case 'instanceid':
 | 
        
           |  |  | 321 |                 return isset($this->_instanceid);
 | 
        
           |  |  | 322 |             case 'path':
 | 
        
           |  |  | 323 |                 return isset($this->_path);
 | 
        
           |  |  | 324 |             case 'depth':
 | 
        
           |  |  | 325 |                 return isset($this->_depth);
 | 
        
           |  |  | 326 |             case 'locked':
 | 
        
           |  |  | 327 |                 // Locked is always set.
 | 
        
           |  |  | 328 |                 return true;
 | 
        
           |  |  | 329 |             default:
 | 
        
           |  |  | 330 |                 return false;
 | 
        
           |  |  | 331 |         }
 | 
        
           |  |  | 332 |     }
 | 
        
           |  |  | 333 |   | 
        
           |  |  | 334 |     /**
 | 
        
           |  |  | 335 |      * All properties are read only, sorry.
 | 
        
           |  |  | 336 |      * @param string $name
 | 
        
           |  |  | 337 |      */
 | 
        
           |  |  | 338 |     public function __unset($name) {
 | 
        
           |  |  | 339 |         debugging('Can not unset context instance properties!');
 | 
        
           |  |  | 340 |     }
 | 
        
           |  |  | 341 |   | 
        
           |  |  | 342 |     /* ====== implementing method from interface IteratorAggregate ====== */
 | 
        
           |  |  | 343 |   | 
        
           |  |  | 344 |     /**
 | 
        
           |  |  | 345 |      * Create an iterator because magic vars can't be seen by 'foreach'.
 | 
        
           |  |  | 346 |      *
 | 
        
           |  |  | 347 |      * Now we can convert context object to array using convert_to_array(),
 | 
        
           |  |  | 348 |      * and feed it properly to json_encode().
 | 
        
           |  |  | 349 |      */
 | 
        
           |  |  | 350 |     public function getIterator(): \Traversable {
 | 
        
           |  |  | 351 |         $ret = array(
 | 
        
           |  |  | 352 |             'id' => $this->id,
 | 
        
           |  |  | 353 |             'contextlevel' => $this->contextlevel,
 | 
        
           |  |  | 354 |             'instanceid' => $this->instanceid,
 | 
        
           |  |  | 355 |             'path' => $this->path,
 | 
        
           |  |  | 356 |             'depth' => $this->depth,
 | 
        
           |  |  | 357 |             'locked' => $this->locked,
 | 
        
           |  |  | 358 |         );
 | 
        
           |  |  | 359 |         return new ArrayIterator($ret);
 | 
        
           |  |  | 360 |     }
 | 
        
           |  |  | 361 |   | 
        
           |  |  | 362 |     /* ====== general context methods ====== */
 | 
        
           |  |  | 363 |   | 
        
           |  |  | 364 |     /**
 | 
        
           |  |  | 365 |      * Constructor is protected so that devs are forced to
 | 
        
           |  |  | 366 |      * use context_xxx::instance() or context::instance_by_id().
 | 
        
           |  |  | 367 |      *
 | 
        
           |  |  | 368 |      * @param stdClass $record
 | 
        
           |  |  | 369 |      */
 | 
        
           |  |  | 370 |     protected function __construct(stdClass $record) {
 | 
        
           |  |  | 371 |         $this->_id = (int)$record->id;
 | 
        
           |  |  | 372 |         $this->_contextlevel = (int)$record->contextlevel;
 | 
        
           |  |  | 373 |         $this->_instanceid = $record->instanceid;
 | 
        
           |  |  | 374 |         $this->_path = $record->path;
 | 
        
           |  |  | 375 |         $this->_depth = $record->depth;
 | 
        
           |  |  | 376 |   | 
        
           |  |  | 377 |         if (isset($record->locked)) {
 | 
        
           |  |  | 378 |             $this->_locked = $record->locked;
 | 
        
           |  |  | 379 |         } else if (!during_initial_install() && !moodle_needs_upgrading()) {
 | 
        
           |  |  | 380 |             debugging('Locked value missing. Code is possibly not usings the getter properly.', DEBUG_DEVELOPER);
 | 
        
           |  |  | 381 |         }
 | 
        
           |  |  | 382 |     }
 | 
        
           |  |  | 383 |   | 
        
           |  |  | 384 |     /**
 | 
        
           |  |  | 385 |      * This function is also used to work around 'protected' keyword problems in context_helper.
 | 
        
           |  |  | 386 |      *
 | 
        
           |  |  | 387 |      * @param stdClass $record
 | 
        
           |  |  | 388 |      * @return context instance
 | 
        
           |  |  | 389 |      */
 | 
        
           |  |  | 390 |     protected static function create_instance_from_record(stdClass $record) {
 | 
        
           |  |  | 391 |         $classname = context_helper::get_class_for_level($record->contextlevel);
 | 
        
           |  |  | 392 |   | 
        
           |  |  | 393 |         if ($context = self::cache_get_by_id($record->id)) {
 | 
        
           |  |  | 394 |             return $context;
 | 
        
           |  |  | 395 |         }
 | 
        
           |  |  | 396 |   | 
        
           |  |  | 397 |         $context = new $classname($record);
 | 
        
           |  |  | 398 |         self::cache_add($context);
 | 
        
           |  |  | 399 |   | 
        
           |  |  | 400 |         return $context;
 | 
        
           |  |  | 401 |     }
 | 
        
           |  |  | 402 |   | 
        
           |  |  | 403 |     /**
 | 
        
           |  |  | 404 |      * Copy prepared new contexts from temp table to context table,
 | 
        
           |  |  | 405 |      * we do this in db specific way for perf reasons only.
 | 
        
           |  |  | 406 |      */
 | 
        
           |  |  | 407 |     protected static function merge_context_temp_table() {
 | 
        
           |  |  | 408 |         global $DB;
 | 
        
           |  |  | 409 |   | 
        
           |  |  | 410 |         /* MDL-11347:
 | 
        
           |  |  | 411 |          *  - mysql does not allow to use FROM in UPDATE statements
 | 
        
           |  |  | 412 |          *  - using two tables after UPDATE works in mysql, but might give unexpected
 | 
        
           |  |  | 413 |          *    results in pg 8 (depends on configuration)
 | 
        
           |  |  | 414 |          *  - using table alias in UPDATE does not work in pg < 8.2
 | 
        
           |  |  | 415 |          *
 | 
        
           |  |  | 416 |          * Different code for each database - mostly for performance reasons
 | 
        
           |  |  | 417 |          */
 | 
        
           |  |  | 418 |   | 
        
           |  |  | 419 |         $dbfamily = $DB->get_dbfamily();
 | 
        
           |  |  | 420 |         if ($dbfamily == 'mysql') {
 | 
        
           |  |  | 421 |             $updatesql = "UPDATE {context} ct, {context_temp} temp
 | 
        
           |  |  | 422 |                              SET ct.path = temp.path,
 | 
        
           |  |  | 423 |                                  ct.depth = temp.depth,
 | 
        
           |  |  | 424 |                                  ct.locked = temp.locked
 | 
        
           |  |  | 425 |                            WHERE ct.id = temp.id";
 | 
        
           |  |  | 426 |         } else if ($dbfamily == 'postgres' || $dbfamily == 'mssql') {
 | 
        
           |  |  | 427 |             $updatesql = "UPDATE {context}
 | 
        
           |  |  | 428 |                              SET path = temp.path,
 | 
        
           |  |  | 429 |                                  depth = temp.depth,
 | 
        
           |  |  | 430 |                                  locked = temp.locked
 | 
        
           |  |  | 431 |                             FROM {context_temp} temp
 | 
        
           |  |  | 432 |                            WHERE temp.id={context}.id";
 | 
        
           |  |  | 433 |         } else {
 | 
        
           | 1441 | ariadna | 434 |             throw new \core\exception\coding_exception("Unsupported database family: {$dbfamily}");
 | 
        
           | 1 | efrain | 435 |         }
 | 
        
           |  |  | 436 |   | 
        
           |  |  | 437 |         $DB->execute($updatesql);
 | 
        
           |  |  | 438 |     }
 | 
        
           |  |  | 439 |   | 
        
           |  |  | 440 |     /**
 | 
        
           |  |  | 441 |      * Get a context instance as an object, from a given context id.
 | 
        
           |  |  | 442 |      *
 | 
        
           |  |  | 443 |      * @param int $id context id
 | 
        
           |  |  | 444 |      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
 | 
        
           |  |  | 445 |      *                        MUST_EXIST means throw exception if no record found
 | 
        
           |  |  | 446 |      * @return context|bool the context object or false if not found
 | 
        
           |  |  | 447 |      */
 | 
        
           |  |  | 448 |     public static function instance_by_id($id, $strictness = MUST_EXIST) {
 | 
        
           |  |  | 449 |         global $DB;
 | 
        
           |  |  | 450 |   | 
        
           |  |  | 451 |         if (get_called_class() !== 'core\context' && get_called_class() !== 'core\context_helper') {
 | 
        
           |  |  | 452 |             // Some devs might confuse context->id and instanceid, better prevent these mistakes completely.
 | 
        
           |  |  | 453 |             throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods');
 | 
        
           |  |  | 454 |         }
 | 
        
           |  |  | 455 |   | 
        
           |  |  | 456 |         if ($id == SYSCONTEXTID) {
 | 
        
           |  |  | 457 |             return context\system::instance(0, $strictness);
 | 
        
           |  |  | 458 |         }
 | 
        
           |  |  | 459 |   | 
        
           |  |  | 460 |         if (is_array($id) || is_object($id) || empty($id)) {
 | 
        
           |  |  | 461 |             throw new coding_exception('Invalid context id specified context::instance_by_id()');
 | 
        
           |  |  | 462 |         }
 | 
        
           |  |  | 463 |   | 
        
           |  |  | 464 |         if ($context = self::cache_get_by_id($id)) {
 | 
        
           |  |  | 465 |             return $context;
 | 
        
           |  |  | 466 |         }
 | 
        
           |  |  | 467 |   | 
        
           |  |  | 468 |         if ($record = $DB->get_record('context', array('id' => $id), '*', $strictness)) {
 | 
        
           |  |  | 469 |             return self::create_instance_from_record($record);
 | 
        
           |  |  | 470 |         }
 | 
        
           |  |  | 471 |   | 
        
           |  |  | 472 |         return false;
 | 
        
           |  |  | 473 |     }
 | 
        
           |  |  | 474 |   | 
        
           |  |  | 475 |     /**
 | 
        
           |  |  | 476 |      * Update context info after moving context in the tree structure.
 | 
        
           |  |  | 477 |      *
 | 
        
           |  |  | 478 |      * @param context $newparent
 | 
        
           |  |  | 479 |      * @return void
 | 
        
           |  |  | 480 |      */
 | 
        
           |  |  | 481 |     public function update_moved(context $newparent) {
 | 
        
           |  |  | 482 |         global $DB;
 | 
        
           |  |  | 483 |   | 
        
           |  |  | 484 |         $frompath = $this->_path;
 | 
        
           |  |  | 485 |         $newpath = $newparent->path . '/' . $this->_id;
 | 
        
           |  |  | 486 |   | 
        
           |  |  | 487 |         $trans = $DB->start_delegated_transaction();
 | 
        
           |  |  | 488 |   | 
        
           |  |  | 489 |         $setdepth = '';
 | 
        
           |  |  | 490 |         if (($newparent->depth + 1) != $this->_depth) {
 | 
        
           |  |  | 491 |             $diff = $newparent->depth - $this->_depth + 1;
 | 
        
           |  |  | 492 |             $setdepth = ", depth = depth + $diff";
 | 
        
           |  |  | 493 |         }
 | 
        
           |  |  | 494 |         $sql = "UPDATE {context}
 | 
        
           |  |  | 495 |                    SET path = ?
 | 
        
           |  |  | 496 |                        $setdepth
 | 
        
           |  |  | 497 |                  WHERE id = ?";
 | 
        
           |  |  | 498 |         $params = array($newpath, $this->_id);
 | 
        
           |  |  | 499 |         $DB->execute($sql, $params);
 | 
        
           |  |  | 500 |   | 
        
           |  |  | 501 |         $this->_path = $newpath;
 | 
        
           |  |  | 502 |         $this->_depth = $newparent->depth + 1;
 | 
        
           |  |  | 503 |   | 
        
           |  |  | 504 |         $sql = "UPDATE {context}
 | 
        
           |  |  | 505 |                    SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath) + 1))."
 | 
        
           |  |  | 506 |                        $setdepth
 | 
        
           |  |  | 507 |                  WHERE path LIKE ?";
 | 
        
           |  |  | 508 |         $params = array($newpath, "{$frompath}/%");
 | 
        
           |  |  | 509 |         $DB->execute($sql, $params);
 | 
        
           |  |  | 510 |   | 
        
           |  |  | 511 |         $this->mark_dirty();
 | 
        
           |  |  | 512 |   | 
        
           |  |  | 513 |         self::reset_caches();
 | 
        
           |  |  | 514 |   | 
        
           |  |  | 515 |         $trans->allow_commit();
 | 
        
           |  |  | 516 |     }
 | 
        
           |  |  | 517 |   | 
        
           |  |  | 518 |     /**
 | 
        
           |  |  | 519 |      * Set whether this context has been locked or not.
 | 
        
           |  |  | 520 |      *
 | 
        
           |  |  | 521 |      * @param   bool    $locked
 | 
        
           |  |  | 522 |      * @return  $this
 | 
        
           |  |  | 523 |      */
 | 
        
           |  |  | 524 |     public function set_locked(bool $locked) {
 | 
        
           |  |  | 525 |         global $DB;
 | 
        
           |  |  | 526 |   | 
        
           |  |  | 527 |         if ($this->_locked == $locked) {
 | 
        
           |  |  | 528 |             return $this;
 | 
        
           |  |  | 529 |         }
 | 
        
           |  |  | 530 |   | 
        
           |  |  | 531 |         $this->_locked = $locked;
 | 
        
           |  |  | 532 |         $DB->set_field('context', 'locked', (int) $locked, ['id' => $this->id]);
 | 
        
           |  |  | 533 |         $this->mark_dirty();
 | 
        
           |  |  | 534 |   | 
        
           |  |  | 535 |         if ($locked) {
 | 
        
           |  |  | 536 |             $eventname = '\\core\\event\\context_locked';
 | 
        
           |  |  | 537 |         } else {
 | 
        
           |  |  | 538 |             $eventname = '\\core\\event\\context_unlocked';
 | 
        
           |  |  | 539 |         }
 | 
        
           |  |  | 540 |         $event = $eventname::create(['context' => $this, 'objectid' => $this->id]);
 | 
        
           |  |  | 541 |         $event->trigger();
 | 
        
           |  |  | 542 |   | 
        
           |  |  | 543 |         self::reset_caches();
 | 
        
           |  |  | 544 |   | 
        
           |  |  | 545 |         return $this;
 | 
        
           |  |  | 546 |     }
 | 
        
           |  |  | 547 |   | 
        
           |  |  | 548 |     /**
 | 
        
           |  |  | 549 |      * Remove all context path info and optionally rebuild it.
 | 
        
           |  |  | 550 |      *
 | 
        
           |  |  | 551 |      * @param bool $rebuild
 | 
        
           |  |  | 552 |      * @return void
 | 
        
           |  |  | 553 |      */
 | 
        
           |  |  | 554 |     public function reset_paths($rebuild = true) {
 | 
        
           |  |  | 555 |         global $DB;
 | 
        
           |  |  | 556 |   | 
        
           |  |  | 557 |         if ($this->_path) {
 | 
        
           |  |  | 558 |             $this->mark_dirty();
 | 
        
           |  |  | 559 |         }
 | 
        
           |  |  | 560 |         $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'");
 | 
        
           |  |  | 561 |         $DB->set_field_select('context', 'path', null, "path LIKE '%/$this->_id/%'");
 | 
        
           |  |  | 562 |         if ($this->_contextlevel != CONTEXT_SYSTEM) {
 | 
        
           |  |  | 563 |             $DB->set_field('context', 'depth', 0, array('id' => $this->_id));
 | 
        
           |  |  | 564 |             $DB->set_field('context', 'path', null, array('id' => $this->_id));
 | 
        
           |  |  | 565 |             $this->_depth = 0;
 | 
        
           |  |  | 566 |             $this->_path = null;
 | 
        
           |  |  | 567 |         }
 | 
        
           |  |  | 568 |   | 
        
           |  |  | 569 |         if ($rebuild) {
 | 
        
           |  |  | 570 |             context_helper::build_all_paths(false);
 | 
        
           |  |  | 571 |         }
 | 
        
           |  |  | 572 |   | 
        
           |  |  | 573 |         self::reset_caches();
 | 
        
           |  |  | 574 |     }
 | 
        
           |  |  | 575 |   | 
        
           |  |  | 576 |     /**
 | 
        
           |  |  | 577 |      * Delete all data linked to content, do not delete the context record itself
 | 
        
           |  |  | 578 |      */
 | 
        
           |  |  | 579 |     public function delete_content() {
 | 
        
           |  |  | 580 |         global $CFG, $DB;
 | 
        
           |  |  | 581 |   | 
        
           |  |  | 582 |         blocks_delete_all_for_context($this->_id);
 | 
        
           |  |  | 583 |         filter_delete_all_for_context($this->_id);
 | 
        
           |  |  | 584 |   | 
        
           |  |  | 585 |         require_once($CFG->dirroot . '/comment/lib.php');
 | 
        
           |  |  | 586 |         \comment::delete_comments(array('contextid' => $this->_id));
 | 
        
           |  |  | 587 |   | 
        
           |  |  | 588 |         require_once($CFG->dirroot.'/rating/lib.php');
 | 
        
           |  |  | 589 |         $delopt = new stdclass();
 | 
        
           |  |  | 590 |         $delopt->contextid = $this->_id;
 | 
        
           |  |  | 591 |         $rm = new \rating_manager();
 | 
        
           |  |  | 592 |         $rm->delete_ratings($delopt);
 | 
        
           |  |  | 593 |   | 
        
           |  |  | 594 |         // Delete all files attached to this context.
 | 
        
           |  |  | 595 |         $fs = get_file_storage();
 | 
        
           |  |  | 596 |         $fs->delete_area_files($this->_id);
 | 
        
           |  |  | 597 |   | 
        
           |  |  | 598 |         // Delete all repository instances attached to this context.
 | 
        
           |  |  | 599 |         require_once($CFG->dirroot . '/repository/lib.php');
 | 
        
           |  |  | 600 |         \repository::delete_all_for_context($this->_id);
 | 
        
           |  |  | 601 |   | 
        
           |  |  | 602 |         // Delete all advanced grading data attached to this context.
 | 
        
           |  |  | 603 |         require_once($CFG->dirroot.'/grade/grading/lib.php');
 | 
        
           |  |  | 604 |         \grading_manager::delete_all_for_context($this->_id);
 | 
        
           |  |  | 605 |   | 
        
           |  |  | 606 |         // Now delete stuff from role related tables, role_unassign_all
 | 
        
           |  |  | 607 |         // and unenrol should be called earlier to do proper cleanup.
 | 
        
           |  |  | 608 |         $DB->delete_records('role_assignments', array('contextid' => $this->_id));
 | 
        
           |  |  | 609 |         $DB->delete_records('role_names', array('contextid' => $this->_id));
 | 
        
           |  |  | 610 |         $this->delete_capabilities();
 | 
        
           |  |  | 611 |     }
 | 
        
           |  |  | 612 |   | 
        
           |  |  | 613 |     /**
 | 
        
           |  |  | 614 |      * Unassign all capabilities from a context.
 | 
        
           |  |  | 615 |      */
 | 
        
           |  |  | 616 |     public function delete_capabilities() {
 | 
        
           |  |  | 617 |         global $DB;
 | 
        
           |  |  | 618 |   | 
        
           |  |  | 619 |         $ids = $DB->get_fieldset_select('role_capabilities', 'DISTINCT roleid', 'contextid = ?', array($this->_id));
 | 
        
           |  |  | 620 |         if ($ids) {
 | 
        
           |  |  | 621 |             $DB->delete_records('role_capabilities', array('contextid' => $this->_id));
 | 
        
           |  |  | 622 |   | 
        
           |  |  | 623 |             // Reset any cache of these roles, including MUC.
 | 
        
           |  |  | 624 |             accesslib_clear_role_cache($ids);
 | 
        
           |  |  | 625 |         }
 | 
        
           |  |  | 626 |     }
 | 
        
           |  |  | 627 |   | 
        
           |  |  | 628 |     /**
 | 
        
           |  |  | 629 |      * Delete the context content and the context record itself
 | 
        
           |  |  | 630 |      */
 | 
        
           |  |  | 631 |     public function delete() {
 | 
        
           |  |  | 632 |         global $DB;
 | 
        
           |  |  | 633 |   | 
        
           |  |  | 634 |         if ($this->_contextlevel <= CONTEXT_SYSTEM) {
 | 
        
           |  |  | 635 |             throw new coding_exception('Cannot delete system context');
 | 
        
           |  |  | 636 |         }
 | 
        
           |  |  | 637 |   | 
        
           |  |  | 638 |         // Double check the context still exists.
 | 
        
           |  |  | 639 |         if (!$DB->record_exists('context', array('id' => $this->_id))) {
 | 
        
           |  |  | 640 |             self::cache_remove($this);
 | 
        
           |  |  | 641 |             return;
 | 
        
           |  |  | 642 |         }
 | 
        
           |  |  | 643 |   | 
        
           |  |  | 644 |         $this->delete_content();
 | 
        
           |  |  | 645 |         $DB->delete_records('context', array('id' => $this->_id));
 | 
        
           |  |  | 646 |         // Purge static context cache if entry present.
 | 
        
           |  |  | 647 |         self::cache_remove($this);
 | 
        
           |  |  | 648 |   | 
        
           |  |  | 649 |         // Inform search engine to delete data related to this context.
 | 
        
           |  |  | 650 |         \core_search\manager::context_deleted($this);
 | 
        
           |  |  | 651 |     }
 | 
        
           |  |  | 652 |   | 
        
           |  |  | 653 |     /* ====== context level related methods ====== */
 | 
        
           |  |  | 654 |   | 
        
           |  |  | 655 |     /**
 | 
        
           |  |  | 656 |      * Utility method for context creation
 | 
        
           |  |  | 657 |      *
 | 
        
           |  |  | 658 |      * @param int $contextlevel
 | 
        
           |  |  | 659 |      * @param int $instanceid
 | 
        
           |  |  | 660 |      * @param string $parentpath
 | 
        
           |  |  | 661 |      * @return stdClass context record
 | 
        
           |  |  | 662 |      */
 | 
        
           |  |  | 663 |     protected static function insert_context_record($contextlevel, $instanceid, $parentpath) {
 | 
        
           |  |  | 664 |         global $DB;
 | 
        
           |  |  | 665 |   | 
        
           |  |  | 666 |         $record = new stdClass();
 | 
        
           |  |  | 667 |         $record->contextlevel = $contextlevel;
 | 
        
           |  |  | 668 |         $record->instanceid = $instanceid;
 | 
        
           |  |  | 669 |         $record->depth = 0;
 | 
        
           |  |  | 670 |         $record->path = null; // Not known before insert.
 | 
        
           |  |  | 671 |         $record->locked = 0;
 | 
        
           |  |  | 672 |   | 
        
           |  |  | 673 |         $record->id = $DB->insert_record('context', $record);
 | 
        
           |  |  | 674 |   | 
        
           |  |  | 675 |         // Now add path if known - it can be added later.
 | 
        
           |  |  | 676 |         if (!is_null($parentpath)) {
 | 
        
           |  |  | 677 |             $record->path = $parentpath.'/'.$record->id;
 | 
        
           |  |  | 678 |             $record->depth = substr_count($record->path, '/');
 | 
        
           |  |  | 679 |             $DB->update_record('context', $record);
 | 
        
           |  |  | 680 |         }
 | 
        
           |  |  | 681 |   | 
        
           |  |  | 682 |         return $record;
 | 
        
           |  |  | 683 |     }
 | 
        
           |  |  | 684 |   | 
        
           |  |  | 685 |     /**
 | 
        
           |  |  | 686 |      * Returns human readable context identifier.
 | 
        
           |  |  | 687 |      *
 | 
        
           |  |  | 688 |      * @param boolean $withprefix whether to prefix the name of the context with the
 | 
        
           |  |  | 689 |      *      type of context, e.g. User, Course, Forum, etc.
 | 
        
           |  |  | 690 |      * @param boolean $short whether to use the short name of the thing. Only applies
 | 
        
           |  |  | 691 |      *      to course contexts
 | 
        
           |  |  | 692 |      * @param boolean $escape Whether the returned name of the thing is to be
 | 
        
           |  |  | 693 |      *      HTML escaped or not.
 | 
        
           |  |  | 694 |      * @return string the human readable context name.
 | 
        
           |  |  | 695 |      */
 | 
        
           |  |  | 696 |     public function get_context_name($withprefix = true, $short = false, $escape = true) {
 | 
        
           |  |  | 697 |         // Must be implemented in all context levels.
 | 
        
           |  |  | 698 |         throw new coding_exception('can not get name of abstract context');
 | 
        
           |  |  | 699 |     }
 | 
        
           |  |  | 700 |   | 
        
           |  |  | 701 |     /**
 | 
        
           |  |  | 702 |      * Whether the current context is locked.
 | 
        
           |  |  | 703 |      *
 | 
        
           |  |  | 704 |      * @return  bool
 | 
        
           |  |  | 705 |      */
 | 
        
           |  |  | 706 |     public function is_locked() {
 | 
        
           |  |  | 707 |         if ($this->_locked) {
 | 
        
           |  |  | 708 |             return true;
 | 
        
           |  |  | 709 |         }
 | 
        
           |  |  | 710 |   | 
        
           |  |  | 711 |         if ($parent = $this->get_parent_context()) {
 | 
        
           |  |  | 712 |             return $parent->is_locked();
 | 
        
           |  |  | 713 |         }
 | 
        
           |  |  | 714 |   | 
        
           |  |  | 715 |         return false;
 | 
        
           |  |  | 716 |     }
 | 
        
           |  |  | 717 |   | 
        
           |  |  | 718 |     /**
 | 
        
           |  |  | 719 |      * Returns the most relevant URL for this context.
 | 
        
           |  |  | 720 |      *
 | 
        
           |  |  | 721 |      * @return moodle_url
 | 
        
           |  |  | 722 |      */
 | 
        
           |  |  | 723 |     abstract public function get_url();
 | 
        
           |  |  | 724 |   | 
        
           |  |  | 725 |     /**
 | 
        
           |  |  | 726 |      * Returns context instance database name.
 | 
        
           |  |  | 727 |      *
 | 
        
           |  |  | 728 |      * @return string|null table name for all levels except system.
 | 
        
           |  |  | 729 |      */
 | 
        
           |  |  | 730 |     protected static function get_instance_table(): ?string {
 | 
        
           |  |  | 731 |         return null;
 | 
        
           |  |  | 732 |     }
 | 
        
           |  |  | 733 |   | 
        
           |  |  | 734 |     /**
 | 
        
           |  |  | 735 |      * Returns list of columns that can be used from behat
 | 
        
           |  |  | 736 |      * to look up context by reference.
 | 
        
           |  |  | 737 |      *
 | 
        
           |  |  | 738 |      * @return array list of column names from instance table
 | 
        
           |  |  | 739 |      */
 | 
        
           |  |  | 740 |     protected static function get_behat_reference_columns(): array {
 | 
        
           |  |  | 741 |         return [];
 | 
        
           |  |  | 742 |     }
 | 
        
           |  |  | 743 |   | 
        
           |  |  | 744 |     /**
 | 
        
           |  |  | 745 |      * Returns list of all role archetypes that are compatible
 | 
        
           |  |  | 746 |      * with role assignments in context level.
 | 
        
           |  |  | 747 |      * @since Moodle 4.2
 | 
        
           |  |  | 748 |      *
 | 
        
           |  |  | 749 |      * @return string[]
 | 
        
           |  |  | 750 |      */
 | 
        
           |  |  | 751 |     protected static function get_compatible_role_archetypes(): array {
 | 
        
           |  |  | 752 |         // Override if archetype roles should be allowed to be assigned in context level.
 | 
        
           |  |  | 753 |         return [];
 | 
        
           |  |  | 754 |     }
 | 
        
           |  |  | 755 |   | 
        
           |  |  | 756 |     /**
 | 
        
           |  |  | 757 |      * Returns list of all possible parent context levels,
 | 
        
           |  |  | 758 |      * it may include itself if nesting is allowed.
 | 
        
           |  |  | 759 |      * @since Moodle 4.2
 | 
        
           |  |  | 760 |      *
 | 
        
           |  |  | 761 |      * @return int[]
 | 
        
           |  |  | 762 |      */
 | 
        
           |  |  | 763 |     public static function get_possible_parent_levels(): array {
 | 
        
           |  |  | 764 |         // Override if other type of parents are expected.
 | 
        
           |  |  | 765 |         return [context\system::LEVEL];
 | 
        
           |  |  | 766 |     }
 | 
        
           |  |  | 767 |   | 
        
           |  |  | 768 |     /**
 | 
        
           |  |  | 769 |      * Returns array of relevant context capability records.
 | 
        
           |  |  | 770 |      *
 | 
        
           |  |  | 771 |      * @param string $sort SQL order by snippet for sorting returned capabilities sensibly for display
 | 
        
           |  |  | 772 |      * @return array
 | 
        
           |  |  | 773 |      */
 | 
        
           |  |  | 774 |     abstract public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT);
 | 
        
           |  |  | 775 |   | 
        
           |  |  | 776 |     /**
 | 
        
           |  |  | 777 |      * Recursive function which, given a context, find all its children context ids.
 | 
        
           |  |  | 778 |      *
 | 
        
           |  |  | 779 |      * For course category contexts it will return immediate children and all subcategory contexts.
 | 
        
           |  |  | 780 |      * It will NOT recurse into courses or subcategories categories.
 | 
        
           |  |  | 781 |      * If you want to do that, call it on the returned courses/categories.
 | 
        
           |  |  | 782 |      *
 | 
        
           |  |  | 783 |      * When called for a course context, it will return the modules and blocks
 | 
        
           |  |  | 784 |      * displayed in the course page and blocks displayed on the module pages.
 | 
        
           |  |  | 785 |      *
 | 
        
           |  |  | 786 |      * If called on a user/course/module context it _will_ populate the cache with the appropriate
 | 
        
           |  |  | 787 |      * contexts ;-)
 | 
        
           |  |  | 788 |      *
 | 
        
           |  |  | 789 |      * @return array Array of child records
 | 
        
           |  |  | 790 |      */
 | 
        
           |  |  | 791 |     public function get_child_contexts() {
 | 
        
           |  |  | 792 |         global $DB;
 | 
        
           |  |  | 793 |   | 
        
           |  |  | 794 |         if (empty($this->_path) || empty($this->_depth)) {
 | 
        
           |  |  | 795 |             debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
 | 
        
           |  |  | 796 |             return array();
 | 
        
           |  |  | 797 |         }
 | 
        
           |  |  | 798 |   | 
        
           |  |  | 799 |         $sql = "SELECT ctx.*
 | 
        
           |  |  | 800 |                   FROM {context} ctx
 | 
        
           |  |  | 801 |                  WHERE ctx.path LIKE ?";
 | 
        
           |  |  | 802 |         $params = array($this->_path.'/%');
 | 
        
           |  |  | 803 |         $records = $DB->get_records_sql($sql, $params);
 | 
        
           |  |  | 804 |   | 
        
           |  |  | 805 |         $result = array();
 | 
        
           |  |  | 806 |         foreach ($records as $record) {
 | 
        
           |  |  | 807 |             $result[$record->id] = self::create_instance_from_record($record);
 | 
        
           |  |  | 808 |         }
 | 
        
           |  |  | 809 |   | 
        
           |  |  | 810 |         return $result;
 | 
        
           |  |  | 811 |     }
 | 
        
           |  |  | 812 |   | 
        
           |  |  | 813 |     /**
 | 
        
           |  |  | 814 |      * Determine if the current context is a parent of the possible child.
 | 
        
           |  |  | 815 |      *
 | 
        
           |  |  | 816 |      * @param   context $possiblechild
 | 
        
           |  |  | 817 |      * @param   bool $includeself Whether to check the current context
 | 
        
           |  |  | 818 |      * @return  bool
 | 
        
           |  |  | 819 |      */
 | 
        
           |  |  | 820 |     public function is_parent_of(context $possiblechild, bool $includeself): bool {
 | 
        
           |  |  | 821 |         // A simple substring check is used on the context path.
 | 
        
           |  |  | 822 |         // The possible child's path is used as a haystack, with the current context as the needle.
 | 
        
           |  |  | 823 |         // The path is prefixed with '+' to ensure that the parent always starts at the top.
 | 
        
           |  |  | 824 |         // It is suffixed with '+' to ensure that parents are not included.
 | 
        
           |  |  | 825 |         // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
 | 
        
           |  |  | 826 |         // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
 | 
        
           |  |  | 827 |         // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
 | 
        
           |  |  | 828 |         $haystacksuffix = $includeself ? '/+' : '+';
 | 
        
           |  |  | 829 |   | 
        
           |  |  | 830 |         $strpos = strpos(
 | 
        
           |  |  | 831 |             "+{$possiblechild->path}{$haystacksuffix}",
 | 
        
           |  |  | 832 |             "+{$this->path}/"
 | 
        
           |  |  | 833 |         );
 | 
        
           |  |  | 834 |         return $strpos === 0;
 | 
        
           |  |  | 835 |     }
 | 
        
           |  |  | 836 |   | 
        
           |  |  | 837 |     /**
 | 
        
           |  |  | 838 |      * Returns parent contexts of this context in reversed order, i.e. parent first,
 | 
        
           |  |  | 839 |      * then grand parent, etc.
 | 
        
           |  |  | 840 |      *
 | 
        
           |  |  | 841 |      * @param bool $includeself true means include self too
 | 
        
           |  |  | 842 |      * @return array of context instances
 | 
        
           |  |  | 843 |      */
 | 
        
           |  |  | 844 |     public function get_parent_contexts($includeself = false) {
 | 
        
           |  |  | 845 |         if (!$contextids = $this->get_parent_context_ids($includeself)) {
 | 
        
           |  |  | 846 |             return array();
 | 
        
           |  |  | 847 |         }
 | 
        
           |  |  | 848 |   | 
        
           |  |  | 849 |         // Preload the contexts to reduce DB calls.
 | 
        
           |  |  | 850 |         context_helper::preload_contexts_by_id($contextids);
 | 
        
           |  |  | 851 |   | 
        
           |  |  | 852 |         $result = array();
 | 
        
           |  |  | 853 |         foreach ($contextids as $contextid) {
 | 
        
           |  |  | 854 |             // Do NOT change this to self!
 | 
        
           |  |  | 855 |             $parent = context_helper::instance_by_id($contextid, MUST_EXIST);
 | 
        
           |  |  | 856 |             $result[$parent->id] = $parent;
 | 
        
           |  |  | 857 |         }
 | 
        
           |  |  | 858 |   | 
        
           |  |  | 859 |         return $result;
 | 
        
           |  |  | 860 |     }
 | 
        
           |  |  | 861 |   | 
        
           |  |  | 862 |     /**
 | 
        
           |  |  | 863 |      * Determine if the current context is a child of the possible parent.
 | 
        
           |  |  | 864 |      *
 | 
        
           |  |  | 865 |      * @param   context $possibleparent
 | 
        
           |  |  | 866 |      * @param   bool $includeself Whether to check the current context
 | 
        
           |  |  | 867 |      * @return  bool
 | 
        
           |  |  | 868 |      */
 | 
        
           |  |  | 869 |     public function is_child_of(context $possibleparent, bool $includeself): bool {
 | 
        
           |  |  | 870 |         // A simple substring check is used on the context path.
 | 
        
           |  |  | 871 |         // The current context is used as a haystack, with the possible parent as the needle.
 | 
        
           |  |  | 872 |         // The path is prefixed with '+' to ensure that the parent always starts at the top.
 | 
        
           |  |  | 873 |         // It is suffixed with '+' to ensure that children are not included.
 | 
        
           |  |  | 874 |         // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
 | 
        
           |  |  | 875 |         // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
 | 
        
           |  |  | 876 |         // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
 | 
        
           |  |  | 877 |         $haystacksuffix = $includeself ? '/+' : '+';
 | 
        
           |  |  | 878 |   | 
        
           |  |  | 879 |         $strpos = strpos(
 | 
        
           |  |  | 880 |             "+{$this->path}{$haystacksuffix}",
 | 
        
           |  |  | 881 |             "+{$possibleparent->path}/"
 | 
        
           |  |  | 882 |         );
 | 
        
           |  |  | 883 |         return $strpos === 0;
 | 
        
           |  |  | 884 |     }
 | 
        
           |  |  | 885 |   | 
        
           |  |  | 886 |     /**
 | 
        
           |  |  | 887 |      * Returns parent context ids of this context in reversed order, i.e. parent first,
 | 
        
           |  |  | 888 |      * then grand parent, etc.
 | 
        
           |  |  | 889 |      *
 | 
        
           |  |  | 890 |      * @param bool $includeself true means include self too
 | 
        
           |  |  | 891 |      * @return array of context ids
 | 
        
           |  |  | 892 |      */
 | 
        
           |  |  | 893 |     public function get_parent_context_ids($includeself = false) {
 | 
        
           |  |  | 894 |         if (empty($this->_path)) {
 | 
        
           |  |  | 895 |             return array();
 | 
        
           |  |  | 896 |         }
 | 
        
           |  |  | 897 |   | 
        
           |  |  | 898 |         $parentcontexts = trim($this->_path, '/'); // Kill leading slash.
 | 
        
           |  |  | 899 |         $parentcontexts = explode('/', $parentcontexts);
 | 
        
           |  |  | 900 |         if (!$includeself) {
 | 
        
           |  |  | 901 |             array_pop($parentcontexts); // And remove its own id.
 | 
        
           |  |  | 902 |         }
 | 
        
           |  |  | 903 |   | 
        
           |  |  | 904 |         return array_reverse($parentcontexts);
 | 
        
           |  |  | 905 |     }
 | 
        
           |  |  | 906 |   | 
        
           |  |  | 907 |     /**
 | 
        
           |  |  | 908 |      * Returns parent context paths of this context.
 | 
        
           |  |  | 909 |      *
 | 
        
           |  |  | 910 |      * @param bool $includeself true means include self too
 | 
        
           |  |  | 911 |      * @return array of context paths
 | 
        
           |  |  | 912 |      */
 | 
        
           |  |  | 913 |     public function get_parent_context_paths($includeself = false) {
 | 
        
           |  |  | 914 |         if (empty($this->_path)) {
 | 
        
           |  |  | 915 |             return array();
 | 
        
           |  |  | 916 |         }
 | 
        
           |  |  | 917 |   | 
        
           |  |  | 918 |         $contextids = explode('/', $this->_path);
 | 
        
           |  |  | 919 |   | 
        
           |  |  | 920 |         $path = '';
 | 
        
           |  |  | 921 |         $paths = array();
 | 
        
           |  |  | 922 |         foreach ($contextids as $contextid) {
 | 
        
           |  |  | 923 |             if ($contextid) {
 | 
        
           |  |  | 924 |                 $path .= '/' . $contextid;
 | 
        
           |  |  | 925 |                 $paths[$contextid] = $path;
 | 
        
           |  |  | 926 |             }
 | 
        
           |  |  | 927 |         }
 | 
        
           |  |  | 928 |   | 
        
           |  |  | 929 |         if (!$includeself) {
 | 
        
           |  |  | 930 |             unset($paths[$this->_id]);
 | 
        
           |  |  | 931 |         }
 | 
        
           |  |  | 932 |   | 
        
           |  |  | 933 |         return $paths;
 | 
        
           |  |  | 934 |     }
 | 
        
           |  |  | 935 |   | 
        
           |  |  | 936 |     /**
 | 
        
           |  |  | 937 |      * Returns parent context
 | 
        
           |  |  | 938 |      *
 | 
        
           |  |  | 939 |      * @return context|false
 | 
        
           |  |  | 940 |      */
 | 
        
           |  |  | 941 |     public function get_parent_context() {
 | 
        
           |  |  | 942 |         if (empty($this->_path) || $this->_id == SYSCONTEXTID) {
 | 
        
           |  |  | 943 |             return false;
 | 
        
           |  |  | 944 |         }
 | 
        
           |  |  | 945 |   | 
        
           |  |  | 946 |         $parentcontexts = trim($this->_path, '/'); // Kill leading slash.
 | 
        
           |  |  | 947 |         $parentcontexts = explode('/', $parentcontexts);
 | 
        
           |  |  | 948 |         array_pop($parentcontexts); // Self.
 | 
        
           |  |  | 949 |         $contextid = array_pop($parentcontexts); // Immediate parent.
 | 
        
           |  |  | 950 |   | 
        
           |  |  | 951 |         // Do NOT change this to self!
 | 
        
           |  |  | 952 |         return context_helper::instance_by_id($contextid, MUST_EXIST);
 | 
        
           |  |  | 953 |     }
 | 
        
           |  |  | 954 |   | 
        
           |  |  | 955 |     /**
 | 
        
           |  |  | 956 |      * Is this context part of any course? If yes return course context.
 | 
        
           |  |  | 957 |      *
 | 
        
           |  |  | 958 |      * @param bool $strict true means throw exception if not found, false means return false if not found
 | 
        
           |  |  | 959 |      * @return context\course|false context of the enclosing course, null if not found or exception
 | 
        
           |  |  | 960 |      */
 | 
        
           |  |  | 961 |     public function get_course_context($strict = true) {
 | 
        
           |  |  | 962 |         if ($strict) {
 | 
        
           |  |  | 963 |             throw new coding_exception('Context does not belong to any course.');
 | 
        
           |  |  | 964 |         } else {
 | 
        
           |  |  | 965 |             return false;
 | 
        
           |  |  | 966 |         }
 | 
        
           |  |  | 967 |     }
 | 
        
           |  |  | 968 |   | 
        
           |  |  | 969 |     /**
 | 
        
           |  |  | 970 |      * Returns sql necessary for purging of stale context instances.
 | 
        
           |  |  | 971 |      *
 | 
        
           |  |  | 972 |      * @return string cleanup SQL
 | 
        
           |  |  | 973 |      */
 | 
        
           |  |  | 974 |     protected static function get_cleanup_sql() {
 | 
        
           |  |  | 975 |         throw new coding_exception('get_cleanup_sql() method must be implemented in all context levels');
 | 
        
           |  |  | 976 |     }
 | 
        
           |  |  | 977 |   | 
        
           |  |  | 978 |     /**
 | 
        
           |  |  | 979 |      * Rebuild context paths and depths at context level.
 | 
        
           |  |  | 980 |      *
 | 
        
           |  |  | 981 |      * @param bool $force
 | 
        
           |  |  | 982 |      * @return void
 | 
        
           |  |  | 983 |      */
 | 
        
           |  |  | 984 |     protected static function build_paths($force) {
 | 
        
           |  |  | 985 |         throw new coding_exception('build_paths() method must be implemented in all context levels');
 | 
        
           |  |  | 986 |     }
 | 
        
           |  |  | 987 |   | 
        
           |  |  | 988 |     /**
 | 
        
           |  |  | 989 |      * Create missing context instances at given level
 | 
        
           |  |  | 990 |      *
 | 
        
           |  |  | 991 |      * @return void
 | 
        
           |  |  | 992 |      */
 | 
        
           |  |  | 993 |     protected static function create_level_instances() {
 | 
        
           |  |  | 994 |         throw new coding_exception('create_level_instances() method must be implemented in all context levels');
 | 
        
           |  |  | 995 |     }
 | 
        
           |  |  | 996 |   | 
        
           |  |  | 997 |     /**
 | 
        
           |  |  | 998 |      * Reset all cached permissions and definitions if the necessary.
 | 
        
           |  |  | 999 |      * @return void
 | 
        
           |  |  | 1000 |      */
 | 
        
           |  |  | 1001 |     public function reload_if_dirty() {
 | 
        
           |  |  | 1002 |         global $ACCESSLIB_PRIVATE, $USER;
 | 
        
           |  |  | 1003 |   | 
        
           |  |  | 1004 |         // Load dirty contexts list if needed.
 | 
        
           |  |  | 1005 |         if (CLI_SCRIPT) {
 | 
        
           |  |  | 1006 |             if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
 | 
        
           |  |  | 1007 |                 // We do not load dirty flags in CLI and cron.
 | 
        
           |  |  | 1008 |                 $ACCESSLIB_PRIVATE->dirtycontexts = array();
 | 
        
           |  |  | 1009 |             }
 | 
        
           |  |  | 1010 |         } else {
 | 
        
           |  |  | 1011 |             if (!isset($USER->access['time'])) {
 | 
        
           |  |  | 1012 |                 // Nothing has been loaded yet, so we do not need to check dirty flags now.
 | 
        
           |  |  | 1013 |                 return;
 | 
        
           |  |  | 1014 |             }
 | 
        
           |  |  | 1015 |   | 
        
           |  |  | 1016 |             // From skodak: No idea why -2 is there, server cluster time difference maybe...
 | 
        
           |  |  | 1017 |             $changedsince = $USER->access['time'] - 2;
 | 
        
           |  |  | 1018 |   | 
        
           |  |  | 1019 |             if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
 | 
        
           |  |  | 1020 |                 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $changedsince);
 | 
        
           |  |  | 1021 |             }
 | 
        
           |  |  | 1022 |   | 
        
           |  |  | 1023 |             if (!isset($ACCESSLIB_PRIVATE->dirtyusers[$USER->id])) {
 | 
        
           |  |  | 1024 |                 $ACCESSLIB_PRIVATE->dirtyusers[$USER->id] = get_cache_flag('accesslib/dirtyusers', $USER->id, $changedsince);
 | 
        
           |  |  | 1025 |             }
 | 
        
           |  |  | 1026 |         }
 | 
        
           |  |  | 1027 |   | 
        
           |  |  | 1028 |         $dirty = false;
 | 
        
           |  |  | 1029 |   | 
        
           |  |  | 1030 |         if (!empty($ACCESSLIB_PRIVATE->dirtyusers[$USER->id])) {
 | 
        
           |  |  | 1031 |             $dirty = true;
 | 
        
           |  |  | 1032 |         } else if (!empty($ACCESSLIB_PRIVATE->dirtycontexts)) {
 | 
        
           |  |  | 1033 |             $paths = $this->get_parent_context_paths(true);
 | 
        
           |  |  | 1034 |   | 
        
           |  |  | 1035 |             foreach ($paths as $path) {
 | 
        
           |  |  | 1036 |                 if (isset($ACCESSLIB_PRIVATE->dirtycontexts[$path])) {
 | 
        
           |  |  | 1037 |                     $dirty = true;
 | 
        
           |  |  | 1038 |                     break;
 | 
        
           |  |  | 1039 |                 }
 | 
        
           |  |  | 1040 |             }
 | 
        
           |  |  | 1041 |         }
 | 
        
           |  |  | 1042 |   | 
        
           |  |  | 1043 |         if ($dirty) {
 | 
        
           |  |  | 1044 |             // Reload all capabilities of USER and others - preserving loginas, roleswitches, etc.
 | 
        
           |  |  | 1045 |             // Then cleanup any marks of dirtyness... at least from our short term memory!
 | 
        
           |  |  | 1046 |             reload_all_capabilities();
 | 
        
           |  |  | 1047 |         }
 | 
        
           |  |  | 1048 |     }
 | 
        
           |  |  | 1049 |   | 
        
           |  |  | 1050 |     /**
 | 
        
           |  |  | 1051 |      * Mark a context as dirty (with timestamp) so as to force reloading of the context.
 | 
        
           |  |  | 1052 |      */
 | 
        
           |  |  | 1053 |     public function mark_dirty() {
 | 
        
           |  |  | 1054 |         global $CFG, $USER, $ACCESSLIB_PRIVATE;
 | 
        
           |  |  | 1055 |   | 
        
           |  |  | 1056 |         if (during_initial_install()) {
 | 
        
           |  |  | 1057 |             return;
 | 
        
           |  |  | 1058 |         }
 | 
        
           |  |  | 1059 |   | 
        
           |  |  | 1060 |         // Only if it is a non-empty string.
 | 
        
           |  |  | 1061 |         if (is_string($this->_path) && $this->_path !== '') {
 | 
        
           |  |  | 1062 |             set_cache_flag('accesslib/dirtycontexts', $this->_path, 1, time() + $CFG->sessiontimeout);
 | 
        
           |  |  | 1063 |             if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
 | 
        
           |  |  | 1064 |                 $ACCESSLIB_PRIVATE->dirtycontexts[$this->_path] = 1;
 | 
        
           |  |  | 1065 |             } else {
 | 
        
           |  |  | 1066 |                 if (CLI_SCRIPT) {
 | 
        
           |  |  | 1067 |                     $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
 | 
        
           |  |  | 1068 |                 } else {
 | 
        
           |  |  | 1069 |                     if (isset($USER->access['time'])) {
 | 
        
           |  |  | 1070 |                         $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time'] - 2);
 | 
        
           |  |  | 1071 |                     } else {
 | 
        
           |  |  | 1072 |                         $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
 | 
        
           |  |  | 1073 |                     }
 | 
        
           |  |  | 1074 |                     // Flags not loaded yet, it will be done later in $context->reload_if_dirty().
 | 
        
           |  |  | 1075 |                 }
 | 
        
           |  |  | 1076 |             }
 | 
        
           |  |  | 1077 |         }
 | 
        
           |  |  | 1078 |     }
 | 
        
           |  |  | 1079 | }
 |