Merge branch 'MDL-59243-master' of git://github.com/jleyva/moodle

This commit is contained in:
Jun Pataleta 2017-09-12 16:29:43 +08:00
commit d887e98b56
7 changed files with 397 additions and 106 deletions

View File

@ -197,6 +197,7 @@ class mod_workshop_external extends external_api {
if (is_null($result['assessingexamplesallowed'])) {
$result['assessingexamplesallowed'] = false;
}
$result['examplesassessed'] = $workshop->check_examples_assessed($USER->id);
$result['warnings'] = array();
return $result;
@ -219,6 +220,8 @@ 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).'),
'warnings' => new external_warnings()
);
@ -419,4 +422,112 @@ 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 add_submission_parameters() {
return new external_function_parameters(array(
'workshopid' => new external_value(PARAM_INT, 'Workshop id'),
'title' => new external_value(PARAM_TEXT, 'Submission title'),
'content' => new external_value(PARAM_RAW, 'Submission text content', VALUE_DEFAULT, ''),
'contentformat' => new external_value(PARAM_INT, 'The format used for the content', VALUE_DEFAULT, FORMAT_MOODLE),
'inlineattachmentsid' => new external_value(PARAM_INT, 'The draft file area id for inline attachments in the content',
VALUE_DEFAULT, 0),
'attachmentsid' => new external_value(PARAM_INT, 'The draft file area id for attachments', VALUE_DEFAULT, 0),
));
}
/**
* Add a new submission to a given workshop.
*
* @param int $workshopid the workshop id
* @param string $title the submission title
* @param string $content the submission text content
* @param int $contentformat the format used for the content
* @param int $inlineattachmentsid the draft file area id for inline attachments in the content
* @param int $attachmentsid the draft file area id for attachments
* @return array Containing the new created submission id and warnings.
* @since Moodle 3.4
* @throws moodle_exception
*/
public static function add_submission($workshopid, $title, $content = '', $contentformat = FORMAT_MOODLE,
$inlineattachmentsid = 0, $attachmentsid = 0) {
global $USER;
$params = self::validate_parameters(self::add_submission_parameters(), array(
'workshopid' => $workshopid,
'title' => $title,
'content' => $content,
'contentformat' => $contentformat,
'inlineattachmentsid' => $inlineattachmentsid,
'attachmentsid' => $attachmentsid,
));
$warnings = array();
// Get and validate the workshop.
list($workshop, $course, $cm, $context) = self::validate_workshop($params['workshopid']);
require_capability('mod/workshop:submit', $context);
// Check if we can submit now.
$canaddsubmission = $workshop->creating_submission_allowed($USER->id);
$canaddsubmission = $canaddsubmission && $workshop->check_examples_assessed($USER->id);
if (!$canaddsubmission) {
throw new moodle_exception('nopermissions', 'error', '', 'add submission');
}
// Prepare the submission object.
$submission = new stdClass;
$submission->id = null;
$submission->cmid = $cm->id;
$submission->example = 0;
$submission->title = trim($params['title']);
$submission->content_editor = array(
'text' => $params['content'],
'format' => $params['contentformat'],
'itemid' => $params['inlineattachmentsid'],
);
$submission->attachment_filemanager = $params['attachmentsid'];
if (empty($submission->title)) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'title');
}
$errors = $workshop->validate_submission_data((array) $submission);
// We can get several errors, return them in warnings.
if (!empty($errors)) {
$submission->id = 0;
foreach ($errors as $itemname => $message) {
$warnings[] = array(
'item' => $itemname,
'itemid' => 0,
'warningcode' => 'fielderror',
'message' => s($message)
);
}
} else {
$submission->id = $workshop->edit_submission($submission);
}
return array(
'submissionid' => $submission->id,
'warnings' => $warnings
);
}
/**
* Returns the description of the external function return value.
*
* @return external_description
* @since Moodle 3.4
*/
public static function add_submission_returns() {
return new external_single_structure(array(
'submissionid' => new external_value(PARAM_INT, 'New workshop submission id (0 if it wasn\'t created).'),
'warnings' => new external_warnings()
));
}
}

View File

@ -61,4 +61,12 @@ $functions = array(
'capabilities' => 'mod/workshop:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_add_submission' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'add_submission',
'description' => 'Add a new submission to a given workshop.',
'type' => 'write',
'capabilities' => 'mod/workshop:submit',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
);

View File

