Source for file linker.php

Documentation is available at linker.php

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

Documentation generated on Fri, 17 Jul 2009 11:01:43 +0100 by phpDocumentor 1.4.0