MDL-59246 mod_workshop: New WS get_submission_assessments
authorJuan Leyva <juanleyvadelgado@gmail.com>
Thu, 22 Jun 2017 19:33:35 +0000 (20:33 +0100)
committerJuan Leyva <juanleyvadelgado@gmail.com>
Mon, 2 Oct 2017 07:36:36 +0000 (09:36 +0200)
mod/workshop/classes/external.php
mod/workshop/classes/external/assessment_exporter.php [new file with mode: 0644]
mod/workshop/db/services.php
mod/workshop/tests/external_test.php
mod/workshop/version.php

index 2d6522a..3c1e13f 100644 (file)
@@ -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,29 +938,164 @@ 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);
+        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
         );
     }
@@ -940,10 +1106,12 @@ class mod_workshop_external extends external_api {
      * @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()
             )
         );
diff --git a/mod/workshop/classes/external/assessment_exporter.php b/mod/workshop/classes/external/assessment_exporter.php
new file mode 100644 (file)
index 0000000..9fdfbaf
--- /dev/null
@@ -0,0 +1,179 @@
+<?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,
+        ];
+    }
+}
index 9f0bd6b..497b793 100644 (file)
@@ -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)
+    ),
 );
index f546179..075f31b 100644 (file)
@@ -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']);
+    }
 }
index c319cc2..12fb425 100644 (file)
@@ -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.