diff --git a/grade/externallib.php b/grade/externallib.php new file mode 100644 index 00000000000..5b7eadcc6b3 --- /dev/null +++ b/grade/externallib.php @@ -0,0 +1,280 @@ +. + +/** + * External assign API + * + * @package core_grade + * @since Moodle 2.5 + * @copyright 2013 Paul Charsley + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +require_once("$CFG->libdir/externallib.php"); +require_once("$CFG->dirroot/grade/grading/lib.php"); + +/** + * core grade functions + */ +class core_grade_external extends external_api { + + /** + * Describes the parameters for get_definitions + * @return external_function_parameters + * @since Moodle 2.5 + */ + public static function get_definitions_parameters () { + return new external_function_parameters( + array( + 'cmids' => new external_multiple_structure( + new external_value(PARAM_INT, 'course module id'), '1 or more course module ids'), + 'areaname' => new external_value(PARAM_AREA, 'area name'), + 'activeonly' => new external_value(PARAM_BOOL, 'Only the active method', VALUE_DEFAULT, 0) + ) + ); + } + + /** + * Returns the definitions for the requested course module ids + * @param array of ints $cmids + * @param string $areaname + * @param boolean $activeonly default is false, if true, only the active method is returned + * @return array of areas with definitions for each requested course module id + * @since Moodle 2.5 + */ + public static function get_definitions ($cmids, $areaname, $activeonly = false) { + global $DB, $CFG; + require_once("$CFG->dirroot/grade/grading/form/lib.php"); + $params = self::validate_parameters(self::get_definitions_parameters(), + array('cmids' => $cmids, + 'areaname' => $areaname, + 'activeonly' => $activeonly)); + $warnings = array(); + $areas = array(); + foreach ($params['cmids'] as $cmid) { + $context = context_module::instance($cmid); + try { + self::validate_context($context); + } catch (Exception $e) { + $warnings[] = array( + 'item' => 'module', + 'itemid' => $cmid, + 'message' => 'No access rights in module context', + 'warningcode' => '1' + ); + continue; + } + // Check if the user has managegradingforms capability. + $isgradingmethodmanager = false; + if (has_capability('moodle/grade:managegradingforms', $context)) { + $isgradingmethodmanager = true; + } + $module = get_coursemodule_from_id('', $cmid, 0, false, MUST_EXIST); + $componentname = "mod_".$module->modname; + + // Get the grading manager. + $gradingmanager = get_grading_manager($context, $componentname, $params['areaname']); + // Get the controller for each grading method. + $methods = array(); + if ($params['activeonly'] == true) { + $methods[] = $gradingmanager->get_active_method(); + } else { + $methods = array_keys($gradingmanager->get_available_methods(false)); + } + + $area = array(); + $area['cmid'] = $cmid; + $area['contextid'] = $context->id; + $area['component'] = $componentname; + $area['activemethod'] = $gradingmanager->get_active_method(); + $area['definitions'] = array(); + + foreach ($methods as $method) { + $controller = $gradingmanager->get_controller($method); + $def = $controller->get_definition(true); + if ($def == false) { + continue; + } + if ($isgradingmethodmanager == false) { + $isviewable = true; + if ($def->status != gradingform_controller::DEFINITION_STATUS_READY) { + $warnings[] = array( + 'item' => 'module', + 'itemid' => $cmid, + 'message' => 'Capability moodle/grade:managegradingforms required to view draft definitions', + 'warningcode' => '1'); + $isviewable = false; + } + if (!empty($def->options)) { + $options = json_decode($def->options); + if (isset ($options->alwaysshowdefinition) + && $options->alwaysshowdefinition == 0) { + $warnings[] = array( + 'item' => 'module', + 'itemid' => $cmid, + 'message' => 'Capability moodle/grade:managegradingforms required to preview definition', + 'warningcode' => '1'); + $isviewable = false; + } + } + if ($isviewable == false) { + continue; + } + } + $definition = array(); + $definition['id'] = $def->id; + $definition['method'] = $method; + $definition['name'] = $def->name; + $definition['description'] = $def->description; + $definition['descriptionformat'] = $def->descriptionformat; + $definition['status'] = $def->status; + $definition['copiedfromid'] = $def->copiedfromid; + $definition['timecreated'] = $def->timecreated; + $definition['usercreated'] = $def->usercreated; + $definition['timemodified'] = $def->timemodified; + $definition['usermodified'] = $def->usermodified; + $definition['timecopied'] = $def->timecopied; + // Format the description text field. + list($definition['description'], $definition['descriptionformat']) = + external_format_text($definition['description'], $definition['descriptionformat'], + $context->id, $componentname, 'description', $def->id); + $details = $controller->get_external_definition_details(); + $items = array(); + foreach ($details as $key => $value) { + $items[$key] = self::format_text($def->{$key}, $context->id, $componentname, $def->id); + } + $definition[$method] = $items; + $area['definitions'][] = $definition; + } + $areas[] = $area; + } + $result = array( + 'areas' => $areas, + 'warnings' => $warnings + ); + return $result; + } + + /** + * Recursively processes all elements in an array and runs external_format_text()on + * all elements which have a text field and associated format field with a key name + * that ends with the text 'format'. The modified array is returned. + * @param array $items the array to be processed + * @param int $contextid + * @param string $componentname + * @param int $itemid + * @see external_format_text in lib/externallib.php + * @return array the input array with all fields formatted + */ + private static function format_text($items, $contextid, $componentname, $itemid) { + $formatkeys = array(); + foreach ($items as $key => $value) { + if (!is_array($value) && substr_compare($key, 'format', -6, 6) === 0) { + $formatkeys[] = $key; + } + } + foreach ($formatkeys as $formatkey) { + $descriptionkey = substr($formatkey, 0, -6); + list($items[$descriptionkey], $items[$formatkey]) = + external_format_text($items[$descriptionkey], $items[$formatkey], + $contextid, $componentname, 'description', $itemid); + } + foreach ($items as &$value) { + if (is_array($value)) { + $value = self::format_text($value, $contextid, $componentname, $itemid); + } + } + return $items; + } + + /** + * Creates a grading area + * @return external_single_structure + * @since Moodle 2.5 + */ + private static function grading_area() { + return new external_single_structure( + array ( + 'cmid' => new external_value(PARAM_INT, 'course module id'), + 'contextid' => new external_value(PARAM_INT, 'context id'), + 'component' => new external_value(PARAM_TEXT, 'component name'), + 'activemethod' => new external_value(PARAM_TEXT, 'active method', VALUE_OPTIONAL), + 'definitions' => new external_multiple_structure(self::definition(), 'definitions') + ) + ); + } + + /** + * creates a grading form definition + * @return external_single_structure + * @since Moodle 2.5 + */ + private static function definition() { + global $CFG; + $definition = array(); + $definition['id'] = new external_value(PARAM_INT, 'definition id'); + $definition['method'] = new external_value(PARAM_TEXT, 'method'); + $definition['name'] = new external_value(PARAM_TEXT, 'name'); + $definition['description'] = new external_value(PARAM_RAW, 'description'); + $definition['descriptionformat'] = new external_format_value('description'); + $definition['status'] = new external_value(PARAM_INT, 'status'); + $definition['copiedfromid'] = new external_value(PARAM_INT, 'copied from id', VALUE_OPTIONAL); + $definition['timecreated'] = new external_value(PARAM_INT, 'creation time'); + $definition['usercreated'] = new external_value(PARAM_INT, 'user who created definition'); + $definition['timemodified'] = new external_value(PARAM_INT, 'last modified time'); + $definition['usermodified'] = new external_value(PARAM_INT, 'user who modified definition'); + $definition['timecopied'] = new external_value(PARAM_INT, 'time copied', VALUE_OPTIONAL); + foreach (self::get_grading_methods() as $method) { + require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php'); + $details = call_user_func('gradingform_'.$method.'_controller::get_external_definition_details'); + if ($details != null) { + $items = array(); + foreach ($details as $key => $value) { + $details[$key]->required = VALUE_OPTIONAL; + $items[$key] = $value; + } + $definition[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL); + } + } + return new external_single_structure($definition); + } + + /** + * Describes the get_definitions return value + * @return external_single_structure + * @since Moodle 2.5 + */ + public static function get_definitions_returns() { + return new external_single_structure( + array( + 'areas' => new external_multiple_structure(self::grading_area(), 'list of grading areas'), + 'warnings' => new external_warnings() + ) + ); + } + + /** + * @return array of available grading methods + * @since Moodle 2.5 + */ + private static function get_grading_methods() { + $methods = array_keys(grading_manager::available_methods(false)); + return $methods; + } + +} diff --git a/grade/grading/form/guide/lib.php b/grade/grading/form/guide/lib.php index 15ef0d7b1de..b6b9ec22c59 100644 --- a/grade/grading/form/guide/lib.php +++ b/grade/grading/form/guide/lib.php @@ -643,6 +643,40 @@ class gradingform_guide_controller extends gradingform_controller { } return $returnvalue; } + + /** + * @return array An array containing 2 key/value pairs which hold the external_multiple_structure + * for the 'guide_criteria' and the 'guide_comment'. + * @see gradingform_controller::get_external_definition_details() + * @since Moodle 2.5 + */ + public static function get_external_definition_details() { + $guide_criteria = new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'criterion id'), + 'sortorder' => new external_value(PARAM_INT, 'sortorder'), + 'description' => new external_value(PARAM_RAW, 'description', VALUE_OPTIONAL), + 'descriptionformat' => new external_format_value('description', VALUE_OPTIONAL), + 'shortname' => new external_value(PARAM_TEXT, 'description'), + 'descriptionmarkers' => new external_value(PARAM_RAW, 'markers description', VALUE_OPTIONAL), + 'descriptionmarkersformat' => new external_format_value('descriptionmarkers', VALUE_OPTIONAL), + 'maxscore' => new external_value(PARAM_FLOAT, 'maximum score') + ) + ) + ); + $guide_comment = new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'criterion id'), + 'sortorder' => new external_value(PARAM_INT, 'sortorder'), + 'description' => new external_value(PARAM_RAW, 'description', VALUE_OPTIONAL), + 'descriptionformat' => new external_format_value('description', VALUE_OPTIONAL) + ) + ), 'comments', VALUE_OPTIONAL + ); + return array('guide_criteria' => $guide_criteria, 'guide_comment' => $guide_comment); + } } /** diff --git a/grade/grading/form/lib.php b/grade/grading/form/lib.php index c096870ba93..6c8c8acc5da 100644 --- a/grade/grading/form/lib.php +++ b/grade/grading/form/lib.php @@ -642,6 +642,22 @@ abstract class gradingform_controller { } return $this->graderange; } + + /** + * Overridden by sub classes that wish to make definition details available to web services. + * When not overridden, only definition data common to all grading methods is made available. + * When overriding, the return value should be an array containing one or more key/value pairs. + * These key/value pairs should match the definition returned by the get_definition() function. + * For examples, look at: + * $gradingform_rubric_controller->get_external_definition_details() + * $gradingform_guide_controller->get_external_definition_details() + * @return array An array of one or more key/value pairs containing the external_multiple_structure/s + * corresponding to the definition returned by $controller->get_definition() + * @since Moodle 2.5 + */ + public static function get_external_definition_details() { + return null; + } } /** diff --git a/grade/grading/form/rubric/lib.php b/grade/grading/form/rubric/lib.php index 22ce4c9422d..7f08a4e6516 100644 --- a/grade/grading/form/rubric/lib.php +++ b/grade/grading/form/rubric/lib.php @@ -654,6 +654,36 @@ class gradingform_rubric_controller extends gradingform_controller { } return $returnvalue; } + + /** + * @return array An array containing a single key/value pair with the 'rubric_criteria' external_multiple_structure. + * @see gradingform_controller::get_external_definition_details() + * @since Moodle 2.5 + */ + public static function get_external_definition_details() { + $rubric_criteria = new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'criterion id'), + 'sortorder' => new external_value(PARAM_INT, 'sortorder'), + 'description' => new external_value(PARAM_RAW, 'description', VALUE_OPTIONAL), + 'descriptionformat' => new external_format_value('description', VALUE_OPTIONAL), + 'levels' => new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'level id'), + 'score' => new external_value(PARAM_FLOAT, 'score'), + 'definition' => new external_value(PARAM_RAW, 'definition', VALUE_OPTIONAL), + 'definitionformat' => new external_format_value('definition', VALUE_OPTIONAL) + ) + ), 'levels', VALUE_OPTIONAL + ) + ) + ), 'definition details', VALUE_OPTIONAL + ); + return array('rubric_criteria' => $rubric_criteria); + } + } /** diff --git a/grade/tests/externallib_test.php b/grade/tests/externallib_test.php new file mode 100644 index 00000000000..a1cbfc5f494 --- /dev/null +++ b/grade/tests/externallib_test.php @@ -0,0 +1,184 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +/** + * External core grade functions unit tests + * + * @package core_grade + * @category external + * @copyright 2013 Paul Charsley + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_grade_external_testcase extends externallib_advanced_testcase { + + /** + * Tests set up + */ + protected function setUp() { + global $CFG; + require_once($CFG->dirroot . '/grade/externallib.php'); + } + + /** + * Test get_definitions + */ + public function test_get_definitions () { + global $DB, $CFG, $USER; + + $this->resetAfterTest(true); + // Create a course and assignment. + $coursedata['idnumber'] = 'idnumbercourse'; + $coursedata['fullname'] = 'Lightwork Course'; + $coursedata['summary'] = 'Lightwork Course description'; + $coursedata['summaryformat'] = FORMAT_MOODLE; + $course = self::getDataGenerator()->create_course($coursedata); + + $assigndata['course'] = $course->id; + $assigndata['name'] = 'lightwork assignment'; + + $cm = self::getDataGenerator()->create_module('assign', $assigndata); + + // Create manual enrolment record. + $manual_enrol_data['enrol'] = 'manual'; + $manual_enrol_data['status'] = 0; + $manual_enrol_data['courseid'] = $course->id; + $enrolid = $DB->insert_record('enrol', $manual_enrol_data); + + // Create a teacher and give them capabilities. + $coursecontext = context_course::instance($course->id); + $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $coursecontext->id, 3); + $modulecontext = context_module::instance($cm->id); + $this->assignUserCapability('mod/assign:grade', $modulecontext->id, $roleid); + + // Create the teacher's enrolment record. + $user_enrolment_data['status'] = 0; + $user_enrolment_data['enrolid'] = $enrolid; + $user_enrolment_data['userid'] = $USER->id; + $DB->insert_record('user_enrolments', $user_enrolment_data); + + // Create a grading area. + $gradingarea = array( + 'contextid' => $modulecontext->id, + 'component' => 'mod_assign', + 'areaname' => 'submissions', + 'activemethod' => 'rubric' + ); + $areaid = $DB->insert_record('grading_areas', $gradingarea); + + // Create a rubric grading definition. + $rubricdefinition = array ( + 'areaid' => $areaid, + 'method' => 'rubric', + 'name' => 'test', + 'status' => 20, + 'copiedfromid' => 1, + 'timecreated' => 1, + 'usercreated' => $USER->id, + 'timemodified' => 1, + 'usermodified' => $USER->id, + 'timecopied' => 0 + ); + $definitionid = $DB->insert_record('grading_definitions', $rubricdefinition); + + // Create a criterion with levels. + $rubriccriteria1 = array ( + 'definitionid' => $definitionid, + 'sortorder' => 1, + 'description' => 'Demonstrate an understanding of disease control', + 'descriptionformat' => 0 + ); + $criterionid1 = $DB->insert_record('gradingform_rubric_criteria', $rubriccriteria1); + $rubriclevel1 = array ( + 'criterionid' => $criterionid1, + 'score' => 5, + 'definition' => 'pass', + 'definitionformat' => 0 + ); + $DB->insert_record('gradingform_rubric_levels', $rubriclevel1); + $rubriclevel2 = array ( + 'criterionid' => $criterionid1, + 'score' => 10, + 'definition' => 'excellent', + 'definitionformat' => 0 + ); + $DB->insert_record('gradingform_rubric_levels', $rubriclevel2); + + // Create a second criterion with levels. + $rubriccriteria2 = array ( + 'definitionid' => $definitionid, + 'sortorder' => 2, + 'description' => 'Demonstrate an understanding of brucellosis', + 'descriptionformat' => 0 + ); + $criterionid2 = $DB->insert_record('gradingform_rubric_criteria', $rubriccriteria2); + $rubriclevel1 = array ( + 'criterionid' => $criterionid2, + 'score' => 5, + 'definition' => 'pass', + 'definitionformat' => 0 + ); + $DB->insert_record('gradingform_rubric_levels', $rubriclevel1); + $rubriclevel2 = array ( + 'criterionid' => $criterionid2, + 'score' => 10, + 'definition' => 'excellent', + 'definitionformat' => 0 + ); + $DB->insert_record('gradingform_rubric_levels', $rubriclevel2); + + // Call the external function. + $cmids = array ($cm->id); + $areaname = 'submissions'; + $result = core_grade_external::get_definitions($cmids, $areaname); + + $this->assertEquals(1, count($result['areas'])); + $this->assertEquals(1, count($result['areas'][0]['definitions'])); + $definition = $result['areas'][0]['definitions'][0]; + + $this->assertEquals($rubricdefinition['method'], $definition['method']); + $this->assertEquals($USER->id, $definition['usercreated']); + + require_once("$CFG->dirroot/grade/grading/lib.php"); + require_once($CFG->dirroot.'/grade/grading/form/'.$rubricdefinition['method'].'/lib.php'); + + $gradingmanager = get_grading_manager($areaid); + + $this->assertEquals(1, count($definition[$rubricdefinition['method']])); + + $rubricdetails = $definition[$rubricdefinition['method']]; + $details = call_user_func('gradingform_'.$rubricdefinition['method'].'_controller::get_external_definition_details'); + + $this->assertEquals(2, count($rubricdetails[key($details)])); + + $found = false; + foreach ($rubricdetails[key($details)] as $criterion) { + if ($criterion['id'] == $criterionid1) { + $this->assertEquals($rubriccriteria1['description'], $criterion['description']); + $this->assertEquals(2, count($criterion['levels'])); + $found = true; + break; + } + } + $this->assertTrue($found); + } + +} diff --git a/lib/db/services.php b/lib/db/services.php index 725b52aa3db..8cdb3761a7f 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -709,6 +709,16 @@ $functions = array( 'capabilities'=> 'moodle/notes:manage', ), + // === grade related functions === + + 'core_grade_get_definitions' => array( + 'classname' => 'core_grade_external', + 'methodname' => 'get_definitions', + 'classpath' => 'grade/externallib.php', + 'description' => 'Get grading definitions', + 'type' => 'read' + ), + // === webservice related functions === 'moodle_webservice_get_siteinfo' => array(