diff --git a/question/engine/tests/helpers.php b/question/engine/tests/helpers.php index a02cd0c7315..69c02d08f33 100644 --- a/question/engine/tests/helpers.php +++ b/question/engine/tests/helpers.php @@ -404,8 +404,12 @@ class test_question_maker { $essay->qtype = question_bank::get_qtype('essay'); $essay->responseformat = 'editor'; + $essay->responserequired = 1; $essay->responsefieldlines = 15; $essay->attachments = 0; + $essay->attachmentsrequired = 0; + $essay->responsetemplate = ''; + $essay->responsetemplateformat = FORMAT_MOODLE; $essay->graderinfo = ''; $essay->graderinfoformat = FORMAT_MOODLE; diff --git a/question/format/blackboard_six/formatpool.php b/question/format/blackboard_six/formatpool.php index 8073055bdf8..07b4ec39608 100644 --- a/question/format/blackboard_six/formatpool.php +++ b/question/format/blackboard_six/formatpool.php @@ -153,8 +153,10 @@ class qformat_blackboard_six_pool extends qformat_blackboard_six_base { $question->responsetemplate = $this->text_field(''); $question->feedback = ''; $question->responseformat = 'editor'; + $question->responserequired = 1; $question->responsefieldlines = 15; $question->attachments = 0; + $question->attachmentsrequired = 0; $question->fraction = 0; $questions[] = $question; diff --git a/question/format/blackboard_six/formatqti.php b/question/format/blackboard_six/formatqti.php index 8349cb08538..bb8777c3180 100644 --- a/question/format/blackboard_six/formatqti.php +++ b/question/format/blackboard_six/formatqti.php @@ -778,8 +778,10 @@ class qformat_blackboard_six_qti extends qformat_blackboard_six_base { $question->fraction[] = 1; $question->defaultmark = 1; $question->responseformat = 'editor'; + $question->responserequired = 1; $question->responsefieldlines = 15; $question->attachments = 0; + $question->attachmentsrequired = 0; $question->responsetemplate = $this->text_field(''); $questions[]=$question; diff --git a/question/format/examview/format.php b/question/format/examview/format.php index 36ad9c7e130..e2e1a9af611 100644 --- a/question/format/examview/format.php +++ b/question/format/examview/format.php @@ -293,10 +293,12 @@ class qformat_examview extends qformat_based_on_xml { $feedback = trim($this->unxmlise($qrec['answer'][0]['#'])); $question->graderinfo = $this->text_field($feedback); $question->responsetemplate = $this->text_field(''); + $question->responserequired = 1; $question->feedback = $feedback; $question->responseformat = 'editor'; $question->responsefieldlines = 15; $question->attachments = 0; + $question->attachmentsrequired = 0; $question->fraction = 0; return $question; } diff --git a/question/format/gift/format.php b/question/format/gift/format.php index 9089ab10ca0..156d4a888a0 100644 --- a/question/format/gift/format.php +++ b/question/format/gift/format.php @@ -327,8 +327,10 @@ class qformat_gift extends qformat_default { case 'essay': $question->responseformat = 'editor'; + $question->responserequired = 1; $question->responsefieldlines = 15; $question->attachments = 0; + $question->attachmentsrequired = 0; $question->graderinfo = array( 'text' => '', 'format' => FORMAT_HTML, 'files' => array()); $question->responsetemplate = array( diff --git a/question/format/webct/format.php b/question/format/webct/format.php index 9bc4f3a4672..5c45f1dbc65 100644 --- a/question/format/webct/format.php +++ b/question/format/webct/format.php @@ -634,8 +634,10 @@ class qformat_webct extends qformat_default { $question = $this->defaultquestion(); $question->qtype = 'essay'; $question->responseformat = 'editor'; + $question->responserequired = 1; $question->responsefieldlines = 15; $question->attachments = 0; + $question->attachmentsrequired = 0; $question->graderinfo = array( 'text' => '', 'format' => FORMAT_HTML, diff --git a/question/format/xml/format.php b/question/format/xml/format.php index 1c2edafdd2e..43c17e03df6 100644 --- a/question/format/xml/format.php +++ b/question/format/xml/format.php @@ -726,8 +726,12 @@ class qformat_xml extends qformat_default { array('#', 'responseformat', 0, '#'), 'editor'); $qo->responsefieldlines = $this->getpath($question, array('#', 'responsefieldlines', 0, '#'), 15); + $qo->responserequired = $this->getpath($question, + array('#', 'responserequired', 0, '#'), 1); $qo->attachments = $this->getpath($question, array('#', 'attachments', 0, '#'), 0); + $qo->attachmentsrequired = $this->getpath($question, + array('#', 'attachmentsrequired', 0, '#'), 0); $qo->graderinfo = $this->import_text_with_files($question, array('#', 'graderinfo', 0), '', $this->get_format($qo->questiontextformat)); $qo->responsetemplate['text'] = $this->getpath($question, @@ -1273,10 +1277,14 @@ class qformat_xml extends qformat_default { case 'essay': $expout .= " " . $question->options->responseformat . "\n"; + $expout .= " " . $question->options->responserequired . + "\n"; $expout .= " " . $question->options->responsefieldlines . "\n"; $expout .= " " . $question->options->attachments . "\n"; + $expout .= " " . $question->options->attachmentsrequired . + "\n"; $expout .= " format($question->options->graderinfoformat) . ">\n"; $expout .= $this->writetext($question->options->graderinfo, 3); diff --git a/question/format/xml/tests/xmlformat_test.php b/question/format/xml/tests/xmlformat_test.php index 72d8fb80625..448cc279370 100644 --- a/question/format/xml/tests/xmlformat_test.php +++ b/question/format/xml/tests/xmlformat_test.php @@ -355,8 +355,10 @@ END; $expectedq->length = 1; $expectedq->penalty = 0; $expectedq->responseformat = 'editor'; + $expectedq->responserequired = 1; $expectedq->responsefieldlines = 15; $expectedq->attachments = 0; + $expectedq->attachmentsrequired = 0; $expectedq->graderinfo['text'] = ''; $expectedq->graderinfo['format'] = FORMAT_MOODLE; $expectedq->responsetemplate['text'] = ''; @@ -380,8 +382,10 @@ END; 0 0 monospaced + 0 42 -1 + 1 Grade generously!

]]>
@@ -404,8 +408,10 @@ END; $expectedq->length = 1; $expectedq->penalty = 0; $expectedq->responseformat = 'monospaced'; + $expectedq->responserequired = 0; $expectedq->responsefieldlines = 42; $expectedq->attachments = -1; + $expectedq->attachmentsrequired = 1; $expectedq->graderinfo['text'] = '

Grade generously!

'; $expectedq->graderinfo['format'] = FORMAT_HTML; $expectedq->responsetemplate['text'] = '

Here is something really interesting.

'; @@ -432,8 +438,10 @@ END; $qdata->options->id = 456; $qdata->options->questionid = 123; $qdata->options->responseformat = 'monospaced'; + $qdata->options->responserequired = 0; $qdata->options->responsefieldlines = 42; $qdata->options->attachments = -1; + $qdata->options->attachmentsrequired = 1; $qdata->options->graderinfo = '

Grade generously!

'; $qdata->options->graderinfoformat = FORMAT_HTML; $qdata->options->responsetemplate = '

Here is something really interesting.

'; @@ -456,8 +464,10 @@ END; 0 0 monospaced + 0 42 -1 + 1 Grade generously!

]]>
diff --git a/question/type/essay/backup/moodle1/lib.php b/question/type/essay/backup/moodle1/lib.php index 51b17984bdf..fcb0aec6cd7 100644 --- a/question/type/essay/backup/moodle1/lib.php +++ b/question/type/essay/backup/moodle1/lib.php @@ -43,8 +43,10 @@ class moodle1_qtype_essay_handler extends moodle1_qtype_handler { $this->write_xml('essay', array( 'id' => $this->converter->get_nextid(), 'responseformat' => 'editor', + 'responserequired' => 1, 'responsefieldlines' => 15, 'attachments' => 0, + 'attachmentsrequired' => 0, 'graderinfo' => '', 'graderinfoformat' => FORMAT_HTML, 'responsetemplate' => '', diff --git a/question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php b/question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php index b53753cb0ec..fb4e5f792f6 100644 --- a/question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php +++ b/question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php @@ -49,9 +49,9 @@ class backup_qtype_essay_plugin extends backup_qtype_plugin { // Now create the qtype own structures. $essay = new backup_nested_element('essay', array('id'), array( - 'responseformat', 'responsefieldlines', 'attachments', - 'graderinfo', 'graderinfoformat', 'responsetemplate', - 'responsetemplateformat')); + 'responseformat', 'responserequired', 'responsefieldlines', + 'attachments', 'attachmentsrequired', 'graderinfo', + 'graderinfoformat', 'responsetemplate', 'responsetemplateformat')); // Now the own qtype tree. $pluginwrapper->add_child($essay); diff --git a/question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php b/question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php index 7a56430c40d..36b929c2fdf 100644 --- a/question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php +++ b/question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php @@ -58,6 +58,12 @@ class restore_qtype_essay_plugin extends restore_qtype_plugin { if (!isset($data->responsetemplateformat)) { $data->responsetemplateformat = FORMAT_HTML; } + if (!isset($data->responserequired)) { + $data->responserequired = 1; + } + if (!isset($data->attachmentsrequired)) { + $data->attachmentsrequired = 0; + } // Detect if the question is created or mapped. $questioncreated = $this->get_mappingid('question_created', @@ -103,8 +109,10 @@ class restore_qtype_essay_plugin extends restore_qtype_plugin { $defaultoptions = new stdClass(); $defaultoptions->questionid = $q->id; $defaultoptions->responseformat = 'editor'; + $defaultoptions->responserequired = 1; $defaultoptions->responsefieldlines = 15; $defaultoptions->attachments = 0; + $defaultoptions->attachmentsrequired = 0; $defaultoptions->graderinfo = ''; $defaultoptions->graderinfoformat = FORMAT_HTML; $defaultoptions->responsetemplate = ''; diff --git a/question/type/essay/db/install.xml b/question/type/essay/db/install.xml index c311a8f8711..7d6d94afc5f 100644 --- a/question/type/essay/db/install.xml +++ b/question/type/essay/db/install.xml @@ -1,5 +1,5 @@ - @@ -9,8 +9,10 @@ + + @@ -22,4 +24,4 @@ - \ No newline at end of file + diff --git a/question/type/essay/db/upgrade.php b/question/type/essay/db/upgrade.php index 7d4992161dc..393eb9f54a1 100644 --- a/question/type/essay/db/upgrade.php +++ b/question/type/essay/db/upgrade.php @@ -140,10 +140,36 @@ function xmldb_qtype_essay_upgrade($oldversion) { // Moodle v2.5.0 release upgrade line. // Put any upgrade step following this. - // Moodle v2.6.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2014011300) { + + // Create new field responserequired (indicates whether inline response is required). + + $table = new xmldb_table('qtype_essay_options'); + $field = new xmldb_field('responserequired', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1', 'responseformat'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Essay savepoint reached. + upgrade_plugin_savepoint(true, 2014011300, 'qtype', 'essay'); + } + + if ($oldversion < 2014011301) { + + // Create new field attachmentsrequired (indicates whether attachments should be required). + + $table = new xmldb_table('qtype_essay_options'); + $field = new xmldb_field('attachmentsrequired', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'attachments'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Essay savepoint reached. + upgrade_plugin_savepoint(true, 2014011301, 'qtype', 'essay'); + } return true; } diff --git a/question/type/essay/edit_essay_form.php b/question/type/essay/edit_essay_form.php index edccbe5f88b..d50437b2780 100644 --- a/question/type/essay/edit_essay_form.php +++ b/question/type/essay/edit_essay_form.php @@ -38,22 +38,40 @@ class qtype_essay_edit_form extends question_edit_form { protected function definition_inner($mform) { $qtype = question_bank::get_qtype('essay'); + $mform->addElement('header', 'responseoptions', get_string('responseoptions', 'qtype_essay')); + $mform->setExpanded('responseoptions'); + $mform->addElement('select', 'responseformat', get_string('responseformat', 'qtype_essay'), $qtype->response_formats()); $mform->setDefault('responseformat', 'editor'); + $mform->addElement('select', 'responserequired', + get_string('responserequired', 'qtype_essay'), $qtype->response_required_options()); + $mform->setDefault('responserequired', 1); + $mform->disabledIf('responserequired', 'responseformat', 'eq', 'noinline'); + $mform->addElement('select', 'responsefieldlines', get_string('responsefieldlines', 'qtype_essay'), $qtype->response_sizes()); $mform->setDefault('responsefieldlines', 15); + $mform->disabledIf('responsefieldlines', 'responseformat', 'eq', 'noinline'); $mform->addElement('select', 'attachments', get_string('allowattachments', 'qtype_essay'), $qtype->attachment_options()); $mform->setDefault('attachments', 0); + $mform->addElement('select', 'attachmentsrequired', + get_string('attachmentsrequired', 'qtype_essay'), $qtype->attachments_required_options()); + $mform->setDefault('attachmentsrequired', 0); + $mform->addHelpButton('attachmentsrequired', 'attachmentsrequired', 'qtype_essay'); + $mform->disabledIf('attachmentsrequired', 'attachments', 'eq', 0); + + $mform->addElement('header', 'responsetemplateheader', get_string('responsetemplateheader', 'qtype_essay')); $mform->addElement('editor', 'responsetemplate', get_string('responsetemplate', 'qtype_essay'), array('rows' => 10), array_merge($this->editoroptions, array('maxfiles' => 0))); $mform->addHelpButton('responsetemplate', 'responsetemplate', 'qtype_essay'); + $mform->addElement('header', 'graderinfoheader', get_string('graderinfoheader', 'qtype_essay')); + $mform->setExpanded('graderinfoheader'); $mform->addElement('editor', 'graderinfo', get_string('graderinfo', 'qtype_essay'), array('rows' => 10), $this->editoroptions); } @@ -66,8 +84,10 @@ class qtype_essay_edit_form extends question_edit_form { } $question->responseformat = $question->options->responseformat; + $question->responserequired = $question->options->responserequired; $question->responsefieldlines = $question->options->responsefieldlines; $question->attachments = $question->options->attachments; + $question->attachmentsrequired = $question->options->attachmentsrequired; $draftid = file_get_submitted_draft_itemid('graderinfo'); $question->graderinfo = array(); @@ -91,6 +111,30 @@ class qtype_essay_edit_form extends question_edit_form { return $question; } + public function validation($fromform, $files) { + $errors = parent::validation($fromform, $files); + + // Don't allow both 'no inline response' and 'no attachments' to be selected, + // as these options would result in there being no input requested from the user. + if ($fromform['responseformat'] == 'noinline' && !$fromform['attachments']) { + $errors['attachments'] = get_string('mustattach', 'qtype_essay'); + } + + // If 'no inline response' is set, force the teacher to require attachments; + // otherwise there will be nothing to grade. + if ($fromform['responseformat'] == 'noinline' && !$fromform['attachmentsrequired']) { + $errors['attachmentsrequired'] = get_string('mustrequire', 'qtype_essay'); + } + + // Don't allow the teacher to require more attachments than they allow; as this would + // create a condition that it's impossible for the student to meet. + if ($fromform['attachments'] != -1 && $fromform['attachments'] < $fromform['attachmentsrequired'] ) { + $errors['attachmentsrequired'] = get_string('mustrequirefewer', 'qtype_essay'); + } + + return $errors; + } + public function qtype() { return 'essay'; } diff --git a/question/type/essay/lang/en/qtype_essay.php b/question/type/essay/lang/en/qtype_essay.php index 068c4d1e76e..134fc4ba9c1 100644 --- a/question/type/essay/lang/en/qtype_essay.php +++ b/question/type/essay/lang/en/qtype_essay.php @@ -24,11 +24,19 @@ */ $string['allowattachments'] = 'Allow attachments'; +$string['attachmentsoptional'] = 'Attachments are optional'; +$string['attachmentsrequired'] = 'Require attachments'; +$string['attachmentsrequired_help'] = 'This option specifies the minimum number of attachments required for a response to be considered gradable.'; $string['formateditor'] = 'HTML editor'; $string['formateditorfilepicker'] = 'HTML editor with file picker'; $string['formatmonospaced'] = 'Plain text, monospaced font'; +$string['formatnoinline'] = 'No inline text'; $string['formatplain'] = 'Plain text'; $string['graderinfo'] = 'Information for graders'; +$string['graderinfoheader'] = 'Grader Information'; +$string['mustattach'] = 'When "no inline text" is selected, or responses are optional, you must allow at least one attachment.'; +$string['mustrequire'] = 'When "no inline text" is selected, or responses are optional, you must require at least one attachment.'; +$string['mustrequirefewer'] = 'You cannot require more attachments than you allow.'; $string['nlines'] = '{$a} lines'; $string['pluginname'] = 'Essay'; $string['pluginname_help'] = 'In response to a question (that may include an image) the respondent writes an answer of a paragraph or two. The essay question will not be assigned a grade until it has been reviewed by a teacher and manually graded.'; @@ -38,5 +46,10 @@ $string['pluginnameediting'] = 'Editing an Essay question'; $string['pluginnamesummary'] = 'Allows a response of a few sentences or paragraphs. This must then be graded manually.'; $string['responsefieldlines'] = 'Input box size'; $string['responseformat'] = 'Response format'; +$string['responseoptions'] = 'Response Options'; +$string['responserequired'] = 'Require text'; +$string['responsenotrequired'] = 'Text input is optional'; +$string['responseisrequired'] = 'Require the student to enter text'; $string['responsetemplate'] = 'Response template'; +$string['responsetemplateheader'] = 'Response Template'; $string['responsetemplate_help'] = 'Any text entered here will be displayed in the response input box when a new attempt at the question starts.'; diff --git a/question/type/essay/question.php b/question/type/essay/question.php index 582a71b6206..54f285f8200 100644 --- a/question/type/essay/question.php +++ b/question/type/essay/question.php @@ -34,9 +34,18 @@ defined('MOODLE_INTERNAL') || die(); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class qtype_essay_question extends question_with_responses { + public $responseformat; + + /** @var int Indicates whether an inline response is required ('0') or optional ('1') */ + public $responserequired; + public $responsefieldlines; public $attachments; + + /** @var int The number of attachments required for a response to be complete. */ + public $attachmentsrequired; + public $graderinfo; public $graderinfoformat; public $responsetemplate; @@ -81,7 +90,27 @@ class qtype_essay_question extends question_with_responses { } public function is_complete_response(array $response) { - return array_key_exists('answer', $response) && ($response['answer'] !== ''); + // Determine if the given response has inline text and attachments. + $hasinlinetext = array_key_exists('answer', $response) && ($response['answer'] !== ''); + $hasattachments = array_key_exists('attachments', $response) + && $response['attachments'] instanceof question_response_files; + + // Determine the number of attachments present. + if ($hasattachments) { + $attachcount = count($response['attachments']->get_files()); + } else { + $attachcount = 0; + } + + // Determine if we have /some/ content to be graded. + $hascontent = $hasinlinetext || ($attachcount > 0); + + // Determine if we meet the optional requirements. + $meetsinlinereq = $hasinlinetext || (!$this->responserequired) || ($this->responseformat == 'noinline'); + $meetsattachmentreq = ($attachcount >= $this->attachmentsrequired); + + // The response is complete iff all of our requirements are met. + return $hascontent && $meetsinlinereq && $meetsattachmentreq; } public function is_same_response(array $prevresponse, array $newresponse) { diff --git a/question/type/essay/questiontype.php b/question/type/essay/questiontype.php index 7ab2df6df53..8e5c580acda 100644 --- a/question/type/essay/questiontype.php +++ b/question/type/essay/questiontype.php @@ -63,8 +63,10 @@ class qtype_essay extends question_type { } $options->responseformat = $formdata->responseformat; + $options->responserequired = $formdata->responserequired; $options->responsefieldlines = $formdata->responsefieldlines; $options->attachments = $formdata->attachments; + $options->attachmentsrequired = $formdata->attachmentsrequired; $options->graderinfo = $this->import_or_save_files($formdata->graderinfo, $context, 'qtype_essay', 'graderinfo', $formdata->id); $options->graderinfoformat = $formdata->graderinfo['format']; @@ -76,8 +78,10 @@ class qtype_essay extends question_type { protected function initialise_question_instance(question_definition $question, $questiondata) { parent::initialise_question_instance($question, $questiondata); $question->responseformat = $questiondata->options->responseformat; + $question->responserequired = $questiondata->options->responserequired; $question->responsefieldlines = $questiondata->options->responsefieldlines; $question->attachments = $questiondata->options->attachments; + $question->attachmentsrequired = $questiondata->options->attachmentsrequired; $question->graderinfo = $questiondata->options->graderinfo; $question->graderinfoformat = $questiondata->options->graderinfoformat; $question->responsetemplate = $questiondata->options->responsetemplate; @@ -101,6 +105,17 @@ class qtype_essay extends question_type { 'editorfilepicker' => get_string('formateditorfilepicker', 'qtype_essay'), 'plain' => get_string('formatplain', 'qtype_essay'), 'monospaced' => get_string('formatmonospaced', 'qtype_essay'), + 'noinline' => get_string('formatnoinline', 'qtype_essay'), + ); + } + + /** + * @return array the choices that should be offerd when asking if a response is required + */ + public function response_required_options() { + return array( + 1 => get_string('responseisrequired', 'qtype_essay'), + 0 => get_string('responsenotrequired', 'qtype_essay'), ); } @@ -128,6 +143,18 @@ class qtype_essay extends question_type { ); } + /** + * @return array the choices that should be offered for the number of required attachments. + */ + public function attachments_required_options() { + return array( + 0 => get_string('attachmentsoptional', 'qtype_essay'), + 1 => '1', + 2 => '2', + 3 => '3' + ); + } + public function move_files($questionid, $oldcontextid, $newcontextid) { parent::move_files($questionid, $oldcontextid, $newcontextid); $fs = get_file_storage(); diff --git a/question/type/essay/renderer.php b/question/type/essay/renderer.php index ae7eafc3b3e..c977014d128 100644 --- a/question/type/essay/renderer.php +++ b/question/type/essay/renderer.php @@ -178,6 +178,28 @@ abstract class qtype_essay_format_renderer_base extends plugin_renderer_base { protected abstract function class_name(); } +/** + * An essay format renderer for essays where the student should not enter + * any inline response. + * + * @copyright 2013 Binghamton University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class qtype_essay_format_noinline_renderer extends plugin_renderer_base { + + protected function class_name() { + return 'qtype_essay_noinline'; + } + + public function response_area_read_only($name, $qa, $step, $lines, $context) { + return ''; + } + + public function response_area_input($name, $qa, $step, $lines, $context) { + return ''; + } + +} /** * An essay format renderer for essays where the student should use the HTML diff --git a/question/type/essay/tests/helper.php b/question/type/essay/tests/helper.php index 751473cff1f..0ce0ba44282 100644 --- a/question/type/essay/tests/helper.php +++ b/question/type/essay/tests/helper.php @@ -34,7 +34,7 @@ defined('MOODLE_INTERNAL') || die(); */ class qtype_essay_test_helper extends question_test_helper { public function get_test_questions() { - return array('editor', 'editorfilepicker', 'plain', 'monospaced', 'responsetemplate'); + return array('editor', 'editorfilepicker', 'plain', 'monospaced', 'responsetemplate', 'noinline'); } /** @@ -49,8 +49,10 @@ class qtype_essay_test_helper extends question_test_helper { $q->questiontext = 'Please write a story about a frog.'; $q->generalfeedback = 'I hope your story had a beginning, a middle and an end.'; $q->responseformat = 'editor'; + $q->responserequired = 1; $q->responsefieldlines = 10; $q->attachments = 0; + $q->attachmentsrequired = 0; $q->graderinfo = ''; $q->graderinfoformat = FORMAT_HTML; $q->qtype = question_bank::get_qtype('essay'); @@ -93,8 +95,10 @@ class qtype_essay_test_helper extends question_test_helper { $fromform->defaultmark = 1.0; $fromform->generalfeedback = array('text' => 'I hope your story had a beginning, a middle and an end.', 'format' => FORMAT_HTML); $fromform->responseformat = 'editorfilepicker'; + $fromform->responserequired = 1; $fromform->responsefieldlines = 10; $fromform->attachments = 3; + $fromform->attachmentsrequired = 0; $fromform->graderinfo = array('text' => '', 'format' => FORMAT_HTML); $fromform->responsetemplate = array('text' => '', 'format' => FORMAT_HTML); @@ -126,8 +130,10 @@ class qtype_essay_test_helper extends question_test_helper { $fromform->defaultmark = 1.0; $fromform->generalfeedback = array('text' => 'I hope your story had a beginning, a middle and an end.', 'format' => FORMAT_HTML); $fromform->responseformat = 'plain'; + $fromform->responserequired = 1; $fromform->responsefieldlines = 10; $fromform->attachments = 0; + $fromform->attachmentsrequired = 0; $fromform->graderinfo = array('text' => '', 'format' => FORMAT_HTML); $fromform->responsetemplate = array('text' => '', 'format' => FORMAT_HTML); @@ -150,4 +156,87 @@ class qtype_essay_test_helper extends question_test_helper { $q->responsetemplateformat = FORMAT_HTML; return $q; } + + /** + * Makes an essay question without an inline text editor. + * @return qtype_essay_question + */ + public function make_essay_question_noinline() { + $q = $this->initialise_essay_question(); + $q->responseformat = 'noinline'; + $q->attachments = 3; + $q->attachmentsrequired = 1; + return $q; + } + + /** + * Creates an empty draft area for attachments. + * @return int The draft area's itemid. + */ + protected function make_attachment_draft_area() { + $draftid = 0; + $contextid = 0; + + $component = 'question'; + $filearea = 'response_attachments'; + + // Create an empty file area. + file_prepare_draft_area($draftid, $contextid, $component, $filearea, null); + return $draftid; + } + + /** + * Creates an attachment in the provided attachment draft area. + * @param int $draftid The itemid for the draft area in which the file should be created. + * @param string $name The filename for the file to be created. + * @param string $contents The contents of the file to be created. + */ + protected function make_attachment($draftid, $name, $contents) { + global $USER; + + $fs = get_file_storage(); + $usercontext = context_user::instance($USER->id); + + // Create the file in the provided draft area. + $fileinfo = array( + 'contextid' => $usercontext->id, + 'component' => 'user', + 'filearea' => 'draft', + 'itemid' => $draftid, + 'filepath' => '/', + 'filename' => $name, + ); + $fs->create_file_from_string($fileinfo, $contents); + } + + /** + * Generates a draft file area that contains the provided number of attachments. You should ensure + * that a user is logged in with setUser before you run this function. + * + * @param int $attachments The number of attachments to generate. + * @return int The itemid of the generated draft file area. + */ + public function make_attachments($attachments) { + $draftid = $this->make_attachment_draft_area(); + + // Create the relevant amount of dummy attachments in the given draft area. + for ($i = 0; $i < $attachments; ++$i) { + $this->make_attachment($draftid, $i, $i); + } + + return $draftid; + } + + /** + * Generates a question_file_saver that contains the provided number of attachments. You should ensure + * that a user is logged in with setUser before you run this function. + * + * @param int $:attachments The number of attachments to generate. + * @return question_file_saver a question_file_saver that contains the given amount of dummy files, for use in testing. + */ + public function make_attachments_saver($attachments) { + return new question_file_saver($this->make_attachments($attachments), 'question', 'response_attachments'); + } + + } diff --git a/question/type/essay/tests/question_test.php b/question/type/essay/tests/question_test.php index bd9cdb732e2..6ab9635e382 100644 --- a/question/type/essay/tests/question_test.php +++ b/question/type/essay/tests/question_test.php @@ -36,7 +36,7 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); * @copyright 2009 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class qtype_essay_question_testcase extends advanced_testcase { +class qtype_essay_question_test extends advanced_testcase { public function test_get_question_summary() { $essay = test_question_maker::make_an_essay_question(); $essay->questiontext = 'Hello world'; @@ -139,11 +139,29 @@ class qtype_essay_question_testcase extends advanced_testcase { } public function test_is_complete_response() { + $this->resetAfterTest(true); + // Create a new logged-in user, so we can test responses with attachments. + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + // Create sample attachments to use in testing. + $helper = test_question_maker::get_test_helper('essay'); + $attachments = array(); + for ($i = 0; $i < 4; ++$i) { + $attachments[$i] = $helper->make_attachments_saver($i); + } + + // Create the essay question under test. $essay = test_question_maker::make_an_essay_question(); $essay->start_attempt(new question_attempt_step(), 1); - // The empty string should be considered an empty response, as should a lack of a response. + // Test the "traditional" case, where we must recieve a response from the user. + $essay->responserequired = 1; + $essay->attachmentsrequired = 0; + $essay->responseformat = 'editor'; + + // The empty string should be considered an incomplete response, as should a lack of a response. $this->assertFalse($essay->is_complete_response(array('answer' => ''))); $this->assertFalse($essay->is_complete_response(array())); @@ -151,5 +169,74 @@ class qtype_essay_question_testcase extends advanced_testcase { $this->assertTrue($essay->is_complete_response(array('answer' => 'A student response.'))); $this->assertTrue($essay->is_complete_response(array('answer' => '0 times.'))); $this->assertTrue($essay->is_complete_response(array('answer' => '0'))); + + // Test the case where two files are required. + $essay->attachmentsrequired = 2; + + // Attaching less than two files should result in an incomplete response. + $this->assertFalse($essay->is_complete_response(array('answer' => 'A'))); + $this->assertFalse($essay->is_complete_response( + array('answer' => 'A', 'attachments' => $attachments[0]))); + $this->assertFalse($essay->is_complete_response( + array('answer' => 'A', 'attachments' => $attachments[1]))); + + // Anything without response text should result in an incomplete response. + $this->assertFalse($essay->is_complete_response( + array('answer' => '', 'attachments' => $attachments[2]))); + + // Attaching two or more files should result in a complete response. + $this->assertTrue($essay->is_complete_response( + array('answer' => 'A', 'attachments' => $attachments[2]))); + $this->assertTrue($essay->is_complete_response( + array('answer' => 'A', 'attachments' => $attachments[3]))); + + // Test the case in which two files are required, but the inline + // response is optional. + $essay->responserequired = 0; + + $this->assertFalse($essay->is_complete_response( + array('answer' => '', 'attachments' => $attachments[1]))); + + $this->assertTrue($essay->is_complete_response( + array('answer' => '', 'attachments' => $attachments[2]))); + + // Test the case in which both the response and inline text are optional. + $essay->attachmentsrequired = 0; + + // Providing no answer and no attachment should result in an incomplete + // response. + $this->assertFalse($essay->is_complete_response( + array('answer' => ''))); + $this->assertFalse($essay->is_complete_response( + array('answer' => '', 'attachments' => $attachments[0]))); + + // Providing an answer _or_ an attachment should result in a complete + // response. + $this->assertTrue($essay->is_complete_response( + array('answer' => '', 'attachments' => $attachments[1]))); + $this->assertTrue($essay->is_complete_response( + array('answer' => 'Answer text.', 'attachments' => $attachments[0]))); + + // Test the case in which we're in "no inline response" mode, + // in which the response is not required (as it's not provided). + $essay->reponserequired = 0; + $essay->responseformat = 'noinline'; + $essay->attachmensrequired = 1; + + $this->assertFalse($essay->is_complete_response( + array())); + $this->assertFalse($essay->is_complete_response( + array('attachments' => $attachments[0]))); + + // Providing an attachment should result in a complete response. + $this->assertTrue($essay->is_complete_response( + array('attachments' => $attachments[1]))); + + // Ensure that responserequired is ignored when we're in inline response mode. + $essay->reponserequired = 1; + $this->assertTrue($essay->is_complete_response( + array('attachments' => $attachments[1]))); + } + } diff --git a/question/type/essay/version.php b/question/type/essay/version.php index b079d4b9e18..28571bded95 100644 --- a/question/type/essay/version.php +++ b/question/type/essay/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'qtype_essay'; -$plugin->version = 2013110500; +$plugin->version = 2014011301; $plugin->requires = 2013110500;