diff --git a/mod/workshop/assessment.php b/mod/workshop/assessment.php index df504ff04eb..a4efdc77092 100644 --- a/mod/workshop/assessment.php +++ b/mod/workshop/assessment.php @@ -69,31 +69,15 @@ if ($isreviewer and $workshop->assessing_allowed($USER->id)) { } // check that all required examples have been assessed by the user -if ($assessmenteditable and $workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_ASSESSMENT - and !has_capability('mod/workshop:manageexamples', $workshop->context)) { - // the reviewer must have submitted their own submission - $reviewersubmission = $workshop->get_submission_by_author($assessment->reviewerid); - $output = $PAGE->get_renderer('mod_workshop'); - if (!$reviewersubmission) { - // no money, no love - $assessmenteditable = false; +if ($assessmenteditable) { + + list($assessed, $notice) = $workshop->check_examples_assessed_before_assessment($assessment->reviewerid); + if (!$assessed) { echo $output->header(); echo $output->heading(format_string($workshop->name)); - notice(get_string('exampleneedsubmission', 'workshop'), new moodle_url('/mod/workshop/view.php', array('id' => $cm->id))); + notice(get_string($notice, 'workshop'), new moodle_url('/mod/workshop/view.php', array('id' => $cm->id))); echo $output->footer(); exit; - } else { - $examples = $workshop->get_examples_for_reviewer($assessment->reviewerid); - foreach ($examples as $exampleid => $example) { - if (is_null($example->grade)) { - $assessmenteditable = false; - echo $output->header(); - echo $output->heading(format_string($workshop->name)); - notice(get_string('exampleneedassessed', 'workshop'), new moodle_url('/mod/workshop/view.php', array('id' => $cm->id))); - echo $output->footer(); - exit; - } - } } } diff --git a/mod/workshop/classes/external.php b/mod/workshop/classes/external.php index 0d5743935bf..6a94cf10dc6 100644 --- a/mod/workshop/classes/external.php +++ b/mod/workshop/classes/external.php @@ -199,7 +199,8 @@ class mod_workshop_external extends external_api { if (is_null($result['assessingexamplesallowed'])) { $result['assessingexamplesallowed'] = false; } - $result['examplesassessed'] = $workshop->check_examples_assessed($USER->id); + $result['examplesassessedbeforesubmission'] = $workshop->check_examples_assessed_before_submission($USER->id); + list($result['examplesassessedbeforeassessment'], $code) = $workshop->check_examples_assessed_before_assessment($USER->id); $result['warnings'] = array(); return $result; @@ -222,8 +223,12 @@ class mod_workshop_external extends external_api { 'Is the user allowed to create/edit his assessments?'), 'assessingexamplesallowed' => new external_value(PARAM_BOOL, 'Are reviewers allowed to create/edit their assessments of the example submissions?.'), - 'examplesassessed' => new external_value(PARAM_BOOL, - 'Whether the given user has assessed all his required examples (always true if there are no examples to assess).'), + 'examplesassessedbeforesubmission' => new external_value(PARAM_BOOL, + 'Whether the given user has assessed all his required examples before submission + (always true if there are not examples to assess or not configured to check before submission).'), + 'examplesassessedbeforeassessment' => new external_value(PARAM_BOOL, + 'Whether the given user has assessed all his required examples before assessment + (always true if there are not examples to assessor not configured to check before assessment).'), 'warnings' => new external_warnings() ); @@ -476,7 +481,7 @@ class mod_workshop_external extends external_api { // Check if we can submit now. $canaddsubmission = $workshop->creating_submission_allowed($USER->id); - $canaddsubmission = $canaddsubmission && $workshop->check_examples_assessed($USER->id); + $canaddsubmission = $canaddsubmission && $workshop->check_examples_assessed_before_submission($USER->id); if (!$canaddsubmission) { throw new moodle_exception('nopermissions', 'error', '', 'add submission'); } @@ -592,7 +597,7 @@ class mod_workshop_external extends external_api { // Check if we can update the submission. $canupdatesubmission = $submission->authorid == $USER->id; $canupdatesubmission = $canupdatesubmission && $workshop->modifying_submission_allowed($USER->id); - $canupdatesubmission = $canupdatesubmission && $workshop->check_examples_assessed($USER->id); + $canupdatesubmission = $canupdatesubmission && $workshop->check_examples_assessed_before_submission($USER->id); if (!$canupdatesubmission) { throw new moodle_exception('nopermissions', 'error', '', 'update submission'); } @@ -1182,4 +1187,137 @@ class mod_workshop_external extends external_api { ) ); } + + /** + * Returns the description of the external function parameters. + * + * @return external_function_parameters + * @since Moodle 3.4 + */ + public static function get_assessment_form_definition_parameters() { + return new external_function_parameters( + array( + 'assessmentid' => new external_value(PARAM_INT, 'Assessment id'), + 'mode' => new external_value(PARAM_ALPHA, 'The form mode (assessment or preview)', VALUE_DEFAULT, 'assessment'), + ) + ); + } + + + /** + * Retrieves the assessment form definition (data required to be able to display the assessment form). + * + * @param int $assessmentid the assessment id + * @param string $mode the form mode (assessment or preview) + * @return array containing the assessment and warnings. + * @since Moodle 3.4 + * @throws moodle_exception + */ + public static function get_assessment_form_definition($assessmentid, $mode = 'assessment') { + global $DB, $USER; + + $params = self::validate_parameters( + self::get_assessment_form_definition_parameters(), array('assessmentid' => $assessmentid, 'mode' => $mode) + ); + $warnings = $pending = array(); + + if ($params['mode'] != 'assessment' && $params['mode'] != 'preview') { + throw new invalid_parameter_exception('Invalid value for mode parameter (value: ' . $params['mode'] . ')'); + } + + // Get and validate the assessment, submission and workshop. + $assessment = $DB->get_record('workshop_assessments', array('id' => $params['assessmentid']), '*', MUST_EXIST); + $submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid), '*', MUST_EXIST); + list($workshop, $course, $cm, $context) = self::validate_workshop($submission->workshopid); + + // Check we can edit the assessment (so we can get the form data). + $workshop->check_edit_assessment($assessment, $submission); + + $cansetassessmentweight = has_capability('mod/workshop:allocate', $context); + $pending = $workshop->get_pending_assessments_by_reviewer($assessment->reviewerid, $assessment->id); + + // Retrieve the data from the strategy plugin. + $strategy = $workshop->grading_strategy_instance(); + $strategyname = str_replace('_strategy', '', get_class($strategy)); // Get strategy name. + $mform = $strategy->get_assessment_form(null, $params['mode'], $assessment, true, + array('editableweight' => $cansetassessmentweight, 'pending' => !empty($pending))); + $formdata = $mform->get_customdata(); + + $result = array( + 'dimenssionscount' => $formdata['nodims'], + 'descriptionfiles' => external_util::get_area_files($context->id, $strategyname, 'description'), + 'warnings' => $warnings + ); + // Include missing dimension fields. + for ($i = 0; $i < $formdata['nodims']; $i++) { + $formdata['fields']->{'gradeid__idx_' . $i} = 0; + $formdata['fields']->{'peercomment__idx_' . $i} = ''; + } + + // Convert all the form data for external. + foreach (array('options', 'fields', 'current') as $typeofdata) { + $result[$typeofdata] = array(); + + if (!empty($formdata[$typeofdata])) { + $alldata = (array) $formdata[$typeofdata]; + foreach ($alldata as $key => $val) { + if (strpos($key, 'peercomment__idx_') === 0) { + // Format reviewer comment. + list($val, $format) = external_format_text($val, FORMAT_MOODLE, $context->id); + } else if (strpos($key, 'description__idx_')) { + // Format dimension description. + $id = str_replace('description__idx_', '', $key); + list($val, $format) = external_format_text($val, $alldata['dimensionid__idx_' . $id . 'format'], + $context->id, $strategyname, 'description', $alldata['dimensionid__idx_' . $id]); + } + $result[$typeofdata][] = array( + 'name' => $key, + 'value' => $val + ); + } + } + } + + return $result; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.4 + */ + public static function get_assessment_form_definition_returns() { + return new external_single_structure( + array( + 'dimenssionscount' => new external_value(PARAM_INT, 'The number of dimenssions used by the form.'), + 'descriptionfiles' => new external_files('Files in the description text'), + 'options' => new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_ALPHANUMEXT, 'Option name.'), + 'value' => new external_value(PARAM_NOTAGS, 'Option value.') + ) + ), 'The form options.' + ), + 'fields' => new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_ALPHANUMEXT, 'Field name.'), + 'value' => new external_value(PARAM_RAW, 'Field default value.') + ) + ), 'The form fields.' + ), + 'current' => new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_ALPHANUMEXT, 'Field name.'), + 'value' => new external_value(PARAM_RAW, 'Current field value.') + ) + ), 'The current field values.' + ), + 'warnings' => new external_warnings() + ) + ); + } } diff --git a/mod/workshop/db/services.php b/mod/workshop/db/services.php index 341e017b662..94b4c19a68d 100644 --- a/mod/workshop/db/services.php +++ b/mod/workshop/db/services.php @@ -113,4 +113,11 @@ $functions = array( 'type' => 'read', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) ), + 'mod_workshop_get_assessment_form_definition' => array( + 'classname' => 'mod_workshop_external', + 'methodname' => 'get_assessment_form_definition', + 'description' => 'Retrieves the assessment form definition.', + 'type' => 'read', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) + ), ); diff --git a/mod/workshop/form/assessment_form.php b/mod/workshop/form/assessment_form.php index 56128dcf5b3..4f7ba99b1da 100644 --- a/mod/workshop/form/assessment_form.php +++ b/mod/workshop/form/assessment_form.php @@ -120,4 +120,14 @@ class workshop_assessment_form extends moodleform { public function is_editable() { return !$this->_form->isFrozen(); } + + /** + * Return the form custom data. + * + * @return array an array containing the custom data + * @since Moodle 3.4 + */ + public function get_customdata() { + return $this->_customdata; + } } diff --git a/mod/workshop/locallib.php b/mod/workshop/locallib.php index 0a6cb3c9bf9..ac243f238bb 100644 --- a/mod/workshop/locallib.php +++ b/mod/workshop/locallib.php @@ -2767,13 +2767,13 @@ class workshop { } /** - * Check whether the given user has assessed all his required examples. + * Check whether the given user has assessed all his required examples before submission. * * @param int $userid the user to check * @return bool false if there are examples missing assessment, true otherwise. * @since Moodle 3.4 */ - public function check_examples_assessed($userid) { + public function check_examples_assessed_before_submission($userid) { if ($this->useexamples and $this->examplesmode == self::EXAMPLES_BEFORE_SUBMISSION and !has_capability('mod/workshop:manageexamples', $this->context)) { @@ -2792,6 +2792,35 @@ class workshop { return true; } + /** + * Check that all required examples have been assessed by the given user. + * + * @param stdClass $userid the user (reviewer) to check + * @return mixed bool|state false and notice code if there are examples missing assessment, true otherwise. + * @since Moodle 3.4 + */ + public function check_examples_assessed_before_assessment($userid) { + + if ($this->useexamples and $this->examplesmode == self::EXAMPLES_BEFORE_ASSESSMENT + and !has_capability('mod/workshop:manageexamples', $this->context)) { + + // The reviewer must have submitted their own submission. + $reviewersubmission = $this->get_submission_by_author($userid); + if (!$reviewersubmission) { + // No money, no love. + return array(false, 'exampleneedsubmission'); + } else { + $examples = $this->get_examples_for_reviewer($userid); + foreach ($examples as $exampleid => $example) { + if (is_null($example->grade)) { + return array(false, 'exampleneedassessed'); + } + } + } + } + return array(true, null); + } + /** * Trigger module viewed event and set the module viewed for completion. * @@ -2969,6 +2998,33 @@ class workshop { } } + /** + * Helper method for validating if the current user can edit the given assessment. + * + * @param stdClass $assessment assessment object + * @param stdClass $submission submission object + * @return void + * @throws moodle_exception + * @since Moodle 3.4 + */ + public function check_edit_assessment($assessment, $submission) { + global $USER; + + $this->check_view_assessment($assessment, $submission); + // Further checks. + $isreviewer = ($USER->id == $assessment->reviewerid); + + $assessmenteditable = $isreviewer && $this->assessing_allowed($USER->id); + if (!$assessmenteditable) { + throw new moodle_exception('nopermissions', 'error', '', 'edit assessments'); + } + + list($assessed, $notice) = $this->check_examples_assessed_before_assessment($assessment->reviewerid); + if (!$assessed) { + throw new moodle_exception($notice, 'mod_workshop'); + } + } + //////////////////////////////////////////////////////////////////////////////// // Internal methods (implementation details) // //////////////////////////////////////////////////////////////////////////////// diff --git a/mod/workshop/submission.php b/mod/workshop/submission.php index 0772b96c11f..f93967ba683 100644 --- a/mod/workshop/submission.php +++ b/mod/workshop/submission.php @@ -103,7 +103,7 @@ if ($submission->id and !$workshop->modifying_submission_allowed($USER->id)) { $canviewall = $canviewall && $workshop->check_group_membership($submission->authorid); -$editable = $editable && $workshop->check_examples_assessed($USER->id); +$editable = ($editable && $workshop->check_examples_assessed_before_submission($USER->id)); $edit = ($editable and $edit); if (!$candeleteall and $ownsubmission and $editable) { diff --git a/mod/workshop/tests/external_test.php b/mod/workshop/tests/external_test.php index 17e30e3ca7c..c319b0a8841 100644 --- a/mod/workshop/tests/external_test.php +++ b/mod/workshop/tests/external_test.php @@ -79,6 +79,20 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $this->context = context_module::instance($this->workshop->cmid); $this->cm = get_coursemodule_from_instance('workshop', $this->workshop->id); + // Add grading strategy data (accumulative is the default). + $workshop = new workshop($this->workshop, $this->cm, $this->course); + $strategy = $workshop->grading_strategy_instance(); + $data = array(); + for ($i = 0; $i < 4; $i++) { + $data['dimensionid__idx_'.$i] = 0; + $data['description__idx_'.$i.'_editor'] = array('text' => "Content $i", 'format' => FORMAT_MOODLE); + $data['grade__idx_'.$i] = 25; + $data['weight__idx_'.$i] = 25; + } + $data['workshopid'] = $workshop->id; + $data['norepeats'] = 4; + $strategy->save_edit_strategy_form((object) $data); + // Create users. $this->student = self::getDataGenerator()->create_user(); $this->anotherstudentg1 = self::getDataGenerator()->create_user(); @@ -242,7 +256,8 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $this->assertFalse($result['modifyingsubmissionallowed']); $this->assertFalse($result['assessingallowed']); $this->assertFalse($result['assessingexamplesallowed']); - $this->assertTrue($result['examplesassessed']); + $this->assertTrue($result['examplesassessedbeforesubmission']); + $this->assertTrue($result['examplesassessedbeforeassessment']); // Switch phase. $workshop = new workshop($this->workshop, $this->cm, $this->course); @@ -254,7 +269,8 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $this->assertTrue($result['modifyingsubmissionallowed']); $this->assertFalse($result['assessingallowed']); $this->assertFalse($result['assessingexamplesallowed']); - $this->assertTrue($result['examplesassessed']); + $this->assertTrue($result['examplesassessedbeforesubmission']); + $this->assertTrue($result['examplesassessedbeforeassessment']); // Switch to next (to assessment). $workshop = new workshop($this->workshop, $this->cm, $this->course); @@ -266,7 +282,8 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $this->assertFalse($result['modifyingsubmissionallowed']); $this->assertTrue($result['assessingallowed']); $this->assertFalse($result['assessingexamplesallowed']); - $this->assertTrue($result['examplesassessed']); + $this->assertTrue($result['examplesassessedbeforesubmission']); + $this->assertTrue($result['examplesassessedbeforeassessment']); } /** @@ -340,7 +357,7 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $this->assertCount(4, $result['userplan']['phases'][0]['tasks']); // For new empty workshops, always 4 tasks. foreach ($result['userplan']['phases'][0]['tasks'] as $task) { - if ($task['code'] == 'intro' || $task['code'] == 'instructauthors') { + if ($task['code'] == 'intro' || $task['code'] == 'instructauthors' || $task['code'] == 'editform') { $this->assertEquals(1, $task['completed']); } else { $this->assertEmpty($task['completed']); @@ -354,7 +371,8 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $result = mod_workshop_external::get_user_plan($this->workshop->id); $result = external_api::clean_returnvalue(mod_workshop_external::get_user_plan_returns(), $result); foreach ($result['userplan']['phases'][0]['tasks'] as $task) { - if ($task['code'] == 'intro' || $task['code'] == 'instructauthors' || $task['code'] == 'switchtonextphase') { + if ($task['code'] == 'intro' || $task['code'] == 'instructauthors' || $task['code'] == 'editform' || + $task['code'] == 'switchtonextphase') { $this->assertEquals(1, $task['completed']); } else { $this->assertEmpty($task['completed']); @@ -1263,4 +1281,69 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase { $this->setExpectedException('moodle_exception'); mod_workshop_external::get_assessment($assessmentid); } + + /** + * Test get_assessment_form_definition_reviewer_new_assessment. + */ + public function test_get_assessment_form_definition_reviewer_new_assessment() { + global $DB; + + // Create the submission. + $submissionid = $this->create_test_submission($this->anotherstudentg1); + + $workshop = new workshop($this->workshop, $this->cm, $this->course); + $submission = $workshop->get_submission_by_id($submissionid); + $assessmentid = $workshop->add_allocation($submission, $this->student->id); + + // Switch to assessment phase. + $DB->set_field('workshop', 'phase', workshop::PHASE_ASSESSMENT, array('id' => $this->workshop->id)); + $this->setUser($this->student); + $result = mod_workshop_external::get_assessment_form_definition($assessmentid); + $result = external_api::clean_returnvalue(mod_workshop_external::get_assessment_form_definition_returns(), $result); + $this->assertEquals(4, $result['dimenssionscount']); // We receive the expected 4 dimensions. + $this->assertEmpty($result['current']); // Assessment not yet done. + foreach ($result['fields'] as $field) { + if (strpos($field['name'], 'grade__idx_') === 0) { + $this->assertEquals(25, $field['value']); // Check one of the dimension fields attributes. + } + } + } + + /** + * Test get_assessment_form_definition_teacher_new_assessment. + */ + public function test_get_assessment_form_definition_teacher_new_assessment() { + global $DB; + + // Create the submission. + $submissionid = $this->create_test_submission($this->anotherstudentg1); + + $workshop = new workshop($this->workshop, $this->cm, $this->course); + $submission = $workshop->get_submission_by_id($submissionid); + $assessmentid = $workshop->add_allocation($submission, $this->student->id); + + // Switch to assessment phase. + $DB->set_field('workshop', 'phase', workshop::PHASE_ASSESSMENT, array('id' => $this->workshop->id)); + $this->setUser($this->teacher); + $this->setExpectedException('moodle_exception'); + mod_workshop_external::get_assessment_form_definition($assessmentid); // Teachers can't add/edit assessments. + } + + /** + * Test get_assessment_form_definition_invalid_phase. + */ + public function test_get_assessment_form_definition_invalid_phase() { + global $DB; + + // Create the submission. + $submissionid = $this->create_test_submission($this->anotherstudentg1); + + $workshop = new workshop($this->workshop, $this->cm, $this->course); + $submission = $workshop->get_submission_by_id($submissionid); + $assessmentid = $workshop->add_allocation($submission, $this->student->id); + + $this->setUser($this->student); + $this->setExpectedException('moodle_exception'); + mod_workshop_external::get_assessment_form_definition($assessmentid); + } } diff --git a/mod/workshop/version.php b/mod/workshop/version.php index 487445b0a54..6bb09d7d69d 100644 --- a/mod/workshop/version.php +++ b/mod/workshop/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017051511; // The current module version (YYYYMMDDXX) +$plugin->version = 2017051512; // 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.