Source for file sl_quiz_linker.php

Documentation is available at sl_quiz_linker.php

  1. <?php
  2.     // This file is part of the Sloodle project (www.sloodle.org).
  3.  
  4.     /**
  5.     * Sloodle quiz linker.
  6.     * 
  7.     * Allows in-world objects to interact with Moodle quizzes.
  8.     * <b>Note:</b> this script was copied and changed from the Moodle quiz script.
  9.     * 
  10.     * @package sloodlequiz
  11.     * @copyright Original copyright (c) Moodle (various contributors)
  12.     * @copyright Sloodle changes copyright (c) 2007-8 Sloodle (various contributors)
  13.     * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  14.     *
  15.     * @contributor (various Moodle developers - original quiz functionality)
  16.     * @contributor Edmund Edgar - altered script for use in Sloodle
  17.     * @contributor Peter R. Bloomfield - updated to use new communications format
  18.     */
  19.     
  20.     // This script is expected to be requested by an in-world object.
  21.     // The following parameters are required:
  22.     //
  23.     //   sloodlepwd = prim password to authenticate the request
  24.     //   sloodleuuid = UUID of the avatar making the request (optional if 'sloodleavname' is specified)
  25.     //   sloodleavname = avatar name of the user making the request (optional if 'sloodleuuid' is specified)
  26.     //
  27.     // The following parameters are optional, depending on the mode we are in:
  28.     //
  29.     //   q = ID of the quiz course module instance
  30.     //   id = ID of the course which this request relates to
  31.     //   page = ?
  32.     //   questionids = ?
  33.     //   finishattempt = ?
  34.     //   timeup = true if submission was by timer
  35.     //    forcenew = teacher has requested a new preview
  36.     //    action = ??
  37.  
  38.     
  39.     require_once('../../config.php');
  40.     require_once(SLOODLE_DIRROOT.'/sl_debug.php');
  41.     require_once(SLOODLE_DIRROOT.'/lib/sl_lsllib.php');
  42.     require_once("locallib.php");
  43.  
  44.     
  45.     // Authenticate the request and login the user
  46.     $lsl new SloodleLSLHandler();
  47.     $lsl->request->process_request_data();
  48.     $lsl->request->authenticate_request();
  49.     $lsl->login_by_request();
  50.     
  51.  
  52.     $output array();
  53.  
  54.     $courseid optional_param('courseid'0PARAM_INT);               // Course Module ID
  55.     $id optional_param('id'0PARAM_INT);               // Course Module ID
  56.     $q optional_param('q'0PARAM_INT);                 // or quiz ID
  57.     $page optional_param('page'0PARAM_INT);
  58.     $questionids optional_param('questionids''');
  59.     $finishattempt optional_param('finishattempt'0PARAM_BOOL);
  60.     $timeup optional_param('timeup'0PARAM_BOOL)// True if form was submitted by timer.
  61.     $forcenew optional_param('forcenew'falsePARAM_BOOL)// Teacher has requested new preview
  62.  
  63.     $isnotify optional_param'action'falsePARAM_RAW == 'notify' ;
  64.  
  65.     // remember the current time as the time any responses were submitted
  66.     // (so as to make sure students don't get penalized for slow processing on this page)
  67.     $timestamp time();
  68.  
  69.     // We treat automatically closed attempts just like normally closed attempts
  70.     if ($timeup{
  71.         $finishattempt 1;
  72.     }
  73.  
  74.     if ( ($courseid != 0&& ($id == 0&& ($q == 0) ) {
  75.         // fetch a quiz with the id for the course
  76.          if ($mod get_record("modules""name""quiz") ) {
  77.              $lsl->response->quick_output(-712'MODULE_INSTANCE''Could not find quiz module'FALSE);
  78.              exit;
  79.          }
  80.  
  81.          //if (! $coursemod = get_record("course_modules", "courseid", $courseid, "module", $mod->id)) {
  82.          if ($coursemod get_record("course_modules""course"$courseid"module"$mod->id)) {
  83.              $lsl->response->quick_output(-712'MODULE_INSTANCE''Could not find course module instance'FALSE);
  84.              exit;
  85.          }
  86.  
  87.          $id $coursemod->id;
  88.  
  89.     }
  90.     if ($id{
  91.         if ($cm get_coursemodule_from_id('quiz'$id)) {
  92.             $lsl->response->quick_output(-701'MODULE_INSTANCE''Identified module instance is not a quiz'FALSE);
  93.             exit();
  94.         }
  95.  
  96.         if ($course get_record("course""id"$cm->course)) {
  97.             $lsl->response->quick_output(-701'MODULE_INSTANCE''Quiz module is misconfigured'FALSE);
  98.             exit();
  99.         }
  100.  
  101.         if ($quiz get_record("quiz""id"$cm->instance)) {
  102.             $lsl->response->quick_output(-712'MODULE_INSTANCE''Part of quiz module instance is missing'FALSE);
  103.             exit();
  104.         }
  105.  
  106.     else {
  107.         if ($quiz get_record("quiz""id"$q)) {
  108.             $lsl->response->quick_output(-712'MODULE_INSTANCE''Could not find quiz module'FALSE);
  109.             exit();
  110.         }
  111.         if ($course get_record("course""id"$quiz->course)) {
  112.             $lsl->response->quick_output(-712'MODULE_INSTANCE''Part of quiz module instance is missing'FALSE);
  113.             exit();
  114.         }
  115.         if ($cm get_coursemodule_from_instance("quiz"$quiz->id$course->id)) {
  116.             $lsl->response->quick_output(-701'MODULE_INSTANCE''Unable to resolve course module instance'FALSE);
  117.             exit();
  118.         }
  119.     }
  120.  
  121.     //require_login($course->id, false, $cm); // I don't think this is a good idea! -PRB
  122.  
  123.  
  124. // Get number for the next or unfinished attempt
  125.     if(!$attemptnumber = (int)get_field_sql('SELECT MAX(attempt)+1 FROM ' .
  126.      "{$CFG->prefix}quiz_attempts WHERE quiz = '{$quiz->id}' AND .
  127.      "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1")) {
  128.         $attemptnumber 1;
  129.     }
  130.  
  131.     $strattemptnum get_string('attempt''quiz'$attemptnumber);
  132.     $strquizzes get_string("modulenameplural""quiz");
  133.  
  134.     // course id: $course->id
  135.     // course id: $course->id
  136.     // course name: $quiz->id
  137.     // attempts: $quiz->attempts
  138.     $output[array('course',$course->id,$course->name);
  139.  
  140.     $numberofpreviousattempts count_records_select('quiz_attempts'"quiz = '{$quiz->id}' AND .
  141.         "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1");
  142.     if ($quiz->attempts and $numberofpreviousattempts >= $quiz->attempts{
  143.         $lsl->response->quick_output(-701'QUIZ''You do not have any attempts left'FALSE);
  144.         exit();
  145.     }
  146.  
  147. /// Check subnet access
  148.     if ($quiz->subnet and !address_in_subnet(getremoteaddr()$quiz->subnet)) {
  149.         $lsl->response->quick_output(-1'MISC''A subnet error occurred'FALSE);
  150.         exit();
  151.     }
  152.  
  153. /// Check password access
  154.     if ($quiz->password{
  155.         $lsl->response->quick_output(-701'QUIZ''Quiz requires password - not supported by Sloodle'FALSE);
  156.         exit();
  157.     }
  158.  
  159.     if ($quiz->delay1 or $quiz->delay2{
  160.         //quiz enforced time delay
  161.         if ($attempts quiz_get_user_attempts($quiz->id$USER->id)) {
  162.             $numattempts count($attempts);
  163.         else {
  164.             $numattempts 0;
  165.         }
  166.         $timenow time();
  167.         $lastattempt_obj get_record_select('quiz_attempts'"quiz = $quiz->id AND attempt = $numattempts AND userid = $USER->id"'timefinish');
  168.         if ($lastattempt_obj{
  169.             $lastattempt $lastattempt_obj->timefinish;
  170.         }
  171.         if ($numattempts == && $quiz->delay1{
  172.             if ($timenow $quiz->delay1 $lastattempt{
  173.                 $lsl->response->quick_output(-701'QUIZ''You need to wait until the time delay has expired.'FALSE);
  174.                 exit();
  175.             }
  176.         else if($numattempts && $quiz->delay2{
  177.             if ($timenow $quiz->delay2 $lastattempt{
  178.                 $lsl->response->quick_output(-701'QUIZ''You need to wait until the time delay has expired.'FALSE);
  179.                 exit();
  180.             }
  181.         }
  182.     }
  183.  
  184. //    sloodle_prim_render_output($output);
  185. //    exit;
  186.  
  187. /// Load attempt or create a new attempt if there is no unfinished one
  188.  
  189.     $attempt get_record('quiz_attempts''quiz'$quiz->id,
  190.      'userid'$USER->id'timefinish'0);
  191.  
  192.     $newattempt false;
  193.     if (!$attempt{
  194.         $newattempt true;
  195.         // Start a new attempt and initialize the question sessions
  196.         $attempt quiz_create_attempt($quiz$attemptnumber);
  197.         // If this is an attempt by a teacher mark it as a preview
  198.         // Save the attempt
  199.         if (!$attempt->id insert_record('quiz_attempts'$attempt)) {
  200.             $lsl->response->quick_output(-701'QUIZ''Could not create new attempt.'FALSE);
  201.             exit();
  202.         }
  203.         // make log entries
  204.         add_to_log($course->id'quiz''attempt',
  205.                        "review.php?attempt=$attempt->id",
  206.                        "$quiz->id"$cm->id);
  207.     else {
  208.         // log continuation of attempt only if some time has lapsed
  209.         if (($timestamp $attempt->timemodified600// 10 minutes have elapsed
  210.              add_to_log($course->id'quiz''continue attemp'// this action used to be called 'continue attempt' but the database field has only 15 characters
  211.                            "review.php?attempt=$attempt->id",
  212.                            "$quiz->id"$cm->id);
  213.         }
  214.     }
  215.     if (!$attempt->timestart// shouldn't really happen, just for robustness
  216.         $attempt->timestart time();
  217.     }
  218.  
  219. /// Load all the questions and states needed by this script
  220.  
  221.     // list of questions needed by page
  222.     $pagelist quiz_questions_on_page($attempt->layout$page);
  223.  
  224.     if ($newattempt{
  225.         $questionlist quiz_questions_in_quiz($attempt->layout);
  226.     else {
  227.         $questionlist $pagelist;
  228.     }
  229.  
  230.     // add all questions that are on the submitted form
  231.     if ($questionids{
  232.         $questionlist .= ','.$questionids;
  233.     }
  234.  
  235.     if (!$questionlist{
  236.         $lsl->response->quick_output(-701'QUIZ''No questions found.'FALSE);
  237.         exit();
  238.     }
  239.  
  240.     $sql "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
  241.            "  FROM {$CFG->prefix}question q,".
  242.            "       {$CFG->prefix}quiz_question_instances i".
  243.            " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
  244.            "   AND q.id IN ($questionlist)";
  245.  
  246.     // Load the questions
  247.     if (!$questions get_records_sql($sql)) {
  248.         $lsl->response->quick_output(-701'QUIZ''No questions found.'FALSE);
  249.         exit();
  250.     }
  251.  
  252.     // Load the question type specific information
  253.     if (!get_question_options($questions)) {
  254.         $lsl->response->quick_output(-701'QUIZ''Could not load question options.'FALSE);
  255.         exit();
  256.     }
  257.  
  258.     // Restore the question sessions to their most recent states
  259.     // creating new sessions where required
  260.     if (!$states get_question_states($questions$quiz$attempt)) {
  261.         $lsl->response->quick_output(-701'QUIZ''Could not restore questions sessions.'FALSE);
  262.         exit();
  263.     }
  264.  
  265.     // Save all the newly created states
  266.     if ($newattempt{
  267.         foreach ($questions as $i => $question{
  268.             save_question_session($questions[$i]$states[$i]);
  269.         }
  270.     }
  271.  
  272.     // If the new attempt is to be based on a previous attempt copy responses over
  273.     if ($newattempt and $attempt->attempt and $quiz->attemptonlast and !$attempt->preview{
  274.         // Find the previous attempt
  275.         if (!$lastattemptid get_field('quiz_attempts''uniqueid''quiz'$attempt->quiz'userid'$attempt->userid'attempt'$attempt->attempt-1)) {
  276.             $lsl->response->quick_output(-701'QUIZ''Could not find previous attempt to build on.'FALSE);
  277.             exit();
  278.         }
  279.         // For each question find the responses from the previous attempt and save them to the new session
  280.         foreach ($questions as $i => $question{
  281.             // Load the last graded state for the question
  282.             $statefields 'n.questionid as question, s.*, n.sumpenalty';
  283.             $sql "SELECT $statefields".
  284.                    "  FROM {$CFG->prefix}question_states s,".
  285.                    "       {$CFG->prefix}question_sessions n".
  286.                    " WHERE s.id = n.newgraded".
  287.                    "   AND n.attemptid = '$lastattemptid'".
  288.                    "   AND n.questionid = '$i'";
  289.             if (!$laststate get_record_sql($sql)) {
  290.                 // Only restore previous responses that have been graded
  291.                 continue;
  292.             }
  293.             // Restore the state so that the responses will be restored
  294.             restore_question_state($questions[$i]$laststate);
  295.             // prepare the previous responses for new processing
  296.             $action new stdClass;
  297.             $action->responses $laststate->responses;
  298.             $action->timestamp $laststate->timestamp;
  299.             $action->event QUESTION_EVENTOPEN;
  300.  
  301.             // Process these responses ...
  302.             question_process_responses($questions[$i]$states[$i]$action$quiz$attempt);
  303.  
  304.             // Fix for Bug #5506: When each attempt is built on the last one,
  305.             // preserve the options from any previous attempt. 
  306.             if isset($laststate->options) ) {
  307.                 $states[$i]->options $laststate->options;
  308.             }
  309.  
  310.             // ... and save the new states
  311.             save_question_session($questions[$i]$states[$i]);
  312.         }
  313.     }
  314.  
  315. /// Process form data /////////////////////////////////////////////////
  316.  
  317.     if ($isnotify{
  318.  
  319.         $responses = (object)$_GET// GET version of data_submitted (see lib/weblib) used in original web version
  320.  
  321.         // set the default event. This can be overruled by individual buttons.
  322.         $event (array_key_exists('markall'$responses)) QUESTION_EVENTSUBMIT :
  323.          ($finishattempt QUESTION_EVENTCLOSE QUESTION_EVENTSAVE);
  324.  
  325.         // Unset any variables we know are not responses
  326.         unset($responses->id);
  327.         unset($responses->q);
  328.         unset($responses->oldpage);
  329.         unset($responses->newpage);
  330.         unset($responses->review);
  331.         unset($responses->questionids);
  332.         unset($responses->saveattempt)// responses get saved anway
  333.         unset($responses->finishattempt)// same as $finishattempt
  334.         unset($responses->markall);
  335.         unset($responses->forcenewattempt);
  336.  
  337.         // extract responses
  338.         // $actions is an array indexed by the questions ids
  339.         $actions question_extract_responses($questions$responses$event);
  340.  
  341.         // Process each question in turn
  342.  
  343.         $questionidarray explode(','$questionids);
  344.         foreach($questionidarray as $i{
  345.             if (!isset($actions[$i])) {
  346.                 $actions[$i]->responses array('' => '');
  347.             }
  348.             $actions[$i]->timestamp $timestamp;
  349.             question_process_responses($questions[$i]$states[$i]$actions[$i]$quiz$attempt);
  350.             save_question_session($questions[$i]$states[$i]);
  351.         }
  352.  
  353.         $attempt->timemodified $timestamp;
  354.  
  355.     // We have now finished processing form data
  356.     }
  357.  
  358.  
  359. /// Finish attempt if requested
  360.     if ($finishattempt{
  361.  
  362.         // Set the attempt to be finished
  363.         $attempt->timefinish $timestamp;
  364.  
  365.         // Find all the questions for this attempt for which the newest
  366.         // state is not also the newest graded state
  367.         if ($closequestions get_records_select('question_sessions',
  368.          "attemptid = $attempt->uniqueid AND newest != newgraded"'''questionid, questionid')) {
  369.  
  370.             // load all the questions
  371.             $closequestionlist implode(','array_keys($closequestions));
  372.             $sql "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
  373.                    "  FROM {$CFG->prefix}question q,".
  374.                    "       {$CFG->prefix}quiz_question_instances i".
  375.                    " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
  376.                    "   AND q.id IN ($closequestionlist)";
  377.             if (!$closequestions get_records_sql($sql)) {
  378.                 $lsl->response->quick_output(-701'QUIZ''Questions missing.'FALSE);
  379.                 exit();
  380.             }
  381.  
  382.             // Load the question type specific information
  383.             if (!get_question_options($closequestions)) {
  384.                 $lsl->response->quick_output(-701'QUIZ''Could not load question options.'FALSE);
  385.                 exit();
  386.             }
  387.  
  388.             // Restore the question sessions
  389.             if (!$closestates get_question_states($closequestions$quiz$attempt)) {
  390.                 $lsl->response->quick_output(-701'QUIZ''Could not restore question sessions.'FALSE);
  391.                 exit();
  392.             }
  393.  
  394.             foreach($closequestions as $key => $question{
  395.                 $action->event QUESTION_EVENTCLOSE;
  396.                 $action->responses $closestates[$key]->responses;
  397.                 $action->timestamp $closestates[$key]->timestamp;
  398.                 question_process_responses($question$closestates[$key]$action$quiz$attempt);
  399.                             save_question_session($question$closestates[$key]);
  400.             }
  401.         }
  402.         add_to_log($course->id'quiz''close attempt',
  403.                            "review.php?attempt=$attempt->id",
  404.                            "$quiz->id"$cm->id);
  405.     }
  406.  
  407. /// Update the quiz attempt and the overall grade for the quiz
  408.     if ($responses || $finishattempt{
  409.         if (!update_record('quiz_attempts'$attempt)) {
  410.             $lsl->response->quick_output(-701'QUIZ''Failed to save current quiz attempt.'FALSE);
  411.             exit();
  412.         }
  413.         if (($attempt->attempt || $attempt->timefinish 0and !$attempt->preview{
  414.             quiz_save_best_grade($quiz);
  415.         }
  416.     }
  417.  
  418. /// Check access to quiz page
  419.  
  420.     // check the quiz times
  421.     //TODO: Figure out what this does...
  422.     if ($timestamp $quiz->timeopen || ($quiz->timeclose and $timestamp $quiz->timeclose)) {
  423.         $lsl->response->quick_output(-701'QUIZ''Quiz not available.'FALSE);
  424.         exit();
  425.     }
  426.  
  427.     if ($finishattempt{
  428.         // redirect('review.php?attempt='.$attempt->id);
  429.         $lsl->response->quick_output(-701'QUIZ''Got to finishattempt - but do not yet have Sloodle code to handle it.'FALSE);
  430.         exit();
  431.     }
  432.  
  433. /// Print the quiz page ////////////////////////////////////////////////////////
  434.  
  435.     if (!$isnotify{
  436.         $output[array('quiz',$quiz->attempts,$quiz->name,$quiz->timelimit,$quiz->id);
  437.         $output[array('quizpages',quiz_number_of_pages($attempt->layout),$page,$pagelist);
  438.  
  439.         /// Print all the questions
  440.  
  441.         $pagequestions explode(','$pagelist);
  442.         $number quiz_first_questionnumber($attempt->layout$pagelist);
  443.         foreach ($pagequestions as $i{
  444.             $options quiz_get_renderoptions($quiz->review$states[$i]);
  445.             // Print the question
  446.             // var_dump($questions[$i]);
  447.             $q $questions[$i];
  448.             $output[array(
  449.                 'question',
  450.                 $i,
  451.                 $q->id,
  452.                 $q->parent,
  453.                 $q->questiontext,
  454.                 $q->defaultgrade,
  455.                 $q->penalty,
  456.                 $q->qtype,
  457.                 $q->hidden,
  458.                 $q->maxgrade,
  459.                 $q->single,
  460.                 $q->shuffleanswers
  461.             );
  462.             $ops $q->options;
  463.             foreach($ops as $opkey=>$op{
  464.             //print "<h1>$opkey is $op</h1>";
  465.                foreach($op as $ok=>$ov{
  466.                   //print '<h1>   '."$ok=>$ov</h1>";
  467.                   //var_dump($ov);
  468.                   $output[array(
  469.                     'questionoption',
  470.                     $i,
  471.                     $ov->id,
  472.                     $ov->question,
  473.                     $ov->answer,
  474.                     $ov->fraction,
  475.                     $ov->feedback
  476.                   );
  477.                }
  478.             }
  479.  
  480.             //print_question($questions[$i], $states[$i], $number, $quiz, $options);
  481.             save_question_session($questions[$i]$states[$i]);
  482.             $number += $questions[$i]->length;
  483.         }
  484.  
  485.         $secondsleft ($quiz->timeclose $quiz->timeclose 999999999999time();
  486.         if ($isteacher{
  487.             // For teachers ignore the quiz closing time
  488.             $secondsleft 999999999999;
  489.         }
  490.         // If time limit is set include floating timer.
  491.         if ($quiz->timelimit 0{
  492.             $output[array('seconds left',$secondsleft);
  493.         }
  494.     }
  495.  
  496.     $lsl->response->set_status_code(1);
  497.     $lsl->response->set_status_descriptor('QUIZ');
  498.     $lsl->response->set_data($output);
  499.     $lsl->response->render_to_output();
  500.     exit;
  501.  
  502. ?>

Documentation generated on Tue, 04 Mar 2008 15:08:52 +0000 by phpDocumentor 1.4.0