Custom context levels database
For adding custom context levels, rather than trying to assign level numbers and hoping to not have collisions, I propose that we add a database table to map a context level name to the context level number. If we allow various plugins to define context levels, then we can create a table (which I've called mdl_context_levels) that has the following fields:
- id - the standard record ID
- name (VARCHAR(255?)) - the name of the context level
- component (VARCHAR(255?)) - the name and type of the component that defined the context level
The context level number would be id + 1000 (or another sufficiently large number to guarantee that we won't collide with any Moodle context levels). So if my block "foo" defines the custom context level "bar", then its context level number would be get_field('context_levels', 'id', 'name', 'bar', 'component', 'block_foo')+1000.
Building on the patch that I've already uploaded to MDL-20045, I would then add some methods to context_level_base to get the custom context levels (draft -- not finalized):
private static $customctxlevels;
private static function load_custom_context_levels() {
if (!isset(context_level_base::$customctxlevels)) {
$ctxlevel = get_records('context_levels');
context_level_base::$customctxlevels = array();
foreach($ctxlevel as $rec) {
context_level_base::$customctxlevels[$rec->component][$rec->name] = $rec->id + 1000;
}
}
}
static function get_custom_context_level($name, $component) {
context_level_base::load_custom_context_levels();
return context_level_base::$customctxlevels[$component][$name];
}
This provides an API to look up the context level number for a custom context level.
I would also add the following code to the get_all_contexts method:
// find all the components that have defined context levels
context_level_base::load_custom_context_levels();
$components = array();
foreach (array_keys(context_level_base::$customctxlevels) as $component) {
require (get_component_directory($component).'/db/access.php');
foreach (${$component.'_contextlevels'} as $name=>$object) {
$contextlevel = context_level_base::get_custom_context_level($name, $component);
context_level_base::$allcontextlevels[$contextlevel] = $object;
$object->level = $contextlevel;
}
}
(see below for how {component}/db/access.php is used)
Adding records to context_levels table
In addition to this, we would need code to create the records in the context_levels table. My prototype code uses a new variable in the {component}/db/access.php file (currently used to define custom capabilities). e.g., in my blocks/foo/db/access.php file, I would have a variable: "block_foo_contextlevels = array('bar' => new context_level_foo_bar());" (where context_level_foo_bar is a subclass of context_level_base).
The code for loading the custom context levels:
/**
* Loads the context level definitions for the component (from file). If no
* context levels are defined for the component, we simply return an empty
* array.
* @param string $component - examples: 'moodle', 'mod_forum', 'block_quiz_results'
* @return array array of contexts levels
*/
function load_context_level_def($component='moodle') {
$defpath = get_component_directory($component).'/db/access.php';
$contexts = array();
if (file_exists($defpath)) {
require($defpath);
if (isset(${$component.'_contextlevels'})) {
$contexts = array_keys(${$component.'_contextlevels'});
}
}
return $contexts;
}
And I've added code to the update_capabilities function:
$filectxlvl = load_context_level_def($component);
$strcomp = str_replace('/','_',$component);
$oldctxlvl = get_records('context_levels', 'component', $strcomp, , 'name');
foreach ($filectxlvl as $contextlevel) {
if (!isset($oldctxlvl[$contextlevel])) {
$level = new object;
$level->name = $contextlevel;
$level->component = $strcomp;
if (!insert_record('context_levels', $level, false, 'id')) {
return false;
}
}
}
// FIXME: remove old context levels
// what needs to be cleaned up?
// - contexts (and kill all sub-contexts?)
// - role assignments