Skip to content

Commit

Permalink
MDL-20636 More progress.
Browse files Browse the repository at this point in the history
  • Loading branch information
timhunt committed Jan 13, 2011
1 parent b691dc8 commit 06f8ed5
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 142 deletions.
8 changes: 8 additions & 0 deletions question/engine/bank.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ public static function get_qtype($qtypename, $mustexist = true) {
return self::$questiontypes[$qtypename];
}

/**
* @param string $qtypename the internal name of a question type. For example multichoice.
* @return boolean whether users are allowed to create questions of this type.
*/
public static function qtype_enabled($qtypename) {
;
}

/**
* @param $qtypename the internal name of a question type, for example multichoice.
* @return string the human_readable name of this question type, from the language pack.
Expand Down
108 changes: 52 additions & 56 deletions question/engine/datalib.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_engine_data_mapper {
/**
* @var moodle_database normally points to global $DB, but I prefer not to
* use globals if I can help it.
*/
protected $db;

/**
* @param moodle_database $db a database connectoin. Defaults to global $DB.
*/
public function __construct($db = null) {
if (is_null($db)) {
global $DB;
new moodle_database;
$this->db = $DB;
} else {
$this->db = $db;
}
}

/**
* Store an entire {@link question_usage_by_activity} in the database,
* including all the question_attempts that comprise it.
Expand All @@ -45,10 +64,7 @@ public function insert_questions_usage_by_activity(question_usage_by_activity $q
$record->component = addslashes($quba->get_owning_component());
$record->preferredbehaviour = addslashes($quba->get_preferred_behaviour());

$newid = insert_record('question_usages', $record);
if (!$newid) {
throw new Exception('Failed to save questions_usage_by_activity.');
}
$newid = $this->db->insert_record('question_usages', $record);
$quba->set_id_from_database($newid);

foreach ($quba->get_attempt_iterator() as $qa) {
Expand All @@ -74,10 +90,7 @@ public function insert_question_attempt(question_attempt $qa) {
$record->rightanswer = addslashes($qa->get_right_answer_summary());
$record->responsesummary = addslashes($qa->get_response_summary());
$record->timemodified = time();
$record->id = insert_record('question_attempts', $record);
if (!$record->id) {
throw new Exception('Failed to save question_attempt ' . $qa->get_slot());
}
$record->id = $this->db->insert_record('question_attempts', $record);

foreach ($qa->get_step_iterator() as $seq => $step) {
$this->insert_question_attempt_step($step, $record->id, $seq);
Expand All @@ -98,18 +111,14 @@ public function insert_question_attempt_step(question_attempt_step $step,
$record->timecreated = $step->get_timecreated();
$record->userid = $step->get_user_id();

$record->id = insert_record('question_attempt_steps', $record);
if (!$record->id) {
throw new Exception('Failed to save question_attempt_step' . $seq .
' for question attempt id ' . $questionattemptid);
}
$record->id = $this->db->insert_record('question_attempt_steps', $record);

foreach ($step->get_all_data() as $name => $value) {
$data = new stdClass;
$data->attemptstepid = $record->id;
$data->name = addslashes($name);
$data->value = addslashes($value);
insert_record('question_attempt_step_data', $data, false);
$this->db->insert_record('question_attempt_step_data', $data, false);
}
}

Expand All @@ -119,8 +128,7 @@ public function insert_question_attempt_step(question_attempt_step $step,
* @param question_attempt_step the step that was loaded.
*/
public function load_question_attempt_step($stepid) {
global $CFG;
$records = get_records_sql("
$records = $this->db->get_records_sql("
SELECT
COALESCE(qasd.id, -1 * qas.id) AS id,
qas.id AS attemptstepid,
Expand All @@ -133,12 +141,12 @@ public function load_question_attempt_step($stepid) {
qasd.name,
qasd.value
FROM {$CFG->prefix}question_attempt_steps qas
LEFT JOIN {$CFG->prefix}question_attempt_step_data qasd ON qasd.attemptstepid = qas.id
FROM {question_attempt_steps} qas
LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
WHERE
qas.id = $stepid
");
qas.id = :stepid
", array('stepid' => $stepid));

if (!$records) {
throw new Exception('Failed to load question_attempt_step ' . $stepid);
Expand All @@ -154,8 +162,7 @@ public function load_question_attempt_step($stepid) {
* @param question_attempt the question attempt that was loaded.
*/
public function load_question_attempt($questionattemptid) {
global $CFG;
$records = get_records_sql("
$records = $this->db->get_records_sql("
SELECT
COALESCE(qasd.id, -1 * qas.id) AS id,
quba.preferredbehaviour,
Expand All @@ -180,17 +187,17 @@ public function load_question_attempt($questionattemptid) {
qasd.name,
qasd.value
FROM {$CFG->prefix}question_attempts qa
JOIN {$CFG->prefix}question_usages quba ON quba.id = qa.questionusageid
LEFT JOIN {$CFG->prefix}question_attempt_steps qas ON qas.questionattemptid = qa.id
LEFT JOIN {$CFG->prefix}question_attempt_step_data qasd ON qasd.attemptstepid = qas.id
FROM {question_attempts qa
JOIN {question_usages} quba ON quba.id = qa.questionusageid
LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
WHERE
qa.id = $questionattemptid
qa.id = :questionattemptid
ORDER BY
qas.sequencenumber
");
", array('questionattemptid' => $questionattemptid));

if (!$records) {
throw new Exception('Failed to load question_attempt ' . $questionattemptid);
Expand All @@ -208,8 +215,7 @@ public function load_question_attempt($questionattemptid) {
* @param question_usage_by_activity the usage that was loaded.
*/
public function load_questions_usage_by_activity($qubaid) {
global $CFG;
$records = get_records_sql("
$records = $this->db->get_records_sql("
SELECT
COALESCE(qasd.id, -1 * qas.id) AS id,
quba.id AS qubaid,
Expand Down Expand Up @@ -237,18 +243,18 @@ public function load_questions_usage_by_activity($qubaid) {
qasd.name,
qasd.value
FROM {$CFG->prefix}question_usages quba
LEFT JOIN {$CFG->prefix}question_attempts qa ON qa.questionusageid = quba.id
LEFT JOIN {$CFG->prefix}question_attempt_steps qas ON qas.questionattemptid = qa.id
LEFT JOIN {$CFG->prefix}question_attempt_step_data qasd ON qasd.attemptstepid = qas.id
FROM {question_usages} quba
LEFT JOIN {question_attempts} qa ON qa.questionusageid = quba.id
LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
WHERE
quba.id = $qubaid
quba.id = :qubaid
ORDER BY
qa.slot,
qas.sequencenumber
");
", array('qubaid', $qubaid));

if (!$records) {
throw new Exception('Failed to load questions_usage_by_activity ' . $qubaid);
Expand All @@ -266,8 +272,6 @@ public function load_questions_usage_by_activity($qubaid) {
* @return array of records. See the SQL in this function to see the fields available.
*/
public function load_questions_usages_latest_steps(qubaid_condition $qubaids, $slots) {
global $CFG;

list($slottest, $params) = get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');

$records = get_records_sql("
Expand All @@ -293,17 +297,13 @@ public function load_questions_usages_latest_steps(qubaid_condition $qubaids, $s
qas.userid
FROM {$qubaids->from_question_attempts('qa')}
JOIN {$CFG->prefix}question_attempt_steps qas ON
JOIN {question_attempt_steps} qas ON
qas.id = {$this->latest_step_for_qa_subquery()}
WHERE
{$qubaids->where()} AND
qa.slot $slottest
");

if (!$records) {
$records = array();
}
", $params + $qubaids->from_where_params());

return $records;
}
Expand All @@ -321,11 +321,9 @@ public function load_questions_usages_latest_steps(qubaid_condition $qubaids, $s
* $manuallygraded and $all.
*/
public function load_questions_usages_question_state_summary(qubaid_condition $qubaids, $slots) {
global $CFG;

list($slottest, $params) = get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');

$rs = get_recordset_sql("
$rs = $this->db->get_recordset_sql("
SELECT
qa.slot,
qa.questionid,
Expand All @@ -336,9 +334,9 @@ public function load_questions_usages_question_state_summary(qubaid_condition $q
COUNT(1) AS numattempts
FROM {$qubaids->from_question_attempts('qa')}
JOIN {$CFG->prefix}question_attempt_steps qas ON
JOIN {question_attempt_steps} qas ON
qas.id = {$this->latest_step_for_qa_subquery()}
JOIN {$CFG->prefix}question q ON q.id = qa.questionid
JOIN {question} q ON q.id = qa.questionid
WHERE
{$qubaids->where()} AND
Expand All @@ -356,7 +354,7 @@ public function load_questions_usages_question_state_summary(qubaid_condition $q
qa.questionid,
q.name,
q.id
");
", $params + $qubaids->from_where_params());

if (!$rs) {
throw new moodle_exception('errorloadingdata');
Expand Down Expand Up @@ -694,15 +692,13 @@ public function delete_previews($questionid) {
* @param integer $sessionid the question_attempt id.
* @param boolean $newstate the new state of the flag. true = flagged.
*/
public function update_question_attempt_flag($qubaid, $questionid, $qaid, $newstate) {
if (!record_exists('question_attempts', 'id', $qaid,
'questionusageid', $qubaid, 'questionid', $questionid)) {
public function update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate) {
if (!$this->db->record_exists('question_attempts', array('id' => $qaid,
'questionusageid' => $qubaid, 'questionid' => $questionid, 'slot' => $slot))) {
throw new Exception('invalid ids');
}

if (!set_field('question_attempts', 'flagged', $newstate, 'id', $qaid)) {
throw new Exception('flag update failed');
}
$this->db->set_field('question_attempts', 'flagged', $newstate, array('id' => $qaid));
}

/**
Expand Down
15 changes: 8 additions & 7 deletions question/engine/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -504,12 +504,12 @@ abstract class question_flags {
* @param object $user the user. If null, defaults to $USER.
* @return string that needs to be sent to question/toggleflag.php for it to work.
*/
protected static function get_toggle_checksum($qubaid, $questionid, $qaid, $user = null) {
protected static function get_toggle_checksum($qubaid, $questionid, $qaid, $slot, $user = null) {
if (is_null($user)) {
global $USER;
$user = $USER;
}
return md5($qubaid . "_" . $user->secret . "_" . $questionid . "_" . $qaid);
return md5($qubaid . "_" . $user->secret . "_" . $questionid . "_" . $qaid . "_" . $slot);
}

/**
Expand All @@ -521,8 +521,9 @@ public static function get_postdata(question_attempt $qa) {
$qaid = $qa->get_database_id();
$qubaid = $qa->get_usage_id();
$qid = $qa->get_question()->id;
$checksum = self::get_toggle_checksum($qubaid, $qid, $qaid);
return "qaid=$qaid&qubaid=$qubaid&qid=$qid&checksum=$checksum&sesskey=" . sesskey();
$slot = $qa->get_slot();
$checksum = self::get_toggle_checksum($qubaid, $qid, $qaid, $slot);
return "qaid=$qaid&qubaid=$qubaid&qid=$qid&slot=$slot&checksum=$checksum&sesskey=" . sesskey();
}

/**
Expand All @@ -535,18 +536,18 @@ public static function get_postdata(question_attempt $qa) {
* corresponding to the last three arguments.
* @param boolean $newstate the new state of the flag. true = flagged.
*/
public static function update_flag($qubaid, $questionid, $qaid, $checksum, $newstate) {
public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate) {
// Check the checksum - it is very hard to know who a question session belongs
// to, so we require that checksum parameter is matches an md5 hash of the
// three ids and the users username. Since we are only updating a flag, that
// probably makes it sufficiently difficult for malicious users to toggle
// other users flags.
if ($checksum != question_flags::get_toggle_checksum($qubaid, $questionid, $qaid)) {
if ($checksum != question_flags::get_toggle_checksum($qubaid, $questionid, $qaid, $slot)) {
throw new Exception('checksum failure');
}

$dm = new question_engine_data_mapper();
$dm->update_question_attempt_flag($qubaid, $questionid, $qaid, $newstate);
$dm->update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate);
}

public static function initialise_js() {
Expand Down
2 changes: 1 addition & 1 deletion question/engine/renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,9 @@ protected function question_flag(question_attempt $qa, $flagsoption) {
// of a stupid IE bug: http://www.456bereastreet.com/archive/200802/beware_of_id_and_name_attribute_mixups_when_using_getelementbyid_in_internet_explorer/
$flagcontent = '<input type="hidden" name="' . $id . '" value="0" />' .
'<input type="checkbox" id="' . $id . 'checkbox" name="' . $id . '" value="1" ' . $checked . ' />' .
'<input type="hidden" value="' . s($postdata) . '" class="questionflagpostdata" />' .
'<label id="' . $id . 'label" for="' . $id . '">' . $this->get_flag_html(
$qa->is_flagged(), $id . 'img') . '</label>' . "\n" .
print_js_call('question_flag_changer.init_flag', array($id, $postdata, $qa->get_slot()), true);
break;
default:
$flagcontent = '';
Expand Down
5 changes: 2 additions & 3 deletions question/flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ M.core_question_flags = {
Y.io(M.core_question_flags.actionurl , {method: 'POST', 'data': postdata});
M.core_question_flags.fire_listeners(postdata);
}, document.body, 'input.questionflagimage');

},

update_flag: function(input, image) {
Expand All @@ -56,8 +55,8 @@ M.core_question_flags = {
fire_listeners: function(postdata) {
for (var i = 0; i < M.core_question_flags.listeners.length; i++) {
M.core_question_flags.listeners[i](
postdata.match(/\baid=(\d+)\b/)[1],
postdata.match(/\bqid=(\d+)\b/)[1],
postdata.match(/\bqubaid=(\d+)\b/)[1],
postdata.match(/\bslot=(\d+)\b/)[1],
postdata.match(/\bnewstate=(\d+)\b/)[1]
);
}
Expand Down
31 changes: 15 additions & 16 deletions question/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


/**
* This file the Moodle question engine.
* JavaScript required by the question preview pop-up.
*
* @package moodlecore
* @subpackage questionengine
Expand All @@ -24,23 +24,22 @@
*/


M.core_question_preview = M.core_question_preview || {};


/**
* Initialise JavaScript-specific parts of the question preview popup.
*/
function question_preview_init(caption, addto) {
// Add a close button to the window.
var button = document.createElement('input');
button.type = 'button';
button.value = caption;
M.core_question_preview.init(Y) {
M.core_question_engine.init_form(Y, '#responseform');

YAHOO.util.Event.addListener(button, 'click', function() { window.close() });

var container = document.getElementById(addto);
container.appendChild(button);
// Add a close button to the window.
var closebutton = Y.Node.create('<input type="button" />');
button.value = M.str.question.closepreview;
Y.one('#previewcontrols').append(closebutton);
Y.on('click', function() { window.close() }, closebutton);

// Make changint the settings disable all submit buttons, like clicking one of the
// question buttons does.
var form = document.getElementById('mform1');
YAHOO.util.Event.addListener(form, 'submit',
question_prevent_repeat_submission, document.body);
}
// Make changing the settings disable all submit buttons, like clicking one
// of the question buttons does.
Y.on('submit', M.core_question_engine.prevent_repeat_submission, '#mform1', null, Y)
}
Loading

0 comments on commit 06f8ed5

Please sign in to comment.