@ -2689,6 +2689,32 @@ class workshop {
}
}
/**
* Check whether the given user has assessed all his required examples.
*
* @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) {
if ($this->useexamples and $this->examplesmode == self::EXAMPLES_BEFORE_SUBMISSION
and !has_capability('mod/workshop:manageexamples', $this->context)) {
// Check that all required examples have been assessed by the user.
$examples = $this->get_examples_for_reviewer($userid);
foreach ($examples as $exampleid => $example) {
if (is_null($example->assessmentid)) {
$examples[$exampleid]->assessmentid = $this->add_allocation($example, $userid, 0);
}
if (is_null($example->grade)) {
return false;
}
}
}
return true;
}
/**
* Trigger module viewed event and set the module viewed for completion.
*
@ -2714,6 +2740,129 @@ class workshop {
$event->trigger();
}
/**
* Validates the submission form or WS data.
*
* @param array $data the data to be validated
* @return array the validation errors (if any)
* @since Moodle 3.4
*/
public function validate_submission_data($data) {
global $DB, $USER;
$errors = array();
if (empty($data['id']) and empty($data['example'])) {
// Make sure there is no submission saved meanwhile from another browser window.
$sql = "SELECT COUNT(s.id)
FROM {workshop_submissions} s
JOIN {workshop} w ON (s.workshopid = w.id)
JOIN {course_modules} cm ON (w.id = cm.instance)
JOIN {modules} m ON (m.name = 'workshop' AND m.id = cm.module)
WHERE cm.id = ? AND s.authorid = ? AND s.example = 0";
if ($DB->count_records_sql($sql, array($data['cmid'], $USER->id))) {
$errors['title'] = get_string('err_multiplesubmissions', 'mod_workshop');
}
}
$getfiles = file_get_drafarea_files($data['attachment_filemanager']);
if (empty($getfiles->list) and html_is_blank($data['content_editor']['text'])) {
$errors['content_editor'] = get_string('submissionrequiredcontent', 'mod_workshop');
$errors['attachment_filemanager'] = get_string('submissionrequiredfile', 'mod_workshop');
}
return $errors;
}
/**
* Adds or updates a submission.
*
* @param stdClass $submission The submissin data (via form or via WS).
* @return the new or updated submission id.
* @since Moodle 3.4
*/
public function edit_submission($submission) {
global $USER, $DB;
if ($submission->example == 0) {
// This was used just for validation, it must be set to zero when dealing with normal submissions.
unset($submission->example);
} else {
throw new coding_exception('Invalid submission form data value: example');
}
$timenow = time();
if (is_null($submission->id)) {
$submission->workshopid = $this->id;
$submission->example = 0;
$submission->authorid = $USER->id;
$submission->timecreated = $timenow;
$submission->feedbackauthorformat = editors_get_preferred_format();
}
$submission->timemodified = $timenow;
$submission->title = trim($submission->title);
$submission->content = ''; // Updated later.
$submission->contentformat = FORMAT_HTML; // Updated later.
$submission->contenttrust = 0; // Updated later.
$submission->late = 0x0; // Bit mask.
if (!empty($this->submissionend) and ($this->submissionend < time())) {
$submission->late = $submission->late | 0x1;
}
if ($this->phase == self::PHASE_ASSESSMENT) {
$submission->late = $submission->late | 0x2;
}
// Event information.
$params = array(
'context' => $this->context,
'courseid' => $this->course->id,
'other' => array(
'submissiontitle' => $submission->title
)
);
$logdata = null;
if (is_null($submission->id)) {
$submission->id = $DB->insert_record('workshop_submissions', $submission);
$params['objectid'] = $submission->id;
$event = \mod_workshop\event\submission_created::create($params);
$event->trigger();
} else {
if (empty($submission->id) or empty($submission->id) or ($submission->id != $submission->id)) {
throw new moodle_exception('err_submissionid', 'workshop');
}
}
$params['objectid'] = $submission->id;
// Save and relink embedded images and save attachments.
$submission = file_postupdate_standard_editor($submission, 'content', $this->submission_content_options(),
$this->context, 'mod_workshop', 'submission_content', $submission->id);
$submission = file_postupdate_standard_filemanager($submission, 'attachment', $this->submission_attachment_options(),
$this->context, 'mod_workshop', 'submission_attachment', $submission->id);
if (empty($submission->attachment)) {
// Explicit cast to zero integer.
$submission->attachment = 0;
}
// Store the updated values or re-save the new submission (re-saving needed because URLs are now rewritten).
$DB->update_record('workshop_submissions', $submission);
$event = \mod_workshop\event\submission_updated::create($params);
$event->add_record_snapshot('workshop', $this->dbrecord);
$event->trigger();
// Send submitted content for plagiarism detection.
$fs = get_file_storage();
$files = $fs->get_area_files($this->context->id, 'mod_workshop', 'submission_attachment', $submission->id);
$params['other']['content'] = $submission->content;
$params['other']['pathnamehashes'] = array_keys($files);
$event = \mod_workshop\event\assessable_uploaded::create($params);
$event->set_legacy_logdata($logdata);
$event->trigger();
return $submission->id;
}
////////////////////////////////////////////////////////////////////////////////
// Internal methods (implementation details) //
////////////////////////////////////////////////////////////////////////////////

