mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
Merge branch 'MDL-53566-master' of git://github.com/andrewnicols/moodle
This commit is contained in:
commit
d27e49448f
96
admin/lock.php
Normal file
96
admin/lock.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file is used to display a categories sub categories, external pages, and settings.
|
||||
*
|
||||
* @package admin
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
require_once("{$CFG->libdir}/adminlib.php");
|
||||
|
||||
$contextid = required_param('id', PARAM_INT);
|
||||
$confirm = optional_param('confirm', null, PARAM_INT);
|
||||
$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
|
||||
|
||||
$PAGE->set_url('/admin/lock.php', ['id' => $contextid]);
|
||||
|
||||
list($context, $course, $cm) = get_context_info_array($contextid);
|
||||
|
||||
$parentcontext = $context->get_parent_context();
|
||||
if ($parentcontext && !empty($parentcontext->locked)) {
|
||||
// Can't make changes to a context whose parent is locked.
|
||||
throw new \coding_exception('Not sure how you got here');
|
||||
}
|
||||
|
||||
if ($course) {
|
||||
$isfrontpage = ($course->id == SITEID);
|
||||
} else {
|
||||
$isfrontpage = false;
|
||||
$course = $SITE;
|
||||
}
|
||||
|
||||
require_login($course, false, $cm);
|
||||
require_capability('moodle/site:managecontextlocks', $context);
|
||||
|
||||
$PAGE->set_pagelayout('admin');
|
||||
$PAGE->navigation->clear_cache();
|
||||
|
||||
$a = (object) [
|
||||
'contextname' => $context->get_context_name(),
|
||||
];
|
||||
|
||||
if (null !== $confirm && confirm_sesskey()) {
|
||||
$context->set_locked(!empty($confirm));
|
||||
|
||||
if ($context->locked) {
|
||||
$lockmessage = get_string('managecontextlocklocked', 'admin', $a);
|
||||
} else {
|
||||
$lockmessage = get_string('managecontextlockunlocked', 'admin', $a);
|
||||
}
|
||||
|
||||
if (empty($returnurl)) {
|
||||
$returnurl = $context->get_url();
|
||||
} else {
|
||||
$returnurl = new moodle_url($returnurl);
|
||||
}
|
||||
redirect($returnurl, $lockmessage);
|
||||
}
|
||||
|
||||
$heading = get_string('managecontextlock', 'admin');
|
||||
$PAGE->set_title($heading);
|
||||
$PAGE->set_heading($heading);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
if ($context->locked) {
|
||||
$confirmstring = get_string('confirmcontextunlock', 'admin', $a);
|
||||
$target = 0;
|
||||
} else {
|
||||
$confirmstring = get_string('confirmcontextlock', 'admin', $a);
|
||||
$target = 1;
|
||||
}
|
||||
|
||||
$confirmurl = new \moodle_url($PAGE->url, ['confirm' => $target]);
|
||||
if (!empty($returnurl)) {
|
||||
$confirmurl->param('returnurl', $returnurl);
|
||||
}
|
||||
|
||||
echo $OUTPUT->confirm($confirmstring, $confirmurl, $context->get_url());
|
||||
echo $OUTPUT->footer();
|
@ -16,6 +16,16 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
|
||||
$temp->add(new admin_setting_configexecutable('pathtosassc', new lang_string('pathtosassc', 'admin'), new lang_string('pathtosassc_help', 'admin'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('contextlocking', new lang_string('contextlocking', 'core_admin'),
|
||||
new lang_string('contextlocking_desc', 'core_admin'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox(
|
||||
'contextlockappliestoadmin',
|
||||
new lang_string('contextlockappliestoadmin', 'core_admin'),
|
||||
new lang_string('contextlockappliestoadmin_desc', 'core_admin'),
|
||||
1
|
||||
));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('forceclean', new lang_string('forceclean', 'core_admin'),
|
||||
new lang_string('forceclean_desc', 'core_admin'), 0));
|
||||
|
||||
|
@ -125,7 +125,15 @@ function tool_monitor_get_user_courses() {
|
||||
$options[0] = get_string('site');
|
||||
}
|
||||
|
||||
$fields = 'fullname, visible, ctxid, ctxpath, ctxdepth, ctxlevel, ctxinstance';
|
||||
$fieldlist = array_merge(
|
||||
[
|
||||
'fullname',
|
||||
'visible',
|
||||
],
|
||||
array_values(context_helper::get_preload_record_columns('c'))
|
||||
);
|
||||
|
||||
$fields = implode(', ', $fieldlist);
|
||||
if ($courses = get_user_capability_course('tool/monitor:subscribe', null, true, $fields, $orderby)) {
|
||||
foreach ($courses as $course) {
|
||||
context_helper::preload_from_record($course);
|
||||
|
@ -634,4 +634,4 @@ function cohort_get_list_of_themes() {
|
||||
}
|
||||
}
|
||||
return $themes;
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +238,7 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
|
||||
$record->visible = 1;
|
||||
$record->depth = 0;
|
||||
$record->path = '';
|
||||
$record->locked = 0;
|
||||
self::$coursecat0 = new self($record);
|
||||
}
|
||||
return self::$coursecat0;
|
||||
@ -2458,6 +2459,7 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
|
||||
$context = $this->get_context();
|
||||
$a['xi'] = $context->id;
|
||||
$a['xp'] = $context->path;
|
||||
$a['xl'] = $context->locked;
|
||||
return $a;
|
||||
}
|
||||
|
||||
@ -2486,6 +2488,7 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
|
||||
$record->ctxdepth = $record->depth + 1;
|
||||
$record->ctxlevel = CONTEXT_COURSECAT;
|
||||
$record->ctxinstance = $record->id;
|
||||
$record->ctxlocked = $a['xl'];
|
||||
return new self($record, true);
|
||||
}
|
||||
|
||||
|
@ -168,6 +168,8 @@ class helper {
|
||||
* @return array
|
||||
*/
|
||||
public static function get_category_listitem_actions(\core_course_category $category) {
|
||||
global $CFG;
|
||||
|
||||
$manageurl = new \moodle_url('/course/management.php', array('categoryid' => $category->id));
|
||||
$baseurl = new \moodle_url($manageurl, array('sesskey' => \sesskey()));
|
||||
$actions = array();
|
||||
@ -280,6 +282,28 @@ class helper {
|
||||
);
|
||||
}
|
||||
|
||||
// Context locking.
|
||||
if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $category->get_context())) {
|
||||
$parentcontext = $category->get_context()->get_parent_context();
|
||||
if (empty($parentcontext) || !$parentcontext->locked) {
|
||||
if ($category->get_context()->locked) {
|
||||
$lockicon = 'i/unlock';
|
||||
$lockstring = get_string('managecontextunlock', 'admin');
|
||||
} else {
|
||||
$lockicon = 'i/lock';
|
||||
$lockstring = get_string('managecontextlock', 'admin');
|
||||
}
|
||||
$actions['managecontextlock'] = [
|
||||
'url' => new \moodle_url('/admin/lock.php', [
|
||||
'id' => $category->get_context()->id,
|
||||
'returnurl' => $manageurl->out_as_local_url(false),
|
||||
]),
|
||||
'icon' => new \pix_icon($lockicon, $lockstring),
|
||||
'string' => $lockstring,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Cohorts.
|
||||
if ($category->can_review_cohorts()) {
|
||||
$actions['cohorts'] = array(
|
||||
|
@ -379,8 +379,14 @@ $string['configvisiblecourses'] = 'Display courses in hidden categories normally
|
||||
$string['configwarning'] = 'Be careful modifying these settings - strange values could cause problems.';
|
||||
$string['configyuicomboloading'] = 'This options enables combined file loading optimisation for YUI libraries. This setting should be enabled on production sites for performance reasons.';
|
||||
$string['confirmation'] = 'Confirmation';
|
||||
$string['confirmcontextlock'] = '{$a->contextname} is currently unlocked. Locking it will prevent any further changes. Are you sure you wish to continue?';
|
||||
$string['confirmcontextunlock'] = '{$a->contextname} is currently locked. Unlocking it will allow users to make changes. Are you sure you wish to continue?';
|
||||
$string['confirmdeletecomments'] = 'You are about to delete comments, are you sure?';
|
||||
$string['confirmed'] = 'Confirmed';
|
||||
$string['contextlocking'] = 'Context locking';
|
||||
$string['contextlocking_desc'] = 'This setting allows you to lock categories, courses, activites, and blocks within the site by removing all write-access to those locations.';
|
||||
$string['contextlockappliestoadmin'] = 'Context locking applies to administrators';
|
||||
$string['contextlockappliestoadmin_desc'] = 'This setting allows administrators to make changes in any context which is locked.';
|
||||
$string['cookiehttponly'] = 'Only http cookies';
|
||||
$string['cookiesecure'] = 'Secure cookies only';
|
||||
$string['country'] = 'Default country';
|
||||
@ -718,6 +724,10 @@ $string['maintenancemodeisscheduled'] = 'This site will be switched to maintenan
|
||||
$string['maintenancemodeisscheduledlong'] = 'This site will be switched to maintenance mode in {$a->hour} hours {$a->min} mins {$a->sec} secs';
|
||||
$string['maintfileopenerror'] = 'Error opening maintenance files!';
|
||||
$string['maintinprogress'] = 'Maintenance is in progress...';
|
||||
$string['managecontextlock'] = 'Lock this context';
|
||||
$string['managecontextlocklocked'] = '{$a->contextname}, and all of its children are now locked.';
|
||||
$string['managecontextlockunlocked'] = '{$a->contextname}, and all of its children are now unlocked.';
|
||||
$string['managecontextunlock'] = 'Unlock this context';
|
||||
$string['manageformats'] = 'Manage course formats';
|
||||
$string['manageformatsgotosettings'] = 'Default format can be changed in {$a}';
|
||||
$string['managelang'] = 'Manage';
|
||||
|
@ -411,6 +411,7 @@ $string['site:maintenanceaccess'] = 'Access site while in maintenance mode';
|
||||
$string['site:manageallmessaging'] = 'Add, remove, block and unblock contacts for any user';
|
||||
$string['site:manageblocks'] = 'Manage blocks on a page';
|
||||
$string['site:messageanyuser'] = 'Bypass user privacy preferences for messaging any user';
|
||||
$string['site:managecontextlocks'] = 'Manage locking of contexts';
|
||||
$string['site:mnetloginfromremote'] = 'Login from a remote application via MNet';
|
||||
$string['site:mnetlogintoremote'] = 'Roam to a remote application via MNet';
|
||||
$string['site:readallmessages'] = 'Read all messages on site';
|
||||
|
@ -478,6 +478,21 @@ function has_capability($capability, context $context, $user = null, $doanything
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether context locking is enabled.
|
||||
if (!empty($CFG->contextlocking)) {
|
||||
if ($capinfo->captype === 'write' && $context->locked) {
|
||||
// Context locking applies to any write capability in a locked context.
|
||||
// It does not apply to moodle/site:managecontextlocks - this is to allow context locking to be unlocked.
|
||||
if ($capinfo->name !== 'moodle/site:managecontextlocks') {
|
||||
// It applies to all users who are not site admins.
|
||||
// It also applies to site admins when contextlockappliestoadmin is set.
|
||||
if (!is_siteadmin($userid) || !empty($CFG->contextlockappliestoadmin)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// somehow make sure the user is not deleted and actually exists
|
||||
if ($userid != 0) {
|
||||
if ($userid == $USER->id and isset($USER->deleted)) {
|
||||
@ -4727,6 +4742,15 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
*/
|
||||
protected $_depth;
|
||||
|
||||
/**
|
||||
* Whether this context is locked or not.
|
||||
*
|
||||
* Can be accessed publicly through $context->locked.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_locked;
|
||||
|
||||
/**
|
||||
* @var array Context caching info
|
||||
*/
|
||||
@ -4862,22 +4886,40 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
* @param stdClass $rec
|
||||
* @return void (modifies $rec)
|
||||
*/
|
||||
protected static function preload_from_record(stdClass $rec) {
|
||||
if (empty($rec->ctxid) or empty($rec->ctxlevel) or !isset($rec->ctxinstance) or empty($rec->ctxpath) or empty($rec->ctxdepth)) {
|
||||
// $rec does not have enough data, passed here repeatedly or context does not exist yet
|
||||
return;
|
||||
}
|
||||
protected static function preload_from_record(stdClass $rec) {
|
||||
$notenoughdata = false;
|
||||
$notenoughdata = $notenoughdata || empty($rec->ctxid);
|
||||
$notenoughdata = $notenoughdata || empty($rec->ctxlevel);
|
||||
$notenoughdata = $notenoughdata || !isset($rec->ctxinstance);
|
||||
$notenoughdata = $notenoughdata || empty($rec->ctxpath);
|
||||
$notenoughdata = $notenoughdata || empty($rec->ctxdepth);
|
||||
$notenoughdata = $notenoughdata || !isset($rec->ctxlocked);
|
||||
if ($notenoughdata) {
|
||||
// The record does not have enough data, passed here repeatedly or context does not exist yet.
|
||||
if (isset($rec->ctxid) && !isset($rec->ctxlocked)) {
|
||||
debugging('Locked value missing. Code is possibly not usings the getter properly.', DEBUG_DEVELOPER);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// note: in PHP5 the objects are passed by reference, no need to return $rec
|
||||
$record = new stdClass();
|
||||
$record->id = $rec->ctxid; unset($rec->ctxid);
|
||||
$record->contextlevel = $rec->ctxlevel; unset($rec->ctxlevel);
|
||||
$record->instanceid = $rec->ctxinstance; unset($rec->ctxinstance);
|
||||
$record->path = $rec->ctxpath; unset($rec->ctxpath);
|
||||
$record->depth = $rec->ctxdepth; unset($rec->ctxdepth);
|
||||
$record = (object) [
|
||||
'id' => $rec->ctxid,
|
||||
'contextlevel' => $rec->ctxlevel,
|
||||
'instanceid' => $rec->ctxinstance,
|
||||
'path' => $rec->ctxpath,
|
||||
'depth' => $rec->ctxdepth,
|
||||
'locked' => $rec->ctxlocked,
|
||||
];
|
||||
|
||||
return context::create_instance_from_record($record);
|
||||
}
|
||||
unset($rec->ctxid);
|
||||
unset($rec->ctxlevel);
|
||||
unset($rec->ctxinstance);
|
||||
unset($rec->ctxpath);
|
||||
unset($rec->ctxdepth);
|
||||
unset($rec->ctxlocked);
|
||||
|
||||
return context::create_instance_from_record($record);
|
||||
}
|
||||
|
||||
|
||||
// ====== magic methods =======
|
||||
@ -4898,11 +4940,18 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
*/
|
||||
public function __get($name) {
|
||||
switch ($name) {
|
||||
case 'id': return $this->_id;
|
||||
case 'contextlevel': return $this->_contextlevel;
|
||||
case 'instanceid': return $this->_instanceid;
|
||||
case 'path': return $this->_path;
|
||||
case 'depth': return $this->_depth;
|
||||
case 'id':
|
||||
return $this->_id;
|
||||
case 'contextlevel':
|
||||
return $this->_contextlevel;
|
||||
case 'instanceid':
|
||||
return $this->_instanceid;
|
||||
case 'path':
|
||||
return $this->_path;
|
||||
case 'depth':
|
||||
return $this->_depth;
|
||||
case 'locked':
|
||||
return $this->is_locked();
|
||||
|
||||
default:
|
||||
debugging('Invalid context property accessed! '.$name);
|
||||
@ -4917,19 +4966,26 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
*/
|
||||
public function __isset($name) {
|
||||
switch ($name) {
|
||||
case 'id': return isset($this->_id);
|
||||
case 'contextlevel': return isset($this->_contextlevel);
|
||||
case 'instanceid': return isset($this->_instanceid);
|
||||
case 'path': return isset($this->_path);
|
||||
case 'depth': return isset($this->_depth);
|
||||
|
||||
default: return false;
|
||||
case 'id':
|
||||
return isset($this->_id);
|
||||
case 'contextlevel':
|
||||
return isset($this->_contextlevel);
|
||||
case 'instanceid':
|
||||
return isset($this->_instanceid);
|
||||
case 'path':
|
||||
return isset($this->_path);
|
||||
case 'depth':
|
||||
return isset($this->_depth);
|
||||
case 'locked':
|
||||
// Locked is always set.
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ALl properties are read only, sorry.
|
||||
* All properties are read only, sorry.
|
||||
* @param string $name
|
||||
*/
|
||||
public function __unset($name) {
|
||||
@ -4950,7 +5006,8 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
'contextlevel' => $this->contextlevel,
|
||||
'instanceid' => $this->instanceid,
|
||||
'path' => $this->path,
|
||||
'depth' => $this->depth
|
||||
'depth' => $this->depth,
|
||||
'locked' => $this->locked,
|
||||
);
|
||||
return new ArrayIterator($ret);
|
||||
}
|
||||
@ -4969,6 +5026,12 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
$this->_instanceid = $record->instanceid;
|
||||
$this->_path = $record->path;
|
||||
$this->_depth = $record->depth;
|
||||
|
||||
if (isset($record->locked)) {
|
||||
$this->_locked = $record->locked;
|
||||
} else if (!during_initial_install() && !moodle_needs_upgrading()) {
|
||||
debugging('Locked value missing. Code is possibly not usings the getter properly.', DEBUG_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5011,12 +5074,13 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
if ($dbfamily == 'mysql') {
|
||||
$updatesql = "UPDATE {context} ct, {context_temp} temp
|
||||
SET ct.path = temp.path,
|
||||
ct.depth = temp.depth
|
||||
ct.depth = temp.depth,
|
||||
ct.locked = temp.locked
|
||||
WHERE ct.id = temp.id";
|
||||
} else if ($dbfamily == 'oracle') {
|
||||
$updatesql = "UPDATE {context} ct
|
||||
SET (ct.path, ct.depth) =
|
||||
(SELECT temp.path, temp.depth
|
||||
SET (ct.path, ct.depth, ct.locked) =
|
||||
(SELECT temp.path, temp.depth, temp.locked
|
||||
FROM {context_temp} temp
|
||||
WHERE temp.id=ct.id)
|
||||
WHERE EXISTS (SELECT 'x'
|
||||
@ -5025,14 +5089,16 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
} else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') {
|
||||
$updatesql = "UPDATE {context}
|
||||
SET path = temp.path,
|
||||
depth = temp.depth
|
||||
depth = temp.depth,
|
||||
locked = temp.locked
|
||||
FROM {context_temp} temp
|
||||
WHERE temp.id={context}.id";
|
||||
} else {
|
||||
// sqlite and others
|
||||
$updatesql = "UPDATE {context}
|
||||
SET path = (SELECT path FROM {context_temp} WHERE id = {context}.id),
|
||||
depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id)
|
||||
depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id),
|
||||
locked = (SELECT locked FROM {context_temp} WHERE id = {context}.id)
|
||||
WHERE id IN (SELECT id FROM {context_temp})";
|
||||
}
|
||||
|
||||
@ -5118,6 +5184,27 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
$trans->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this context has been locked or not.
|
||||
*
|
||||
* @param bool $locked
|
||||
* @return $this
|
||||
*/
|
||||
public function set_locked(bool $locked) {
|
||||
global $DB;
|
||||
|
||||
if ($this->_locked == $locked) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->_locked = $locked;
|
||||
$DB->set_field('context', 'locked', (int) $locked, ['id' => $this->id]);
|
||||
$this->mark_dirty();
|
||||
self::reset_caches();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all context path info and optionally rebuild it.
|
||||
*
|
||||
@ -5239,6 +5326,7 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
$record->instanceid = $instanceid;
|
||||
$record->depth = 0;
|
||||
$record->path = null; //not known before insert
|
||||
$record->locked = 0;
|
||||
|
||||
$record->id = $DB->insert_record('context', $record);
|
||||
|
||||
@ -5266,6 +5354,23 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
throw new coding_exception('can not get name of abstract context');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current context is locked.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
if ($this->_locked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($parent = $this->get_parent_context()) {
|
||||
return $parent->is_locked();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most relevant URL for this context.
|
||||
*
|
||||
@ -5724,7 +5829,14 @@ class context_helper extends context {
|
||||
* @return array (table.column=>alias, ...)
|
||||
*/
|
||||
public static function get_preload_record_columns($tablealias) {
|
||||
return array("$tablealias.id"=>"ctxid", "$tablealias.path"=>"ctxpath", "$tablealias.depth"=>"ctxdepth", "$tablealias.contextlevel"=>"ctxlevel", "$tablealias.instanceid"=>"ctxinstance");
|
||||
return [
|
||||
"$tablealias.id" => "ctxid",
|
||||
"$tablealias.path" => "ctxpath",
|
||||
"$tablealias.depth" => "ctxdepth",
|
||||
"$tablealias.contextlevel" => "ctxlevel",
|
||||
"$tablealias.instanceid" => "ctxinstance",
|
||||
"$tablealias.locked" => "ctxlocked",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5737,7 +5849,12 @@ class context_helper extends context {
|
||||
* @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";
|
||||
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";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5920,12 +6037,12 @@ class context_system extends context {
|
||||
$record->instanceid = 0;
|
||||
$record->path = '/'.SYSCONTEXTID;
|
||||
$record->depth = 1;
|
||||
$record->locked = 0;
|
||||
context::$systemcontext = new context_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'=>CONTEXT_SYSTEM), '*', MUST_EXIST);
|
||||
@ -5943,7 +6060,8 @@ class context_system extends context {
|
||||
$record->contextlevel = CONTEXT_SYSTEM;
|
||||
$record->instanceid = 0;
|
||||
$record->depth = 1;
|
||||
$record->path = null; //not known before insert
|
||||
$record->path = null; // Not known before insert.
|
||||
$record->locked = 0;
|
||||
|
||||
try {
|
||||
if ($DB->count_records('context')) {
|
||||
@ -5976,6 +6094,10 @@ class context_system extends context {
|
||||
$DB->update_record('context', $record);
|
||||
}
|
||||
|
||||
if (empty($record->locked)) {
|
||||
$record->locked = 0;
|
||||
}
|
||||
|
||||
if (!defined('SYSCONTEXTID')) {
|
||||
define('SYSCONTEXTID', $record->id);
|
||||
}
|
||||
@ -6056,6 +6178,18 @@ class context_system extends context {
|
||||
$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) {
|
||||
throw new \coding_exception('It is not possible to lock the system context');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6458,8 +6592,8 @@ class context_coursecat extends context {
|
||||
// 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)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
|
||||
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
|
||||
FROM {context} ctx
|
||||
JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT." AND cc.depth = $n)
|
||||
JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
|
||||
@ -6682,8 +6816,8 @@ class context_course extends context {
|
||||
$DB->execute($sql);
|
||||
|
||||
// standard courses
|
||||
$sql = "INSERT INTO {context_temp} (id, path, depth)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
|
||||
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
|
||||
FROM {context} ctx
|
||||
JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSE." AND c.category <> 0)
|
||||
JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
|
||||
@ -6951,8 +7085,8 @@ class context_module extends context {
|
||||
$ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO {context_temp} (id, path, depth)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
|
||||
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
|
||||
FROM {context} ctx
|
||||
JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_MODULE.")
|
||||
JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = ".CONTEXT_COURSE.")
|
||||
@ -7172,8 +7306,8 @@ class context_block extends context {
|
||||
}
|
||||
|
||||
// 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)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
|
||||
$sql = "INSERT INTO {context_temp} (id, path, depth, locked)
|
||||
SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
|
||||
FROM {context} ctx
|
||||
JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_BLOCK.")
|
||||
JOIN {context} pctx ON (pctx.id = bi.parentcontextid)
|
||||
|
@ -1378,6 +1378,30 @@ class block_manager {
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $block->context)) {
|
||||
$parentcontext = $block->context->get_parent_context();
|
||||
if (empty($parentcontext) || empty($parentcontext->locked)) {
|
||||
if ($block->context->locked) {
|
||||
$lockicon = 'i/unlock';
|
||||
$lockstring = get_string('managecontextunlock', 'admin');
|
||||
} else {
|
||||
$lockicon = 'i/lock';
|
||||
$lockstring = get_string('managecontextlock', 'admin');
|
||||
}
|
||||
$controls[] = new action_menu_link_secondary(
|
||||
new moodle_url(
|
||||
'/admin/lock.php',
|
||||
[
|
||||
'id' => $block->context->id,
|
||||
]
|
||||
),
|
||||
new pix_icon($lockicon, $lockstring, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$lockstring,
|
||||
['class' => 'editing_lock']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $controls;
|
||||
}
|
||||
|
||||
|
@ -382,7 +382,7 @@ class core_user {
|
||||
protected static function get_enrolled_sql_on_courses_with_capability($capability) {
|
||||
// Get all courses where user have the capability.
|
||||
$courses = get_user_capability_course($capability, null, true,
|
||||
'ctxid, ctxpath, ctxdepth, ctxlevel, ctxinstance');
|
||||
implode(',', array_values(context_helper::get_preload_record_columns('ctx'))));
|
||||
if (!$courses) {
|
||||
return [null, null];
|
||||
}
|
||||
|
@ -2432,4 +2432,11 @@ $capabilities = array(
|
||||
)
|
||||
),
|
||||
|
||||
// Context locking/unlocking.
|
||||
'moodle/site:managecontextlocks' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => [
|
||||
],
|
||||
],
|
||||
);
|
||||
|
@ -1130,6 +1130,7 @@
|
||||
<FIELD NAME="instanceid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="path" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="depth" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="locked" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether this context and its children are locked"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@ -1145,6 +1146,7 @@
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="This id isn't autonumeric/sequence. It's the context->id"/>
|
||||
<FIELD NAME="path" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="depth" TYPE="int" LENGTH="2" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="locked" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether this context and its children are locked"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
|
@ -2658,10 +2658,6 @@ function xmldb_main_upgrade($oldversion) {
|
||||
$field = new xmldb_field('predictionsprocessor', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'timesplitting');
|
||||
|
||||
// Conditionally launch add field predictionsprocessor.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2018102900.00);
|
||||
}
|
||||
@ -2771,5 +2767,28 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2018110700.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2018111301.00) {
|
||||
// Define field locked to be added to context.
|
||||
$table = new xmldb_table('context');
|
||||
$field = new xmldb_field('locked', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'depth');
|
||||
|
||||
// Conditionally launch add field locked.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field locked to be added to context_temp.
|
||||
$table = new xmldb_table('context_temp');
|
||||
$field = new xmldb_field('locked', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'depth');
|
||||
|
||||
// Conditionally launch add field locked.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Note: This change also requires a bump in is_major_upgrade_required.
|
||||
upgrade_main_savepoint(true, 2018111301.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -530,17 +530,24 @@ class file_info_context_course extends file_info {
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'depth' => $this->context->depth + 1,
|
||||
'pathmask' => $this->context->path . '/%'];
|
||||
$sql1 = "SELECT ctx.id AS contextid, f.component, f.filearea, f.itemid, ctx.instanceid AS cmid, " .
|
||||
context_helper::get_preload_record_columns_sql('ctx') . "
|
||||
$ctxfieldsas = context_helper::get_preload_record_columns_sql('ctx');
|
||||
$ctxfields = implode(', ', array_keys(context_helper::get_preload_record_columns('ctx')));
|
||||
$sql1 = "SELECT
|
||||
ctx.id AS contextid,
|
||||
f.component,
|
||||
f.filearea,
|
||||
f.itemid,
|
||||
ctx.instanceid AS cmid,
|
||||
{$ctxfieldsas}
|
||||
FROM {files} f
|
||||
INNER JOIN {context} ctx ON ctx.id = f.contextid
|
||||
WHERE f.filename <> :emptyfilename
|
||||
AND ctx.contextlevel = :contextlevel
|
||||
AND ctx.depth = :depth
|
||||
AND " . $DB->sql_like('ctx.path', ':pathmask') . " ";
|
||||
$sql3 = ' GROUP BY ctx.id, f.component, f.filearea, f.itemid, ctx.instanceid,
|
||||
ctx.path, ctx.depth, ctx.contextlevel
|
||||
ORDER BY ctx.id, f.component, f.filearea, f.itemid';
|
||||
$sql3 = "
|
||||
GROUP BY ctx.id, f.component, f.filearea, f.itemid, {$ctxfields}
|
||||
ORDER BY ctx.id, f.component, f.filearea, f.itemid";
|
||||
list($sql2, $params2) = $this->build_search_files_sql($extensions);
|
||||
$areas = [];
|
||||
if ($rs = $DB->get_recordset_sql($sql1. $sql2 . $sql3, array_merge($params1, $params2))) {
|
||||
|
@ -834,6 +834,43 @@ class navigation_node implements renderable {
|
||||
}
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu item to handle locking and unlocking of a conext.
|
||||
*
|
||||
* @param \navigation_node $node Node to add
|
||||
* @param \context $context The context to be locked
|
||||
*/
|
||||
protected function add_context_locking_node(\navigation_node $node, \context $context) {
|
||||
global $CFG;
|
||||
// Manage context locking.
|
||||
if (!empty($CFG->contextlocking) && has_capability('moodle/site:managecontextlocks', $context)) {
|
||||
$parentcontext = $context->get_parent_context();
|
||||
if (empty($parentcontext) || !$parentcontext->locked) {
|
||||
if ($context->locked) {
|
||||
$lockicon = 'i/unlock';
|
||||
$lockstring = get_string('managecontextunlock', 'admin');
|
||||
} else {
|
||||
$lockicon = 'i/lock';
|
||||
$lockstring = get_string('managecontextlock', 'admin');
|
||||
}
|
||||
$node->add(
|
||||
$lockstring,
|
||||
new moodle_url(
|
||||
'/admin/lock.php',
|
||||
[
|
||||
'id' => $context->id,
|
||||
]
|
||||
),
|
||||
self::TYPE_SETTING,
|
||||
null,
|
||||
'contextlocking',
|
||||
new pix_icon($lockicon, '')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4371,6 +4408,9 @@ class settings_navigation extends navigation_node {
|
||||
null, 'gradebooksetup', new pix_icon('i/settings', ''));
|
||||
}
|
||||
|
||||
// Add the context locking node.
|
||||
$this->add_context_locking_node($coursenode, $coursecontext);
|
||||
|
||||
// Add outcome if permitted
|
||||
if ($adminoptions->outcomes) {
|
||||
$url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$course->id));
|
||||
@ -4507,6 +4547,10 @@ class settings_navigation extends navigation_node {
|
||||
$url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->page->cm->context->id));
|
||||
$modulenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, 'rolecheck');
|
||||
}
|
||||
|
||||
// Add the context locking node.
|
||||
$this->add_context_locking_node($modulenode, $this->page->cm->context);
|
||||
|
||||
// Manage filters
|
||||
if (has_capability('moodle/filter:manage', $this->page->cm->context) && count(filter_get_available_in_context($this->page->cm->context))>0) {
|
||||
$url = new moodle_url('/filter/manage.php', array('contextid'=>$this->page->cm->context->id));
|
||||
@ -5087,6 +5131,9 @@ class settings_navigation extends navigation_node {
|
||||
'checkpermissions', new pix_icon('i/checkpermissions', ''));
|
||||
}
|
||||
|
||||
// Add the context locking node.
|
||||
$this->add_context_locking_node($blocknode, $this->context);
|
||||
|
||||
return $blocknode;
|
||||
}
|
||||
|
||||
@ -5149,6 +5196,9 @@ class settings_navigation extends navigation_node {
|
||||
$categorynode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, 'checkpermissions', new pix_icon('i/checkpermissions', ''));
|
||||
}
|
||||
|
||||
// Add the context locking node.
|
||||
$this->add_context_locking_node($categorynode, $catcontext);
|
||||
|
||||
// Cohorts
|
||||
if (has_any_capability(array('moodle/cohort:view', 'moodle/cohort:manage'), $catcontext)) {
|
||||
$categorynode->add(get_string('cohorts', 'cohort'), new moodle_url('/cohort/index.php',
|
||||
|
@ -132,6 +132,9 @@ abstract class advanced_testcase extends base_testcase {
|
||||
self::resetAllData(true);
|
||||
}
|
||||
|
||||
// Reset context cache.
|
||||
context_helper::reset_caches();
|
||||
|
||||
// make sure test did not forget to close transaction
|
||||
if ($DB->is_transaction_started()) {
|
||||
self::resetAllData();
|
||||
|
@ -1395,7 +1395,7 @@ function disable_output_buffering() {
|
||||
*/
|
||||
function is_major_upgrade_required() {
|
||||
global $CFG;
|
||||
$lastmajordbchanges = 2017092900.00;
|
||||
$lastmajordbchanges = 2018111301.00;
|
||||
|
||||
$required = empty($CFG->version);
|
||||
$required = $required || (float)$CFG->version < $lastmajordbchanges;
|
||||
|
445
lib/tests/accesslib_has_capability_test.php
Normal file
445
lib/tests/accesslib_has_capability_test.php
Normal file
@ -0,0 +1,445 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* A collection of tests for accesslib::has_capability().
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Unit tests tests for has_capability.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class accesslib_has_capability_testcase extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Unit tests to check the operation of locked contexts.
|
||||
*
|
||||
* Note: We only check the admin user here.
|
||||
* If the admin cannot do it, then no-one can.
|
||||
*
|
||||
* @dataProvider locked_context_provider
|
||||
* @param string[] $lockedcontexts The list of contexts, by name, to mark as locked
|
||||
* @param string[] $blocked The list of contexts which will be 'blocked' by has_capability
|
||||
*/
|
||||
public function test_locked_contexts($lockedcontexts, $blocked) {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
set_config('contextlocking', 1);
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$otheruser = $generator->create_user();
|
||||
|
||||
// / (system)
|
||||
// /Cat1
|
||||
// /Cat1/Block
|
||||
// /Cat1/Course1
|
||||
// /Cat1/Course1/Block
|
||||
// /Cat1/Course2
|
||||
// /Cat1/Course2/Block
|
||||
// /Cat1/Cat1a
|
||||
// /Cat1/Cat1a/Block
|
||||
// /Cat1/Cat1a/Course1
|
||||
// /Cat1/Cat1a/Course1/Block
|
||||
// /Cat1/Cat1a/Course2
|
||||
// /Cat1/Cat1a/Course2/Block
|
||||
// /Cat1/Cat1b
|
||||
// /Cat1/Cat1b/Block
|
||||
// /Cat1/Cat1b/Course1
|
||||
// /Cat1/Cat1b/Course1/Block
|
||||
// /Cat1/Cat1b/Course2
|
||||
// /Cat1/Cat1b/Course2/Block
|
||||
// /Cat2
|
||||
// /Cat2/Block
|
||||
// /Cat2/Course1
|
||||
// /Cat2/Course1/Block
|
||||
// /Cat2/Course2
|
||||
// /Cat2/Course2/Block
|
||||
// /Cat2/Cat2a
|
||||
// /Cat2/Cat2a/Block
|
||||
// /Cat2/Cat2a/Course1
|
||||
// /Cat2/Cat2a/Course1/Block
|
||||
// /Cat2/Cat2a/Course2
|
||||
// /Cat2/Cat2a/Course2/Block
|
||||
// /Cat2/Cat2b
|
||||
// /Cat2/Cat2b/Block
|
||||
// /Cat2/Cat2b/Course1
|
||||
// /Cat2/Cat2b/Course1/Block
|
||||
// /Cat2/Cat2b/Course2
|
||||
// /Cat2/Cat2b/Course2/Block
|
||||
|
||||
$adminuser = \core_user::get_user_by_username('admin');
|
||||
$contexts = (object) [
|
||||
'system' => \context_system::instance(),
|
||||
'adminuser' => \context_user::instance($adminuser->id),
|
||||
];
|
||||
|
||||
$cat1 = $generator->create_category();
|
||||
$cat1a = $generator->create_category(['parent' => $cat1->id]);
|
||||
$cat1b = $generator->create_category(['parent' => $cat1->id]);
|
||||
|
||||
$contexts->cat1 = \context_coursecat::instance($cat1->id);
|
||||
$contexts->cat1a = \context_coursecat::instance($cat1a->id);
|
||||
$contexts->cat1b = \context_coursecat::instance($cat1b->id);
|
||||
|
||||
$cat1course1 = $generator->create_course(['category' => $cat1->id]);
|
||||
$cat1course2 = $generator->create_course(['category' => $cat1->id]);
|
||||
$cat1acourse1 = $generator->create_course(['category' => $cat1a->id]);
|
||||
$cat1acourse2 = $generator->create_course(['category' => $cat1a->id]);
|
||||
$cat1bcourse1 = $generator->create_course(['category' => $cat1b->id]);
|
||||
$cat1bcourse2 = $generator->create_course(['category' => $cat1b->id]);
|
||||
|
||||
$contexts->cat1course1 = \context_course::instance($cat1course1->id);
|
||||
$contexts->cat1acourse1 = \context_course::instance($cat1acourse1->id);
|
||||
$contexts->cat1bcourse1 = \context_course::instance($cat1bcourse1->id);
|
||||
$contexts->cat1course2 = \context_course::instance($cat1course2->id);
|
||||
$contexts->cat1acourse2 = \context_course::instance($cat1acourse2->id);
|
||||
$contexts->cat1bcourse2 = \context_course::instance($cat1bcourse2->id);
|
||||
|
||||
$cat1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1->id]);
|
||||
$cat1ablock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1a->id]);
|
||||
$cat1bblock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1b->id]);
|
||||
$cat1course1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1course1->id]);
|
||||
$cat1course2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1course2->id]);
|
||||
$cat1acourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1acourse1->id]);
|
||||
$cat1acourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1acourse2->id]);
|
||||
$cat1bcourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1bcourse1->id]);
|
||||
$cat1bcourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1bcourse2->id]);
|
||||
|
||||
$contexts->cat1block = \context_block::instance($cat1block->id);
|
||||
$contexts->cat1ablock = \context_block::instance($cat1ablock->id);
|
||||
$contexts->cat1bblock = \context_block::instance($cat1bblock->id);
|
||||
$contexts->cat1course1block = \context_block::instance($cat1course1block->id);
|
||||
$contexts->cat1course2block = \context_block::instance($cat1course2block->id);
|
||||
$contexts->cat1acourse1block = \context_block::instance($cat1acourse1block->id);
|
||||
$contexts->cat1acourse2block = \context_block::instance($cat1acourse2block->id);
|
||||
$contexts->cat1bcourse1block = \context_block::instance($cat1bcourse1block->id);
|
||||
$contexts->cat1bcourse2block = \context_block::instance($cat1bcourse2block->id);
|
||||
|
||||
$writecapability = 'moodle/block:edit';
|
||||
$readcapability = 'moodle/block:view';
|
||||
$managecapability = 'moodle/site:managecontextlocks';
|
||||
|
||||
$this->setAdminUser();
|
||||
$totest = (array) $contexts;
|
||||
foreach ($totest as $context) {
|
||||
$this->assertTrue(has_capability($writecapability, $context));
|
||||
$this->assertTrue(has_capability($readcapability, $context));
|
||||
$this->assertTrue(has_capability($managecapability, $context));
|
||||
}
|
||||
|
||||
// Lock the specified contexts.
|
||||
foreach ($lockedcontexts as $contextname => $value) {
|
||||
$contexts->$contextname->set_locked($value);
|
||||
}
|
||||
|
||||
// All read capabilities should remain.
|
||||
foreach ((array) $contexts as $context) {
|
||||
$this->assertTrue(has_capability($readcapability, $context));
|
||||
$this->assertTrue(has_capability($managecapability, $context));
|
||||
}
|
||||
|
||||
// Check writes.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
if (false !== array_search($contextname, $blocked)) {
|
||||
$this->assertFalse(has_capability($writecapability, $context));
|
||||
} else {
|
||||
$this->assertTrue(has_capability($writecapability, $context));
|
||||
}
|
||||
}
|
||||
|
||||
$this->setUser($otheruser);
|
||||
// Check writes.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
$this->assertFalse(has_capability($writecapability, $context));
|
||||
}
|
||||
|
||||
// Disable the contextlocking experimental feature.
|
||||
set_config('contextlocking', 0);
|
||||
|
||||
$this->setAdminUser();
|
||||
// All read capabilities should remain.
|
||||
foreach ((array) $contexts as $context) {
|
||||
$this->assertTrue(has_capability($readcapability, $context));
|
||||
$this->assertTrue(has_capability($managecapability, $context));
|
||||
}
|
||||
|
||||
// All write capabilities should now be present again.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
$this->assertTrue(has_capability($writecapability, $context));
|
||||
}
|
||||
|
||||
$this->setUser($otheruser);
|
||||
// Check writes.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
$this->assertFalse(has_capability($writecapability, $context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit tests to check the operation of locked contexts.
|
||||
*
|
||||
* Note: We only check the admin user here.
|
||||
* If the admin cannot do it, then no-one can.
|
||||
*
|
||||
* @dataProvider locked_context_provider
|
||||
* @param string[] $lockedcontexts The list of contexts, by name, to mark as locked
|
||||
* @param string[] $blocked The list of contexts which will be 'blocked' by has_capability
|
||||
*/
|
||||
public function test_locked_contexts_for_admin_with_config($lockedcontexts, $blocked) {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
set_config('contextlocking', 1);
|
||||
set_config('contextlockappliestoadmin', 0);
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$otheruser = $generator->create_user();
|
||||
|
||||
// / (system)
|
||||
// /Cat1
|
||||
// /Cat1/Block
|
||||
// /Cat1/Course1
|
||||
// /Cat1/Course1/Block
|
||||
// /Cat1/Course2
|
||||
// /Cat1/Course2/Block
|
||||
// /Cat1/Cat1a
|
||||
// /Cat1/Cat1a/Block
|
||||
// /Cat1/Cat1a/Course1
|
||||
// /Cat1/Cat1a/Course1/Block
|
||||
// /Cat1/Cat1a/Course2
|
||||
// /Cat1/Cat1a/Course2/Block
|
||||
// /Cat1/Cat1b
|
||||
// /Cat1/Cat1b/Block
|
||||
// /Cat1/Cat1b/Course1
|
||||
// /Cat1/Cat1b/Course1/Block
|
||||
// /Cat1/Cat1b/Course2
|
||||
// /Cat1/Cat1b/Course2/Block
|
||||
// /Cat2
|
||||
// /Cat2/Block
|
||||
// /Cat2/Course1
|
||||
// /Cat2/Course1/Block
|
||||
// /Cat2/Course2
|
||||
// /Cat2/Course2/Block
|
||||
// /Cat2/Cat2a
|
||||
// /Cat2/Cat2a/Block
|
||||
// /Cat2/Cat2a/Course1
|
||||
// /Cat2/Cat2a/Course1/Block
|
||||
// /Cat2/Cat2a/Course2
|
||||
// /Cat2/Cat2a/Course2/Block
|
||||
// /Cat2/Cat2b
|
||||
// /Cat2/Cat2b/Block
|
||||
// /Cat2/Cat2b/Course1
|
||||
// /Cat2/Cat2b/Course1/Block
|
||||
// /Cat2/Cat2b/Course2
|
||||
// /Cat2/Cat2b/Course2/Block
|
||||
|
||||
$adminuser = \core_user::get_user_by_username('admin');
|
||||
$contexts = (object) [
|
||||
'system' => \context_system::instance(),
|
||||
'adminuser' => \context_user::instance($adminuser->id),
|
||||
];
|
||||
|
||||
$cat1 = $generator->create_category();
|
||||
$cat1a = $generator->create_category(['parent' => $cat1->id]);
|
||||
$cat1b = $generator->create_category(['parent' => $cat1->id]);
|
||||
|
||||
$contexts->cat1 = \context_coursecat::instance($cat1->id);
|
||||
$contexts->cat1a = \context_coursecat::instance($cat1a->id);
|
||||
$contexts->cat1b = \context_coursecat::instance($cat1b->id);
|
||||
|
||||
$cat1course1 = $generator->create_course(['category' => $cat1->id]);
|
||||
$cat1course2 = $generator->create_course(['category' => $cat1->id]);
|
||||
$cat1acourse1 = $generator->create_course(['category' => $cat1a->id]);
|
||||
$cat1acourse2 = $generator->create_course(['category' => $cat1a->id]);
|
||||
$cat1bcourse1 = $generator->create_course(['category' => $cat1b->id]);
|
||||
$cat1bcourse2 = $generator->create_course(['category' => $cat1b->id]);
|
||||
|
||||
$contexts->cat1course1 = \context_course::instance($cat1course1->id);
|
||||
$contexts->cat1acourse1 = \context_course::instance($cat1acourse1->id);
|
||||
$contexts->cat1bcourse1 = \context_course::instance($cat1bcourse1->id);
|
||||
$contexts->cat1course2 = \context_course::instance($cat1course2->id);
|
||||
$contexts->cat1acourse2 = \context_course::instance($cat1acourse2->id);
|
||||
$contexts->cat1bcourse2 = \context_course::instance($cat1bcourse2->id);
|
||||
|
||||
$cat1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1->id]);
|
||||
$cat1ablock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1a->id]);
|
||||
$cat1bblock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1b->id]);
|
||||
$cat1course1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1course1->id]);
|
||||
$cat1course2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1course2->id]);
|
||||
$cat1acourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1acourse1->id]);
|
||||
$cat1acourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1acourse2->id]);
|
||||
$cat1bcourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1bcourse1->id]);
|
||||
$cat1bcourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1bcourse2->id]);
|
||||
|
||||
$contexts->cat1block = \context_block::instance($cat1block->id);
|
||||
$contexts->cat1ablock = \context_block::instance($cat1ablock->id);
|
||||
$contexts->cat1bblock = \context_block::instance($cat1bblock->id);
|
||||
$contexts->cat1course1block = \context_block::instance($cat1course1block->id);
|
||||
$contexts->cat1course2block = \context_block::instance($cat1course2block->id);
|
||||
$contexts->cat1acourse1block = \context_block::instance($cat1acourse1block->id);
|
||||
$contexts->cat1acourse2block = \context_block::instance($cat1acourse2block->id);
|
||||
$contexts->cat1bcourse1block = \context_block::instance($cat1bcourse1block->id);
|
||||
$contexts->cat1bcourse2block = \context_block::instance($cat1bcourse2block->id);
|
||||
|
||||
$writecapability = 'moodle/block:edit';
|
||||
$readcapability = 'moodle/block:view';
|
||||
$managecapability = 'moodle/site:managecontextlocks';
|
||||
|
||||
$this->setAdminUser();
|
||||
$totest = (array) $contexts;
|
||||
foreach ($totest as $context) {
|
||||
$this->assertTrue(has_capability($writecapability, $context));
|
||||
$this->assertTrue(has_capability($readcapability, $context));
|
||||
$this->assertTrue(has_capability($managecapability, $context));
|
||||
}
|
||||
|
||||
// Lock the specified contexts.
|
||||
foreach ($lockedcontexts as $contextname => $value) {
|
||||
$contexts->$contextname->set_locked($value);
|
||||
}
|
||||
|
||||
// All read capabilities should remain.
|
||||
foreach ((array) $contexts as $context) {
|
||||
$this->assertTrue(has_capability($readcapability, $context));
|
||||
$this->assertTrue(has_capability($managecapability, $context));
|
||||
}
|
||||
|
||||
// Check writes.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
$this->assertTrue(has_capability($writecapability, $context));
|
||||
}
|
||||
|
||||
$this->setUser($otheruser);
|
||||
// Check writes.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
$this->assertFalse(has_capability($writecapability, $context));
|
||||
}
|
||||
|
||||
// Disable the contextlocking experimental feature.
|
||||
set_config('contextlocking', 0);
|
||||
|
||||
$this->setAdminUser();
|
||||
// All read capabilities should remain.
|
||||
foreach ((array) $contexts as $context) {
|
||||
$this->assertTrue(has_capability($readcapability, $context));
|
||||
$this->assertTrue(has_capability($managecapability, $context));
|
||||
}
|
||||
|
||||
// All write capabilities should now be present again.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
$this->assertTrue(has_capability($writecapability, $context));
|
||||
}
|
||||
|
||||
$this->setUser($otheruser);
|
||||
// Check writes.
|
||||
foreach ((array) $contexts as $contextname => $context) {
|
||||
$this->assertFalse(has_capability($writecapability, $context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testing that has_capability() deals with locked contexts.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function locked_context_provider() {
|
||||
return [
|
||||
'All unlocked' => [
|
||||
'locked' => [
|
||||
],
|
||||
'blockedwrites' => [
|
||||
],
|
||||
],
|
||||
'User is locked (yes, this is weird)' => [
|
||||
'locked' => [
|
||||
'adminuser' => true,
|
||||
],
|
||||
'blockedwrites' => [
|
||||
'adminuser',
|
||||
],
|
||||
],
|
||||
'Cat1/Block locked' => [
|
||||
'locked' => [
|
||||
'cat1block' => true,
|
||||
],
|
||||
'blockedwrites' => [
|
||||
'cat1block',
|
||||
],
|
||||
],
|
||||
'Cat1' => [
|
||||
'locked' => [
|
||||
'cat1' => true,
|
||||
],
|
||||
'blockedwrites' => [
|
||||
'cat1',
|
||||
'cat1block',
|
||||
'cat1a',
|
||||
'cat1ablock',
|
||||
'cat1b',
|
||||
'cat1bblock',
|
||||
'cat1course1',
|
||||
'cat1course1block',
|
||||
'cat1course2',
|
||||
'cat1course2block',
|
||||
'cat1acourse1',
|
||||
'cat1acourse1block',
|
||||
'cat1acourse2',
|
||||
'cat1acourse2block',
|
||||
'cat1bcourse1',
|
||||
'cat1bcourse1block',
|
||||
'cat1bcourse2',
|
||||
'cat1bcourse2block',
|
||||
],
|
||||
],
|
||||
'Cat1 locked and a child explicitly unlocked' => [
|
||||
'locked' => [
|
||||
'cat1' => true,
|
||||
'cat1a' => false,
|
||||
],
|
||||
'blockedwrites' => [
|
||||
'cat1',
|
||||
'cat1block',
|
||||
'cat1a',
|
||||
'cat1ablock',
|
||||
'cat1b',
|
||||
'cat1bblock',
|
||||
'cat1course1',
|
||||
'cat1course1block',
|
||||
'cat1course2',
|
||||
'cat1course2block',
|
||||
'cat1acourse1',
|
||||
'cat1acourse1block',
|
||||
'cat1acourse2',
|
||||
'cat1acourse2block',
|
||||
'cat1bcourse1',
|
||||
'cat1bcourse1block',
|
||||
'cat1bcourse2',
|
||||
'cat1bcourse2block',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
205
lib/tests/behat/locking.feature
Normal file
205
lib/tests/behat/locking.feature
Normal file
@ -0,0 +1,205 @@
|
||||
@core
|
||||
Feature: Context locks apply to child contexts
|
||||
In order to preserve content
|
||||
As a manager
|
||||
I can disbale writes at different areas
|
||||
|
||||
Background:
|
||||
Given the following config values are set as admin:
|
||||
| contextlocking | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher | Ateacher | Teacher | teacher@example.com |
|
||||
| student1 | Astudent | Astudent | student1@example.com |
|
||||
And the following "categories" exist:
|
||||
| name | category | idnumber |
|
||||
| cata | 0 | cata |
|
||||
| cataa | cata | cataa |
|
||||
| catb | 0 | catb |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| courseaa1 | courseaa1 | cataa |
|
||||
| courseaa2 | courseaa2 | cataa |
|
||||
| courseb | courseb | catb |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber |
|
||||
| forum | faa1 | courseaa1 | faa1 |
|
||||
| forum | faa1b | courseaa1 | faa1b |
|
||||
| forum | faa2 | courseaa2 | faa2 |
|
||||
| forum | fb | courseb | fb |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher | courseaa1 | editingteacher |
|
||||
| student1 | courseaa1 | student |
|
||||
| teacher | courseaa2 | editingteacher |
|
||||
| student1 | courseaa2 | student |
|
||||
| teacher | courseb | editingteacher |
|
||||
| student1 | courseb | student |
|
||||
|
||||
Scenario: Lock course module module should lock just that module
|
||||
Given I log in as "admin"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
And I should see "Add a new discussion topic"
|
||||
When I follow "Lock this context"
|
||||
And I click on "Continue" "button"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
And I log out
|
||||
When I log in as "teacher"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
When I follow "faa1b"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseaa2" course homepage
|
||||
When I follow "faa2"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
Scenario: Lock course should lock all children
|
||||
Given I log in as "admin"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I should see "Turn editing on"
|
||||
When I follow "Lock this context"
|
||||
And I click on "Continue" "button"
|
||||
Then I should not see "Turn editing on"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
And I should see "Unlock this context"
|
||||
When I follow "faa1b"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
And I should not see "Unlock this context"
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
And I log out
|
||||
When I log in as "teacher"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
When I follow "faa1b"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa2" course homepage
|
||||
When I follow "faa2"
|
||||
Then I should see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
Scenario: Lock course category should lock all children
|
||||
Given I log in as "admin"
|
||||
And I go to the courses management page
|
||||
And I click on "managecontextlock" action for "cata" in management category listing
|
||||
And I click on "Continue" "button"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I should not see "Turn editing on"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
And I should not see "Unlock this context"
|
||||
When I follow "faa1b"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
And I should not see "Unlock this context"
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
And I should not see "Unlock this context"
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
And I log out
|
||||
When I log in as "teacher"
|
||||
And I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
And I follow "faa1"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa1b"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa2" course homepage
|
||||
Then I should not see "Turn editing on"
|
||||
When I follow "faa2"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
Then I should see "Turn editing on"
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
||||
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "courseaa1" course homepage
|
||||
And I follow "faa1"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa1" course homepage
|
||||
When I follow "faa1b"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseaa2" course homepage
|
||||
When I follow "faa2"
|
||||
Then I should not see "Add a new discussion topic"
|
||||
When I am on "courseb" course homepage
|
||||
When I follow "fb"
|
||||
Then I should see "Add a new discussion topic"
|
@ -2429,7 +2429,8 @@ class core_moodlelib_testcase extends advanced_testcase {
|
||||
'contextlevel' => $obj->contextlevel,
|
||||
'instanceid' => $obj->instanceid,
|
||||
'path' => $obj->path,
|
||||
'depth' => $obj->depth
|
||||
'depth' => $obj->depth,
|
||||
'locked' => $obj->locked,
|
||||
);
|
||||
$this->assertEquals(convert_to_array($obj), $ar);
|
||||
}
|
||||
|
@ -571,7 +571,7 @@ class core_session_manager_testcase extends advanced_testcase {
|
||||
\core\session\manager::loginas($user->id, context_system::instance());
|
||||
|
||||
$this->assertSame($user->id, $USER->id);
|
||||
$this->assertSame(context_system::instance(), $USER->loginascontext);
|
||||
$this->assertEquals(context_system::instance(), $USER->loginascontext);
|
||||
$this->assertSame($adminuser->id, $USER->realuser);
|
||||
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
|
||||
$this->assertSame($GLOBALS['USER'], $USER);
|
||||
|
@ -166,6 +166,7 @@ the groupid field.
|
||||
the function from other parts of Moodle.
|
||||
The return value: $settingsoutput is an array of setting names and the values that were set by the function.
|
||||
* Webservices no longer update the lastaccess time for a user in a course. Call core_course_view_course() manually if needed.
|
||||
* A new field has been added to the context table. Please ensure that any contxt preloading uses get_preload_record_columns_sql or get_preload_record_columns to fetch the list of columns.
|
||||
|
||||
=== 3.5 ===
|
||||
|
||||
|
@ -181,7 +181,7 @@ $capabilities = array(
|
||||
|
||||
'mod/forum:deleteownpost' => array(
|
||||
|
||||
'captype' => 'read',
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'student' => CAP_ALLOW,
|
||||
@ -193,7 +193,7 @@ $capabilities = array(
|
||||
|
||||
'mod/forum:deleteanypost' => array(
|
||||
|
||||
'captype' => 'read',
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'teacher' => CAP_ALLOW,
|
||||
@ -204,7 +204,7 @@ $capabilities = array(
|
||||
|
||||
'mod/forum:splitdiscussions' => array(
|
||||
|
||||
'captype' => 'read',
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'teacher' => CAP_ALLOW,
|
||||
@ -215,7 +215,7 @@ $capabilities = array(
|
||||
|
||||
'mod/forum:movediscussions' => array(
|
||||
|
||||
'captype' => 'read',
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'teacher' => CAP_ALLOW,
|
||||
@ -274,7 +274,7 @@ $capabilities = array(
|
||||
|
||||
'riskbitmask' => RISK_SPAM,
|
||||
|
||||
'captype' => 'read',
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'teacher' => CAP_ALLOW,
|
||||
|
@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2018051400; // The current module version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2018051401; // The current module version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version
|
||||
$plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics)
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2018111300.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2018111301.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user