[system] / trunk / wwmoodle / wwquestion / questiontype.php Repository:
ViewVC logotype

View of /trunk/wwmoodle/wwquestion/questiontype.php

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5477 - (download) (as text) (annotate)
Sun Sep 9 02:53:16 2007 UTC (12 years, 5 months ago) by mleventi
File size: 25103 byte(s)
*** empty log message ***

    1 <?php
    2 
    3 require_once("$CFG->dirroot/question/type/webwork/config.php");
    4 require_once("$CFG->dirroot/question/type/webwork/locallib.php");
    5 require_once("$CFG->dirroot/question/type/questiontype.php");
    6 require_once("$CFG->dirroot/backup/lib.php");
    7 
    8 /**
    9  * The question type class for the webwork question type.
   10  *
   11  * @copyright &copy; 2007 Matthew Leventi
   12  * @author mleventi@gmail.com
   13  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
   14  * @package webwork_qtype
   15 **/
   16 
   17 /**
   18  * The webwork question class
   19  *
   20  * Allows webwork questions to be used in Moodle through a new question type.
   21  */
   22 class webwork_qtype extends default_questiontype {
   23 
   24     //////////////////////////////////////////////////////////////////
   25     // Functions overriding default_questiontype functions
   26     //////////////////////////////////////////////////////////////////
   27 
   28     /**
   29     * @desc Required function that names the question type.
   30     * @return string webwork.
   31     */
   32     function name() {
   33         return 'webwork';
   34     }
   35 
   36     /**
   37     * @desc Gives the label in the Create Question dropdown.
   38     * @return string WeBWorK
   39     */
   40     function menu_name() {
   41         return 'WeBWorK';
   42     }
   43 
   44     /**
   45      * @desc Retrieves information out of question_webwork table and puts it into question object.
   46      * @return boolean to indicate success of failure.
   47      */
   48     function get_question_options(&$question) {
   49         if (!$record = get_record('question_webwork', 'question', $question->id)) {
   50             notify('Error: Missing question options!');
   51             return false;
   52         }
   53         $question->trials = $record->trials;
   54         $question->seed = $record->seed;
   55         $question->code = base64_decode($record->code);
   56         $question->codecheck = $record->codecheck;
   57         //hold onto the ID of the question_webwork record
   58         $question->webworkid = $record->id;
   59         return true;
   60     }
   61 
   62     /**
   63      * @desc Saves the webwork question code and default seed setting into question_webwork. Will recreate all corresponding derived questions.
   64      * @param $question object The question object holding new data.
   65      * @return boolean to indicate success of failure.
   66      */
   67     function save_question_options($question) {
   68         //determing update or insert
   69         $oldrecord = get_record('question_webwork','question',$question->id);
   70         if(!$oldrecord) {
   71             $isupdate = false;
   72         } else {
   73             $isupdate = true;
   74         }
   75         //set new variables for DB entry
   76         $record = new stdClass;
   77         $record->question = $question->id;
   78         $record->codecheck = $question->codecheck;
   79         $record->code = base64_encode(stripslashes($question->code));
   80         $record->seed = $question->seed;
   81         $record->trials = $question->trials;
   82         //insert or update question in DB
   83         if($isupdate) {
   84             //update
   85             $record->id = $oldrecord->id;
   86             $errresult = update_record("question_webwork", $record);
   87             if (!$errresult) {
   88                 $errresult->error = "Could not update question_webwork record! (id=$record->id)";
   89                 return $errresult;
   90             }
   91         } else {
   92             //insert
   93             $errresult = insert_record("question_webwork", $record);
   94             if (!$errresult) {
   95                 $errresult->error = "Could not insert question_webwork record!";
   96                 return $errresult;
   97             }
   98             //set the new record id
   99             $record->id = $errresult;
  100         }
  101         $wwquestionid = $record->id;
  102         //copy the tmp directory to the question one
  103         if($isupdate == false) {
  104             rename(webwork_get_tmp_path_full(),webwork_get_wwquestion_path_full($wwquestionid));
  105         }
  106 
  107 
  108         //update the derivations
  109         $this->_update_derivations($wwquestionid);
  110         return true;
  111     }
  112 
  113     /**
  114     * @desc Creates an empty response before a student answers a question. This contains the possibly randomized seed for that particular student. Sticky seeds are created here.
  115     * @param $question object The question object.
  116     * @param $state object The state object.
  117     * @param $cmoptions object The cmoptions containing the course ID
  118     * @param $attempt id The attempt ID.
  119     * @return bool true.
  120     */
  121     function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
  122         global $CFG,$USER;
  123         //here we get the derived results for this question
  124         $derivations = get_records("question_webwork_derived","question_webwork",$question->webworkid,'','id');
  125         if(!$derivations) {
  126             print_error(get_string('error_db_webwork_derived','qtype_webwork'));
  127             return false;
  128         }
  129 
  130         //make sure its not 0
  131         if(count($derivations) == 0) {
  132             print_error(get_string('error_no_webwork_derived','qtype_webwork'));
  133             return false;
  134         }
  135 
  136         //pick a random question based on time
  137         srand(time());
  138         $random = rand(0,count($derivations)-1);
  139         $values = array_values($derivations);
  140         $derivationid = $values[$random]->id;
  141 
  142         //get the actual data
  143         $derivation = get_record('question_webwork_derived','id',$derivationid);
  144         //build state
  145         $state->responses['seed'] = $derivation->seed;
  146         $state->responses['derivationid'] = $derivation->id;
  147         return true;
  148     }
  149 
  150     /**
  151      * @desc Deletes question from the question_webwork table
  152      * @param integer $questionid The question being deleted
  153      * @return boolean to indicate success of failure.
  154      */
  155     function delete_question($questionid) {
  156         //Get wwquestion from DB
  157         $record = get_record('question_webwork','question',$questionid);
  158         $wwquestionid = $record->id;
  159 
  160         //delete DB and Files
  161         webwork_delete_wwquestion_dir($wwquestionid);
  162         delete_records('question_webwork', 'id' , $wwquestionid);
  163 
  164         //delete derivations
  165         webwork_delete_derivations_db($wwquestionid);
  166         return true;
  167     }
  168 
  169     /**
  170     * @desc Decodes and unserializes a students response into the response array carried by state
  171     * @param $question object The question object.
  172     * @param $state object The state that needs to be restored.
  173     * @return bool true.
  174     */
  175     function restore_session_and_responses(&$question, &$state) {
  176         $serializedresponse = $state->responses[''];
  177         $serializedresponse = base64_decode($serializedresponse);
  178         $responses = unserialize($serializedresponse);
  179         $state->responses = $responses;
  180         return true;
  181     }
  182 
  183     /**
  184     * @desc Serialize, encodes and inserts a students response into the question_states table.
  185     * @param $question object The question object for the session.
  186     * @param $state object The state to save.
  187     * @return true, or error on db change.
  188     */
  189     function save_session_and_responses(&$question, &$state) {
  190         $responses = $state->responses;
  191         $serialized = serialize($responses);
  192         $serialized = base64_encode($serialized);
  193         return set_field('question_states', 'answer', $serialized, 'id', $state->id);
  194     }
  195 
  196     /**
  197     * @desc Prints the question. Calls question_webwork_derived, and prints out the html associated with derivedid.
  198     * @param $question object The question object to print.
  199     * @param $state object The state of the responses for the question.
  200     * @param $cmoptions object Options containing course ID.
  201     * @param $options object
  202     */
  203     function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
  204         global $CFG,$USER;
  205         $readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
  206         //Formulate question image and text
  207         $questiontext = $this->format_text($question->questiontext,
  208                 $question->questiontextformat, $cmoptions);
  209         $image = get_question_image($question, $cmoptions->course);
  210 
  211         $derivationid = $state->responses['derivationid'];
  212         $derivation = get_record('question_webwork_derived','id',$derivationid);
  213 
  214         $unparsedhtml = base64_decode($derivation->html);
  215 
  216 
  217         //new array keyed by field
  218         $fieldhash = $state->responses['answers'];
  219         $answerfields = array();
  220         $parser = new HtmlParser($unparsedhtml);
  221         $currentselect = "";
  222         while($parser->parse()) {
  223             //change some attributes of html tags for moodle compliance
  224             if ($parser->iNodeType == NODE_TYPE_ELEMENT) {
  225                 $nodename = $parser->iNodeName;
  226                 $name = $parser->iNodeAttributes['name'];
  227                 //handle generic change of node's attribute name
  228                 if(($nodename == "INPUT") || ($nodename == "SELECT") || ($nodename == "TEXTAREA")) {
  229                     $parser->iNodeAttributes['name'] = 'resp' . $question->id . '_' . $name;
  230                     if(($state->event == QUESTION_EVENTGRADE) && (isset($fieldhash[$name]))) {
  231                         $parser->iNodeAttributes['class'] = $parser->iNodeAttributes['class'] . ' ' . question_get_feedback_class($fieldhash[$name]['score']);
  232                     }
  233                     if(!strstr($name,'previous')) {
  234                         $answerfields[$name] = $fieldhash[$name];
  235                     }
  236                 }
  237                 //handle specific change
  238                 if($nodename == "INPUT") {
  239                     //put submitted value into field
  240                     if(isset($fieldhash[$name])) {
  241                         $parser->iNodeAttributes['value'] = $fieldhash[$name]['answer'];
  242                     }
  243                 } else if($nodename == "SELECT") {
  244                     $currentselect = $name;
  245                 } else if($nodename == "OPTION") {
  246                     if($parser->iNodeAttributes['value'] == $fieldhash[$currentselect]['answer'])
  247                         $parser->iNodeAttributes['selected'] = '1';
  248                 } else if($nodename == "TEXTAREA") {
  249                 }
  250             }
  251             $problemhtml .= $parser->printTag();
  252         }
  253 
  254         //for the seed form field
  255         $qid = $question->id;
  256         $seed = $state->responses['seed'];
  257 
  258         //if the student has answered
  259         include("$CFG->dirroot/question/type/webwork/display.html");
  260     }
  261 
  262     /**
  263     * @desc Assigns a grade for a student response. Currently a percentage right/total questions. Calls the Webwork Server to evaluate answers.
  264     * @param $question object The question to grade.
  265     * @param $state object The response to the question.
  266     * @param $cmoptions object ...
  267     * @return boolean true.
  268     */
  269     function grade_responses(&$question, &$state, $cmoptions) {
  270         global $CFG,$USER;
  271 
  272         //get code and seed of the students problem
  273         $code = base64_encode($question->code);
  274         $seed = $state->responses['seed'];
  275         $derivationid = $state->responses['derivationid'];
  276         $wwquestionid = $question->webworkid;
  277 
  278         //get answers & build answer request
  279         $answerarray = array();
  280         foreach($state->responses as $key => $value) {
  281             array_push($answerarray, array('field' => $key, 'answer'=> $value));
  282         }
  283 
  284         //build problem request
  285         $problem = array();
  286         $problem['code'] = $code;
  287         $problem['seed'] = $seed;
  288 
  289         //SOAP request
  290         $params = array();
  291         $params['request'] = $problem;
  292         $params['answers'] = $answerarray;
  293 
  294         //SOAP Call
  295         $client = new webwork_client();
  296         $response = $client->handler('checkAnswers',$params);
  297 
  298         //process output from soap & calculate score
  299         $answers = $response;
  300         $state->raw_grade = 0;
  301         $total = 0;
  302         $num = 0;
  303         foreach($answers as $answer) {
  304             $total += $answer['score'];
  305             $num++;
  306         }
  307         if($num != 0) {
  308             $state->raw_grade = $total / $num;
  309         }
  310 
  311         //create the directory for this user
  312         webwork_make_derivation_user_dir($wwquestionid,$derivationid,$USER->id);
  313 
  314         // Apply the penalty for this attempt
  315         $state->penalty = $question->penalty * $question->maxgrade;
  316 
  317         // mark the state as graded
  318         $state->event = ($state->event ==  QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
  319 
  320         //put the responses into the state to remember
  321         $state->responses['answers'] = array();
  322 
  323         foreach ($answers as $answer) {
  324             $html = base64_decode($answer['preview']);
  325             $copyto = webwork_get_derivation_user_path_full($wwquestionid,$derivationid,$USER->id);
  326             $replacer = webwork_get_derivation_user_url($wwquestionid,$derivationid,$USER->id);
  327             $html = webwork_parse_change_ans($html,$replacer,$copyto);
  328             $answer['preview'] = $html;
  329             $state->responses['answers'][$answer['field']] = $answer;
  330         }
  331         return true;
  332     }
  333 
  334     /**
  335     * @desc Comparison of two student responses for the same question. Checks based on seed equality, and response equality.
  336     * Perhaps we could add check on evaluated answer (depends on whether the server is called before this function)
  337     * @param $question object The question object to compare.
  338     * @param $state object The first response.
  339     * @param $teststate object The second response.
  340     * @return boolean, Returns true if the state are equal | false if not.
  341     */
  342     function compare_responses($question, $state, $teststate) {
  343         if(sizeof($state->responses) != sizeof($teststate->responses)) {
  344             return false;
  345         }
  346         //check values are equal
  347         foreach($state->responses as $key => $value) {
  348             if($value != $teststate->responses[$key]) {
  349                 return false;
  350             }
  351         }
  352         return true;
  353     }
  354 
  355     /**
  356     * @desc Gets the correct answers from the SOAP server for the seed in state. Places them into the state->responses array.
  357     * @param $question object The question object.
  358     * @param $state object The state object.
  359     * @return object Object containing the seed,derivedid, and answers.
  360     */
  361     function get_correct_responses(&$question, &$state) {
  362         //get code and seed of response
  363         $code = base64_encode($question->code);
  364         $seed = $state->responses['seed'];
  365         $derivationid = $state->responses['derivationid'];
  366 
  367         //get empty answers & build answer request
  368         $answerarray = array();
  369         foreach($state->responses as $key => $value) {
  370             array_push($answerarray, array('field' => $key, 'answer'=> $value));
  371         }
  372 
  373         //build problem request
  374         $problem = array();
  375         $problem['code'] = $code;
  376         $problem['seed'] = $seed;
  377 
  378         //SOAP request
  379         $params = array();
  380         $params['request'] = $problem;
  381         $params['answers'] = $answerarray;
  382 
  383         //SOAP Call
  384         $client = new webwork_client();
  385         $response = $client->handler('checkAnswers',$params);
  386 
  387         //make the state perfect graded
  388         $state->raw_grade = 1;
  389         $state->event = ($state->event ==  QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
  390         $state->penalty = 0;
  391 
  392         //process correct answers into fields
  393         $answers = $response;
  394         $ret = array();
  395         $ret['answers'] = array();
  396 
  397         foreach ($answers as $answer) {
  398             $ret['answers'][$answer['field']] = $answer;
  399             $ret['answers'][$answer['field']]['answer'] = $answer['correct'];
  400             $ret['answers'][$answer['field']]['score'] = 1;
  401             $ret['answers'][$answer['field']]['evaluated'] = "";
  402             $ret['answers'][$answer['field']]['preview'] = "";
  403         }
  404 
  405         //push the seed onto the answer array, keep track of what seed these are for.
  406         $ret['seed'] = $seed;
  407         $ret['derivationid'] = $derivationid;
  408         return $ret;
  409     }
  410 
  411     /**
  412     * @desc Prints a short 40 character limited version of all the answers for a question.
  413     * @param $question object The question object.
  414     * @param $state object The state object.
  415     */
  416     function get_actual_response($question, $state) {
  417         $temp = '';
  418         $i = 1;
  419         foreach($state->responses['answers'] as $key => $value) {
  420             $temp .= "$i) " . $value['answer'] . " ";
  421             $i++;
  422         }
  423         $lmax = 40;
  424         $responses[] = (strlen($temp) > $lmax) ? substr($temp, 0, $lmax).'...' : $temp;
  425         return $responses;
  426     }
  427 
  428     /**
  429     * Changes all states for the given attempts over to a new question
  430     *
  431     * This is used by the versioning code if the teacher requests that a question
  432     * gets replaced by the new version. In order for the attempts to be regraded
  433     * properly all data in the states referring to the old question need to be
  434     * changed to refer to the new version instead. In particular for question types
  435     * that use the answers table the answers belonging to the old question have to
  436     * be changed to those belonging to the new version.
  437     *
  438     * @param integer $oldquestionid  The id of the old question
  439     * @param object $newquestion    The new question
  440     * @param array  $attempts       An array of all attempt objects in whose states
  441     *                               replacement should take place
  442     */
  443     function replace_question_in_attempts($oldquestionid, $newquestion, $attempts) {
  444         echo 'Not yet implemented';
  445         return;
  446     }
  447 
  448     /**
  449     * @desc Updates the derivations of a wwquestion.
  450     * @param integer $wwquestionid The derivation to update.
  451     * @return boolean true
  452     */
  453     function _update_derivations($wwquestionid) {
  454         global $CFG;
  455         //retrieve the new records
  456         $newrecordset = webwork_qtype::_derivations();
  457         //retrieve the old records
  458         $oldrecordset = get_records('question_webwork_derived','question_webwork',$wwquestionid);
  459         //records that we will have
  460         $therecordset = array();
  461 
  462         //load in the new ones (by seed)
  463         foreach($newrecordset as $newrecord) {
  464             unset($temprecord);
  465             //assign parentid
  466             $temprecord->question_webwork = $wwquestionid;
  467             //copy data
  468             $temprecord->seed = $newrecord['seed'];
  469             $temprecord->html = $newrecord['output'];
  470             $therecordset[$temprecord->seed] = $temprecord;
  471         }
  472         //overwrite IDs with old IDs if seeds match
  473         if(isset($oldrecordset)) {
  474             //stuff that exists and might be updatable
  475             foreach($oldrecordset as $oldrecord) {
  476                 //do we have an old seed that matches new seeds
  477                 $oldseed = $oldrecord->seed;
  478                 if(isset($therecordset[$oldseed]) && ($therecordset[$oldseed] != null)) {
  479                     //found a seed that already exists, make sure it goes into the old ID
  480                     $therecordset[$oldseed]->id = $oldrecord->id;
  481                 } else {
  482                     //didnt find a seed that exists, delete the old record
  483                     $derivationid = $oldrecord->id;
  484                     webwork_delete_derivation_db($derivationid);
  485                     webwork_delete_derivation_dir($wwquestionid,$derivationid);
  486                 }
  487             }
  488         }
  489         //update or insert into database
  490         foreach($therecordset as $record) {
  491 
  492             //initial insert to get the ID when necessary
  493             if(!isset($record->id)) {
  494                 unset($record->id);
  495                 $result = insert_record('question_webwork_derived',$record);
  496                 if(!$result) {
  497                     print_error('DB opertaion failed');
  498                 }
  499                 $record->id = $result;
  500             }
  501 
  502             //makes the derivation directory
  503             webwork_make_derivation_dir($wwquestionid,$record->id);
  504 
  505             webwork_parse_change_derivation($record);
  506             //updates record
  507             $err = update_record('question_webwork_derived',$record);
  508             if(!$err) {
  509                 print_error('DB error on updating question_webwork_derived');
  510             }
  511         }
  512         return true;
  513     }
  514 
  515 
  516 
  517     /**
  518     * @desc This hold the derivation data that comes out of form validation.
  519     * @param array $derivations The derivation data.
  520     * @return object true or derivation data.
  521     */
  522     function _derivations($derivations = null) {
  523         static $temp = null;
  524         if($derivations == null) {
  525             return $temp;
  526         }
  527         $temp = $derivations;
  528         return true;
  529     }
  530 
  531     /**
  532      * Backup the data in the question
  533      *
  534      * This is used in question/backuplib.php
  535      */
  536     function backup($bf,$preferences,$question,$level=6) {
  537 
  538         $status = true;
  539 
  540         $webworks = get_records('question_webwork', 'question', $question, 'id');
  541         //If there are webworks
  542         if ($webworks) {
  543             //Print webworks header
  544             //Iterate over each webwork
  545             foreach ($webworks as $webwork) {
  546 
  547                 $status = fwrite ($bf,start_tag("WEBWORK",$level,true));
  548 
  549                 fwrite ($bf,full_tag("CODE",$level+1,false,$webwork->code));
  550                 fwrite ($bf,full_tag("SEED",$level+1,false,$webwork->seed));
  551                 fwrite ($bf,full_tag("TRIALS",$level+1,false,$webwork->trials));
  552 
  553                 $webworksderived = get_records('question_webwork_derived','question_webwork',$webwork->id);
  554                 if($webworksderived) {
  555                     $status = fwrite ($bf,start_tag("WEBWORKDERIVED",$level+1,true));
  556                     foreach ($webworksderived as $webworkderived) {
  557                         fwrite ($bf,full_tag("ID",$level+2,false,$webworkderived->id));
  558                         fwrite ($bf,full_tag("QUESTION_WEBWORK",$level+2,false,$webworkderived->question_webwork));
  559                         fwrite ($bf,full_tag("HTML",$level+2,false,$webworkderived->html));
  560                         fwrite ($bf,full_tag("SEED",$level+2,false,$webworkderived->seed));
  561                     }
  562                     $status = fwrite ($bf,end_tag("WEBWORKDERIVED",$level+1,true));
  563                 }
  564                 $status = fwrite ($bf,end_tag("WEBWORK",$level,true));
  565             }
  566             //Print webworks footer
  567             //Now print question_webwork
  568             $status = question_backup_answers($bf,$preferences,$question);
  569         }
  570         return $status;
  571     }
  572 
  573     /**
  574      * Restores the data in the question
  575      *
  576      * This is used in question/restorelib.php
  577      */
  578      function restore($old_question_id,$new_question_id,$info,$restore) {
  579 
  580         $status = true;
  581 
  582         //Get the webworks array
  583         $webworks = $info['#']['WEBWORK'];
  584 
  585         //Iterate over webworks
  586         for($i = 0; $i < sizeof($webworks); $i++) {
  587             $webwork_info = $webworks[$i];
  588 
  589             //Now, build the question_webwork record structure
  590             $webwork = new stdClass;
  591             $webwork->question = $new_question_id;
  592             $webwork->code = backup_todb($webwork_info['#']['CODE']['0']['#']);
  593             $webwork->seed = backup_todb($webwork_info['#']['SEED']['0']['#']);
  594             $webwork->trials = backup_todb($webwork_info['#']['TRIALS']['0']['#']);
  595 
  596             //The structure is equal to the db, so insert the question_shortanswer
  597             $newid = insert_record("question_webwork",$webwork);
  598 
  599             $webworksderived = $webwork_info['#']['WEBWORKDERIVED'];
  600             for($j=0; $j < sizeof($webworksderived); $j++) {
  601                 $webworkderived_info = $webworksderived[$j];
  602 
  603                 $webworkderived = new stdClass;
  604                 $webworkderived->question_webwork = $newid;
  605                 $webworkderived->html = backup_todb($webworkderived_info['#']['HTML']['0']['#']);
  606                 $webworkderived->seed = backup_todb($webworkderived_info['#']['SEED']['0']['#']);
  607 
  608                 $newidderived = insert_record("question_webwork_derived",$webworkderived);
  609                 if (!$newidderived) {
  610                     $status = false;
  611                 }
  612             }
  613 
  614             //Do some output
  615             if (($i+1) % 50 == 0) {
  616                 if (!defined('RESTORE_SILENTLY')) {
  617                     echo ".";
  618                     if (($i+1) % 1000 == 0) {
  619                         echo "<br />";
  620                     }
  621                 }
  622                 backup_flush(300);
  623             }
  624 
  625             if (!$newid) {
  626                 $status = false;
  627             }
  628         }
  629         return $status;
  630     }
  631 }
  632 
  633 // Register this question type with the system.
  634 question_register_questiontype(new webwork_qtype());
  635 ?>

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9