Source for file locallib.1.9.php

Documentation is available at locallib.1.9.php

  1. <?php  // $Id: locallib.php,v 1.127.2.10 2008/02/29 16:22:13 tjhunt Exp $
  2. /**
  3.  * Library of functions used by the quiz module.
  4.  *
  5.  * This contains functions that are called from within the quiz module only
  6.  * Functions that are also called by core Moodle are in {@link lib.php}
  7.  * This script also loads the code in {@link questionlib.php} which holds
  8.  * the module-indpendent code for handling questions and which in turn
  9.  * initialises all the questiontype classes.
  10.  *
  11.  * @author Martin Dougiamas and many others. This has recently been completely
  12.  *          rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
  13.  *          the Serving Mathematics project
  14.  *          {@link http://maths.york.ac.uk/serving_maths}
  15.  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  16.  * @package quiz
  17.  */
  18.  
  19. /**
  20.  * Include those library functions that are also used by core Moodle or other modules
  21.  */
  22. require_once($CFG->dirroot '/mod/quiz/lib.php');
  23. require_once($CFG->dirroot '/question/editlib.php');
  24.  
  25. /// Constants ///////////////////////////////////////////////////////////////////
  26.  
  27. /**#@+
  28.  * Options determining how the grades from individual attempts are combined to give
  29.  * the overall grade for a user
  30.  */
  31. define("QUIZ_GRADEHIGHEST""1");
  32. define("QUIZ_GRADEAVERAGE""2");
  33. define("QUIZ_ATTEMPTFIRST""3");
  34. define("QUIZ_ATTEMPTLAST",  "4");
  35. /**#@-*/
  36.  
  37. /**#@+
  38.  * Constants to describe the various states a quiz attempt can be in.
  39.  */
  40. define('QUIZ_STATE_DURING''during')
  41. define('QUIZ_STATE_IMMEDIATELY''immedately')
  42. define('QUIZ_STATE_OPEN''open')
  43. define('QUIZ_STATE_CLOSED''closed')
  44. define('QUIZ_STATE_TEACHERACCESS''teacheraccess')// State only relevant if you are in a studenty role.
  45. /**#@-*/
  46.  
  47. /// Functions related to attempts /////////////////////////////////////////
  48.  
  49. /**
  50.  * Creates an object to represent a new attempt at a quiz
  51.  *
  52.  * Creates an attempt object to represent an attempt at the quiz by the current
  53.  * user starting at the current time. The ->id field is not set. The object is
  54.  * NOT written to the database.
  55.  * @return object                The newly created attempt object.
  56.  * @param object $quiz           The quiz to create an attempt for.
  57.  * @param integer $attemptnumber The sequence number for the attempt.
  58.  */
  59. function quiz_create_attempt($quiz$attemptnumber{
  60.     global $USER$CFG;
  61.  
  62.     if (!$attemptnumber or !$quiz->attemptonlast or !$attempt get_record('quiz_attempts''quiz'$quiz->id'userid'$USER->id'attempt'$attemptnumber-1)) {
  63.         // we are not building on last attempt so create a new attempt
  64.         $attempt->quiz $quiz->id;
  65.         $attempt->userid $USER->id;
  66.         $attempt->preview 0;
  67.         if ($quiz->shufflequestions{
  68.             $attempt->layout quiz_repaginate($quiz->questions$quiz->questionsperpagetrue);
  69.         else {
  70.             $attempt->layout $quiz->questions;
  71.         }
  72.     }
  73.  
  74.     $timenow time();
  75.     $attempt->attempt $attemptnumber;
  76.     $attempt->sumgrades 0.0;
  77.     $attempt->timestart $timenow;
  78.     $attempt->timefinish 0;
  79.     $attempt->timemodified $timenow;
  80.     $attempt->uniqueid question_new_attempt_uniqueid();
  81.  
  82.     return $attempt;
  83. }
  84.  
  85. /**
  86.  * Returns an unfinished attempt (if there is one) for the given
  87.  * user on the given quiz. This function does not return preview attempts.
  88.  *
  89.  * @param integer $quizid the id of the quiz.
  90.  * @param integer $userid the id of the user.
  91.  *
  92.  * @return mixed the unfinished attempt if there is one, false if not.
  93.  */
  94. function quiz_get_user_attempt_unfinished($quizid$userid{
  95.     $attempts quiz_get_user_attempts($quizid$userid'unfinished'true);
  96.     if ($attempts{
  97.         return array_shift($attempts);
  98.     else {
  99.         return false;
  100.     }
  101. }
  102.  
  103. /**
  104.  * Delete a quiz attempt.
  105.  */
  106. function quiz_delete_attempt($attempt$quiz{
  107.     if (is_numeric($attempt)) {
  108.         if (!$attempt get_record('quiz_attempts''id'$attempt)) {
  109.             return;
  110.         }
  111.     }
  112.  
  113.     if ($attempt->quiz != $quiz->id{
  114.         debugging("Trying to delete attempt $attempt->id which belongs to quiz $attempt->quiz .
  115.                 "but was passed quiz $quiz->id.");
  116.         return;
  117.     }
  118.  
  119.     delete_records('quiz_attempts''id'$attempt->id);
  120.     delete_attempt($attempt->uniqueid);
  121.  
  122.     // Search quiz_attempts for other instances by this user.
  123.     // If none, then delete record for this quiz, this user from quiz_grades
  124.     // else recalculate best grade
  125.  
  126.     $userid $attempt->userid;
  127.     if (!record_exists('quiz_attempts''userid'$userid'quiz'$quiz->id)) {
  128.         delete_records('quiz_grades''userid'$userid,'quiz'$quiz->id);
  129.     else {
  130.         quiz_save_best_grade($quiz$userid);
  131.     }
  132.  
  133.     quiz_update_grades($quiz$userid);
  134. }
  135.  
  136. /// Functions to do with quiz layout and pages ////////////////////////////////
  137.  
  138. /**
  139.  * Returns a comma separated list of question ids for the current page
  140.  *
  141.  * @return string         Comma separated list of question ids
  142.  * @param string $layout  The string representing the quiz layout. Each page is represented as a
  143.  *                         comma separated list of question ids and 0 indicating page breaks.
  144.  *                         So 5,2,0,3,0 means questions 5 and 2 on page 1 and question 3 on page 2
  145.  * @param integer $page   The number of the current page.
  146.  */
  147. function quiz_questions_on_page($layout$page{
  148.     $pages explode(',0'$layout);
  149.     return trim($pages[$page]',');
  150. }
  151.  
  152. /**
  153.  * Returns a comma separated list of question ids for the quiz
  154.  *
  155.  * @return string         Comma separated list of question ids
  156.  * @param string $layout  The string representing the quiz layout. Each page is represented as a
  157.  *                         comma separated list of question ids and 0 indicating page breaks.
  158.  *                         So 5,2,0,3,0 means questions 5 and 2 on page 1 and question 3 on page 2
  159.  */
  160. function quiz_questions_in_quiz($layout{
  161.     return str_replace(',0'''$layout);
  162. }
  163.  
  164. /**
  165.  * Returns the number of pages in the quiz layout
  166.  *
  167.  * @return integer         Comma separated list of question ids
  168.  * @param string $layout  The string representing the quiz layout.
  169.  */
  170. function quiz_number_of_pages($layout{
  171.     return substr_count($layout',0');
  172. }
  173.  
  174. /**
  175.  * Returns the first question number for the current quiz page
  176.  *
  177.  * @return integer  The number of the first question
  178.  * @param string $quizlayout The string representing the layout for the whole quiz
  179.  * @param string $pagelayout The string representing the layout for the current page
  180.  */
  181. function quiz_first_questionnumber($quizlayout$pagelayout{
  182.     // this works by finding all the questions from the quizlayout that
  183.     // come before the current page and then adding up their lengths.
  184.     global $CFG;
  185.     $start strpos($quizlayout','.$pagelayout.',')-2;
  186.     if ($start 0{
  187.         $prevlist substr($quizlayout0$start);
  188.         return get_field_sql("SELECT sum(length)+1 FROM {$CFG->prefix}question
  189.          WHERE id IN ($prevlist)");
  190.     else {
  191.         return 1;
  192.     }
  193. }
  194.  
  195. /**
  196.  * Re-paginates the quiz layout
  197.  *
  198.  * @return string         The new layout string
  199.  * @param string $layout  The string representing the quiz layout.
  200.  * @param integer $perpage The number of questions per page
  201.  * @param boolean $shuffle Should the questions be reordered randomly?
  202.  */
  203. function quiz_repaginate($layout$perpage$shuffle=false{
  204.     $layout str_replace(',0'''$layout)// remove existing page breaks
  205.     $questions explode(','$layout);
  206.     if ($shuffle{
  207.         srand((float)microtime(1000000)// for php < 4.2
  208.         shuffle($questions);
  209.     }
  210.     $i 1;
  211.     $layout '';
  212.     foreach ($questions as $question{
  213.         if ($perpage and $i $perpage{
  214.             $layout .= '0,';
  215.             $i 1;
  216.         }
  217.         $layout .= $question.',';
  218.         $i++;
  219.     }
  220.     return $layout.'0';
  221. }
  222.  
  223. /**
  224.  * Print navigation panel for quiz attempt and review pages
  225.  *
  226.  * @param integer $page     The number of the current page (counting from 0).
  227.  * @param integer $pages    The total number of pages.
  228.  */
  229. function quiz_print_navigation_panel($page$pages{
  230.     //$page++;
  231.     echo '<div class="paging pagingbar">';
  232.     echo '<span class="title">' get_string('page'':</span>';
  233.     if ($page 0{
  234.         // Print previous link
  235.         $strprev get_string('previous');
  236.         echo '<a href="javascript:navigate(' ($page 1');" title="'
  237.          . $strprev '">(' $strprev ')</a>';
  238.     }
  239.     for ($i 0$i $pages$i++{
  240.         if ($i == $page{
  241.             echo '<span class="thispage">'.($i+1).'</span>';
  242.         else {
  243.             echo '<a href="javascript:navigate(' ($i');">'.($i+1).'</a>';
  244.         }
  245.     }
  246.  
  247.     if ($page $pages 1{
  248.         // Print next link
  249.         $strnext get_string('next');
  250.         echo '<a href="javascript:navigate(' ($page 1');" title="'
  251.          . $strnext '">(' $strnext ')</a>';
  252.     }
  253.     echo '</div>';
  254. }
  255.  
  256. /// Functions to do with quiz grades //////////////////////////////////////////
  257.  
  258. /**
  259.  * Creates an array of maximum grades for a quiz
  260.  *
  261.  * The grades are extracted from the quiz_question_instances table.
  262.  * @return array        Array of grades indexed by question id
  263.  *                       These are the maximum possible grades that
  264.  *                       students can achieve for each of the questions
  265.  * @param integer $quiz The quiz object
  266.  */
  267. function quiz_get_all_question_grades($quiz{
  268.     global $CFG;
  269.  
  270.     $questionlist quiz_questions_in_quiz($quiz->questions);
  271.     if (empty($questionlist)) {
  272.         return array();
  273.     }
  274.  
  275.     $instances get_records_sql("SELECT question,grade,id
  276.                             FROM {$CFG->prefix}quiz_question_instances
  277.                             WHERE quiz = '$quiz->id'.
  278.                             (is_null($questionlist'' :
  279.                             "AND question IN ($questionlist)"));
  280.  
  281.     $list explode(","$questionlist);
  282.     $grades array();
  283.  
  284.     foreach ($list as $qid{
  285.         if (isset($instances[$qid])) {
  286.             $grades[$qid$instances[$qid]->grade;
  287.         else {
  288.             $grades[$qid1;
  289.         }
  290.     }
  291.     return $grades;
  292. }
  293.  
  294. /**
  295.  * Get the best current grade for a particular user in a quiz.
  296.  *
  297.  * @param object $quiz the quiz object.
  298.  * @param integer $userid the id of the user.
  299.  * @return float the user's current grade for this quiz.
  300.  */
  301. function quiz_get_best_grade($quiz$userid{
  302.     $grade get_field('quiz_grades''grade''quiz'$quiz->id'userid'$userid);
  303.  
  304.     // Need to detect errors/no result, without catching 0 scores.
  305.     if (is_numeric($grade)) {
  306.         return round($grade$quiz->decimalpoints);
  307.     else {
  308.         return NULL;
  309.     }
  310. }
  311.  
  312. /**
  313.  * Convert the raw grade stored in $attempt into a grade out of the maximum
  314.  * grade for this quiz.
  315.  *
  316.  * @param float $rawgrade the unadjusted grade, fof example $attempt->sumgrades
  317.  * @param object $quiz the quiz object. Only the fields grade, sumgrades and decimalpoints are used.
  318.  * @return float the rescaled grade.
  319.  */
  320. function quiz_rescale_grade($rawgrade$quiz$round true{
  321.     if ($quiz->sumgrades{
  322.         $grade $rawgrade $quiz->grade $quiz->sumgrades;
  323.         if ($round{
  324.             $grade round($grade$quiz->decimalpoints);
  325.         }
  326.     else {
  327.         $grade 0;
  328.     }
  329.     return $grade;
  330. }
  331.  
  332. /**
  333.  * Get the feedback text that should be show to a student who
  334.  * got this grade on this quiz. The feedback is processed ready for diplay.
  335.  *
  336.  * @param float $grade a grade on this quiz.
  337.  * @param integer $quizid the id of the quiz object.
  338.  * @return string the comment that corresponds to this grade (empty string if there is not one.
  339.  */
  340. function quiz_feedback_for_grade($grade$quizid{
  341.     $feedback get_field_select('quiz_feedback''feedbacktext',
  342.             "quizid = $quizid AND mingrade <= $grade AND $grade < maxgrade");
  343.  
  344.     if (empty($feedback)) {
  345.         $feedback '';
  346.     }
  347.  
  348.     // Clean the text, ready for display.
  349.     $formatoptions new stdClass;
  350.     $formatoptions->noclean true;
  351.     $feedback format_text($feedbackFORMAT_MOODLE$formatoptions);
  352.  
  353.     return $feedback;
  354. }
  355.  
  356. /**
  357.  * @param integer $quizid the id of the quiz object.
  358.  * @return boolean Whether this quiz has any non-blank feedback text.
  359.  */
  360. function quiz_has_feedback($quizid{
  361.     static $cache array();
  362.     if (!array_key_exists($quizid$cache)) {
  363.         $cache[$quizidrecord_exists_select('quiz_feedback',
  364.                 "quizid = $quizid AND sql_isnotempty('quiz_feedback''feedbacktext'falsetrue));
  365.     }
  366.     return $cache[$quizid];
  367. }
  368.  
  369. /**
  370.  * The quiz grade is the score that student's results are marked out of. When it
  371.  * changes, the corresponding data in quiz_grades and quiz_feedback needs to be
  372.  * rescaled.
  373.  *
  374.  * @param float $newgrade the new maximum grade for the quiz.
  375.  * @param object $quiz the quiz we are updating. Passed by reference so its grade field can be updated too.
  376.  * @return boolean indicating success or failure.
  377.  */
  378. function quiz_set_grade($newgrade&$quiz{
  379.     // This is potentially expensive, so only do it if necessary.
  380.     if (abs($quiz->grade $newgrade1e-7{
  381.         // Nothing to do.
  382.         return true;
  383.     }
  384.  
  385.     // Use a transaction, so that on those databases that support it, this is safer.
  386.     begin_sql();
  387.  
  388.     // Update the quiz table.
  389.     $success set_field('quiz''grade'$newgrade'id'$quiz->instance);
  390.  
  391.     // Rescaling the other data is only possible if the old grade was non-zero.
  392.     if ($quiz->grade 1e-7{
  393.         global $CFG;
  394.  
  395.         $factor $newgrade/$quiz->grade;
  396.         $quiz->grade $newgrade;
  397.  
  398.         // Update the quiz_grades table.
  399.         $timemodified time();
  400.         $success $success && execute_sql("
  401.                 UPDATE {$CFG->prefix}quiz_grades
  402.                 SET grade = $factor * grade, timemodified = $timemodified
  403.                 WHERE quiz = $quiz->id
  404.         "false);
  405.  
  406.         // Update the quiz_grades table.
  407.         $success $success && execute_sql("
  408.                 UPDATE {$CFG->prefix}quiz_feedback
  409.                 SET mingrade = $factor * mingrade, maxgrade = $factor * maxgrade
  410.                 WHERE quizid = $quiz->id
  411.         "false);
  412.     }
  413.  
  414.     // update grade item and send all grades to gradebook
  415.     quiz_grade_item_update($quiz);
  416.     quiz_update_grades($quiz);
  417.  
  418.     if ($success{
  419.         return commit_sql();
  420.     else {
  421.         rollback_sql();
  422.         return false;
  423.     }
  424. }
  425.  
  426. /**
  427.  * Save the overall grade for a user at a quiz in the quiz_grades table
  428.  *
  429.  * @param object $quiz The quiz for which the best grade is to be calculated and then saved.
  430.  * @param integer $userid The userid to calculate the grade for. Defaults to the current user.
  431.  * @return boolean Indicates success or failure.
  432.  */
  433. function quiz_save_best_grade($quiz$userid null{
  434.     global $USER;
  435.  
  436.     if (empty($userid)) {
  437.         $userid $USER->id;
  438.     }
  439.  
  440.     // Get all the attempts made by the user
  441.     if (!$attempts quiz_get_user_attempts($quiz->id$userid)) {
  442.         notify('Could not find any user attempts');
  443.         return false;
  444.     }
  445.  
  446.     // Calculate the best grade
  447.     $bestgrade quiz_calculate_best_grade($quiz$attempts);
  448.     $bestgrade quiz_rescale_grade($bestgrade$quiz);
  449.  
  450.     // Save the best grade in the database
  451.     if ($grade get_record('quiz_grades''quiz'$quiz->id'userid'$userid)) {
  452.         $grade->grade $bestgrade;
  453.         $grade->timemodified time();
  454.         if (!update_record('quiz_grades'$grade)) {
  455.             notify('Could not update best grade');
  456.             return false;
  457.         }
  458.     else {
  459.         $grade->quiz $quiz->id;
  460.         $grade->userid $userid;
  461.         $grade->grade $bestgrade;
  462.         $grade->timemodified time();
  463.         if (!insert_record('quiz_grades'$grade)) {
  464.             notify('Could not insert new best grade');
  465.             return false;
  466.         }
  467.     }
  468.  
  469.     quiz_update_grades($quiz$userid);
  470.     return true;
  471. }
  472.  
  473. /**
  474.  * Calculate the overall grade for a quiz given a number of attempts by a particular user.
  475.  *
  476.  * @return float          The overall grade
  477.  * @param object $quiz    The quiz for which the best grade is to be calculated
  478.  * @param array $attempts An array of all the attempts of the user at the quiz
  479.  */
  480. function quiz_calculate_best_grade($quiz$attempts{
  481.  
  482.     switch ($quiz->grademethod{
  483.  
  484.         case QUIZ_ATTEMPTFIRST:
  485.             foreach ($attempts as $attempt{
  486.                 return $attempt->sumgrades;
  487.             }
  488.             break;
  489.  
  490.         case QUIZ_ATTEMPTLAST:
  491.             foreach ($attempts as $attempt{
  492.                 $final $attempt->sumgrades;
  493.             }
  494.             return $final;
  495.  
  496.         case QUIZ_GRADEAVERAGE:
  497.             $sum 0;
  498.             $count 0;
  499.             foreach ($attempts as $attempt{
  500.                 $sum += $attempt->sumgrades;
  501.                 $count++;
  502.             }
  503.             return (float)$sum/$count;
  504.  
  505.         default:
  506.         case QUIZ_GRADEHIGHEST:
  507.             $max 0;
  508.             foreach ($attempts as $attempt{
  509.                 if ($attempt->sumgrades $max{
  510.                     $max $attempt->sumgrades;
  511.                 }
  512.             }
  513.             return $max;
  514.     }
  515. }
  516.  
  517. /**
  518.  * Return the attempt with the best grade for a quiz
  519.  *
  520.  * Which attempt is the best depends on $quiz->grademethod. If the grade
  521.  * method is GRADEAVERAGE then this function simply returns the last attempt.
  522.  * @return object         The attempt with the best grade
  523.  * @param object $quiz    The quiz for which the best grade is to be calculated
  524.  * @param array $attempts An array of all the attempts of the user at the quiz
  525.  */
  526. function quiz_calculate_best_attempt($quiz$attempts{
  527.  
  528.     switch ($quiz->grademethod{
  529.  
  530.         case QUIZ_ATTEMPTFIRST:
  531.             foreach ($attempts as $attempt{
  532.                 return $attempt;
  533.             }
  534.             break;
  535.  
  536.         case QUIZ_GRADEAVERAGE// need to do something with it :-)
  537.         case QUIZ_ATTEMPTLAST:
  538.             foreach ($attempts as $attempt{
  539.                 $final $attempt;
  540.             }
  541.             return $final;
  542.  
  543.         default:
  544.         case QUIZ_GRADEHIGHEST:
  545.             $max = -1;
  546.             foreach ($attempts as $attempt{
  547.                 if ($attempt->sumgrades $max{
  548.                     $max $attempt->sumgrades;
  549.                     $maxattempt $attempt;
  550.                 }
  551.             }
  552.             return $maxattempt;
  553.     }
  554. }
  555.  
  556. /**
  557.  * @return the options for calculating the quiz grade from the individual attempt grades.
  558.  */
  559.     return array (
  560.             QUIZ_GRADEHIGHEST => get_string('gradehighest''quiz'),
  561.             QUIZ_GRADEAVERAGE => get_string('gradeaverage''quiz'),
  562.             QUIZ_ATTEMPTFIRST => get_string('attemptfirst''quiz'),
  563.             QUIZ_ATTEMPTLAST  => get_string('attemptlast''quiz'));
  564. }
  565.  
  566. /**
  567.  * @param int $option one of the values QUIZ_GRADEHIGHEST, QUIZ_GRADEAVERAGE, QUIZ_ATTEMPTFIRST or QUIZ_ATTEMPTLAST.
  568.  * @return the lang string for that option.
  569.  */
  570. function quiz_get_grading_option_name($option{
  571.     $strings quiz_get_grading_options();
  572.     return $strings[$option];
  573. }
  574.  
  575. /// Other quiz functions ////////////////////////////////////////////////////
  576.  
  577. /**
  578.  * Parse field names used for the replace options on question edit forms
  579.  */
  580. function quiz_parse_fieldname($name$nameprefix='question'{
  581.     $reg array();
  582.     if (preg_match("/$nameprefix(\\d+)(\w+)/"$name$reg)) {
  583.         return array('mode' => $reg[2]'id' => (int)$reg[1]);
  584.     else {
  585.         return false;
  586.     }
  587. }
  588.  
  589. /**
  590.  * Upgrade states for an attempt to Moodle 1.5 model
  591.  *
  592.  * Any state that does not yet have its timestamp set to nonzero has not yet been upgraded from Moodle 1.4
  593.  * The reason these are still around is that for large sites it would have taken too long to
  594.  * upgrade all states at once. This function sets the timestamp field and creates an entry in the
  595.  * question_sessions table.
  596.  * @param object $attempt  The attempt whose states need upgrading
  597.  */
  598. function quiz_upgrade_states($attempt{
  599.     global $CFG;
  600.     // The old quiz model only allowed a single response per quiz attempt so that there will be
  601.     // only one state record per question for this attempt.
  602.  
  603.     // We set the timestamp of all states to the timemodified field of the attempt.
  604.     execute_sql("UPDATE {$CFG->prefix}question_states SET timestamp = '$attempt->timemodified' WHERE attempt = '$attempt->uniqueid'"false);
  605.  
  606.     // For each state we create an entry in the question_sessions table, with both newest and
  607.     // newgraded pointing to this state.
  608.     // Actually we only do this for states whose question is actually listed in $attempt->layout.
  609.     // We do not do it for states associated to wrapped questions like for example the questions
  610.     // used by a RANDOM question
  611.     $session new stdClass;
  612.     $session->attemptid $attempt->uniqueid;
  613.     $questionlist quiz_questions_in_quiz($attempt->layout);
  614.     if ($questionlist and $states get_records_select('question_states'"attempt = '$attempt->uniqueid' AND question IN ($questionlist)")) {
  615.         foreach ($states as $state{
  616.             $session->newgraded $state->id;
  617.             $session->newest $state->id;
  618.             $session->questionid $state->question;
  619.             insert_record('question_sessions'$sessionfalse);
  620.         }
  621.     }
  622. }
  623.  
  624. /**
  625.  * @param object $quiz the quiz
  626.  * @param object $question the question
  627.  * @return the HTML for a preview question icon.
  628.  */
  629. function quiz_question_preview_button($quiz$question{
  630.     global $CFG$COURSE;
  631.     if (!question_has_capability_on($question'use'$question->category)){
  632.         return '';
  633.     }
  634.     $strpreview get_string('previewquestion''quiz');
  635.     $quizorcourseid $quiz->id?('&amp;quizid=' $quiz->id):('&amp;courseid=' .$COURSE->id);
  636.     return link_to_popup_window('/question/preview.php?id=' $question->id $quizorcourseid'questionpreview',
  637.             "<img src=\"$CFG->pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" />",
  638.             00$strpreviewQUESTION_PREVIEW_POPUP_OPTIONStrue);
  639. }
  640.  
  641. /**
  642.  * Determine render options
  643.  *
  644.  * @param int $reviewoptions 
  645.  * @param object $state 
  646.  */
  647. function quiz_get_renderoptions($reviewoptions$state{
  648.     $options new stdClass;
  649.  
  650.     // Show the question in readonly (review) mode if the question is in
  651.     // the closed state
  652.     $options->readonly question_state_is_closed($state);
  653.  
  654.     // Show feedback once the question has been graded (if allowed by the quiz)
  655.     $options->feedback question_state_is_graded($state&& ($reviewoptions QUIZ_REVIEW_FEEDBACK QUIZ_REVIEW_IMMEDIATELY);
  656.  
  657.     // Show validation only after a validation event
  658.     $options->validation QUESTION_EVENTVALIDATE === $state->event;
  659.  
  660.     // Show correct responses in readonly mode if the quiz allows it
  661.     $options->correct_responses $options->readonly && ($reviewoptions QUIZ_REVIEW_ANSWERS QUIZ_REVIEW_IMMEDIATELY);
  662.  
  663.     // Show general feedback if the question has been graded and the quiz allows it.
  664.     $options->generalfeedback question_state_is_graded($state&& ($reviewoptions QUIZ_REVIEW_GENERALFEEDBACK QUIZ_REVIEW_IMMEDIATELY);
  665.  
  666.     // Show overallfeedback once the attempt is over.
  667.     $options->overallfeedback false;
  668.  
  669.     // Always show responses and scores
  670.     $options->responses true;
  671.     $options->scores true;
  672.     $options->quizstate QUIZ_STATE_DURING;
  673.  
  674.     return $options;
  675. }
  676.  
  677. /**
  678.  * Determine review options
  679.  *
  680.  * @param object $quiz the quiz instance.
  681.  * @param object $attempt the attempt in question.
  682.  * @param $context the roles and permissions context,
  683.  *           normally the context for the quiz module instance.
  684.  *
  685.  * @return object an object with boolean fields responses, scores, feedback,
  686.  *           correct_responses, solutions and general feedback
  687.  */
  688. function quiz_get_reviewoptions($quiz$attempt$context=null{
  689.     $options new stdClass;
  690.     $options->readonly true;
  691.  
  692.     // Provide the links to the question review and comment script
  693.     $options->questionreviewlink '/mod/quiz/reviewquestion.php';
  694.  
  695.     // Show a link to the comment box only for closed attempts
  696.     if ($attempt->timefinish && !is_null($context&& has_capability('mod/quiz:grade'$context)) {
  697.         $options->questioncommentlink '/mod/quiz/comment.php';
  698.     }
  699.  
  700.     if (!is_null($context&& has_capability('mod/quiz:viewreports'$context&& 
  701.             has_capability('moodle/grade:viewhidden'$context&& !$attempt->preview{
  702.         // People who can see reports and hidden grades should be shown everything,
  703.         // except during preview when teachers want to see what students see.
  704.         $options->responses true;
  705.         $options->scores true
  706.         $options->feedback true;
  707.         $options->correct_responses true;
  708.         $options->solutions false;
  709.         $options->generalfeedback true;
  710.         $options->overallfeedback true;
  711.         $options->quizstate QUIZ_STATE_TEACHERACCESS;
  712.     else {
  713.         // Work out the state of the attempt ...
  714.         if (((time($attempt->timefinish120|| $attempt->timefinish==0{
  715.             $quiz_state_mask QUIZ_REVIEW_IMMEDIATELY;
  716.             $options->quizstate QUIZ_STATE_IMMEDIATELY;
  717.         else if (!$quiz->timeclose or time($quiz->timeclose{
  718.             $quiz_state_mask QUIZ_REVIEW_OPEN;
  719.             $options->quizstate QUIZ_STATE_OPEN;
  720.         else {
  721.             $quiz_state_mask QUIZ_REVIEW_CLOSED;
  722.             $options->quizstate QUIZ_STATE_CLOSED;
  723.         }
  724.  
  725.         // ... and hence extract the appropriate review options. 
  726.         $options->responses ($quiz->review $quiz_state_mask QUIZ_REVIEW_RESPONSES0;
  727.         $options->scores ($quiz->review $quiz_state_mask QUIZ_REVIEW_SCORES0;
  728.         $options->feedback ($quiz->review $quiz_state_mask QUIZ_REVIEW_FEEDBACK0;
  729.         $options->correct_responses ($quiz->review $quiz_state_mask QUIZ_REVIEW_ANSWERS0;
  730.         $options->solutions ($quiz->review $quiz_state_mask QUIZ_REVIEW_SOLUTIONS0;
  731.         $options->generalfeedback ($quiz->review $quiz_state_mask QUIZ_REVIEW_GENERALFEEDBACK0;
  732.         $options->overallfeedback $attempt->timefinish && ($quiz->review $quiz_state_mask QUIZ_REVIEW_OVERALLFEEDBACK);
  733.     }
  734.  
  735.     return $options;
  736. }
  737.  
  738. /**
  739.  * Combines the review options from a number of different quiz attempts.
  740.  * Returns an array of two ojects, so he suggested way of calling this
  741.  * funciton is:
  742.  * list($someoptions, $alloptions) = quiz_get_combined_reviewoptions(...)
  743.  *
  744.  * @param object $quiz the quiz instance.
  745.  * @param array $attempts an array of attempt objects.
  746.  * @param $context the roles and permissions context,
  747.  *           normally the context for the quiz module instance.
  748.  *
  749.  * @return array of two options objects, one showing which options are true for
  750.  *           at least one of the attempts, the other showing which options are true
  751.  *           for all attempts.
  752.  */
  753. function quiz_get_combined_reviewoptions($quiz$attempts$context=null{
  754.     $fields array('readonly''scores''feedback''correct_responses''solutions''generalfeedback''overallfeedback');
  755.     $someoptions new stdClass;
  756.     $alloptions new stdClass;
  757.     foreach ($fields as $field{
  758.         $someoptions->$field false;
  759.         $alloptions->$field true;
  760.     }
  761.     foreach ($attempts as $attempt{
  762.         $attemptoptions quiz_get_reviewoptions($quiz$attempt$context);
  763.         foreach ($fields as $field{
  764.             $someoptions->$field $someoptions->$field || $attemptoptions->$field;
  765.             $alloptions->$field $alloptions->$field && $attemptoptions->$field;
  766.         }
  767.     }
  768.     return array($someoptions$alloptions);
  769. }
  770.  
  771. /// FUNCTIONS FOR SENDING NOTIFICATION EMAILS ///////////////////////////////
  772.  
  773. /**
  774.  * Sends confirmation email to the student taking the course
  775.  *
  776.  * @param stdClass $a associative array of replaceable fields for the templates
  777.  *
  778.  * @return bool|stringresult of email_to_user()
  779.  */
  780. function quiz_send_confirmation($a{
  781.  
  782.     global $USER;
  783.  
  784.     // recipient is self
  785.     $a->useridnumber $USER->idnumber;
  786.     $a->username fullname($USER);
  787.     $a->userusername $USER->username;
  788.  
  789.     // fetch the subject and body from strings
  790.     $subject get_string('emailconfirmsubject''quiz'$a);
  791.     $body get_string('emailconfirmbody''quiz'$a);
  792.  
  793.     // send email and analyse result
  794.     return email_to_user($USERget_admin()$subject$body);
  795. }
  796.  
  797. /**
  798.  * Sends notification email to the interested parties that assign the role capability
  799.  *
  800.  * @param object $recipient user object of the intended recipient
  801.  * @param stdClass $a associative array of replaceable fields for the templates
  802.  *
  803.  * @return bool|stringresult of email_to_user()
  804.  */
  805. function quiz_send_notification($recipient$a{
  806.  
  807.     global $USER;
  808.  
  809.     // recipient info for template
  810.     $a->username fullname($recipient);
  811.     $a->userusername $recipient->username;
  812.     $a->userusername $recipient->username;
  813.  
  814.     // fetch the subject and body from strings
  815.     $subject get_string('emailnotifysubject''quiz'$a);
  816.     $body get_string('emailnotifybody''quiz'$a);
  817.  
  818.     // send email and analyse result
  819.     return email_to_user($recipient$USER$subject$body);
  820. }
  821.  
  822. /**
  823.  * Takes a bunch of information to format into an email and send
  824.  * to the specified recipient.
  825.  *
  826.  * @param object $course the course
  827.  * @param object $quiz the quiz
  828.  * @param object $attempt this attempt just finished
  829.  * @param object $context the quiz context
  830.  * @param object $cm the coursemodule for this quiz
  831.  *
  832.  * @return int number of emails sent
  833.  */
  834. function quiz_send_notification_emails($course$quiz$attempt$context$cm{
  835.     global $CFG$USER;
  836.     // we will count goods and bads for error logging
  837.     $emailresult array('good' => 0'block' => 0'fail' => 0);
  838.  
  839.     // do nothing if required objects not present
  840.     if (empty($courseor empty($quizor empty($attemptor empty($context)) {
  841.         debugging('quiz_send_notification_emails: Email(s) not sent due to program error.',
  842.                 DEBUG_DEVELOPER);
  843.         return $emailresult['fail'];
  844.     }
  845.  
  846.     // check for confirmation required
  847.     $sendconfirm false;
  848.     $notifyexcludeusers '';
  849.     if (has_capability('mod/quiz:emailconfirmsubmission'$contextNULLfalse)) {
  850.         // exclude from notify emails later
  851.         $notifyexcludeusers $USER->id;
  852.         // send the email
  853.         $sendconfirm true;
  854.     }
  855.  
  856.     // check for notifications required
  857.     $notifyfields 'u.id, u.username, u.firstname, u.lastname, u.email, u.emailstop, u.lang, u.timezone, u.mailformat, u.maildisplay';
  858.     $groups groups_get_all_groups($course->id$USER->id);
  859.     if (is_array($groups&& count($groups0{
  860.         $groups array_keys($groups);
  861.     else if (groups_get_activity_groupmode($cm$course!= NOGROUPS{
  862.         // If the user is not in a group, and the quiz is set to group mode,
  863.         // then set $gropus to a non-existant id so that only users with
  864.         // 'moodle/site:accessallgroups' get notified.
  865.         $groups = -1;
  866.     else {
  867.         $groups '';
  868.     }
  869.     $userstonotify get_users_by_capability($context'mod/quiz:emailnotifysubmission',
  870.             $notifyfields''''''$groups$notifyexcludeusersfalsefalsetrue);
  871.  
  872.     // if something to send, then build $a
  873.     if (empty($userstonotifyor $sendconfirm{
  874.         $a new stdClass;
  875.         // course info
  876.         $a->coursename $course->fullname;
  877.         $a->courseshortname $course->shortname;
  878.         // quiz info
  879.         $a->quizname $quiz->name;
  880.         $a->quizreporturl $CFG->wwwroot '/mod/quiz/report.php?q=' $quiz->id;
  881.         $a->quizreportlink '<a href="' $a->quizreporturl '">' format_string($quiz->name' report</a>';
  882.         $a->quizreviewurl $CFG->wwwroot '/mod/quiz/review.php?attempt=' $attempt->id;
  883.         $a->quizreviewlink '<a href="' $a->quizreviewurl '">' format_string($quiz->name' review</a>';
  884.         $a->quizurl $CFG->wwwroot '/mod/quiz/view.php?q=' $quiz->id;
  885.         $a->quizlink '<a href="' $a->quizurl '">' format_string($quiz->name'</a>';
  886.         // attempt info
  887.         $a->submissiontime userdate($attempt->timefinish);
  888.         $a->timetaken format_time($attempt->timefinish $attempt->timestart);
  889.         // student who sat the quiz info
  890.         $a->studentidnumber $USER->idnumber;
  891.         $a->studentname fullname($USER);
  892.         $a->studentusername $USER->username;
  893.     }
  894.  
  895.     // send confirmation if required
  896.     if ($sendconfirm{
  897.         // send the email and update stats
  898.         switch (quiz_send_confirmation($a)) {
  899.             case true:
  900.                 $emailresult['good']++;
  901.                 break;
  902.             case false:
  903.                 $emailresult['fail']++;
  904.                 break;
  905.             case 'emailstop':
  906.                 $emailresult['block']++;
  907.                 break;
  908.         }
  909.     }
  910.  
  911.     // send notifications if required
  912.     if (!empty($userstonotify)) {
  913.         // loop through recipients and send an email to each and update stats
  914.         foreach ($userstonotify as $recipient{
  915.             switch (quiz_send_notification($recipient$a)) {
  916.                 case true:
  917.                     $emailresult['good']++;
  918.                     break;
  919.                 case false:
  920.                     $emailresult['fail']++;
  921.                     break;
  922.                 case 'emailstop':
  923.                     $emailresult['block']++;
  924.                     break;
  925.             }
  926.         }
  927.     }
  928.  
  929.     // log errors sending emails if any
  930.     if (empty($emailresult['fail'])) {
  931.         debugging('quiz_send_notification_emails:: '.$emailresult['fail'].' email(s) failed to be sent.'DEBUG_DEVELOPER);
  932.     }
  933.     if (empty($emailresult['block'])) {
  934.         debugging('quiz_send_notification_emails:: '.$emailresult['block'].' email(s) were blocked by the user.'DEBUG_DEVELOPER);
  935.     }
  936.  
  937.     // return the number of successfully sent emails
  938.     return $emailresult['good'];
  939. }
  940. ?>

Documentation generated on Mon, 16 Jun 2008 15:56:38 +0100 by phpDocumentor 1.4.0