diff --git a/lang/en/question.php b/lang/en/question.php index 3f31f78afded8..de670c2a63503 100644 --- a/lang/en/question.php +++ b/lang/en/question.php @@ -150,6 +150,7 @@ $string['exporterror'] = 'Errors occur during exporting!'; $string['exportfilename'] = 'questions'; $string['exportnameformat'] = '%Y%m%d-%H%M'; +$string['exportonequestion'] = 'Download this question in Moodle XML format'; $string['exportquestions'] = 'Export questions to file'; $string['exportquestions_help'] = 'This function enables the export of a complete category (and any subcategories) of questions to file. Please note that, depending on the file format selected, some question data and certain question types may not be exported.'; $string['exportquestions_link'] = 'question/export'; diff --git a/lib/questionlib.php b/lib/questionlib.php index 1439ea87364d5..61764ed59ea48 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -2251,6 +2251,33 @@ function question_make_export_url($contextid, $categoryid, $format, $withcategor "/{$withcontexts}/{$filename}", true); } +/** + * Get the URL to export a single question (exportone.php). + * + * @param stdClass|question_definition $question the question definition as obtained from + * question_bank::load_question_data() or question_bank::make_question(). + * (Only ->id and ->contextid are used.) + * @return moodle_url the requested URL. + */ +function question_get_export_single_question_url($question) { + $params = ['id' => $question->id, 'sesskey' => sesskey()]; + $context = context::instance_by_id($question->contextid); + switch ($context->contextlevel) { + case CONTEXT_MODULE: + $params['cmid'] = $context->instanceid; + break; + + case CONTEXT_COURSE: + $params['courseid'] = $context->instanceid; + break; + + default: + $params['courseid'] = SITEID; + } + + return new moodle_url('/question/exportone.php', $params); +} + /** * Return a list of page types * @param string $pagetype current page type diff --git a/lib/tests/questionlib_test.php b/lib/tests/questionlib_test.php index cab87536c9c1f..ced468bbfb253 100644 --- a/lib/tests/questionlib_test.php +++ b/lib/tests/questionlib_test.php @@ -2000,4 +2000,34 @@ public function test_question_categorylist_parents() { $this->assertEquals($cat1->id, $parentcategories[1]); $this->assertCount(2, $parentcategories); } + + public function test_question_get_export_single_question_url() { + $generator = $this->getDataGenerator(); + + // Create a course and an activity. + $course = $generator->create_course(); + $quiz = $generator->create_module('quiz', ['course' => $course->id]); + + // Create a question in each place. + $questiongenerator = $generator->get_plugin_generator('core_question'); + $courseqcat = $questiongenerator->create_question_category(['contextid' => context_course::instance($course->id)->id]); + $courseq = $questiongenerator->create_question('truefalse', null, ['category' => $courseqcat->id]); + $quizqcat = $questiongenerator->create_question_category(['contextid' => context_module::instance($quiz->cmid)->id]); + $quizq = $questiongenerator->create_question('truefalse', null, ['category' => $quizqcat->id]); + $systemqcat = $questiongenerator->create_question_category(); + $systemq = $questiongenerator->create_question('truefalse', null, ['category' => $systemqcat->id]); + + // Verify some URLs. + $this->assertEquals(new moodle_url('/question/exportone.php', + ['id' => $courseq->id, 'courseid' => $course->id, 'sesskey' => sesskey()]), + question_get_export_single_question_url(question_bank::load_question_data($courseq->id))); + + $this->assertEquals(new moodle_url('/question/exportone.php', + ['id' => $quizq->id, 'cmid' => $quiz->cmid, 'sesskey' => sesskey()]), + question_get_export_single_question_url(question_bank::load_question($quizq->id))); + + $this->assertEquals(new moodle_url('/question/exportone.php', + ['id' => $systemq->id, 'courseid' => SITEID, 'sesskey' => sesskey()]), + question_get_export_single_question_url(question_bank::load_question($systemq->id))); + } } diff --git a/question/exportone.php b/question/exportone.php new file mode 100644 index 0000000000000..c2de2bc2ce1a1 --- /dev/null +++ b/question/exportone.php @@ -0,0 +1,79 @@ +. + +/** + * Script to download the export of a single question. + * + * @copyright 2015 the Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__.'/../config.php'); + +require_once($CFG->libdir . '/questionlib.php'); +require_once($CFG->dirroot . '/question/format/xml/format.php'); + +// Get the parameters from the URL. +$questionid = required_param('id', PARAM_INT); +$cmid = optional_param('cmid', 0, PARAM_INT); +$courseid = optional_param('courseid', 0, PARAM_INT); +$urlparams = ['id' => $questionid, 'sesskey' => sesskey()]; + +if ($cmid) { + $cm = get_coursemodule_from_id(null, $cmid); + require_login($cm->course, false, $cm); + $thiscontext = context_module::instance($cmid); + $urlparams['cmid'] = $cmid; +} else if ($courseid) { + require_login($courseid, false); + $thiscontext = context_course::instance($courseid); + $urlparams['courseid'] = $courseid; +} else { + print_error('missingcourseorcmid', 'question'); +} +require_sesskey(); + +// Load the necessary data. +$contexts = new question_edit_contexts($thiscontext); +$questiondata = question_bank::load_question_data($questionid); + +// Check permissions. +question_require_capability_on($questiondata, 'view'); + +// Initialise $PAGE. Nothing is output, so this does not really matter. Just avoids notices. +$nexturl = new moodle_url('/question/type/stack/questiontestrun.php', $urlparams); +$PAGE->set_url('/question/exportone.php', $urlparams); +$PAGE->set_heading($COURSE->fullname); +$PAGE->set_pagelayout('admin'); + +// Set up the export format. +$qformat = new qformat_xml(); +$filename = question_default_export_filename($COURSE, $questiondata) . + $qformat->export_file_extension(); +$qformat->setContexts($contexts->having_one_edit_tab_cap('export')); +$qformat->setCourse($COURSE); +$qformat->setQuestions([$questiondata]); +$qformat->setCattofile(false); +$qformat->setContexttofile(false); + +// Do the export. +if (!$qformat->exportpreprocess()) { + send_file_not_found(); +} +if (!$content = $qformat->exportprocess(true)) { + send_file_not_found(); +} +send_file($content, $filename, 0, 0, true, true, $qformat->mime_type()); diff --git a/question/preview.php b/question/preview.php index 7e0a719d2553c..2f0908b8bed5d 100644 --- a/question/preview.php +++ b/question/preview.php @@ -274,6 +274,12 @@ } print_collapsible_region_end(); +// Output a link to export this single question. +if (question_has_capability_on($question, 'view')) { + echo html_writer::link(question_get_export_single_question_url($question), + get_string('exportonequestion', 'question')); +} + // Display the settings form. $optionsform->display(); diff --git a/question/tests/behat/preview_question.feature b/question/tests/behat/preview_question.feature index 91d2c23167280..5cacce52953b1 100644 --- a/question/tests/behat/preview_question.feature +++ b/question/tests/behat/preview_question.feature @@ -78,3 +78,7 @@ Feature: A teacher can preview questions in the question bank Scenario: Preview lets the teacher "Fill in correct response" while previewing When I press "Fill in correct responses" Then the field "Answer:" matches value "3.14" + + @javascript @_switch_window + Scenario: Preview has an option to export the individual quesiton. + Then following "Download this question in Moodle XML format" should download between "1000" and "2500" bytes