MDL-71696 core_question: Changes for versioning

This commit adds the changes in questiontype base
to work with new question tables and the new structure in the
databse. Also needed for versioning.
This implementation will also introduct the question status
which allows a question to be in draft and ready status.
I also introduces changes to the base view where it shows
the latest version of the questions. The view of versions
for a question is not implemented in this commit.
This implementation will also introduce changes in the core
qtype plugins to support versioning and the changed
db schema.
This commit is contained in:
Guillermo Gomez
2022-02-03 22:22:44 +11:00
committed by Safat Shahin
parent 7547f3e9f0
commit c6cfca2a08
118 changed files with 2946 additions and 1397 deletions
+128 -21
View File
@@ -27,6 +27,7 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/engine/lib.php');
require_once($CFG->libdir . '/questionlib.php');
/**
@@ -367,8 +368,9 @@ class question_type {
// The actual update/insert done with multiple DB access, so we do it in a transaction.
$transaction = $DB->start_delegated_transaction ();
list($question->category) = explode(',', $form->category);
$context = $this->get_context_by_category_id($question->category);
list($form->category) = explode(',', $form->category);
$context = $this->get_context_by_category_id($form->category);
$question->category = $form->category;
// This default implementation is suitable for most
// question types.
@@ -425,24 +427,107 @@ class question_type {
} else {
$category = $form->category;
}
if (!$DB->record_exists('question',
['idnumber' => $form->idnumber, 'category' => $category])) {
$sql = "SELECT qbe.id
FROM {question_bank_entry} qbe
WHERE qbe.idnumber = :idnumber
AND qbe.questioncategoryid = :categoryid";
if (!$DB->record_exists_sql($sql,
['idnumber' => $form->idnumber, 'categoryid' => $category])) {
$question->idnumber = $form->idnumber;
}
}
} else {
$question->idnumber = null;
}
// Only create a new bank entry if the question is not a new version (New question or duplicating a question).
$questionbankentry = null;
if (isset($question->id)) {
$oldparent = $question->id;
if (!empty($question->id)) {
// Get the bank entry record where the question is referenced.
$questionbankentry = get_question_bank_entry($question->id);
}
}
// Get the bank entry old id (this is when there are questions related with a parent, e.g.: qtype_multianswers).
if (isset($question->oldid)) {
if (!empty($question->oldid)) {
$questionbankentry = get_question_bank_entry($question->oldid);
}
}
// Always creates a new question and version record.
// Set the unique code.
$question->stamp = make_unique_id_code();
$question->createdby = $USER->id;
$question->timecreated = time();
// Idnumber validation.
$question->idnumber = null;
if (isset($form->idnumber)) {
if ((string) $form->idnumber === '') {
$question->idnumber = null;
} else {
// While this check already exists in the form validation,
// this is a backstop preventing unnecessary errors.
// Only set the idnumber if it has changed and will not cause a unique index violation.
if (strpos($form->category, ',') !== false) {
list($category, $categorycontextid) = explode(',', $form->category);
} else {
$category = $form->category;
}
$params = ['idnumber' => $form->idnumber, 'categoryid' => $category];
$andcondition = '';
if (isset($question->id) && isset($questionbankentry->id)) {
$andcondition = 'AND qbe.id != :notid';
$params['notid'] = $questionbankentry->id;
}
$sql = "SELECT qbe.id
FROM {question_bank_entries} qbe
WHERE qbe.idnumber = :idnumber
AND qbe.questioncategoryid = :categoryid
$andcondition";
if (!$DB->record_exists_sql($sql, $params)) {
$question->idnumber = $form->idnumber;
}
}
}
// If the question is new, create it.
$newquestion = false;
if (empty($question->id)) {
// Set the unique code.
$question->stamp = make_unique_id_code();
$question->createdby = $USER->id;
$question->timecreated = time();
$question->id = $DB->insert_record('question', $question);
$newquestion = true;
// Create the question.
$question->id = $DB->insert_record('question', $question);
if (!$questionbankentry) {
// Create a record for question_bank_entries, question_versions and question_references.
$questionbankentry = new \stdClass();
$questionbankentry->questioncategoryid = $form->category;
$questionbankentry->idnumber = $question->idnumber;
$questionbankentry->ownerid = $question->createdby;
$questionbankentry->id = $DB->insert_record('question_bank_entries', $questionbankentry);
} else {
$questionbankentryold = new \stdClass();
$questionbankentryold->id = $questionbankentry->id;
$questionbankentryold->idnumber = $question->idnumber;
$DB->update_record('question_bank_entries', $questionbankentryold);
}
// Create question_versions records.
$questionversion = new \stdClass();
$questionversion->questionbankentryid = $questionbankentry->id;
$questionversion->questionid = $question->id;
// Get the version and status from the parent question if parent is set.
if (!$question->parent) {
// Get the status field. It comes from the form, but for testing we can.
$status = $form->status ?? $question->status ?? \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
$questionversion->version = get_next_version($questionbankentry->id);
$questionversion->status = $status;
} else {
$parentversion = get_question_version($form->parent);
$questionversion->version = $parentversion[array_key_first($parentversion)]->version;
$questionversion->status = $parentversion[array_key_first($parentversion)]->status;
}
$questionversion->id = $DB->insert_record('question_versions', $questionversion);
// Now, whether we are updating a existing question, or creating a new
// one, we have to do the files processing and update the record.
// Question already exists, update.
@@ -465,12 +550,16 @@ class question_type {
// Now to save all the answers and type-specific options.
$form->id = $question->id;
$form->qtype = $question->qtype;
$form->category = $question->category;
$form->questiontext = $question->questiontext;
$form->questiontextformat = $question->questiontextformat;
// Current context.
$form->context = $context;
// Old parent question id is used when there are questions related with a parent, e.g.: qtype_multianswers).
if (isset($oldparent)) {
$form->oldparent = $oldparent;
} else {
$form->oldparent = $question->parent;
}
$result = $this->save_question_options($form);
if (!empty($result->error)) {
@@ -486,10 +575,6 @@ class question_type {
'$result->noticeyesno no longer supported in save_question.');
}
// Give the question a unique version stamp determined by question_hash().
$DB->set_field('question', 'version', question_hash($question),
array('id' => $question->id));
if ($newquestion) {
// Log the creation of this question.
$event = \core\event\question_created::create_from_question_instance($question, $context);
@@ -945,14 +1030,14 @@ class question_type {
$question->length = $questiondata->length;
$question->penalty = $questiondata->penalty;
$question->stamp = $questiondata->stamp;
$question->version = $questiondata->version;
$question->hidden = $questiondata->hidden;
$question->idnumber = $questiondata->idnumber;
$question->timecreated = $questiondata->timecreated;
$question->timemodified = $questiondata->timemodified;
$question->createdby = $questiondata->createdby;
$question->modifiedby = $questiondata->modifiedby;
$this->initialise_question_extra_record($question, $questiondata);
// Fill extra question fields values.
$extraquestionfields = $this->extra_question_fields();
if (is_array($extraquestionfields)) {
@@ -969,6 +1054,27 @@ class question_type {
$this->initialise_custom_fields($question, $questiondata);
}
/**
* Initialise the extra question 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_extra_record(question_definition $question, $questiondata) {
$fields = [
'status',
'versionid',
'version',
'questionbankentryid',
'idnumber',
];
foreach ($fields as $field) {
if (isset($questiondata->{$field})) {
$question->{$field} = $questiondata->{$field};
}
}
}
/**
* Initialise question_definition::hints field.
* @param question_definition $question the question_definition we are creating.
@@ -1320,6 +1426,7 @@ class question_type {
$form->questiontext = 'test question, generated by script';
$form->defaultmark = 1;
$form->penalty = 0.3333333;
$form->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
$form->generalfeedback = "Well done";
$context = context_course::instance($courseid);