function print_question(&$question, &$state, $number, $cmoptions, $options) { global $CFG; $isfinished = question_state_is_graded($state->last_graded) || $state->event == QUESTION_EVENTCLOSE; if (!empty($cmoptions->id)) { $cm = get_coursemodule_from_instance('quiz', $cmoptions->id); $cmorcourseid = '&cmid=' . $cm->id; } else { if (!empty($cmoptions->course)) { $cmorcourseid = '&courseid=' . $cmoptions->course; } else { error('Need to provide courseid or cmid to print_question.'); } } // For editing teachers print a link to an editing popup window $editlink = ''; if (question_has_capability_on($question, 'edit')) { $stredit = get_string('edit'); $linktext = '<img src="' . $CFG->pixpath . '/t/edit.gif" alt="' . $stredit . '" />'; $editlink = link_to_popup_window('/question/question.php?id=' . $question->id . $cmorcourseid, $stredit, $linktext, 450, 550, $stredit, '', true); } $questiontext = $this->format_text($question->questiontext, $question->questiontextformat, $cmoptions); $image = get_question_image($question); $generalfeedback = ''; if ($isfinished && $options->generalfeedback) { $generalfeedback = $this->format_text($question->generalfeedback, $question->questiontextformat, $cmoptions); } include "{$CFG->dirroot}/question/type/description/question.html"; }
function print_question(&$question, &$state, $number, $cmoptions, $options) { global $CFG; $isfinished = question_state_is_graded($state->last_graded) || $state->event == QUESTION_EVENTCLOSE; // For editing teachers print a link to an editing popup window $editlink = $this->get_question_edit_link($question, $cmoptions, $options); $questiontext = $this->format_text($question->questiontext, $question->questiontextformat, $cmoptions); $generalfeedback = ''; if ($isfinished && $options->generalfeedback) { $generalfeedback = $this->format_text($question->generalfeedback, $question->generalfeedbackformat, $cmoptions); } include "{$CFG->dirroot}/question/type/description/question.html"; }
function print_question(&$question, &$state, $number, $cmoptions, $options) { global $CFG; $isfinished = question_state_is_graded($state->last_graded) || $state->event == QUESTION_EVENTCLOSE; // For editing teachers print a link to an editing popup window $editlink = $this->get_question_edit_link($question, $cmoptions, $options); $context = $this->get_context_by_category_id($question->category); $question->questiontext = quiz_rewrite_question_urls($question->questiontext, 'pluginfile.php', $context->id, 'question', 'questiontext', array($state->attempt, $state->question), $question->id); $questiontext = $this->format_text($question->questiontext, $question->questiontextformat, $cmoptions); $generalfeedback = ''; if ($isfinished && $options->generalfeedback) { $question->generalfeedback = quiz_rewrite_question_urls($question->generalfeedback, 'pluginfile.php', $context->id, 'question', 'generalfeedback', array($state->attempt, $state->question), $question->id); $generalfeedback = $this->format_text($question->generalfeedback, $question->generalfeedbackformat, $cmoptions); } include "{$CFG->dirroot}/question/type/description/question.html"; }
function print_question(&$question, &$state, $number, $cmoptions, $options) { global $CFG; $isfinished = question_state_is_graded($state->last_graded) || $state->event == QUESTION_EVENTCLOSE; // For editing teachers print a link to an editing popup window $editlink = ''; if (has_capability('moodle/question:manage', get_context_instance(CONTEXT_COURSE, $cmoptions->course))) { $stredit = get_string('edit'); $linktext = '<img src="' . $CFG->pixpath . '/t/edit.gif" alt="' . $stredit . '" />'; $editlink = link_to_popup_window('/question/question.php?id=' . $question->id, $stredit, $linktext, 450, 550, $stredit, '', true); } $questiontext = $this->format_text($question->questiontext, $question->questiontextformat, $cmoptions); $image = get_question_image($question, $cmoptions->course); $generalfeedback = ''; if ($isfinished && $options->generalfeedback) { $generalfeedback = $this->format_text($question->generalfeedback, $question->questiontextformat, $cmoptions); } include "{$CFG->dirroot}/question/type/description/question.html"; }
/** * Processes an array of student responses, grading and saving them as appropriate * * @param object $question Full question object, passed by reference * @param object $state Full state object, passed by reference * @param object $action object with the fields ->responses which * is an array holding the student responses, * ->action which specifies the action, e.g., QUESTION_EVENTGRADE, * and ->timestamp which is a timestamp from when the responses * were submitted by the student. * @param object $cmoptions * @param object $attempt The attempt is passed by reference so that * during grading its ->sumgrades field can be updated * @return boolean Indicates success/failure */ function question_process_responses(&$question, &$state, $action, $cmoptions, &$attempt) { global $QTYPES; // if no responses are set initialise to empty response if (!isset($action->responses)) { $action->responses = array('' => ''); } // make sure these are gone! unset($action->responses['submit'], $action->responses['validate']); // Check the question session is still open if (question_state_is_closed($state)) { return true; } // If $action->event is not set that implies saving if (!isset($action->event)) { debugging('Ambiguous action in question_process_responses.', DEBUG_DEVELOPER); $action->event = QUESTION_EVENTSAVE; } // If submitted then compare against last graded // responses, not last given responses in this case if (question_isgradingevent($action->event)) { $state->responses = $state->last_graded->responses; } // Check for unchanged responses (exactly unchanged, not equivalent). // We also have to catch questions that the student has not yet attempted $sameresponses = $QTYPES[$question->qtype]->compare_responses($question, $action, $state); if (!empty($state->last_graded) && $state->last_graded->event == QUESTION_EVENTOPEN && question_isgradingevent($action->event)) { $sameresponses = false; } // If the response has not been changed then we do not have to process it again // unless the attempt is closing or validation is requested if ($sameresponses and QUESTION_EVENTCLOSE != $action->event and QUESTION_EVENTVALIDATE != $action->event) { return true; } // Roll back grading information to last graded state and set the new // responses $newstate = clone $state->last_graded; $newstate->responses = $action->responses; $newstate->seq_number = $state->seq_number + 1; $newstate->changed = true; // will assure that it gets saved to the database $newstate->last_graded = clone $state->last_graded; $newstate->timestamp = $action->timestamp; $state = $newstate; // Set the event to the action we will perform. The question type specific // grading code may override this by setting it to QUESTION_EVENTCLOSE if the // attempt at the question causes the session to close $state->event = $action->event; if (!question_isgradingevent($action->event)) { // Grade the response but don't update the overall grade if (!$QTYPES[$question->qtype]->grade_responses($question, $state, $cmoptions)) { return false; } // Temporary hack because question types are not given enough control over what is going // on. Used by Opaque questions. // TODO fix this code properly. if (!empty($state->believeevent)) { // If the state was graded we need to ... if (question_state_is_graded($state)) { question_apply_penalty_and_timelimit($question, $state, $attempt, $cmoptions); // update the attempt grade $attempt->sumgrades -= (double) $state->last_graded->grade; $attempt->sumgrades += (double) $state->grade; // and update the last_graded field. unset($state->last_graded); $state->last_graded = clone $state; unset($state->last_graded->changed); } } else { // Don't allow the processing to change the event type $state->event = $action->event; } } else { // grading event // Unless the attempt is closing, we want to work out if the current responses // (or equivalent responses) were already given in the last graded attempt. if (QUESTION_EVENTCLOSE != $action->event && QUESTION_EVENTOPEN != $state->last_graded->event && $QTYPES[$question->qtype]->compare_responses($question, $state, $state->last_graded)) { $state->event = QUESTION_EVENTDUPLICATE; } // If we did not find a duplicate or if the attempt is closing, perform grading if (!$sameresponses and QUESTION_EVENTDUPLICATE != $state->event or QUESTION_EVENTCLOSE == $action->event) { if (!$QTYPES[$question->qtype]->grade_responses($question, $state, $cmoptions)) { return false; } // Calculate overall grade using correct penalty method question_apply_penalty_and_timelimit($question, $state, $attempt, $cmoptions); } // If the state was graded we need to ... if (question_state_is_graded($state)) { // update the attempt grade $attempt->sumgrades -= (double) $state->last_graded->grade; $attempt->sumgrades += (double) $state->grade; // and update the last_graded field. unset($state->last_graded); $state->last_graded = clone $state; unset($state->last_graded->changed); } } $attempt->timemodified = $action->timestamp; return true; }
/** * Determine render options * * @param int $reviewoptions * @param object $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); // Show feedback once the question has been graded (if allowed by the quiz) $options->feedback = question_state_is_graded($state) && $reviewoptions & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY; // Show correct responses in readonly mode if the quiz allows it $options->correct_responses = $options->readonly && $reviewoptions & QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY; // Show general feedback if the question has been graded and the quiz allows it. $options->generalfeedback = question_state_is_graded($state) && $reviewoptions & QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_IMMEDIATELY; // Show overallfeedback once the attempt is over. $options->overallfeedback = false; // Always show responses and scores $options->responses = true; $options->scores = true; $options->quizstate = QUIZ_STATE_DURING; $options->history = false; return $options; }
/** * Checks to see if a question in a particular attempt is graded * * @return boolean * @todo Finnish documenting this function **/ function is_graded($question, $attempt) { global $CFG; if (!($state = get_record_sql("SELECT state.id, state.event FROM\n {$CFG->prefix}question_states state, {$CFG->prefix}question_sessions sess\n WHERE sess.newest = state.id AND\n sess.attemptid = {$attempt->uniqueid} AND\n sess.questionid = {$question->id}"))) { error('Could not find question state'); } return question_state_is_graded($state); }
function other_cols($colname, $attempt) { global $QTYPES, $OUTPUT; static $states = array(); if (preg_match('/^qsanswer([0-9]+)$/', $colname, $matches)) { if ($attempt->uniqueid == 0) { return '-'; } $questionid = $matches[1]; if (isset($this->gradedstatesbyattempt[$attempt->uniqueid][$questionid])) { $stateforqinattempt = $this->gradedstatesbyattempt[$attempt->uniqueid][$questionid]; } else { return '-'; } $question = $this->questions[$questionid]; restore_question_state($question, $stateforqinattempt); if (!$this->is_downloading() || $this->is_downloading() == 'xhtml') { $formathtml = true; } else { $formathtml = false; } $summary = $QTYPES[$question->qtype]->response_summary($question, $stateforqinattempt, QUIZ_REPORT_RESPONSES_MAX_LEN_TO_DISPLAY, $formathtml); if (!$this->is_downloading()) { if ($summary) { $link = html_link::make("/mod/quiz/reviewquestion.php?attempt={$attempt->attempt}&question={$question->id}", $summary); $link->add_action(new popup_action('click', $link->url, 'reviewquestion', array('height' => 450, 'width' => 650))); $link->title = $question->formattedname; $summary = $OUTPUT->link($link); if (question_state_is_graded($stateforqinattempt) && $question->maxgrade > 0) { $grade = $stateforqinattempt->grade / $question->maxgrade; $qclass = question_get_feedback_class($grade); $feedbackimg = question_get_feedback_image($grade); $questionclass = "que"; return "<span class=\"{$questionclass}\"><span class=\"{$qclass}\">" . $summary . "</span></span>{$feedbackimg}"; } else { return $summary; } } else { return ''; } } else { return $summary; } } else { return NULL; } }
function question_print_comment_box($question, $state, $attempt, $url) { global $CFG, $QTYPES; $prefix = 'response'; $usehtmleditor = can_use_richtext_editor(); if (!question_state_is_graded($state) && $QTYPES[$question->qtype]->is_question_manual_graded($question, $attempt->layout)) { $grade = ''; } else { $grade = round($state->last_graded->grade, 3); } echo '<form method="post" action="' . $url . '">'; include $CFG->dirroot . '/question/comment.html'; echo '<input type="hidden" name="attempt" value="' . $attempt->uniqueid . '" />'; echo '<input type="hidden" name="question" value="' . $question->id . '" />'; echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />'; echo '<input type="submit" name="submit" value="' . get_string('save', 'quiz') . '" />'; echo '</form>'; if ($usehtmleditor) { use_html_editor(); } }
/** * Prints questions with comment and grade form underneath each question * * @return void * @todo Finish documenting this function **/ function print_questions_and_form($quiz, $question, $userid, $attemptid, $gradeungraded, $gradenextungraded, $ungraded) { global $CFG; // TODO get the context, and put in proper roles an permissions checks. $context = NULL; $questions[$question->id] =& $question; $usehtmleditor = can_use_richtext_editor(); list($select, $from, $where) = $this->attempts_sql($quiz->id, false, $question->id, $userid, $attemptid, $gradeungraded, $gradenextungraded); $sort = 'ORDER BY u.firstname, u.lastname, qa.attempt ASC'; if ($gradenextungraded) { $attempts = get_records_sql($select . $from . $where . $sort, 0, QUIZ_REPORT_DEFAULT_GRADING_PAGE_SIZE); } else { $attempts = get_records_sql($select . $from . $where . $sort); } if ($attempts) { $firstattempt = current($attempts); $fullname = fullname($firstattempt); if ($gradeungraded) { // getting all ungraded attempts print_heading(get_string('gradingungraded', 'quiz_grading', $ungraded), '', 3); } else { if ($gradenextungraded) { // getting next ungraded attempts print_heading(get_string('gradingnextungraded', 'quiz_grading', QUIZ_REPORT_DEFAULT_GRADING_PAGE_SIZE), '', 3); } else { if ($userid) { print_heading(get_string('gradinguser', 'quiz_grading', $fullname), '', 3); } else { if ($attemptid) { $a = new object(); $a->fullname = $fullname; $a->attempt = $firstattempt->attempt; print_heading(get_string('gradingattempt', 'quiz_grading', $a), '', 3); } else { print_heading(get_string('gradingall', 'quiz_grading', count($attempts)), '', 3); } } } } // Display the form with one part for each selected attempt echo '<form method="post" action="report.php">' . '<input type="hidden" name="mode" value="grading" />' . '<input type="hidden" name="q" value="' . $quiz->id . '" />' . '<input type="hidden" name="sesskey" value="' . sesskey() . '" />' . '<input type="hidden" name="questionid" value="' . $question->id . '" />'; foreach ($attempts as $attempt) { // Load the state for this attempt (The questions array was created earlier) $states = get_question_states($questions, $quiz, $attempt); // The $states array is indexed by question id but because we are dealing // with only one question there is only one entry in this array $state =& $states[$question->id]; $options = quiz_get_reviewoptions($quiz, $attempt, $context); unset($options->questioncommentlink); $options->noeditlink = true; $copy = $state->manualcomment; $state->manualcomment = ''; $options->readonly = 1; $gradedclass = question_state_is_graded($state) ? ' class="highlightgraded" ' : ''; $gradedstring = question_state_is_graded($state) ? ' ' . get_string('graded', 'quiz_grading') : ''; $a = new object(); $a->fullname = fullname($attempt, true); $a->attempt = $attempt->attempt; // print the user name, attempt count, the question, and some more hidden fields echo '<div class="boxaligncenter" width="80%" style="clear:left;padding:15px;">'; echo "<span{$gradedclass}>" . get_string('gradingattempt', 'quiz_grading', $a); echo $gradedstring . "</span>"; print_question($question, $state, '', $quiz, $options); $prefix = "manualgrades[{$attempt->uniqueid}]"; if (!question_state_is_graded($state)) { $grade = ''; } else { $grade = round($state->last_graded->grade, 3); } $state->manualcomment = $copy; include $CFG->dirroot . '/question/comment.html'; echo '</div>'; } echo '<div class="boxaligncenter"><input type="submit" value="' . get_string('savechanges') . '" /></div>' . '</form>'; if ($usehtmleditor) { use_html_editor(); } } else { notify(get_string('noattemptstoshow', 'quiz')); } }
/** * @param string $colname the name of the column. * @param object $attempt the row of data - see the SQL in display() in * mod/quiz/report/overview/report.php to see what fields are present, * and what they are called. * @return string the contents of the cell. */ function other_cols($colname, $attempt) { if (preg_match('/^qsgrade([0-9]+)$/', $colname, $matches)) { $questionid = $matches[1]; $question = $this->questions[$questionid]; if (isset($this->gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid])) { $stateforqinattempt = $this->gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid]; } else { $stateforqinattempt = false; } if ($stateforqinattempt && question_state_is_graded($stateforqinattempt)) { $grade = quiz_rescale_grade($stateforqinattempt->grade, $this->quiz, 'question'); if (!$this->is_downloading()) { if (isset($this->regradedqs[$attempt->attemptuniqueid][$questionid])) { $gradefromdb = $grade; $newgrade = quiz_rescale_grade($this->regradedqs[$attempt->attemptuniqueid][$questionid]->newgrade, $this->quiz, 'question'); $oldgrade = quiz_rescale_grade($this->regradedqs[$attempt->attemptuniqueid][$questionid]->oldgrade, $this->quiz, 'question'); $grade = '<del>' . $oldgrade . '</del><br />' . $newgrade; } $linktopopup = link_to_popup_window('/mod/quiz/reviewquestion.php?attempt=' . $attempt->attempt . '&question=' . $question->id, 'reviewquestion', $grade, 450, 650, get_string('reviewresponse', 'quiz'), 'none', true); if ($this->questions[$questionid]->maxgrade != 0) { $fractionofgrade = $stateforqinattempt->grade / $this->questions[$questionid]->maxgrade; $qclass = question_get_feedback_class($fractionofgrade); $feedbackimg = question_get_feedback_image($fractionofgrade); $questionclass = "que"; return "<span class=\"{$questionclass}\"><span class=\"{$qclass}\">" . $linktopopup . "</span></span>{$feedbackimg}"; } else { return $linktopopup; } } else { return $grade; } } else { return '--'; } } else { return NULL; } }
/** * @param string $colname the name of the column. * @param object $attempt the row of data - see the SQL in display() in * mod/quiz/report/overview/report.php to see what fields are present, * and what they are called. * @return string the contents of the cell. */ function other_cols($colname, $attempt) { global $OUTPUT; if (preg_match('/^qsgrade([0-9]+)$/', $colname, $matches)) { $questionid = $matches[1]; $question = $this->questions[$questionid]; if (isset($this->gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid])) { $stateforqinattempt = $this->gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid]; } else { $stateforqinattempt = false; } if ($stateforqinattempt && question_state_is_graded($stateforqinattempt)) { $grade = quiz_rescale_grade($stateforqinattempt->grade, $this->quiz, 'question'); if (!$this->is_downloading()) { if (isset($this->regradedqs[$attempt->attemptuniqueid][$questionid])) { $gradefromdb = $grade; $newgrade = quiz_rescale_grade($this->regradedqs[$attempt->attemptuniqueid][$questionid]->newgrade, $this->quiz, 'question'); $oldgrade = quiz_rescale_grade($this->regradedqs[$attempt->attemptuniqueid][$questionid]->oldgrade, $this->quiz, 'question'); $grade = '<del>' . $oldgrade . '</del><br />' . $newgrade; } $link = new moodle_url("/mod/quiz/reviewquestion.php?attempt={$attempt->attempt}&question={$question->id}"); $action = new popup_action('click', $link, 'reviewquestion', array('height' => 450, 'width' => 650)); $linktopopup = $OUTPUT->action_link($link, $grade, $action, array('title' => get_string('reviewresponsetoq', 'quiz', $question->formattedname))); if ($this->questions[$questionid]->maxgrade != 0) { $fractionofgrade = $stateforqinattempt->grade / $this->questions[$questionid]->maxgrade; $qclass = question_get_feedback_class($fractionofgrade); $feedbackimg = question_get_feedback_image($fractionofgrade); $questionclass = "que"; return "<span class=\"{$questionclass}\"><span class=\"{$qclass}\">" . $linktopopup . "</span></span>{$feedbackimg}"; } else { return $linktopopup; } } else { return $grade; } } else { if ($stateforqinattempt && question_state_is_closed($stateforqinattempt)) { $text = get_string('requiresgrading', 'quiz_overview'); if (!$this->is_downloading()) { $link = new moodle_url("/mod/quiz/reviewquestion.php?attempt={$attempt->attempt}&question={$question->id}"); $action = new popup_action('click', $link, 'reviewquestion', array('height' => 450, 'width' => 650)); return $OUTPUT->action_link($link, $text, $action, array('title' => get_string('reviewresponsetoq', 'quiz', $question->formattedname))); } else { return $text; } } else { return '--'; } } } else { return NULL; } }
/** * This is a copy & paste of original shortanswer/questiontype.php print_question_grading_details() but * filters correct answers before printing. */ function wrsqz_print_question_grading_details($questionType, $dbType, &$question, &$state, $cmoptions, $options){ if($questionType=='shortanswer'){ global $QTYPES ; // MDL-7496 show correct answer after "Incorrect" $correctanswer = ''; if ($correctanswers = wrsqz_get_correct_responses('shortanswer', 'wshanprom', null, $question, $state, 'W')) { if ($options->readonly && $options->correct_responses) { $delimiter = ''; if ($correctanswers) { foreach ($correctanswers as $ca) { $correctanswer .= $delimiter.$ca; $delimiter = ', '; } } } } if (QUESTION_EVENTDUPLICATE == $state->event) { echo ' '; print_string('duplicateresponse', 'quiz'); } if (!empty($question->maxgrade) && $options->scores) { if (question_state_is_graded($state->last_graded)) { // Display the grading details from the last graded state $grade = new stdClass; $grade->cur = round($state->last_graded->grade, $cmoptions->decimalpoints); $grade->max = $question->maxgrade; $grade->raw = round($state->last_graded->raw_grade, $cmoptions->decimalpoints); // let student know wether the answer was correct echo '<div class="correctness '; if ($state->last_graded->raw_grade >= $question->maxgrade/1.01) { // We divide by 1.01 so that rounding errors dont matter. echo ' correct">'; print_string('correct', 'quiz'); } else if ($state->last_graded->raw_grade > 0) { echo ' partiallycorrect">'; print_string('partiallycorrect', 'quiz'); // MDL-7496 if ($correctanswer) { echo ('<div class="correctness">'); $format_options = new StdClass(); $format_options->filter=true; $format_options->smiley=false; $format_options->para=false; $format_options->newlines=false; $filtered_correct_answer = format_text($correctanswer, $question->questiontextformat, $format_options, $cmoptions->course); print_string('correctansweris', 'quiz', $filtered_correct_answer); echo ('</div>'); } } else { echo ' incorrect">'; // MDL-7496 print_string('incorrect', 'quiz'); if ($correctanswer) { echo ('<div class="correctness">'); $format_options = new StdClass(); $format_options->filter=true; $format_options->smiley=false; $format_options->para=false; $format_options->newlines=false; $filtered_correct_answer = format_text($correctanswer, FORMAT_MOODLE, $format_options, $cmoptions->course); print_string('correctansweris', 'quiz', $filtered_correct_answer); echo ('</div>'); } } echo '</div>'; echo '<div class="gradingdetails">'; // print grade for this submission print_string('gradingdetails', 'quiz', $grade); if ($cmoptions->penaltyscheme) { // print details of grade adjustment due to penalties if ($state->last_graded->raw_grade > $state->last_graded->grade){ echo ' '; print_string('gradingdetailsadjustment', 'quiz', $grade); } // print info about new penalty // penalty is relevant only if the answer is not correct and further attempts are possible if (($state->last_graded->raw_grade < $question->maxgrade) and (QUESTION_EVENTCLOSEANDGRADE != $state->event)) { if ('' !== $state->last_graded->penalty && ((float)$state->last_graded->penalty) > 0.0) { // A penalty was applied so display it echo ' '; print_string('gradingdetailspenalty', 'quiz', $state->last_graded->penalty); } else { /* No penalty was applied even though the answer was not correct (eg. a syntax error) so tell the student that they were not penalised for the attempt */ echo ' '; print_string('gradingdetailszeropenalty', 'quiz'); } } } echo '</div>'; } } } }
function other_cols($colname, $attempt) { global $QTYPES; static $states = array(); if (preg_match('/^qsanswer([0-9]+)$/', $colname, $matches)) { if ($attempt->uniqueid == 0) { return '-'; } $questionid = $matches[1]; if (isset($this->gradedstatesbyattempt[$attempt->uniqueid][$questionid])) { $stateforqinattempt = $this->gradedstatesbyattempt[$attempt->uniqueid][$questionid]; } else { return '-'; } $question = $this->questions[$questionid]; restore_question_state($question, $stateforqinattempt); if (!$this->is_downloading() || $this->is_downloading() == 'xhtml') { $formathtml = true; } else { $formathtml = false; } $summary = $QTYPES[$question->qtype]->response_summary($question, $stateforqinattempt, QUIZ_REPORT_RESPONSES_MAX_LEN_TO_DISPLAY, $formathtml); if (!$this->is_downloading()) { if ($summary) { $summary = link_to_popup_window('/mod/quiz/reviewquestion.php?attempt=' . $attempt->attempt . '&question=' . $question->id, 'reviewquestion', $summary, 450, 650, get_string('reviewresponse', 'quiz'), 'none', true); if (question_state_is_graded($stateforqinattempt) && $question->maxgrade > 0) { $grade = $stateforqinattempt->grade / $question->maxgrade; $qclass = question_get_feedback_class($grade); $feedbackimg = question_get_feedback_image($grade); $questionclass = "que"; return "<span class=\"{$questionclass}\"><span class=\"{$qclass}\">" . $summary . "</span></span>{$feedbackimg}"; } else { return $summary; } } else { return ''; } } else { return $summary; } } else { return NULL; } }
/** * Display the report. */ function display($quiz, $cm, $course) { global $CFG, $db; // Define some strings $strreallydel = addslashes(get_string('deleteattemptcheck', 'quiz')); $strtimeformat = get_string('strftimedatetime'); $strreviewquestion = get_string('reviewresponse', 'quiz'); $context = get_context_instance(CONTEXT_MODULE, $cm->id); // Only print headers if not asked to download data if (!($download = optional_param('download', NULL))) { $this->print_header_and_tabs($cm, $course, $quiz, "overview"); } if ($attemptids = optional_param('attemptid', array(), PARAM_INT)) { //attempts need to be deleted require_capability('mod/quiz:deleteattempts', $context); $attemptids = optional_param('attemptid', array(), PARAM_INT); foreach ($attemptids as $attemptid) { add_to_log($course->id, 'quiz', 'delete attempt', 'report.php?id=' . $cm->id, $attemptid, $cm->id); quiz_delete_attempt($attemptid, $quiz); } //No need for a redirect, any attemptids that do not exist are ignored. //So no problem if the user refreshes and tries to delete the same attempts //twice. } // Work out some display options - whether there is feedback, and whether scores should be shown. $hasfeedback = quiz_has_feedback($quiz->id) && $quiz->grade > 1.0E-7 && $quiz->sumgrades > 1.0E-7; $fakeattempt = new stdClass(); $fakeattempt->preview = false; $fakeattempt->timefinish = $quiz->timeopen; $reviewoptions = quiz_get_reviewoptions($quiz, $fakeattempt, $context); $showgrades = $quiz->grade && $quiz->sumgrades && $reviewoptions->scores; $pageoptions = array(); $pageoptions['id'] = $cm->id; $pageoptions['q'] = $quiz->id; $pageoptions['mode'] = 'overview'; /// find out current groups mode $currentgroup = groups_get_activity_group($cm, true); $reporturl = new moodle_url($CFG->wwwroot . '/mod/quiz/report.php', $pageoptions); $qmsubselect = quiz_report_qm_filter_select($quiz); $mform = new mod_quiz_report_overview_settings($reporturl, compact('qmsubselect', 'quiz', 'currentgroup')); if ($fromform = $mform->get_data()) { $attemptsmode = $fromform->attemptsmode; if ($qmsubselect) { //control is not on the form if //the grading method is not set //to grade one attempt per user eg. for average attempt grade. $qmfilter = $fromform->qmfilter; } else { $qmfilter = 0; } set_user_preference('quiz_report_overview_detailedmarks', $fromform->detailedmarks); set_user_preference('quiz_report_pagesize', $fromform->pagesize); $detailedmarks = $fromform->detailedmarks; $pagesize = $fromform->pagesize; } else { $qmfilter = optional_param('qmfilter', 0, PARAM_INT); $attemptsmode = optional_param('attemptsmode', QUIZ_REPORT_ATTEMPTS_ALL, PARAM_INT); $detailedmarks = get_user_preferences('quiz_report_overview_detailedmarks', 1); $pagesize = get_user_preferences('quiz_report_pagesize', 0); } if ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL && $currentgroup) { $attemptsmode = QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH; } if (!$reviewoptions->scores) { $detailedmarks = 0; } if ($pagesize < 1) { $pagesize = QUIZ_REPORT_DEFAULT_PAGE_SIZE; } // We only want to show the checkbox to delete attempts // if the user has permissions and if the report mode is showing attempts. $candelete = has_capability('mod/quiz:deleteattempts', $context) && $attemptsmode != QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO; $displayoptions = array(); $displayoptions['attemptsmode'] = $attemptsmode; $displayoptions['qmfilter'] = $qmfilter; $reporturlwithdisplayoptions = new moodle_url($CFG->wwwroot . '/mod/quiz/report.php', $pageoptions + $displayoptions); if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used if (!$download) { groups_print_activity_menu($cm, $reporturlwithdisplayoptions->out()); } } // Print information on the number of existing attempts if (!$download) { //do not print notices when downloading if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm, true, $currentgroup)) { echo '<div class="quizattemptcounts">' . $strattemptnum . '</div>'; } } $nostudents = false; if (!($students = get_users_by_capability($context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '', '', '', false))) { notify(get_string('nostudentsyet')); $nostudents = true; $studentslist = ''; } else { $studentslist = join(',', array_keys($students)); } if (empty($currentgroup)) { // all users who can attempt quizzes $groupstudentslist = ''; $allowedlist = $studentslist; } else { // all users who can attempt quizzes and who are in the currently selected group if (!($groupstudents = get_users_by_capability($context, 'mod/quiz:attempt', '', '', '', '', $currentgroup, '', false))) { notify(get_string('nostudentsingroup')); $nostudents = true; $groupstudents = array(); } $groupstudentslist = join(',', array_keys($groupstudents)); $allowedlist = $groupstudentslist; } if (!$nostudents || $attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) { // Print information on the grading method and whether we are displaying // if (!$download) { //do not print notices when downloading if ($strattempthighlight = quiz_report_highlighting_grading_method($quiz, $qmsubselect, $qmfilter)) { echo '<div class="quizattemptcounts">' . $strattempthighlight . '</div>'; } } // Now check if asked download of data if ($download) { $filename = clean_filename("{$course->shortname} " . format_string($quiz->name, true)); } // Define table columns $columns = array(); $headers = array(); if (!$download && $candelete) { $columns[] = 'checkbox'; $headers[] = NULL; } if (!$download && $CFG->grade_report_showuserimage) { $columns[] = 'picture'; $headers[] = ''; } $columns[] = 'fullname'; $headers[] = get_string('name'); if ($CFG->grade_report_showuseridnumber) { $columns[] = 'idnumber'; $headers[] = get_string('idnumber'); } $columns[] = 'timestart'; $headers[] = get_string('startedon', 'quiz'); $columns[] = 'timefinish'; $headers[] = get_string('timecompleted', 'quiz'); $columns[] = 'duration'; $headers[] = get_string('attemptduration', 'quiz'); if ($showgrades) { $columns[] = 'sumgrades'; $headers[] = get_string('grade', 'quiz') . '/' . $quiz->grade; } if ($detailedmarks) { // we want to display marks for all questions $questions = quiz_report_load_questions($quiz); foreach ($questions as $id => $question) { // Ignore questions of zero length $columns[] = 'qsgrade' . $id; $headers[] = '#' . $question->number; } } if ($hasfeedback) { $columns[] = 'feedbacktext'; $headers[] = get_string('feedback', 'quiz'); } if (!$download) { // Set up the table $table = new flexible_table('mod-quiz-report-overview-report'); $table->define_columns($columns); $table->define_headers($headers); $table->define_baseurl($reporturlwithdisplayoptions->out()); $table->sortable(true); $table->collapsible(true); $table->column_suppress('picture'); $table->column_suppress('fullname'); $table->column_suppress('idnumber'); $table->no_sorting('feedbacktext'); $table->column_class('picture', 'picture'); $table->column_class('fullname', 'bold'); $table->column_class('sumgrades', 'bold'); $table->set_attribute('cellspacing', '0'); $table->set_attribute('id', 'attempts'); $table->set_attribute('class', 'generaltable generalbox'); // Start working -- this is necessary as soon as the niceties are over $table->setup(); } else { if ($download == 'ODS') { require_once "{$CFG->libdir}/odslib.class.php"; $filename .= ".ods"; // Creating a workbook $workbook = new MoodleODSWorkbook("-"); // Sending HTTP headers $workbook->send($filename); // Creating the first worksheet $sheettitle = get_string('reportoverview', 'quiz'); $myxls =& $workbook->add_worksheet($sheettitle); // format types $format =& $workbook->add_format(); $format->set_bold(0); $formatbc =& $workbook->add_format(); $formatbc->set_bold(1); $formatbc->set_align('center'); $formatb =& $workbook->add_format(); $formatb->set_bold(1); $formaty =& $workbook->add_format(); $formaty->set_bg_color('yellow'); $formatc =& $workbook->add_format(); $formatc->set_align('center'); $formatr =& $workbook->add_format(); $formatr->set_bold(1); $formatr->set_color('red'); $formatr->set_align('center'); $formatg =& $workbook->add_format(); $formatg->set_bold(1); $formatg->set_color('green'); $formatg->set_align('center'); // Here starts workshhet headers $colnum = 0; foreach ($headers as $item) { $myxls->write(0, $colnum, $item, $formatbc); $colnum++; } $rownum = 1; } else { if ($download == 'Excel') { require_once "{$CFG->libdir}/excellib.class.php"; $filename .= ".xls"; // Creating a workbook $workbook = new MoodleExcelWorkbook("-"); // Sending HTTP headers $workbook->send($filename); // Creating the first worksheet $sheettitle = get_string('reportoverview', 'quiz'); $myxls =& $workbook->add_worksheet($sheettitle); // format types $format =& $workbook->add_format(); $format->set_bold(0); $formatbc =& $workbook->add_format(); $formatbc->set_bold(1); $formatbc->set_align('center'); $formatb =& $workbook->add_format(); $formatb->set_bold(1); $formaty =& $workbook->add_format(); $formaty->set_bg_color('yellow'); $formatc =& $workbook->add_format(); $formatc->set_align('center'); $formatr =& $workbook->add_format(); $formatr->set_bold(1); $formatr->set_color('red'); $formatr->set_align('center'); $formatg =& $workbook->add_format(); $formatg->set_bold(1); $formatg->set_color('green'); $formatg->set_align('center'); $colnum = 0; foreach ($headers as $item) { $myxls->write(0, $colnum, $item, $formatbc); $colnum++; } $rownum = 1; } else { if ($download == 'CSV') { $filename .= ".txt"; header("Content-Type: application/download\n"); header("Content-Disposition: attachment; filename=\"{$filename}\""); header("Expires: 0"); header("Cache-Control: must-revalidate,post-check=0,pre-check=0"); header("Pragma: public"); echo implode("\t", $headers) . " \n"; } } } } // Construct the SQL $select = 'SELECT ' . sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')) . ' AS uniqueid, '; if ($qmsubselect) { $select .= "(CASE " . " WHEN {$qmsubselect} THEN 1" . " ELSE 0 " . "END) AS gradedattempt, "; } $select .= 'qa.uniqueid AS attemptuniqueid, qa.id AS attempt, ' . 'u.id AS userid, u.idnumber, u.firstname, u.lastname, u.picture, u.imagealt, ' . 'qa.sumgrades, qa.timefinish, qa.timestart, qa.timefinish - qa.timestart AS duration '; // This part is the same for all cases - join users and quiz_attempts tables $from = 'FROM ' . $CFG->prefix . 'user u '; $from .= 'LEFT JOIN ' . $CFG->prefix . 'quiz_attempts qa ON qa.userid = u.id AND qa.quiz = ' . $quiz->id; if ($qmsubselect && $qmfilter) { $from .= ' AND ' . $qmsubselect; } switch ($attemptsmode) { case QUIZ_REPORT_ATTEMPTS_ALL: // Show all attempts, including students who are no longer in the course $where = ' WHERE qa.id IS NOT NULL AND qa.preview = 0'; break; case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH: // Show only students with attempts $where = ' WHERE u.id IN (' . $allowedlist . ') AND qa.preview = 0 AND qa.id IS NOT NULL'; break; case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO: // Show only students without attempts $where = ' WHERE u.id IN (' . $allowedlist . ') AND qa.id IS NULL'; break; case QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS: // Show all students with or without attempts $where = ' WHERE u.id IN (' . $allowedlist . ') AND (qa.preview = 0 OR qa.preview IS NULL)'; break; } $countsql = 'SELECT COUNT(DISTINCT(' . sql_concat('u.id', '\'#\'', 'COALESCE(qa.attempt, 0)') . ')) ' . $from . $where; // Add table joins so we can sort by question grade // unfortunately can't join all tables necessary to fetch all grades // to get the state for one question per attempt row we must join two tables // and there is a limit to how many joins you can have in one query. In MySQL it // is 61. This means that when having more than 29 questions the query will fail. // So we join just the tables needed to sort the attempts. if (!$download && ($sort = $table->get_sql_sort())) { if (!$download && $detailedmarks) { $from .= ' '; $sortparts = explode(',', $sort); $matches = array(); foreach ($sortparts as $sortpart) { $sortpart = trim($sortpart); if (preg_match('/^qsgrade([0-9]+)/', $sortpart, $matches)) { $qid = intval($matches[1]); $select .= ", qs{$qid}.grade AS qsgrade{$qid}, qs{$qid}.event AS qsevent{$qid}, qs{$qid}.id AS qsid{$qid}"; $from .= "LEFT JOIN {$CFG->prefix}question_sessions qns{$qid} ON qns{$qid}.attemptid = qa.uniqueid AND qns{$qid}.questionid = {$qid} "; $from .= "LEFT JOIN {$CFG->prefix}question_states qs{$qid} ON qs{$qid}.id = qns{$qid}.newgraded "; } else { $newsort[] = $sortpart; } } $select .= ' '; } } if ($download) { $sort = ''; } // Fix some wired sorting if (empty($sort)) { $sort = ' ORDER BY uniqueid'; } else { $sort = ' ORDER BY ' . $sort; } if (!$download) { // Add extra limits due to initials bar if ($table->get_sql_where()) { $where .= ' AND ' . $table->get_sql_where(); } if (!empty($countsql)) { $totalinitials = count_records_sql($countsql); if ($table->get_sql_where()) { $countsql .= ' AND ' . $table->get_sql_where(); } $total = count_records_sql($countsql); } $table->pagesize($pagesize, $total); } // Fetch the attempts if (!$download) { $attempts = get_records_sql($select . $from . $where . $sort, $table->get_page_start(), $table->get_page_size()); } else { $attempts = get_records_sql($select . $from . $where . $sort); } // Build table rows if (!$download) { $table->initialbars($totalinitials > 20); } if ($attempts) { if ($detailedmarks) { //get all the attempt ids we want to display on this page //or to export for download. $attemptids = array(); foreach ($attempts as $attempt) { if ($attempt->attemptuniqueid > 0) { $attemptids[] = $attempt->attemptuniqueid; } } $gradedstatesbyattempt = quiz_get_newgraded_states($attemptids, true, 'qs.id, qs.grade, qs.event, qs.question, qs.attempt'); } foreach ($attempts as $attempt) { // Username columns. $row = array(); if (in_array('checkbox', $columns)) { if ($attempt->attempt) { $row[] = '<input type="checkbox" name="attemptid[]" value="' . $attempt->attempt . '" />'; } else { $row[] = ''; } } if (in_array('picture', $columns)) { $attempt->id = $attempt->userid; $picture = print_user_picture($attempt, $course->id, NULL, false, true); $row[] = $picture; } if (!$download) { $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $attempt->userid . '&course=' . $course->id . '">' . fullname($attempt) . '</a>'; $row[] = $userlink; } else { $row[] = fullname($attempt); } if (in_array('idnumber', $columns)) { $row[] = $attempt->idnumber; } // Timing columns. if ($attempt->attempt) { $startdate = userdate($attempt->timestart, $strtimeformat); if (!$download) { $row[] = '<a href="review.php?q=' . $quiz->id . '&attempt=' . $attempt->attempt . '">' . $startdate . '</a>'; } else { $row[] = $startdate; } if ($attempt->timefinish) { $timefinish = userdate($attempt->timefinish, $strtimeformat); $duration = format_time($attempt->duration); if (!$download) { $row[] = '<a href="review.php?q=' . $quiz->id . '&attempt=' . $attempt->attempt . '">' . $timefinish . '</a>'; } else { $row[] = $timefinish; } $row[] = $duration; } else { $row[] = '-'; $row[] = get_string('unfinished', 'quiz'); } } else { $row[] = '-'; $row[] = '-'; $row[] = '-'; } // Grades columns. if ($showgrades) { if ($attempt->timefinish) { $grade = quiz_rescale_grade($attempt->sumgrades, $quiz); if (!$download) { $gradehtml = '<a href="review.php?q=' . $quiz->id . '&attempt=' . $attempt->attempt . '">' . $grade . '</a>'; if ($qmsubselect && $attempt->gradedattempt) { $gradehtml = '<div class="highlight">' . $gradehtml . '</div>'; } $row[] = $gradehtml; } else { $row[] = $grade; } } else { $row[] = '-'; } } if ($detailedmarks) { if (empty($attempt->attempt)) { foreach ($questions as $question) { $row[] = '-'; } } else { foreach ($questions as $questionid => $question) { $stateforqinattempt = $gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid]; if (question_state_is_graded($stateforqinattempt)) { $grade = quiz_rescale_grade($stateforqinattempt->grade, $quiz); } else { $grade = '--'; } if (!$download) { $grade = $grade . '/' . quiz_rescale_grade($question->grade, $quiz); $row[] = link_to_popup_window('/mod/quiz/reviewquestion.php?state=' . $stateforqinattempt->id . '&number=' . $question->number, 'reviewquestion', $grade, 450, 650, $strreviewquestion, 'none', true); } else { $row[] = $grade; } } } } // Feedback column. if ($hasfeedback) { if ($attempt->timefinish) { $row[] = quiz_report_feedback_for_grade(quiz_rescale_grade($attempt->sumgrades, $quiz), $quiz->id); } else { $row[] = '-'; } } if (!$download) { $table->add_data($row); } else { if ($download == 'Excel' or $download == 'ODS') { $colnum = 0; foreach ($row as $item) { $myxls->write($rownum, $colnum, $item, $format); $colnum++; } $rownum++; } else { if ($download == 'CSV') { $text = implode("\t", $row); echo $text . " \n"; } } } } //end of adding data from attempts data to table / download //now add averages : if (!$download && $attempts) { $averagesql = "SELECT AVG(qg.grade) AS grade " . "FROM {$CFG->prefix}quiz_grades qg " . "WHERE quiz=" . $quiz->id; $table->add_separator(); if ($groupstudentslist) { $groupaveragesql = $averagesql . " AND qg.userid IN ({$groupstudentslist})"; $groupaverage = get_record_sql($groupaveragesql); $groupaveragerow = array('fullname' => get_string('groupavg', 'grades'), 'sumgrades' => round($groupaverage->grade, $quiz->decimalpoints), 'feedbacktext' => quiz_report_feedback_for_grade($groupaverage->grade, $quiz->id)); if ($detailedmarks && $qmsubselect) { $avggradebyq = quiz_get_average_grade_for_questions($quiz, $groupstudentslist); $groupaveragerow += quiz_format_average_grade_for_questions($avggradebyq, $questions, $quiz, $download); } $table->add_data_keyed($groupaveragerow); } $overallaverage = get_record_sql($averagesql . " AND qg.userid IN ({$studentslist})"); $overallaveragerow = array('fullname' => get_string('overallaverage', 'grades'), 'sumgrades' => round($overallaverage->grade, $quiz->decimalpoints), 'feedbacktext' => quiz_report_feedback_for_grade($overallaverage->grade, $quiz->id)); if ($detailedmarks && $qmsubselect) { $avggradebyq = quiz_get_average_grade_for_questions($quiz, $studentslist); $overallaveragerow += quiz_format_average_grade_for_questions($avggradebyq, $questions, $quiz, $download); } $table->add_data_keyed($overallaveragerow); } if (!$download) { // Start form echo '<div id="tablecontainer">'; echo '<form id="attemptsform" method="post" action="' . $reporturlwithdisplayoptions->out(true) . '" onsubmit="return confirm(\'' . $strreallydel . '\');">'; echo '<div style="display: none;">'; echo $reporturlwithdisplayoptions->hidden_params_out(); echo '</div>'; echo '<div>'; // Print table $table->print_html(); // Print "Select all" etc. if (!empty($attempts) && $candelete) { echo '<table id="commands">'; echo '<tr><td>'; echo '<a href="javascript:select_all_in(\'DIV\',null,\'tablecontainer\');">' . get_string('selectall', 'quiz') . '</a> / '; echo '<a href="javascript:deselect_all_in(\'DIV\',null,\'tablecontainer\');">' . get_string('selectnone', 'quiz') . '</a> '; echo ' '; echo '<input type="submit" value="' . get_string('deleteselected', 'quiz_overview') . '"/>'; echo '</td></tr></table>'; } // Close form echo '</div>'; echo '</form></div>'; if (!empty($attempts)) { echo '<table class="boxaligncenter"><tr>'; echo '<td>'; print_single_button($reporturl->out(true), $pageoptions + $displayoptions + array('download' => 'ODS'), get_string('downloadods')); echo "</td>\n"; echo '<td>'; print_single_button($reporturl->out(true), $pageoptions + $displayoptions + array('download' => 'Excel'), get_string('downloadexcel')); echo "</td>\n"; echo '<td>'; print_single_button($reporturl->out(true), $pageoptions + $displayoptions + array('download' => 'CSV'), get_string('downloadtext')); echo "</td>\n"; echo "<td>"; helpbutton('overviewdownload', get_string('overviewdownload', 'quiz_overview'), 'quiz'); echo "</td>\n"; echo '</tr></table>'; } } } else { if (!$download) { $table->print_html(); } } if ($download == 'Excel' or $download == 'ODS') { $workbook->close(); exit; } else { if ($download == 'CSV') { exit; } } } if (!$download) { // Print display options $mform->set_data($displayoptions + compact('detailedmarks', 'pagesize')); $mform->display(); //should be quicker than a COUNT to test if there is at least one record : if ($showgrades && record_exists('quiz_grades', 'quiz', $quiz->id)) { $imageurl = $CFG->wwwroot . '/mod/quiz/report/overview/overviewgraph.php?id=' . $quiz->id; print_heading(get_string('overviewreportgraph', 'quiz_overview')); echo '<div class="mdl-align"><img src="' . $imageurl . '" alt="' . get_string('overviewreportgraph', 'quiz_overview') . '" /></div>'; } } return true; }
function check_file_access($question, $state, $options, $contextid, $component, $filearea, $args) { if ($component == 'question' && $filearea == 'questiontext') { // Question text always visible. return true; } else { if ($component == 'question' && ($filearea = 'generalfeedback')) { return $options->generalfeedback && question_state_is_graded($state->last_graded); } else { // Unrecognised component or filearea. return false; } } }
function test_question_state_is_graded() { $state = new stdClass(); $state->event = QUESTION_EVENTOPEN; $this->assertFalse(question_state_is_graded($state)); $state->event = QUESTION_EVENTNAVIGATE; $this->assertFalse(question_state_is_graded($state)); $state->event = QUESTION_EVENTSAVE; $this->assertFalse(question_state_is_graded($state)); $state->event = QUESTION_EVENTDUPLICATE; $this->assertFalse(question_state_is_graded($state)); $state->event = QUESTION_EVENTVALIDATE; $this->assertFalse(question_state_is_graded($state)); $state->event = QUESTION_EVENTSUBMIT; $this->assertFalse(question_state_is_graded($state)); $state->event = QUESTION_EVENTCLOSE; $this->assertFalse(question_state_is_graded($state)); $state->event = QUESTION_EVENTCLOSEANDGRADE; $this->assertTrue(question_state_is_graded($state)); $state->event = QUESTION_EVENTMANUALGRADE; $this->assertTrue(question_state_is_graded($state)); $state->event = QUESTION_EVENTGRADE; $this->assertTrue(question_state_is_graded($state)); }
function question_print_comment_fields($question, $state, $prefix, $cmoptions, $caption = '') { global $QTYPES; $idprefix = preg_replace('/[^-_a-zA-Z0-9]/', '', $prefix); $otherquestionsinuse = ''; if (!empty($cmoptions->questions)) { $otherquestionsinuse = $cmoptions->questions; } if (!question_state_is_graded($state) && $QTYPES[$question->qtype]->is_question_manual_graded($question, $otherquestionsinuse)) { $grade = ''; } else { $grade = question_format_grade($cmoptions, $state->last_graded->grade); } $maxgrade = question_format_grade($cmoptions, $question->maxgrade); $fieldsize = strlen($maxgrade) - 1; if (empty($caption)) { $caption = format_string($question->name); } ?> <fieldset class="que comment clearfix"> <legend class="ftoggler"><?php echo $caption; ?> </legend> <div class="fcontainer clearfix"> <div class="fitem"> <div class="fitemtitle"> <label for="<?php echo $idprefix; ?> _comment_box"><?php print_string('comment', 'quiz'); ?> </label> </div> <div class="felement fhtmleditor"> <?php print_textarea(can_use_html_editor(), 15, 60, 630, 300, $prefix . '[comment]', $state->manualcomment, 0, false, $idprefix . '_comment_box'); ?> </div> </div> <div class="fitem"> <div class="fitemtitle"> <label for="<?php echo $idprefix; ?> _grade_field"><?php print_string('grade', 'quiz'); ?> </label> </div> <div class="felement ftext"> <input type="text" name="<?php echo $prefix; ?> [grade]" size="<?php echo $fieldsize; ?> " id="<?php echo $idprefix; ?> _grade_field" value="<?php echo $grade; ?> " /> / <?php echo $maxgrade; ?> </div> </div> </div> </fieldset> <?php }
/** * Prints the score obtained and maximum score available plus any penalty * information * * This function prints a summary of the scoring in the most recently * graded state (the question may not have been submitted for marking at * the current state). The default implementation should be suitable for most * question types. * @param object $question The question for which the grading details are * to be rendered. Question type specific information * is included. The maximum possible grade is in * ->maxgrade. * @param object $state The state. In particular the grading information * is in ->grade, ->raw_grade and ->penalty. * @param object $cmoptions * @param object $options An object describing the rendering options. */ function print_question_grading_details(&$question, &$state, $cmoptions, $options) { /* The default implementation prints the number of marks if no attempt has been made. Otherwise it displays the grade obtained out of the maximum grade available and a warning if a penalty was applied for the attempt and displays the overall grade obtained counting all previous responses (and penalties) */ global $QTYPES; // MDL-7496 show correct answer after "Incorrect" $correctanswer = ''; if ($correctanswers = $QTYPES[$question->qtype]->get_correct_responses($question, $state)) { if ($options->readonly && $options->correct_responses) { $delimiter = ''; if ($correctanswers) { foreach ($correctanswers as $ca) { $correctanswer .= $delimiter . $ca; $delimiter = ', '; } } } } if (QUESTION_EVENTDUPLICATE == $state->event) { echo ' '; print_string('duplicateresponse', 'quiz'); } if ($question->maxgrade > 0 && $options->scores) { if (question_state_is_graded($state->last_graded)) { // Display the grading details from the last graded state $grade = new stdClass(); $grade->cur = question_format_grade($cmoptions, $state->last_graded->grade); $grade->max = question_format_grade($cmoptions, $question->maxgrade); $grade->raw = question_format_grade($cmoptions, $state->last_graded->raw_grade); // let student know wether the answer was correct $class = question_get_feedback_class($state->last_graded->raw_grade / $question->maxgrade); echo '<div class="correctness ' . $class . '">' . get_string($class, 'quiz'); if ($correctanswer != '' && ($class == 'partiallycorrect' || $class == 'incorrect')) { echo '<div class="correctness">'; print_string('correctansweris', 'quiz', s($correctanswer)); echo '</div>'; } echo '</div>'; echo '<div class="gradingdetails">'; // print grade for this submission print_string('gradingdetails', 'quiz', $grade); // A unit penalty for numerical was applied so display it // a temporary solution for unit rendering in numerical // waiting for the new question engine code for a permanent one if (isset($state->options->raw_unitpenalty) && $state->options->raw_unitpenalty > 0.0) { echo ' '; print_string('unitappliedpenalty', 'qtype_numerical', question_format_grade($cmoptions, $state->options->raw_unitpenalty)); } if ($cmoptions->penaltyscheme) { // print details of grade adjustment due to penalties if ($state->last_graded->raw_grade > $state->last_graded->grade) { echo ' '; print_string('gradingdetailsadjustment', 'quiz', $grade); } // print info about new penalty // penalty is relevant only if the answer is not correct and further attempts are possible if ($state->last_graded->raw_grade < $question->maxgrade and QUESTION_EVENTCLOSEANDGRADE != $state->event) { if ('' !== $state->last_graded->penalty && (double) $state->last_graded->penalty > 0.0) { echo ' '; print_string('gradingdetailspenalty', 'quiz', question_format_grade($cmoptions, $state->last_graded->penalty)); } else { /* No penalty was applied even though the answer was not correct (eg. a syntax error) so tell the student that they were not penalised for the attempt */ echo ' '; print_string('gradingdetailszeropenalty', 'quiz'); } } } echo '</div>'; } } }
/** * Prints the score obtained and maximum score available plus any penalty * information * * This function prints a summary of the scoring in the most recently * graded state (the question may not have been submitted for marking at * the current state). The default implementation should be suitable for most * question types. * @param object $question The question for which the grading details are * to be rendered. Question type specific information * is included. The maximum possible grade is in * ->maxgrade. * @param object $state The state. In particular the grading information * is in ->grade, ->raw_grade and ->penalty. * @param object $cmoptions * @param object $options An object describing the rendering options. */ function print_question_grading_details(&$question, &$state, $cmoptions, $options) { /* The default implementation prints the number of marks if no attempt has been made. Otherwise it displays the grade obtained out of the maximum grade available and a warning if a penalty was applied for the attempt and displays the overall grade obtained counting all previous responses (and penalties) */ if (QUESTION_EVENTDUPLICATE == $state->event) { echo ' '; print_string('duplicateresponse', 'quiz'); } if (!empty($question->maxgrade) && $options->scores) { if (question_state_is_graded($state->last_graded)) { // Display the grading details from the last graded state $grade = new stdClass(); $grade->cur = round($state->last_graded->grade, $cmoptions->decimalpoints); $grade->max = $question->maxgrade; $grade->raw = round($state->last_graded->raw_grade, $cmoptions->decimalpoints); // let student know wether the answer was correct echo '<div class="correctness '; if ($state->last_graded->raw_grade >= $question->maxgrade / 1.01) { // We divide by 1.01 so that rounding errors dont matter. echo ' correct">'; print_string('correct', 'quiz'); } else { if ($state->last_graded->raw_grade > 0) { echo ' partiallycorrect">'; print_string('partiallycorrect', 'quiz'); } else { echo ' incorrect">'; print_string('incorrect', 'quiz'); } } echo '</div>'; echo '<div class="gradingdetails">'; // print grade for this submission print_string('gradingdetails', 'quiz', $grade); if ($cmoptions->penaltyscheme) { // print details of grade adjustment due to penalties if ($state->last_graded->raw_grade > $state->last_graded->grade) { echo ' '; print_string('gradingdetailsadjustment', 'quiz', $grade); } // print info about new penalty // penalty is relevant only if the answer is not correct and further attempts are possible if ($state->last_graded->raw_grade < $question->maxgrade / 1.01 and QUESTION_EVENTCLOSEANDGRADE != $state->event) { if ('' !== $state->last_graded->penalty && (double) $state->last_graded->penalty > 0.0) { // A penalty was applied so display it echo ' '; print_string('gradingdetailspenalty', 'quiz', $state->last_graded->penalty); } else { /* No penalty was applied even though the answer was not correct (eg. a syntax error) so tell the student that they were not penalised for the attempt */ echo ' '; print_string('gradingdetailszeropenalty', 'quiz'); } } } echo '</div>'; } } }
/** * Prints questions with comment and grade form underneath each question * * @return void * @todo Finish documenting this function **/ function print_questions_and_form($quiz, $question, $userid, $attemptid, $gradeungraded, $gradenextungraded, $ungraded) { global $CFG, $DB, $OUTPUT; $context = get_context_instance(CONTEXT_MODULE, $this->cm->id); $questions[$question->id] =& $question; $usehtmleditor = can_use_html_editor(); list($select, $from, $where, $params) = $this->attempts_sql($quiz->id, false, $question->id, $userid, $attemptid, $gradeungraded, $gradenextungraded); $sort = 'ORDER BY u.firstname, u.lastname, qa.attempt ASC'; if ($gradenextungraded) { $attempts = $DB->get_records_sql($select . $from . $where . $sort, $params, 0, QUIZ_REPORT_DEFAULT_GRADING_PAGE_SIZE); } else { $attempts = $DB->get_records_sql($select . $from . $where . $sort, $params); } if ($attempts) { $firstattempt = current($attempts); $fullname = fullname($firstattempt); if ($gradeungraded) { // getting all ungraded attempts echo $OUTPUT->heading(get_string('gradingungraded', 'quiz_grading', $ungraded), 3); } else { if ($gradenextungraded) { // getting next ungraded attempts echo $OUTPUT->heading(get_string('gradingnextungraded', 'quiz_grading', QUIZ_REPORT_DEFAULT_GRADING_PAGE_SIZE), 3); } else { if ($userid) { echo $OUTPUT->heading(get_string('gradinguser', 'quiz_grading', $fullname), 3); } else { if ($attemptid) { $a = new object(); $a->fullname = $fullname; $a->attempt = $firstattempt->attempt; echo $OUTPUT->heading(get_string('gradingattempt', 'quiz_grading', $a), 3); } else { echo $OUTPUT->heading(get_string('gradingall', 'quiz_grading', count($attempts)), 3); } } } } // Display the form with one part for each selected attempt echo '<form method="post" action="report.php" class="mform" id="manualgradingform">' . '<input type="hidden" name="mode" value="grading" />' . '<input type="hidden" name="q" value="' . $quiz->id . '" />' . '<input type="hidden" name="sesskey" value="' . sesskey() . '" />' . '<input type="hidden" name="questionid" value="' . $question->id . '" />'; foreach ($attempts as $attempt) { // Load the state for this attempt (The questions array was created earlier) $states = get_question_states($questions, $quiz, $attempt); // The $states array is indexed by question id but because we are dealing // with only one question there is only one entry in this array $state =& $states[$question->id]; $options = quiz_get_reviewoptions($quiz, $attempt, $context); unset($options->questioncommentlink); $options->readonly = 1; if (question_state_is_graded($state)) { $gradedclass = 'main highlightgraded'; $gradedstring = ' ' . get_string('graded', 'quiz_grading'); } else { $gradedclass = 'main'; $gradedstring = ''; } $a = new object(); $a->fullname = fullname($attempt, true); $a->attempt = $attempt->attempt; // print the user name, attempt count, the question, and some more hidden fields echo '<div class="boxaligncenter" width="80%" style="clear:left;padding:15px;">'; echo $OUTPUT->heading(get_string('gradingattempt', 'quiz_grading', $a) . $gradedstring, 3, $gradedclass); // Print the question, without showing any previous comment. $copy = $state->manualcomment; $state->manualcomment = ''; $options->noeditlink = true; print_question($question, $state, '', $quiz, $options); // The print the comment and grade fields, putting back the previous comment. $state->manualcomment = $copy; question_print_comment_fields($question, $state, 'manualgrades[' . $attempt->uniqueid . ']', $quiz, get_string('manualgrading', 'quiz')); echo '</div>'; } echo '<div class="boxaligncenter"><input type="submit" value="' . get_string('savechanges') . '" /></div>' . '</form>'; } else { echo $OUTPUT->notification(get_string('noattemptstoshow', 'quiz')); } }
/** * Determine render options * * @param int $reviewoptions * @param object $state */ function quiz_get_renderoptions($reviewoptions, $state) { $options = new stdClass(); // Show the question in readonly (review) mode if the question is in // the closed state $options->readonly = question_state_is_closed($state); // Show feedback once the question has been graded (if allowed by the quiz) $options->feedback = question_state_is_graded($state) && $reviewoptions & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY; // Show validation only after a validation event $options->validation = QUESTION_EVENTVALIDATE === $state->event; // Show correct responses in readonly mode if the quiz allows it $options->correct_responses = $options->readonly && $reviewoptions & QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY; // Show general feedback if the question has been graded and the quiz allows it. $options->generalfeedback = question_state_is_graded($state) && $reviewoptions & QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_IMMEDIATELY; // Show overallfeedback once the attempt is over. $options->overallfeedback = false; // Always show responses and scores $options->responses = true; $options->scores = true; $options->quizstate = QUIZ_STATE_DURING; return $options; }
function reader_get_renderoptions($reviewoptions, $state) { $options = new stdClass(); $options->readonly = question_state_is_closed($state); $options->feedback = question_state_is_graded($state) && $reviewoptions & READER_REVIEW_FEEDBACK & READER_REVIEW_IMMEDIATELY; $options->validation = QUESTION_EVENTVALIDATE === $state->event; $options->correct_responses = $options->readonly && $reviewoptions & READER_REVIEW_ANSWERS & READER_REVIEW_IMMEDIATELY; $options->generalfeedback = question_state_is_graded($state) && $reviewoptions & READER_REVIEW_GENERALFEEDBACK & READER_REVIEW_IMMEDIATELY; $options->overallfeedback = false; $options->responses = true; $options->scores = true; $options->readerstate = READER_STATE_DURING; return $options; }