mirror of
https://github.com/moodle/moodle.git
synced 2025-04-22 08:55:15 +02:00
MDL-16263 A way for students to flag/bookmark, particular questions during a quiz attempt for later review.
This is an initial implementation that is now at a working state, but with a few things left to do. It seemed like a good idea to commit it before leaving work on Friday night.
This commit is contained in:
parent
57f43d239a
commit
62e76c6766
@ -44,6 +44,8 @@ $string['categorycurrent'] = 'Current Category';
|
||||
$string['categorycurrentuse'] = 'Use This Category';
|
||||
$string['categorymoveto'] = 'Save in Category';
|
||||
$string['changepublishstatuscat'] = '<a href=\"$a->caturl\">Category \"$a->name\"</a> in course \"$a->coursename\" will have it\'s sharing status changed from <strong>$a->changefrom to $a->changeto</strong>.';
|
||||
$string['clicktoflag'] = 'Click to flag this question';
|
||||
$string['clicktounflag'] = 'Click to un-flag this question';
|
||||
$string['cwrqpfs'] = 'Random questions selecting questions from sub categories.';
|
||||
$string['cwrqpfsinfo'] = '<p>During the upgrade to Moodle 1.9 we will separate question categories into
|
||||
different contexts. Some question categories and questions on your site will have to have their sharing
|
||||
@ -86,6 +88,8 @@ $string['exporterror'] = 'Errors occur during exporting!';
|
||||
$string['filesareasite']= 'the site files area';
|
||||
$string['filesareacourse']= 'the course files area';
|
||||
$string['filestomove']= 'Move / copy files to $a?';
|
||||
$string['flagged'] = 'Flagged';
|
||||
$string['flagthisquestion'] = 'Flag this question';
|
||||
$string['formquestionnotinids'] = 'Form contained question that is not in questionids';
|
||||
$string['fractionsnomax'] = 'One of the answers should have a score of 100%% so it is possible to get full marks for this question.';
|
||||
$string['getcategoryfromfile'] = 'Get category from file';
|
||||
@ -123,6 +127,7 @@ $string['nopermissionadd'] = 'You don\'t have permission to add questions here.'
|
||||
$string['noprobs'] = 'No problems found in your question database.';
|
||||
$string['notenoughdatatoeditaquestion'] = 'Neither a question id, nor a category id and question type, was specified.';
|
||||
$string['notenoughdatatomovequestions'] = 'You need to provide the question ids of questions you want to move.';
|
||||
$string['notflagged'] = 'Not flagged';
|
||||
$string['novirtualquestiontype'] = 'No virtual question type for question type $a';
|
||||
$string['parenthesisinproperstart'] = 'Parenthesis before ** is not properly started in $a**';
|
||||
$string['parenthesisinproperclose'] = 'Parenthesis before ** is not properly closed in $a**';
|
||||
|
@ -110,6 +110,7 @@ $string['question:add'] = 'Add new questions';
|
||||
$string['question:config'] = 'Configure question types';
|
||||
$string['question:editall'] = 'Edit all questions';
|
||||
$string['question:editmine'] = 'Edit your own questions';
|
||||
$string['question:flag'] = 'Flag questions while attempting them';
|
||||
$string['question:managecategory'] = 'Edit question categories';
|
||||
$string['question:moveall'] = 'Move all questions';
|
||||
$string['question:movemine'] = 'Move your own questions';
|
||||
|
@ -1002,8 +1002,20 @@ $moodle_capabilities = array(
|
||||
)
|
||||
),
|
||||
|
||||
'moodle/site:doclinks' => array(
|
||||
// While attempting questions, the ability to flag particular questions for later reference.
|
||||
'moodle/question:flag' => array(
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'legacy' => array(
|
||||
'student' => CAP_ALLOW,
|
||||
'teacher' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'coursecreator' => CAP_ALLOW,
|
||||
'admin' => CAP_ALLOW
|
||||
)
|
||||
),
|
||||
|
||||
'moodle/site:doclinks' => array(
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'legacy' => array(
|
||||
|
@ -1043,7 +1043,8 @@
|
||||
<FIELD NAME="newest" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="questionid" NEXT="newgraded"/>
|
||||
<FIELD NAME="newgraded" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="newest" NEXT="sumpenalty"/>
|
||||
<FIELD NAME="sumpenalty" TYPE="number" LENGTH="12" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" ENUM="false" DECIMALS="7" PREVIOUS="newgraded" NEXT="manualcomment"/>
|
||||
<FIELD NAME="manualcomment" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="sumpenalty"/>
|
||||
<FIELD NAME="manualcomment" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="sumpenalty" NEXT="flagged"/>
|
||||
<FIELD NAME="flagged" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="The person attempting the question may mark certain questions within their question_attempt if the module that owns the attempt allow it. This field stores the status of that flag." PREVIOUS="manualcomment"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="attemptid"/>
|
||||
|
@ -721,6 +721,22 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint($result, 2008082602);
|
||||
}
|
||||
|
||||
if ($result && $oldversion < 2008082700) {
|
||||
/// Add a new column to the question sessions table to record whether a
|
||||
/// question has been flagged.
|
||||
|
||||
/// Define field flagged to be added to question_sessions
|
||||
$table = new xmldb_table('question_sessions');
|
||||
$field = new xmldb_field('flagged', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, null, null, '0', 'manualcomment');
|
||||
|
||||
/// Conditionally launch add field flagged
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
/// Main savepoint reached
|
||||
upgrade_main_savepoint($result, 2008082700);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -94,17 +94,24 @@ define('QUESTION_PREVIEW_POPUP_OPTIONS', 'scrollbars=yes,resizable=yes,width=700
|
||||
* is how question is Moodle always worked before version 1.5
|
||||
*/
|
||||
define('QUESTION_ADAPTIVE', 1);
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
* options used in forms that move files.
|
||||
*
|
||||
/**#@+
|
||||
* Options used in forms that move files.
|
||||
*/
|
||||
define('QUESTION_FILENOTHINGSELECTED', 0);
|
||||
define('QUESTION_FILEDONOTHING', 1);
|
||||
define('QUESTION_FILECOPY', 2);
|
||||
define('QUESTION_FILEMOVE', 3);
|
||||
define('QUESTION_FILEMOVELINKSONLY', 4);
|
||||
/**#@-*/
|
||||
|
||||
/**#@+
|
||||
* Options for whether flags are shown/editable when rendering questions.
|
||||
*/
|
||||
define('QUESTION_FLAGSHIDDEN', 0);
|
||||
define('QUESTION_FLAGSSHOWN', 1);
|
||||
define('QUESTION_FLAGSEDITABLE', 2);
|
||||
/**#@-*/
|
||||
|
||||
/// QTYPES INITIATION //////////////////
|
||||
@ -909,7 +916,7 @@ function get_question_states(&$questions, $cmoptions, $attempt, $lastattemptid =
|
||||
|
||||
// The question field must be listed first so that it is used as the
|
||||
// array index in the array returned by $DB->get_records_sql
|
||||
$statefields = 'n.questionid as question, s.*, n.sumpenalty, n.manualcomment';
|
||||
$statefields = 'n.questionid as question, s.*, n.sumpenalty, n.manualcomment, n.flagged, n.id as questionsessionid';
|
||||
// Load the newest states for the questions
|
||||
$sql = "SELECT $statefields
|
||||
FROM {question_states} s, {question_sessions} n
|
||||
@ -1816,6 +1823,34 @@ function question_format_grade($cmoptions, $grade) {
|
||||
return format_float($grade, $cmoptions->decimalpoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string An inline script that creates a JavaScript object storing
|
||||
* various strings and bits of configuration that the scripts in qengine.js need
|
||||
* to get from PHP.
|
||||
*/
|
||||
function question_init_qenginejs_script() {
|
||||
global $CFG;
|
||||
|
||||
// Get the properties we want into a PHP array first, becase that is easier
|
||||
// to build.
|
||||
$config = array(
|
||||
'pixpath' => $CFG->pixpath,
|
||||
'wwwroot' => $CFG->wwwroot,
|
||||
'flagtooltip' => get_string('clicktoflag', 'question'),
|
||||
'unflagtooltip' => get_string('clicktounflag', 'question'),
|
||||
'flaggedalt' => get_string('flagged', 'question'),
|
||||
'unflaggedalt' => get_string('notflagged', 'question'),
|
||||
);
|
||||
|
||||
// Then generate the script tag.
|
||||
$script = '<script type="text/javascript">qengine_config = {' . "\n";
|
||||
foreach ($config as $property => $value) {
|
||||
$script .= " $property: '" . addslashes_js($value) . "',\n";
|
||||
}
|
||||
$script .= "};</script>\n";
|
||||
return $script;
|
||||
}
|
||||
|
||||
/// FUNCTIONS THAT SIMPLY WRAP QUESTIONTYPE METHODS //////////////////////////////////
|
||||
/**
|
||||
* Get the HTML that needs to be included in the head tag when the
|
||||
@ -1830,15 +1865,23 @@ function question_format_grade($cmoptions, $grade) {
|
||||
* @return string some HTML code that can go inside the head tag.
|
||||
*/
|
||||
function get_html_head_contributions($questionlist, &$questions, &$states) {
|
||||
global $QTYPES;
|
||||
global $CFG, $QTYPES;
|
||||
|
||||
$contributions = array();
|
||||
// The question engine's own JavaScript.
|
||||
require_js(array('yui_yahoo','yui_event', 'yui_connection'));
|
||||
require_js($CFG->wwwroot . '/question/qengine.js');
|
||||
|
||||
// An inline script to record various lang strings, etc. that qengine.js needs.
|
||||
$contributions = array(question_init_qenginejs_script());
|
||||
|
||||
// Anything that questions on this page need.
|
||||
foreach ($questionlist as $questionid) {
|
||||
$question = $questions[$questionid];
|
||||
$contributions = array_merge($contributions,
|
||||
$QTYPES[$question->qtype]->get_html_head_contributions(
|
||||
$question, $states[$questionid]));
|
||||
}
|
||||
|
||||
return implode("\n", array_unique($contributions));
|
||||
}
|
||||
|
||||
@ -2509,4 +2552,31 @@ function question_get_real_state($state){
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the flagged state of a particular question session.
|
||||
*
|
||||
* @param integer $sessionid question_session id.
|
||||
* @param boolean $newstate the new state for the flag.
|
||||
* @return boolean success or failure.
|
||||
*/
|
||||
function question_update_flag($sessionid, $newstate) {
|
||||
global $DB;
|
||||
return $DB->set_field('question_sessions', 'flagged', $newstate, array('id' => $sessionid));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $attemptid the question_attempt id.
|
||||
* @param integer $questionid the question id.
|
||||
* @param integer $sessionid the question_session id.
|
||||
* @param object $user a user, or null to use $USER.
|
||||
* @return string that needs to be sent to question/toggleflag.php for it to work.
|
||||
*/
|
||||
function question_get_toggleflag_checksum($attemptid, $questionid, $sessionid, $user = null) {
|
||||
if (is_null($user)) {
|
||||
global $USER;
|
||||
$user = $USER;
|
||||
}
|
||||
return md5($attemptid . "_" . $user->secret . "_" . $questionid . "_" . $sessionid);
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -497,7 +497,7 @@ class quiz_attempt extends quiz {
|
||||
* @return object the render options for this user on this attempt.
|
||||
*/
|
||||
public function get_render_options($state) {
|
||||
return quiz_get_renderoptions($this->quiz->review, $state);
|
||||
return quiz_get_renderoptions($this->quiz, $this->attempt, $this->context, $state);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -534,7 +534,7 @@ class quiz_attempt extends quiz {
|
||||
case QUESTION_EVENTCLOSEANDGRADE:
|
||||
case QUESTION_EVENTCLOSE:
|
||||
case QUESTION_EVENTMANUALGRADE:
|
||||
$options = quiz_get_renderoptions($this->quiz->review, $this->states[$questionid]);
|
||||
$options = $this->get_render_options($this->states[$questionid]);
|
||||
if ($options->scores) {
|
||||
return question_get_feedback_class($state->last_graded->raw_grade /
|
||||
$this->questions[$questionid]->maxgrade);
|
||||
@ -551,6 +551,16 @@ class quiz_attempt extends quiz {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $questionid question id of a question that belongs to this quiz.
|
||||
* @return boolean whether this question hss been flagged by the attempter.
|
||||
*/
|
||||
public function is_question_flagged($questionid) {
|
||||
$this->ensure_state_loaded($questionid);
|
||||
$state = $this->states[$questionid];
|
||||
return $state->flagged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the grade obtained on a particular question, if the user is permitted to see it.
|
||||
* You must previously have called load_question_states to load the state data about this question.
|
||||
@ -560,7 +570,7 @@ class quiz_attempt extends quiz {
|
||||
*/
|
||||
public function get_question_score($questionid) {
|
||||
$this->ensure_state_loaded($questionid);
|
||||
$options = quiz_get_renderoptions($this->quiz->review, $this->states[$questionid]);
|
||||
$options = $this->get_render_options($this->quiz->review, $this->states[$questionid]);
|
||||
if ($options->scores) {
|
||||
return quiz_format_grade($this->quiz, $this->states[$questionid]->last_graded->grade);
|
||||
} else {
|
||||
@ -803,12 +813,20 @@ abstract class quiz_nav_panel_base {
|
||||
|
||||
abstract protected function get_end_bits();
|
||||
|
||||
protected function get_question_state($question) {
|
||||
$state = 'todo'; // TODO MDL-15653
|
||||
protected function get_question_state_classes($question) {
|
||||
// The current status of the question.
|
||||
$classes = $this->attemptobj->get_question_status($question->id);
|
||||
|
||||
// Plus a marker for the current page.
|
||||
if ($question->_page == $this->page) {
|
||||
$state .= ' thispage';
|
||||
$classes .= ' thispage';
|
||||
}
|
||||
return $state;
|
||||
|
||||
// Plus a marker for flagged questions.
|
||||
if ($this->attemptobj->is_question_flagged($question->id)) {
|
||||
$classes .= ' flagged';
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
|
||||
public function display() {
|
||||
@ -833,7 +851,7 @@ class quiz_attempt_nav_panel extends quiz_nav_panel_base {
|
||||
}
|
||||
return '<input type="submit" name="gotopage' . $question->_page .
|
||||
'" value="' . $number . '" class="qnbutton ' .
|
||||
$this->get_question_state($question) . '"' . $onclick . '/>';
|
||||
$this->get_question_state_classes($question) . '"' . $onclick . '/>';
|
||||
}
|
||||
|
||||
protected function get_end_bits() {
|
||||
@ -853,7 +871,7 @@ class quiz_review_nav_panel extends quiz_nav_panel_base {
|
||||
|
||||
protected function get_question_button($number, $question) {
|
||||
return '<a href="' . $this->attemptobj->review_url($question->id) .
|
||||
'" class="qnbutton ' . $this->get_question_state($question) .
|
||||
'" class="qnbutton ' . $this->get_question_state_classes($question) .
|
||||
'">' . $number . '</a>';
|
||||
}
|
||||
|
||||
|
@ -1257,6 +1257,7 @@ function quiz_get_extra_capabilities() {
|
||||
'moodle/question:movemine',
|
||||
'moodle/question:moveall',
|
||||
'moodle/question:managecategory',
|
||||
'moodle/question:flag',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -752,15 +752,38 @@ function quiz_question_preview_button($quiz, $question) {
|
||||
0, 0, $strpreview, QUESTION_PREVIEW_POPUP_OPTIONS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $attempt the attempt.
|
||||
* @param object $context the quiz context.
|
||||
* @return integer whether flags should be shown/editable to the current user for this attempt.
|
||||
*/
|
||||
function quiz_get_flag_option($attempt, $context) {
|
||||
global $USER;
|
||||
static $flagmode = null;
|
||||
if (is_null($flagmode)) {
|
||||
if (!has_capability('moodle/question:flag', $context)) {
|
||||
$flagmode = QUESTION_FLAGSHIDDEN;
|
||||
} else if ($attempt->userid == $USER->id) {
|
||||
$flagmode = QUESTION_FLAGSEDITABLE;
|
||||
} else {
|
||||
$flagmode = QUESTION_FLAGSSHOWN;
|
||||
}
|
||||
}
|
||||
return $flagmode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine render options
|
||||
*
|
||||
* @param int $reviewoptions
|
||||
* @param object $state
|
||||
*/
|
||||
function quiz_get_renderoptions($reviewoptions, $state) {
|
||||
function quiz_get_renderoptions($quiz, $attempt, $context, $state) {
|
||||
$reviewoptions = $quiz->review;
|
||||
$options = new stdClass;
|
||||
|
||||
$options->flags = quiz_get_flag_option($attempt, $context);
|
||||
|
||||
// Show the question in readonly (review) mode if the question is in
|
||||
// the closed state
|
||||
$options->readonly = question_state_is_closed($state);
|
||||
@ -791,28 +814,31 @@ function quiz_get_renderoptions($reviewoptions, $state) {
|
||||
*
|
||||
* @param object $quiz the quiz instance.
|
||||
* @param object $attempt the attempt in question.
|
||||
* @param $context the roles and permissions context,
|
||||
* normally the context for the quiz module instance.
|
||||
* @param $context the quiz module context.
|
||||
*
|
||||
* @return object an object with boolean fields responses, scores, feedback,
|
||||
* correct_responses, solutions and general feedback
|
||||
*/
|
||||
function quiz_get_reviewoptions($quiz, $attempt, $context=null) {
|
||||
function quiz_get_reviewoptions($quiz, $attempt, $context) {
|
||||
global $USER;
|
||||
|
||||
$options = new stdClass;
|
||||
$options->readonly = true;
|
||||
|
||||
$options->flags = quiz_get_flag_option($attempt, $context);
|
||||
|
||||
// Provide the links to the question review and comment script
|
||||
if (!empty($attempt->id)) {
|
||||
$options->questionreviewlink = '/mod/quiz/reviewquestion.php?attempt=' . $attempt->id;
|
||||
}
|
||||
|
||||
// Show a link to the comment box only for closed attempts
|
||||
if ($attempt->timefinish && !is_null($context) && has_capability('mod/quiz:grade', $context)) {
|
||||
if ($attempt->timefinish && has_capability('mod/quiz:grade', $context)) {
|
||||
$options->questioncommentlink = '/mod/quiz/comment.php';
|
||||
}
|
||||
|
||||
// Whether to display a response history.
|
||||
$canviewreports = !is_null($context) && has_capability('mod/quiz:viewreports', $context);
|
||||
$canviewreports = has_capability('mod/quiz:viewreports', $context);
|
||||
$options->history = ($canviewreports && !$attempt->preview) ? 'all' : 'graded';
|
||||
|
||||
if ($canviewreports && has_capability('moodle/grade:viewhidden', $context) && !$attempt->preview) {
|
||||
@ -867,7 +893,7 @@ function quiz_get_reviewoptions($quiz, $attempt, $context=null) {
|
||||
* at least one of the attempts, the other showing which options are true
|
||||
* for all attempts.
|
||||
*/
|
||||
function quiz_get_combined_reviewoptions($quiz, $attempts, $context=null) {
|
||||
function quiz_get_combined_reviewoptions($quiz, $attempts, $context) {
|
||||
$fields = array('readonly', 'scores', 'feedback', 'correct_responses', 'solutions', 'generalfeedback', 'overallfeedback');
|
||||
$someoptions = new stdClass;
|
||||
$alloptions = new stdClass;
|
||||
|
BIN
pix/i/flagged.png
Normal file
BIN
pix/i/flagged.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 203 B |
BIN
pix/i/unflagged.png
Normal file
BIN
pix/i/unflagged.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 204 B |
@ -68,10 +68,13 @@
|
||||
$quiz->review = $CFG->quiz_review;
|
||||
require_login($courseid, false);
|
||||
$quiz->course = $courseid;
|
||||
$context = get_context_instance(CONTEXT_COURSE, $courseid);
|
||||
} else if (!$quiz = $DB->get_record('quiz', array('id' => $quizid))) {
|
||||
print_error('invalidquizid', 'quiz', '', $quizid);
|
||||
} else {
|
||||
require_login($quiz->course, false, get_coursemodule_from_instance('quiz', $quizid, $quiz->course));
|
||||
$cm = get_coursemodule_from_instance('quiz', $quizid, $quiz->course);
|
||||
require_login($quiz->course, false, $cm);
|
||||
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
|
||||
}
|
||||
|
||||
|
||||
@ -187,7 +190,7 @@
|
||||
}
|
||||
|
||||
// TODO: should not use quiz-specific function here
|
||||
$options = quiz_get_renderoptions($quiz->review, $curstate);
|
||||
$options = quiz_get_renderoptions($quiz, $attempt, $context, $curstate);
|
||||
|
||||
// Fill in the correct responses (unless the question is in readonly mode)
|
||||
if ($fillcorrect && !$options->readonly) {
|
||||
|
38
question/qengine.js
Normal file
38
question/qengine.js
Normal file
@ -0,0 +1,38 @@
|
||||
// This script, and the YUI libraries that it needs, are inluded by
|
||||
// the require_js calls in get_html_head_contributions in lib/questionlib.php.
|
||||
|
||||
question_flag_changer = {
|
||||
init_flag: function(checkboxid, postdata) {
|
||||
var checkbox = document.getElementById(checkboxid);
|
||||
checkbox.ajaxpostdata = postdata;
|
||||
checkbox.className += ' jsworking';
|
||||
question_flag_changer.update_image(checkbox);
|
||||
YAHOO.util.Event.addListener(checkbox, 'change', this.checkbox_state_change);
|
||||
YAHOO.util.Event.addListener(checkbox, 'focus', 'blur()');
|
||||
},
|
||||
|
||||
checkbox_state_change: function(e) {
|
||||
var checkbox = e.target ? e.target : e.srcElement;
|
||||
question_flag_changer.update_image(checkbox);
|
||||
var postdata = checkbox.ajaxpostdata
|
||||
if (checkbox.checked) {
|
||||
postdata += '&newstate=1'
|
||||
} else {
|
||||
postdata += '&newstate=0'
|
||||
}
|
||||
YAHOO.util.Connect.asyncRequest('POST', qengine_config.wwwroot + '/question/toggleflag.php', null, postdata);
|
||||
},
|
||||
|
||||
update_image: function(checkbox) {
|
||||
var img = document.getElementById(checkbox.id + 'img');
|
||||
if (checkbox.checked) {
|
||||
img.src = qengine_config.pixpath + '/i/flagged.png';
|
||||
img.alt = qengine_config.flaggedalt;
|
||||
img.title = qengine_config.unflagtooltip;
|
||||
} else {
|
||||
img.src = qengine_config.pixpath + '/i/unflagged.png';
|
||||
img.alt = qengine_config.unflaggedalt;
|
||||
img.title = qengine_config.flagtooltip;
|
||||
}
|
||||
}
|
||||
};
|
48
question/toggleflag.php
Normal file
48
question/toggleflag.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php // $Id$
|
||||
/**
|
||||
* Used by ajax calls to toggle the flagged state of a question in an attempt.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package questionbank
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir.'/questionlib.php');
|
||||
|
||||
// Parameters
|
||||
$sessionid = required_param('qsid', PARAM_INT);
|
||||
$attemptid = required_param('aid', PARAM_INT);
|
||||
$questionid = required_param('qid', PARAM_INT);
|
||||
$newstate = required_param('newstate', PARAM_BOOL);
|
||||
$checksum = required_param('checksum', PARAM_ALPHANUM);
|
||||
|
||||
// Check user is logged in.
|
||||
require_login();
|
||||
|
||||
// Check the sesskey.
|
||||
if (!confirm_sesskey()) {
|
||||
echo 'sesskey failure';
|
||||
}
|
||||
|
||||
// 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 != md5($attemptid . "_" . $USER->secret . "_" . $questionid . "_" . $sessionid)) {
|
||||
echo 'checksum failure';
|
||||
}
|
||||
|
||||
// Check that the requested session really exists
|
||||
$questionsession = $DB->get_record('question_sessions', array('id' => $sessionid,
|
||||
'attemptid' => $attemptid, 'questionid' => $questionid));
|
||||
if (!$questionsession) {
|
||||
echo 'invalid ids';
|
||||
}
|
||||
|
||||
// Now change state
|
||||
if (!question_update_flag($sessionid, $newstate)) {
|
||||
echo 'update failed';
|
||||
}
|
||||
|
||||
echo 'OK';
|
||||
?>
|
@ -13,7 +13,8 @@
|
||||
<div class="grade">
|
||||
<?php echo get_string('marks', 'quiz').': '.$grade; ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php }
|
||||
$this->print_question_flag($question, $state, $options->flags); ?>
|
||||
</div>
|
||||
<div class="content">
|
||||
<?php $this->print_question_formulation_and_controls($question, $state, $cmoptions, $options);
|
||||
|
@ -879,7 +879,69 @@ class default_questiontype {
|
||||
include "$CFG->dirroot/question/type/question.html";
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Render the question flag, assuming $flagsoption allows it. You will probably
|
||||
* never need to override this method.
|
||||
*
|
||||
* @param object $question the question
|
||||
* @param object $state its current state
|
||||
* @param integer $flagsoption the option that says whether flags should be displayed.
|
||||
*/
|
||||
protected function print_question_flag($question, $state, $flagsoption) {
|
||||
global $CFG;
|
||||
switch ($flagsoption) {
|
||||
case QUESTION_FLAGSSHOWN:
|
||||
$flagcontent = $this->get_question_flag_tag($state->flagged);
|
||||
break;
|
||||
case QUESTION_FLAGSEDITABLE:
|
||||
$id = $question->name_prefix . '_flagged';
|
||||
if ($state->flagged) {
|
||||
$checked = 'checked="checked" ';
|
||||
} else {
|
||||
$checked = '';
|
||||
}
|
||||
$qsid = $state->questionsessionid;
|
||||
$aid = $state->attempt;
|
||||
$qid = $state->question;
|
||||
$checksum = question_get_toggleflag_checksum($aid, $qid, $qsid);
|
||||
$postdata = "qsid=$qsid&aid=$aid&qid=$qid&checksum=$checksum&sesskey=" . sesskey();
|
||||
$flagcontent = '<input type="checkbox" id="' . $id . '" name="' . $id .
|
||||
'" value="1" ' . $checked . ' />' .
|
||||
'<label for="' . $id . '">' . $this->get_question_flag_tag(
|
||||
$state->flagged, $id . 'img') . '</label>' .
|
||||
"\n" . '<script type="text/javascript">question_flag_changer.init_flag(' .
|
||||
"'$id', '$postdata');</script>";
|
||||
break;
|
||||
default:
|
||||
$flagcontent = '';
|
||||
}
|
||||
if ($flagcontent) {
|
||||
echo '<div class="questionflag">' . $flagcontent . "</div>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out the actual img tag needed for the flag
|
||||
*
|
||||
* @param boolean $flagged whether the question is currently flagged.
|
||||
* @param string $id an id to be added as an attribute to the img (optional).
|
||||
* @return string the img tag.
|
||||
*/
|
||||
protected function get_question_flag_tag($flagged, $id = '') {
|
||||
global $CFG;
|
||||
if ($id) {
|
||||
$id = 'id="' . $id . '" ';
|
||||
}
|
||||
if ($flagged) {
|
||||
$img = 'flagged.png';
|
||||
} else {
|
||||
$img = 'unflagged.png';
|
||||
}
|
||||
return '<img ' . $id . 'src="' . $CFG->pixpath . '/i/' . $img .
|
||||
'" alt="' . get_string('flagthisquestion', 'question') . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Print history of responses
|
||||
*
|
||||
* Used by print_question()
|
||||
|
@ -2623,6 +2623,15 @@ body.notes .notesgroup {
|
||||
.que .info div {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.que .info .questionflag {
|
||||
margin-top: 1em;
|
||||
margin-right: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
.que .info .questionflag .jsworking {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
}
|
||||
.que .content {
|
||||
float: left;
|
||||
margin: 0;
|
||||
|
@ -6,7 +6,7 @@
|
||||
// This is compared against the values stored in the database to determine
|
||||
// whether upgrades should be performed (see lib/db/*.php)
|
||||
|
||||
$version = 2008082602; // YYYYMMDD = date of the last version bump
|
||||
$version = 2008082702; // YYYYMMDD = date of the last version bump
|
||||
// XX = daily increments
|
||||
|
||||
$release = '2.0 dev (Build: 20080829)'; // Human-friendly version name
|
||||
|
Loading…
x
Reference in New Issue
Block a user