This commit is contained in:
Eloy Lafuente (stronk7)
2014-04-02 03:14:06 +02:00
3 changed files with 241 additions and 56 deletions
+59
View File
@@ -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()}.
+174 -9
View File
@@ -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.
+8 -47
View File
@@ -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) {