Commit d3db4b03 authored by sam marshall's avatar sam marshall
Browse files

MDL-44070 Conditional availability enhancements (2): subsystem, API

This commit defines the new /availability root folder, with
/availability/classes, /availability/tests, and
/availability/condition where the condition plugins will live.
Condition plugin prefix is availability_, e.g. availability_date.

Rationale for this organisation:

1. I was originally going to put this in /lib/availability but
   it has been pointed out that putting even more junk in lib
   is probably bad.
2. 'availability' and 'condition' are the two names used in code
   to refer to this system ($CFG->enableavailability).
3. The prefix has to be short enough to allow database tables
   (although in practice I assume that condition plugins will not
   normally contain database tables).

The new API includes a Boolean tree structure that controls the
availability of an item.

AMOS BEGIN
 CPY [availabilityconditions,core_condition],[restrictaccess,core_availability]
 CPY [enableavailability,core_condition],[enableavailability,core_availability]
 CPY [configenableavailability,core_condition],[enableavailability_desc,core_availability]
AMOS END
parent 8e97006a
<?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/>.
/**
* Provides an overview of installed availability conditions.
*
* You can also enable/disable them from this screen.
*
* @package tool_availabilityconditions
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->libdir . '/tablelib.php');
admin_externalpage_setup('manageavailability');
// Get sorted list of all availability condition plugins.
$plugins = array();
foreach (core_component::get_plugin_list('availability') as $plugin => $plugindir) {
if (get_string_manager()->string_exists('pluginname', 'availability_' . $plugin)) {
$strpluginname = get_string('pluginname', 'availability_' . $plugin);
} else {
$strpluginname = $plugin;
}
$plugins[$plugin] = $strpluginname;
}
core_collator::asort($plugins);
// Do plugin actions.
$pageurl = new moodle_url('/' . $CFG->admin . '/availabilityconditions.php');
if (($plugin = optional_param('plugin', '', PARAM_PLUGIN))) {
require_sesskey();
if (!array_key_exists($plugin, $plugins)) {
print_error('invalidcomponent', 'error', $pageurl);
}
$action = required_param('action', PARAM_ALPHA);
switch ($action) {
case 'hide' :
set_config('disabled', 1, 'availability_' . $plugin);
break;
case 'show' :
unset_config('disabled', 'availability_' . $plugin);
break;
}
core_plugin_manager::reset_caches();
// Always redirect back after an action.
redirect($pageurl);
}
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('manageplugins', 'availability'));
// Show a table of installed availability conditions.
$table = new flexible_table('availabilityconditions_administration_table');
$table->define_columns(array('name', 'version', 'enable'));
$table->define_headers(array(get_string('plugin'),
get_string('version'), get_string('hide') . '/' . get_string('show')));
$table->define_baseurl($PAGE->url);
$table->set_attribute('id', 'availabilityconditions');
$table->set_attribute('class', 'admintable generaltable');
$table->setup();
$enabledlist = core\plugininfo\availability::get_enabled_plugins();
foreach ($plugins as $plugin => $name) {
// Get version or ? if unknown.
$version = get_config('availability_' . $plugin);
if (!empty($version->version)) {
$version = $version->version;
} else {
$version = '?';
}
// Get enabled status and use to grey out name if necessary.
$enabled = in_array($plugin, $enabledlist);
if ($enabled) {
$enabledaction = 'hide';
$enabledstr = get_string('hide');
$class = '';
} else {
$enabledaction = 'show';
$enabledstr = get_string('show');
$class = 'dimmed_text';
}
$namespan = html_writer::span($name, $class);
// Make enable control. This is a POST request (using a form control rather
// than just a link) because it makes a database change.
$targeturl = new moodle_url('availabilityconditions.php', array(
'plugin' => $plugin, 'action' => $enabledaction, 'sesskey' => sesskey()));
$enablecontrol = html_writer::tag('form', html_writer::div(
html_writer::empty_tag('input', array('type' => 'hidden',
'name' => 'sesskey', 'value' => sesskey())) .
html_writer::empty_tag('input', array('type' => 'hidden',
'name' => 'plugin', 'value' => $plugin)) .
html_writer::empty_tag('input', array('type' => 'hidden',
'name' => 'action', 'value' => $enabledaction)) .
html_writer::empty_tag('input', array('type' => 'image',
'src' => $OUTPUT->pix_url('t/' . $enabledaction), 'alt' => $enabledstr,
'title' => $enabledstr))
), array(
'method' => 'post', 'action' => 'availabilityconditions.php'));
$table->add_data(array($namespan, $version, $enablecontrol));
}
$table->print_html();
echo $OUTPUT->footer();
<?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/>.
/**
* Strings for availability conditions options.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['manageplugins'] = 'Manage restrictions';
$string['pluginname'] = 'Availability condition management';
<?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/>.
/**
* Adds settings links to admin tree.
*
* @package tool_availabilityconditions
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
if ($hassiteconfig) {
$ADMIN->add('modules', new admin_category('availabilitysettings',
new lang_string('type_availability_plural', 'plugin')));
$ADMIN->add('availabilitysettings', new admin_externalpage('manageavailability',
new lang_string('manageplugins', 'tool_availabilityconditions'),
$CFG->wwwroot . '/' . $CFG->admin . '/tool/availabilityconditions/'));
foreach (core_plugin_manager::instance()->get_plugins_of_type('availability') as $plugin) {
/** @var \core\plugininfo\format $plugin */
$plugin->load_settings($ADMIN, 'availabilitysettings', $hassiteconfig);
}
}
<?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/>.
/**
* Version
*
* @package tool_availabilityconditions
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2014012900;
$plugin->requires = 2014040401;
$plugin->component = 'tool_availabilityconditions';
<?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/>.
/**
* Used while evaluating conditions in bulk.
*
* This object caches get_users_by_capability results in case they are needed
* by multiple conditions.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_availability;
defined('MOODLE_INTERNAL') || die();
/**
* Used while evaluating conditions in bulk.
*
* This object caches get_users_by_capability results in case they are needed
* by multiple conditions.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class capability_checker {
/** @var \context Course or module context */
protected $context;
/** @var array Associative array of capability => result */
protected $cache = array();
/**
* Constructs for given context.
*
* @param \context $context Context
*/
public function __construct(\context $context) {
$this->context = $context;
}
/**
* Gets users on course who have the specified capability. Returns an array
* of user objects which only contain the 'id' field. If the same capability
* has already been checked (e.g. by another condition) then a cached
* result will be used.
*
* More fields are not necessary because this code is only used to filter
* users from an existing list.
*
* @param string $capability Required capability
* @return array Associative array of user id => objects containing only id
*/
public function get_users_by_capability($capability) {
if (!array_key_exists($capability, $this->cache)) {
$this->cache[$capability] = get_users_by_capability(
$this->context, $capability, 'u.id');
}
return $this->cache[$capability];
}
}
<?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/>.
/**
* Base class for a single availability condition.
*
* All condition types must extend this class.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_availability;
defined('MOODLE_INTERNAL') || die();
/**
* Base class for a single availability condition.
*
* All condition types must extend this class.
*
* The structure of a condition in JSON input data is:
*
* { type:'date', ... }
*
* where 'date' is the name of the plugin (availability_date in this case) and
* ... is arbitrary extra data to be used by the plugin.
*
* Conditions require a constructor with one parameter: $structure. This will
* contain all the JSON data for the condition. If the structure of the data
* is incorrect (e.g. missing fields) then the constructor may throw a
* coding_exception. However, the constructor should cope with all data that
* was previously valid (e.g. if the format changes, old data may still be
* present in a restore, so there should be a default value for any new fields
* and old ones should be handled correctly).
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class condition extends tree_node {
/**
* Determines whether a particular item is currently available
* according to this availability condition.
*
* If implementations require a course or modinfo, they should use
* the get methods in $info.
*
* The $not option is potentially confusing. This option always indicates
* the 'real' value of NOT. For example, a condition inside a 'NOT AND'
* group will get this called with $not = true, but if you put another
* 'NOT OR' group inside the first group, then a condition inside that will
* be called with $not = false. We need to use the real values, rather than
* the more natural use of the current value at this point inside the tree,
* so that the information displayed to users makes sense.
*
* @param bool $not Set true if we are inverting the condition
* @param info $info Item we're checking
* @param bool $grabthelot Performance hint: if true, caches information
* required for all course-modules, to make the front page and similar
* pages work more quickly (works only for current user)
* @param int $userid User ID to check availability for
* @return bool True if available
*/
public abstract function is_available($not, info $info, $grabthelot, $userid);
public function check_available($not, info $info, $grabthelot, $userid) {
// Use is_available, and we always display (at this stage).
$allow = $this->is_available($not, $info, $grabthelot, $userid);
return new result($allow, $this);
}
public function is_available_for_all($not = false) {
// Default is that all conditions may make something unavailable.
return false;
}
/**
* Display a representation of this condition (used for debugging).
*
* @return string Text representation of condition
*/
public function __toString() {
return '{' . $this->get_type() . ':' . $this->get_debug_string() . '}';
}
/**
* Gets the type name (e.g. 'date' for availability_date) of plugin.
*
* @return string The type name for this plugin
*/
protected function get_type() {
return preg_replace('~^availability_(.*?)\\\\condition$~', '$1', get_class($this));
}
/**
* Obtains a string describing this restriction (whether or not
* it actually applies). Used to obtain information that is displayed to
* students if the activity is not available to them, and for staff to see
* what conditions are.
*
* The $full parameter can be used to distinguish between 'staff' cases
* (when displaying all information about the activity) and 'student' cases
* (when displaying only conditions they don't meet).
*
* If implementations require a course or modinfo, they should use
* the get methods in $info.
*
* The special string <AVAILABILITY_CMNAME_123/> can be returned, where
* 123 is any number. It will be replaced with the correctly-formatted
* name for that activity.
*
* @param bool $full Set true if this is the 'full information' view
* @param bool $not Set true if we are inverting the condition
* @param info $info Item we're checking
* @return string Information string (for admin) about all restrictions on
* this item
*/
public abstract function get_description($full, $not, info $info);
/**
* Obtains a string describing this restriction, used when there is only
* a single restriction to display. (I.e. this provides a 'short form'
* rather than showing in a list.)
*
* Default behaviour sticks the prefix text, normally displayed above
* the list, in front of the standard get_description call.
*
* If implementations require a course or modinfo, they should use
* the get methods in $info.
*
* The special string <AVAILABILITY_CMNAME_123/> can be returned, where
* 123 is any number. It will be replaced with the correctly-formatted
* name for that activity.
*
* @param bool $full Set true if this is the 'full information' view
* @param bool $not Set true if we are inverting the condition
* @param info $info Item we're checking
* @return string Information string (for admin) about all restrictions on
* this item
*/
public function get_standalone_description($full, $not, info $info) {
return get_string('list_root_and', 'availability') . ' ' .
$this->get_description($full, $not, $info);
}
/**
* Obtains a representation of the options of this condition as a string,
* for debugging.
*
* @return string Text representation of parameters
*/
protected abstract function get_debug_string();
public function update_dependency_id($table, $oldid, $newid) {
// By default, assumes there are no dependent ids.
return false;
}
/**
* If the plugin has been configured to rely on a particular activity's
* completion value, it should return true here. (This is necessary so that
* we know the course page needs to update when that activity becomes
* complete.)
*
* Default implementation returns false.
*
* @param stdClass $course Moodle course object
* @param int $cmid ID of activity whose completion value is considered
* @return boolean True if the availability of something else may rely on it
*/
public static function completion_value_used($course, $cmid) {
return false;
}
}
<?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/>.
/**
* Class with front-end (editing form) functionality.
*
* This is a base class of a class implemented by each component, and also has
* static methods.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_availability;
defined('MOODLE_INTERNAL') || die();
/**
* Class with front-end (editing form) functionality.
*
* This is a base class of a class implemented by each component, and also has
* static methods.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class frontend {
/**
* Decides whether this plugin should be available in a given course. The
* plugin can do this depending on course or system settings.
*
* Default returns true.
*
* @param stdClass $course Course object
* @param \cm_info $cm Course-module currently being edited (null if none)
* @param \section_info $section Section currently being edited (null if none)
*/
protected function allow_add($course, \cm_info $cm = null,
\section_info $section = null) {
return true;
}
/**
* Gets a list of string identifiers (in the plugin's language file) that
* are required in JavaScript for this plugin. The default returns nothing.
*
* You do not need to include the 'title' string (which is used by core) as
* this is automatically added.
*
* @return array Array of required string identifiers
*/
protected function get_javascript_strings() {
return array();
}
/**
* Gets additional parameters for the plugin's initInner function.
*
* Default returns no parameters.
*
* @param stdClass $course Course object
* @param \cm_info $cm Course-module currently being edited (null if none)
* @param \section_info $section Section currently being edited (null if none)
* @return array Array of parameters for the JavaScript function
*/
protected function get_javascript_init_params($course, \cm_info $cm = null,
\section_info $section = null) {
return array();
}
/**
* Gets the Frankenstyle component name for this plugin.
*
* @return string The component name for this plugin
*/
protected function get_component() {
return preg_replace('~^(availability_.*?)\\\\frontend$~', '$1', get_class($this));
}
/**
* Includes JavaScript for the main system and all plugins.
*
* @param stdClass $course Course object
* @param \cm_info $cm Course-module currently being edited (null if none)
* @param \section_info $section Section currently being edited (null if none)
*/
public static function include_all_javascript($course, \cm_info $cm = null,
\section_info $section = null) {
global $PAGE;
// Prepare array of required YUI modules. It is bad for performance to
// make multiple yui_module calls, so we group all the plugin modules
// into a single call (the main init function will call init for each
// plugin).
$modules = array('moodle-core_availability-form', 'base', 'node',
'panel', 'moodle-core-notification-dialogue', 'json');
// Work out JS to include for all components.
$pluginmanager = \core_plugin_manager::instance();
$enabled = $pluginmanager->get_enabled_plugins('availability');
$componentparams = new \stdClass();
foreach ($enabled as $plugin => $info) {
// Create plugin front-end object.
$class = '\availability_' . $plugin . '\frontend';
$frontend = new $class();
// Add to array of required YUI modules.
$component = $frontend->get_component();
$modules[] = 'moodle-' . $component . '-form';