This commit is contained in:
Andrew Nicols
2023-04-04 11:22:49 +08:00
27 changed files with 5779 additions and 2860 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';
+14 -2796
View File
File diff suppressed because it is too large Load Diff
+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");
}
/**
File diff suppressed because it is too large Load Diff
+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;
use stdClass;
use coding_exception, moodle_url;
/**
* Block context class
*
* @package core_access
* @category access
* @copyright Petr Skoda
* @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 \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 != 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.
*
* @return string the human readable context level name.
*/
public static function get_level_name() {
return get_string('block');
}
/**
* Returns human readable context identifier.
*
* @param boolean $withprefix whether to prefix the name of the context with Block
* @param boolean $short does not apply to block context
* @param boolean $escape does not apply to block context
* @return string the human readable context name.
*/
public function get_context_name($withprefix = true, $short = false, $escape = true) {
global $DB, $CFG;
$name = '';
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) {
$name = get_string('block').': ';
}
$name .= $blockobject->title;
}
}
return $name;
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
*/
public function get_url() {
$parentcontexts = $this->get_parent_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.
*
* @param string $sort
* @return array
*/
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB;
$bi = $DB->get_record('block_instances', array('id' => $this->_instanceid));
$select = '(contextlevel = :level AND component = :component)';
$params = [
'level' => self::LEVEL,
'component' => 'block_' . $bi->blockname,
];
$extracaps = block_method_result($bi->blockname, 'get_extra_capabilities');
if ($extracaps) {
list($extra, $extraparams) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
$select .= " OR name $extra";
$params = array_merge($params, $extraparams);
}
return $DB->get_records_select('capabilities', $select, $params, $sort);
}
/**
* Is this context part of any course? If yes return course context.
*
* @param bool $strict true means throw exception if not found, false means return false if not found
* @return course context of the enclosing course, null if not found or exception
*/
public function get_course_context($strict = true) {
$parentcontext = $this->get_parent_context();
return $parentcontext->get_course_context($strict);
}
/**
* Returns block context instance.
*
* @param int $blockinstanceid id from {block_instances} table.
* @param int $strictness
* @return block|false context instance
*/
public static function instance($blockinstanceid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(self::LEVEL, $blockinstanceid)) {
return $context;
}
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(self::LEVEL, $bi->id, $parentcontext->path);
}
}
if ($record) {
$context = new block($record);
context::cache_add($context);
return $context;
}
return false;
}
/**
* Block do not have child contexts...
* @return array
*/
public function get_child_contexts() {
return array();
}
/**
* Create missing context instances at block context level
*/
protected static function create_level_instances() {
global $DB;
$sql = <<<EOF
INSERT INTO {context} (
contextlevel,
instanceid
) SELECT
:contextlevel,
bi.id as instanceid
FROM {block_instances} bi
WHERE NOT EXISTS (
SELECT 'x' FROM {context} cx WHERE bi.id = cx.instanceid AND cx.contextlevel = :existingcontextlevel
)
EOF;
$DB->execute($sql, [
'contextlevel' => self::LEVEL,
'existingcontextlevel' => self::LEVEL,
]);
}
/**
* Returns sql necessary for purging of stale context instances.
*
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
$sql = "
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {block_instances} bi ON c.instanceid = bi.id
WHERE bi.id IS NULL AND c.contextlevel = ".self::LEVEL."
";
return $sql;
}
/**
* Rebuild context paths and depths at block context level.
*
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
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)";
}
// 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 = " . self::LEVEL . ")
JOIN {context} pctx ON (pctx.id = bi.parentcontextid)
WHERE (pctx.path IS NOT NULL AND pctx.depth > 0)
$ctxemptyclause";
$trans = $DB->start_delegated_transaction();
$DB->delete_records('context_temp');
$DB->execute($sql);
context::merge_context_temp_table();
$DB->delete_records('context_temp');
$trans->allow_commit();
}
}
}
+298
View File
@@ -0,0 +1,298 @@
<?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;
use stdClass;
use coding_exception, moodle_url;
/**
* Course context class
*
* @package core_access
* @category access
* @copyright Petr Skoda
* @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 \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 != 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.
*
* @return string the human readable context level name.
*/
public static function get_level_name() {
return get_string('course');
}
/**
* Returns human readable context identifier.
*
* @param boolean $withprefix whether to prefix the name of the context with Course
* @param boolean $short whether to use the short name of the thing.
* @param bool $escape Whether the returned category name is to be HTML escaped or not.
* @return string the human readable context name.
*/
public function get_context_name($withprefix = true, $short = false, $escape = true) {
global $DB;
$name = '';
if ($this->_instanceid == SITEID) {
$name = get_string('frontpage', 'admin');
} else {
if ($course = $DB->get_record('course', array('id' => $this->_instanceid))) {
if ($withprefix) {
$name = get_string('course').': ';
}
if ($short) {
if (!$escape) {
$name .= format_string($course->shortname, true, array('context' => $this, 'escape' => false));
} else {
$name .= format_string($course->shortname, true, array('context' => $this));
}
} else {
if (!$escape) {
$name .= format_string(get_course_display_name_for_list($course), true, array('context' => $this,
'escape' => false));
} else {
$name .= format_string(get_course_display_name_for_list($course), true, array('context' => $this));
}
}
}
}
return $name;
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
*/
public function get_url() {
if ($this->_instanceid != SITEID) {
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.
*
* @param string $sort
* @return array
*/
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB;
$levels = \core\context_helper::get_child_levels(self::LEVEL);
$levels[] = self::LEVEL;
return $DB->get_records_list('capabilities', 'contextlevel', $levels, $sort);
}
/**
* Is this context part of any course? If yes return course context.
*
* @param bool $strict true means throw exception if not found, false means return false if not found
* @return course context of the enclosing course, null if not found or exception
*/
public function get_course_context($strict = true) {
return $this;
}
/**
* Returns course context instance.
*
* @param int $courseid id from {course} table
* @param int $strictness
* @return course|false context instance
*/
public static function instance($courseid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(self::LEVEL, $courseid)) {
return $context;
}
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(self::LEVEL, $course->id, $parentcontext->path);
} else {
$record = context::insert_context_record(self::LEVEL, $course->id, '/'.SYSCONTEXTID, 0);
}
}
}
if ($record) {
$context = new course($record);
context::cache_add($context);
return $context;
}
return false;
}
/**
* Create missing context instances at course context level
*/
protected static function create_level_instances() {
global $DB;
$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=".self::LEVEL.")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
/**
* Returns sql necessary for purging of stale context instances.
*
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
$sql = "
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {course} co ON c.instanceid = co.id
WHERE co.id IS NULL AND c.contextlevel = ".self::LEVEL."
";
return $sql;
}
/**
* Rebuild context paths and depths at course context level.
*
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
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)";
}
$base = '/'.SYSCONTEXTID;
// Standard frontpage.
$sql = "UPDATE {context}
SET depth = 2,
path = ".$DB->sql_concat("'$base/'", 'id')."
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.
$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 = ".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();
$DB->delete_records('context_temp');
$DB->execute($sql);
context::merge_context_temp_table();
$DB->delete_records('context_temp');
$trans->allow_commit();
}
}
}
+304
View File
@@ -0,0 +1,304 @@
<?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;
use stdClass;
use coding_exception, moodle_url;
/**
* Course category context class
*
* @package core_access
* @category access
* @copyright Petr Skoda
* @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 \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 != 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.
*
* @return string the human readable context level name.
*/
public static function get_level_name() {
return get_string('category');
}
/**
* Returns human readable context identifier.
*
* @param boolean $withprefix whether to prefix the name of the context with Category
* @param boolean $short does not apply to course categories
* @param boolean $escape Whether the returned name of the context is to be HTML escaped or not.
* @return string the human readable context name.
*/
public function get_context_name($withprefix = true, $short = false, $escape = true) {
global $DB;
$name = '';
if ($category = $DB->get_record('course_categories', array('id' => $this->_instanceid))) {
if ($withprefix) {
$name = get_string('category').': ';
}
if (!$escape) {
$name .= format_string($category->name, true, array('context' => $this, 'escape' => false));
} else {
$name .= format_string($category->name, true, array('context' => $this));
}
}
return $name;
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
*/
public function get_url() {
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.
*
* @param string $sort
* @return array
*/
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB;
$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.
*
* @param int $categoryid id from {course_categories} table
* @param int $strictness
* @return coursecat|false context instance
*/
public static function instance($categoryid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(self::LEVEL, $categoryid)) {
return $context;
}
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 = self::instance($category->parent);
$record = context::insert_context_record(self::LEVEL, $category->id, $parentcontext->path);
} else {
$record = context::insert_context_record(self::LEVEL, $category->id, '/'.SYSCONTEXTID, 0);
}
}
}
if ($record) {
$context = new coursecat($record);
context::cache_add($context);
return $context;
}
return false;
}
/**
* Returns immediate child contexts of category and all subcategories,
* children of subcategories and courses are not returned.
*
* @return array
*/
public function get_child_contexts() {
global $DB;
if (empty($this->_path) || empty($this->_depth)) {
debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
return array();
}
$sql = "SELECT ctx.*
FROM {context} ctx
WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)";
$params = array($this->_path.'/%', $this->depth + 1, self::LEVEL);
$records = $DB->get_records_sql($sql, $params);
$result = array();
foreach ($records as $record) {
$result[$record->id] = context::create_instance_from_record($record);
}
return $result;
}
/**
* Create missing context instances at course category context level
*/
protected static function create_level_instances() {
global $DB;
$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=".self::LEVEL.")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
/**
* Returns sql necessary for purging of stale context instances.
*
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
$sql = "
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {course_categories} cc ON c.instanceid = cc.id
WHERE cc.id IS NULL AND c.contextlevel = ".self::LEVEL."
";
return $sql;
}
/**
* Rebuild context paths and depths at course category context level.
*
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
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)";
}
$base = '/'.SYSCONTEXTID;
// Normal top level categories.
$sql = "UPDATE {context}
SET depth=2,
path=".$DB->sql_concat("'$base/'", 'id')."
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.
$maxdepth = $DB->get_field_sql("SELECT MAX(depth) FROM {course_categories}");
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 = ".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();
$DB->delete_records('context_temp');
$DB->execute($sql);
context::merge_context_temp_table();
$DB->delete_records('context_temp');
$trans->allow_commit();
}
}
}
}
+349
View File
@@ -0,0 +1,349 @@
<?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;
use stdClass;
use coding_exception, moodle_url;
/**
* Course module context class
*
* @package core_access
* @category access
* @copyright Petr Skoda
* @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 \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 != 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.
*
* @return string the human readable context level name.
*/
public static function get_level_name() {
return get_string('activitymodule');
}
/**
* Returns human readable context identifier.
*
* @param boolean $withprefix whether to prefix the name of the context with the
* module name, e.g. Forum, Glossary, etc.
* @param boolean $short does not apply to module context
* @param boolean $escape Whether the returned name of the context is to be HTML escaped or not.
* @return string the human readable context name.
*/
public function get_context_name($withprefix = true, $short = false, $escape = true) {
global $DB;
$name = '';
if ($cm = $DB->get_record_sql("SELECT cm.*, md.name AS modname
FROM {course_modules} cm
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) {
$name = get_string('modulename', $cm->modname).': ';
}
if (!$escape) {
$name .= format_string($mod->name, true, array('context' => $this, 'escape' => false));
} else {
$name .= format_string($mod->name, true, array('context' => $this));
}
}
}
return $name;
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
*/
public function get_url() {
global $DB;
if ($modname = $DB->get_field_sql("SELECT md.name AS modname
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('/');
}
/**
* 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.
*
* @param string $sort
* @return array
*/
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));
$subcaps = array();
$modulepath = "{$CFG->dirroot}/mod/{$module->name}";
if (file_exists("{$modulepath}/db/subplugins.json")) {
$subplugins = (array) json_decode(file_get_contents("{$modulepath}/db/subplugins.json"))->plugintypes;
} else if (file_exists("{$modulepath}/db/subplugins.php")) {
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.
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) {
$subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname)));
}
}
}
$modfile = "{$modulepath}/lib.php";
$extracaps = array();
if (file_exists($modfile)) {
include_once($modfile);
$modfunction = $module->name.'_get_extra_capabilities';
if (function_exists($modfunction)) {
$extracaps = $modfunction();
}
}
$extracaps = array_merge($subcaps, $extracaps);
$extra = '';
list($extra, $params) = $DB->get_in_or_equal(
$extracaps, SQL_PARAMS_NAMED, 'cap0', true, '');
if (!empty($extra)) {
$extra = "OR name $extra";
}
// Fetch the list of modules, and remove this one.
$components = \core_component::get_component_list();
$componentnames = $components['mod'];
unset($componentnames["mod_{$module->name}"]);
$componentnames = array_keys($componentnames);
// Exclude all other modules.
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 = [];
foreach ($componentnames as $mod) {
if ($subplugins = \core_component::get_subplugins($mod)) {
foreach (array_keys($subplugins) as $subplugintype) {
$paramname = "notlike{$i}";
$ignorecomponents[] = $DB->sql_like('component', ":{$paramname}", true, true, true);
$params[$paramname] = "{$subplugintype}_%";
$i++;
}
}
}
$notlikesql = "(" . implode(' AND ', $ignorecomponents) . ")";
$sql = "SELECT *
FROM {capabilities}
WHERE (contextlevel = ".self::LEVEL."
AND component {$notcompsql}
AND {$notlikesql})
$extra
ORDER BY $sort";
return $DB->get_records_sql($sql, $params);
}
/**
* Is this context part of any course? If yes return course context.
*
* @param bool $strict true means throw exception if not found, false means return false if not found
* @return course|false context of the enclosing course, null if not found or exception
*/
public function get_course_context($strict = true) {
return $this->get_parent_context();
}
/**
* Returns module context instance.
*
* @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
*/
public static function instance($cmid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(self::LEVEL, $cmid)) {
return $context;
}
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(self::LEVEL, $cm->id, $parentcontext->path);
}
}
if ($record) {
$context = new module($record);
context::cache_add($context);
return $context;
}
return false;
}
/**
* Create missing context instances at module context level
*/
protected static function create_level_instances() {
global $DB;
$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=" . self::LEVEL . ")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
/**
* Returns sql necessary for purging of stale context instances.
*
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
$sql = "
SELECT c.*
FROM {context} c
LEFT OUTER JOIN {course_modules} cm ON c.instanceid = cm.id
WHERE cm.id IS NULL AND c.contextlevel = " . self::LEVEL . "
";
return $sql;
}
/**
* Rebuild context paths and depths at module context level.
*
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
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)";
}
$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 = " . 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();
$DB->delete_records('context_temp');
$DB->execute($sql);
context::merge_context_temp_table();
$DB->delete_records('context_temp');
$trans->allow_commit();
}
}
}
+307
View File
@@ -0,0 +1,307 @@
<?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;
use stdClass;
use coding_exception, moodle_url;
/**
* System context class
*
* @package core_access
* @category access
* @copyright Petr Skoda
* @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 \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 != 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.
*
* @return string the human readable context level name.
*/
public static function get_level_name() {
return get_string('coresystem');
}
/**
* Returns human readable context identifier.
*
* @param boolean $withprefix does not apply to system context
* @param boolean $short does not apply to system context
* @param boolean $escape does not apply to system context
* @return string the human readable context name.
*/
public function get_context_name($withprefix = true, $short = false, $escape = true) {
return self::get_level_name();
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
*/
public function get_url() {
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.
*
* @param string $sort
* @return array
*/
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB;
return $DB->get_records('capabilities', [], $sort);
}
/**
* Create missing context instances at system context
*/
protected static function create_level_instances() {
// Nothing to do here, the system context is created automatically in installer.
self::instance(0);
}
/**
* Returns system context instance.
*
* @param int $instanceid should be 0
* @param int $strictness
* @param bool $cache
* @return system context instance
*/
public static function instance($instanceid = 0, $strictness = MUST_EXIST, $cache = true) {
global $DB;
if ($instanceid != 0) {
debugging('context_system::instance(): invalid $id parameter detected, should be 0');
}
// SYSCONTEXTID is cached in local cache to eliminate 1 query per page.
if (defined('SYSCONTEXTID') && $cache) {
if (!isset(context::$systemcontext)) {
$record = new stdClass();
$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;
}
try {
// We ignore the strictness completely because system context must exist except during install.
$record = $DB->get_record('context', array('contextlevel' => self::LEVEL), '*', MUST_EXIST);
} catch (\dml_exception $e) {
// Table or record does not exist.
if (!during_initial_install()) {
// Do not mess with system context after install, it simply must exist.
throw $e;
}
$record = null;
}
if (!$record) {
$record = new stdClass();
$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!!!
return null;
}
if (defined('SYSCONTEXTID')) {
// 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');
} else {
$record->id = $DB->insert_record('context', $record);
}
} catch (\dml_exception $e) {
// 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.
debugging('Invalid system context detected');
}
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);
}
if (empty($record->locked)) {
$record->locked = 0;
}
if (!defined('SYSCONTEXTID')) {
define('SYSCONTEXTID', $record->id);
}
context::$systemcontext = new system($record);
return context::$systemcontext;
}
/**
* Returns all site contexts except the system context, DO NOT call on production servers!!
*
* Contexts are not cached.
*
* @return array
*/
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)!');
// 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 > " . self::LEVEL;
$records = $DB->get_records_sql($sql);
$result = array();
foreach ($records as $record) {
$result[$record->id] = context::create_instance_from_record($record);
}
return $result;
}
/**
* Returns sql necessary for purging of stale context instances.
*
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
$sql = "
SELECT c.*
FROM {context} c
WHERE 1=2
";
return $sql;
}
/**
* Rebuild context paths and depths at system context level.
*
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
/* note: ignore $force here, we always do full test of system context */
// 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') && $record->id != SYSCONTEXTID) {
debugging('Invalid SYSCONTEXTID detected');
}
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);
}
}
/**
* Set whether this context has been locked or not.
*
* @param bool $locked
* @return $this
*/
public function set_locked(bool $locked) {
if ($locked) {
throw new \coding_exception('It is not possible to lock the system context');
}
return parent::set_locked($locked);
}
}
+242
View File
@@ -0,0 +1,242 @@
<?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;
use stdClass;
use coding_exception, moodle_url;
/**
* User context class
*
* @package core_access
* @category access
* @copyright Petr Skoda
* @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 \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 != 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.
*
* @return string the human readable context level name.
*/
public static function get_level_name() {
return get_string('user');
}
/**
* Returns human readable context identifier.
*
* @param boolean $withprefix whether to prefix the name of the context with User
* @param boolean $short does not apply to user context
* @param boolean $escape does not apply to user context
* @return string the human readable context name.
*/
public function get_context_name($withprefix = true, $short = false, $escape = true) {
global $DB;
$name = '';
if ($user = $DB->get_record('user', array('id' => $this->_instanceid, 'deleted' => 0))) {
if ($withprefix) {
$name = get_string('user').': ';
}
$name .= fullname($user);
}
return $name;
}
/**
* Returns the most relevant URL for this context.
*
* @return moodle_url
*/
public function get_url() {
global $COURSE;
if ($COURSE->id == SITEID) {
$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));
}
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.
*
* @param string $sort
* @return array
*/
public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
global $DB;
$extracaps = array('moodle/grade:viewall');
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' => self::LEVEL], $sort);
}
/**
* Returns user context instance.
*
* @param int $userid id from {user} table
* @param int $strictness
* @return user|false context instance
*/
public static function instance($userid, $strictness = MUST_EXIST) {
global $DB;
if ($context = context::cache_get(self::LEVEL, $userid)) {
return $context;
}
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(self::LEVEL, $user->id, '/'.SYSCONTEXTID, 0);
}
}
if ($record) {
$context = new user($record);
context::cache_add($context);
return $context;
}
return false;
}
/**
* Create missing context instances at user context level
*/
protected static function create_level_instances() {
global $DB;
$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=" . self::LEVEL . ")";
$contextdata = $DB->get_recordset_sql($sql);
foreach ($contextdata as $context) {
context::insert_context_record(self::LEVEL, $context->id, null);
}
$contextdata->close();
}
/**
* Returns sql necessary for purging of stale context instances.
*
* @return string cleanup SQL
*/
protected static function get_cleanup_sql() {
$sql = "
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 = " . self::LEVEL . "
";
return $sql;
}
/**
* Rebuild context paths and depths at user context level.
*
* @param bool $force
*/
protected static function build_paths($force) {
global $DB;
// First update normal users.
$path = $DB->sql_concat('?', 'id');
$pathstart = '/' . SYSCONTEXTID . '/';
$params = array($pathstart);
if ($force) {
$where = "depth <> 2 OR path IS NULL OR path <> ({$path})";
$params[] = $pathstart;
} else {
$where = "depth = 0 OR path IS NULL";
}
$sql = "UPDATE {context}
SET depth = 2,
path = {$path}
WHERE contextlevel = " . self::LEVEL . "
AND ($where)";
$DB->execute($sql, $params);
}
}
+484
View File
@@ -0,0 +1,484 @@
<?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;
use stdClass;
use coding_exception;
/**
* Context maintenance and helper methods.
*
* This is "extends context" is a bloody hack that tires to work around the deficiencies
* in the "protected" keyword in PHP, this helps us to hide all the internals of context
* level implementation from the rest of code, the code completion returns what developers need.
*
* Thank you Tim Hunt for helping me with this nasty trick.
*
* @package core_access
* @category access
* @copyright Petr Skoda
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
abstract class context_helper extends context {
/**
* @var array An array definitions of all context levels
*/
private static $alllevels;
/**
* Reset internal context levels array.
*/
public static function reset_levels() {
self::$alllevels = null;
}
/**
* Initialise context levels, call before using self::$alllevels.
*/
private static function init_levels():void {
global $CFG;
if (isset(self::$alllevels)) {
return;
}
self::$alllevels = array(
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)) {
return;
}
$levels = $CFG->custom_context_classes;
if (!is_array($levels)) {
$levels = @unserialize($levels);
}
if (!is_array($levels)) {
debugging('Invalid $CFG->custom_context_classes detected, value ignored.', DEBUG_DEVELOPER);
return;
}
// Unsupported custom levels, use with care!!!
foreach ($levels as $level => $classname) {
self::$alllevels[$level] = $classname;
}
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
*
* @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(int $contextlevel): string {
self::init_levels();
if (isset(self::$alllevels[$contextlevel])) {
return self::$alllevels[$contextlevel];
} else {
throw new coding_exception('Invalid context level specified');
}
}
/**
* Returns a list of all context levels
*
* @return array int=>string (level=>level class name)
*/
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...
*
* @return void
*/
public static function cleanup_instances() {
global $DB;
self::init_levels();
$sqls = array();
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.
$transaction = $DB->start_delegated_transaction();
$rs = $DB->get_recordset_sql($sql);
foreach ($rs as $record) {
$context = context::create_instance_from_record($record);
$context->delete();
}
$rs->close();
$transaction->allow_commit();
}
/**
* Create all context instances at the given level and above.
*
* @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 && $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();
if ($buildpaths) {
$classname::build_paths(false);
}
}
}
/**
* Rebuild paths and depths in all context levels.
*
* @param bool $force false means add missing only
* @return void
*/
public static function build_all_paths($force = false) {
self::init_levels();
foreach (self::$alllevels as $classname) {
$classname::build_paths($force);
}
// Reset static course cache - it might have incorrect cached data.
accesslib_clear_all_caches(true);
}
/**
* Resets the cache to remove all data.
*/
public static function reset_caches() {
context::reset_caches();
}
/**
* Returns all fields necessary for context preloading from user $rec.
*
* This helps with performance when dealing with hundreds of contexts.
*
* @param string $tablealias context table alias in the query
* @return array (table.column=>alias, ...)
*/
public static function get_preload_record_columns($tablealias) {
return [
"$tablealias.id" => "ctxid",
"$tablealias.path" => "ctxpath",
"$tablealias.depth" => "ctxdepth",
"$tablealias.contextlevel" => "ctxlevel",
"$tablealias.instanceid" => "ctxinstance",
"$tablealias.locked" => "ctxlocked",
];
}
/**
* Returns all fields necessary for context preloading from user $rec.
*
* This helps with performance when dealing with hundreds of contexts.
*
* @param string $tablealias context table alias in the query
* @return string
*/
public static function get_preload_record_columns_sql($tablealias) {
return "$tablealias.id AS ctxid, " .
"$tablealias.path AS ctxpath, " .
"$tablealias.depth AS ctxdepth, " .
"$tablealias.contextlevel AS ctxlevel, " .
"$tablealias.instanceid AS ctxinstance, " .
"$tablealias.locked AS ctxlocked";
}
/**
* Preloads context cache with information from db record and strips the cached info.
*
* The db request has to contain all columns from context_helper::get_preload_record_columns().
*
* @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): void {
context::preload_from_record($rec);
}
/**
* Preload a set of contexts using their contextid.
*
* @param array $contextids
*/
public static function preload_contexts_by_id(array $contextids): void {
global $DB;
// Determine which contexts are not already cached.
$tofetch = [];
foreach ($contextids as $contextid) {
if (!self::cache_get_by_id($contextid)) {
$tofetch[] = $contextid;
}
}
if (count($tofetch) > 1) {
// There are at least two to fetch.
// 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, '',
self::get_preload_record_columns_sql('{context}'));
foreach ($ctxs as $ctx) {
self::preload_from_record($ctx);
}
}
}
/**
* Preload all contexts instances from course.
*
* To be used if you expect multiple queries for course activities...
*
* @param int $courseid
*/
public static function preload_course($courseid) {
// Users can call this multiple times without doing any harm.
if (isset(context::$cache_preloaded[$courseid])) {
return;
}
$coursecontext = context\course::instance($courseid);
$coursecontext->get_child_contexts();
context::$cache_preloaded[$courseid] = true;
}
/**
* Delete context instance
*
* @param int $contextlevel
* @param int $instanceid
* @return void
*/
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))) {
$context = context::create_instance_from_record($record);
$context->delete();
}
}
/**
* Returns the name of specified context level
*
* @param int $contextlevel
* @return string name of the context level
*/
public static function get_level_name($contextlevel) {
$classname = self::get_class_for_level($contextlevel);
return $classname::get_level_name();
}
/**
* Gets the current context to be used for navigation tree filtering.
*
* @param context|null $context The current context to be checked against.
* @return context|null the context that navigation tree filtering should use.
*/
public static function get_navigation_filter_context(?context $context): ?context {
global $CFG;
if (!empty($CFG->filternavigationwithsystemcontext)) {
return context\system::instance();
} else {
return $context;
}
}
}
+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