User:Hubert Chathi/Custom Context Levels

Jump to: navigation, search

Note: You are currently viewing documentation for Moodle 3.1. Up-to-date documentation for the latest stable version of Moodle is probably available here: Hubert Chathi/Custom Context Levels.

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) {
        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
            $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)) {
        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