From 9b8550f8d98cbbb264b7586c5e02a8dd4be320ab Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Mon, 3 Oct 2011 21:57:16 +0200 Subject: [PATCH] MDL-29479 Grading method plugins can extend module settings block As a part of this, new class grading_controller is introduced and bunch of smaller changes was done here and there. --- grade/grading/form/lib.php | 72 ++++++++++ .../rubric/lang/en/gradingform_rubric.php | 1 + grade/grading/form/rubric/lib.php | 51 +++++++ grade/grading/lib.php | 136 ++++++++++++++++-- grade/grading/simpletest/testlib.php | 15 +- lang/en/grading.php | 1 + lib/navigationlib.php | 8 ++ 7 files changed, 269 insertions(+), 15 deletions(-) create mode 100644 grade/grading/form/lib.php create mode 100644 grade/grading/form/rubric/lib.php diff --git a/grade/grading/form/lib.php b/grade/grading/form/lib.php new file mode 100644 index 00000000000..e533bafa11f --- /dev/null +++ b/grade/grading/form/lib.php @@ -0,0 +1,72 @@ +. + +/** + * @package core + * @subpackage grading + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Grading method controller encapsulates the logic of the plugin + * + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class grading_controller { + + /** @var stdClass the context */ + protected $context; + + /** @var string the frankenstyle name of the component */ + protected $component; + + /** @var string the name of the gradable area */ + protected $area; + + /** @var int the id of the gradable area record */ + protected $areaid; + + /** + * Do not instantinate this directly, use {@link grading_manager::get_controller()} + * + * @return grading_controller instance + */ + public function __construct(stdClass $context, $component, $area, $areaid) { + $this->context = $context; + $this->component = $component; + $this->area = $area; + $this->areaid = $areaid; + } + + /** + * Extends the module settings navigation + * + * This function is called when the context for the page is an activity module with the + * FEATURE_ADVANCED_GRADING, the user has the permission moodle/grade:managegradingforms + * and there is an area with the active grading method set to the given plugin. + * + * @param settings_navigation $settingsnav {@link settings_navigation} + * @param navigation_node $node {@link navigation_node} + */ + public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $node=null) { + // do not extend by default + } +} diff --git a/grade/grading/form/rubric/lang/en/gradingform_rubric.php b/grade/grading/form/rubric/lang/en/gradingform_rubric.php index 2b9caabc090..3053f344b5c 100644 --- a/grade/grading/form/rubric/lang/en/gradingform_rubric.php +++ b/grade/grading/form/rubric/lang/en/gradingform_rubric.php @@ -24,4 +24,5 @@ defined('MOODLE_INTERNAL') || die(); +$string['definerubric'] = 'Define rubric'; $string['pluginname'] = 'Rubric'; diff --git a/grade/grading/form/rubric/lib.php b/grade/grading/form/rubric/lib.php new file mode 100644 index 00000000000..c501919947b --- /dev/null +++ b/grade/grading/form/rubric/lib.php @@ -0,0 +1,51 @@ +. + +/** + * Grading method controller for the Rubric plugin + * + * @package gradingform + * @subpackage rubric + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot.'/grade/grading/form/lib.php'); // parent class + +/** + * This controller encapsulates the rubric grading logic + */ +class rubric_grading_controller extends grading_controller { + + /** + * Extends the module settings navigation with the rubric grading settings + * + * This function is called when the context for the page is an activity module with the + * FEATURE_ADVANCED_GRADING, the user has the permission moodle/grade:managegradingforms + * and there is an area with the active grading method set to 'rubric'. + * + * @param settings_navigation $settingsnav {@link settings_navigation} + * @param navigation_node $node {@link navigation_node} + */ + public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $node=null) { + $node->add(get_string('definerubric', 'gradingform_rubric'), + new moodle_url('/grade/grading/form/rubric/edit.php', array('area' => $this->areaid)), settings_navigation::TYPE_CUSTOM, + null, null, new pix_icon('icon', '', 'gradingform_rubric')); + } +} diff --git a/grade/grading/lib.php b/grade/grading/lib.php index 621f04f9beb..6cebef60491 100644 --- a/grade/grading/lib.php +++ b/grade/grading/lib.php @@ -56,6 +56,9 @@ function get_grading_manager($context = null, $component = null, $area = null) { /** * General class providing access to common grading features * + * Grading manager provides access to the particular grading method controller + * in that area. + * * Fully initialized instance of the grading manager operates over a single * gradable area. It is possible to work with a partially initialized manager * that knows just context and component without known area, for example. @@ -73,12 +76,16 @@ class grading_manager { /** @var string the name of the gradable area */ protected $area; + /** @var stdClass|false|null the raw record from {grading_areas}, false if does not exist, null if invalidated cache */ + private $areacache = null; + /** * Sets the context the manager operates on * * @param stdClass $context */ public function set_context(stdClass $context) { + $this->areacache = null; $this->context = $context; } @@ -88,7 +95,9 @@ class grading_manager { * @param string $component the frankenstyle name of the component */ public function set_component($component) { - $this->component = $component; + $this->areacache = null; + list($type, $name) = normalize_component($component); + $this->component = $type.'_'.$name; } /** @@ -97,6 +106,7 @@ class grading_manager { * @param string $area the name of the gradable area */ public function set_area($area) { + $this->areacache = null; $this->area = $area; } @@ -147,7 +157,7 @@ class grading_manager { // return assignment_gradable_area_list(); // todo - hardcoded list for now - return array('submission' => get_string('assignmentsubmission', 'assignment')); + return array('submission' => 'Submissions'); } /** @@ -161,15 +171,20 @@ class grading_manager { $this->ensure_isset(array('context', 'component', 'area')); // get the current grading area record if it exists - $area = $DB->get_record('grading_areas', - array('contextid' => $this->context->id, 'component' => $this->component, 'areaname' => $this->area), 'id,activemethod', IGNORE_MISSING); + if (is_null($this->areacache)) { + $this->areacache = $DB->get_record('grading_areas', array( + 'contextid' => $this->context->id, + 'component' => $this->component, + 'areaname' => $this->area), + '*', IGNORE_MISSING); + } - if (empty($area)) { + if ($this->areacache === false) { // no area record yet return null; } - return $area->activemethod; + return $this->areacache->activemethod; } /** @@ -192,10 +207,15 @@ class grading_manager { } // get the current grading area record if it exists - $area = $DB->get_record('grading_areas', - array('contextid' => $this->context->id, 'component' => $this->component, 'areaname' => $this->area), 'id,activemethod', IGNORE_MISSING); + if (is_null($this->areacache)) { + $this->areacache = $DB->get_record('grading_areas', array( + 'contextid' => $this->context->id, + 'component' => $this->component, + 'areaname' => $this->area), + '*', IGNORE_MISSING); + } - if (empty($area)) { + if ($this->areacache === false) { // no area record yet, create one with the active method set $area = array( 'contextid' => $this->context->id, @@ -206,12 +226,104 @@ class grading_manager { } else { // update the existing record if needed - if ($area->activemethod != $method) { - $DB->set_field('grading_areas', 'activemethod', $method, array('id' => $area->id)); + if ($this->areacache->activemethod != $method) { + $DB->set_field('grading_areas', 'activemethod', $method, array('id' => $this->areacache->id)); } } + + $this->areacache = null; + } + + /** + * Extends the settings navigation with the grading settings + * + * This function is called when the context for the page is an activity module with the + * FEATURE_ADVANCED_GRADING and the user has the permission moodle/grade:managegradingforms. + * + * @param settings_navigation $settingsnav {@link settings_navigation} + * @param navigation_node $modulenode {@link navigation_node} + */ + public function extend_settings_navigation(settings_navigation $settingsnav, navigation_node $modulenode=null) { + global $PAGE, $CFG; + + $this->ensure_isset(array('context', 'component')); + + $areas = $this->get_available_areas(); + + if (empty($areas)) { + // no money, no funny + return; + } + + foreach ($areas as $areaname => $areatitle) { + $this->set_area($areaname); + $method = $this->get_active_method(); + + if (empty($method)) { + // no grading method selected for the given area - nothing to display + continue; + } + + if (count($areas) > 1) { + // if the module supports multiple gradable areas, make a node for each of them + $node = $modulenode->add(get_string('gradinginarea', 'core_grading', $areatitle), null, settings_navigation::NODETYPE_BRANCH); + } else { + // otherwise put the items directly into the module's node + $node = $modulenode; + } + + $controller = $this->get_controller($method); + $controller->extend_settings_navigation($settingsnav, $node); + } } + /** + * Returns the given method's controller in the gradable area + * + * @param string $method the method name, eg 'rubric' (must be available) + * @return grading_controller + */ + public function get_controller($method) { + global $CFG; + + $this->ensure_isset(array('context', 'component', 'area')); + + // make sure the passed method is a valid plugin name + if ('gradingform_'.$method !== clean_param('gradingform_'.$method, PARAM_COMPONENT)) { + throw new moodle_exception('invalid_method_name', 'core_grading'); + } + $available = $this->get_available_methods(false); + if (!array_key_exists($method, $available)) { + throw new moodle_exception('invalid_method_name', 'core_grading'); + } + + // get the current grading area record if it exists + if (is_null($this->areacache)) { + $this->areacache = $DB->get_record('grading_areas', array( + 'contextid' => $this->context->id, + 'component' => $this->component, + 'areaname' => $this->area), + '*', IGNORE_MISSING); + } + + if ($this->areacache === false) { + // no area record yet, create one + $area = array( + 'contextid' => $this->context->id, + 'component' => $this->component, + 'areaname' => $this->area); + $areaid = $DB->insert_record('grading_areas', $area); + // reload the cache + $this->areacache = $DB->get_record('grading_areas', array('id' => $areaid), '*', MUST_EXIST); + } + + require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php'); + $classname = $method.'_grading_controller'; + + return new $classname($this->context, $this->component, $this->area, $this->areacache->id); + } + + //////////////////////////////////////////////////////////////////////////// /** * Make sure that the given properties were set to some not-null value @@ -222,7 +334,7 @@ class grading_manager { private function ensure_isset(array $properties) { foreach ($properties as $property) { if (!isset($this->$property)) { - throw new coding_exception('The property '.$property.' is not set.'); + throw new coding_exception('The property "'.$property.'" is not set.'); } } } diff --git a/grade/grading/simpletest/testlib.php b/grade/grading/simpletest/testlib.php index 092b92f7727..379599f4620 100644 --- a/grade/grading/simpletest/testlib.php +++ b/grade/grading/simpletest/testlib.php @@ -96,7 +96,8 @@ class grading_manager_test extends UnitTestCase { global $DB; sleep(2); // to make sure the microtime will always return unique values - $areaname = 'area' . (string)microtime(true); + $areaname1 = 'area1-' . (string)microtime(true); + $areaname2 = 'area2-' . (string)microtime(true); $fakecontext = (object)array( 'id' => 42, 'contextlevel' => CONTEXT_MODULE, @@ -105,13 +106,21 @@ class grading_manager_test extends UnitTestCase { 'depth' => 4); // non-existing area - $gradingman = get_grading_manager($fakecontext, 'mod_foobar', $areaname); + $gradingman = get_grading_manager($fakecontext, 'mod_foobar', $areaname1); $this->assertNull($gradingman->get_active_method()); - // create area and set active method + // creates area implicitly and sets active method $gradingman->set_active_method('rubric'); $this->assertEqual('rubric', $gradingman->get_active_method()); + // switch the manager to another area + $gradingman->set_area($areaname2); + $this->assertNull($gradingman->get_active_method()); + + // switch back and ask again + $gradingman->set_area($areaname1); + $this->assertEqual('rubric', $gradingman->get_active_method()); + // attempting to set an invalid method $this->expectException('moodle_exception'); $gradingman->set_active_method('no_one_should_ever_try_to_implement_a_method_with_this_silly_name'); diff --git a/lang/en/grading.php b/lang/en/grading.php index 19625ca5cb0..6c78cad3631 100644 --- a/lang/en/grading.php +++ b/lang/en/grading.php @@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die(); +$string['gradinginarea'] = 'Grading ({$a})'; $string['gradingmethod'] = 'Grading method'; $string['gradingmethods'] = 'Grading methods'; $string['gradingmethodnone'] = 'Simple direct grading'; diff --git a/lib/navigationlib.php b/lib/navigationlib.php index 0c88ad06a4a..cfc982bfb43 100644 --- a/lib/navigationlib.php +++ b/lib/navigationlib.php @@ -3433,6 +3433,14 @@ class settings_navigation extends navigation_node { $modulenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, 'restore'); } + // Allow the active advanced grading method plugin to append its settings + $featuresfunc = $this->page->activityname.'_supports'; + if (function_exists($featuresfunc) && $featuresfunc(FEATURE_ADVANCED_GRADING) && has_capability('moodle/grade:managegradingforms', $this->page->cm->context)) { + require_once($CFG->dirroot.'/grade/grading/lib.php'); + $gradingman = get_grading_manager($this->page->cm->context, $this->page->activityname); + $gradingman->extend_settings_navigation($this, $modulenode); + } + $function = $this->page->activityname.'_extend_settings_navigation'; if (!function_exists($function)) { return $modulenode;