MDL-74936 core: tidy up context API

This commit is contained in:
Petr Skoda
2023-03-31 23:09:39 +02:00
parent 728c3c4bd1
commit 176191dab5
27 changed files with 3210 additions and 463 deletions
+13 -5
View File
@@ -80,8 +80,15 @@ class core_role_preset {
$top->appendChild($contextlevels);
foreach (get_role_contextlevels($roleid) as $level) {
$name = context_helper::get_class_for_level($level);
$name = preg_replace('/^context_/', '', $name);
$contextlevels->appendChild($dom->createElement('level', $name));
if (strpos($name, 'core\\context\\') === 0) {
// Use short names of standard contexts for backwards compatibility.
$value = preg_replace('/^core\\\\context\\\\/', '', $name);
} else {
// Must be a custom plugin level, use numbers to work around
// potential duplicate short names of contexts in add-ons.
$value = $level;
}
$contextlevels->appendChild($dom->createElement('level', $value));
}
foreach (array('assign', 'override', 'switch', 'view') as $type) {
@@ -197,9 +204,10 @@ class core_role_preset {
$info['contextlevels'] = array();
$levelmap = array_flip(context_helper::get_all_levels());
foreach ($values as $value) {
$level = 'context_'.$value;
if (isset($levelmap[$level])) {
$cl = $levelmap[$level];
// Numbers and short names are supported since Moodle 4.2.
$classname = \core\context_helper::parse_external_level($value);
if ($classname) {
$cl = $classname::LEVEL;
$info['contextlevels'][$cl] = $cl;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<role>
<shortname>courseviewer</shortname>
<name/>
<description/>
<archetype/>
<contextlevels>
<level>system</level>
<level>40</level>
<level>50</level>
</contextlevels>
<allowassign/>
<allowoverride/>
<allowswitch/>
<allowview>
<shortname>student</shortname>
</allowview>
<permissions>
<allow>moodle/category:viewhiddencategories</allow>
<allow>moodle/course:ignoreavailabilityrestrictions</allow>
<allow>moodle/course:viewhiddencourses</allow>
<allow>moodle/site:configview</allow>
</permissions>
</role>
+16
View File
@@ -76,4 +76,20 @@ class preset_test extends \advanced_testcase {
}
}
}
/**
* Tests covered method.
* @covers \core_role_preset::parse_preset
*/
public function test_mixed_levels() {
// The problem here is that we cannot guarantee plugin contexts
// have unique short names, so we have to also support level numbers.
$xml = file_get_contents(__DIR__ . '/fixtures/mixed_levels.xml');
$this->assertTrue(\core_role_preset::is_valid_preset($xml));
$preset = \core_role_preset::parse_preset($xml);
$expected = [\core\context\system::LEVEL, \core\context\coursecat::LEVEL, \core\context\course::LEVEL];
$expected = array_combine($expected, $expected);
$this->assertSame($expected, $preset['contextlevels']);
}
}
@@ -47,6 +47,12 @@ class data_registry {
* @return string[]
*/
public static function var_names_from_context($classname, $pluginname = '') {
// Unfortunately authors of privacy API did not expect that we would be
// on day fixing auto-loading of context classes.
// The best way would have been probably level numbers at the end of vars,
// but it is probably too late to fix it.
$classname = preg_replace('/^[a-z0-9_]+\\\\context\\\\/', 'context_', $classname);
$pluginname = trim($pluginname ?? '');
if (!empty($pluginname)) {
$categoryvar = $classname . '_' . $pluginname . '_category';
+2 -16
View File
@@ -3549,21 +3549,7 @@ function get_roles_for_contextlevels($contextlevel) {
* @return array list of the context levels at which this type of role may be assigned by default.
*/
function get_default_contextlevels($rolearchetype) {
static $defaults = array(
'manager' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE),
'coursecreator' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT),
'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
'teacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
'student' => array(CONTEXT_COURSE, CONTEXT_MODULE),
'guest' => array(),
'user' => array(),
'frontpage' => array());
if (isset($defaults[$rolearchetype])) {
return $defaults[$rolearchetype];
} else {
return array();
}
return \context_helper::get_compatible_levels($rolearchetype);
}
/**
@@ -5024,7 +5010,7 @@ function role_change_permission($roleid, $context, $capname, $permission) {
assign_capability($capname, $permission, $roleid, $context->id, true);
}
// ============== DEPRECATED FUNCTIONS ==========================================
/* ============== DEPRECATED FUNCTIONS ========================================== */
// Old context related functions were deprecated in 2.0, it is recommended
// to use context classes in new code. Old function can be used when
// creating patches that are supposed to be backported to older stable branches.
+4 -45
View File
@@ -1148,53 +1148,12 @@ EOF;
* @return context
*/
public static function get_context(string $levelname, string $contextref): context {
global $DB;
// Getting context levels and names (we will be using the English ones as it is the test site language).
$contextlevels = context_helper::get_all_levels();
$contextnames = array();
foreach ($contextlevels as $level => $classname) {
$contextnames[context_helper::get_level_name($level)] = $level;
$context = \core\context_helper::resolve_behat_reference($levelname, $contextref);
if ($context) {
return $context;
}
if (empty($contextnames[$levelname])) {
throw new Exception('The specified "' . $levelname . '" context level does not exist');
}
$contextlevel = $contextnames[$levelname];
// Return it, we don't need to look for other internal ids.
if ($contextlevel == CONTEXT_SYSTEM) {
return context_system::instance();
}
switch ($contextlevel) {
case CONTEXT_USER:
$instanceid = $DB->get_field('user', 'id', array('username' => $contextref));
break;
case CONTEXT_COURSECAT:
$instanceid = $DB->get_field('course_categories', 'id', array('idnumber' => $contextref));
break;
case CONTEXT_COURSE:
$instanceid = $DB->get_field('course', 'id', array('shortname' => $contextref));
break;
case CONTEXT_MODULE:
$instanceid = $DB->get_field('course_modules', 'id', array('idnumber' => $contextref));
break;
default:
break;
}
$contextclass = $contextlevels[$contextlevel];
if (!$context = $contextclass::instance($instanceid, IGNORE_MISSING)) {
throw new Exception('The specified "' . $contextref . '" context reference does not exist');
}
return $context;
throw new Exception("The specified context \"$levelname, $contextref\" does not exist");
}
/**
+161 -115
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,13 +12,12 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core;
use stdClass, IteratorAggregate, ArrayIterator;
use coding_exception, moodle_url;
use core\context\system;
/**
* Basic moodle context abstraction class.
@@ -33,7 +32,7 @@ use core\context\system;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*
* @property-read int $id context id
@@ -41,6 +40,7 @@ use core\context\system;
* @property-read int $instanceid id of related instance in each context
* @property-read string $path path to context, starts with system context
* @property-read int $depth
* @property-read bool $locked true means write capabilities are ignored in this context or parents
*/
abstract class context extends stdClass implements IteratorAggregate {
@@ -99,34 +99,46 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* @var array Context caching info
*/
private static $cache_contexts = array();
private static $cache_contexts = array();
/**
* Context count
* Why do we do count contexts? Because count($array) is horribly slow for large arrays
* @var int
*/
protected static $cache_count = 0;
protected static $cache_count = 0;
/**
* @var array Context caching info
*/
protected static $cache_preloaded = array();
protected static $cache_preloaded = array();
/**
* @var system The system context once initialised
* @var context\system The system context once initialised
*/
protected static $systemcontext = null;
protected static $systemcontext = null;
/**
* Returns short context name.
*
* @since Moodle 4.2
*
* @return string
*/
public static function get_short_name(): string {
// NOTE: it would be more correct to make this abstract,
// unfortunately there are tests that attempt to mock context classes.
throw new \coding_exception('get_short_name() method must be overridden in custom context levels');
}
/**
* Resets the cache to remove all data.
* @static
*/
protected static function reset_caches() {
self::$cache_contextsbyid = array();
self::$cache_contexts = array();
self::$cache_count = 0;
self::$cache_preloaded = array();
self::$cache_contexts = array();
self::$cache_count = 0;
self::$cache_preloaded = array();
self::$systemcontext = null;
}
@@ -135,13 +147,12 @@ abstract class context extends stdClass implements IteratorAggregate {
* Adds a context to the cache. If the cache is full, discards a batch of
* older entries.
*
* @static
* @param context $context New context to add
* @return void
*/
protected static function cache_add(context $context) {
if (isset(self::$cache_contextsbyid[$context->id])) {
// already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
// Already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow.
return;
}
@@ -150,11 +161,11 @@ abstract class context extends stdClass implements IteratorAggregate {
foreach (self::$cache_contextsbyid as $ctx) {
$i++;
if ($i <= 100) {
// we want to keep the first contexts to be loaded on this page, hopefully they will be needed again later
// We want to keep the first contexts to be loaded on this page, hopefully they will be needed again later.
continue;
}
if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) {
// we remove oldest third of the contexts to make room for more contexts
// We remove oldest third of the contexts to make room for more contexts.
break;
}
unset(self::$cache_contextsbyid[$ctx->id]);
@@ -171,13 +182,12 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Removes a context from the cache.
*
* @static
* @param context $context Context object to remove
* @return void
*/
protected static function cache_remove(context $context) {
if (!isset(self::$cache_contextsbyid[$context->id])) {
// not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
// Not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow.
return;
}
unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]);
@@ -193,7 +203,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Gets a context from the cache.
*
* @static
* @param int $contextlevel Context level
* @param int $instance Instance ID
* @return context|bool Context or false if not in cache
@@ -208,7 +217,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Gets a context from the cache based on its id.
*
* @static
* @param int $id Context ID
* @return context|bool Context or false if not in cache
*/
@@ -222,7 +230,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Preloads context information from db record and strips the cached info.
*
* @static
* @param stdClass $rec
* @return context|null (modifies $rec)
*/
@@ -243,12 +250,12 @@ abstract class context extends stdClass implements IteratorAggregate {
}
$record = (object) [
'id' => $rec->ctxid,
'contextlevel' => $rec->ctxlevel,
'instanceid' => $rec->ctxinstance,
'path' => $rec->ctxpath,
'depth' => $rec->ctxdepth,
'locked' => $rec->ctxlocked,
'id' => $rec->ctxid,
'contextlevel' => $rec->ctxlevel,
'instanceid' => $rec->ctxinstance,
'path' => $rec->ctxpath,
'depth' => $rec->ctxdepth,
'locked' => $rec->ctxlocked,
];
unset($rec->ctxid);
@@ -258,11 +265,11 @@ abstract class context extends stdClass implements IteratorAggregate {
unset($rec->ctxdepth);
unset($rec->ctxlocked);
return context::create_instance_from_record($record);
return self::create_instance_from_record($record);
}
// ====== magic methods =======
/* ====== magic methods ======= */
/**
* Magic setter method, we do not want anybody to modify properties from the outside
@@ -332,7 +339,7 @@ abstract class context extends stdClass implements IteratorAggregate {
debugging('Can not unset context instance properties!');
}
// ====== implementing method from interface IteratorAggregate ======
/* ====== implementing method from interface IteratorAggregate ====== */
/**
* Create an iterator because magic vars can't be seen by 'foreach'.
@@ -342,17 +349,17 @@ abstract class context extends stdClass implements IteratorAggregate {
*/
public function getIterator(): \Traversable {
$ret = array(
'id' => $this->id,
'id' => $this->id,
'contextlevel' => $this->contextlevel,
'instanceid' => $this->instanceid,
'path' => $this->path,
'depth' => $this->depth,
'locked' => $this->locked,
'instanceid' => $this->instanceid,
'path' => $this->path,
'depth' => $this->depth,
'locked' => $this->locked,
);
return new ArrayIterator($ret);
}
// ====== general context methods ======
/* ====== general context methods ====== */
/**
* Constructor is protected so that devs are forced to
@@ -361,11 +368,11 @@ abstract class context extends stdClass implements IteratorAggregate {
* @param stdClass $record
*/
protected function __construct(stdClass $record) {
$this->_id = (int)$record->id;
$this->_id = (int)$record->id;
$this->_contextlevel = (int)$record->contextlevel;
$this->_instanceid = $record->instanceid;
$this->_path = $record->path;
$this->_depth = $record->depth;
$this->_instanceid = $record->instanceid;
$this->_path = $record->path;
$this->_depth = $record->depth;
if (isset($record->locked)) {
$this->_locked = $record->locked;
@@ -376,19 +383,19 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* This function is also used to work around 'protected' keyword problems in context_helper.
* @static
*
* @param stdClass $record
* @return context instance
*/
protected static function create_instance_from_record(stdClass $record) {
$classname = context_helper::get_class_for_level($record->contextlevel);
if ($context = context::cache_get_by_id($record->id)) {
if ($context = self::cache_get_by_id($record->id)) {
return $context;
}
$context = new $classname($record);
context::cache_add($context);
self::cache_add($context);
return $context;
}
@@ -396,7 +403,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Copy prepared new contexts from temp table to context table,
* we do this in db specific way for perf reasons only.
* @static
*/
protected static function merge_context_temp_table() {
global $DB;
@@ -413,9 +419,9 @@ abstract class context extends stdClass implements IteratorAggregate {
$dbfamily = $DB->get_dbfamily();
if ($dbfamily == 'mysql') {
$updatesql = "UPDATE {context} ct, {context_temp} temp
SET ct.path = temp.path,
ct.depth = temp.depth,
ct.locked = temp.locked
SET ct.path = temp.path,
ct.depth = temp.depth,
ct.locked = temp.locked
WHERE ct.id = temp.id";
} else if ($dbfamily == 'oracle') {
$updatesql = "UPDATE {context} ct
@@ -426,19 +432,19 @@ abstract class context extends stdClass implements IteratorAggregate {
WHERE EXISTS (SELECT 'x'
FROM {context_temp} temp
WHERE temp.id = ct.id)";
} else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') {
} else if ($dbfamily == 'postgres' || $dbfamily == 'mssql') {
$updatesql = "UPDATE {context}
SET path = temp.path,
depth = temp.depth,
locked = temp.locked
SET path = temp.path,
depth = temp.depth,
locked = temp.locked
FROM {context_temp} temp
WHERE temp.id={context}.id";
} else {
// sqlite and others
// Sqlite and others.
$updatesql = "UPDATE {context}
SET path = (SELECT path FROM {context_temp} WHERE id = {context}.id),
depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id),
locked = (SELECT locked FROM {context_temp} WHERE id = {context}.id)
SET path = (SELECT path FROM {context_temp} WHERE id = {context}.id),
depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id),
locked = (SELECT locked FROM {context_temp} WHERE id = {context}.id)
WHERE id IN (SELECT id FROM {context_temp})";
}
@@ -448,7 +454,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Get a context instance as an object, from a given context id.
*
* @static
* @param int $id context id
* @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
* MUST_EXIST means throw exception if no record found
@@ -457,25 +462,25 @@ abstract class context extends stdClass implements IteratorAggregate {
public static function instance_by_id($id, $strictness = MUST_EXIST) {
global $DB;
if (get_called_class() !== 'core\context' and get_called_class() !== 'core\context_helper') {
// some devs might confuse context->id and instanceid, better prevent these mistakes completely
if (get_called_class() !== 'core\context' && get_called_class() !== 'core\context_helper') {
// Some devs might confuse context->id and instanceid, better prevent these mistakes completely.
throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods');
}
if ($id == SYSCONTEXTID) {
return system::instance(0, $strictness);
return context\system::instance(0, $strictness);
}
if (is_array($id) or is_object($id) or empty($id)) {
if (is_array($id) || is_object($id) || empty($id)) {
throw new coding_exception('Invalid context id specified context::instance_by_id()');
}
if ($context = context::cache_get_by_id($id)) {
if ($context = self::cache_get_by_id($id)) {
return $context;
}
if ($record = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
return context::create_instance_from_record($record);
if ($record = $DB->get_record('context', array('id' => $id), '*', $strictness)) {
return self::create_instance_from_record($record);
}
return false;
@@ -491,12 +496,12 @@ abstract class context extends stdClass implements IteratorAggregate {
global $DB;
$frompath = $this->_path;
$newpath = $newparent->path . '/' . $this->_id;
$newpath = $newparent->path . '/' . $this->_id;
$trans = $DB->start_delegated_transaction();
$setdepth = '';
if (($newparent->depth +1) != $this->_depth) {
if (($newparent->depth + 1) != $this->_depth) {
$diff = $newparent->depth - $this->_depth + 1;
$setdepth = ", depth = depth + $diff";
}
@@ -507,11 +512,11 @@ abstract class context extends stdClass implements IteratorAggregate {
$params = array($newpath, $this->_id);
$DB->execute($sql, $params);
$this->_path = $newpath;
$this->_path = $newpath;
$this->_depth = $newparent->depth + 1;
$sql = "UPDATE {context}
SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath)+1))."
SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath) + 1))."
$setdepth
WHERE path LIKE ?";
$params = array($newpath, "{$frompath}/%");
@@ -519,7 +524,7 @@ abstract class context extends stdClass implements IteratorAggregate {
$this->mark_dirty();
context::reset_caches();
self::reset_caches();
$trans->allow_commit();
}
@@ -567,10 +572,10 @@ abstract class context extends stdClass implements IteratorAggregate {
$this->mark_dirty();
}
$DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'");
$DB->set_field_select('context', 'path', NULL, "path LIKE '%/$this->_id/%'");
$DB->set_field_select('context', 'path', null, "path LIKE '%/$this->_id/%'");
if ($this->_contextlevel != CONTEXT_SYSTEM) {
$DB->set_field('context', 'depth', 0, array('id'=>$this->_id));
$DB->set_field('context', 'path', NULL, array('id'=>$this->_id));
$DB->set_field('context', 'depth', 0, array('id' => $this->_id));
$DB->set_field('context', 'path', null, array('id' => $this->_id));
$this->_depth = 0;
$this->_path = null;
}
@@ -579,7 +584,7 @@ abstract class context extends stdClass implements IteratorAggregate {
context_helper::build_all_paths(false);
}
context::reset_caches();
self::reset_caches();
}
/**
@@ -592,7 +597,7 @@ abstract class context extends stdClass implements IteratorAggregate {
filter_delete_all_for_context($this->_id);
require_once($CFG->dirroot . '/comment/lib.php');
\comment::delete_comments(array('contextid'=>$this->_id));
\comment::delete_comments(array('contextid' => $this->_id));
require_once($CFG->dirroot.'/rating/lib.php');
$delopt = new stdclass();
@@ -600,7 +605,7 @@ abstract class context extends stdClass implements IteratorAggregate {
$rm = new \rating_manager();
$rm->delete_ratings($delopt);
// delete all files attached to this context
// Delete all files attached to this context.
$fs = get_file_storage();
$fs->delete_area_files($this->_id);
@@ -608,14 +613,14 @@ abstract class context extends stdClass implements IteratorAggregate {
require_once($CFG->dirroot . '/repository/lib.php');
\repository::delete_all_for_context($this->_id);
// delete all advanced grading data attached to this context
// Delete all advanced grading data attached to this context.
require_once($CFG->dirroot.'/grade/grading/lib.php');
\grading_manager::delete_all_for_context($this->_id);
// now delete stuff from role related tables, role_unassign_all
// and unenrol should be called earlier to do proper cleanup
$DB->delete_records('role_assignments', array('contextid'=>$this->_id));
$DB->delete_records('role_names', array('contextid'=>$this->_id));
// Now delete stuff from role related tables, role_unassign_all
// and unenrol should be called earlier to do proper cleanup.
$DB->delete_records('role_assignments', array('contextid' => $this->_id));
$DB->delete_records('role_names', array('contextid' => $this->_id));
$this->delete_capabilities();
}
@@ -644,27 +649,26 @@ abstract class context extends stdClass implements IteratorAggregate {
throw new coding_exception('Cannot delete system context');
}
// double check the context still exists
if (!$DB->record_exists('context', array('id'=>$this->_id))) {
context::cache_remove($this);
// Double check the context still exists.
if (!$DB->record_exists('context', array('id' => $this->_id))) {
self::cache_remove($this);
return;
}
$this->delete_content();
$DB->delete_records('context', array('id'=>$this->_id));
// purge static context cache if entry present
context::cache_remove($this);
$DB->delete_records('context', array('id' => $this->_id));
// Purge static context cache if entry present.
self::cache_remove($this);
// Inform search engine to delete data related to this context.
\core_search\manager::context_deleted($this);
}
// ====== context level related methods ======
/* ====== context level related methods ====== */
/**
* Utility method for context creation
*
* @static
* @param int $contextlevel
* @param int $instanceid
* @param string $parentpath
@@ -675,14 +679,14 @@ abstract class context extends stdClass implements IteratorAggregate {
$record = new stdClass();
$record->contextlevel = $contextlevel;
$record->instanceid = $instanceid;
$record->depth = 0;
$record->path = null; //not known before insert
$record->locked = 0;
$record->instanceid = $instanceid;
$record->depth = 0;
$record->path = null; // Not known before insert.
$record->locked = 0;
$record->id = $DB->insert_record('context', $record);
// now add path if known - it can be added later
// Now add path if known - it can be added later.
if (!is_null($parentpath)) {
$record->path = $parentpath.'/'.$record->id;
$record->depth = substr_count($record->path, '/');
@@ -704,7 +708,7 @@ abstract class context extends stdClass implements IteratorAggregate {
* @return string the human readable context name.
*/
public function get_context_name($withprefix = true, $short = false, $escape = true) {
// must be implemented in all context levels
// Must be implemented in all context levels.
throw new coding_exception('can not get name of abstract context');
}
@@ -730,7 +734,50 @@ abstract class context extends stdClass implements IteratorAggregate {
*
* @return moodle_url
*/
public abstract function get_url();
abstract public function get_url();
/**
* Returns context instance database name.
*
* @return string|null table name for all levels except system.
*/
protected static function get_instance_table(): ?string {
return null;
}
/**
* Returns list of columns that can be used from behat
* to look up context by reference.
*
* @return array list of column names from instance table
*/
protected static function get_behat_reference_columns(): array {
return [];
}
/**
* Returns list of all role archetypes that are compatible
* with role assignments in context level.
* @since Moodle 4.2
*
* @return string[]
*/
protected static function get_compatible_role_archetypes(): array {
// Override if archetype roles should be allowed to be assigned in context level.
return [];
}
/**
* Returns list of all possible parent context levels,
* it may include itself if nesting is allowed.
* @since Moodle 4.2
*
* @return int[]
*/
public static function get_possible_parent_levels(): array {
// Override if other type of parents are expected.
return [context\system::LEVEL];
}
/**
* Returns array of relevant context capability records.
@@ -738,7 +785,7 @@ abstract class context extends stdClass implements IteratorAggregate {
* @param string $sort SQL order by snippet for sorting returned capabilities sensibly for display
* @return array
*/
public abstract function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT);
abstract public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT);
/**
* Recursive function which, given a context, find all its children context ids.
@@ -758,7 +805,7 @@ abstract class context extends stdClass implements IteratorAggregate {
public function get_child_contexts() {
global $DB;
if (empty($this->_path) or empty($this->_depth)) {
if (empty($this->_path) || empty($this->_depth)) {
debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
return array();
}
@@ -771,7 +818,7 @@ abstract class context extends stdClass implements IteratorAggregate {
$result = array();
foreach ($records as $record) {
$result[$record->id] = context::create_instance_from_record($record);
$result[$record->id] = self::create_instance_from_record($record);
}
return $result;
@@ -818,7 +865,8 @@ abstract class context extends stdClass implements IteratorAggregate {
$result = array();
foreach ($contextids as $contextid) {
$parent = context::instance_by_id($contextid, MUST_EXIST);
// Do NOT change this to self!
$parent = context_helper::instance_by_id($contextid, MUST_EXIST);
$result[$parent->id] = $parent;
}
@@ -861,10 +909,10 @@ abstract class context extends stdClass implements IteratorAggregate {
return array();
}
$parentcontexts = trim($this->_path, '/'); // kill leading slash
$parentcontexts = trim($this->_path, '/'); // Kill leading slash.
$parentcontexts = explode('/', $parentcontexts);
if (!$includeself) {
array_pop($parentcontexts); // and remove its own id
array_pop($parentcontexts); // And remove its own id.
}
return array_reverse($parentcontexts);
@@ -905,16 +953,17 @@ abstract class context extends stdClass implements IteratorAggregate {
* @return context|false
*/
public function get_parent_context() {
if (empty($this->_path) or $this->_id == SYSCONTEXTID) {
if (empty($this->_path) || $this->_id == SYSCONTEXTID) {
return false;
}
$parentcontexts = trim($this->_path, '/'); // kill leading slash
$parentcontexts = trim($this->_path, '/'); // Kill leading slash.
$parentcontexts = explode('/', $parentcontexts);
array_pop($parentcontexts); // self
$contextid = array_pop($parentcontexts); // immediate parent
array_pop($parentcontexts); // Self.
$contextid = array_pop($parentcontexts); // Immediate parent.
return context::instance_by_id($contextid, MUST_EXIST);
// Do NOT change this to self!
return context_helper::instance_by_id($contextid, MUST_EXIST);
}
/**
@@ -934,7 +983,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Returns sql necessary for purging of stale context instances.
*
* @static
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
@@ -944,7 +992,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Rebuild context paths and depths at context level.
*
* @static
* @param bool $force
* @return void
*/
@@ -955,7 +1002,6 @@ abstract class context extends stdClass implements IteratorAggregate {
/**
* Create missing context instances at given level
*
* @static
* @return void
*/
protected static function create_level_instances() {
@@ -969,10 +1015,10 @@ abstract class context extends stdClass implements IteratorAggregate {
public function reload_if_dirty() {
global $ACCESSLIB_PRIVATE, $USER;
// Load dirty contexts list if needed
// Load dirty contexts list if needed.
if (CLI_SCRIPT) {
if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
// we do not load dirty flags in CLI and cron
// We do not load dirty flags in CLI and cron.
$ACCESSLIB_PRIVATE->dirtycontexts = array();
}
} else {
@@ -1025,9 +1071,9 @@ abstract class context extends stdClass implements IteratorAggregate {
return;
}
// only if it is a non-empty string
// Only if it is a non-empty string.
if (is_string($this->_path) && $this->_path !== '') {
set_cache_flag('accesslib/dirtycontexts', $this->_path, 1, time()+$CFG->sessiontimeout);
set_cache_flag('accesslib/dirtycontexts', $this->_path, 1, time() + $CFG->sessiontimeout);
if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
$ACCESSLIB_PRIVATE->dirtycontexts[$this->_path] = 1;
} else {
@@ -1035,11 +1081,11 @@ abstract class context extends stdClass implements IteratorAggregate {
$ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
} else {
if (isset($USER->access['time'])) {
$ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2);
$ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time'] - 2);
} else {
$ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
}
// flags not loaded yet, it will be done later in $context->reload_if_dirty()
// Flags not loaded yet, it will be done later in $context->reload_if_dirty().
}
}
}
+46 -24
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,7 +12,7 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
@@ -26,27 +26,40 @@ use coding_exception, moodle_url;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class block extends context {
/** @var int numeric context level value matching legacy CONTEXT_BLOCK */
public const LEVEL = 80;
/**
* Please use context_block::instance($blockinstanceid) if you need the instance of context.
* Alternatively if you know only the context id use context::instance_by_id($contextid)
* Please use \core\context\block::instance($blockinstanceid) if you need the instance of context.
* Alternatively if you know only the context id use \core\context::instance_by_id($contextid)
*
* @param stdClass $record
*/
protected function __construct(stdClass $record) {
parent::__construct($record);
if ($record->contextlevel != CONTEXT_BLOCK) {
throw new coding_exception('Invalid $record->contextlevel in context_block constructor');
if ($record->contextlevel != self::LEVEL) {
throw new coding_exception('Invalid $record->contextlevel in core\context\block constructor');
}
}
/**
* Returns short context name.
*
* @since Moodle 4.2
*
* @return string
*/
public static function get_short_name(): string {
return 'block';
}
/**
* Returns human readable context level name.
*
* @static
* @return string the human readable context level name.
*/
public static function get_level_name() {
@@ -65,13 +78,13 @@ class block extends context {
global $DB, $CFG;
$name = '';
if ($blockinstance = $DB->get_record('block_instances', array('id'=>$this->_instanceid))) {
if ($blockinstance = $DB->get_record('block_instances', array('id' => $this->_instanceid))) {
global $CFG;
require_once("$CFG->dirroot/blocks/moodleblock.class.php");
require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
$blockname = "block_$blockinstance->blockname";
if ($blockobject = new $blockname()) {
if ($withprefix){
if ($withprefix) {
$name = get_string('block').': ';
}
$name .= $blockobject->title;
@@ -91,6 +104,19 @@ class block extends context {
return $parentcontexts->get_url();
}
/**
* Returns list of all possible parent context levels.
* @since Moodle 4.2
*
* @return int[]
*/
public static function get_possible_parent_levels(): array {
// Blocks may be added to any other context instance.
$alllevels = \core\context_helper::get_all_levels();
unset($alllevels[self::LEVEL]);
return array_keys($alllevels);
}
/**
* Returns array of relevant context capability records.
*
@@ -104,7 +130,7 @@ class block extends context {
$select = '(contextlevel = :level AND component = :component)';
$params = [
'level' => CONTEXT_BLOCK,
'level' => self::LEVEL,
'component' => 'block_' . $bi->blockname,
];
@@ -132,7 +158,6 @@ class block extends context {
/**
* Returns block context instance.
*
* @static
* @param int $blockinstanceid id from {block_instances} table.
* @param int $strictness
* @return block|false context instance
@@ -140,14 +165,14 @@ class block extends context {
public static function instance($blockinstanceid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(CONTEXT_BLOCK, $blockinstanceid)) {
if ($context = context::cache_get(self::LEVEL, $blockinstanceid)) {
return $context;
}
if (!$record = $DB->get_record('context', array('contextlevel' => CONTEXT_BLOCK, 'instanceid' => $blockinstanceid))) {
if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $blockinstanceid))) {
if ($bi = $DB->get_record('block_instances', array('id' => $blockinstanceid), 'id,parentcontextid', $strictness)) {
$parentcontext = context::instance_by_id($bi->parentcontextid);
$record = context::insert_context_record(CONTEXT_BLOCK, $bi->id, $parentcontext->path);
$record = context::insert_context_record(self::LEVEL, $bi->id, $parentcontext->path);
}
}
@@ -170,7 +195,6 @@ class block extends context {
/**
* Create missing context instances at block context level
* @static
*/
protected static function create_level_instances() {
global $DB;
@@ -189,15 +213,14 @@ class block extends context {
EOF;
$DB->execute($sql, [
'contextlevel' => CONTEXT_BLOCK,
'existingcontextlevel' => CONTEXT_BLOCK,
'contextlevel' => self::LEVEL,
'existingcontextlevel' => self::LEVEL,
]);
}
/**
* Returns sql necessary for purging of stale context instances.
*
* @static
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
@@ -205,7 +228,7 @@ class block extends context {
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {block_instances} bi ON c.instanceid = bi.id
WHERE bi.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
WHERE bi.id IS NULL AND c.contextlevel = ".self::LEVEL."
";
return $sql;
@@ -214,24 +237,23 @@ class block extends context {
/**
* Rebuild context paths and depths at block context level.
*
* @static
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_BLOCK." AND (depth = 0 OR path IS NULL)")) {
if ($force || $DB->record_exists_select('context', "contextlevel = ".self::LEVEL." AND (depth = 0 OR path IS NULL)")) {
if ($force) {
$ctxemptyclause = '';
} else {
$ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
}
// pctx.path IS NOT NULL prevents fatal problems with broken block instances that point to invalid context parent
// The pctx.path IS NOT NULL prevents fatal problems with broken block instances that point to invalid context parent.
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
FROM {context} ctx
JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_BLOCK.")
JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = " . self::LEVEL . ")
JOIN {context} pctx ON (pctx.id = bi.parentcontextid)
WHERE (pctx.path IS NOT NULL AND pctx.depth > 0)
$ctxemptyclause";
+85 -37
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,7 +12,7 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
@@ -26,27 +26,40 @@ use coding_exception, moodle_url;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class course extends context {
/** @var int numeric context level value matching legacy CONTEXT_COURSE */
public const LEVEL = 50;
/**
* Please use context_course::instance($courseid) if you need the instance of context.
* Alternatively if you know only the context id use context::instance_by_id($contextid)
* Please use \core\context\course::instance($courseid) if you need the instance of context.
* Alternatively if you know only the context id use \core\context::instance_by_id($contextid)
*
* @param stdClass $record
*/
protected function __construct(stdClass $record) {
parent::__construct($record);
if ($record->contextlevel != CONTEXT_COURSE) {
throw new coding_exception('Invalid $record->contextlevel in context_course constructor.');
if ($record->contextlevel != self::LEVEL) {
throw new coding_exception('Invalid $record->contextlevel in core\context\course constructor.');
}
}
/**
* Returns short context name.
*
* @since Moodle 4.2
*
* @return string
*/
public static function get_short_name(): string {
return 'course';
}
/**
* Returns human readable context level name.
*
* @static
* @return string the human readable context level name.
*/
public static function get_level_name() {
@@ -68,11 +81,11 @@ class course extends context {
if ($this->_instanceid == SITEID) {
$name = get_string('frontpage', 'admin');
} else {
if ($course = $DB->get_record('course', array('id'=>$this->_instanceid))) {
if ($withprefix){
if ($course = $DB->get_record('course', array('id' => $this->_instanceid))) {
if ($withprefix) {
$name = get_string('course').': ';
}
if ($short){
if ($short) {
if (!$escape) {
$name .= format_string($course->shortname, true, array('context' => $this, 'escape' => false));
} else {
@@ -98,12 +111,52 @@ class course extends context {
*/
public function get_url() {
if ($this->_instanceid != SITEID) {
return new moodle_url('/course/view.php', array('id'=>$this->_instanceid));
return new moodle_url('/course/view.php', array('id' => $this->_instanceid));
}
return new moodle_url('/');
}
/**
* Returns context instance database name.
*
* @return string|null table name for all levels except system.
*/
protected static function get_instance_table(): ?string {
return 'course';
}
/**
* Returns list of columns that can be used from behat
* to look up context by reference.
*
* @return array list of column names from instance table
*/
protected static function get_behat_reference_columns(): array {
return ['shortname'];
}
/**
* Returns list of all role archetypes that are compatible
* with role assignments in context level.
* @since Moodle 4.2
*
* @return int[]
*/
protected static function get_compatible_role_archetypes(): array {
return ['manager', 'editingteacher', 'teacher', 'student'];
}
/**
* Returns list of all possible parent context levels.
* @since Moodle 4.2
*
* @return int[]
*/
public static function get_possible_parent_levels(): array {
return [coursecat::LEVEL];
}
/**
* Returns array of relevant context capability records.
*
@@ -113,11 +166,10 @@ class course extends context {
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB;
return $DB->get_records_list('capabilities', 'contextlevel', [
CONTEXT_COURSE,
CONTEXT_MODULE,
CONTEXT_BLOCK,
], $sort);
$levels = \core\context_helper::get_child_levels(self::LEVEL);
$levels[] = self::LEVEL;
return $DB->get_records_list('capabilities', 'contextlevel', $levels, $sort);
}
/**
@@ -133,25 +185,24 @@ class course extends context {
/**
* Returns course context instance.
*
* @static
* @param int $courseid id from {course} table
* @param int $strictness
* @return course context instance
* @return course|false context instance
*/
public static function instance($courseid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(CONTEXT_COURSE, $courseid)) {
if ($context = context::cache_get(self::LEVEL, $courseid)) {
return $context;
}
if (!$record = $DB->get_record('context', array('contextlevel' => CONTEXT_COURSE, 'instanceid' => $courseid))) {
if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $courseid))) {
if ($course = $DB->get_record('course', array('id' => $courseid), 'id,category', $strictness)) {
if ($course->category) {
$parentcontext = coursecat::instance($course->category);
$record = context::insert_context_record(CONTEXT_COURSE, $course->id, $parentcontext->path);
$record = context::insert_context_record(self::LEVEL, $course->id, $parentcontext->path);
} else {
$record = context::insert_context_record(CONTEXT_COURSE, $course->id, '/'.SYSCONTEXTID, 0);
$record = context::insert_context_record(self::LEVEL, $course->id, '/'.SYSCONTEXTID, 0);
}
}
}
@@ -167,19 +218,18 @@ class course extends context {
/**
* Create missing context instances at course context level
* @static
*/
protected static function create_level_instances() {
global $DB;
$sql = "SELECT ".CONTEXT_COURSE.", c.id
$sql = "SELECT ".self::LEVEL.", c.id
FROM {course} c
WHERE NOT EXISTS (SELECT 'x'
FROM {context} cx
WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
WHERE c.id = cx.instanceid AND cx.contextlevel=".self::LEVEL.")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(CONTEXT_COURSE, $context->id, null);
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
@@ -187,7 +237,6 @@ class course extends context {
/**
* Returns sql necessary for purging of stale context instances.
*
* @static
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
@@ -195,7 +244,7 @@ class course extends context {
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {course} co ON c.instanceid = co.id
WHERE co.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
WHERE co.id IS NULL AND c.contextlevel = ".self::LEVEL."
";
return $sql;
@@ -204,39 +253,38 @@ class course extends context {
/**
* Rebuild context paths and depths at course context level.
*
* @static
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSE." AND (depth = 0 OR path IS NULL)")) {
if ($force || $DB->record_exists_select('context', "contextlevel = ".self::LEVEL." AND (depth = 0 OR path IS NULL)")) {
if ($force) {
$ctxemptyclause = $emptyclause = '';
} else {
$ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
$emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
$emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
}
$base = '/'.SYSCONTEXTID;
// Standard frontpage
// Standard frontpage.
$sql = "UPDATE {context}
SET depth = 2,
path = ".$DB->sql_concat("'$base/'", 'id')."
WHERE contextlevel = ".CONTEXT_COURSE."
WHERE contextlevel = ".self::LEVEL."
AND EXISTS (SELECT 'x'
FROM {course} c
WHERE c.id = {context}.instanceid AND c.category = 0)
$emptyclause";
$DB->execute($sql);
// standard courses
// Standard courses.
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
FROM {context} ctx
JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSE." AND c.category <> 0)
JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".self::LEVEL." AND c.category <> 0)
JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".coursecat::LEVEL.")
WHERE pctx.path IS NOT NULL AND pctx.depth > 0
$ctxemptyclause";
$trans = $DB->start_delegated_transaction();
+87 -39
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,7 +12,7 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
@@ -26,27 +26,40 @@ use coding_exception, moodle_url;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class coursecat extends context {
/** @var int numeric context level value matching legacy CONTEXT_COURSECAT */
public const LEVEL = 40;
/**
* Please use context_coursecat::instance($coursecatid) if you need the instance of context.
* Alternatively if you know only the context id use context::instance_by_id($contextid)
* Please use \core\context\coursecat::instance($coursecatid) if you need the instance of context.
* Alternatively if you know only the context id use \core\context::instance_by_id($contextid)
*
* @param stdClass $record
*/
protected function __construct(stdClass $record) {
parent::__construct($record);
if ($record->contextlevel != CONTEXT_COURSECAT) {
throw new coding_exception('Invalid $record->contextlevel in context_coursecat constructor.');
if ($record->contextlevel != self::LEVEL) {
throw new coding_exception('Invalid $record->contextlevel in core\context\coursecat constructor.');
}
}
/**
* Returns short context name.
*
* @since Moodle 4.2
*
* @return string
*/
public static function get_short_name(): string {
return 'coursecat';
}
/**
* Returns human readable context level name.
*
* @static
* @return string the human readable context level name.
*/
public static function get_level_name() {
@@ -65,8 +78,8 @@ class coursecat extends context {
global $DB;
$name = '';
if ($category = $DB->get_record('course_categories', array('id'=>$this->_instanceid))) {
if ($withprefix){
if ($category = $DB->get_record('course_categories', array('id' => $this->_instanceid))) {
if ($withprefix) {
$name = get_string('category').': ';
}
if (!$escape) {
@@ -87,6 +100,46 @@ class coursecat extends context {
return new moodle_url('/course/index.php', array('categoryid' => $this->_instanceid));
}
/**
* Returns context instance database name.
*
* @return string|null table name for all levels except system.
*/
protected static function get_instance_table(): ?string {
return 'course_categories';
}
/**
* Returns list of columns that can be used from behat
* to look up context by reference.
*
* @return array list of column names from instance table
*/
protected static function get_behat_reference_columns(): array {
return ['idnumber'];
}
/**
* Returns list of all role archetypes that are compatible
* with role assignments in context level.
* @since Moodle 4.2
*
* @return int[]
*/
protected static function get_compatible_role_archetypes(): array {
return ['manager', 'coursecreator'];
}
/**
* Returns list of all possible parent context levels.
* @since Moodle 4.2
*
* @return int[]
*/
public static function get_possible_parent_levels(): array {
return [system::LEVEL, self::LEVEL];
}
/**
* Returns array of relevant context capability records.
*
@@ -96,18 +149,15 @@ class coursecat extends context {
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB;
return $DB->get_records_list('capabilities', 'contextlevel', [
CONTEXT_COURSECAT,
CONTEXT_COURSE,
CONTEXT_MODULE,
CONTEXT_BLOCK,
], $sort);
$levels = \core\context_helper::get_child_levels(self::LEVEL);
$levels[] = self::LEVEL;
return $DB->get_records_list('capabilities', 'contextlevel', $levels, $sort);
}
/**
* Returns course category context instance.
*
* @static
* @param int $categoryid id from {course_categories} table
* @param int $strictness
* @return coursecat|false context instance
@@ -115,17 +165,17 @@ class coursecat extends context {
public static function instance($categoryid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(CONTEXT_COURSECAT, $categoryid)) {
if ($context = context::cache_get(self::LEVEL, $categoryid)) {
return $context;
}
if (!$record = $DB->get_record('context', array('contextlevel' => CONTEXT_COURSECAT, 'instanceid' => $categoryid))) {
if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $categoryid))) {
if ($category = $DB->get_record('course_categories', array('id' => $categoryid), 'id,parent', $strictness)) {
if ($category->parent) {
$parentcontext = coursecat::instance($category->parent);
$record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, $parentcontext->path);
$parentcontext = self::instance($category->parent);
$record = context::insert_context_record(self::LEVEL, $category->id, $parentcontext->path);
} else {
$record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, '/'.SYSCONTEXTID, 0);
$record = context::insert_context_record(self::LEVEL, $category->id, '/'.SYSCONTEXTID, 0);
}
}
}
@@ -148,7 +198,7 @@ class coursecat extends context {
public function get_child_contexts() {
global $DB;
if (empty($this->_path) or empty($this->_depth)) {
if (empty($this->_path) || empty($this->_depth)) {
debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
return array();
}
@@ -156,7 +206,7 @@ class coursecat extends context {
$sql = "SELECT ctx.*
FROM {context} ctx
WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)";
$params = array($this->_path.'/%', $this->depth+1, CONTEXT_COURSECAT);
$params = array($this->_path.'/%', $this->depth + 1, self::LEVEL);
$records = $DB->get_records_sql($sql, $params);
$result = array();
@@ -169,19 +219,18 @@ class coursecat extends context {
/**
* Create missing context instances at course category context level
* @static
*/
protected static function create_level_instances() {
global $DB;
$sql = "SELECT ".CONTEXT_COURSECAT.", cc.id
$sql = "SELECT ".self::LEVEL.", cc.id
FROM {course_categories} cc
WHERE NOT EXISTS (SELECT 'x'
FROM {context} cx
WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
WHERE cc.id = cx.instanceid AND cx.contextlevel=".self::LEVEL.")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(CONTEXT_COURSECAT, $context->id, null);
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
@@ -189,7 +238,6 @@ class coursecat extends context {
/**
* Returns sql necessary for purging of stale context instances.
*
* @static
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
@@ -197,7 +245,7 @@ class coursecat extends context {
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {course_categories} cc ON c.instanceid = cc.id
WHERE cc.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
WHERE cc.id IS NULL AND c.contextlevel = ".self::LEVEL."
";
return $sql;
@@ -206,41 +254,41 @@ class coursecat extends context {
/**
* Rebuild context paths and depths at course category context level.
*
* @static
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSECAT." AND (depth = 0 OR path IS NULL)")) {
if ($force || $DB->record_exists_select('context', "contextlevel = ".self::LEVEL." AND (depth = 0 OR path IS NULL)")) {
if ($force) {
$ctxemptyclause = $emptyclause = '';
} else {
$ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
$emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
$emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
}
$base = '/'.SYSCONTEXTID;
// Normal top level categories
// Normal top level categories.
$sql = "UPDATE {context}
SET depth=2,
path=".$DB->sql_concat("'$base/'", 'id')."
WHERE contextlevel=".CONTEXT_COURSECAT."
WHERE contextlevel=".self::LEVEL."
AND EXISTS (SELECT 'x'
FROM {course_categories} cc
WHERE cc.id = {context}.instanceid AND cc.depth=1)
$emptyclause";
$DB->execute($sql);
// Deeper categories - one query per depthlevel
// Deeper categories - one query per depthlevel.
$maxdepth = $DB->get_field_sql("SELECT MAX(depth) FROM {course_categories}");
for ($n=2; $n<=$maxdepth; $n++) {
for ($n = 2; $n <= $maxdepth; $n++) {
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
FROM {context} ctx
JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT." AND cc.depth = $n)
JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".self::LEVEL."
AND cc.depth = $n)
JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".self::LEVEL.")
WHERE pctx.path IS NOT NULL AND pctx.depth > 0
$ctxemptyclause";
$trans = $DB->start_delegated_transaction();
+78 -30
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,7 +12,7 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
@@ -26,27 +26,40 @@ use coding_exception, moodle_url;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class module extends context {
/** @var int numeric context level value matching legacy CONTEXT_MODULE */
public const LEVEL = 70;
/**
* Please use context_module::instance($cmid) if you need the instance of context.
* Alternatively if you know only the context id use context::instance_by_id($contextid)
* Please use \core\context\module::instance($cmid) if you need the instance of context.
* Alternatively if you know only the context id use \core\context::instance_by_id($contextid)
*
* @param stdClass $record
*/
protected function __construct(stdClass $record) {
parent::__construct($record);
if ($record->contextlevel != CONTEXT_MODULE) {
throw new coding_exception('Invalid $record->contextlevel in context_module constructor.');
if ($record->contextlevel != self::LEVEL) {
throw new coding_exception('Invalid $record->contextlevel in core\context\module constructor.');
}
}
/**
* Returns short context name.
*
* @since Moodle 4.2
*
* @return string
*/
public static function get_short_name(): string {
return 'module';
}
/**
* Returns human readable context level name.
*
* @static
* @return string the human readable context level name.
*/
public static function get_level_name() {
@@ -71,7 +84,7 @@ class module extends context {
JOIN {modules} md ON md.id = cm.module
WHERE cm.id = ?", array($this->_instanceid))) {
if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) {
if ($withprefix){
if ($withprefix) {
$name = get_string('modulename', $cm->modname).': ';
}
if (!$escape) {
@@ -96,12 +109,52 @@ class module extends context {
FROM {course_modules} cm
JOIN {modules} md ON md.id = cm.module
WHERE cm.id = ?", array($this->_instanceid))) {
return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$this->_instanceid));
return new moodle_url('/mod/' . $modname . '/view.php', array('id' => $this->_instanceid));
}
return new moodle_url('/');
}
/**
* Returns context instance database name.
*
* @return string|null table name for all levels except system.
*/
protected static function get_instance_table(): ?string {
return 'course_modules';
}
/**
* Returns list of columns that can be used from behat
* to look up context by reference.
*
* @return array list of column names from instance table
*/
protected static function get_behat_reference_columns(): array {
return ['idnumber'];
}
/**
* Returns list of all role archetypes that are compatible
* with role assignments in context level.
* @since Moodle 4.2
*
* @return int[]
*/
protected static function get_compatible_role_archetypes(): array {
return ['editingteacher', 'teacher', 'student'];
}
/**
* Returns list of all possible parent context levels.
* @since Moodle 4.2
*
* @return int[]
*/
public static function get_possible_parent_levels(): array {
return [course::LEVEL];
}
/**
* Returns array of relevant context capability records.
*
@@ -111,8 +164,8 @@ class module extends context {
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB, $CFG;
$cm = $DB->get_record('course_modules', array('id'=>$this->_instanceid));
$module = $DB->get_record('modules', array('id'=>$cm->module));
$cm = $DB->get_record('course_modules', array('id' => $this->_instanceid));
$module = $DB->get_record('modules', array('id' => $cm->module));
$subcaps = array();
@@ -123,13 +176,13 @@ class module extends context {
debugging('Use of subplugins.php has been deprecated. ' .
'Please update your plugin to provide a subplugins.json file instead.',
DEBUG_DEVELOPER);
$subplugins = array(); // should be redefined in the file
$subplugins = array(); // Should be redefined in the file.
include("{$modulepath}/db/subplugins.php");
}
if (!empty($subplugins)) {
foreach (array_keys($subplugins) as $subplugintype) {
foreach (array_keys(core_component::get_plugin_list($subplugintype)) as $subpluginname) {
foreach (array_keys(\core_component::get_plugin_list($subplugintype)) as $subpluginname) {
$subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname)));
}
}
@@ -163,7 +216,6 @@ class module extends context {
list($notcompsql, $notcompparams) = $DB->get_in_or_equal($componentnames, SQL_PARAMS_NAMED, 'notcomp', false);
$params = array_merge($params, $notcompparams);
// Exclude other component submodules.
$i = 0;
$ignorecomponents = [];
@@ -181,7 +233,7 @@ class module extends context {
$sql = "SELECT *
FROM {capabilities}
WHERE (contextlevel = ".CONTEXT_MODULE."
WHERE (contextlevel = ".self::LEVEL."
AND component {$notcompsql}
AND {$notlikesql})
$extra
@@ -203,7 +255,6 @@ class module extends context {
/**
* Returns module context instance.
*
* @static
* @param int $cmid id of the record from {course_modules} table; pass cmid there, NOT id in the instance column
* @param int $strictness
* @return module|false context instance
@@ -211,14 +262,14 @@ class module extends context {
public static function instance($cmid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(CONTEXT_MODULE, $cmid)) {
if ($context = context::cache_get(self::LEVEL, $cmid)) {
return $context;
}
if (!$record = $DB->get_record('context', array('contextlevel' => CONTEXT_MODULE, 'instanceid' => $cmid))) {
if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $cmid))) {
if ($cm = $DB->get_record('course_modules', array('id' => $cmid), 'id,course', $strictness)) {
$parentcontext = course::instance($cm->course);
$record = context::insert_context_record(CONTEXT_MODULE, $cm->id, $parentcontext->path);
$record = context::insert_context_record(self::LEVEL, $cm->id, $parentcontext->path);
}
}
@@ -233,19 +284,18 @@ class module extends context {
/**
* Create missing context instances at module context level
* @static
*/
protected static function create_level_instances() {
global $DB;
$sql = "SELECT ".CONTEXT_MODULE.", cm.id
$sql = "SELECT " . self::LEVEL . ", cm.id
FROM {course_modules} cm
WHERE NOT EXISTS (SELECT 'x'
FROM {context} cx
WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
WHERE cm.id = cx.instanceid AND cx.contextlevel=" . self::LEVEL . ")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(CONTEXT_MODULE, $context->id, null);
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
@@ -253,7 +303,6 @@ class module extends context {
/**
* Returns sql necessary for purging of stale context instances.
*
* @static
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
@@ -261,7 +310,7 @@ class module extends context {
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {course_modules} cm ON c.instanceid = cm.id
WHERE cm.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
WHERE cm.id IS NULL AND c.contextlevel = " . self::LEVEL . "
";
return $sql;
@@ -270,13 +319,12 @@ class module extends context {
/**
* Rebuild context paths and depths at module context level.
*
* @static
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_MODULE." AND (depth = 0 OR path IS NULL)")) {
if ($force || $DB->record_exists_select('context', "contextlevel = " . self::LEVEL . " AND (depth = 0 OR path IS NULL)")) {
if ($force) {
$ctxemptyclause = '';
} else {
@@ -286,8 +334,8 @@ class module extends context {
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
FROM {context} ctx
JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_MODULE.")
JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = ".CONTEXT_COURSE.")
JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = " . self::LEVEL . ")
JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = " . course::LEVEL . ")
WHERE pctx.path IS NOT NULL AND pctx.depth > 0
$ctxemptyclause";
$trans = $DB->start_delegated_transaction();
+80 -48
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,7 +12,7 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
@@ -26,26 +26,39 @@ use coding_exception, moodle_url;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class system extends context {
/** @var int numeric context level value matching legacy CONTEXT_SYSTEM */
public const LEVEL = 10;
/**
* Please use context_system::instance() if you need the instance of context.
* Please use \core\context\system::instance() if you need the instance of context.
*
* @param stdClass $record
*/
protected function __construct(stdClass $record) {
parent::__construct($record);
if ($record->contextlevel != CONTEXT_SYSTEM) {
throw new coding_exception('Invalid $record->contextlevel in context_system constructor.');
if ($record->contextlevel != self::LEVEL) {
throw new coding_exception('Invalid $record->contextlevel in core\context\system constructor.');
}
}
/**
* Returns short context name.
*
* @since Moodle 4.2
*
* @return string
*/
public static function get_short_name(): string {
return 'system';
}
/**
* Returns human readable context level name.
*
* @static
* @return string the human readable context level name.
*/
public static function get_level_name() {
@@ -73,6 +86,27 @@ class system extends context {
return new moodle_url('/');
}
/**
* Returns list of all role archetypes that are compatible
* with role assignments in context level.
* @since Moodle 4.2
*
* @return int[]
*/
protected static function get_compatible_role_archetypes(): array {
return ['manager', 'coursecreator'];
}
/**
* Returns list of all possible parent context levels.
* @since Moodle 4.2
*
* @return int[]
*/
public static function get_possible_parent_levels(): array {
return [];
}
/**
* Returns array of relevant context capability records.
*
@@ -87,17 +121,15 @@ class system extends context {
/**
* Create missing context instances at system context
* @static
*/
protected static function create_level_instances() {
// nothing to do here, the system context is created automatically in installer
// Nothing to do here, the system context is created automatically in installer.
self::instance(0);
}
/**
* Returns system context instance.
*
* @static
* @param int $instanceid should be 0
* @param int $strictness
* @param bool $cache
@@ -111,15 +143,15 @@ class system extends context {
}
// SYSCONTEXTID is cached in local cache to eliminate 1 query per page.
if (defined('SYSCONTEXTID') and $cache) {
if (defined('SYSCONTEXTID') && $cache) {
if (!isset(context::$systemcontext)) {
$record = new stdClass();
$record->id = SYSCONTEXTID;
$record->contextlevel = CONTEXT_SYSTEM;
$record->instanceid = 0;
$record->path = '/'.SYSCONTEXTID;
$record->depth = 1;
$record->locked = 0;
$record->id = SYSCONTEXTID;
$record->contextlevel = self::LEVEL;
$record->instanceid = 0;
$record->path = '/'.SYSCONTEXTID;
$record->depth = 1;
$record->locked = 0;
context::$systemcontext = new system($record);
}
return context::$systemcontext;
@@ -127,11 +159,11 @@ class system extends context {
try {
// We ignore the strictness completely because system context must exist except during install.
$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST);
$record = $DB->get_record('context', array('contextlevel' => self::LEVEL), '*', MUST_EXIST);
} catch (\dml_exception $e) {
//table or record does not exist
// Table or record does not exist.
if (!during_initial_install()) {
// do not mess with system context after install, it simply must exist
// Do not mess with system context after install, it simply must exist.
throw $e;
}
$record = null;
@@ -139,19 +171,19 @@ class system extends context {
if (!$record) {
$record = new stdClass();
$record->contextlevel = CONTEXT_SYSTEM;
$record->instanceid = 0;
$record->depth = 1;
$record->path = null; // Not known before insert.
$record->locked = 0;
$record->contextlevel = self::LEVEL;
$record->instanceid = 0;
$record->depth = 1;
$record->path = null; // Not known before insert.
$record->locked = 0;
try {
if ($DB->count_records('context')) {
// contexts already exist, this is very weird, system must be first!!!
// Contexts already exist, this is very weird, system must be first!!!
return null;
}
if (defined('SYSCONTEXTID')) {
// this would happen only in unittest on sites that went through weird 1.7 upgrade
// This would happen only in unittest on sites that went through weird 1.7 upgrade.
$record->id = SYSCONTEXTID;
$DB->import_record('context', $record);
$DB->get_manager()->reset_sequence('context');
@@ -159,20 +191,20 @@ class system extends context {
$record->id = $DB->insert_record('context', $record);
}
} catch (\dml_exception $e) {
// can not create context - table does not exist yet, sorry
// Can not create context - table does not exist yet, sorry.
return null;
}
}
if ($record->instanceid != 0) {
// this is very weird, somebody must be messing with context table
// This is very weird, somebody must be messing with context table.
debugging('Invalid system context detected');
}
if ($record->depth != 1 or $record->path != '/'.$record->id) {
// fix path if necessary, initial install or path reset
if ($record->depth != 1 || $record->path != '/'.$record->id) {
// Fix path if necessary, initial install or path reset.
$record->depth = 1;
$record->path = '/'.$record->id;
$record->path = '/'.$record->id;
$DB->update_record('context', $record);
}
@@ -198,13 +230,14 @@ class system extends context {
public function get_child_contexts() {
global $DB;
debugging('Fetching of system context child courses is strongly discouraged on production servers (it may eat all available memory)!');
debugging('Fetching of system context child courses is strongly discouraged'
. ' on production servers (it may eat all available memory)!');
// Just get all the contexts except for CONTEXT_SYSTEM level
// and hope we don't OOM in the process - don't cache
// Just get all the contexts except for system level
// and hope we don't OOM in the process - don't cache.
$sql = "SELECT c.*
FROM {context} c
WHERE contextlevel > ".CONTEXT_SYSTEM;
WHERE contextlevel > " . self::LEVEL;
$records = $DB->get_records_sql($sql);
$result = array();
@@ -218,7 +251,6 @@ class system extends context {
/**
* Returns sql necessary for purging of stale context instances.
*
* @static
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
@@ -234,7 +266,6 @@ class system extends context {
/**
* Rebuild context paths and depths at system context level.
*
* @static
* @param bool $force
*/
protected static function build_paths($force) {
@@ -242,21 +273,21 @@ class system extends context {
/* note: ignore $force here, we always do full test of system context */
// exactly one record must exist
$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST);
// Exactly one record must exist.
$record = $DB->get_record('context', array('contextlevel' => self::LEVEL), '*', MUST_EXIST);
if ($record->instanceid != 0) {
debugging('Invalid system context detected');
}
if (defined('SYSCONTEXTID') and $record->id != SYSCONTEXTID) {
if (defined('SYSCONTEXTID') && $record->id != SYSCONTEXTID) {
debugging('Invalid SYSCONTEXTID detected');
}
if ($record->depth != 1 or $record->path != '/'.$record->id) {
// fix path if necessary, initial install or path reset
$record->depth = 1;
$record->path = '/'.$record->id;
if ($record->depth != 1 || $record->path != '/'.$record->id) {
// Fix path if necessary, initial install or path reset.
$record->depth = 1;
$record->path = '/'.$record->id;
$DB->update_record('context', $record);
}
}
@@ -268,8 +299,9 @@ class system extends context {
* @return $this
*/
public function set_locked(bool $locked) {
throw new \coding_exception('It is not possible to lock the system context');
return $this;
if ($locked) {
throw new \coding_exception('It is not possible to lock the system context');
}
return parent::set_locked($locked);
}
}
+63 -25
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,7 +12,7 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
@@ -26,27 +26,40 @@ use coding_exception, moodle_url;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class user extends context {
/** @var int numeric context level value matching legacy CONTEXT_USER */
public const LEVEL = 30;
/**
* Please use context_user::instance($userid) if you need the instance of context.
* Alternatively if you know only the context id use context::instance_by_id($contextid)
* Please use \core\context\user::instance($userid) if you need the instance of context.
* Alternatively if you know only the context id use \core\context::instance_by_id($contextid)
*
* @param stdClass $record
*/
protected function __construct(stdClass $record) {
parent::__construct($record);
if ($record->contextlevel != CONTEXT_USER) {
throw new coding_exception('Invalid $record->contextlevel in context_user constructor.');
if ($record->contextlevel != self::LEVEL) {
throw new coding_exception('Invalid $record->contextlevel in core\context\user constructor.');
}
}
/**
* Returns short context name.
*
* @since Moodle 4.2
*
* @return string
*/
public static function get_short_name(): string {
return 'user';
}
/**
* Returns human readable context level name.
*
* @static
* @return string the human readable context level name.
*/
public static function get_level_name() {
@@ -65,8 +78,8 @@ class user extends context {
global $DB;
$name = '';
if ($user = $DB->get_record('user', array('id'=>$this->_instanceid, 'deleted'=>0))) {
if ($withprefix){
if ($user = $DB->get_record('user', array('id' => $this->_instanceid, 'deleted' => 0))) {
if ($withprefix) {
$name = get_string('user').': ';
}
$name .= fullname($user);
@@ -83,13 +96,42 @@ class user extends context {
global $COURSE;
if ($COURSE->id == SITEID) {
$url = new moodle_url('/user/profile.php', array('id'=>$this->_instanceid));
$url = new moodle_url('/user/profile.php', array('id' => $this->_instanceid));
} else {
$url = new moodle_url('/user/view.php', array('id'=>$this->_instanceid, 'courseid'=>$COURSE->id));
$url = new moodle_url('/user/view.php', array('id' => $this->_instanceid, 'courseid' => $COURSE->id));
}
return $url;
}
/**
* Returns list of all possible parent context levels.
* @since Moodle 4.2
*
* @return int[]
*/
public static function get_possible_parent_levels(): array {
return [system::LEVEL];
}
/**
* Returns context instance database name.
*
* @return string|null table name for all levels except system.
*/
protected static function get_instance_table(): ?string {
return 'user';
}
/**
* Returns list of columns that can be used from behat
* to look up context by reference.
*
* @return array list of column names from instance table
*/
protected static function get_behat_reference_columns(): array {
return ['username'];
}
/**
* Returns array of relevant context capability records.
*
@@ -103,13 +145,12 @@ class user extends context {
list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
return $DB->get_records_select('capabilities', "contextlevel = :level OR name {$extra}",
$params + ['level' => CONTEXT_USER], $sort);
$params + ['level' => self::LEVEL], $sort);
}
/**
* Returns user context instance.
*
* @static
* @param int $userid id from {user} table
* @param int $strictness
* @return user|false context instance
@@ -117,13 +158,13 @@ class user extends context {
public static function instance($userid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(CONTEXT_USER, $userid)) {
if ($context = context::cache_get(self::LEVEL, $userid)) {
return $context;
}
if (!$record = $DB->get_record('context', array('contextlevel' => CONTEXT_USER, 'instanceid' => $userid))) {
if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $userid))) {
if ($user = $DB->get_record('user', array('id' => $userid, 'deleted' => 0), 'id', $strictness)) {
$record = context::insert_context_record(CONTEXT_USER, $user->id, '/'.SYSCONTEXTID, 0);
$record = context::insert_context_record(self::LEVEL, $user->id, '/'.SYSCONTEXTID, 0);
}
}
@@ -138,20 +179,19 @@ class user extends context {
/**
* Create missing context instances at user context level
* @static
*/
protected static function create_level_instances() {
global $DB;
$sql = "SELECT ".CONTEXT_USER.", u.id
$sql = "SELECT " . self::LEVEL . ", u.id
FROM {user} u
WHERE u.deleted = 0
AND NOT EXISTS (SELECT 'x'
FROM {context} cx
WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
WHERE u.id = cx.instanceid AND cx.contextlevel=" . self::LEVEL . ")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(CONTEXT_USER, $context->id, null);
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
@@ -159,7 +199,6 @@ class user extends context {
/**
* Returns sql necessary for purging of stale context instances.
*
* @static
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
@@ -167,7 +206,7 @@ class user extends context {
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {user} u ON (c.instanceid = u.id AND u.deleted = 0)
WHERE u.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
WHERE u.id IS NULL AND c.contextlevel = " . self::LEVEL . "
";
return $sql;
@@ -176,7 +215,6 @@ class user extends context {
/**
* Rebuild context paths and depths at user context level.
*
* @static
* @param bool $force
*/
protected static function build_paths($force) {
@@ -197,7 +235,7 @@ class user extends context {
$sql = "UPDATE {context}
SET depth = 2,
path = {$path}
WHERE contextlevel = " . CONTEXT_USER . "
WHERE contextlevel = " . self::LEVEL . "
AND ($where)";
$DB->execute($sql, $params);
}
+188 -65
View File
@@ -1,5 +1,5 @@
<?php
// This file is part of Moodle - http://moodle.org/
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -12,14 +12,12 @@
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core;
use stdClass;
use coding_exception;
use core\context\system;
use core\context\course;
/**
* Context maintenance and helper methods.
@@ -33,22 +31,16 @@ use core\context\course;
* @package core_access
* @category access
* @copyright Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class context_helper extends context {
abstract class context_helper extends context {
/**
* @var array An array mapping context levels to classes
* @var array An array definitions of all context levels
*/
private static $alllevels;
/**
* Instance does not make sense here, only static use
*/
protected function __construct() {
}
/**
* Reset internal context levels array.
*/
@@ -59,19 +51,19 @@ class context_helper extends context {
/**
* Initialise context levels, call before using self::$alllevels.
*/
private static function init_levels() {
private static function init_levels():void {
global $CFG;
if (isset(self::$alllevels)) {
return;
}
self::$alllevels = array(
CONTEXT_SYSTEM => 'context_system',
CONTEXT_USER => 'context_user',
CONTEXT_COURSECAT => 'context_coursecat',
CONTEXT_COURSE => 'context_course',
CONTEXT_MODULE => 'context_module',
CONTEXT_BLOCK => 'context_block',
CONTEXT_SYSTEM => \core\context\system::class,
CONTEXT_USER => \core\context\user::class,
CONTEXT_COURSECAT => \core\context\coursecat::class,
CONTEXT_COURSE => \core\context\course::class,
CONTEXT_MODULE => \core\context\module::class,
CONTEXT_BLOCK => \core\context\block::class,
);
if (empty($CFG->custom_context_classes)) {
@@ -94,14 +86,113 @@ class context_helper extends context {
ksort(self::$alllevels);
}
/**
* Converts legacy context_* class name to new class name.
*
* NOTE: this is needed for external API which uses short context names.
* @since Moodle 4.2
*
* @param int|string $extlevel
* @return string|null context class name or null if not found
*/
public static function parse_external_level($extlevel): ?string {
self::init_levels();
if (is_number($extlevel)) {
if (isset(self::$alllevels[$extlevel])) {
return self::$alllevels[$extlevel];
} else {
return null;
}
}
if ($extlevel && is_string($extlevel)) {
$found = null;
foreach (self::$alllevels as $classname) {
if ($classname::get_short_name() === $extlevel) {
if ($found) {
debugging("Duplicate short context level name found '$extlevel', use numeric value instead",
DEBUG_DEVELOPER);
} else {
$found = $classname;
}
}
}
return $found;
}
return null;
}
/**
* Resolve reference to context used in behat feature files.
*
* @param string $level
* @param string $reference
* @return context|null
*/
public static function resolve_behat_reference(string $level, string $reference): ?context {
global $DB;
if (!PHPUNIT_TEST && !defined('BEHAT_SITE_RUNNING')) {
throw new coding_exception('resolve_behat_reference() cannot be used outside of tests');
}
self::init_levels();
$classname = null;
if (is_number($level)) {
if (isset(self::$alllevels[$level])) {
$classname = self::$alllevels[$level];
}
} else {
foreach (self::$alllevels as $levelclassname) {
if ($level === $levelclassname::get_level_name()) {
$classname = $levelclassname;
break;
}
if ($level === $levelclassname::get_short_name()) {
$classname = $levelclassname;
break;
}
}
}
if (!$classname) {
return null;
}
if ($classname::LEVEL === context\system::LEVEL) {
return context\system::instance();
}
if (trim($reference) === '') {
return null;
}
$table = $classname::get_instance_table();
if (!$table) {
return null;
}
$columns = $classname::get_behat_reference_columns();
foreach ($columns as $column) {
$instance = $DB->get_record($table, [$column => $reference]);
if ($instance) {
$context = $classname::instance($instance->id, IGNORE_MISSING);
if ($context) {
return $context;
}
return null;
}
}
return null;
}
/**
* Returns a class name of the context level class
*
* @static
* @param int $contextlevel (CONTEXT_SYSTEM, etc.)
* @return string class name of the context class
* @throws coding_exception if level does not exist
*/
public static function get_class_for_level($contextlevel) {
public static function get_class_for_level(int $contextlevel): string {
self::init_levels();
if (isset(self::$alllevels[$contextlevel])) {
return self::$alllevels[$contextlevel];
@@ -113,19 +204,75 @@ class context_helper extends context {
/**
* Returns a list of all context levels
*
* @static
* @return array int=>string (level=>level class name)
*/
public static function get_all_levels() {
public static function get_all_levels(): array {
self::init_levels();
return self::$alllevels;
}
/**
* Get list of possible child levels for given level.
* @since Moodle 4.2
*
* @param int $parentlevel
* @return int[] list of context levels that my be children of given context level.
*/
public static function get_child_levels(int $parentlevel): array {
self::init_levels();
$result = [];
$definitions = self::$alllevels;
$recursion = function(int $pl) use (&$result, $definitions, &$recursion): void {
foreach ($definitions as $contextlevel => $classname) {
$parentlevels = $classname::get_possible_parent_levels();
if (in_array($pl, $parentlevels)) {
if (isset($result[$contextlevel])) {
continue;
}
$result[$contextlevel] = $contextlevel;
$recursion($contextlevel);
}
}
};
$recursion($parentlevel);
$classname = self::get_class_for_level($parentlevel);
$parentlevels = $classname::get_possible_parent_levels();
if (!in_array($parentlevel, $parentlevels)) {
unset($result[$parentlevel]);
}
return array_values($result);
}
/**
* Returns context levels that compatible with role archetype assignments.
* @since Moodle 4.2
*
* @param string $archetype
* @return array
*/
public static function get_compatible_levels(string $archetype): array {
self::init_levels();
$result = [];
foreach (self::$alllevels as $contextlevel => $classname) {
$compatiblearchetypes = $classname::get_compatible_role_archetypes();
foreach ($compatiblearchetypes as $at) {
if ($at === $archetype) {
$result[] = $contextlevel;
}
}
}
return $result;
}
/**
* Remove stale contexts that belonged to deleted instances.
* Ideally all code should cleanup contexts properly, unfortunately accidents happen...
*
* @static
* @return void
*/
public static function cleanup_instances() {
@@ -133,13 +280,13 @@ class context_helper extends context {
self::init_levels();
$sqls = array();
foreach (self::$alllevels as $level=>$classname) {
foreach (self::$alllevels as $classname) {
$sqls[] = $classname::get_cleanup_sql();
}
$sql = implode(" UNION ", $sqls);
// it is probably better to use transactions, it might be faster too
// It is probably better to use transactions, it might be faster too.
$transaction = $DB->start_delegated_transaction();
$rs = $DB->get_recordset_sql($sql);
@@ -155,16 +302,16 @@ class context_helper extends context {
/**
* Create all context instances at the given level and above.
*
* @static
* @param int $contextlevel null means all levels
* @param bool $buildpaths
* @return void
*/
public static function create_instances($contextlevel = null, $buildpaths = true) {
self::init_levels();
foreach (self::$alllevels as $level=>$classname) {
if ($contextlevel and $level > $contextlevel) {
// skip potential sub-contexts
foreach (self::$alllevels as $level => $classname) {
if ($contextlevel && $contextlevel != context\block::LEVEL && $level > $contextlevel) {
// Skip potential sub-contexts,
// in case of blocks build all contexts because plugin contexts may have higher levels.
continue;
}
$classname::create_level_instances();
@@ -177,7 +324,6 @@ class context_helper extends context {
/**
* Rebuild paths and depths in all context levels.
*
* @static
* @param bool $force false means add missing only
* @return void
*/
@@ -187,13 +333,12 @@ class context_helper extends context {
$classname::build_paths($force);
}
// reset static course cache - it might have incorrect cached data
// Reset static course cache - it might have incorrect cached data.
accesslib_clear_all_caches(true);
}
/**
* Resets the cache to remove all data.
* @static
*/
public static function reset_caches() {
context::reset_caches();
@@ -204,7 +349,6 @@ class context_helper extends context {
*
* This helps with performance when dealing with hundreds of contexts.
*
* @static
* @param string $tablealias context table alias in the query
* @return array (table.column=>alias, ...)
*/
@@ -224,7 +368,6 @@ class context_helper extends context {
*
* This helps with performance when dealing with hundreds of contexts.
*
* @static
* @param string $tablealias context table alias in the query
* @return string
*/
@@ -242,12 +385,11 @@ class context_helper extends context {
*
* The db request has to contain all columns from context_helper::get_preload_record_columns().
*
* @static
* @param stdClass $rec
* @return void This is intentional. See MDL-37115. You will need to get the context
* in the normal way, but it is now cached, so that will be fast.
*/
public static function preload_from_record(stdClass $rec) {
public static function preload_from_record(stdClass $rec): void {
context::preload_from_record($rec);
}
@@ -256,7 +398,7 @@ class context_helper extends context {
*
* @param array $contextids
*/
public static function preload_contexts_by_id(array $contextids) {
public static function preload_contexts_by_id(array $contextids): void {
global $DB;
// Determine which contexts are not already cached.
@@ -272,7 +414,7 @@ class context_helper extends context {
// There is no point only fetching a single context as this would be no more efficient than calling the existing code.
list($insql, $inparams) = $DB->get_in_or_equal($tofetch, SQL_PARAMS_NAMED);
$ctxs = $DB->get_records_select('context', "id {$insql}", $inparams, '',
context_helper::get_preload_record_columns_sql('{context}'));
self::get_preload_record_columns_sql('{context}'));
foreach ($ctxs as $ctx) {
self::preload_from_record($ctx);
}
@@ -284,15 +426,14 @@ class context_helper extends context {
*
* To be used if you expect multiple queries for course activities...
*
* @static
* @param int $courseid
*/
public static function preload_course($courseid) {
// Users can call this multiple times without doing any harm
// Users can call this multiple times without doing any harm.
if (isset(context::$cache_preloaded[$courseid])) {
return;
}
$coursecontext = course::instance($courseid);
$coursecontext = context\course::instance($courseid);
$coursecontext->get_child_contexts();
context::$cache_preloaded[$courseid] = true;
@@ -301,7 +442,6 @@ class context_helper extends context {
/**
* Delete context instance
*
* @static
* @param int $contextlevel
* @param int $instanceid
* @return void
@@ -309,24 +449,21 @@ class context_helper extends context {
public static function delete_instance($contextlevel, $instanceid) {
global $DB;
// double check the context still exists
if ($record = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
// Double check the context still exists.
if ($record = $DB->get_record('context', array('contextlevel' => $contextlevel, 'instanceid' => $instanceid))) {
$context = context::create_instance_from_record($record);
$context->delete();
} else {
// we should try to purge the cache anyway
}
}
/**
* Returns the name of specified context level
*
* @static
* @param int $contextlevel
* @return string name of the context level
*/
public static function get_level_name($contextlevel) {
$classname = context_helper::get_class_for_level($contextlevel);
$classname = self::get_class_for_level($contextlevel);
return $classname::get_level_name();
}
@@ -339,23 +476,9 @@ class context_helper extends context {
public static function get_navigation_filter_context(?context $context): ?context {
global $CFG;
if (!empty($CFG->filternavigationwithsystemcontext)) {
return system::instance();
return context\system::instance();
} else {
return $context;
}
}
/**
* not used
*/
public function get_url() {
}
/**
* not used
*
* @param string $sort
*/
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
}
}
+9 -7
View File
@@ -519,7 +519,9 @@ class external_api {
* The passed array must either contain a contextid or a combination of context level and instance id to fetch the context.
* For example, the context level can be "course" and instanceid can be courseid.
*
* See context_helper::get_all_levels() for a list of valid context levels.
* See context_helper::get_all_levels() for a list of valid numeric context levels,
* legacy short names such as 'system', 'user', 'course' are not supported in new
* plugin capabilities.
*
* @param array $param
* @since Moodle 2.6
@@ -527,15 +529,15 @@ class external_api {
* @return context
*/
protected static function get_context_from_params($param) {
$levels = context_helper::get_all_levels();
if (!empty($param['contextid'])) {
return context::instance_by_id($param['contextid'], IGNORE_MISSING);
} else if (!empty($param['contextlevel']) && isset($param['instanceid'])) {
$contextlevel = "context_{$param['contextlevel']}";
if (!array_search($contextlevel, $levels)) {
throw new invalid_parameter_exception("Invalid context level = {$param['contextlevel']}");
// Numbers and short names are supported since Moodle 4.2.
$classname = \core\context_helper::parse_external_level($param['contextlevel']);
if (!$classname) {
throw new invalid_parameter_exception('Invalid context level = '.$param['contextlevel']);
}
return $contextlevel::instance($param['instanceid'], IGNORE_MISSING);
return $classname::instance($param['instanceid'], IGNORE_MISSING);
} else {
// No valid context info was found.
throw new invalid_parameter_exception(
@@ -556,7 +558,7 @@ class external_api {
0
);
$level = new external_value(
PARAM_ALPHA,
PARAM_ALPHANUM, // Since Moodle 4.2 numeric context level values are supported too.
'Context level. To be used with instanceid.',
VALUE_DEFAULT,
''
+22 -3
View File
@@ -184,6 +184,11 @@ class external_api_test extends \advanced_testcase {
$fetchedcontext = $this->get_context_from_params(["contextlevel" => "course", "instanceid" => $course->id]);
$this->assertEquals($realcontext, $fetchedcontext);
// Use context level numbers instead of legacy short level names.
$fetchedcontext = $this->get_context_from_params(
["contextlevel" => \core\context\course::LEVEL, "instanceid" => $course->id]);
$this->assertEquals($realcontext, $fetchedcontext);
// Passing empty values.
try {
$fetchedcontext = $this->get_context_from_params(["contextid" => 0]);
@@ -211,9 +216,23 @@ class external_api_test extends \advanced_testcase {
$fetchedcontext = $this->get_context_from_params(["contextlevel" => "system", "instanceid" => 0]);
$this->assertEquals($realcontext, $fetchedcontext);
// Passing wrong level.
$this->expectException('invalid_parameter_exception');
$fetchedcontext = $this->get_context_from_params(["contextlevel" => "random", "instanceid" => $course->id]);
// Passing wrong level name.
try {
$fetchedcontext = $this->get_context_from_params(["contextlevel" => "random", "instanceid" => $course->id]);
$this->fail('exception expected when level name is invalid');
} catch (\moodle_exception $e) {
$this->assertInstanceOf('invalid_parameter_exception', $e);
$this->assertSame('Invalid parameter value detected (Invalid context level = random)', $e->getMessage());
}
// Passing wrong level number.
try {
$fetchedcontext = $this->get_context_from_params(["contextlevel" => -10, "instanceid" => $course->id]);
$this->fail('exception expected when level name is invalid');
} catch (\moodle_exception $e) {
$this->assertInstanceOf('invalid_parameter_exception', $e);
$this->assertSame('Invalid parameter value detected (Invalid context level = -10)', $e->getMessage());
}
}
/**
+1 -1
View File
@@ -5190,7 +5190,7 @@ class accesslib_test extends advanced_testcase {
/**
* Context caching fixture
*/
class context_inspection extends context_helper {
abstract class context_inspection extends \core\context_helper {
public static function test_context_cache_size() {
return self::$cache_count;
}
+269
View File
@@ -0,0 +1,269 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
use core\context, core\context_helper;
/**
* Unit tests for block context class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context\block
*/
class block_test extends \advanced_testcase {
/**
* Tests legacy class name.
* @covers \context_block
*/
public function test_legacy_classname() {
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$this->assertInstanceOf(block::class, $context);
$this->assertInstanceOf(\context_block::class, $context);
}
/**
* Tests covered methods.
* @covers ::instance
* @covers \core\context::instance_by_id
*/
public function test_factory_methods() {
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$this->assertInstanceOf(block::class, $context);
$this->assertSame((string)$block->id, $context->instanceid);
$context = context::instance_by_id($context->id);
$this->assertInstanceOf(block::class, $context);
$this->assertSame((string)$block->id, $context->instanceid);
}
/**
* Tests covered method.
* @covers ::get_short_name
*/
public function test_get_short_name() {
$this->assertSame('block', block::get_short_name());
}
/**
* Tests levels.
* @coversNothing
*/
public function test_level() {
$this->assertSame(80, block::LEVEL);
$this->assertSame(CONTEXT_BLOCK, block::LEVEL);
}
/**
* Tests covered method.
* @covers ::get_level_name
*/
public function test_get_level_name() {
$this->assertSame('Block', block::get_level_name());
}
/**
* Tests covered method.
* @covers ::get_context_name
*/
public function test_get_context_name() {
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$this->assertSame('Block: Online users', $context->get_context_name());
$this->assertSame('Block: Online users', $context->get_context_name(true));
$this->assertSame('Online users', $context->get_context_name(false));
$this->assertSame('Online users', $context->get_context_name(false, true));
$this->assertSame('Block: Online users', $context->get_context_name(true, true, false));
}
/**
* Tests covered method.
* @covers ::get_url
*/
public function test_get_url() {
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$expected = new \moodle_url('/');
$url = $context->get_url();
$this->assertInstanceOf(\moodle_url::class, $url);
$this->assertSame($expected->out(), $url->out());
}
/**
* Tests covered method.
* @covers ::get_compatible_role_archetypes
*/
public function test_get_compatible_role_archetypes() {
global $DB;
$allarchetypes = $DB->get_fieldset_select('role', 'DISTINCT archetype', 'archetype IS NOT NULL');
foreach ($allarchetypes as $allarchetype) {
$levels = context_helper::get_compatible_levels($allarchetype);
$this->assertNotContains(block::LEVEL, $levels, "$allarchetype is not expected to be compatible with context");
}
}
/**
* Tests covered method.
* @covers ::get_possible_parent_levels
*/
public function test_get_possible_parent_levels() {
$result = block::get_possible_parent_levels();
// All except itself.
$this->assertContains(system::LEVEL, $result);
$this->assertContains(user::LEVEL, $result);
$this->assertContains(coursecat::LEVEL, $result);
$this->assertContains(course::LEVEL, $result);
$this->assertContains(module::LEVEL, $result);
$this->assertNotContains(block::LEVEL, $result);
// Make sure plugin contexts are covered too.
$all = \core\context_helper::get_all_levels();
$this->assertCount(count($all) - 1, $result);
}
/**
* Tests covered method.
* @covers ::get_capabilities
*/
public function test_get_capabilities() {
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$capabilities = $context->get_capabilities();
$capabilities = convert_to_array($capabilities);
$capabilities = array_column($capabilities, 'name');
$this->assertNotContains('moodle/site:config', $capabilities);
$this->assertNotContains('moodle/course:view', $capabilities);
$this->assertNotContains('moodle/category:manage', $capabilities);
$this->assertNotContains('moodle/user:viewalldetails', $capabilities);
$this->assertNotContains('mod/page:view', $capabilities);
$this->assertNotContains('mod/url:view', $capabilities);
}
/**
* Tests covered method.
* @covers ::create_level_instances
*/
public function test_create_level_instances() {
global $DB;
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$DB->delete_records('context', ['id' => $context->id]);
context_helper::create_instances(block::LEVEL);
$record = $DB->get_record('context', ['contextlevel' => block::LEVEL, 'instanceid' => $block->id], '*', MUST_EXIST);
}
/**
* Tests covered method.
* @covers ::get_child_contexts
*/
public function test_get_child_contexts() {
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$children = $context->get_child_contexts();
$this->assertCount(0, $children);
}
/**
* Tests covered method.
* @covers ::get_cleanup_sql
*/
public function test_get_cleanup_sql() {
global $DB;
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$DB->delete_records('block_instances', ['id' => $block->id]);
context_helper::cleanup_instances();
$this->assertFalse($DB->record_exists('context', ['contextlevel' => block::LEVEL, 'instanceid' => $block->id]));
}
/**
* Tests covered method.
* @covers ::build_paths
*/
public function test_build_paths() {
global $DB;
$this->resetAfterTest();
$syscontext = system::instance();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$DB->set_field('context', 'depth', 1, ['id' => $context->id]);
$DB->set_field('context', 'path', '/0', ['id' => $context->id]);
context_helper::build_all_paths(true);
$record = $DB->get_record('context', ['id' => $context->id]);
$this->assertSame('2', $record->depth);
$this->assertSame('/' . $syscontext->id . '/' . $record->id, $record->path);
}
/**
* Tests covered method.
* @covers ::set_locked
*/
public function test_set_locked() {
global $DB;
$this->resetAfterTest();
$block = $this->getDataGenerator()->create_block('online_users');
$context = block::instance($block->id);
$context->set_locked(true);
$context = block::instance($block->id);
$this->assertTrue($context->locked);
$record = $DB->get_record('context', ['id' => $context->id]);
$this->assertSame('1', $record->locked);
$context->set_locked(false);
$context = block::instance($block->id);
$this->assertFalse($context->locked);
$record = $DB->get_record('context', ['id' => $context->id]);
$this->assertSame('0', $record->locked);
}
}
+291
View File
@@ -0,0 +1,291 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
use core\context, core\context_helper;
/**
* Unit tests for course context class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context\course
*/
class course_test extends \advanced_testcase {
/**
* Tests legacy class.
* @coversNothing
*/
public function test_legacy_classname() {
global $SITE;
$course = $SITE;
$context = \context_course::instance($course->id);
$this->assertInstanceOf(course::class, $context);
$this->assertInstanceOf(\context_course::class, $context);
}
/**
* Tests covered methods.
* @covers ::instance
* @covers \core\context::instance_by_id
*/
public function test_factory_methods() {
global $SITE;
$course = $SITE;
$context = course::instance($course->id);
$this->assertInstanceOf(course::class, $context);
$this->assertSame($course->id, $context->instanceid);
$context = context::instance_by_id($context->id);
$this->assertInstanceOf(course::class, $context);
$this->assertSame($course->id, $context->instanceid);
}
/**
* Tests covered method.
* @covers ::get_short_name
*/
public function test_get_short_name() {
$this->assertSame('course', course::get_short_name());
}
/**
* Tests levels.
* @coversNothing
*/
public function test_level() {
$this->assertSame(50, course::LEVEL);
$this->assertSame(CONTEXT_COURSE, course::LEVEL);
}
/**
* Tests covered method.
* @covers ::get_level_name
*/
public function test_get_level_name() {
$this->assertSame('Course', course::get_level_name());
}
/**
* Tests covered method.
* @covers ::get_context_name
*/
public function test_get_context_name() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Test course', 'shortname' => 'TST']);
$context = course::instance($course->id);
$this->assertSame('Course: Test course', $context->get_context_name());
$this->assertSame('Course: Test course', $context->get_context_name(true));
$this->assertSame('Test course', $context->get_context_name(false));
$this->assertSame('TST', $context->get_context_name(false, true));
$this->assertSame('Course: TST', $context->get_context_name(true, true, false));
}
/**
* Tests covered method.
* @covers ::get_url
*/
public function test_get_url() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$context = course::instance($course->id);
$expected = new \moodle_url('/course/view.php', ['id' => $course->id]);
$url = $context->get_url();
$this->assertInstanceOf(\moodle_url::class, $url);
$this->assertSame($expected->out(), $url->out());
}
/**
* Tests covered methods.
* @covers ::get_instance_table()
* @covers ::get_behat_reference_columns()
* @covers \core\context_helper::resolve_behat_reference
*/
public function test_resolve_behat_reference() {
$this->resetAfterTest();
$instance = $this->getDataGenerator()->create_course(['shortname' => 'xyz']);
$context = context\course::instance($instance->id);
$result = context_helper::resolve_behat_reference('Course', $instance->shortname);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('course', $instance->shortname);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('50', $instance->shortname);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('Course', 'dshjkdshjkhjsadjhdsa');
$this->assertNull($result);
$result = context_helper::resolve_behat_reference('Course', '');
$this->assertNull($result);
}
/**
* Tests covered method.
* @covers ::get_compatible_role_archetypes
*/
public function test_get_compatible_role_archetypes() {
global $DB;
$allarchetypes = $DB->get_fieldset_select('role', 'DISTINCT archetype', 'archetype IS NOT NULL');
foreach ($allarchetypes as $allarchetype) {
$levels = context_helper::get_compatible_levels($allarchetype);
if ($allarchetype === 'editingteacher' || $allarchetype === 'teacher'
|| $allarchetype === 'student' || $allarchetype === 'manager') {
$this->assertContains(course::LEVEL, $levels, "$allarchetype is expected to be compatible with context");
} else {
$this->assertNotContains(course::LEVEL, $levels, "$allarchetype is not expected to be compatible with context");
}
}
}
/**
* Tests covered method.
* @covers ::get_possible_parent_levels
*/
public function test_get_possible_parent_levels() {
$this->assertSame([coursecat::LEVEL], course::get_possible_parent_levels());
}
/**
* Tests covered method.
* @covers ::get_capabilities
*/
public function test_get_capabilities() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$context = course::instance($course->id);
$capabilities = $context->get_capabilities();
$capabilities = convert_to_array($capabilities);
$capabilities = array_column($capabilities, 'name');
$this->assertContains('moodle/course:view', $capabilities);
$this->assertContains('mod/page:view', $capabilities);
$this->assertContains('mod/url:view', $capabilities);
$this->assertNotContains('moodle/category:manage', $capabilities);
$this->assertNotContains('moodle/user:viewalldetails', $capabilities);
}
/**
* Tests covered method.
* @covers ::create_level_instances
*/
public function test_create_level_instances() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = course::instance($course->id);
$DB->delete_records('context', ['id' => $coursecontext->id]);
context_helper::create_instances(course::LEVEL);
$record = $DB->get_record('context', ['contextlevel' => course::LEVEL, 'instanceid' => $course->id], '*', MUST_EXIST);
}
/**
* Tests covered method.
* @covers ::get_child_contexts
*/
public function test_get_child_contexts() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = course::instance($course->id);
$children = $context->get_child_contexts();
$this->assertCount(1, $children);
$childcontext = reset($children);
$this->assertInstanceOf(module::class, $childcontext);
$this->assertEquals($page->cmid, $childcontext->instanceid);
}
/**
* Tests covered method.
* @covers ::get_cleanup_sql
*/
public function test_get_cleanup_sql() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = course::instance($course->id);
$DB->delete_records('course', ['id' => $course->id]);
context_helper::cleanup_instances();
$this->assertFalse($DB->record_exists('context', ['contextlevel' => course::LEVEL, 'instanceid' => $course->id]));
}
/**
* Tests covered method.
* @covers ::build_paths
*/
public function test_build_paths() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = course::instance($course->id);
$syscontext = system::instance();
$DB->set_field('context', 'depth', 1, ['id' => $coursecontext->id]);
$DB->set_field('context', 'path', '/0', ['id' => $coursecontext->id]);
context_helper::build_all_paths(true);
$record = $DB->get_record('context', ['id' => $coursecontext->id]);
$categorycontext = coursecat::instance($course->category);
$this->assertSame('3', $record->depth);
$this->assertSame('/' . $syscontext->id . '/' . $categorycontext->id . '/' . $record->id, $record->path);
}
/**
* Tests covered method.
* @covers ::set_locked
*/
public function test_set_locked() {
global $DB;
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course();
$context1 = course::instance($course1->id);
$context1->set_locked(true);
$context1 = course::instance($course1->id);
$this->assertTrue($context1->locked);
$record = $DB->get_record('context', ['id' => $context1->id]);
$this->assertSame('1', $record->locked);
$context1->set_locked(false);
$context1 = course::instance($course1->id);
$this->assertFalse($context1->locked);
$record = $DB->get_record('context', ['id' => $context1->id]);
$this->assertSame('0', $record->locked);
}
}
+289
View File
@@ -0,0 +1,289 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
use core\context, core\context_helper;
/**
* Unit tests for coursecat context class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context\coursecat
*/
class coursecat_test extends \advanced_testcase {
/**
* Tests legacy class name.
* @coversNothing
*/
public function test_legacy_classname() {
$category = \core_course_category::get_default();
$context = \context_coursecat::instance($category->id);
$this->assertInstanceOf(coursecat::class, $context);
$this->assertInstanceOf(\context_coursecat::class, $context);
}
/**
* Tests covered methods.
* @covers ::instance
* @covers \core\context::instance_by_id
*/
public function test_factory_methods() {
$category = \core_course_category::get_default();
$context = coursecat::instance($category->id);
$this->assertInstanceOf(coursecat::class, $context);
$this->assertSame($category->id, $context->instanceid);
$context = context::instance_by_id($context->id);
$this->assertInstanceOf(coursecat::class, $context);
$this->assertSame($category->id, $context->instanceid);
}
/**
* Tests covered method.
* @covers ::get_short_name
*/
public function test_get_short_name() {
$this->assertSame('coursecat', coursecat::get_short_name());
}
/**
* Tests levels.
* @coversNothing
*/
public function test_level() {
$this->assertSame(40, coursecat::LEVEL);
$this->assertSame(CONTEXT_COURSECAT, coursecat::LEVEL);
}
/**
* Tests covered method.
* @covers ::get_level_name
*/
public function test_get_level_name() {
$this->assertSame('Category', coursecat::get_level_name());
}
/**
* Tests covered method.
* @covers ::get_context_name
*/
public function test_get_context_name() {
$category = \core_course_category::get_default();
$context = coursecat::instance($category->id);
$this->assertSame('Category: Category 1', $context->get_context_name());
$this->assertSame('Category: Category 1', $context->get_context_name(true));
$this->assertSame('Category 1', $context->get_context_name(false));
$this->assertSame('Category 1', $context->get_context_name(false, true));
$this->assertSame('Category: Category 1', $context->get_context_name(true, true, false));
}
/**
* Tests covered method.
* @covers ::get_url
*/
public function test_get_url() {
$category = \core_course_category::get_default();
$context = coursecat::instance($category->id);
$expected = new \moodle_url('/course/index.php', ['categoryid' => $category->id]);
$url = $context->get_url();
$this->assertInstanceOf(\moodle_url::class, $url);
$this->assertSame($expected->out(), $url->out());
}
/**
* Tests covered methods.
* @covers ::get_instance_table()
* @covers ::get_behat_reference_columns()
* @covers \core\context_helper::resolve_behat_reference
*/
public function test_resolve_behat_reference() {
$this->resetAfterTest();
$instance = $this->getDataGenerator()->create_category(['idnumber' => 'xyz']);
$context = context\coursecat::instance($instance->id);
$result = context_helper::resolve_behat_reference('Category', $instance->idnumber);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('coursecat', $instance->idnumber);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('40', $instance->idnumber);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('Category', 'dshjkdshjkhjsadjhdsa');
$this->assertNull($result);
$result = context_helper::resolve_behat_reference('Category', '');
$this->assertNull($result);
}
/**
* Tests covered method.
* @covers ::get_compatible_role_archetypes
*/
public function test_get_compatible_role_archetypes() {
global $DB;
$allarchetypes = $DB->get_fieldset_select('role', 'DISTINCT archetype', 'archetype IS NOT NULL');
foreach ($allarchetypes as $allarchetype) {
$levels = context_helper::get_compatible_levels($allarchetype);
if ($allarchetype === 'manager' || $allarchetype === 'coursecreator') {
$this->assertContains(coursecat::LEVEL, $levels, "$allarchetype is expected to be compatible with context");
} else {
$this->assertNotContains(coursecat::LEVEL, $levels, "$allarchetype is not expected to be compatible with context");
}
}
}
/**
* Tests covered method.
* @covers ::get_possible_parent_levels
*/
public function test_get_possible_parent_levels() {
$this->assertSame([system::LEVEL, coursecat::LEVEL], coursecat::get_possible_parent_levels());
}
/**
* Tests covered method.
* @covers ::get_capabilities
*/
public function test_get_capabilities() {
$category = \core_course_category::get_default();
$context = coursecat::instance($category->id);
$capabilities = $context->get_capabilities();
$capabilities = convert_to_array($capabilities);
$capabilities = array_column($capabilities, 'name');
$this->assertContains('moodle/category:manage', $capabilities);
$this->assertContains('moodle/course:view', $capabilities);
$this->assertNotContains('moodle/user:viewalldetails', $capabilities);
}
/**
* Tests covered method.
* @covers ::create_level_instances
*/
public function test_create_level_instances() {
global $DB;
$this->resetAfterTest();
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = coursecat::instance($coursecat->id);
$DB->delete_records('context', ['id' => $coursecatcontext->id]);
context_helper::create_instances(coursecat::LEVEL);
$record = $DB->get_record('context', ['contextlevel' => coursecat::LEVEL, 'instanceid' => $coursecat->id], '*', MUST_EXIST);
}
/**
* Tests covered method.
* @covers ::get_child_contexts
*/
public function test_get_child_contexts() {
$this->resetAfterTest();
$category = \core_course_category::get_default();
$context = coursecat::instance($category->id);
$children = $context->get_child_contexts();
$this->assertCount(0, $children);
$course = $this->getDataGenerator()->create_course(['categoryid' => $category->id]);
// This may fail if some plugin auto-creates activities.
$children = $context->get_child_contexts();
$this->assertCount(1, $children);
}
/**
* Tests covered method.
* @covers ::get_cleanup_sql
*/
public function test_get_cleanup_sql() {
global $DB;
$this->resetAfterTest();
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = coursecat::instance($coursecat->id);
$DB->delete_records('course_categories', ['id' => $coursecat->id]);
context_helper::cleanup_instances();
$this->assertFalse($DB->record_exists('context', ['contextlevel' => coursecat::LEVEL, 'instanceid' => $coursecat->id]));
}
/**
* Tests covered method.
* @covers ::build_paths
*/
public function test_build_paths() {
global $DB;
$this->resetAfterTest();
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = coursecat::instance($coursecat->id);
$syscontext = system::instance();
$DB->set_field('context', 'depth', 1, ['id' => $coursecatcontext->id]);
$DB->set_field('context', 'path', '/0', ['id' => $coursecatcontext->id]);
context_helper::build_all_paths(true);
$record = $DB->get_record('context', ['id' => $coursecatcontext->id]);
$this->assertSame('2', $record->depth);
$this->assertSame('/' . $syscontext->id . '/' . $record->id, $record->path);
}
/**
* Tests covered method.
* @covers ::set_locked
*/
public function test_set_locked() {
global $DB;
$this->resetAfterTest();
$category1 = $this->getDataGenerator()->create_category();
$category2 = $this->getDataGenerator()->create_category(['parent' => $category1->id]);
$context1 = coursecat::instance($category1->id);
$context2 = coursecat::instance($category2->id);
$context1->set_locked(true);
$context1 = coursecat::instance($category1->id);
$context2 = coursecat::instance($category2->id);
$this->assertTrue($context1->locked);
$this->assertTrue($context2->locked);
$record = $DB->get_record('context', ['id' => $context1->id]);
$this->assertSame('1', $record->locked);
$record = $DB->get_record('context', ['id' => $context2->id]);
$this->assertSame('0', $record->locked);
$context1->set_locked(false);
$context1 = coursecat::instance($category1->id);
$context2 = coursecat::instance($category2->id);
$this->assertFalse($context1->locked);
$this->assertFalse($context2->locked);
$record = $DB->get_record('context', ['id' => $context1->id]);
$this->assertSame('0', $record->locked);
$record = $DB->get_record('context', ['id' => $context2->id]);
$this->assertSame('0', $record->locked);
}
}
+306
View File
@@ -0,0 +1,306 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
use core\context, core\context_helper;
/**
* Unit tests for module context class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context\module
*/
class module_test extends \advanced_testcase {
/**
* Tests legacy class.
* @coversNothing
*/
public function test_legacy_classname() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
$context = module::instance($page->cmid);
$this->assertInstanceOf(module::class, $context);
$this->assertInstanceOf(\context_module::class, $context);
}
/**
* Tests covered methods.
* @covers ::instance
* @covers \core\context::instance_by_id
*/
public function test_factory_methods() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
$context = module::instance($page->cmid);
$this->assertInstanceOf(module::class, $context);
$this->assertSame((string)$page->cmid, $context->instanceid);
$context = context::instance_by_id($context->id);
$this->assertInstanceOf(module::class, $context);
$this->assertSame((string)$page->cmid, $context->instanceid);
}
/**
* Tests covered method.
* @covers ::get_short_name
*/
public function test_get_short_name() {
$this->assertSame('module', module::get_short_name());
}
/**
* Tests context level.
* @coversNothing
*/
public function test_level() {
$this->assertSame(70, module::LEVEL);
$this->assertSame(CONTEXT_MODULE, module::LEVEL);
}
/**
* Tests covered method.
* @covers ::get_level_name
*/
public function test_get_level_name() {
$this->assertSame('Activity module', module::get_level_name());
}
/**
* Tests covered method.
* @covers ::get_context_name
*/
public function test_get_context_name() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = module::instance($page->cmid);
$this->assertSame('Page: Pokus', $context->get_context_name());
$this->assertSame('Page: Pokus', $context->get_context_name(true));
$this->assertSame('Pokus', $context->get_context_name(false));
$this->assertSame('Pokus', $context->get_context_name(false, true));
$this->assertSame('Page: Pokus', $context->get_context_name(true, true, false));
}
/**
* Tests covered method.
* @covers ::get_url
*/
public function test_get_url() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = module::instance($page->cmid);
$expected = new \moodle_url('/mod/page/view.php', ['id' => $page->cmid]);
$url = $context->get_url();
$this->assertInstanceOf(\moodle_url::class, $url);
$this->assertSame($expected->out(), $url->out());
}
/**
* Tests covered methods.
* @covers ::get_instance_table()
* @covers ::get_behat_reference_columns()
* @covers \core\context_helper::resolve_behat_reference
*/
public function test_resolve_behat_reference() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'idnumber' => 'xyz']);
$instance = $DB->get_record('course_modules', ['id' => $page->cmid], '*', MUST_EXIST);
$context = module::instance($instance->id);
$result = context_helper::resolve_behat_reference('Activity module', $instance->idnumber);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('module', $instance->idnumber);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('70', $instance->idnumber);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('Activity module', 'dshjkdshjkhjsadjhdsa');
$this->assertNull($result);
$result = context_helper::resolve_behat_reference('Activity module', '');
$this->assertNull($result);
}
/**
* Tests covered method.
* @covers ::get_compatible_role_archetypes
*/
public function test_get_compatible_role_archetypes() {
global $DB;
$allarchetypes = $DB->get_fieldset_select('role', 'DISTINCT archetype', 'archetype IS NOT NULL');
foreach ($allarchetypes as $allarchetype) {
$levels = context_helper::get_compatible_levels($allarchetype);
if ($allarchetype === 'editingteacher' || $allarchetype === 'teacher' || $allarchetype === 'student') {
$this->assertContains(module::LEVEL, $levels, "$allarchetype is expected to be compatible with context");
} else {
$this->assertNotContains(module::LEVEL, $levels, "$allarchetype is not expected to be compatible with context");
}
}
}
/**
* Tests covered method.
* @covers ::get_possible_parent_levels
*/
public function test_get_possible_parent_levels() {
$this->assertSame([course::LEVEL], module::get_possible_parent_levels());
}
/**
* Tests covered method.
* @covers ::get_capabilities
*/
public function test_get_capabilities() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = module::instance($page->cmid);
$capabilities = $context->get_capabilities();
$capabilities = convert_to_array($capabilities);
$capabilities = array_column($capabilities, 'name');
$this->assertContains('mod/page:view', $capabilities);
$this->assertNotContains('mod/url:view', $capabilities);
$this->assertNotContains('moodle/course:view', $capabilities);
$this->assertNotContains('moodle/category:manage', $capabilities);
$this->assertNotContains('moodle/user:viewalldetails', $capabilities);
}
/**
* Tests covered method.
* @covers ::create_level_instances
*/
public function test_create_level_instances() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = module::instance($page->cmid);
$DB->delete_records('context', ['id' => $context->id]);
context_helper::create_instances(module::LEVEL);
$record = $DB->get_record('context', ['contextlevel' => module::LEVEL, 'instanceid' => $page->cmid], '*', MUST_EXIST);
}
/**
* Tests covered method.
* @covers ::get_child_contexts
*/
public function test_get_child_contexts() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = module::instance($page->cmid);
$children = $context->get_child_contexts();
$this->assertCount(0, $children);
}
/**
* Tests covered method.
* @covers ::get_cleanup_sql
*/
public function test_get_cleanup_sql() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = module::instance($page->cmid);
$DB->delete_records('course_modules', ['id' => $page->cmid]);
$DB->delete_records('page', ['id' => $page->id]);
context_helper::cleanup_instances();
$this->assertFalse($DB->record_exists('context', ['contextlevel' => module::LEVEL, 'instanceid' => $page->cmid]));
}
/**
* Tests covered method.
* @covers ::build_paths
*/
public function test_build_paths() {
global $DB;
$this->resetAfterTest();
$syscontext = system::instance();
$course = $this->getDataGenerator()->create_course();
$coursecontext = course::instance($course->id);
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$pagecontext = module::instance($page->cmid);
$DB->set_field('context', 'depth', 1, ['id' => $pagecontext->id]);
$DB->set_field('context', 'path', '/0', ['id' => $pagecontext->id]);
context_helper::build_all_paths(true);
$record = $DB->get_record('context', ['id' => $pagecontext->id]);
$categorycontext = coursecat::instance($course->category);
$this->assertSame('4', $record->depth);
$this->assertSame('/' . $syscontext->id . '/' . $categorycontext->id . '/'
. $coursecontext->id . '/' . $record->id, $record->path);
}
/**
* Tests covered method.
* @covers ::set_locked
*/
public function test_set_locked() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => 'Pokus']);
$context = module::instance($page->cmid);
$context->set_locked(true);
$context = module::instance($page->cmid);
$this->assertTrue($context->locked);
$record = $DB->get_record('context', ['id' => $context->id]);
$this->assertSame('1', $record->locked);
$context->set_locked(false);
$context = module::instance($page->cmid);
$this->assertFalse($context->locked);
$record = $DB->get_record('context', ['id' => $context->id]);
$this->assertSame('0', $record->locked);
}
}
+233
View File
@@ -0,0 +1,233 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
use core\context, core\context_helper;
/**
* Unit tests for system context class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context\system
*/
class system_test extends \advanced_testcase {
/**
* Tests legacy class.
* @coversNothing
*/
public function test_legacy_classname() {
$context = \context_system::instance();
$this->assertInstanceOf(system::class, $context);
$this->assertInstanceOf(\context_system::class, $context);
}
/**
* Tests covered methods.
* @covers ::instance
* @covers \core\context::instance_by_id
*/
public function test_factory_methods() {
$context = system::instance();
$this->assertInstanceOf(system::class, $context);
$this->assertEquals(SYSCONTEXTID, $context->id);
$context = context::instance_by_id($context->id);
$this->assertInstanceOf(system::class, $context);
$this->assertEquals(SYSCONTEXTID, $context->id);
}
/**
* Tests covered method.
* @covers ::get_short_name
*/
public function test_get_short_name() {
$this->assertSame('system', system::get_short_name());
}
/**
* Tests context level.
* @coversNothing
*/
public function test_level() {
$this->assertSame(10, system::LEVEL);
$this->assertSame(CONTEXT_SYSTEM, system::LEVEL);
}
/**
* Tests covered method.
* @covers ::get_level_name
*/
public function test_get_level_name() {
$this->assertSame('System', system::get_level_name());
}
/**
* Tests covered method.
* @covers ::get_context_name
*/
public function test_get_context_name() {
$context = system::instance();
$this->assertSame('System', $context->get_context_name());
$this->assertSame('System', $context->get_context_name(true));
$this->assertSame('System', $context->get_context_name(false));
$this->assertSame('System', $context->get_context_name(false, true));
$this->assertSame('System', $context->get_context_name(true, true, false));
}
/**
* Tests covered method.
* @covers ::get_url
*/
public function test_get_url() {
$context = system::instance();
$expected = new \moodle_url('/');
$url = $context->get_url();
$this->assertInstanceOf(\moodle_url::class, $url);
$this->assertSame($expected->out(), $url->out());
}
/**
* Tests covered method.
* @covers \core\context_helper::resolve_behat_reference
*/
public function test_resolve_behat_reference() {
$syscontext = context\system::instance();
$result = context_helper::resolve_behat_reference('System', '');
$this->assertSame($syscontext->id, $result->id);
$result = context_helper::resolve_behat_reference('System', '44');
$this->assertSame($syscontext->id, $result->id);
$result = context_helper::resolve_behat_reference('system', '');
$this->assertSame($syscontext->id, $result->id);
$result = context_helper::resolve_behat_reference('10', '');
$this->assertSame($syscontext->id, $result->id);
}
/**
* Tests covered method.
* @covers ::get_compatible_role_archetypes
*/
public function test_get_compatible_role_archetypes() {
global $DB;
$allarchetypes = $DB->get_fieldset_select('role', 'DISTINCT archetype', 'archetype IS NOT NULL');
foreach ($allarchetypes as $allarchetype) {
$levels = context_helper::get_compatible_levels($allarchetype);
if ($allarchetype === 'manager' || $allarchetype === 'coursecreator') {
$this->assertContains(system::LEVEL, $levels, "$allarchetype is expected to be compatible with context");
} else {
$this->assertNotContains(system::LEVEL, $levels, "$allarchetype is not expected to be compatible with context");
}
}
}
/**
* Tests covered method.
* @covers ::get_possible_parent_levels
*/
public function test_get_possible_parent_levels() {
$this->assertSame([], system::get_possible_parent_levels());
}
/**
* Tests covered method.
* @covers ::get_capabilities
*/
public function test_get_capabilities() {
global $DB;
$context = system::instance();
$capabilities = $context->get_capabilities();
$expected = $DB->count_records('capabilities', []);
$this->assertCount($expected, $capabilities);
}
/**
* Tests covered method.
* @covers ::create_level_instances
*/
public function test_create_level_instances() {
context_helper::create_instances(system::LEVEL);
}
/**
* Tests covered method.
* @covers ::get_child_contexts
*/
public function test_get_child_contexts() {
global $DB;
$context = system::instance();
$children = $context->get_child_contexts();
$expected = $DB->count_records('context', []) - 1;
$this->assertCount($expected, $children);
$this->assertDebuggingCalled('Fetching of system context child courses is strongly '
. 'discouraged on production servers (it may eat all available memory)!');
}
/**
* Tests covered method.
* @covers ::get_cleanup_sql
*/
public function test_get_cleanup_sql() {
// Nothing to clean up actually.
context_helper::cleanup_instances();
}
/**
* Tests covered method.
* @covers ::build_paths
*/
public function test_build_paths() {
global $DB;
$this->resetAfterTest();
$DB->set_field('context', 'depth', 2, ['id' => SYSCONTEXTID]);
$DB->set_field('context', 'path', '/0', ['id' => SYSCONTEXTID]);
context_helper::build_all_paths(true);
$record = $DB->get_record('context', ['id' => SYSCONTEXTID]);
$this->assertSame('1', $record->depth);
$this->assertSame('/' . $record->id, $record->path);
}
/**
* Tests covered method.
* @covers ::set_locked
*/
public function test_set_locked() {
$context = system::instance();
$context->set_locked(false);
try {
$context->set_locked(true);
} catch (\moodle_exception $e) {
$this->assertInstanceOf(\coding_exception::class, $e);
$this->assertSame('Coding error detected, it must be fixed by a programmer: '
. 'It is not possible to lock the system context', $e->getMessage());
}
}
}
+268
View File
@@ -0,0 +1,268 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core\context;
use core\context, core\context_helper;
/**
* Unit tests for user context class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context\user
*/
class user_test extends \advanced_testcase {
/**
* Tests legacy class.
* @coversNothing
*/
public function test_legacy_classname() {
$admin = get_admin();
$context = \context_user::instance($admin->id);
$this->assertInstanceOf(user::class, $context);
$this->assertInstanceOf(\context_user::class, $context);
}
/**
* Tests covered methods.
* @covers ::instance
* @covers \core\context::instance_by_id
*/
public function test_factory_methods() {
$admin = get_admin();
$context = user::instance($admin->id);
$this->assertInstanceOf(user::class, $context);
$this->assertSame($admin->id, $context->instanceid);
$context = context::instance_by_id($context->id);
$this->assertInstanceOf(user::class, $context);
$this->assertSame($admin->id, $context->instanceid);
}
/**
* Tests covered method.
* @covers ::get_short_name
*/
public function test_get_short_name() {
$this->assertSame('user', user::get_short_name());
}
/**
* Tests context level.
* @coversNothing
*/
public function test_level() {
$this->assertSame(30, user::LEVEL);
$this->assertSame(CONTEXT_USER, user::LEVEL);
}
/**
* Tests covered method.
* @covers ::get_level_name
*/
public function test_get_level_name() {
$this->assertSame('User', user::get_level_name());
}
/**
* Tests covered method.
* @covers ::get_context_name
*/
public function test_get_context_name() {
$admin = get_admin();
$context = user::instance($admin->id);
$this->assertSame('User: Admin User', $context->get_context_name());
$this->assertSame('User: Admin User', $context->get_context_name(true));
$this->assertSame('Admin User', $context->get_context_name(false));
$this->assertSame('Admin User', $context->get_context_name(false, true));
$this->assertSame('User: Admin User', $context->get_context_name(true, true, false));
}
/**
* Tests covered method.
* @covers ::get_url
*/
public function test_get_url() {
$admin = get_admin();
$context = user::instance($admin->id);
$expected = new \moodle_url('/user/profile.php', ['id' => $admin->id]);
$url = $context->get_url();
$this->assertInstanceOf(\moodle_url::class, $url);
$this->assertSame($expected->out(), $url->out());
}
/**
* Tests covered methods.
* @covers ::get_instance_table()
* @covers ::get_behat_reference_columns()
* @covers \core\context_helper::resolve_behat_reference
*/
public function test_resolve_behat_reference() {
$this->resetAfterTest();
$instance = $this->getDataGenerator()->create_user();
$context = context\user::instance($instance->id);
$result = context_helper::resolve_behat_reference('User', $instance->username);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('user', $instance->username);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('30', $instance->username);
$this->assertSame($context->id, $result->id);
$result = context_helper::resolve_behat_reference('User', 'dshjkdshjkhjsadjhdsa');
$this->assertNull($result);
$result = context_helper::resolve_behat_reference('User', '');
$this->assertNull($result);
}
/**
* Tests covered method.
* @covers ::get_compatible_role_archetypes
*/
public function test_get_compatible_role_archetypes() {
global $DB;
$allarchetypes = $DB->get_fieldset_select('role', 'DISTINCT archetype', 'archetype IS NOT NULL');
foreach ($allarchetypes as $allarchetype) {
$levels = context_helper::get_compatible_levels($allarchetype);
$this->assertNotContains(user::LEVEL, $levels, "$allarchetype is not expected to be compatible with context");
}
}
/**
* Tests covered method.
* @covers ::get_possible_parent_levels
*/
public function test_get_possible_parent_levels() {
$this->assertSame([system::LEVEL], user::get_possible_parent_levels());
}
/**
* Tests covered method.
* @covers ::get_capabilities
*/
public function test_get_capabilities() {
$admin = get_admin();
$context = user::instance($admin->id);
$capabilities = $context->get_capabilities();
$capabilities = convert_to_array($capabilities);
$capabilities = array_column($capabilities, 'name');
$this->assertContains('moodle/user:viewalldetails', $capabilities);
$this->assertContains('moodle/grade:viewall', $capabilities);
$this->assertNotContains('moodle/course:view', $capabilities);
$this->assertNotContains('moodle/site:config', $capabilities);
}
/**
* Tests covered method.
* @covers ::create_level_instances
*/
public function test_create_level_instances() {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$usercontext = user::instance($user->id);
$DB->delete_records('context', ['id' => $usercontext->id]);
context_helper::create_instances(user::LEVEL);
$record = $DB->get_record('context', ['contextlevel' => user::LEVEL, 'instanceid' => $user->id], '*', MUST_EXIST);
}
/**
* Tests covered method.
* @covers ::get_child_contexts
*/
public function test_get_child_contexts() {
$admin = get_admin();
$context = user::instance($admin->id);
$children = $context->get_child_contexts();
$this->assertCount(0, $children);
}
/**
* Tests covered method.
* @covers ::get_cleanup_sql
*/
public function test_get_cleanup_sql() {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$usercontext = user::instance($user->id);
$DB->set_field('user', 'deleted', 1, ['id' => $user->id]);
context_helper::cleanup_instances();
$this->assertFalse($DB->record_exists('context', ['contextlevel' => user::LEVEL, 'instanceid' => $user->id]));
}
/**
* Tests covered method.
* @covers ::build_paths
*/
public function test_build_paths() {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$usercontext = user::instance($user->id);
$syscontext = system::instance();
$DB->set_field('context', 'depth', 1, ['id' => $usercontext->id]);
$DB->set_field('context', 'path', '/0', ['id' => $usercontext->id]);
context_helper::build_all_paths(true);
$record = $DB->get_record('context', ['id' => $usercontext->id]);
$this->assertSame('2', $record->depth);
$this->assertSame('/' . $syscontext->id . '/' . $record->id, $record->path);
}
/**
* Tests covered method.
* @covers ::set_locked
*/
public function test_set_locked() {
global $DB;
$this->resetAfterTest();
$admin = get_admin();
$context = user::instance($admin->id);
$this->assertFalse($context->locked);
$context->set_locked(true);
$this->assertTrue($context->locked);
$record = $DB->get_record('context', ['id' => $context->id]);
$this->assertSame('1', $record->locked);
$context->set_locked(false);
$this->assertFalse($context->locked);
$record = $DB->get_record('context', ['id' => $context->id]);
$this->assertSame('0', $record->locked);
}
}
+530
View File
@@ -0,0 +1,530 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core;
/**
* Unit tests for context helper class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context_helper
*/
class context_helper_test extends \advanced_testcase {
/**
* Tests covered method.
* @covers ::parse_external_level
*/
public function test_parse_external_level() {
$this->assertSame(context\system::class, context_helper::parse_external_level('system'));
$this->assertSame(context\system::class, context_helper::parse_external_level(CONTEXT_SYSTEM));
$this->assertSame(context\system::class, context_helper::parse_external_level((string)CONTEXT_SYSTEM));
$this->assertSame(context\user::class, context_helper::parse_external_level('user'));
$this->assertSame(context\user::class, context_helper::parse_external_level(CONTEXT_USER));
$this->assertSame(context\user::class, context_helper::parse_external_level((string)CONTEXT_USER));
$this->assertSame(context\coursecat::class, context_helper::parse_external_level('coursecat'));
$this->assertSame(context\coursecat::class, context_helper::parse_external_level(CONTEXT_COURSECAT));
$this->assertSame(context\coursecat::class, context_helper::parse_external_level((string)CONTEXT_COURSECAT));
$this->assertSame(context\course::class, context_helper::parse_external_level('course'));
$this->assertSame(context\course::class, context_helper::parse_external_level(CONTEXT_COURSE));
$this->assertSame(context\course::class, context_helper::parse_external_level((string)CONTEXT_COURSE));
$this->assertSame(context\module::class, context_helper::parse_external_level('module'));
$this->assertSame(context\module::class, context_helper::parse_external_level(CONTEXT_MODULE));
$this->assertSame(context\module::class, context_helper::parse_external_level((string)CONTEXT_MODULE));
$this->assertSame(context\block::class, context_helper::parse_external_level('block'));
$this->assertSame(context\block::class, context_helper::parse_external_level(CONTEXT_BLOCK));
$this->assertSame(context\block::class, context_helper::parse_external_level((string)CONTEXT_BLOCK));
$this->assertNull(context_helper::parse_external_level('core_system'));
$this->assertNull(context_helper::parse_external_level('xsystem'));
$this->assertNull(context_helper::parse_external_level(1));
$this->assertNull(context_helper::parse_external_level(''));
}
/**
* Tests covered method.
* @covers ::resolve_behat_reference
*/
public function test_resolve_behat_reference() {
$this->assertNull(context_helper::resolve_behat_reference('blahbla', 'blahbla'));
$this->assertNull(context_helper::resolve_behat_reference('', ''));
$this->assertNull(context_helper::resolve_behat_reference('0', ''));
$syscontext = context\system::instance();
$result = context_helper::resolve_behat_reference('System', '');
$this->assertSame($syscontext->id, $result->id);
$syscontext = context\system::instance();
$result = context_helper::resolve_behat_reference('10', '');
$this->assertSame($syscontext->id, $result->id);
// The rest is tested in each context class test.
}
/**
* Tests covered method.
* @covers ::get_class_for_level
*/
public function test_get_class_for_level() {
$this->assertSame(context\system::class, context_helper::get_class_for_level(CONTEXT_SYSTEM));
$this->assertSame(context\system::class, context_helper::get_class_for_level((string)CONTEXT_SYSTEM));
$this->assertSame(context\user::class, context_helper::get_class_for_level(CONTEXT_USER));
$this->assertSame(context\user::class, context_helper::get_class_for_level((string)CONTEXT_USER));
$this->assertSame(context\coursecat::class, context_helper::get_class_for_level(CONTEXT_COURSECAT));
$this->assertSame(context\coursecat::class, context_helper::get_class_for_level((string)CONTEXT_COURSECAT));
$this->assertSame(context\course::class, context_helper::get_class_for_level(CONTEXT_COURSE));
$this->assertSame(context\course::class, context_helper::get_class_for_level((string)CONTEXT_COURSE));
$this->assertSame(context\module::class, context_helper::get_class_for_level(CONTEXT_MODULE));
$this->assertSame(context\module::class, context_helper::get_class_for_level((string)CONTEXT_MODULE));
$this->assertSame(context\block::class, context_helper::get_class_for_level(CONTEXT_BLOCK));
$this->assertSame(context\block::class, context_helper::get_class_for_level((string)CONTEXT_BLOCK));
try {
context_helper::get_class_for_level(1);
$this->fail('Exception expected if level does not exist');
} catch (\moodle_exception $e) {
$this->assertInstanceOf(\coding_exception::class, $e);
$this->assertSame('Coding error detected, it must be fixed by a programmer: Invalid context level specified',
$e->getMessage());
}
}
/**
* Tests covered method.
* @covers ::get_all_levels
*/
public function test_get_all_levels() {
$levels = context_helper::get_all_levels();
$this->assertArrayHasKey(CONTEXT_SYSTEM, $levels);
$this->assertSame(context\system::class, $levels[CONTEXT_SYSTEM]);
$this->assertArrayHasKey(CONTEXT_USER, $levels);
$this->assertSame(context\user::class, $levels[CONTEXT_USER]);
$this->assertArrayHasKey(CONTEXT_COURSECAT, $levels);
$this->assertSame(context\coursecat::class, $levels[CONTEXT_COURSECAT]);
$this->assertArrayHasKey(CONTEXT_COURSE, $levels);
$this->assertSame(context\course::class, $levels[CONTEXT_COURSE]);
$this->assertArrayHasKey(CONTEXT_MODULE, $levels);
$this->assertSame(context\module::class, $levels[CONTEXT_MODULE]);
$this->assertArrayHasKey(CONTEXT_BLOCK, $levels);
$this->assertSame(context\block::class, $levels[CONTEXT_BLOCK]);
$sorted = $levels;
ksort($sorted, SORT_NUMERIC);
$block = $sorted[CONTEXT_BLOCK];
unset($sorted[CONTEXT_BLOCK]);
$sorted[CONTEXT_BLOCK] = $block;
$this->assertSame(array_keys($sorted), array_keys($levels));
// Make sure level is set properly.
foreach ($levels as $level => $classname) {
$this->assertEquals($level, $classname::LEVEL);
if ($level != CONTEXT_SYSTEM) {
$this->assertGreaterThan(CONTEXT_SYSTEM, $level);
}
}
}
/**
* Tests covered method.
* @covers ::get_child_levels
*/
public function test_get_child_levels() {
$alllevels = context_helper::get_all_levels();
$childlevels = context_helper::get_child_levels(CONTEXT_SYSTEM);
$this->assertSame(count($alllevels) - 1, count($childlevels));
$childlevels = context_helper::get_child_levels(CONTEXT_USER);
$this->assertNotContains(CONTEXT_SYSTEM, $childlevels);
$this->assertNotContains(CONTEXT_USER, $childlevels);
$this->assertNotContains(CONTEXT_COURSECAT, $childlevels);
$this->assertNotContains(CONTEXT_COURSE, $childlevels);
$this->assertNotContains(CONTEXT_MODULE, $childlevels);
$this->assertContains(CONTEXT_BLOCK, $childlevels);
$childlevels = context_helper::get_child_levels(CONTEXT_COURSECAT);
$this->assertNotContains(CONTEXT_SYSTEM, $childlevels);
$this->assertNotContains(CONTEXT_USER, $childlevels);
$this->assertContains(CONTEXT_COURSECAT, $childlevels);
$this->assertContains(CONTEXT_COURSE, $childlevels);
$this->assertContains(CONTEXT_MODULE, $childlevels);
$this->assertContains(CONTEXT_BLOCK, $childlevels);
$childlevels = context_helper::get_child_levels(CONTEXT_COURSE);
$this->assertNotContains(CONTEXT_SYSTEM, $childlevels);
$this->assertNotContains(CONTEXT_USER, $childlevels);
$this->assertNotContains(CONTEXT_COURSECAT, $childlevels);
$this->assertNotContains(CONTEXT_COURSE, $childlevels);
$this->assertContains(CONTEXT_MODULE, $childlevels);
$this->assertContains(CONTEXT_BLOCK, $childlevels);
$childlevels = context_helper::get_child_levels(CONTEXT_MODULE);
$this->assertNotContains(CONTEXT_SYSTEM, $childlevels);
$this->assertNotContains(CONTEXT_USER, $childlevels);
$this->assertNotContains(CONTEXT_COURSECAT, $childlevels);
$this->assertNotContains(CONTEXT_COURSE, $childlevels);
$this->assertNotContains(CONTEXT_MODULE, $childlevels);
$this->assertContains(CONTEXT_BLOCK, $childlevels);
$childlevels = context_helper::get_child_levels(CONTEXT_BLOCK);
$this->assertCount(0, $childlevels);
}
/**
* Tests covered method.
* @covers ::get_compatible_levels
*/
public function test_get_compatible_levels() {
$levels = context_helper::get_compatible_levels('manager');
$this->assertContains(CONTEXT_SYSTEM, $levels);
$this->assertNotContains(CONTEXT_USER, $levels);
$this->assertContains(CONTEXT_COURSECAT, $levels);
$this->assertContains(CONTEXT_COURSE, $levels);
$this->assertNotContains(CONTEXT_MODULE, $levels);
$this->assertNotContains(CONTEXT_BLOCK, $levels);
$levels = context_helper::get_compatible_levels('coursecreator');
$this->assertContains(CONTEXT_SYSTEM, $levels);
$this->assertNotContains(CONTEXT_USER, $levels);
$this->assertContains(CONTEXT_COURSECAT, $levels);
$this->assertNotContains(CONTEXT_COURSE, $levels);
$this->assertNotContains(CONTEXT_MODULE, $levels);
$this->assertNotContains(CONTEXT_BLOCK, $levels);
$levels = context_helper::get_compatible_levels('editingteacher');
$this->assertNotContains(CONTEXT_SYSTEM, $levels);
$this->assertNotContains(CONTEXT_USER, $levels);
$this->assertNotContains(CONTEXT_COURSECAT, $levels);
$this->assertContains(CONTEXT_COURSE, $levels);
$this->assertContains(CONTEXT_MODULE, $levels);
$this->assertNotContains(CONTEXT_BLOCK, $levels);
$levels = context_helper::get_compatible_levels('teacher');
$this->assertNotContains(CONTEXT_SYSTEM, $levels);
$this->assertNotContains(CONTEXT_USER, $levels);
$this->assertNotContains(CONTEXT_COURSECAT, $levels);
$this->assertContains(CONTEXT_COURSE, $levels);
$this->assertContains(CONTEXT_MODULE, $levels);
$this->assertNotContains(CONTEXT_BLOCK, $levels);
$levels = context_helper::get_compatible_levels('student');
$this->assertNotContains(CONTEXT_SYSTEM, $levels);
$this->assertNotContains(CONTEXT_USER, $levels);
$this->assertNotContains(CONTEXT_COURSECAT, $levels);
$this->assertContains(CONTEXT_COURSE, $levels);
$this->assertContains(CONTEXT_MODULE, $levels);
$this->assertNotContains(CONTEXT_BLOCK, $levels);
$levels = context_helper::get_compatible_levels('user');
$this->assertCount(0, $levels);
$levels = context_helper::get_compatible_levels('guest');
$this->assertCount(0, $levels);
$levels = context_helper::get_compatible_levels('frontpage');
$this->assertCount(0, $levels);
}
/**
* Tests covered method.
* @covers ::cleanup_instances
*/
public function test_cleanup_instances() {
global $DB;
$this->resetAfterTest();
$this->preventResetByRollback();
$prevcount = $DB->count_records('context', []);
context_helper::cleanup_instances();
$count = $DB->count_records('context', []);
$this->assertSame($prevcount, $count);
// Insert bogus records for each level and see if they get removed,
// more test should be in tests for each context level.
$alllevels = context_helper::get_all_levels();
foreach ($alllevels as $classname) {
if ($classname::LEVEL == CONTEXT_SYSTEM) {
continue;
}
$record = new \stdClass();
$record->contextlevel = $classname::LEVEL;
$record->instanceid = 9999999999;
$record->path = null;
$record->depth = '2';
$record->id = $DB->insert_record('context', $record);
$DB->set_field('context', 'path', SYSCONTEXTID . '/' . $record->id, ['id' => $record->id]);
}
context_helper::cleanup_instances();
$count = $DB->count_records('context', []);
$this->assertSame($prevcount, $count);
}
/**
* Tests covered method.
* @covers ::create_instances
*/
public function test_create_instances() {
global $DB;
$this->resetAfterTest();
$this->preventResetByRollback();
$user = $this->getDataGenerator()->create_user();
$usercontext = context\user::instance($user->id);
$category = $this->getDataGenerator()->create_category();
$categorycontext = context\coursecat::instance($category->id);
$course = $this->getDataGenerator()->create_course();
$coursecontext = context\course::instance($course->id);
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
$pagecontext = context\module::instance($page->cmid);
$prevcount = $DB->count_records('context', []);
$DB->delete_records('context', ['id' => $usercontext->id]);
$DB->delete_records('context', ['id' => $categorycontext->id]);
$DB->delete_records('context', ['id' => $coursecontext->id]);
$DB->delete_records('context', ['id' => $pagecontext->id]);
context_helper::create_instances();
$count = $DB->count_records('context', []);
$this->assertSame($prevcount, $count);
}
/**
* Tests covered method.
* @covers ::build_all_paths
*/
public function test_build_all_paths() {
$this->resetAfterTest();
$this->preventResetByRollback();
// Just make sure there are no fatal errors for now.
context_helper::build_all_paths(true);
context_helper::build_all_paths();
}
/**
* Tests covered method.
* @covers ::reset_caches
*/
public function test_reset_caches() {
$this->resetAfterTest();
$this->preventResetByRollback();
// Just make sure there are no fatal errors for now.
context_helper::reset_caches();
}
/**
* Tests covered method.
* @covers ::get_preload_record_columns
*/
public function test_get_preload_record_columns() {
$expected = array (
'testalias.id' => 'ctxid',
'testalias.path' => 'ctxpath',
'testalias.depth' => 'ctxdepth',
'testalias.contextlevel' => 'ctxlevel',
'testalias.instanceid' => 'ctxinstance',
'testalias.locked' => 'ctxlocked',
);
$result = context_helper::get_preload_record_columns('testalias');
$this->assertSame($expected, $result);
}
/**
* Tests covered method.
* @covers ::get_preload_record_columns_sql
*/
public function test_get_preload_record_columns_sql() {
global $DB;
$result = context_helper::get_preload_record_columns_sql('testalias');
$expected = 'testalias.id AS ctxid, testalias.path AS ctxpath, testalias.depth AS ctxdepth,'
.' testalias.contextlevel AS ctxlevel, testalias.instanceid AS ctxinstance, testalias.locked AS ctxlocked';
$this->assertSame($expected, $result);
$sql = "SELECT id, $result
FROM {context} testalias";
$DB->get_records_sql($sql, []);
}
/**
* Tests covered method.
* @covers ::preload_from_record
*/
public function test_preload_from_record() {
global $DB;
$select = context_helper::get_preload_record_columns_sql('testalias');
$sql = "SELECT id, $select
FROM {context} testalias";
$records = $DB->get_records_sql($sql, []);
foreach ($records as $record) {
$this->assertNull(context_helper::preload_from_record($record));
}
$this->assertDebuggingNotCalled();
$record = reset($records);
unset($record->ctxlevel);
$this->assertNull(context_helper::preload_from_record($record));
}
/**
* Tests covered method.
* @covers ::preload_contexts_by_id
*/
public function test_preload_contexts_by_id() {
global $DB;
$contextids = $DB->get_fieldset_sql("SELECT id FROM {context}", []);
context_helper::preload_contexts_by_id($contextids);
context_helper::preload_contexts_by_id($contextids);
context_helper::reset_caches();
context_helper::preload_contexts_by_id($contextids);
}
/**
* Tests covered method.
* @covers ::preload_course
*/
public function test_preload_course() {
global $SITE;
context_helper::preload_course($SITE->id);
}
/**
* Tests covered method.
* @covers ::delete_instance
*/
public function test_delete_instance() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$category = $this->getDataGenerator()->create_category();
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
// This is a bit silly test, it might start failing in the future
// because the instances are not deleted before deleting the contexts.
context_helper::delete_instance(CONTEXT_USER, $user->id);
context_helper::delete_instance(CONTEXT_COURSECAT, $category->id);
context_helper::delete_instance(CONTEXT_COURSE, $course->id);
context_helper::delete_instance(CONTEXT_MODULE, $page->cmid);
}
/**
* Tests covered method.
* @covers ::get_level_name
*/
public function test_get_level_name() {
$allevels = context_helper::get_all_levels();
foreach ($allevels as $level => $classname) {
$name = context_helper::get_level_name($level);
$this->assertIsString($name);
}
$this->assertSame('System', context_helper::get_level_name(CONTEXT_SYSTEM));
$this->assertSame('User', context_helper::get_level_name(CONTEXT_USER));
$this->assertSame('Category', context_helper::get_level_name(CONTEXT_COURSECAT));
$this->assertSame('Course', context_helper::get_level_name(CONTEXT_COURSE));
$this->assertSame('Activity module', context_helper::get_level_name(CONTEXT_MODULE));
$this->assertSame('Block', context_helper::get_level_name(CONTEXT_BLOCK));
}
/**
* Tests covered method.
* @covers ::get_navigation_filter_context
*/
public function test_get_navigation_filter_context() {
global $CFG;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$usercontext = context\user::instance($user->id);
$category = $this->getDataGenerator()->create_category();
$categorycontext = context\coursecat::instance($category->id);
$course = $this->getDataGenerator()->create_course();
$coursecontext = context\course::instance($course->id);
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
$pagecontext = context\module::instance($page->cmid);
$systemcontext = context\system::instance();
// Default is OFF.
$this->assertSame('0', $CFG->filternavigationwithsystemcontext);
// First test passed values are returned if disabled.
set_config('filternavigationwithsystemcontext', '0');
$this->assertNull(context_helper::get_navigation_filter_context(null));
$filtercontext = context_helper::get_navigation_filter_context($systemcontext);
$this->assertSame($systemcontext, $filtercontext);
$filtercontext = context_helper::get_navigation_filter_context($usercontext);
$this->assertSame($usercontext, $filtercontext);
$filtercontext = context_helper::get_navigation_filter_context($categorycontext);
$this->assertSame($categorycontext, $filtercontext);
$filtercontext = context_helper::get_navigation_filter_context($coursecontext);
$this->assertSame($coursecontext, $filtercontext);
$filtercontext = context_helper::get_navigation_filter_context($pagecontext);
$this->assertSame($pagecontext, $filtercontext);
// Now test that any input returns system context if enabled.
set_config('filternavigationwithsystemcontext', '1');
$filtercontext = context_helper::get_navigation_filter_context(null);
$this->assertSame($systemcontext->id, $filtercontext->id);
$filtercontext = context_helper::get_navigation_filter_context($systemcontext);
$this->assertSame($systemcontext->id, $filtercontext->id);
$filtercontext = context_helper::get_navigation_filter_context($usercontext);
$this->assertSame($systemcontext->id, $filtercontext->id);
$filtercontext = context_helper::get_navigation_filter_context($categorycontext);
$this->assertSame($systemcontext->id, $filtercontext->id);
$filtercontext = context_helper::get_navigation_filter_context($coursecontext);
$this->assertSame($systemcontext->id, $filtercontext->id);
$filtercontext = context_helper::get_navigation_filter_context($pagecontext);
$this->assertSame($systemcontext->id, $filtercontext->id);
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
namespace core;
/**
* Unit tests for base context class.
*
* NOTE: more tests are in lib/tests/accesslib_test.php
*
* @package core
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\context
*/
class context_test extends \advanced_testcase {
/**
* Tests legacy class name.
* @coversNothing
*/
public function test_legacy_classname() {
$this->assertSame('core\context', context::class);
$context = \context_system::instance();
$this->assertInstanceOf(context::class, $context);
$this->assertInstanceOf('context', $context);
}
/**
* Tests covered method.
* @covers ::instance_by_id
*/
public function test_factory_methods() {
$context = context::instance_by_id(SYSCONTEXTID);
$this->assertSame('core\\context\\system', get_class($context));
}
/**
* Tests covered methods.
* @covers ::__set
* @covers ::__unset
*/
public function test_propery_change_protection() {
$context = context\system::instance();
$context->contextlevel = -10;
$this->assertEquals($context::LEVEL, $context->contextlevel);
$this->assertDebuggingCalled('Can not change context instance properties!');
$context->instanceid = -10;
$this->assertEquals(0, $context->instanceid);
$this->assertDebuggingCalled('Can not change context instance properties!');
$context->id = -10;
$this->assertDebuggingCalled('Can not change context instance properties!');
$context->locked = -10;
$this->assertDebuggingCalled('Can not change context instance properties!');
unset($context->contextlevel);
$this->assertEquals($context::LEVEL, $context->contextlevel);
$this->assertDebuggingCalled('Can not unset context instance properties!');
}
/**
* Tests covered method.
* @covers ::__get
*/
public function test_incorrect_property() {
$context = context\system::instance();
$a = $context->whatever;
$this->assertDebuggingCalled('Invalid context property accessed! whatever');
}
/**
* Tests covered method.
* @covers ::getIterator
*/
public function test_iterator() {
$context = context\system::instance();
$array = iterator_to_array($context->getIterator());
$expected = [
'id' => $context->id,
'contextlevel' => $context->contextlevel,
'instanceid' => $context->instanceid,
'path' => $context->path,
'depth' => $context->depth,
'locked' => false,
];
$this->assertSame($expected, $array);
}
}
+30 -3
View File
@@ -84,10 +84,19 @@ class customcontext_test extends \advanced_testcase {
* Bogus custom context class for testing
*/
class context_bogus1 extends context {
/**
* Returns context shortname.
*
* @return string
*/
public static function get_short_name(): string {
return 'bogus1';
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
* @return \moodle_url
*/
public function get_url() {
global $ME;
@@ -108,10 +117,19 @@ class context_bogus1 extends context {
* Bogus custom context class for testing
*/
class context_bogus2 extends context {
/**
* Returns context shortname.
*
* @return string
*/
public static function get_short_name(): string {
return 'bogus2';
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
* @return \moodle_url
*/
public function get_url() {
global $ME;
@@ -132,10 +150,19 @@ class context_bogus2 extends context {
* Bogus custom context class for testing
*/
class context_bogus3 extends context {
/**
* Returns context shortname.
*
* @return string
*/
public static function get_short_name(): string {
return 'bogus3';
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
* @return \moodle_url
*/
public function get_url() {
global $ME;
+3
View File
@@ -3,6 +3,9 @@ information provided here is intended especially for developers.
=== 4.2 ===
* All context classes were moved to \core\context namespace while keeping full backwards compatibility.
Please note it is not necessary to update plugins unless they rely on exact class names returned from
get_class() or context_helper.
* Travis CI integration has been removed from core (MDLSITE-7135). Please, use GitHub Actions (GHA) instead. For
more information visit https://moodledev.io/general/development/tools/gha
* A new constant COMPLETION_COMPLETE_FAIL_HIDDEN is introduced to mark that user has received a failing grade