2 // This file is part of Moodle - https://moodle.org/
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.
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.
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/>.
19 use stdClass
, IteratorAggregate
, ArrayIterator
;
20 use coding_exception
, moodle_url
;
23 * Basic moodle context abstraction class.
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...
29 * This class is derived from stdClass for backwards compatibility with
30 * odl $context record that was returned from DML $DB->get_record()
32 * @package core_access
34 * @copyright Petr Skoda
35 * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
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
45 abstract class context
extends stdClass
implements IteratorAggregate
{
47 /** @var string Default sorting of capabilities in {@see get_capabilities} */
48 protected const DEFAULT_CAPABILITY_SORT
= 'contextlevel, component, name';
52 * Can be accessed publicly through $context->id
59 * Can be accessed publicly through $context->contextlevel
60 * @var int One of CONTEXT_* e.g. CONTEXT_COURSE, CONTEXT_MODULE
62 protected $_contextlevel;
65 * Id of the item this context is related to e.g. COURSE_CONTEXT => course.id
66 * Can be accessed publicly through $context->instanceid
69 protected $_instanceid;
72 * The path to the context always starting from the system context
73 * Can be accessed publicly through $context->path
79 * The depth of the context in relation to parent contexts
80 * Can be accessed publicly through $context->depth
86 * Whether this context is locked or not.
88 * Can be accessed publicly through $context->locked.
95 * @var array Context caching info
97 private static $cache_contextsbyid = array();
100 * @var array Context caching info
102 private static $cache_contexts = array();
106 * Why do we do count contexts? Because count($array) is horribly slow for large arrays
109 protected static $cache_count = 0;
112 * @var array Context caching info
114 protected static $cache_preloaded = array();
117 * @var context\system The system context once initialised
119 protected static $systemcontext = null;
122 * Returns short context name.
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');
135 * Resets the cache to remove all data.
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();
143 self
::$systemcontext = null;
147 * Adds a context to the cache. If the cache is full, discards a batch of
150 * @param context $context New context to add
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.
159 if (self
::$cache_count >= CONTEXT_CACHE_MAX_SIZE
) {
161 foreach (self
::$cache_contextsbyid as $ctx) {
164 // We want to keep the first contexts to be loaded on this page, hopefully they will be needed again later.
167 if ($i > (CONTEXT_CACHE_MAX_SIZE
/ 3)) {
168 // We remove oldest third of the contexts to make room for more contexts.
171 unset(self
::$cache_contextsbyid[$ctx->id
]);
172 unset(self
::$cache_contexts[$ctx->contextlevel
][$ctx->instanceid
]);
173 self
::$cache_count--;
177 self
::$cache_contexts[$context->contextlevel
][$context->instanceid
] = $context;
178 self
::$cache_contextsbyid[$context->id
] = $context;
179 self
::$cache_count++
;
183 * Removes a context from the cache.
185 * @param context $context Context object to remove
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.
193 unset(self
::$cache_contexts[$context->contextlevel
][$context->instanceid
]);
194 unset(self
::$cache_contextsbyid[$context->id
]);
196 self
::$cache_count--;
198 if (self
::$cache_count < 0) {
199 self
::$cache_count = 0;
204 * Gets a context from the cache.
206 * @param int $contextlevel Context level
207 * @param int $instance Instance ID
208 * @return context|bool Context or false if not in cache
210 protected static function cache_get($contextlevel, $instance) {
211 if (isset(self
::$cache_contexts[$contextlevel][$instance])) {
212 return self
::$cache_contexts[$contextlevel][$instance];
218 * Gets a context from the cache based on its id.
220 * @param int $id Context ID
221 * @return context|bool Context or false if not in cache
223 protected static function cache_get_by_id($id) {
224 if (isset(self
::$cache_contextsbyid[$id])) {
225 return self
::$cache_contextsbyid[$id];
231 * Preloads context information from db record and strips the cached info.
233 * @param stdClass $rec
234 * @return context|null (modifies $rec)
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
);
254 'contextlevel' => $rec->ctxlevel
,
255 'instanceid' => $rec->ctxinstance
,
256 'path' => $rec->ctxpath
,
257 'depth' => $rec->ctxdepth
,
258 'locked' => $rec->ctxlocked
,
262 unset($rec->ctxlevel
);
263 unset($rec->ctxinstance
);
264 unset($rec->ctxpath
);
265 unset($rec->ctxdepth
);
266 unset($rec->ctxlocked
);
268 return self
::create_instance_from_record($record);
272 /* ====== magic methods ======= */
275 * Magic setter method, we do not want anybody to modify properties from the outside
276 * @param string $name
277 * @param mixed $value
279 public function __set($name, $value) {
280 debugging('Can not change context instance properties!');
284 * Magic method getter, redirects to read only values.
285 * @param string $name
288 public function __get($name) {
293 return $this->_contextlevel
;
295 return $this->_instanceid
;
299 return $this->_depth
;
301 return $this->is_locked();
304 debugging('Invalid context property accessed! '.$name);
310 * Full support for isset on our magic read only properties.
311 * @param string $name
314 public function __isset($name) {
317 return isset($this->_id
);
319 return isset($this->_contextlevel
);
321 return isset($this->_instanceid
);
323 return isset($this->_path
);
325 return isset($this->_depth
);
327 // Locked is always set.
335 * All properties are read only, sorry.
336 * @param string $name
338 public function __unset($name) {
339 debugging('Can not unset context instance properties!');
342 /* ====== implementing method from interface IteratorAggregate ====== */
345 * Create an iterator because magic vars can't be seen by 'foreach'.
347 * Now we can convert context object to array using convert_to_array(),
348 * and feed it properly to json_encode().
350 public function getIterator(): \Traversable
{
353 'contextlevel' => $this->contextlevel
,
354 'instanceid' => $this->instanceid
,
355 'path' => $this->path
,
356 'depth' => $this->depth
,
357 'locked' => $this->locked
,
359 return new ArrayIterator($ret);
362 /* ====== general context methods ====== */
365 * Constructor is protected so that devs are forced to
366 * use context_xxx::instance() or context::instance_by_id().
368 * @param stdClass $record
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
;
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
);
385 * This function is also used to work around 'protected' keyword problems in context_helper.
387 * @param stdClass $record
388 * @return context instance
390 protected static function create_instance_from_record(stdClass
$record) {
391 $classname = context_helper
::get_class_for_level($record->contextlevel
);
393 if ($context = self
::cache_get_by_id($record->id
)) {
397 $context = new $classname($record);
398 self
::cache_add($context);
404 * Copy prepared new contexts from temp table to context table,
405 * we do this in db specific way for perf reasons only.
407 protected static function merge_context_temp_table() {
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
416 * Different code for each database - mostly for performance reasons
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 == 'oracle') {
427 $updatesql = "UPDATE {context} ct
428 SET (ct.path, ct.depth, ct.locked) =
429 (SELECT temp.path, temp.depth, temp.locked
430 FROM {context_temp} temp
432 WHERE EXISTS (SELECT 'x'
433 FROM {context_temp} temp
434 WHERE temp.id = ct.id)";
435 } else if ($dbfamily == 'postgres' ||
$dbfamily == 'mssql') {
436 $updatesql = "UPDATE {context}
437 SET path = temp.path,
440 FROM {context_temp} temp
441 WHERE temp.id={context}.id";
443 // Sqlite and others.
444 $updatesql = "UPDATE {context}
445 SET path = (SELECT path FROM {context_temp} WHERE id = {context}.id),
446 depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id),
447 locked = (SELECT locked FROM {context_temp} WHERE id = {context}.id)
448 WHERE id IN (SELECT id FROM {context_temp})";
451 $DB->execute($updatesql);
455 * Get a context instance as an object, from a given context id.
457 * @param int $id context id
458 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
459 * MUST_EXIST means throw exception if no record found
460 * @return context|bool the context object or false if not found
462 public static function instance_by_id($id, $strictness = MUST_EXIST
) {
465 if (get_called_class() !== 'core\context' && get_called_class() !== 'core\context_helper') {
466 // Some devs might confuse context->id and instanceid, better prevent these mistakes completely.
467 throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods');
470 if ($id == SYSCONTEXTID
) {
471 return context\system
::instance(0, $strictness);
474 if (is_array($id) ||
is_object($id) ||
empty($id)) {
475 throw new coding_exception('Invalid context id specified context::instance_by_id()');
478 if ($context = self
::cache_get_by_id($id)) {
482 if ($record = $DB->get_record('context', array('id' => $id), '*', $strictness)) {
483 return self
::create_instance_from_record($record);
490 * Update context info after moving context in the tree structure.
492 * @param context $newparent
495 public function update_moved(context
$newparent) {
498 $frompath = $this->_path
;
499 $newpath = $newparent->path
. '/' . $this->_id
;
501 $trans = $DB->start_delegated_transaction();
504 if (($newparent->depth +
1) != $this->_depth
) {
505 $diff = $newparent->depth
- $this->_depth +
1;
506 $setdepth = ", depth = depth + $diff";
508 $sql = "UPDATE {context}
512 $params = array($newpath, $this->_id
);
513 $DB->execute($sql, $params);
515 $this->_path
= $newpath;
516 $this->_depth
= $newparent->depth +
1;
518 $sql = "UPDATE {context}
519 SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath) +
1))."
522 $params = array($newpath, "{$frompath}/%");
523 $DB->execute($sql, $params);
527 self
::reset_caches();
529 $trans->allow_commit();
533 * Set whether this context has been locked or not.
535 * @param bool $locked
538 public function set_locked(bool $locked) {
541 if ($this->_locked
== $locked) {
545 $this->_locked
= $locked;
546 $DB->set_field('context', 'locked', (int) $locked, ['id' => $this->id
]);
550 $eventname = '\\core\\event\\context_locked';
552 $eventname = '\\core\\event\\context_unlocked';
554 $event = $eventname::create(['context' => $this, 'objectid' => $this->id
]);
557 self
::reset_caches();
563 * Remove all context path info and optionally rebuild it.
565 * @param bool $rebuild
568 public function reset_paths($rebuild = true) {
574 $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'");
575 $DB->set_field_select('context', 'path', null, "path LIKE '%/$this->_id/%'");
576 if ($this->_contextlevel
!= CONTEXT_SYSTEM
) {
577 $DB->set_field('context', 'depth', 0, array('id' => $this->_id
));
578 $DB->set_field('context', 'path', null, array('id' => $this->_id
));
584 context_helper
::build_all_paths(false);
587 self
::reset_caches();
591 * Delete all data linked to content, do not delete the context record itself
593 public function delete_content() {
596 blocks_delete_all_for_context($this->_id
);
597 filter_delete_all_for_context($this->_id
);
599 require_once($CFG->dirroot
. '/comment/lib.php');
600 \comment
::delete_comments(array('contextid' => $this->_id
));
602 require_once($CFG->dirroot
.'/rating/lib.php');
603 $delopt = new stdclass();
604 $delopt->contextid
= $this->_id
;
605 $rm = new \rating_manager
();
606 $rm->delete_ratings($delopt);
608 // Delete all files attached to this context.
609 $fs = get_file_storage();
610 $fs->delete_area_files($this->_id
);
612 // Delete all repository instances attached to this context.
613 require_once($CFG->dirroot
. '/repository/lib.php');
614 \repository
::delete_all_for_context($this->_id
);
616 // Delete all advanced grading data attached to this context.
617 require_once($CFG->dirroot
.'/grade/grading/lib.php');
618 \grading_manager
::delete_all_for_context($this->_id
);
620 // Now delete stuff from role related tables, role_unassign_all
621 // and unenrol should be called earlier to do proper cleanup.
622 $DB->delete_records('role_assignments', array('contextid' => $this->_id
));
623 $DB->delete_records('role_names', array('contextid' => $this->_id
));
624 $this->delete_capabilities();
628 * Unassign all capabilities from a context.
630 public function delete_capabilities() {
633 $ids = $DB->get_fieldset_select('role_capabilities', 'DISTINCT roleid', 'contextid = ?', array($this->_id
));
635 $DB->delete_records('role_capabilities', array('contextid' => $this->_id
));
637 // Reset any cache of these roles, including MUC.
638 accesslib_clear_role_cache($ids);
643 * Delete the context content and the context record itself
645 public function delete() {
648 if ($this->_contextlevel
<= CONTEXT_SYSTEM
) {
649 throw new coding_exception('Cannot delete system context');
652 // Double check the context still exists.
653 if (!$DB->record_exists('context', array('id' => $this->_id
))) {
654 self
::cache_remove($this);
658 $this->delete_content();
659 $DB->delete_records('context', array('id' => $this->_id
));
660 // Purge static context cache if entry present.
661 self
::cache_remove($this);
663 // Inform search engine to delete data related to this context.
664 \core_search\manager
::context_deleted($this);
667 /* ====== context level related methods ====== */
670 * Utility method for context creation
672 * @param int $contextlevel
673 * @param int $instanceid
674 * @param string $parentpath
675 * @return stdClass context record
677 protected static function insert_context_record($contextlevel, $instanceid, $parentpath) {
680 $record = new stdClass();
681 $record->contextlevel
= $contextlevel;
682 $record->instanceid
= $instanceid;
684 $record->path
= null; // Not known before insert.
687 $record->id
= $DB->insert_record('context', $record);
689 // Now add path if known - it can be added later.
690 if (!is_null($parentpath)) {
691 $record->path
= $parentpath.'/'.$record->id
;
692 $record->depth
= substr_count($record->path
, '/');
693 $DB->update_record('context', $record);
700 * Returns human readable context identifier.
702 * @param boolean $withprefix whether to prefix the name of the context with the
703 * type of context, e.g. User, Course, Forum, etc.
704 * @param boolean $short whether to use the short name of the thing. Only applies
706 * @param boolean $escape Whether the returned name of the thing is to be
707 * HTML escaped or not.
708 * @return string the human readable context name.
710 public function get_context_name($withprefix = true, $short = false, $escape = true) {
711 // Must be implemented in all context levels.
712 throw new coding_exception('can not get name of abstract context');
716 * Whether the current context is locked.
720 public function is_locked() {
721 if ($this->_locked
) {
725 if ($parent = $this->get_parent_context()) {
726 return $parent->is_locked();
733 * Returns the most relevant URL for this context.
737 abstract public function get_url();
740 * Returns context instance database name.
742 * @return string|null table name for all levels except system.
744 protected static function get_instance_table(): ?
string {
749 * Returns list of columns that can be used from behat
750 * to look up context by reference.
752 * @return array list of column names from instance table
754 protected static function get_behat_reference_columns(): array {
759 * Returns list of all role archetypes that are compatible
760 * with role assignments in context level.
765 protected static function get_compatible_role_archetypes(): array {
766 // Override if archetype roles should be allowed to be assigned in context level.
771 * Returns list of all possible parent context levels,
772 * it may include itself if nesting is allowed.
777 public static function get_possible_parent_levels(): array {
778 // Override if other type of parents are expected.
779 return [context\system
::LEVEL
];
783 * Returns array of relevant context capability records.
785 * @param string $sort SQL order by snippet for sorting returned capabilities sensibly for display
788 abstract public function get_capabilities(string $sort = self
::DEFAULT_CAPABILITY_SORT
);
791 * Recursive function which, given a context, find all its children context ids.
793 * For course category contexts it will return immediate children and all subcategory contexts.
794 * It will NOT recurse into courses or subcategories categories.
795 * If you want to do that, call it on the returned courses/categories.
797 * When called for a course context, it will return the modules and blocks
798 * displayed in the course page and blocks displayed on the module pages.
800 * If called on a user/course/module context it _will_ populate the cache with the appropriate
803 * @return array Array of child records
805 public function get_child_contexts() {
808 if (empty($this->_path
) ||
empty($this->_depth
)) {
809 debugging('Can not find child contexts of context '.$this->_id
.' try rebuilding of context paths');
815 WHERE ctx.path LIKE ?";
816 $params = array($this->_path
.'/%');
817 $records = $DB->get_records_sql($sql, $params);
820 foreach ($records as $record) {
821 $result[$record->id
] = self
::create_instance_from_record($record);
828 * Determine if the current context is a parent of the possible child.
830 * @param context $possiblechild
831 * @param bool $includeself Whether to check the current context
834 public function is_parent_of(context
$possiblechild, bool $includeself): bool {
835 // A simple substring check is used on the context path.
836 // The possible child's path is used as a haystack, with the current context as the needle.
837 // The path is prefixed with '+' to ensure that the parent always starts at the top.
838 // It is suffixed with '+' to ensure that parents are not included.
839 // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
840 // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
841 // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
842 $haystacksuffix = $includeself ?
'/+' : '+';
845 "+{$possiblechild->path}{$haystacksuffix}",
848 return $strpos === 0;
852 * Returns parent contexts of this context in reversed order, i.e. parent first,
853 * then grand parent, etc.
855 * @param bool $includeself true means include self too
856 * @return array of context instances
858 public function get_parent_contexts($includeself = false) {
859 if (!$contextids = $this->get_parent_context_ids($includeself)) {
863 // Preload the contexts to reduce DB calls.
864 context_helper
::preload_contexts_by_id($contextids);
867 foreach ($contextids as $contextid) {
868 // Do NOT change this to self!
869 $parent = context_helper
::instance_by_id($contextid, MUST_EXIST
);
870 $result[$parent->id
] = $parent;
877 * Determine if the current context is a child of the possible parent.
879 * @param context $possibleparent
880 * @param bool $includeself Whether to check the current context
883 public function is_child_of(context
$possibleparent, bool $includeself): bool {
884 // A simple substring check is used on the context path.
885 // The current context is used as a haystack, with the possible parent as the needle.
886 // The path is prefixed with '+' to ensure that the parent always starts at the top.
887 // It is suffixed with '+' to ensure that children are not included.
888 // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
889 // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
890 // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
891 $haystacksuffix = $includeself ?
'/+' : '+';
894 "+{$this->path}{$haystacksuffix}",
895 "+{$possibleparent->path}/"
897 return $strpos === 0;
901 * Returns parent context ids of this context in reversed order, i.e. parent first,
902 * then grand parent, etc.
904 * @param bool $includeself true means include self too
905 * @return array of context ids
907 public function get_parent_context_ids($includeself = false) {
908 if (empty($this->_path
)) {
912 $parentcontexts = trim($this->_path
, '/'); // Kill leading slash.
913 $parentcontexts = explode('/', $parentcontexts);
915 array_pop($parentcontexts); // And remove its own id.
918 return array_reverse($parentcontexts);
922 * Returns parent context paths of this context.
924 * @param bool $includeself true means include self too
925 * @return array of context paths
927 public function get_parent_context_paths($includeself = false) {
928 if (empty($this->_path
)) {
932 $contextids = explode('/', $this->_path
);
936 foreach ($contextids as $contextid) {
938 $path .= '/' . $contextid;
939 $paths[$contextid] = $path;
944 unset($paths[$this->_id
]);
951 * Returns parent context
953 * @return context|false
955 public function get_parent_context() {
956 if (empty($this->_path
) ||
$this->_id
== SYSCONTEXTID
) {
960 $parentcontexts = trim($this->_path
, '/'); // Kill leading slash.
961 $parentcontexts = explode('/', $parentcontexts);
962 array_pop($parentcontexts); // Self.
963 $contextid = array_pop($parentcontexts); // Immediate parent.
965 // Do NOT change this to self!
966 return context_helper
::instance_by_id($contextid, MUST_EXIST
);
970 * Is this context part of any course? If yes return course context.
972 * @param bool $strict true means throw exception if not found, false means return false if not found
973 * @return context\course|false context of the enclosing course, null if not found or exception
975 public function get_course_context($strict = true) {
977 throw new coding_exception('Context does not belong to any course.');
984 * Returns sql necessary for purging of stale context instances.
986 * @return string cleanup SQL
988 protected static function get_cleanup_sql() {
989 throw new coding_exception('get_cleanup_sql() method must be implemented in all context levels');
993 * Rebuild context paths and depths at context level.
998 protected static function build_paths($force) {
999 throw new coding_exception('build_paths() method must be implemented in all context levels');
1003 * Create missing context instances at given level
1007 protected static function create_level_instances() {
1008 throw new coding_exception('create_level_instances() method must be implemented in all context levels');
1012 * Reset all cached permissions and definitions if the necessary.
1015 public function reload_if_dirty() {
1016 global $ACCESSLIB_PRIVATE, $USER;
1018 // Load dirty contexts list if needed.
1020 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts
)) {
1021 // We do not load dirty flags in CLI and cron.
1022 $ACCESSLIB_PRIVATE->dirtycontexts
= array();
1025 if (!isset($USER->access
['time'])) {
1026 // Nothing has been loaded yet, so we do not need to check dirty flags now.
1030 // From skodak: No idea why -2 is there, server cluster time difference maybe...
1031 $changedsince = $USER->access
['time'] - 2;
1033 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts
)) {
1034 $ACCESSLIB_PRIVATE->dirtycontexts
= get_cache_flags('accesslib/dirtycontexts', $changedsince);
1037 if (!isset($ACCESSLIB_PRIVATE->dirtyusers
[$USER->id
])) {
1038 $ACCESSLIB_PRIVATE->dirtyusers
[$USER->id
] = get_cache_flag('accesslib/dirtyusers', $USER->id
, $changedsince);
1044 if (!empty($ACCESSLIB_PRIVATE->dirtyusers
[$USER->id
])) {
1046 } else if (!empty($ACCESSLIB_PRIVATE->dirtycontexts
)) {
1047 $paths = $this->get_parent_context_paths(true);
1049 foreach ($paths as $path) {
1050 if (isset($ACCESSLIB_PRIVATE->dirtycontexts
[$path])) {
1058 // Reload all capabilities of USER and others - preserving loginas, roleswitches, etc.
1059 // Then cleanup any marks of dirtyness... at least from our short term memory!
1060 reload_all_capabilities();
1065 * Mark a context as dirty (with timestamp) so as to force reloading of the context.
1067 public function mark_dirty() {
1068 global $CFG, $USER, $ACCESSLIB_PRIVATE;
1070 if (during_initial_install()) {
1074 // Only if it is a non-empty string.
1075 if (is_string($this->_path
) && $this->_path
!== '') {
1076 set_cache_flag('accesslib/dirtycontexts', $this->_path
, 1, time() +
$CFG->sessiontimeout
);
1077 if (isset($ACCESSLIB_PRIVATE->dirtycontexts
)) {
1078 $ACCESSLIB_PRIVATE->dirtycontexts
[$this->_path
] = 1;
1081 $ACCESSLIB_PRIVATE->dirtycontexts
= array($this->_path
=> 1);
1083 if (isset($USER->access
['time'])) {
1084 $ACCESSLIB_PRIVATE->dirtycontexts
= get_cache_flags('accesslib/dirtycontexts', $USER->access
['time'] - 2);
1086 $ACCESSLIB_PRIVATE->dirtycontexts
= array($this->_path
=> 1);
1088 // Flags not loaded yet, it will be done later in $context->reload_if_dirty().