Skip to content

Commit

Permalink
MDL-39312 cache: interfaces for managing lock instances.
Browse files Browse the repository at this point in the history
Bug: There was no way to create an instance of a lock plugin for use
within Moodle.
Solution: Implemented management interfaces as part of cache/admin.php
to allow for instances to be added and deleted.
This was done in along the same lines of adding store instances.
  • Loading branch information
Sam Hemelryk committed Apr 30, 2013
1 parent cf5a329 commit acf49f4
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 10 deletions.
50 changes: 50 additions & 0 deletions cache/admin.php
Expand Up @@ -84,6 +84,7 @@
} else if ($data = $mform->get_data()) {
$config = cache_administration_helper::get_store_configuration_from_data($data);
$writer = cache_config_writer::instance();

unset($config['lock']);
foreach ($writer->get_locks() as $lock => $lockconfig) {
if ($lock == $data->lock) {
Expand Down Expand Up @@ -180,6 +181,55 @@
cache_helper::purge_store($store);
redirect($PAGE->url, get_string('purgestoresuccess', 'cache'), 5);
break;

case 'newlockinstance':
// Adds a new lock instance.
$lock = required_param('lock', PARAM_ALPHANUMEXT);
$mform = cache_administration_helper::get_add_lock_form($lock);
if ($mform->is_cancelled()) {
redirect($PAGE->url);
} else if ($data = $mform->get_data()) {
$factory = cache_factory::instance();
$config = $factory->create_config_instance(true);
$name = $data->name;
$data = cache_administration_helper::get_lock_configuration_from_data($lock, $data);
$config->add_lock_instance($name, $lock, $data);
redirect($PAGE->url, get_string('addlocksuccess', 'cache', $name), 5);
}
break;
case 'deletelock':
// Deletes a lock instance.
$lock = required_param('lock', PARAM_ALPHANUMEXT);
$confirm = optional_param('confirm', false, PARAM_BOOL);
if (!array_key_exists($lock, $locks)) {
$notifysuccess = false;
$notification = get_string('invalidlock');
} else if ($locks[$lock]['uses'] > 0) {
$notifysuccess = false;
$notification = get_string('deletelockhasuses', 'cache');
}
if ($notifysuccess) {
if (!$confirm) {
$title = get_string('confirmlockdeletion', 'cache');
$params = array('lock' => $lock, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey());
$url = new moodle_url($PAGE->url, $params);
$button = new single_button($url, get_string('deletelock', 'cache'));

$PAGE->set_title($title);
$PAGE->set_heading($SITE->fullname);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
$confirmation = get_string('deletelockconfirmation', 'cache', $lock);
echo $OUTPUT->confirm($confirmation, $button, $PAGE->url);
echo $OUTPUT->footer();
exit;
} else {
$writer = cache_config_writer::instance();
$writer->delete_lock_instance($lock);
redirect($PAGE->url, get_string('deletelocksuccess', 'cache'), 5);
}
}
break;
}
}

Expand Down
10 changes: 10 additions & 0 deletions cache/classes/config.php
Expand Up @@ -541,6 +541,16 @@ public function get_lock_for_store($storename) {
return $this->configlocks[$lock];
}
}
return $this->get_default_lock();
}

