mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 04:22:07 +02:00
MDL-55580 core: Process for deprecating a capability
* Add a $deprecatedcapabilities variable to deal with deprecated capabilities Change-Id: I14f44d331e8a1c4bd9abe9566c78d911c0205583 Co-authored-by: Mark Johnson <mark.johnson@catalyst-eu.net>
This commit is contained in:
parent
cc4fec275f
commit
bcc18e2439
@ -56,6 +56,7 @@ $string['cachedef_courseeditorstate'] = 'Session course state cache keys to dete
|
||||
$string['cachedef_course_image'] = 'Course images';
|
||||
$string['cachedef_course_user_dates'] = 'The user dates for courses set to relative dates mode';
|
||||
$string['cachedef_completion'] = 'Activity completion status';
|
||||
$string['cachedef_deprecatedcapabilities'] = 'System deprecated capabilities list';
|
||||
$string['cachedef_databasemeta'] = 'Database meta information';
|
||||
$string['cachedef_eventinvalidation'] = 'Event invalidation';
|
||||
$string['cachedef_externalbadges'] = 'External badges for particular user';
|
||||
|
@ -2525,6 +2525,20 @@ function is_inside_frontpage(context $context) {
|
||||
function get_capability_info($capabilityname) {
|
||||
$caps = get_all_capabilities();
|
||||
|
||||
// Check for deprecated capability.
|
||||
if ($deprecatedinfo = get_deprecated_capability_info($capabilityname)) {
|
||||
if (!empty($deprecatedinfo['replacement'])) {
|
||||
// Let's try again with this capability if it exists.
|
||||
if (isset($caps[$deprecatedinfo['replacement']])) {
|
||||
$capabilityname = $deprecatedinfo['replacement'];
|
||||
} else {
|
||||
debugging("Capability '{$capabilityname}' was supposed to be replaced with ".
|
||||
"'{$deprecatedinfo['replacement']}', which does not exist !");
|
||||
}
|
||||
}
|
||||
$fullmessage = $deprecatedinfo['fullmessage'];
|
||||
debugging($fullmessage, DEBUG_DEVELOPER);
|
||||
}
|
||||
if (!isset($caps[$capabilityname])) {
|
||||
return null;
|
||||
}
|
||||
@ -2532,6 +2546,57 @@ function get_capability_info($capabilityname) {
|
||||
return (object) $caps[$capabilityname];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns deprecation info for this particular capabilty (cached)
|
||||
*
|
||||
* Do not use this function except in the get_capability_info
|
||||
*
|
||||
* @param string $capabilityname
|
||||
* @return stdClass|null with deprecation message and potential replacement if not null
|
||||
*/
|
||||
function get_deprecated_capability_info($capabilityname) {
|
||||
// Here if we do like get_all_capabilities, we run into performance issues as the full array is unserialised each time.
|
||||
// We could have used an adhoc task but this also had performance issue. Last solution was to create a cache using
|
||||
// the official caches.php file. The performance issue shows in test_permission_evaluation.
|
||||
$cache = cache::make('core', 'deprecatedcapabilities');
|
||||
// Cache has not be initialised.
|
||||
if (!$cache->get('deprecated_capabilities_initialised')) {
|
||||
// Look for deprecated capabilities in each components.
|
||||
$allcaps = get_all_capabilities();
|
||||
$components = [];
|
||||
$alldeprecatedcaps = [];
|
||||
foreach ($allcaps as $cap) {
|
||||
if (!in_array($cap['component'], $components)) {
|
||||
$components[] = $cap['component'];
|
||||
$defpath = core_component::get_component_directory($cap['component']).'/db/access.php';
|
||||
if (file_exists($defpath)) {
|
||||
$deprecatedcapabilities = [];
|
||||
require($defpath);
|
||||
if (!empty($deprecatedcapabilities)) {
|
||||
foreach ($deprecatedcapabilities as $cname => $cdef) {
|
||||
$cache->set($cname, $cdef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$cache->set('deprecated_capabilities_initialised', true);
|
||||
}
|
||||
if (!$cache->has($capabilityname)) {
|
||||
return null;
|
||||
}
|
||||
$deprecatedinfo = $cache->get($capabilityname);
|
||||
$deprecatedinfo['fullmessage'] = "The capability '{$capabilityname}' is deprecated.";
|
||||
if (!empty($deprecatedinfo['message'])) {
|
||||
$deprecatedinfo['fullmessage'] .= $deprecatedinfo['message'];
|
||||
}
|
||||
if (!empty($deprecatedinfo['replacement'])) {
|
||||
$deprecatedinfo['fullmessage'] .=
|
||||
"It will be replaced by '{$deprecatedinfo['replacement']}'.";
|
||||
}
|
||||
return $deprecatedinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all capabilitiy records, preferably from MUC and not database.
|
||||
*
|
||||
@ -4135,6 +4200,11 @@ function get_user_capability_contexts(string $capability, bool $getcategories, $
|
||||
$userid = $USER->id;
|
||||
}
|
||||
|
||||
if (!$capinfo = get_capability_info($capability)) {
|
||||
debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
|
||||
return [false, false];
|
||||
}
|
||||
|
||||
if ($doanything && is_siteadmin($userid)) {
|
||||
// If the user is a site admin and $doanything is enabled then there is no need to restrict
|
||||
// the list of courses.
|
||||
@ -4143,7 +4213,7 @@ function get_user_capability_contexts(string $capability, bool $getcategories, $
|
||||
} else {
|
||||
// Gets SQL to limit contexts ('x' table) to those where the user has this capability.
|
||||
list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
|
||||
$userid, $capability);
|
||||
$userid, $capinfo->name);
|
||||
if (!$contextlimitsql) {
|
||||
// If the does not have this capability in any context, return false without querying.
|
||||
return [false, false];
|
||||
|
@ -149,6 +149,16 @@ $definitions = array(
|
||||
'ttl' => 3600, // Just in case.
|
||||
),
|
||||
|
||||
// Cache the deprecated capabilities list. See get_deprecated_capability_info in accesslib.
|
||||
'deprecatedcapabilities' => array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => false, // We need to hash the key.
|
||||
'simpledata' => true,
|
||||
'staticacceleration' => true,
|
||||
'staticaccelerationsize' => 1,
|
||||
'ttl' => 3600, // Just in case.
|
||||
),
|
||||
|
||||
// YUI Module cache.
|
||||
// This stores the YUI module metadata for Shifted YUI modules in Moodle.
|
||||
'yuimodules' => array(
|
||||
|
@ -1970,6 +1970,157 @@ class accesslib_test extends advanced_testcase {
|
||||
$this->assertFalse(has_all_capabilities($sca, $coursecontext, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to fake a plugin
|
||||
*
|
||||
* @param string $pluginname plugin name
|
||||
* @return void
|
||||
*/
|
||||
protected function setup_fake_plugin($pluginname) {
|
||||
global $CFG;
|
||||
// Here we have to hack the component loader so we can insert our fake plugin and test that
|
||||
// the access.php works.
|
||||
$mockedcomponent = new ReflectionClass(core_component::class);
|
||||
$mockedplugins = $mockedcomponent->getProperty('plugins');
|
||||
$mockedplugins->setAccessible(true);
|
||||
$plugins = $mockedplugins->getValue();
|
||||
$plugins['fake'] = [$pluginname => "{$CFG->dirroot}/lib/tests/fixtures/fakeplugins/$pluginname"];
|
||||
$mockedplugins->setValue($plugins);
|
||||
update_capabilities('fake_access');
|
||||
$this->resetDebugging(); // We have debugging messages here that we need to get rid of.
|
||||
// End of the component loader mock.
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_deprecated_capability_info()
|
||||
*
|
||||
* @covers ::get_deprecated_capability_info
|
||||
*/
|
||||
public function test_get_deprecated_capability_info() {
|
||||
$this->resetAfterTest();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course);
|
||||
$this->setup_fake_plugin('access');
|
||||
|
||||
// For now we have deprecated fake/access:fakecapability.
|
||||
$capinfo = get_deprecated_capability_info('fake/access:fakecapability');
|
||||
$this->assertNotEmpty($capinfo);
|
||||
$this->assertEquals("The capability 'fake/access:fakecapability' is"
|
||||
. " deprecated.This capability should not be used anymore.", $capinfo['fullmessage']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_deprecated_capability_info() through has_capability
|
||||
*
|
||||
* @covers ::get_deprecated_capability_info
|
||||
*/
|
||||
public function test_get_deprecated_capability_info_through_has_capability() {
|
||||
$this->resetAfterTest();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course);
|
||||
$this->setup_fake_plugin('access');
|
||||
|
||||
// For now we have deprecated fake/access:fakecapability.
|
||||
$hascap = has_capability('fake/access:fakecapability', $coursecontext, $user);
|
||||
$this->assertTrue($hascap);
|
||||
$this->assertDebuggingCalled("The capability 'fake/access:fakecapability' is deprecated."
|
||||
. "This capability should not be used anymore.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_deprecated_capability_info() through get_user_capability_contexts()
|
||||
*
|
||||
* @covers ::get_deprecated_capability_info
|
||||
*/
|
||||
public function test_get_deprecated_capability_info_through_get_user_capability_contexts() {
|
||||
$this->resetAfterTest();
|
||||
$category = $this->getDataGenerator()->create_category();
|
||||
$course = $this->getDataGenerator()->create_course(['categoryid' => $category->id]);
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course);
|
||||
$this->setup_fake_plugin('access');
|
||||
|
||||
// For now we have deprecated fake/access:fakecapability.
|
||||
list($categories, $courses) = get_user_capability_contexts('fake/access:fakecapability', false, $user->id);
|
||||
$this->assertNotEmpty($courses);
|
||||
$this->assertDebuggingCalled("The capability 'fake/access:fakecapability' is deprecated."
|
||||
. "This capability should not be used anymore.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_deprecated_capability_info with a capability that does not exist
|
||||
*
|
||||
* @param string $capability the capability name
|
||||
* @param array $debugmessages the debug messsages we expect
|
||||
* @param bool $expectedexisting does the capability exist
|
||||
* @covers ::get_deprecated_capability_info
|
||||
* @dataProvider deprecated_capabilities_use_cases
|
||||
*/
|
||||
public function test_get_deprecated_capability_specific_cases(string $capability, array $debugmessages,
|
||||
bool $expectedexisting) {
|
||||
$this->resetAfterTest();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course);
|
||||
$this->setup_fake_plugin('access');
|
||||
|
||||
// For now we have deprecated fake/access:fakecapability.
|
||||
$this->resetDebugging();
|
||||
$hascap = has_capability($capability, $coursecontext, $user);
|
||||
$this->assertEquals($expectedexisting, $hascap);
|
||||
$this->assertDebuggingCalledCount(count($debugmessages), $debugmessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific use case for deprecated capabilities
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function deprecated_capabilities_use_cases() {
|
||||
return [
|
||||
'capability missing' => [
|
||||
'fake/access:missingcapability',
|
||||
[
|
||||
"Capability \"fake/access:missingcapability\" was not found! This has to be fixed in code."
|
||||
],
|
||||
false
|
||||
],
|
||||
'replacement no info' => [
|
||||
'fake/access:replacementnoinfo',
|
||||
[
|
||||
"The capability 'fake/access:replacementnoinfo' is deprecated.",
|
||||
],
|
||||
true
|
||||
],
|
||||
'replacement missing' => [
|
||||
'fake/access:replacementmissing',
|
||||
[
|
||||
"The capability 'fake/access:replacementmissing' is deprecated.This capability should not be used anymore.",
|
||||
],
|
||||
true
|
||||
],
|
||||
'replacement with non existing cap' => [
|
||||
'fake/access:replacementwithwrongcapability',
|
||||
[
|
||||
"Capability 'fake/access:replacementwithwrongcapability' was supposed to be replaced with"
|
||||
. " 'fake/access:nonexistingcapabilty', which does not exist !",
|
||||
"The capability 'fake/access:replacementwithwrongcapability' is deprecated."
|
||||
. "This capability should not be used anymore.It will be replaced by 'fake/access:nonexistingcapabilty'."
|
||||
],
|
||||
true
|
||||
],
|
||||
'replacement with existing' => [
|
||||
'fake/access:replacementwithexisting', // Existing capability buf for a different role.
|
||||
[
|
||||
"The capability 'fake/access:replacementwithexisting' is deprecated.This capability should not be used anymore."
|
||||
. "It will be replaced by 'fake/access:existingcapability'.",
|
||||
],
|
||||
false // As the capability is applied to managers, we should not have this capability for this simple user.
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that assigning a fake cap does not return.
|
||||
*
|
||||
|
80
lib/tests/fixtures/fakeplugins/access/db/access.php
vendored
Normal file
80
lib/tests/fixtures/fakeplugins/access/db/access.php
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Fake component for testing
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2022 Laurent David <laurent.david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// Fake capabilities.
|
||||
$capabilities = [
|
||||
'fake/access:fakecapability' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_CONFIG | RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW
|
||||
]
|
||||
],
|
||||
'fake/access:existingcapability' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_CONFIG | RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'manager' => CAP_ALLOW
|
||||
]
|
||||
],
|
||||
'fake/access:replacementnoinfo' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_CONFIG | RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW
|
||||
]
|
||||
],
|
||||
'fake/access:replacementmissing' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_CONFIG | RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW
|
||||
]
|
||||
],
|
||||
'fake/access:replacementwithwrongcapability' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_CONFIG | RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
// Deprecated capabilities - MDL-55580.
|
||||
$deprecatedcapabilities = [
|
||||
'fake/access:fakecapability' => ['replacement' => '', 'message' => 'This capability should not be used anymore.'],
|
||||
'fake/access:replacementmissing' => ['message' => 'This capability should not be used anymore.'],
|
||||
'fake/access:replacementnoinfo' => [],
|
||||
'fake/access:replacementwithwrongcapability' => ['replacement' => 'fake/access:nonexistingcapabilty',
|
||||
'message' => 'This capability should not be used anymore.'],
|
||||
'fake/access:replacementwithexisting' => ['replacement' => 'fake/access:existingcapability',
|
||||
'message' => 'This capability should not be used anymore.'],
|
||||
];
|
29
lib/tests/fixtures/fakeplugins/access/version.php
vendored
Normal file
29
lib/tests/fixtures/fakeplugins/access/version.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Fake component for testing
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2022 Laurent David <laurent.david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2022050200;
|
||||
$plugin->requires = 2022041200;
|
||||
$plugin->component = 'fake_access';
|
@ -1,5 +1,14 @@
|
||||
This files describes API changes in core libraries and APIs,
|
||||
information provided here is intended especially for developers.
|
||||
=== 4.1 ===
|
||||
|
||||
* A process to deprecate capabilities by flagging them in the access.php by adding a $deprecatedcapabilities variable (an array).
|
||||
This array will list the deprecated capabilities and a possible replacement. Once we declare the capability as deprecated, a debugging
|
||||
message will be displayed (in DEBUG_DEVELOPPER mode only) when using the deprecated capability.
|
||||
Declaration is as follow:
|
||||
$deprecatedcapabilities = [
|
||||
'fake/access:fakecapability' => ['replacement' => '', 'message' => 'This capability should not be used anymore.']
|
||||
];
|
||||
|
||||
=== 4.1 ===
|
||||
* Final deprecation of the following functions behat_field_manager::get_node_type() and behat_field_manager::get_field()
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2022100700.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2022100700.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.1dev (Build: 20221007)'; // Human-friendly version name
|
||||
|
Loading…
x
Reference in New Issue
Block a user