From 1a282212c18cea304f6759e163f4821d2268cd3d Mon Sep 17 00:00:00 2001 From: M Kassaei Date: Thu, 17 Dec 2015 10:00:48 +0000 Subject: [PATCH] MDL-50794 workshop: Allow restricting submitted file types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was reported at the Open University that there have been some problems with the formats of files submitted by students. Not all students completed their outlines as a Word .doc (despite this being the format of the template provided). Some formats (e.g. .pages) couldn’t be translated by some of the reviewing students. Therefore, they were unable to provide a review and the submitting students not receiving any comments. This patch allows the teacher to define list of allowed file types that can be attached to submitted work and/or overall feedback in the workshop. --- .../moodle2/backup_workshop_stepslib.php | 4 +- mod/workshop/db/install.xml | 2 + mod/workshop/db/upgrade.php | 27 +++++- mod/workshop/exsubmission.php | 1 + mod/workshop/form/assessment_form.php | 48 ++++++++++ mod/workshop/lang/en/workshop.php | 7 ++ mod/workshop/locallib.php | 43 ++++++++- mod/workshop/mod_form.php | 40 +++++++-- mod/workshop/submission.php | 5 +- mod/workshop/submission_form.php | 38 ++++++++ mod/workshop/tests/generator/lib.php | 2 + mod/workshop/tests/locallib_test.php | 88 +++++++++++++++++++ mod/workshop/version.php | 2 +- 13 files changed, 296 insertions(+), 11 deletions(-) diff --git a/mod/workshop/backup/moodle2/backup_workshop_stepslib.php b/mod/workshop/backup/moodle2/backup_workshop_stepslib.php index 8f2868ba1d7..3e9d8d10b5d 100644 --- a/mod/workshop/backup/moodle2/backup_workshop_stepslib.php +++ b/mod/workshop/backup/moodle2/backup_workshop_stepslib.php @@ -53,11 +53,11 @@ class backup_workshop_activity_structure_step extends backup_activity_structure_ 'instructauthorsformat', 'instructreviewers', 'instructreviewersformat', 'timemodified', 'phase', 'useexamples', 'usepeerassessment', 'useselfassessment', 'grade', 'gradinggrade', - 'strategy', 'evaluation', 'gradedecimals', 'nattachments', + 'strategy', 'evaluation', 'gradedecimals', 'nattachments', 'submissionfiletypes', 'latesubmissions', 'maxbytes', 'examplesmode', 'submissionstart', 'submissionend', 'assessmentstart', 'assessmentend', 'conclusion', 'conclusionformat', 'overallfeedbackmode', - 'overallfeedbackfiles', 'overallfeedbackmaxbytes')); + 'overallfeedbackfiles', 'overallfeedbackfiletypes', 'overallfeedbackmaxbytes')); // assessment forms definition $this->add_subplugin_structure('workshopform', $workshop, true); diff --git a/mod/workshop/db/install.xml b/mod/workshop/db/install.xml index 260787f700d..3dc972f4583 100644 --- a/mod/workshop/db/install.xml +++ b/mod/workshop/db/install.xml @@ -26,6 +26,7 @@ + @@ -38,6 +39,7 @@ + diff --git a/mod/workshop/db/upgrade.php b/mod/workshop/db/upgrade.php index 99acfbd7eae..4c76794c07e 100644 --- a/mod/workshop/db/upgrade.php +++ b/mod/workshop/db/upgrade.php @@ -35,7 +35,7 @@ defined('MOODLE_INTERNAL') || die(); * @return bool result */ function xmldb_workshop_upgrade($oldversion) { - global $CFG; + global $CFG, $DB; // Moodle v2.8.0 release upgrade line. // Put any upgrade step following this. @@ -46,5 +46,30 @@ function xmldb_workshop_upgrade($oldversion) { // Moodle v3.0.0 release upgrade line. // Put any upgrade step following this. + $dbman = $DB->get_manager(); + + if ($oldversion < 2016022200) { + + // Define field submissionfiletypes to be added to workshop. + $table = new xmldb_table('workshop'); + $field = new xmldb_field('submissionfiletypes', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'nattachments'); + + // Conditionally launch add field submissionfiletypes. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field overallfeedbackfiletypes to be added to workshop. + $field = new xmldb_field('overallfeedbackfiletypes', + XMLDB_TYPE_CHAR, '255', null, null, null, null, 'overallfeedbackfiles'); + + // Conditionally launch add field overallfeedbackfiletypes. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Workshop savepoint reached. + upgrade_mod_savepoint(true, 2016022200, 'workshop'); + } return true; } diff --git a/mod/workshop/exsubmission.php b/mod/workshop/exsubmission.php index 327ea2e8482..ecb7f98a923 100644 --- a/mod/workshop/exsubmission.php +++ b/mod/workshop/exsubmission.php @@ -121,6 +121,7 @@ if ($edit and $canmanage) { 'trusttext' => true, 'subdirs' => false, 'maxfiles' => $maxfiles, + 'filetypes' => $workshop->submissionfiletypes, 'maxbytes' => $maxbytes, 'context' => $workshop->context ); diff --git a/mod/workshop/form/assessment_form.php b/mod/workshop/form/assessment_form.php index 56128dcf5b3..809c0ef133d 100644 --- a/mod/workshop/form/assessment_form.php +++ b/mod/workshop/form/assessment_form.php @@ -120,4 +120,52 @@ class workshop_assessment_form extends moodleform { public function is_editable() { return !$this->_form->isFrozen(); } + + /** + * Validate incoming data. + * + * @param array $data + * @param array $files + * @return array + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + + if (isset($data['feedbackauthorattachment_filemanager'])) { + $draftitemid = $data['feedbackauthorattachment_filemanager']; + + // If we have draft files, then make sure they are the correct ones. + if ($draftfiles = file_get_drafarea_files($draftitemid)) { + + if (!$validfileextensions = workshop::get_array_of_file_extensions($this->workshop->overallfeedbackfiletypes)) { + return $errors; + } + $wrongfileextensions = null; + $bigfiles = null; + + // Check the size of each file. + foreach ($draftfiles->list as $file) { + $a = new stdClass(); + $a->maxbytes = $this->workshop->overallfeedbackmaxbytes; + $a->currentbytes = $file->size; + $a->filename = $file->filename; + $a->validfileextensions = implode(',', $validfileextensions); + + // Check whether the extension of uploaded file is in the list. + $thisextension = substr(strrchr($file->filename, '.'), 1); + if (!in_array($thisextension, $validfileextensions)) { + $wrongfileextensions .= get_string('err_wrongfileextension', 'workshop', $a) . '
'; + } + if ($file->size > $this->workshop->overallfeedbackmaxbytes) { + $bigfiles .= get_string('err_maxbytes', 'workshop', $a) . '
'; + } + } + if ($bigfiles || $wrongfileextensions) { + $errors['feedbackauthorattachment_filemanager'] = $bigfiles . $wrongfileextensions; + } + } + } + return $errors; + } + } diff --git a/mod/workshop/lang/en/workshop.php b/mod/workshop/lang/en/workshop.php index 4477b2bde5d..a0dd09ec9c0 100644 --- a/mod/workshop/lang/en/workshop.php +++ b/mod/workshop/lang/en/workshop.php @@ -30,6 +30,10 @@ $string['allocation'] = 'Submission allocation'; $string['allocationdone'] = 'Allocation done'; $string['allocationerror'] = 'Allocation error'; $string['allocationconfigured'] = 'Allocation configured'; +$string['allowedfiletypesforoverallfeedback'] = 'Feedback attachment allowed file types'; +$string['allowedfiletypesforoverallfeedback_help'] = 'Feedback attachment allowed file types can be restricted by entering a comma-separated list of file extensions, for example \'mp4, mp3, jpg, jpeg\'. If the field is left empty, then all file types are allowed.'; +$string['allowedfiletypesforsubmission'] = 'Submission attachment allowed file types'; +$string['allowedfiletypesforsubmission_help'] = 'Submission attachment allowed file types can be restricted by entering a comma-separated list of file extensions, for example \'mp4, mp3, jpg, jpeg\'. If the field is left empty, then all file types are allowed.'; $string['allsubmissions'] = 'All submissions ({$a})'; $string['alreadygraded'] = 'Already graded'; $string['areaconclusion'] = 'Conclusion text'; @@ -100,6 +104,9 @@ $string['editingsubmission'] = 'Editing submission'; $string['editsubmission'] = 'Edit submission'; $string['err_multiplesubmissions'] = 'While editing this form, another version of the submission has been saved. Multiple submissions per user are not allowed.'; $string['err_removegrademappings'] = 'Unable to remove the unused grade mappings'; +$string['err_maxbytes'] = 'The attachment "{$a->filename} ({$a->currentbytes} bytes)" exeeds the maximum allowed file size ({$a->maxbytes} bytes)'; +$string['err_notallowedfiletype'] = 'The file extension "{$a}" is not allowed'; +$string['err_wrongfileextension'] = 'The file "{$a->filename}" cannot be uploaded, only file types "{$a->validfileextensions}" are allowed'; $string['evaluategradeswait'] = 'Please wait until the assessments are evaluated and the grades are calculated'; $string['evaluation'] = 'Grading evaluation'; $string['evaluationmethod'] = 'Grading evaluation method'; diff --git a/mod/workshop/locallib.php b/mod/workshop/locallib.php index 4f98583aa54..eb7199b9cfe 100644 --- a/mod/workshop/locallib.php +++ b/mod/workshop/locallib.php @@ -1,5 +1,4 @@ 0, 'maxbytes' => $this->overallfeedbackmaxbytes, + 'filetypes' => $this->overallfeedbackfiletypes, 'maxfiles' => $this->overallfeedbackfiles, 'changeformat' => 1, 'context' => $this->context, @@ -2386,6 +2426,7 @@ class workshop { return array( 'subdirs' => 1, 'maxbytes' => $this->overallfeedbackmaxbytes, + 'filetypes' => $this->overallfeedbackfiletypes, 'maxfiles' => $this->overallfeedbackfiles, 'return_types' => FILE_INTERNAL, ); diff --git a/mod/workshop/mod_form.php b/mod/workshop/mod_form.php index f5a2f9f4023..6dbbf2da6d0 100644 --- a/mod/workshop/mod_form.php +++ b/mod/workshop/mod_form.php @@ -1,5 +1,4 @@ addElement('text', 'name', $label, array('size'=>'64')); + $mform->addElement('text', 'name', $label, array('size' => '64')); if (!empty($CFG->formatstringstriptags)) { $mform->setType('name', PARAM_TEXT); } else { @@ -118,7 +117,7 @@ class mod_workshop_mod_form extends moodleform_mod { $mform->addRule('gradinggradepass', null, 'numeric', null, 'client'); $options = array(); - for ($i=5; $i>=0; $i--) { + for ($i = 5; $i >= 0; $i--) { $options[$i] = $i; } $label = get_string('gradedecimals', 'workshop'); @@ -133,16 +132,24 @@ class mod_workshop_mod_form extends moodleform_mod { workshop::instruction_editors_options($this->context)); $options = array(); - for ($i=7; $i>=0; $i--) { + for ($i = 7; $i >= 0; $i--) { $options[$i] = $i; } $label = get_string('nattachments', 'workshop'); $mform->addElement('select', 'nattachments', $label, $options); $mform->setDefault('nattachments', 1); + $label = get_string('allowedfiletypesforsubmission', 'workshop'); + $mform->addElement('text', 'submissionfiletypes', $label, array('maxlength' => 255, 'size' => 64)); + $mform->addHelpButton('submissionfiletypes', 'allowedfiletypesforsubmission', 'workshop'); + $mform->setType('submissionfiletypes', PARAM_TEXT); + $mform->addRule('submissionfiletypes', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->disabledIf('submissionfiletypes', 'nattachments', 'eq', 0); + $options = get_max_upload_sizes($CFG->maxbytes, $this->course->maxbytes, 0, $workshopconfig->maxbytes); $mform->addElement('select', 'maxbytes', get_string('maxbytes', 'workshop'), $options); $mform->setDefault('maxbytes', $workshopconfig->maxbytes); + $mform->disabledIf('maxbytes', 'nattachments', 'eq', 0); $label = get_string('latesubmissions', 'workshop'); $text = get_string('latesubmissions_desc', 'workshop'); @@ -179,6 +186,13 @@ class mod_workshop_mod_form extends moodleform_mod { $mform->setDefault('overallfeedbackfiles', 0); $mform->disabledIf('overallfeedbackfiles', 'overallfeedbackmode', 'eq', 0); + $label = get_string('allowedfiletypesforoverallfeedback', 'workshop'); + $mform->addElement('text', 'overallfeedbackfiletypes', $label, array('maxlength' => 255, 'size' => 64)); + $mform->addHelpButton('overallfeedbackfiletypes', 'allowedfiletypesforoverallfeedback', 'workshop'); + $mform->setType('overallfeedbackfiletypes', PARAM_TEXT); + $mform->addRule('overallfeedbackfiletypes', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->disabledIf('overallfeedbackfiletypes', 'overallfeedbackfiles', 'eq', 0); + $options = get_max_upload_sizes($CFG->maxbytes, $this->course->maxbytes); $mform->addElement('select', 'overallfeedbackmaxbytes', get_string('overallfeedbackmaxbytes', 'workshop'), $options); $mform->setDefault('overallfeedbackmaxbytes', $workshopconfig->maxbytes); @@ -229,7 +243,7 @@ class mod_workshop_mod_form extends moodleform_mod { // Common module settings, Restrict availability, Activity completion etc. ---- $features = array('groups' => true, 'groupings' => true, - 'outcomes'=>true, 'gradecat'=>false, 'idnumber'=>false); + 'outcomes' => true, 'gradecat' => false, 'idnumber' => false); $this->standard_coursemodule_elements(); @@ -347,6 +361,22 @@ class mod_workshop_mod_form extends moodleform_mod { public function validation($data, $files) { $errors = parent::validation($data, $files); + // Check the input of submissionfiletypes field. + if ($data['submissionfiletypes']) { + $invalidextension = workshop::check_allowed_file_types($data['submissionfiletypes']); + if ($invalidextension) { + $errors['submissionfiletypes'] = $invalidextension; + } + } + + // Check the input of overallfeedbackfiletypes field. + if ($data['overallfeedbackfiletypes']) { + $invalidextension = workshop::check_allowed_file_types($data['overallfeedbackfiletypes']); + if ($invalidextension) { + $errors['overallfeedbackfiletypes'] = $invalidextension; + } + } + // check the phases borders are valid if ($data['submissionstart'] > 0 and $data['submissionend'] > 0 and $data['submissionstart'] >= $data['submissionend']) { $errors['submissionend'] = get_string('submissionendbeforestart', 'mod_workshop'); diff --git a/mod/workshop/submission.php b/mod/workshop/submission.php index 02997122536..d67dfd0aa2e 100644 --- a/mod/workshop/submission.php +++ b/mod/workshop/submission.php @@ -184,17 +184,20 @@ if ($edit) { require_once(dirname(__FILE__).'/submission_form.php'); $maxfiles = $workshop->nattachments; + $filetypes = $workshop->submissionfiletypes; $maxbytes = $workshop->maxbytes; $contentopts = array( 'trusttext' => true, 'subdirs' => false, 'maxfiles' => $maxfiles, + 'filetypes' => $filetypes, 'maxbytes' => $maxbytes, 'context' => $workshop->context, 'return_types' => FILE_INTERNAL | FILE_EXTERNAL ); - $attachmentopts = array('subdirs' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes, 'return_types' => FILE_INTERNAL); + $attachmentopts = array('subdirs' => true, 'maxfiles' => $maxfiles, 'filetypes' => $filetypes, 'maxbytes' => $maxbytes, + 'return_types' => FILE_INTERNAL); $submission = file_prepare_standard_editor($submission, 'content', $contentopts, $workshop->context, 'mod_workshop', 'submission_content', $submission->id); $submission = file_prepare_standard_filemanager($submission, 'attachment', $attachmentopts, $workshop->context, diff --git a/mod/workshop/submission_form.php b/mod/workshop/submission_form.php index 144e5363276..f411f4e82a3 100644 --- a/mod/workshop/submission_form.php +++ b/mod/workshop/submission_form.php @@ -37,6 +37,8 @@ class workshop_submission_form extends moodleform { $contentopts = $this->_customdata['contentopts']; $attachmentopts = $this->_customdata['attachmentopts']; + $this->attachmentopts = $attachmentopts; + $mform->addElement('header', 'general', get_string('submission', 'workshop')); $mform->addElement('text', 'title', get_string('submissiontitle', 'workshop')); @@ -88,6 +90,42 @@ class workshop_submission_form extends moodleform { } } + if (isset ($data['attachment_filemanager'])) { + $draftitemid = $data['attachment_filemanager']; + + // If we have draft files, then make sure they are the correct ones. + if ($draftfiles = file_get_drafarea_files($draftitemid)) { + + if (!$validfileextensions = workshop::get_array_of_file_extensions($this->attachmentopts['filetypes'])) { + return $errors; + } + $wrongfileextensions = null; + $bigfiles = null; + + // Check the size and type of each file. + foreach ($draftfiles->list as $file) { + $a = new stdClass(); + $a->maxbytes = $this->attachmentopts['maxbytes']; + $a->currentbytes = $file->size; + $a->filename = $file->filename; + $a->validfileextensions = implode(',', $validfileextensions); + + // Check whether the extension of uploaded file is in the list. + $thisextension = substr(strrchr($file->filename, '.'), 1); + if (!in_array($thisextension, $validfileextensions)) { + $wrongfileextensions .= get_string('err_wrongfileextension', 'workshop', $a) . '
'; + } + + // Check whether the file size exceeds the maximum submission attachment size. + if ($file->size > $this->attachmentopts['maxbytes']) { + $bigfiles .= get_string('err_maxbytes', 'workshop', $a) . '
'; + } + } + if ($bigfiles || $wrongfileextensions) { + $errors['attachment_filemanager'] = $bigfiles . $wrongfileextensions; + } + } + } return $errors; } } diff --git a/mod/workshop/tests/generator/lib.php b/mod/workshop/tests/generator/lib.php index 3774b8c96c9..0f893a05c96 100644 --- a/mod/workshop/tests/generator/lib.php +++ b/mod/workshop/tests/generator/lib.php @@ -48,11 +48,13 @@ class mod_workshop_generator extends testing_module_generator { 'gradinggrade' => $workshopconfig->gradinggrade, 'gradedecimals' => $workshopconfig->gradedecimals, 'nattachments' => 1, + 'submissionfiletypes' => null, 'maxbytes' => $workshopconfig->maxbytes, 'latesubmissions' => 0, 'useselfassessment' => 0, 'overallfeedbackmode' => 1, 'overallfeedbackfiles' => 0, + 'overallfeedbackfiletypes' => null, 'overallfeedbackmaxbytes' => $workshopconfig->maxbytes, 'useexamples' => 0, 'examplesmode' => $workshopconfig->examplesmode, diff --git a/mod/workshop/tests/locallib_test.php b/mod/workshop/tests/locallib_test.php index 70900ec8d42..13b5979b069 100644 --- a/mod/workshop/tests/locallib_test.php +++ b/mod/workshop/tests/locallib_test.php @@ -622,4 +622,92 @@ class mod_workshop_internal_api_testcase extends advanced_testcase { $this->assertEquals(0, $DB->count_records('workshop_submissions', array('workshopid' => $this->workshop->id))); $this->assertEquals(0, $DB->count_records('workshop_assessments')); } + + /** + * Test converting the string to array. + */ + public function test_get_array_of_file_extensions() { + $this->resetAfterTest(true); + + $listofextensions = 'doc, jpg, mp3'; + $actual = workshop::get_array_of_file_extensions($listofextensions); + $expected = array('doc', 'jpg', 'mp3'); + $this->assertEquals($expected, $actual); + + $listofextensions = 'mp4,; docx,; gif'; + $actual = workshop::get_array_of_file_extensions($listofextensions); + $expected = array('mp4', 'docx', 'gif'); + $this->assertEquals($expected, $actual); + + $listofextensions = 'mp4 docx gif'; + $actual = workshop::get_array_of_file_extensions($listofextensions); + $expected = array('mp4', 'docx', 'gif'); + $this->assertEquals($expected, $actual); + + $listofextensions = 'MP4 DOCx Gif'; + $actual = workshop::get_array_of_file_extensions($listofextensions); + $expected = array('mp4', 'docx', 'gif'); + $this->assertEquals($expected, $actual); + + $listofextensions = '.doc; .jpg; Mp4 "mp3"'; + $actual = workshop::get_array_of_file_extensions($listofextensions); + $expected = array('.doc', '.jpg', 'mp4', 'mp3'); + $this->assertEquals($expected, $actual); + + $listofextensions = '.doc,;.jpg; .Mp3, ".Avi"'; + $actual = workshop::get_array_of_file_extensions($listofextensions); + $expected = array('.doc', '.jpg', '.mp3', '.avi'); + $this->assertEquals($expected, $actual); + } + + /** + * Test the list of allowed file extensions. + */ + public function test_check_allowed_file_types() { + $this->resetAfterTest(true); + + // Valid file extensions. + $listofextensions = ''; + $expected = ''; + // The function returns '' when file extensions are valid or the input field is empty. + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + + $listofextensions = 'doc, jpg, mp3'; + $expected = ''; + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + + $listofextensions = 'doc; ".jpg"; mp4 ...mp3'; + $expected = ''; + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + + // Error handling. + $listofextensions = 'doc.jpg .mp3 .avi'; + $expected = get_string('err_notallowedfiletype', 'workshop', 'doc.jpg'); + // The function returns and error on the form-field: 'The file extension "doc.jpg" is not allowed'. + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + + $listofextensions = 'doc, jpg, mp3, unusual'; + $expected = get_string('err_notallowedfiletype', 'workshop', 'unusual'); + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + + $listofextensions = 'doc,; unusual1, unusual2'; + $expected = get_string('err_notallowedfiletype', 'workshop', 'unusual1'); + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + + $listofextensions = 'unusual1,; unsusual2, doc, jpg'; + $expected = get_string('err_notallowedfiletype', 'workshop', 'unusual1'); + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + + $listofextensions = 'unusual1; unusual2; mp4'; + $expected = get_string('err_notallowedfiletype', 'workshop', 'unusual1'); + $actual = workshop::check_allowed_file_types($listofextensions); + $this->assertEquals($expected, $actual); + } } diff --git a/mod/workshop/version.php b/mod/workshop/version.php index 5f8e1963c54..2eaa821efa2 100644 --- a/mod/workshop/version.php +++ b/mod/workshop/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016012101; // The current module version (YYYYMMDDXX) +$plugin->version = 2016022200; // The current module version (YYYYMMDDXX) $plugin->requires = 2015111000; // Requires this Moodle version. $plugin->component = 'mod_workshop'; $plugin->cron = 60; // Give as a chance every minute.