/**
* Gets the default lock instance.
*
* @return array
* @throws cache_exception
*/
public function get_default_lock() {
foreach ($this->configlocks as $lockconf) {
if (!empty($lockconf['default'])) {
return $lockconf;
Expand Down
76 changes: 74 additions & 2 deletions cache/forms.php
Expand Up @@ -67,10 +67,10 @@ protected final function definition() {
if (is_array($locks)) {
$form->addElement('select', 'lock', get_string('lockmethod', 'cache'), $locks);
$form->addHelpButton('lock', 'lockmethod', 'cache');
$form->setType('lock', PARAM_PLUGIN);
$form->setType('lock', PARAM_ALPHANUMEXT);
} else {
$form->addElement('hidden', 'lock', '');
$form->setType('lock', PARAM_PLUGIN);
$form->setType('lock', PARAM_ALPHANUMEXT);
$form->addElement('static', 'lock-value', get_string('lockmethod', 'cache'),
'<em>'.get_string('nativelocking', 'cache').'</em>');
}
Expand Down Expand Up @@ -222,3 +222,75 @@ protected function definition() {
$this->add_action_buttons();
}
}

/**
* Form to add a cache lock instance.
*
* All cache lock plugins that wish to have custom configuration should override
* this form, and more explicitly the plugin_definition and plugin_validation methods.
*
* @package core
* @category cache
* @copyright 2013 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cache_lock_form extends moodleform {

/**
* Defines this form.
*/
final public function definition() {
$plugin = $this->_customdata['lock'];

$this->_form->addElement('hidden', 'action', 'newlockinstance');
$this->_form->setType('action', PARAM_ALPHANUMEXT);
$this->_form->addElement('hidden', 'lock', $plugin);
$this->_form->setType('lock', PARAM_COMPONENT);
$this->_form->addElement('text', 'name', get_string('lockname', 'cache'));
$this->_form->setType('name', PARAM_ALPHANUMEXT);
$this->_form->addRule('name', get_string('required'), 'required');
$this->_form->addElement('static', 'namedesc', '', get_string('locknamedesc', 'cache'));

$this->plugin_definition();

$this->add_action_buttons();
}

/**
* Validates this form.
*
* @param array $data
* @param array $files
* @return array
*/
final public function validation($data, $files) {
$errors = parent::validation($data, $files);
if (!isset($errors['name'])) {
$config = cache_config::instance();
if (in_array($data['name'], array_keys($config->get_locks()))) {
$errors['name'] = get_string('locknamenotunique', 'cache');
}
}
$errors = $this->plugin_validation($data, $files, $errors);
return $errors;
}

/**
* Plugin specific definition.
*/
public function plugin_definition() {
// No custom validation going on here.
}

/**
* Plugin specific validation.
*
* @param array $data
* @param array $files
* @param array $errors
* @return array
*/
public function plugin_validation($data, $files, array $errors) {
return $errors;
}
}
148 changes: 146 additions & 2 deletions cache/locallib.php
Expand Up @@ -187,6 +187,68 @@ public function add_store_instance($name, $plugin, array $configuration = array(
return true;
}

/**
* Adds a new lock instance to the config file.
*
* @param string $name The name the user gave the instance. PARAM_ALHPANUMEXT
* @param string $plugin The plugin we are creating an instance of.
* @param string $configuration Configuration data from the config instance.
* @throws cache_exception
*/
public function add_lock_instance($name, $plugin, $configuration = array()) {
if (array_key_exists($name, $this->configlocks)) {
throw new cache_exception('Duplicate name specificed for cache lock instance. You must provide a unique name.');
}
$class = 'cachelock_'.$plugin;
if (!class_exists($class)) {
$plugins = get_plugin_list_with_file('cachelock', 'lib.php');
if (!array_key_exists($plugin, $plugins)) {
throw new cache_exception('Invalid lock name specified. The plugin does not exist or is not valid.');
}
$file = $plugins[$plugin];
if (file_exists($file)) {
require_once($file);
}
if (!class_exists($class)) {
throw new cache_exception('Invalid lock plugin specified. The plugin does not contain the required class.');
}
}
$reflection = new ReflectionClass($class);
if (!$reflection->implementsInterface('cache_lock_interface')) {
throw new cache_exception('Invalid lock plugin specified. The plugin does not implement the required interface.');
}
$this->configlocks[$name] = array_merge($configuration, array(
'name' => $name,
'type' => 'cachelock_'.$plugin,
'default' => false
));
$this->config_save();
}

/**
* Deletes a lock instance given its name.
*
* @param string $name The name of the plugin, PARAM_ALPHANUMEXT.
* @return bool
* @throws cache_exception
*/
public function delete_lock_instance($name) {
if (!array_key_exists($name, $this->configlocks)) {
throw new cache_exception('The requested store does not exist.');
}
if ($this->configlocks[$name]['default']) {
throw new cache_exception('You can not delete the default lock.');
}
foreach ($this->configstores as $store) {
if (isset($store['lock']) && $store['lock'] === $name) {
throw new cache_exception('You cannot delete a cache lock that is being used by a store.');
}
}
unset($this->configlocks[$name]);
$this->config_save();
return true;
}

/**
* Sets the mode mappings.
*
Expand Down Expand Up @@ -578,16 +640,19 @@ public static function get_store_instance_summaries() {
$default = array();
$instance = cache_config::instance();
$stores = $instance->get_all_stores();
$locks = $instance->get_locks();
foreach ($stores as $name => $details) {
$class = $details['class'];
$store = new $class($details['name'], $details['configuration']);
$lock = (isset($details['lock'])) ? $locks[$details['lock']] : $instance->get_default_lock();
$record = array(
'name' => $name,
'plugin' => $details['plugin'],
'default' => $details['default'],
'isready' => $store->is_ready(),
'requirementsmet' => $store->are_requirements_met(),
'mappings' => 0,
'lock' => $lock,
'modes' => array(
cache_store::MODE_APPLICATION =>
($store->get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
Expand Down Expand Up @@ -870,6 +935,9 @@ public static function get_edit_store_form($plugin, $store) {

$url = new moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store));
$editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks));
if (isset($stores[$store]['lock'])) {
$editform->set_data(array('lock' => $lock));
}
// See if the cachestore is going to want to load data for the form.
// If it has a customised add instance form then it is going to want to.
$storeclass = 'cachestore_'.$plugin;
Expand Down Expand Up @@ -1030,10 +1098,86 @@ public static function get_lock_summaries() {
$lockdata = array(
'name' => $name,
'default' => $default,
'uses' => $uses
'uses' => $uses,
'type' => get_string('pluginname', $lock['type'])
);
$locks[] = $lockdata;
$locks[$lock['name']] = $lockdata;
}
return $locks;
}

/**
* Returns an array of lock plugins for which we can add an instance.
*
* Suitable for use within an mform select element.
*
* @return array
*/
public static function get_addable_lock_options() {
$plugins = get_plugin_list_with_class('cachelock', '', 'lib.php');
$options = array();
$len = strlen('cachelock_');
foreach ($plugins as $plugin => $class) {
$method = "$class::can_add_instance";
if (is_callable($method) && !call_user_func($method)) {
// Can't add an instance of this plugin.
continue;
}
$options[substr($plugin, $len)] = get_string('pluginname', $plugin);
}
return $options;
}

/**
* Gets the form to use when adding a lock instance.
*
* @param string $plugin
* @param array $lockplugin
* @return cache_lock_form
* @throws coding_exception
*/
public static function get_add_lock_form($plugin, array $lockplugin = null) {
global $CFG; // Needed for includes.
$plugins = get_plugin_list('cachelock');
if (!array_key_exists($plugin, $plugins)) {
throw new coding_exception('Invalid cache lock plugin requested when trying to create a form.');
}
$plugindir = $plugins[$plugin];
$class = 'cache_lock_form';
if (file_exists($plugindir.'/addinstanceform.php') && in_array('cache_is_configurable', class_implements($class))) {
require_once($plugindir.'/addinstanceform.php');
if (class_exists('cachelock_'.$plugin.'_addinstance_form')) {
$class = 'cachelock_'.$plugin.'_addinstance_form';
if (!array_key_exists('cache_lock_form', class_parents($class))) {
throw new coding_exception('Cache lock plugin add instance forms must extend cache_lock_form');
}
}
}
return new $class(null, array('lock' => $plugin));
}

/**
* Gets configuration data from a new lock instance form.
*
* @param string $plugin
* @param stdClass $data
* @return array
* @throws coding_exception
*/
public static function get_lock_configuration_from_data($plugin, $data) {
global $CFG;
$file = $CFG->dirroot.'/cache/locks/'.$plugin.'/lib.php';
if (!file_exists($file)) {
throw new coding_exception('Invalid cache plugin provided. '.$file);
}
require_once($file);
$class = 'cachelock_'.$plugin;
if (!class_exists($class)) {
throw new coding_exception('Invalid cache plugin provided.');
}
if (array_key_exists('cache_is_configurable', class_implements($class))) {
return $class::config_get_configuration_array($data);
}
return array();
}
}

0 comments on commit acf49f4

Please sign in to comment.