From 30b54b823a5c499d4f4bdc7aea4fe056c586d8a2 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Thu, 22 Jun 2017 20:33:35 +0100 Subject: [PATCH] MDL-59246 mod_workshop: New WS get_submission_assessments --- mod/workshop/classes/external.php | 200 ++++++++++++++++-- .../classes/external/assessment_exporter.php | 179 ++++++++++++++++ mod/workshop/db/services.php | 7 + mod/workshop/tests/external_test.php | 77 +++++++ mod/workshop/version.php | 2 +- 5 files changed, 448 insertions(+), 17 deletions(-) create mode 100644 mod/workshop/classes/external/assessment_exporter.php diff --git a/mod/workshop/classes/external.php b/mod/workshop/classes/external.php index 2d6522ac23f..3c1e13ff99b 100644 --- a/mod/workshop/classes/external.php +++ b/mod/workshop/classes/external.php @@ -31,6 +31,7 @@ require_once($CFG->dirroot . '/mod/workshop/locallib.php'); use mod_workshop\external\workshop_summary_exporter; use mod_workshop\external\submission_exporter; +use mod_workshop\external\assessment_exporter; /** * Workshop external functions @@ -874,6 +875,36 @@ class mod_workshop_external extends external_api { ); } + /** + * Helper method for validating a submission. + * + * @param stdClass $submission submission object + * @param workshop $workshop workshop instance + * @return void + * @since Moodle 3.4 + */ + protected static function validate_submission($submission, workshop $workshop) { + global $USER; + + $workshopclosed = $workshop->phase == workshop::PHASE_CLOSED; + $canviewpublished = has_capability('mod/workshop:viewpublishedsubmissions', $workshop->context); + + $canview = $submission->authorid == $USER->id; // I did it. + $canview = $canview || !empty($workshop->get_assessment_of_submission_by_user($submission->id, $USER->id)); // I reviewed. + $canview = $canview || has_capability('mod/workshop:viewallsubmissions', $workshop->context); // I can view all. + $canview = $canview || ($submission->published && $workshopclosed && $canviewpublished); // It has been published. + + if ($canview) { + // Here we should check if the user share group. + if ($submission->authorid != $USER->id && + !groups_user_groups_visible($workshop->course, $submission->authorid, $workshop->cm)) { + throw new moodle_exception('notingroup'); + } + } else { + throw new moodle_exception('nopermissions', 'error', '', 'view submission'); + } + } + /** * Returns the description of the external function parameters. * @@ -907,22 +938,7 @@ class mod_workshop_external extends external_api { $submission = $DB->get_record('workshop_submissions', array('id' => $params['submissionid']), '*', MUST_EXIST); list($workshop, $course, $cm, $context) = self::validate_workshop($submission->workshopid); - $workshopclosed = $workshop->phase == workshop::PHASE_CLOSED; - $canviewpublished = has_capability('mod/workshop:viewpublishedsubmissions', $context); - - $canview = $submission->authorid == $USER->id; // I did it. - $canview = $canview || !empty($workshop->get_assessment_of_submission_by_user($submission->id, $USER->id)); // I reviewed. - $canview = $canview || has_capability('mod/workshop:viewallsubmissions', $context); // I can view all. - $canview = $canview || ($submission->published && $workshopclosed && $canviewpublished); // It has been published. - - if ($canview) { - // Here we should check if the user share group. - if ($submission->authorid != $USER->id && !groups_user_groups_visible($course, $submission->authorid, $cm)) { - throw new moodle_exception('notingroup'); - } - } else { - throw new moodle_exception('nopermissions', 'error', '', 'view submission'); - } + self::validate_submission($submission, $workshop); $submission = self::prepare_submission_for_external($submission, $workshop); @@ -948,4 +964,156 @@ class mod_workshop_external extends external_api { ) ); } + + /** + * Helper method for validating if the current user can view the submission assessments. + * + * @param stdClass $submission submission object + * @param workshop $workshop workshop instance + * @return void + * @since Moodle 3.4 + */ + protected static function check_view_submission_assessments($submission, workshop $workshop) { + global $USER; + + $ownsubmission = $submission->authorid == $USER->id; + $canview = has_capability('mod/workshop:viewallassessments', $workshop->context) || + ($ownsubmission && $workshop->assessments_available()); + + if ($canview) { + // Here we should check if the user share group. + if ($submission->authorid != $USER->id && + !groups_user_groups_visible($workshop->course, $submission->authorid, $workshop->cm)) { + throw new moodle_exception('notingroup'); + } + } else { + throw new moodle_exception('nopermissions', 'error', '', 'view assessment'); + } + } + + /** + * Helper method for returning the assessment data according the current user capabilities and current phase. + * + * @param stdClass $assessment the assessment data + * @param workshop $workshop the workshop class + * @return stdClass object with the assessment data filtered or null if is not viewable yet + * @since Moodle 3.4 + */ + protected static function prepare_assessment_for_external($assessment, workshop $workshop) { + global $USER; + static $canviewallassessments = null; + static $canviewreviewers = null; + static $canoverridegrades = null; + + // Remove all the properties that does not belong to the assessment table. + $properties = assessment_exporter::properties_definition(); + foreach ($assessment as $key => $value) { + if (!isset($properties[$key])) { + unset($assessment->{$key}); + } + } + + if (is_null($canviewallassessments)) { + $canviewallassessments = has_capability('mod/workshop:viewallassessments', $workshop->context); + } + if (is_null($canviewreviewers)) { + $canviewreviewers = has_capability('mod/workshop:viewreviewernames', $workshop->context); + } + if (is_null($canoverridegrades)) { + $canoverridegrades = has_capability('mod/workshop:overridegrades', $workshop->context); + } + + $isreviewer = $assessment->reviewerid == $USER->id; + + if (!$isreviewer && is_null($assessment->grade) && !$canviewallassessments) { + // Students do not see peer-assessment that are not graded yet. + return null; + } + + // Remove the feedback for the reviewer if the feedback is not closed or if we don't have enough permissions to see it. + if (!$canoverridegrades && ($workshop->phase != workshop::PHASE_CLOSED || !$isreviewer)) { + // Remove all the feedback information (all the optional fields). + foreach ($properties as $attribute => $settings) { + if (!empty($settings['optional'])) { + unset($assessment->{$attribute}); + } + } + } + + if (!$isreviewer && !$canviewreviewers) { + $assessment->reviewerid = 0; + } + + return $assessment; + } + + /** + * Returns the description of the external function parameters. + * + * @return external_function_parameters + * @since Moodle 3.4 + */ + public static function get_submission_assessments_parameters() { + return new external_function_parameters( + array( + 'submissionid' => new external_value(PARAM_INT, 'Submission id'), + ) + ); + } + + + /** + * Retrieves the given submission assessments. + * + * @param int $submissionid the submission id + * @return array containing the assessments and warnings. + * @since Moodle 3.4 + * @throws moodle_exception + */ + public static function get_submission_assessments($submissionid) { + global $USER, $DB, $PAGE; + + $params = self::validate_parameters(self::get_submission_assessments_parameters(), array('submissionid' => $submissionid)); + $warnings = $assessments = array(); + + // Get and validate the submission and workshop. + $submission = $DB->get_record('workshop_submissions', array('id' => $params['submissionid']), '*', MUST_EXIST); + list($workshop, $course, $cm, $context) = self::validate_workshop($submission->workshopid); + + // Check that we can get the assessments and get them. + self::check_view_submission_assessments($submission, $workshop); + $assessmentsrecords = $workshop->get_assessments_of_submission($submission->id); + + $related = array('context' => $context); + foreach ($assessmentsrecords as $assessment) { + $assessment = self::prepare_assessment_for_external($assessment, $workshop); + if (empty($assessment)) { + continue; + } + $exporter = new assessment_exporter($assessment, $related); + $assessments[] = $exporter->export($PAGE->get_renderer('core')); + } + + return array( + 'assessments' => $assessments, + 'warnings' => $warnings + ); + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.4 + */ + public static function get_submission_assessments_returns() { + return new external_single_structure( + array( + 'assessments' => new external_multiple_structure( + assessment_exporter::get_read_structure() + ), + 'warnings' => new external_warnings() + ) + ); + } } diff --git a/mod/workshop/classes/external/assessment_exporter.php b/mod/workshop/classes/external/assessment_exporter.php new file mode 100644 index 00000000000..9fdfbafa325 --- /dev/null +++ b/mod/workshop/classes/external/assessment_exporter.php @@ -0,0 +1,179 @@ +. + +/** + * Class for exporting assessment data. + * + * @package mod_workshop + * @copyright 2017 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_workshop\external; +defined('MOODLE_INTERNAL') || die(); + +use core\external\exporter; +use renderer_base; +use external_util; +use external_files; + +/** + * Class for exporting assessment data. + * + * @copyright 2017 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class assessment_exporter extends exporter { + + protected static function define_properties() { + + return array( + 'id' => array( + 'type' => PARAM_INT, + 'description' => 'The primary key of the record.', + ), + 'submissionid' => array( + 'type' => PARAM_INT, + 'description' => 'The id of the assessed submission', + ), + 'reviewerid' => array( + 'type' => PARAM_INT, + 'description' => 'The id of the reviewer who makes this assessment', + ), + 'weight' => array( + 'type' => PARAM_INT, + 'default' => 1, + 'description' => 'The weight of the assessment for the purposes of aggregation', + ), + 'timecreated' => array( + 'type' => PARAM_INT, + 'null' => NULL_ALLOWED, + 'default' => 0, + 'description' => 'If 0 then the assessment was allocated but the reviewer has not assessed yet. + If greater than 0 then the timestamp of when the reviewer assessed for the first time', + ), + 'timemodified' => array( + 'type' => PARAM_INT, + 'null' => NULL_ALLOWED, + 'default' => 0, + 'description' => 'If 0 then the assessment was allocated but the reviewer has not assessed yet. + If greater than 0 then the timestamp of when the reviewer assessed for the last time', + ), + 'grade' => array( + 'type' => PARAM_FLOAT, + 'null' => NULL_ALLOWED, + 'description' => 'The aggregated grade for submission suggested by the reviewer. + The grade 0..100 is computed from the values assigned to the assessment dimensions fields. If NULL then it has not been aggregated yet.', + ), + 'gradinggrade' => array( + 'type' => PARAM_FLOAT, + 'null' => NULL_ALLOWED, + 'description' => 'The computed grade 0..100 for this assessment. If NULL then it has not been computed yet.', + ), + 'gradinggradeover' => array( + 'type' => PARAM_FLOAT, + 'null' => NULL_ALLOWED, + 'description' => 'Grade for the assessment manually overridden by a teacher. + Grade is always from interval 0..100. If NULL then the grade is not overriden.', + ), + 'gradinggradeoverby' => array( + 'type' => PARAM_INT, + 'null' => NULL_ALLOWED, + 'description' => 'The id of the user who has overridden the grade for submission.', + ), + 'feedbackauthor' => array( + 'type' => PARAM_RAW, + 'null' => NULL_ALLOWED, + 'description' => 'The comment/feedback from the reviewer for the author.', + ), + 'feedbackauthorformat' => array( + 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), + 'type' => PARAM_INT, + 'default' => FORMAT_MOODLE, + 'description' => 'Feedback text format.', + ), + 'feedbackauthorattachment' => array( + 'type' => PARAM_INT, + 'null' => NULL_ALLOWED, + 'default' => 0, + 'description' => 'Are there some files attached to the feedbackauthor field? + Sets to 1 by file_postupdate_standard_filemanager().', + ), + 'feedbackreviewer' => array( + 'type' => PARAM_RAW, + 'null' => NULL_ALLOWED, + 'description' => 'The comment/feedback from the teacher for the reviewer. + For example the reason why the grade for assessment was overridden', + 'optional' => true, + ), + 'feedbackreviewerformat' => array( + 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), + 'type' => PARAM_INT, + 'default' => FORMAT_MOODLE, + 'description' => 'Feedback text format.', + ), + + 'feedbackauthorformat' => array( + 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), + 'type' => PARAM_INT, + 'default' => FORMAT_MOODLE, + 'description' => 'Feedback text format.', + ), + ); + } + + protected static function define_related() { + return array( + 'context' => 'context' + ); + } + + protected static function define_other_properties() { + return array( + 'feedbackcontentfiles' => array( + 'type' => external_files::get_properties_for_exporter(), + 'multiple' => true, + ), + 'feedbackattachmentfiles' => array( + 'type' => external_files::get_properties_for_exporter(), + 'multiple' => true, + ), + ); + } + + protected function get_other_values(renderer_base $output) { + $context = $this->related['context']; + + $values['feedbackcontentfiles'] = + external_util::get_area_files($context->id, 'mod_workshop', 'overallfeedback_content', $this->data->id); + $values['feedbackattachmentfiles'] = + external_util::get_area_files($context->id, 'mod_workshop', 'overallfeedback_attachment', $this->data->id); + + return $values; + } + + /** + * Get the formatting parameters for the content. + * + * @return array + */ + protected function get_format_parameters_for_feedbackauthor() { + return [ + 'component' => 'mod_workshop', + 'filearea' => 'overallfeedback_content', + 'itemid' => $this->data->id, + ]; + } +} diff --git a/mod/workshop/db/services.php b/mod/workshop/db/services.php index 9f0bd6be647..497b793dac5 100644 --- a/mod/workshop/db/services.php +++ b/mod/workshop/db/services.php @@ -99,4 +99,11 @@ $functions = array( 'type' => 'read', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) ), + 'mod_workshop_get_submission_assessments' => array( + 'classname' => 'mod_workshop_external', + 'methodname' => 'get_submission_assessments', + 'description' => 'Retrieves all the assessments of the given submission.', + 'type' => 'read', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) + ), ); diff --git a/mod/workshop/tests/external_test.php b/mod/workshop/tests/external_test.php index f5461798618..075f31b3902 100644 --- a/mod/workshop/tests/external_test.php +++ b/mod/workshop/tests/external_test.php @@ -1065,4 +1065,81 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $result = external_api::clean_returnvalue(mod_workshop_external::get_submission_returns(), $result); $this->assertEquals($submissionid3, $result['submission']['id']); } + + + /** + * Test get_submission_assessments_student. + */ + public function test_get_submission_assessments_student() { + global $DB; + + // Create the submission that will be deleted. + $submissionid = $this->create_test_submission($this->student); + + $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); + $workshopgenerator->create_assessment($submissionid, $this->anotherstudentg1->id, array( + 'weight' => 3, + 'grade' => 95, + )); + $workshopgenerator->create_assessment($submissionid, $this->student->id, array( + 'weight' => 2, + 'grade' => 90, + )); + + $DB->set_field('workshop', 'phase', workshop::PHASE_CLOSED, array('id' => $this->workshop->id)); + $this->setUser($this->student); + $result = mod_workshop_external::get_submission_assessments($submissionid); + $result = external_api::clean_returnvalue(mod_workshop_external::get_submission_assessments_returns(), $result); + $this->assertCount(2, $result['assessments']); // I received my two assessments. + foreach ($result['assessments'] as $assessment) { + if ($assessment['grade'] == 90) { + // My own assessment, I can see me. + $this->assertEquals($this->student->id, $assessment['reviewerid']); + } else { + // Student's can't see who did the review. + $this->assertEquals(0, $assessment['reviewerid']); + } + } + } + + /** + * Test get_submission_assessments_invalid_phase. + */ + public function test_get_submission_assessments_invalid_phase() { + global $DB; + + // Create the submission that will be deleted. + $submissionid = $this->create_test_submission($this->student); + + $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); + $workshopgenerator->create_assessment($submissionid, $this->anotherstudentg1->id, array( + 'weight' => 3, + 'grade' => 95, + )); + + $this->expectException('moodle_exception'); + mod_workshop_external::get_submission_assessments($submissionid); + } + + /** + * Test get_submission_assessments_teacher. + */ + public function test_get_submission_assessments_teacher() { + + // Create the submission that will be deleted. + $submissionid = $this->create_test_submission($this->student); + + $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); + $assessmentid = $workshopgenerator->create_assessment($submissionid, $this->anotherstudentg1->id, array( + 'weight' => 1, + 'grade' => 50, + )); + + $this->setUser($this->teacher); + $result = mod_workshop_external::get_submission_assessments($submissionid); + $result = external_api::clean_returnvalue(mod_workshop_external::get_submission_assessments_returns(), $result); + $this->assertCount(1, $result['assessments']); + $this->assertEquals(50, $result['assessments'][0]['grade']); + $this->assertEquals($assessmentid, $result['assessments'][0]['id']); + } } diff --git a/mod/workshop/version.php b/mod/workshop/version.php index c319cc26aff..12fb42529bb 100644 --- a/mod/workshop/version.php +++ b/mod/workshop/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017051509; // The current module version (YYYYMMDDXX) +$plugin->version = 2017051510; // The current module version (YYYYMMDDXX) $plugin->requires = 2017050500; // Requires this Moodle version. $plugin->component = 'mod_workshop'; $plugin->cron = 60; // Give as a chance every minute.