diff --git a/.gitignore b/.gitignore
index a67a00f6..b8687642 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
# Since package.json only contains dev dependencies, comitting the lockfile makes little sence.
/package-lock.json
/node_modules
+
+.idea
diff --git a/classes/question_service.php b/classes/question_service.php
index 0c2008cb..2789c5ca 100644
--- a/classes/question_service.php
+++ b/classes/question_service.php
@@ -70,6 +70,7 @@ public function get_question(int $questionid): object {
$result->qpy_package_hash = $package->hash;
$result->qpy_state = $record->state;
+ $result->shuffleanswers = $record->shuffleanswers;
}
return $result;
@@ -124,6 +125,10 @@ public function upsert_question(object $question): void {
$update["pkgversionid"] = $pkgversionid;
}
+ if ($question->shuffleanswers !== $existingrecord->shuffleanswers) {
+ $update["shuffleanswers"] = $question->shuffleanswers;
+ }
+
if (count($update) > 1) {
$DB->update_record(self::QUESTION_TABLE, (object)$update);
}
@@ -134,6 +139,7 @@ public function upsert_question(object $question): void {
"feedback" => "",
"pkgversionid" => $pkgversionid,
"state" => $response->state,
+ "shuffleanswers" => $question->shuffleanswers,
]);
}
}
diff --git a/classes/question_ui_renderer.php b/classes/question_ui_renderer.php
index a2237f60..ddf56361 100644
--- a/classes/question_ui_renderer.php
+++ b/classes/question_ui_renderer.php
@@ -53,6 +53,9 @@ class question_ui_renderer {
/** @var question_metadata|null $metadata */
private ?question_metadata $metadata = null;
+ /** @var boolean Whether the questions answers should be shuffled. */
+ public bool $shuffleanswers;
+
/**
* Parses the given XML and initializes a new {@see question_ui_renderer} instance.
*
@@ -282,14 +285,16 @@ private function shuffle_contents(\DOMXPath $xpath): void {
$childelements[] = $child;
}
}
- shuffle($childelements);
+ if ($this->shuffleanswers) {
+ shuffle($childelements);
+ }
// Iterate over children, replacing elements with random ones while copying everything else.
$i = 1;
while ($element->hasChildNodes()) {
$child = $element->firstChild;
if ($child instanceof DOMElement) {
- $child = array_pop($childelements);
+ $child = array_shift($childelements);
$newelement->appendChild($child);
$this->replace_shuffled_indices($xpath, $child, $i++);
} else {
diff --git a/db/install.xml b/db/install.xml
index 8b5ddc0b..e3b25bb1 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -28,6 +28,7 @@
+
diff --git a/edit_questionpy_form.php b/edit_questionpy_form.php
index a53560e8..fb960e56 100644
--- a/edit_questionpy_form.php
+++ b/edit_questionpy_form.php
@@ -129,6 +129,10 @@ protected function definition_inner($mform): void {
$mform->addElement('hidden', 'qpy_package_hash', '');
$mform->setType('qpy_package_hash', PARAM_RAW);
+ // Whether the content of a qpy:shuffle-contents can be randomly shuffled.
+ $mform->addElement('hidden', 'shuffleanswers', get_config('qtype_questionpy', 'shuffleanswers'));
+ $mform->setType('shuffleanswers', PARAM_BOOL);
+
// While not a button, we need a way of telling moodle not to save the submitted data to the question when the
// package has simply been changed. The hidden element is enabled from JS when a package is selected or changed.
$mform->registerNoSubmitButton('qpy_package_changed');
diff --git a/lang/en/qtype_questionpy.php b/lang/en/qtype_questionpy.php
index 05651cef..c4edf883 100644
--- a/lang/en/qtype_questionpy.php
+++ b/lang/en/qtype_questionpy.php
@@ -34,6 +34,7 @@
$string['server_password_description'] = 'The Password to access the Application Server';
$string['server_timeout'] = 'Server timeout time';
$string['server_timeout_description'] = 'Server timeout time in seconds';
+$string['heading_packages'] = 'Packages';
$string['max_package_size_kb'] = 'Maximum file size of a QuestionPy package';
$string['max_package_size_kb_description'] = 'Maximum file size in kB';
$string['packages_subheading'] = 'Packages';
@@ -52,6 +53,10 @@
$string['server_info_requests_in_process'] = 'Requests in process';
$string['server_info_requests_in_queue'] = 'Requests in queue';
+// Question settings.
+$string['shuffleanswers'] = 'Shuffle the contents?';
+$string['shuffleanswers_desc'] = 'Whether the content of a qpy:shuffle-contents should be randomly shuffled for each attempt by default.';
+
// Package upload.
$string['formerror_noqpy_package'] = 'Selected file must be of type .qpy';
diff --git a/question.php b/question.php
index 5f629397..655feb7f 100644
--- a/question.php
+++ b/question.php
@@ -45,6 +45,8 @@ class qtype_questionpy_question extends question_graded_automatically_with_count
private string $packagehash;
/** @var string */
private string $questionstate;
+ /** @var boolean Whether the questions answers should be shuffled. */
+ public bool $shuffleanswers;
// Properties which do change between attempts (i.e. are modified by start_attempt and apply_attempt_state).
/** @var string */
@@ -92,6 +94,7 @@ public function start_attempt(question_attempt_step $step, $variant): void {
$this->scoringstate = null;
$this->ui = new question_ui_renderer($attempt->ui->content, $attempt->ui->placeholders);
+ $this->ui->shuffleanswers = $this->shuffleanswers;
}
/**
@@ -123,6 +126,7 @@ public function apply_attempt_state(question_attempt_step $step) {
$attempt = $this->api->view_attempt($this->packagehash, $this->questionstate, $this->attemptstate,
$this->scoringstate);
$this->ui = new question_ui_renderer($attempt->ui->content, $attempt->ui->placeholders);
+ $this->ui->shuffleanswers = $this->shuffleanswers;
}
/**
@@ -223,6 +227,7 @@ public function grade_response(array $response): array {
$response
);
$this->ui = new question_ui_renderer($attemptscored->ui->content, $attemptscored->ui->placeholders);
+ $this->ui->shuffleanswers = $this->shuffleanswers;
// TODO: Persist scoring state. We need to set a qtvar, but we don't have access to the pending step here.
$this->scoringstate = $attemptscored->scoringstate;
switch ($attemptscored->scoringcode) {
diff --git a/questiontype.php b/questiontype.php
index 401d7f62..79290b79 100644
--- a/questiontype.php
+++ b/questiontype.php
@@ -145,4 +145,14 @@ public function get_question_options($question): bool {
protected function make_question_instance($questiondata) {
return new qtype_questionpy_question($questiondata->qpy_package_hash, $questiondata->qpy_state);
}
+
+ /**
+ * Initialise the question_definition fields.
+ * @param question_definition $question the question_definition we are creating.
+ * @param object $questiondata the question data loaded from the database.
+ */
+ protected function initialise_question_instance(question_definition $question, $questiondata) {
+ parent::initialise_question_instance($question, $questiondata);
+ $question->shuffleanswers = $questiondata->shuffleanswers;
+ }
}
diff --git a/renderer.php b/renderer.php
index 4e2afc6a..1cef094c 100644
--- a/renderer.php
+++ b/renderer.php
@@ -53,8 +53,19 @@ public function head_code(question_attempt $qa) {
* @throws coding_exception
*/
public function formulation_and_controls(question_attempt $qa, question_display_options $options): string {
+ global $DB;
$question = $qa->get_question();
assert($question instanceof qtype_questionpy_question);
+
+ // Check if we are in a quiz context.
+ if ($this->page->context->contextlevel == CONTEXT_MODULE && $this->page->cm->modname == 'quiz') {
+ $quiz = $DB->get_record('quiz', ['id' => $this->page->cm->instance]);
+ if ($quiz) {
+ // Access the shuffleanswers property of the quiz.
+ $question->ui->shuffleanswers = $quiz->shuffleanswers;
+ }
+ }
+
return $question->ui->render_formulation($qa, $options);
}
diff --git a/settings.php b/settings.php
index dc62fb4c..bf44abe1 100644
--- a/settings.php
+++ b/settings.php
@@ -56,7 +56,7 @@
// Package settings.
$settings->add(new admin_setting_heading(
'qtype_questionpy/heading_packages',
- 'Packages',
+ new lang_string('heading_packages', 'qtype_questionpy'),
null
));
@@ -71,6 +71,12 @@
5
));
+ // Question settings.
+ $settings->add(new admin_setting_configcheckbox('qtype_questionpy/shuffleanswers',
+ new lang_string('shuffleanswers', 'qtype_questionpy'),
+ new lang_string('shuffleanswers_desc', 'qtype_questionpy'), '1'));
+
+ // Server Status/Info.
$settings->add(new admin_setting_heading(
'qtype_questionpy/server_info',
new lang_string('server_info_heading', 'qtype_questionpy'),