use mod_workshop\external\workshop_summary_exporter;
use mod_workshop\external\submission_exporter;
+use mod_workshop\external\assessment_exporter;
/**
* Workshop external functions
);
}
+ /**
+ * 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.
*
$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);
+ self::validate_submission($submission, $workshop);
- $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.
+ $submission = self::prepare_submission_for_external($submission, $workshop);
+
+ $related = array('context' => $context);
+ $exporter = new submission_exporter($submission, $related);
+ return array(
+ 'submission' => $exporter->export($PAGE->get_renderer('core')),
+ 'warnings' => $warnings
+ );
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 3.4
+ */
+ public static function get_submission_returns() {
+ return new external_single_structure(
+ array(
+ 'submission' => submission_exporter::get_read_structure(),
+ 'warnings' => new external_warnings()
+ )
+ );
+ }
+
+ /**
+ * 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($course, $submission->authorid, $cm)) {
+ 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');
+ throw new moodle_exception('nopermissions', 'error', '', 'view assessment');
}
+ }
- $submission = self::prepare_submission_for_external($submission, $workshop);
+ /**
+ * 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);
- $exporter = new submission_exporter($submission, $related);
+ 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(
- 'submission' => $exporter->export($PAGE->get_renderer('core')),
+ 'assessments' => $assessments,
'warnings' => $warnings
);
}
* @return external_description
* @since Moodle 3.4
*/
- public static function get_submission_returns() {
+ public static function get_submission_assessments_returns() {
return new external_single_structure(
array(
- 'submission' => submission_exporter::get_read_structure(),
+ 'assessments' => new external_multiple_structure(
+ assessment_exporter::get_read_structure()
+ ),
'warnings' => new external_warnings()
)
);
--- /dev/null
+<?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 for exporting assessment data.
+ *
+ * @package mod_workshop
+ * @copyright 2017 Juan Leyva <juan@moodle.com>
+ * @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 <juan@moodle.com>
+ * @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,
+ ];
+ }
+}
'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)
+ ),
);
$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']);
+ }
}
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.