Merge branch 'MDL-24408_2' of https://github.com/oasychev/moodle
This commit is contained in:
@@ -577,9 +577,68 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$question->feedback[$key]['format'] = $answer->feedbackformat;
|
||||
$key++;
|
||||
}
|
||||
|
||||
// Now process extra answer fields.
|
||||
$extraanswerfields = question_bank::get_qtype($question->qtype)->extra_answer_fields();
|
||||
if (is_array($extraanswerfields)) {
|
||||
// Omit table name.
|
||||
array_shift($extraanswerfields);
|
||||
$question = $this->data_preprocessing_extra_answer_fields($question, $extraanswerfields);
|
||||
}
|
||||
|
||||
return $question;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the necessary preprocessing for the extra answer fields.
|
||||
*
|
||||
* Questions that do something not trivial when editing extra answer fields
|
||||
* will want to override this.
|
||||
* @param object $question the data being passed to the form.
|
||||
* @param array $extraanswerfields extra answer fields (without table name).
|
||||
* @return object $question the modified data.
|
||||
*/
|
||||
protected function data_preprocessing_extra_answer_fields($question, $extraanswerfields) {
|
||||
// Setting $question->$field[$key] won't work in PHP, so we need set an array of answer values to $question->$field.
|
||||
// As we may have several extra fields with data for several answers in each, we use an array of arrays.
|
||||
// Index in $extrafieldsdata is an extra answer field name, value - array of it's data for each answer.
|
||||
$extrafieldsdata = array();
|
||||
// First, prepare an array if empty arrays for each extra answer fields data.
|
||||
foreach ($extraanswerfields as $field) {
|
||||
$extrafieldsdata[$field] = array();
|
||||
}
|
||||
|
||||
// Fill arrays with data from $question->options->answers.
|
||||
$key = 0;
|
||||
foreach ($question->options->answers as $answer) {
|
||||
foreach ($extraanswerfields as $field) {
|
||||
// See hack comment in {@link data_preprocessing_answers()}.
|
||||
unset($this->_form->_defaultValues["$field[$key]"]);
|
||||
$extrafieldsdata[$field][$key] = $this->data_preprocessing_extra_answer_field($answer, $field);
|
||||
}
|
||||
$key++;
|
||||
}
|
||||
|
||||
// Set this data in the $question object.
|
||||
foreach ($extraanswerfields as $field) {
|
||||
$question->$field = $extrafieldsdata[$field];
|
||||
}
|
||||
return $question;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perfmorm preprocessing for particular extra answer field.
|
||||
*
|
||||
* Questions with non-trivial DB - form element relationship will
|
||||
* want to override this.
|
||||
* @param object $answer an answer object to get extra field from.
|
||||
* @param string $field extra answer field name.
|
||||
* @return field value to be set to the form.
|
||||
*/
|
||||
protected function data_preprocessing_extra_answer_field($answer, $field) {
|
||||
return $answer->$field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the necessary preprocessing for the fields added by
|
||||
* {@link add_combined_feedback_fields()}.
|
||||
|
||||
@@ -433,7 +433,7 @@ class question_type {
|
||||
* Saves question-type specific options
|
||||
*
|
||||
* This is called by {@link save_question()} to save the question-type specific data
|
||||
* @return object $result->error or $result->noticeyesno or $result->notice
|
||||
* @return object $result->error or $result->notice
|
||||
* @param object $question This holds the information from the editing form,
|
||||
* it is not a standard question object.
|
||||
*/
|
||||
@@ -461,9 +461,162 @@ class question_type {
|
||||
|
||||
$DB->{$function}($question_extension_table, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the answers, with any extra data.
|
||||
*
|
||||
* Questions that use answers will call it from {@link save_question_options()}.
|
||||
* @param object $question This holds the information from the editing form,
|
||||
* it is not a standard question object.
|
||||
* @return object $result->error or $result->notice
|
||||
*/
|
||||
public function save_question_answers($question) {
|
||||
global $DB;
|
||||
|
||||
$context = $question->context;
|
||||
$oldanswers = $DB->get_records('question_answers',
|
||||
array('question' => $question->id), 'id ASC');
|
||||
|
||||
// We need separate arrays for answers and extra answer data, so no JOINS there.
|
||||
$extraanswerfields = $this->extra_answer_fields();
|
||||
// TODO save the answers, with any extra data.
|
||||
$isextraanswerfields = is_array($extraanswerfields);
|
||||
$extraanswertable = '';
|
||||
$oldanswerextras = array();
|
||||
if ($isextraanswerfields) {
|
||||
$extraanswertable = array_shift($extraanswerfields);
|
||||
if (!empty($oldanswers)) {
|
||||
$oldanswerextras = $DB->get_records_sql("SELECT * FROM {{$extraanswertable}} WHERE " .
|
||||
'answerid IN (SELECT id FROM {question_answers} WHERE question = ' . $question->id . ')' );
|
||||
}
|
||||
}
|
||||
|
||||
// Insert all the new answers.
|
||||
foreach ($question->answer as $key => $answerdata) {
|
||||
// Check for, and ignore, completely blank answer from the form.
|
||||
if ($this->is_answer_empty($question, $key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update an existing answer if possible.
|
||||
$answer = array_shift($oldanswers);
|
||||
if (!$answer) {
|
||||
$answer = new stdClass();
|
||||
$answer->question = $question->id;
|
||||
$answer->answer = '';
|
||||
$answer->feedback = '';
|
||||
$answer->id = $DB->insert_record('question_answers', $answer);
|
||||
}
|
||||
|
||||
$answer = $this->fill_answer_fields($answer, $question, $key, $context);
|
||||
$DB->update_record('question_answers', $answer);
|
||||
|
||||
if ($isextraanswerfields) {
|
||||
// Check, if this answer contains some extra field data.
|
||||
if ($this->is_extra_answer_fields_empty($question, $key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$answerextra = array_shift($oldanswerextras);
|
||||
if (!$answerextra) {
|
||||
$answerextra = new stdClass();
|
||||
$answerextra->answerid = $answer->id;
|
||||
// Avoid looking for correct default for any possible DB field type
|
||||
// by setting real values.
|
||||
$answerextra = $this->fill_extra_answer_fields($answerextra, $question, $key, $context, $extraanswerfields);
|
||||
$answerextra->id = $DB->insert_record($extraanswertable, $answerextra);
|
||||
} else {
|
||||
// Update answerid, as record may be reused from another answer.
|
||||
$answerextra->answerid = $answer->id;
|
||||
$answerextra = $this->fill_extra_answer_fields($answerextra, $question, $key, $context, $extraanswerfields);
|
||||
$DB->update_record($extraanswertable, $answerextra);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isextraanswerfields) {
|
||||
// Delete any left over extra answer fields records.
|
||||
$oldanswerextraids = array();
|
||||
foreach ($oldanswerextras as $oldextra) {
|
||||
$oldanswerextraids[] = $oldextra->id;
|
||||
}
|
||||
$DB->delete_records_list($extraanswertable, 'id', $oldanswerextraids);
|
||||
}
|
||||
|
||||
// Delete any left over old answer records.
|
||||
$fs = get_file_storage();
|
||||
foreach ($oldanswers as $oldanswer) {
|
||||
$fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id);
|
||||
$DB->delete_records('question_answers', array('id' => $oldanswer->id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true is answer with the $key is empty in the question data and should not be saved in DB.
|
||||
*
|
||||
* The questions using question_answers table may want to overload this. Default code will work
|
||||
* for shortanswer and similar question types.
|
||||
* @param object $questiondata This holds the information from the question editing form or import.
|
||||
* @param int $key A key of the answer in question.
|
||||
* @return bool True if answer shouldn't be saved in DB.
|
||||
*/
|
||||
protected function is_answer_empty($questiondata, $key) {
|
||||
return trim($questiondata->answer[$key]) == '' && $questiondata->fraction[$key] == 0 &&
|
||||
html_is_blank($questiondata->feedback[$key]['text']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return $answer, filling necessary fields for the question_answers table.
|
||||
*
|
||||
* The questions using question_answers table may want to overload this. Default code will work
|
||||
* for shortanswer and similar question types.
|
||||
* @param stdClass $answer Object to save data.
|
||||
* @param object $questiondata This holds the information from the question editing form or import.
|
||||
* @param int $key A key of the answer in question.
|
||||
* @param object $context needed for working with files.
|
||||
* @return $answer answer with filled data.
|
||||
*/
|
||||
protected function fill_answer_fields($answer, $questiondata, $key, $context) {
|
||||
$answer->answer = $questiondata->answer[$key];
|
||||
$answer->fraction = $questiondata->fraction[$key];
|
||||
$answer->feedback = $this->import_or_save_files($questiondata->feedback[$key],
|
||||
$context, 'question', 'answerfeedback', $answer->id);
|
||||
$answer->feedbackformat = $questiondata->feedback[$key]['format'];
|
||||
return $answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if extra answer fields for answer with the $key is empty
|
||||
* in the question data and should not be saved in DB.
|
||||
*
|
||||
* Questions where extra answer fields are optional will want to overload this.
|
||||
* @param object $questiondata This holds the information from the question editing form or import.
|
||||
* @param int $key A key of the answer in question.
|
||||
* @return bool True if extra answer data shouldn't be saved in DB.
|
||||
*/
|
||||
protected function is_extra_answer_fields_empty($questiondata, $key) {
|
||||
// No extra answer data in base class.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return $answerextra, filling necessary fields for the extra answer fields table.
|
||||
*
|
||||
* The questions may want to overload it to save files or do other data processing.
|
||||
* @param stdClass $answerextra Object to save data.
|
||||
* @param object $questiondata This holds the information from the question editing form or import.
|
||||
* @param int $key A key of the answer in question.
|
||||
* @param object $context needed for working with files.
|
||||
* @param array $extraanswerfields extra answer fields (without table name).
|
||||
* @return $answer answerextra with filled data.
|
||||
*/
|
||||
protected function fill_extra_answer_fields($answerextra, $questiondata, $key, $context, $extraanswerfields) {
|
||||
foreach ($extraanswerfields as $field) {
|
||||
// The $questiondata->$field[$key] won't work in PHP, break it down to two strings of code.
|
||||
$fieldarray = $questiondata->$field;
|
||||
$answerextra->$field = $fieldarray[$key];
|
||||
}
|
||||
return $answerextra;
|
||||
}
|
||||
|
||||
public function save_hints($formdata, $withparts = false) {
|
||||
@@ -645,15 +798,17 @@ class question_type {
|
||||
|
||||
$extraanswerfields = $this->extra_answer_fields();
|
||||
if (is_array($extraanswerfields)) {
|
||||
$answer_extension_table = array_shift($extraanswerfields);
|
||||
$answerextensiontable = array_shift($extraanswerfields);
|
||||
// Use LEFT JOIN in case not every answer has extra data.
|
||||
$question->options->answers = $DB->get_records_sql("
|
||||
SELECT qa.*, qax." . implode(', qax.', $extraanswerfields) . "
|
||||
FROM {question_answers} qa, {{$answer_extension_table}} qax
|
||||
WHERE qa.question = ? AND qax.answerid = qa.id
|
||||
SELECT qa.*, qax." . implode(', qax.', $extraanswerfields) . '
|
||||
FROM {question_answers} qa ' . "
|
||||
LEFT JOIN {{$answerextensiontable}} qax ON qa.id = qax.answerid
|
||||
WHERE qa.question = ?
|
||||
ORDER BY qa.id", array($question->id));
|
||||
if (!$question->options->answers) {
|
||||
echo $OUTPUT->notification('Failed to load question answers from the table ' .
|
||||
$answer_extension_table . 'for questionid ' . $question->id);
|
||||
$answerextensiontable . 'for questionid ' . $question->id);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -795,14 +950,24 @@ class question_type {
|
||||
return;
|
||||
}
|
||||
foreach ($questiondata->options->answers as $a) {
|
||||
$question->answers[$a->id] = new question_answer($a->id, $a->answer,
|
||||
$a->fraction, $a->feedback, $a->feedbackformat);
|
||||
$question->answers[$a->id] = $this->make_answer($a);
|
||||
if (!$forceplaintextanswers) {
|
||||
$question->answers[$a->id]->answerformat = $a->answerformat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a question_answer, or an appropriate subclass for this question,
|
||||
* from a row loaded from the database.
|
||||
* @param object $answer the DB row from the question_answers table plus extra answer fields.
|
||||
* @return question_answer
|
||||
*/
|
||||
protected function make_answer($answer) {
|
||||
return new question_answer($answer->id, $answer->answer,
|
||||
$answer->fraction, $answer->feedback, $answer->feedbackformat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the question-type specific data when a question is deleted.
|
||||
* @param int $question the question being deleted.
|
||||
|
||||
@@ -58,63 +58,24 @@ class qtype_shortanswer extends question_type {
|
||||
global $DB;
|
||||
$result = new stdClass();
|
||||
|
||||
$context = $question->context;
|
||||
|
||||
$oldanswers = $DB->get_records('question_answers',
|
||||
array('question' => $question->id), 'id ASC');
|
||||
|
||||
// Perform sanity checks on fractional grades.
|
||||
$maxfraction = -1;
|
||||
|
||||
// Insert all the new answers.
|
||||
foreach ($question->answer as $key => $answerdata) {
|
||||
// Check for, and ignore, completely blank answer from the form.
|
||||
if (trim($answerdata) == '' && $question->fraction[$key] == 0 &&
|
||||
html_is_blank($question->feedback[$key]['text'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update an existing answer if possible.
|
||||
$answer = array_shift($oldanswers);
|
||||
if (!$answer) {
|
||||
$answer = new stdClass();
|
||||
$answer->question = $question->id;
|
||||
$answer->answer = '';
|
||||
$answer->feedback = '';
|
||||
$answer->id = $DB->insert_record('question_answers', $answer);
|
||||
}
|
||||
|
||||
$answer->answer = trim($answerdata);
|
||||
$answer->fraction = $question->fraction[$key];
|
||||
$answer->feedback = $this->import_or_save_files($question->feedback[$key],
|
||||
$context, 'question', 'answerfeedback', $answer->id);
|
||||
$answer->feedbackformat = $question->feedback[$key]['format'];
|
||||
$DB->update_record('question_answers', $answer);
|
||||
|
||||
if ($question->fraction[$key] > $maxfraction) {
|
||||
$maxfraction = $question->fraction[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$parentresult = parent::save_question_options($question);
|
||||
if ($parentresult !== null) {
|
||||
// Parent function returns null if all is OK.
|
||||
return $parentresult;
|
||||
}
|
||||
|
||||
// Delete any left over old answer records.
|
||||
$fs = get_file_storage();
|
||||
foreach ($oldanswers as $oldanswer) {
|
||||
$fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id);
|
||||
$DB->delete_records('question_answers', array('id' => $oldanswer->id));
|
||||
}
|
||||
|
||||
$this->save_hints($question);
|
||||
|
||||
// Perform sanity checks on fractional grades.
|
||||
if ($maxfraction != 1) {
|
||||
$result->noticeyesno = get_string('fractionsnomax', 'question', $maxfraction * 100);
|
||||
$result->error = get_string('fractionsnomax', 'question', $maxfraction * 100);
|
||||
return $result;
|
||||
}
|
||||
|
||||
parent::save_question_options($question);
|
||||
|
||||
$this->save_question_answers($question);
|
||||
|
||||
$this->save_hints($question);
|
||||
}
|
||||
|
||||
protected function initialise_question_instance(question_definition $question, $questiondata) {
|
||||
|
||||
Reference in New Issue
Block a user