View File

@ -103,17 +103,7 @@ if ($submission->id and !$workshop->modifying_submission_allowed($USER->id)) {
$canviewall = $canviewall && $workshop->check_group_membership($submission->authorid);
if ($editable and $workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_SUBMISSION
and !has_capability('mod/workshop:manageexamples', $workshop->context)) {
// check that all required examples have been assessed by the user
$examples = $workshop->get_examples_for_reviewer($USER->id);
foreach ($examples as $exampleid => $example) {
if (is_null($example->grade)) {
$editable = false;
break;
}
}
}
$editable = $editable && $workshop->check_examples_assessed($USER->id);
$edit = ($editable and $edit);
if (!$candeleteall and $ownsubmission and $editable) {
@ -181,83 +171,12 @@ if ($edit) {
redirect($workshop->view_url());
} elseif ($cansubmit and $formdata = $mform->get_data()) {
if ($formdata->example == 0) {
// this was used just for validation, it must be set to zero when dealing with normal submissions
unset($formdata->example);
} else {
throw new coding_exception('Invalid submission form data value: example');
}
$timenow = time();
if (is_null($submission->id)) {
$formdata->workshopid = $workshop->id;
$formdata->example = 0;
$formdata->authorid = $USER->id;
$formdata->timecreated = $timenow;
$formdata->feedbackauthorformat = editors_get_preferred_format();
}
$formdata->timemodified = $timenow;
$formdata->title = trim($formdata->title);
$formdata->content = ''; // updated later
$formdata->contentformat = FORMAT_HTML; // updated later
$formdata->contenttrust = 0; // updated later
$formdata->late = 0x0; // bit mask
if (!empty($workshop->submissionend) and ($workshop->submissionend < time())) {
$formdata->late = $formdata->late | 0x1;
}
if ($workshop->phase == workshop::PHASE_ASSESSMENT) {
$formdata->late = $formdata->late | 0x2;
}
// Event information.
$params = array(
'context' => $workshop->context,
'courseid' => $workshop->course->id,
'other' => array(
'submissiontitle' => $formdata->title
)
);
$logdata = null;
if (is_null($submission->id)) {
$submission->id = $formdata->id = $DB->insert_record('workshop_submissions', $formdata);
$params['objectid'] = $submission->id;
$event = \mod_workshop\event\submission_created::create($params);
$event->trigger();
} else {
if (empty($formdata->id) or empty($submission->id) or ($formdata->id != $submission->id)) {
throw new moodle_exception('err_submissionid', 'workshop');
}
}
$params['objectid'] = $submission->id;
$formdata->id = $submission->id;
// Creates or updates submission.
$submission->id = $workshop->edit_submission($formdata);
// Save and relink embedded images and save attachments.
$formdata = file_postupdate_standard_editor($formdata, 'content', $workshop->submission_content_options(),
$workshop->context, 'mod_workshop', 'submission_content', $submission->id);
$formdata = file_postupdate_standard_filemanager($formdata, 'attachment', $workshop->submission_attachment_options(),
$workshop->context, 'mod_workshop', 'submission_attachment', $submission->id);
if (empty($formdata->attachment)) {
// explicit cast to zero integer
$formdata->attachment = 0;
}
// store the updated values or re-save the new submission (re-saving needed because URLs are now rewritten)
$DB->update_record('workshop_submissions', $formdata);
$event = \mod_workshop\event\submission_updated::create($params);
$event->add_record_snapshot('workshop', $workshoprecord);
$event->trigger();
// send submitted content for plagiarism detection
$fs = get_file_storage();
$files = $fs->get_area_files($workshop->context->id, 'mod_workshop', 'submission_attachment', $submission->id);
$params['other']['content'] = $formdata->content;
$params['other']['pathnamehashes'] = array_keys($files);
$event = \mod_workshop\event\assessable_uploaded::create($params);
$event->set_legacy_logdata($logdata);
$event->trigger();
redirect($workshop->submission_url($formdata->id));
redirect($workshop->submission_url($submission->id));
}
}

View File

@ -74,25 +74,7 @@ class workshop_submission_form extends moodleform {
$errors = parent::validation($data, $files);
if (empty($data['id']) and empty($data['example'])) {
// make sure there is no submission saved meanwhile from another browser window
$sql = "SELECT COUNT(s.id)
FROM {workshop_submissions} s
JOIN {workshop} w ON (s.workshopid = w.id)
JOIN {course_modules} cm ON (w.id = cm.instance)
JOIN {modules} m ON (m.name = 'workshop' AND m.id = cm.module)
WHERE cm.id = ? AND s.authorid = ? AND s.example = 0";
if ($DB->count_records_sql($sql, array($data['cmid'], $USER->id))) {
$errors['title'] = get_string('err_multiplesubmissions', 'mod_workshop');
}
}
$getfiles = file_get_drafarea_files($data['attachment_filemanager']);
if (empty($getfiles->list) and html_is_blank($data['content_editor']['text'])) {
$errors['content_editor'] = get_string('submissionrequiredcontent', 'mod_workshop');
$errors['attachment_filemanager'] = get_string('submissionrequiredfile', 'mod_workshop');
}
$errors += $this->_customdata['workshop']->validate_submission_data($data);
return $errors;
}

View File

@ -228,6 +228,7 @@ 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']);
// Switch phase.
$workshop = new workshop($this->workshop, $this->cm, $this->course);
@ -239,6 +240,7 @@ 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']);
// Switch to next (to assessment).
$workshop = new workshop($this->workshop, $this->cm, $this->course);
@ -250,6 +252,7 @@ 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']);
}
/**
@ -412,4 +415,123 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase {
$this->expectException('moodle_exception');
mod_workshop_external::view_workshop($this->workshop->id);
}
/**
* Test test_add_submission.
*/
public function test_add_submission() {
$fs = get_file_storage();
// Test user with full capabilities.
$this->setUser($this->student);
$title = 'Submission title';
$content = 'Submission contents';
// Create a file in a draft area for inline attachments.
$draftidinlineattach = file_get_unused_draft_itemid();
$usercontext = context_user::instance($this->student->id);
$filenameimg = 'shouldbeanimage.txt';
$filerecordinline = array(
'contextid' => $usercontext->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftidinlineattach,
'filepath' => '/',
'filename' => $filenameimg,
);
$fs->create_file_from_string($filerecordinline, 'image contents (not really)');
// Create a file in a draft area for regular attachments.
$draftidattach = file_get_unused_draft_itemid();
$filerecordattach = $filerecordinline;
$attachfilename = 'attachment.txt';
$filerecordattach['filename'] = $attachfilename;
$filerecordattach['itemid'] = $draftidattach;
$fs->create_file_from_string($filerecordattach, 'simple text attachment');
// Switch to submission phase.
$workshop = new workshop($this->workshop, $this->cm, $this->course);
$workshop->switch_phase(workshop::PHASE_SUBMISSION);
$result = mod_workshop_external::add_submission($this->workshop->id, $title, $content, FORMAT_MOODLE, $draftidinlineattach,
$draftidattach);
$result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
$this->assertEmpty($result['warnings']);
// Check submission created.
$submission = $workshop->get_submission_by_author($this->student->id);
$this->assertEquals($result['submissionid'], $submission->id);
$this->assertEquals($title, $submission->title);
$this->assertEquals($content, $submission->content);
// Check files.
$contentfiles = $fs->get_area_files($this->context->id, 'mod_workshop', 'submission_content', $submission->id);
$this->assertCount(2, $contentfiles);
foreach ($contentfiles as $file) {
if ($file->is_directory()) {
continue;
} else {
$this->assertEquals($filenameimg, $file->get_filename());
}
}
$contentfiles = $fs->get_area_files($this->context->id, 'mod_workshop', 'submission_attachment', $submission->id);
$this->assertCount(2, $contentfiles);
foreach ($contentfiles as $file) {
if ($file->is_directory()) {
continue;
} else {
$this->assertEquals($attachfilename, $file->get_filename());
}
}
}
/**
* Test test_add_submission invalid phase.
*/
public function test_add_submission_invalid_phase() {
$this->setUser($this->student);
$this->expectException('moodle_exception');
mod_workshop_external::add_submission($this->workshop->id, 'Test');
}
/**
* Test test_add_submission empty title.
*/
public function test_add_submission_empty_title() {
$this->setUser($this->student);
// Switch to submission phase.
$workshop = new workshop($this->workshop, $this->cm, $this->course);
$workshop->switch_phase(workshop::PHASE_SUBMISSION);
$this->expectException('moodle_exception');
mod_workshop_external::add_submission($this->workshop->id, '');
}
/**
* Test test_add_submission already added.
*/
public function test_add_submission_already_added() {
$this->setUser($this->student);
// Switch to submission phase.
$workshop = new workshop($this->workshop, $this->cm, $this->course);
$workshop->switch_phase(workshop::PHASE_SUBMISSION);
// Create the submission.
$result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
$result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
// Try to create it again.
$result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
$result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
$this->assertEquals(0, $result['submissionid']);
$this->assertCount(2, $result['warnings']);
$this->assertEquals('fielderror', $result['warnings'][0]['warningcode']);
$this->assertEquals('content_editor', $result['warnings'][0]['item']);
$this->assertEquals('fielderror', $result['warnings'][1]['warningcode']);
$this->assertEquals('attachment_filemanager', $result['warnings'][1]['item']);
}
}

View File

@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017051504; // The current module version (YYYYMMDDXX)
$plugin->version = 2017051505; // 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.