Availability API for items within a module

Jump to: navigation, search

Moodle 2.7


Introduction

The Availability API provides availability controls for activity modules (course-modules) and sections. It can also be used, with some limitations, to implement an interface for availability restrictions within a module (if you wanted to add different restrictions to different pages within a Book, for example).

Limitations

The following limitations currently apply:

  • The editing interface must be within a Moodle form.
  • You may only have a single 'Restrict access' control on a page. (This means that you can't have a second one on your main module settings form, as that already has one; you would have to put this on a separate form.)
  • The API only works within a course. You can't use it for system-level items.
  • None of this has been tested for real. The API wasn't really designed for this so although it seems to work perfectly well, there may be some catch.

How to implement

To implement the API for an item within your module, assuming that item has its own editing form, you need to do the following:

  • Editing interface:
    • Add code to the form definition to add the 'restrict access' field and JavaScript.
    • Add code to the form validation to check its results.
    • Store the returned 'availabiltyconditionsjson' value in a suitable database location when the form is saved, and ensure this value is provided in the normal form data when displaying the form.
  • Back-end class:
    • Implement a new class that extends \core_availability\info with regard to your type of item.
    • This class needs to provide the availability settings value to the parent class, supply a name for debugging purposes, have a mechanism to set the value in the database, and return a Moodle context relating to the item.
  • Implement backup and restore:
    • Include the availabilityconditionsjson value in backup and restore as usual.
    • After restore completes (NOT during restore of your individual module - must be after all modules were restored), create an instance of your class and call the update_after_restore function.
  • Check availability:
    • Check Moodle capabilities that may mean the current user bypasses the restrictions.
    • Construct an object of your class and call the is_available function.
    • Use the returned data to control whether your item displays (or whether we show information about when it will be available, or whether we don't show anything at all).

Example

This example is a complete script, demonstrating everything except backup/restore. If you save it as silly.php within mod/page, and then call it as silly.php?id=1234 where 1234 is a valid page id, you'll be able to set an availability condition with the form and see the result.

Note: For real use you would probably create several suitably namespaced classes, rather than slinging everything into one script. The single script here is just an example.

require(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/formslib.php');
 
// Standard Moodle setup for module access.
$id = required_param('id', PARAM_INT);
$course = $DB->get_record_sql("
        SELECT c.*
          FROM {course_modules} cm
          JOIN {course} c ON c.id = cm.course
         WHERE cm.id = ?", array($id), MUST_EXIST);
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->get_cm($id);
require_login($course, true, $cm);
 
// Set up page stuff.
$pageurl = new moodle_url('/mod/page/silly.php', array('id' => $id));
$PAGE->set_url($pageurl);
$PAGE->set_context(context_module::instance($id));
 
// Define a form that includes availability settings.
class mod_mymodule_silly_form extends moodleform {
    public function definition() {
        global $COURSE;
 
        $mform = $this->_form;
        $cm = $this->_customdata->cm;
        $mform->addElement('hidden', 'id', $cm->id);
        $mform->setType('id', PARAM_INT);
 
        // Use this code to add the 'Restrict access' section.
        // NOTE: Due to limitations in the JavaScript and CSS, you may only
        // have one of these fields on a page! Sorry.
        $mform->addElement('header', 'availabilityconditionsheader',
                get_string('restrictaccess', 'availability'));
        $mform->addElement('textarea', 'availabilityconditionsjson',
                get_string('accessrestrictions', 'availability'));
        \core_availability\frontend::include_all_javascript($COURSE, $cm);
 
        $this->add_action_buttons(false);
    }
 
    public function validation($data, $files) {
        $errors = array();
 
        // Use this code to validate the 'Restrict access' section.
        \core_availability\frontend::report_validation_errors($data, $errors);
 
        return $errors;
    }
}
 
// Custom availability class. You need one of these for each type of items that
// can have availability, so there's one in core for modules and one for
// sections, and you make a new one if you put availability on anything else.
class mod_mymodule_availability_info extends \core_availability\info {
    protected $cm;
 
    // You would probably define more suitable parameters here about the
    // specific thing you're controlling availability for.
    public function __construct(\cm_info $cm, $availability) {
        // You should probably set the $visible parameter to 'true' unless
        // you want to implement a separate eye icon for your thingy.
        parent::__construct($cm->get_course(), true, $availability);
        $this->cm = $cm;
    }
 
    protected function get_thing_name() {
        // This may be used in error messages etc. You would probably use
        // the name of the thing you're controlling availability for.
        return 'Special thing within module';
    }
 
    protected function set_in_database($availability) {
        // This function should save the availability settings back to database.
        // It's needed if doing an update after restore, so you do need to
        // implement it.
    }
 
    public function get_context() {
        return \context_module::instance($this->cm->id);
    }
 
    // I didn't bother to implement filter_user_list, so it's using the default
    // which considers only this condition. You might want to make a
    // filter_user_list that takes into account the course-module's permissions
    // too (like how the info_module class includes the section), if you expect
    // to actually use the 'list users who can access this' APIs.
}
 
$mform = new mod_mymodule_silly_form('silly.php', (object)array('cm' => $cm));
if ($data = $mform->get_data()) {
    // Because this is a silly test, we're not going to store the availability
    // settings in the database, instead we put it in user session.
    $SESSION->mod_mymodule_silly_availability = $data->availabilityconditionsjson;
    redirect($pageurl);
}
 
echo $OUTPUT->header();
 
// If there is some availability data...
if (isset($SESSION->mod_mymodule_silly_availability)) {
    // Set form data.
    $mform->set_data(array('availabilityconditionsjson' => $SESSION->mod_mymodule_silly_availability));
 
    // Check availability. Note that this does NOT take into account any
    // capabilities the user might have - you probably want to check for
    // viewhiddenactivities capability and skip availability checks in that
    // case.
    $info = new mod_mymodule_availability_info($cm, $SESSION->mod_mymodule_silly_availability);
    $information = '';
    $available = $info->is_available($information);
 
    // Because this is a test page, we'll just display whether it's available or
    // not, and the message(s) if it isn't.
    echo html_writer::start_tag('ul');
    echo html_writer::tag('li', $available ? 'This page IS available to you' :
            'This page IS NOT available to you');
    if (!$available) {
        echo html_writer::tag('li', $information === '' ?
                'No information displayed (hide entirely)' :
                'Information displayed (show info)');
    }
    echo html_writer::end_tag('ul');
    if (!$available) {
        echo html_writer::start_div();
        echo $information;
        echo html_writer::end_div();
    }
}
 
$mform->display();
echo $OUTPUT->footer();