diff --git a/essaydownload_form.php b/essaydownload_form.php index e7f5681..0834521 100644 --- a/essaydownload_form.php +++ b/essaydownload_form.php @@ -54,8 +54,13 @@ class quiz_essaydownload_form extends moodleform { public function definition() { $mform = $this->_form; - $mform->addElement('header', 'preferencespage', get_string('options', 'quiz_essaydownload')); + $mform->addElement('header', 'generaloptions', get_string('generaloptions', 'quiz_essaydownload')); $this->standard_preference_fields($mform); + + $mform->addElement('header', 'pdfoptions', get_string('pdfoptions', 'quiz_essaydownload')); + $this->pdf_layout_fields($mform); + $mform->closeHeaderBefore('download'); + $mform->addElement('submit', 'download', get_string('download')); } @@ -78,15 +83,6 @@ protected function standard_preference_fields(MoodleQuickForm $mform) { $mform->setType('groupby', PARAM_ALPHA); $mform->addHelpButton('groupby', 'groupby', 'quiz_essaydownload'); - $mform->addElement( - 'advcheckbox', - 'questiontext', - get_string('whattoinclude', 'quiz_essaydownload'), - get_string('includequestiontext', 'quiz_essaydownload') - ); - $mform->addElement('advcheckbox', 'responsetext', get_string('includeresponsetext', 'quiz_essaydownload')); - $mform->addElement('advcheckbox', 'attachments', get_string('includeattachments', 'quiz_essaydownload')); - $mform->addElement( 'select', 'nameordering', @@ -98,12 +94,125 @@ protected function standard_preference_fields(MoodleQuickForm $mform) { ); $mform->setType('nameordering', PARAM_ALPHA); + $mform->addElement( + 'advcheckbox', + 'attachments', + get_string('attachments', 'quiz_essaydownload'), + get_string('includeattachments', 'quiz_essaydownload') + ); + $mform->addHelpButton('attachments', 'includeattachments', 'quiz_essaydownload'); + + $mform->addElement( + 'advcheckbox', + 'questiontext', + get_string('questiontext', 'question'), + get_string('includequestiontext', 'quiz_essaydownload') + ); + $mform->addHelpButton('questiontext', 'includequestiontext', 'quiz_essaydownload'); + + $mform->addElement('select', 'fileformat', get_string('fileformat', 'quiz_essaydownload'), [ + 'txt' => get_string('fileformattxt', 'quiz_essaydownload'), + 'pdf' => get_string('fileformatpdf', 'quiz_essaydownload'), + ]); + $mform->setType('fileformat', PARAM_ALPHA); + $mform->setDefault('fileformat', 'pdf'); + $mform->addHelpButton('fileformat', 'fileformat', 'quiz_essaydownload'); + + $mform->addElement('select', 'source', get_string('source', 'quiz_essaydownload'), [ + 'plain' => get_string('sourcesummary', 'quiz_essaydownload'), + 'html' => get_string('sourceoriginal', 'quiz_essaydownload'), + ]); + $mform->disabledIf('source', 'fileformat', 'neq', 'pdf'); + $mform->setType('source', PARAM_ALPHA); + $mform->setDefault('source', 'html'); + $mform->addHelpButton('source', 'source', 'quiz_essaydownload'); + $mform->addElement( 'advcheckbox', 'shortennames', - get_string('additionalsettings', 'quiz_essaydownload'), + get_string('compatibility', 'quiz_essaydownload'), get_string('shortennames', 'quiz_essaydownload') ); $mform->addHelpButton('shortennames', 'shortennames', 'quiz_essaydownload'); } + + /** + * Fields to configure the PDF layout. + * + * @param MoodleQuickForm $mform the form + * @return void + */ + protected function pdf_layout_fields(MoodleQuickForm $mform) { + $mform->addElement('select', 'page', get_string('page', 'quiz_essaydownload'), [ + 'a4' => get_string('pagea4', 'quiz_essaydownload'), + 'letter' => get_string('pageletter', 'quiz_essaydownload'), + ]); + $mform->setType('page', PARAM_ALPHA); + $mform->setDefault('page', 'a4'); + $mform->disabledIf('page', 'fileformat', 'neq', 'pdf'); + + $margingroup = []; + $margingroup[] = $mform->createElement('text', 'marginleft', '', ['size' => 3]); + $mform->setType('marginleft', PARAM_INT); + $margingroup[] = $mform->createElement('text', 'marginright', '', ['size' => 3]); + $mform->setType('marginright', PARAM_INT); + $margingroup[] = $mform->createElement('text', 'margintop', '', ['size' => 3]); + $mform->setType('margintop', PARAM_INT); + $margingroup[] = $mform->createElement('text', 'marginbottom', '', ['size' => 3]); + $mform->setType('marginbottom', PARAM_INT); + $mform->addGroup($margingroup, 'margingroup', get_string('margins', 'quiz_essaydownload'), ' ', false); + $mform->disabledIf('margingroup', 'fileformat', 'neq', 'pdf'); + + $mform->addElement('select', 'linespacing', get_string('linespacing', 'quiz_essaydownload'), [ + '1' => get_string('linesingle', 'quiz_essaydownload'), + '1.5' => get_string('lineoneandhalf', 'quiz_essaydownload'), + '2' => get_string('linedouble', 'quiz_essaydownload'), + ]); + $mform->setType('linespacing', PARAM_FLOAT); + $mform->disabledIf('linespacing', 'fileformat', 'neq', 'pdf'); + + $mform->addElement('select', 'font', get_string('font', 'quiz_essaydownload'), [ + 'sans' => get_string('fontsans', 'quiz_essaydownload'), + 'serif' => get_string('fontserif', 'quiz_essaydownload'), + 'mono' => get_string('fontmono', 'quiz_essaydownload'), + ]); + $mform->setType('font', PARAM_ALPHA); + $mform->setDefault('font', 'serif'); + $mform->disabledIf('font', 'fileformat', 'neq', 'pdf'); + $mform->addHelpButton('font', 'font', 'quiz_essaydownload'); + + $mform->addElement('text', 'fontsize', get_string('fontsize', 'quiz_essaydownload'), ['size' => 3]); + $mform->setType('fontsize', PARAM_INT); + $mform->disabledIf('fontsize', 'fileformat', 'neq', 'pdf'); + $mform->addHelpButton('fontsize', 'fontsize', 'quiz_essaydownload'); + } + + /** + * Validation of our settings form, e. g. font size or page margins. + * + * @param array $data submitted data in form ['fieldname' => value] + * @param array $files array of uploaded files ['element_name' => tmp_file_path] + * @return array errors in form ['element_name' => 'error message'] or [] if no errors + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + + // No further validation to be done if using plain text format. + if ($data['fileformat'] === 'txt') { + return $errors; + } + + $margins = [$data['marginleft'], $data['marginright'], $data['margintop'], $data['marginbottom']]; + foreach ($margins as $margin) { + if ($margin > 80 || $margin < 0) { + $errors['margingroup'] = get_string('errormargin', 'quiz_essaydownload'); + } + } + + if ($data['fontsize'] > 50 || $data['fontsize'] < 6) { + $errors['fontsize'] = get_string('errorfontsize', 'quiz_essaydownload'); + } + + return $errors; + } } diff --git a/essaydownload_options.php b/essaydownload_options.php index 75d1d21..f37f485 100644 --- a/essaydownload_options.php +++ b/essaydownload_options.php @@ -23,8 +23,6 @@ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -use mod_quiz\local\reports\attempts_report_options; - defined('MOODLE_INTERNAL') || die(); // This work-around is required until Moodle 4.2 is the lowest version we support. @@ -45,24 +43,51 @@ class_alias('\mod_quiz_attempts_report_options', '\quiz_essaydownload_options_pa */ class quiz_essaydownload_options extends quiz_essaydownload_options_parent_class_alias { - /** @var bool whether to include the text response files in the archive */ - public $responsetext = true; - - /** @var bool whether to include the question text in the archive */ - public $questiontext = true; - /** @var bool whether to include attachments (if there are) in the archive */ public $attachments = true; - /** @var string whether to shorten file and path names to workaround a Windows issue */ - public $shortennames = false; + /** @var string file format TXT or PDF */ + public $fileformat = 'pdf'; + + /** @var string base font family for PDF export */ + public $font = 'sansserif'; + + /** @var int font size for PDF export */ + public $fontsize = 12; /** @var string how to organise the sub folders in the archive (by question or by attempt) */ public $groupby = 'byattempt'; + /** @var float line spacing for PDF export */ + public $linespacing = 1; + + /** @var int bottom margin for PDF export */ + public $marginbottom = 20; + + /** @var int left margin for PDF export */ + public $marginleft = 20; + + /** @var int right margin for PDF export */ + public $marginright = 20; + + /** @var int top margin for PDF export */ + public $margintop = 20; + /** @var string whether to have the last name or the first name first */ public $nameordering = 'lastfirst'; + /** @var string page format for PDF export */ + public $pageformat = 'a4'; + + /** @var bool whether to include the question text in the archive */ + public $questiontext = true; + + /** @var bool whether to shorten file and path names to workaround a Windows issue */ + public $shortennames = false; + + /** @var string which source to use: plain-text summary or original HTML text */ + public $source = 'html'; + /** * Constructor * @@ -84,12 +109,21 @@ public function __construct($mode, $quiz, $cm, $course) { public function get_initial_form_data() { $toform = new stdClass(); - $toform->responsetext = $this->responsetext; - $toform->questiontext = $this->questiontext; $toform->attachments = $this->attachments; - $toform->shortennames = $this->shortennames; + $toform->fileformat = $this->fileformat; + $toform->font = $this->font; + $toform->fontsize = $this->fontsize; $toform->groupby = $this->groupby; + $toform->linespacing = $this->linespacing; + $toform->marginbottom = $this->marginbottom; + $toform->marginleft = $this->marginleft; + $toform->marginright = $this->marginright; + $toform->margintop = $this->margintop; $toform->nameordering = $this->nameordering; + $toform->pageformat = $this->pageformat; + $toform->questiontext = $this->questiontext; + $toform->shortennames = $this->shortennames; + $toform->source = $this->source; return $toform; } @@ -100,24 +134,42 @@ public function get_initial_form_data() { * @param object $fromform data from the settings form */ public function setup_from_form_data($fromform): void { - $this->responsetext = $fromform->responsetext; - $this->questiontext = $fromform->questiontext; $this->attachments = $fromform->attachments; - $this->shortennames = $fromform->shortennames; + $this->fileformat = $fromform->fileformat; + $this->font = $fromform->font ?? ''; + $this->fontsize = $fromform->fontsize ?? ''; $this->groupby = $fromform->groupby; + $this->linespacing = $fromform->linespacing ?? ''; + $this->marginbottom = $fromform->marginbottom ?? ''; + $this->marginleft = $fromform->marginleft ?? ''; + $this->marginright = $fromform->marginright ?? ''; + $this->margintop = $fromform->margintop ?? ''; $this->nameordering = $fromform->nameordering; + $this->pageformat = $fromform->pageformat ?? ''; + $this->questiontext = $fromform->questiontext; + $this->shortennames = $fromform->shortennames; + $this->source = $fromform->source ?? ''; } /** * Set the fields of this object from the URL parameters. */ public function setup_from_params() { - $this->responsetext = optional_param('responsetext', $this->responsetext, PARAM_BOOL); - $this->questiontext = optional_param('questiontext', $this->questiontext, PARAM_BOOL); $this->attachments = optional_param('attachments', $this->attachments, PARAM_BOOL); - $this->shortennames = optional_param('shortennames', $this->shortennames, PARAM_BOOL); + $this->fileformat = optional_param('fileformat', $this->fileformat, PARAM_ALPHA); + $this->font = optional_param('font', $this->font, PARAM_ALPHA); + $this->fontsize = optional_param('fontsize', $this->fontsize, PARAM_INT); $this->groupby = optional_param('groupby', $this->groupby, PARAM_ALPHA); + $this->linespacing = optional_param('linespacing', $this->linespacing, PARAM_FLOAT); + $this->marginbottom = optional_param('marginbottom', $this->marginbottom, PARAM_INT); + $this->marginleft = optional_param('marginleft', $this->marginleft, PARAM_INT); + $this->marginright = optional_param('marginright', $this->marginright, PARAM_INT); + $this->margintop = optional_param('margintop', $this->margintop, PARAM_INT); $this->nameordering = optional_param('nameordering', $this->nameordering, PARAM_ALPHA); + $this->pageformat = optional_param('pageformat', $this->pageformat, PARAM_ALPHA); + $this->questiontext = optional_param('questiontext', $this->questiontext, PARAM_BOOL); + $this->shortennames = optional_param('shortennames', $this->shortennames, PARAM_BOOL); + $this->source = optional_param('source', $this->source, PARAM_ALPHA); } /** @@ -135,8 +187,11 @@ public function update_user_preferences() { } /** - * Override parent method, because our settings cannot be incompatible. + * Deal with conflicting options, e.g. user requesting TXT output, but HTML source. */ public function resolve_dependencies() { + if ($this->fileformat === 'txt') { + $this->source = 'plain'; + } } } diff --git a/lang/en/quiz_essaydownload.php b/lang/en/quiz_essaydownload.php index a21ac1c..b220210 100644 --- a/lang/en/quiz_essaydownload.php +++ b/lang/en/quiz_essaydownload.php @@ -23,26 +23,55 @@ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['additionalsettings'] = 'Additional settings'; +$string['attachments'] = 'Attachments'; $string['byattempt'] = 'Attempt'; $string['byquestion'] = 'Question'; +$string['compatibility'] = 'Compatibility setting'; $string['errorfilename'] = 'error-{$a}.txt'; +$string['errorfontsize'] = 'Font size should be an integer between 6 and 50.'; +$string['errormargin'] = 'All page margins must be integers between 0 and 80.'; $string['errormessage'] = 'An internal error occurred. The archive is probably incomplete. Please contact the developers of the Essay responses downloader plugin (quiz_essaydownload) and send them the details below:'; $string['essaydownload'] = 'Download essay responses'; +$string['fileformat'] = 'File format'; +$string['fileformat_help'] = 'You can choose between two formats:
Go write your stuff!
', 'format' => FORMAT_HTML], + 'questiontext' => ['text' => 'Go write your stuff!', 'format' => FORMAT_PLAIN], ]); // Add a student and start an attempt. @@ -406,7 +419,7 @@ public function test_get_details_for_attempt_with_single_essay_question(): void // Submit a response and finish the attempt. $timenow = time(); - $tosubmit = [1 => ['answer' => 'Here we go.
', 'answerformat' => FORMAT_HTML]]; + $tosubmit = [1 => ['answer' => 'Here we go.', 'answerformat' => FORMAT_PLAIN]]; $attemptobj->process_submitted_actions($timenow, false, $tosubmit); $attemptobj->process_finish($timenow, false); @@ -453,7 +466,7 @@ public function test_get_details_for_attempt_with_two_essay_questions(): void { foreach ($questionsandanswers as $data) { quiz_essaydownload_test_helper::add_essay_question($questiongenerator, $quiz, [ 'name' => $data['name'], - 'questiontext' => ['text' => "{$data['text']}", 'format' => FORMAT_HTML], + 'questiontext' => ['text' => $data['text'], 'format' => FORMAT_PLAIN], ]); } @@ -465,8 +478,8 @@ public function test_get_details_for_attempt_with_two_essay_questions(): void { // Submit a response and finish the attempt. $timenow = time(); $tosubmit = [ - 1 => ['answer' => "{$questionsandanswers[1]['response']}
", 'answerformat' => FORMAT_HTML], - 2 => ['answer' => "{$questionsandanswers[2]['response']}
", 'answerformat' => FORMAT_HTML], + 1 => ['answer' => $questionsandanswers[1]['response'], 'answerformat' => FORMAT_PLAIN], + 2 => ['answer' => $questionsandanswers[2]['response'], 'answerformat' => FORMAT_PLAIN], ]; $attemptobj->process_submitted_actions($timenow, false, $tosubmit); $attemptobj->process_finish($timenow, false); @@ -511,7 +524,7 @@ public function test_get_details_for_attempt_with_one_essay_and_two_other_questi $this->add_two_regular_questions($questiongenerator, $quiz); quiz_essaydownload_test_helper::add_essay_question($questiongenerator, $quiz, [ 'name' => 'My Question Title / Test', - 'questiontext' => ['text' => 'Go write your stuff!
', 'format' => FORMAT_HTML], + 'questiontext' => ['text' => 'Go write your stuff!', 'format' => FORMAT_PLAIN], ]); // Add a student and start an attempt. @@ -525,7 +538,7 @@ public function test_get_details_for_attempt_with_one_essay_and_two_other_questi $tosubmit = [ 1 => ['answer' => 'frog'], 2 => ['answer' => '3.14'], - 3 => ['answer' => 'Here we go.
', 'answerformat' => FORMAT_HTML], + 3 => ['answer' => 'Here we go.', 'answerformat' => FORMAT_PLAIN], ]; $attemptobj->process_submitted_actions($timenow, false, $tosubmit); $attemptobj->process_finish($timenow, false); @@ -619,7 +632,7 @@ public function test_get_details_for_attempt_with_random_essay_question(): void // resolve to a shortanswer question. $timenow = time(); $tosubmit = [ - 1 => ['answer' => 'Foo Bar Quak.
', 'answerformat' => FORMAT_HTML], + 1 => ['answer' => 'Foo Bar Quak.', 'answerformat' => FORMAT_PLAIN], ]; $attemptobj->process_submitted_actions($timenow, false, $tosubmit); $attemptobj->process_finish($timenow, false); @@ -657,7 +670,7 @@ public function test_get_details_for_attempt_with_text_and_attachment(): void { $quiz = $this->create_test_quiz($course); quiz_essaydownload_test_helper::add_essay_question($questiongenerator, $quiz, [ 'name' => 'My Question Title / Test', - 'questiontext' => ['text' => 'Go write your stuff!
', 'format' => FORMAT_HTML], + 'questiontext' => ['text' => 'Go write your stuff!', 'format' => FORMAT_PLAIN], 'responseformat' => 'editorfilepicker', 'attachments' => 2, ]); @@ -675,8 +688,8 @@ public function test_get_details_for_attempt_with_text_and_attachment(): void { quiz_essaydownload_test_helper::save_file_to_draft_area($usercontextid, $attachementsdraftid, 'greeting.txt', 'Foobar'); $timenow = time(); $tosubmit = [1 => [ - 'answer' => 'Foo.
', - 'answerformat' => FORMAT_HTML, + 'answer' => 'Foo.', + 'answerformat' => FORMAT_PLAIN, 'answer:itemid' => 1, 'attachments' => $attachementsdraftid, ]]; @@ -694,8 +707,8 @@ public function test_get_details_for_attempt_with_text_and_attachment(): void { // Submit a response and finish the attempt. $timenow = time(); $tosubmit = [1 => [ - 'answer' => 'Here we go.
', - 'answerformat' => FORMAT_HTML, + 'answer' => 'Here we go.', + 'answerformat' => FORMAT_PLAIN, 'answer:itemid' => 1, 'attachments' => $attachementsdraftid, ]]; @@ -707,6 +720,14 @@ public function test_get_details_for_attempt_with_text_and_attachment(): void { list($currentgroup, $allstudentjoins, $groupstudentjoins, $allowedjoins) = $report->init('essaydownload', 'quiz_essaydownload_form', $quiz, $cm, $course); + // Use reflection to force text source to plain (i. e. summary). + $reflectedreport = new \ReflectionClass($report); + $reflectedoptions = $reflectedreport->getProperty('options'); + $reflectedoptions->setAccessible(true); + $options = new quiz_essaydownload_options('essaydownload', $quiz, $cm, $course); + $options->source = 'plain'; + $reflectedoptions->setValue($report, $options); + // Fetch the attemp using the report's API. $fetchedattempts = $report->get_attempts_and_names($groupstudentjoins); self::assertCount(1, $fetchedattempts); @@ -746,7 +767,7 @@ public function test_get_details_for_attempt_with_unanswered_question(): void { $quiz = $this->create_test_quiz($course); quiz_essaydownload_test_helper::add_essay_question($questiongenerator, $quiz, [ 'name' => 'My Question Title / Test', - 'questiontext' => ['text' => 'Go write your stuff!
', 'format' => FORMAT_HTML], + 'questiontext' => ['text' => 'Go write your stuff!', 'format' => FORMAT_PLAIN], ]); // Add a student and start an attempt. @@ -756,7 +777,7 @@ public function test_get_details_for_attempt_with_unanswered_question(): void { // Finish the attempt without submitting an answer. $timenow = time(); - $tosubmit = [1 => ['answer' => '', 'answerformat' => FORMAT_HTML]]; + $tosubmit = [1 => ['answer' => '', 'answerformat' => FORMAT_PLAIN]]; $attemptobj->process_submitted_actions($timenow, false, $tosubmit); $attemptobj->process_finish($timenow, false); @@ -786,4 +807,214 @@ public function test_get_details_for_attempt_with_unanswered_question(): void { self::assertEmpty($detail['attachments']); } } + + public function test_pdf_from_summary_when_input_is_html(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create a course and a quiz with an essay question. + $generator = $this->getDataGenerator(); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $course = $generator->create_course(); + $quiz = $this->create_test_quiz($course); + quiz_essaydownload_test_helper::add_essay_question($questiongenerator, $quiz, [ + 'name' => 'My Question Title / Test', + 'questiontext' => ['text' => 'Go write your stuff!
', 'format' => FORMAT_HTML], + ]); + + // Add a student and start an attempt. + $student = $generator->create_user(); + $generator->enrol_user($student->id, $course->id, 'student'); + list($quizobj, $quba, $attemptobj) = quiz_essaydownload_test_helper::start_attempt_at_quiz($quiz, $student); + + // Submit a response and finish the attempt. + $timenow = time(); + $tosubmit = [1 => ['answer' => 'Here we go.
', 'answerformat' => FORMAT_HTML]]; + $attemptobj->process_submitted_actions($timenow, false, $tosubmit); + $attemptobj->process_finish($timenow, false); + + $cm = get_coursemodule_from_id('quiz', $quiz->cmid); + $report = new quiz_essaydownload_report(); + list($currentgroup, $allstudentjoins, $groupstudentjoins, $allowedjoins) = + $report->init('essaydownload', 'quiz_essaydownload_form', $quiz, $cm, $course); + + // Use reflection to force options. + $reflectedreport = new \ReflectionClass($report); + $reflectedoptions = $reflectedreport->getProperty('options'); + $reflectedoptions->setAccessible(true); + $options = new quiz_essaydownload_options('essaydownload', $quiz, $cm, $course); + $options->source = 'plain'; + $reflectedoptions->setValue($report, $options); + + // Fetch the attemp using the report's API. + $fetchedattempts = $report->get_attempts_and_names($groupstudentjoins); + self::assertCount(1, $fetchedattempts); + + // Fetch the details. + $details = $report->get_details_for_attempt(array_keys($fetchedattempts)[0]); + + // We expect the result to be an array with one element. The data should match the + // second response. + self::assertCount(1, $details); + foreach ($details as $label => $detail) { + self::assertEquals('Question_1_-_My_Question_Title__Test', $label); + self::assertEquals('Go write YOUR stuff!', trim($detail['questiontext'])); + self::assertStringStartsWith('Here WE go.', $detail['responsetext']); + self::assertCount(0, $detail['attachments']); + } + } + + public function test_pdf_from_html_when_input_is_html(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create a course and a quiz with an essay question. + $generator = $this->getDataGenerator(); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $course = $generator->create_course(); + $quiz = $this->create_test_quiz($course); + quiz_essaydownload_test_helper::add_essay_question($questiongenerator, $quiz, [ + 'name' => 'My Question Title / Test', + 'questiontext' => ['text' => 'Go write your stuff!
', 'format' => FORMAT_HTML], + ]); + + // Add a student and start an attempt. + $student = $generator->create_user(); + $generator->enrol_user($student->id, $course->id, 'student'); + list($quizobj, $quba, $attemptobj) = quiz_essaydownload_test_helper::start_attempt_at_quiz($quiz, $student); + + // Submit a response and finish the attempt. + $timenow = time(); + $tosubmit = [1 => ['answer' => 'Here we go.
', 'answerformat' => FORMAT_HTML]]; + $attemptobj->process_submitted_actions($timenow, false, $tosubmit); + $attemptobj->process_finish($timenow, false); + + $cm = get_coursemodule_from_id('quiz', $quiz->cmid); + $report = new quiz_essaydownload_report(); + list($currentgroup, $allstudentjoins, $groupstudentjoins, $allowedjoins) = + $report->init('essaydownload', 'quiz_essaydownload_form', $quiz, $cm, $course); + + // Fetch the attemp using the report's API. + $fetchedattempts = $report->get_attempts_and_names($groupstudentjoins); + self::assertCount(1, $fetchedattempts); + + // Fetch the details. + $details = $report->get_details_for_attempt(array_keys($fetchedattempts)[0]); + + // We expect the result to be an array with one element. The data should match the + // second response. + self::assertCount(1, $details); + foreach ($details as $label => $detail) { + self::assertEquals('Question_1_-_My_Question_Title__Test', $label); + self::assertEquals('Go write your stuff!
', trim($detail['questiontext'])); + self::assertStringStartsWith('Here we go.
', $detail['responsetext']); + self::assertCount(0, $detail['attachments']); + } + } + + public function test_pdf_from_html_when_input_is_plaintext_with_newlines(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create a course and a quiz with an essay question. + $generator = $this->getDataGenerator(); + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $course = $generator->create_course(); + $quiz = $this->create_test_quiz($course); + quiz_essaydownload_test_helper::add_essay_question($questiongenerator, $quiz, [ + 'name' => 'My Question Title / Test', + 'questiontext' => ['text' => 'Go write your stuff!
', 'format' => FORMAT_HTML], + ]); + + // Add a student and start an attempt. + $student = $generator->create_user(); + $generator->enrol_user($student->id, $course->id, 'student'); + list($quizobj, $quba, $attemptobj) = quiz_essaydownload_test_helper::start_attempt_at_quiz($quiz, $student); + + // Submit a response and finish the attempt. + $timenow = time(); + $tosubmit = [1 => ['answer' => "Here\nwe\ngo.", 'answerformat' => FORMAT_PLAIN]]; + $attemptobj->process_submitted_actions($timenow, false, $tosubmit); + $attemptobj->process_finish($timenow, false); + + $cm = get_coursemodule_from_id('quiz', $quiz->cmid); + $report = new quiz_essaydownload_report(); + list($currentgroup, $allstudentjoins, $groupstudentjoins, $allowedjoins) = + $report->init('essaydownload', 'quiz_essaydownload_form', $quiz, $cm, $course); + + // Fetch the attemp using the report's API. + $fetchedattempts = $report->get_attempts_and_names($groupstudentjoins); + self::assertCount(1, $fetchedattempts); + + // Fetch the details. + $details = $report->get_details_for_attempt(array_keys($fetchedattempts)[0]); + + // We expect the result to be an array with one element. The data should match the + // second response. + self::assertCount(1, $details); + foreach ($details as $label => $detail) { + self::assertEquals('Question_1_-_My_Question_Title__Test', $label); + self::assertEquals('Go write your stuff!
', trim($detail['questiontext'])); + self::assertStringStartsWith("HereGo write your stuff!
', 'format' => FORMAT_HTML], + ]); + + // Add a student and start an attempt. + $student = $generator->create_user(); + $generator->enrol_user($student->id, $course->id, 'student'); + list($quizobj, $quba, $attemptobj) = quiz_essaydownload_test_helper::start_attempt_at_quiz($quiz, $student); + + // Submit a response and finish the attempt. + $timenow = time(); + $tosubmit = [1 => ['answer' => 'Here we go.
', 'answerformat' => FORMAT_HTML]]; + $attemptobj->process_submitted_actions($timenow, false, $tosubmit); + $attemptobj->process_finish($timenow, false); + + $cm = get_coursemodule_from_id('quiz', $quiz->cmid); + $report = new quiz_essaydownload_report(); + list($currentgroup, $allstudentjoins, $groupstudentjoins, $allowedjoins) = + $report->init('essaydownload', 'quiz_essaydownload_form', $quiz, $cm, $course); + + // Use reflection to force options. + $reflectedreport = new \ReflectionClass($report); + $reflectedoptions = $reflectedreport->getProperty('options'); + $reflectedoptions->setAccessible(true); + $options = new quiz_essaydownload_options('essaydownload', $quiz, $cm, $course); + $options->fileformat = 'txt'; + $options->source = 'plain'; + $reflectedoptions->setValue($report, $options); + + // Fetch the attemp using the report's API. + $fetchedattempts = $report->get_attempts_and_names($groupstudentjoins); + self::assertCount(1, $fetchedattempts); + + // Fetch the details. + $details = $report->get_details_for_attempt(array_keys($fetchedattempts)[0]); + + // We expect the result to be an array with one element. The data should match the + // second response. + self::assertCount(1, $details); + foreach ($details as $label => $detail) { + self::assertEquals('Question_1_-_My_Question_Title__Test', $label); + self::assertEquals('Go write YOUR stuff!', trim($detail['questiontext'])); + self::assertStringStartsWith('Here WE go.', $detail['responsetext']); + self::assertCount(0, $detail['attachments']); + } + } + }