From: Matt L. v. a. <we...@ma...> - 2007-08-29 04:40:33
|
Log Message: ----------- New version see readme Modified Files: -------------- wwmoodle/wwquestion: README.txt display.html edit_webwork_form.php questiontype.php version.php wwmoodle/wwquestion/db: install.xml wwmoodle/wwquestion/lang/en_utf8: qtype_webwork.php Revision Data ------------- Index: version.php =================================================================== RCS file: /webwork/cvs/system/wwmoodle/wwquestion/version.php,v retrieving revision 1.2 retrieving revision 1.3 diff -Lwwquestion/version.php -Lwwquestion/version.php -u -r1.2 -r1.3 --- wwquestion/version.php +++ wwquestion/version.php @@ -1,5 +1,5 @@ <?PHP // $Id$ -$plugin->version = 2007072600; // TODO. +$plugin->version = 2007082900; // TODO. $plugin->requires = 2006032200; ?> \ No newline at end of file Index: README.txt =================================================================== RCS file: /webwork/cvs/system/wwmoodle/wwquestion/README.txt,v retrieving revision 1.3 retrieving revision 1.4 diff -Lwwquestion/README.txt -Lwwquestion/README.txt -u -r1.3 -r1.4 --- wwquestion/README.txt +++ wwquestion/README.txt @@ -1,9 +1,28 @@ Webwork Question Type ---------------------- -Version: 0.1 (stable) +Version: 0.2 (stable) Maintainer: Matthew Leventi <mle...@gm...> CVS: cvs.webwork.rochester.edu:/webwork/cvs/system wwmoodle/wwquestion +**If your using this send me an email.** + +Whats coming soon (sept 1): +* Question Importer (alpha at CVS: cvs.webwork.rochester.edu:/webwork/cvs/system wwmoodle/wwquestion_importer) +* Applet & External Dep. support for PG files. + +Whats New: +* DB consistency issues fixed (thanks to Jean-Marc) +* New code checking, makes sure PG code is correct +* Images are now copied locally for faster problem loading +* Minor bug Fixes + + +Upgrading (from 0.1): +A database column was added called codecheck. If you have data that you don't want to lose your going to have to add the new column manually to the db +mysql command: ALTER TABLE mdl_question_webwork ADD COLUMN codecheck int(10) not null default 0; +Then you can delete the webwork directory and recreate it from the CVS +** I am not really sure if this is backward compatible to 0.1. If you are having problems with questions edit them and submit to regenerate derived copies. + Setup: 1) Make a new folder named 'webwork' in the question/type directory. 2) Copy all the files from this directory into . @@ -11,7 +30,6 @@ Configuration: 1) Change the WSDL path variable in the webwork/questiontype.php file to point to your Webwork Problem Server's WSDL file. -2) Modify the displayMode to your preferences (images) Use: A webwork question only has three special fields. Index: questiontype.php =================================================================== RCS file: /webwork/cvs/system/wwmoodle/wwquestion/questiontype.php,v retrieving revision 1.10 retrieving revision 1.11 diff -Lwwquestion/questiontype.php -Lwwquestion/questiontype.php -u -r1.10 -r1.11 --- wwquestion/questiontype.php +++ wwquestion/questiontype.php @@ -5,8 +5,7 @@ require_once("htmlparser.php"); //Path to the WSDL file on the Webwork Server -define('PROBLEMSERVER_WSDL','http://128.151.231.20/WSDL.wsdl'); -define('PROBLEMSERVER_DISPLAYMODE','images'); +define('PROBLEMSERVER_WSDL','YOUR WSDL PATH HERE'); /** @@ -29,6 +28,15 @@ function name() { return 'webwork'; } + + function _derivedquestions($derivedquestions = null) { + static $temp = null; + if($derivedquestions == null) { + return $temp; + } + $temp = $derivedquestions; + return true; + } /** * @desc Retrieves the seed and decoded code out of the question_webwork table. @@ -41,74 +49,182 @@ notify('Error: Missing question options!'); return false; } + $question->trials = $record->trials; $question->seed = $record->seed; $question->code = base64_decode($record->code); + $question->codecheck = $record->codecheck; + $question->webworkid = $record->id; return true; } /** - * @desc Saves the webwork question code and default seed setting into question_webwork + * @desc Saves the webwork question code and default seed setting into question_webwork. Will recreate all corresponding derived questions. * @return boolean to indicate success of failure. */ function save_question_options($question) { - // TODO code to save the extra data to your database tables from the - // $question object, which has all the post data from editquestion.html - // Save question options in question_webwork table + + //UPDATE OR INSERTION if ($record = get_record("question_webwork", "question", $question->id)) { - //get rid of all the derived questions based on this one - $this->delete_derived_questions($question->id); - - $record->code = base64_encode(stripslashes($question->code)); - $record->seed = $question->seed; - $record->trials = $question->trials; - $result = update_record("question_webwork", $record); - if (!$result) { - $result->error = "Could not update quiz webwork options! (id=$record->id)"; - return $result; - } - + $isupdate = true; } else { + $isupdate = false; unset($record); - $record->question = $question->id; - $record->code = base64_encode(stripslashes($question->code)); - $record->seed = $question->seed; - $record->trials = $question->trials; - $result = insert_record("question_webwork", $record); - if (!$result) { - $result->error = "Could not insert quiz webwork options!"; - return $result; + } + //set new variables for update or insert + $record->question = $question->id; + $record->codecheck = $question->codecheck; + $record->code = base64_encode(stripslashes($question->code)); + $record->seed = $question->seed; + $record->trials = $question->trials; + + //create the derived questions and check for errors + //$results = $question->derivedquestions; + + //do the database action on question_webwork + if($isupdate) { + $errresult = update_record("question_webwork", $record); + if (!$errresult) { + $errresult->error = "Could not update quiz webwork options! (id=$record->id)"; + return $errresult; + } + } else { + $errresult = insert_record("question_webwork", $record); + if (!$errresult) { + $errresult->error = "Could not insert quiz webwork options!"; + return $errresult; } - $record->id = $result; + $record->id = $errresult; } - return $this->create_derived_questions($record); + //delete the derived questions + $this->delete_derived_questions($record->id); + + //do the database action on question_webwork_derived + $err = $this->insert_derived_questions($record->id,$this->_derivedquestions()); + if($err != 0) { + return $err; + } + return true; } - function delete_derived_questions($questionid) { - delete_records("question_webwork_derived", "question_webwork", $questionid); + + /** + * @desc Deletes all derived questions that are children of the ID passed in. + * @param $webworkquestionid integer The ID of the parent question + */ + function delete_derived_questions($webworkquestionid) { + delete_records("question_webwork_derived", "question_webwork", $webworkquestionid); return true; } - function create_derived_questions($question) { - $code = $question->code; - $seed = $question->seed; - $trials = $question->trials; - $parentid = $question->id; + + /** + * @desc Gets derived questions from a webworkquestion record object by calling the SOAP object. + * @param $webworkquestion The record to create from + * + */ + function get_derived_questions($webworkquestion) { + //parameters needed from the webworkquestion object + $code = $webworkquestion->code; + $seed = $webworkquestion->seed; + $trials = $webworkquestion->trials; + + + //problem to be generated + $problem = array(); + $problem['code'] = $code; + $problem['seed'] = $seed; + + //requested # times for generation + $request = array(); + $request['trials'] = $trials; + $request['problem'] = $problem; - $params = array('code' => $code, 'seed' => $seed, 'trials' => $trials); + //SOAP CALL + $params = array($request); $client = new problemserver_client(); - $response = $client->handler('generateProblems',$params); + $response = $client->handler('generateProblem',$params); + return $response; + } + + + /** + * @desc Inserts the derived questions into the DB. + * @param $parentid The parent ID of the derived questions. + * @param $derivedrecordset The recordset to create from. + */ + function insert_derived_questions($parentid,$derivedrecordset) { - $record->question_webwork = $parentid; - foreach($response as $problem) { - $record->html = $problem['html']; + foreach($derivedrecordset as $problem) { + unset($record); + //set the parent id for the derived questions + $record->question_webwork = $parentid; + $record->html = $problem['output']; $record->seed = $problem['seed']; + //initial insert $result = insert_record("question_webwork_derived",$record); if (!$result) { $result->error = "Could not insert quiz webwork derived options!"; return $result; } + $record->id = $result; + + //brings image files to local drive + //THIS SHOULD ALSO DO APPLETS SOON + $err = $this->copy_derived_question_data($record); + if($err != 0) { + return $err; + } + + $result = update_record("question_webwork_derived",$record); + if(!$result) { + $result->error = "Could not update quiz webwork derived options! (id=$record->id)"; + return $result; + } + } - return true; + return false; } + + function copy_derived_question_data(&$derivedrecord) { + global $CFG; + //make the base directory if needed + $dir = $CFG->dataroot . '/wwquestions'; + mkdir($dir); + //make the directory for this question + $dir = $CFG->dataroot . '/wwquestions/'.$derivedrecord->id; + mkdir($dir); + + //first we need to find the image paths + $imagestocopy = array(); + $problemhtml = ""; + $unparsedhtml = base64_decode($derivedrecord->html); + $parser = new HtmlParser($unparsedhtml); + while($parser->parse()) { + if ($parser->iNodeType == NODE_TYPE_ELEMENT) { + $nodename = $parser->iNodeName; + //rewrite the images + if(($nodename == "IMG") || ($nodename == "img")) { + //found one + $srcpath = $parser->iNodeAttributes['src']; + $srcfilename = strrchr($srcpath,'/'); + $parser->iNodeAttributes['src'] = $CFG->wwwroot . "/question/type/webwork/file.php/wwquestions/" . $derivedrecord->id . '/' . $srcfilename; + //NOTE explore the possibility of having an existence check here, filenames hashed? + //copy it + $err = copy($srcpath,$CFG->dataroot.'/wwquestions/'.$derivedrecord->id.$srcfilename); + if($err == false) { + $err->error = 'Copy Failed for: '.$srcpath; + return $err; + } + + + } + } + $problemhtml .= $parser->printTag(); + } + $html = base64_encode($problemhtml); + $derivedrecord->html = $html; + return false; + } + /** * @desc Deletes question from the question_webwork table @@ -116,7 +232,12 @@ * @return boolean to indicate success of failure. */ function delete_question($questionid) { - $this->delete_derived_questions($questionid); + //Deleting the webwork derived questions + $records = get_records('question_webwork','question',$questionid,'','id'); + foreach($records as $record) { + $this->delete_derived_questions($record->id); + } + //Deleting the webwork questions delete_records("question_webwork", "question", $questionid); return true; } @@ -124,9 +245,17 @@ /** * @desc Creates an empty response before student answers a question. This contains the possibly randomized seed for that particular student. Sticky seeds are created here. */ - function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) { + function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) { + global $CFG,$USER; + + //directory housekeeping (insure directories are setup) + mkdir($CFG->dataroot.'/wwquestions/users'); + mkdir($CFG->dataroot.'/wwquestions/users/'.$USER->id); + + + //here we get the derived results for this question - $results = get_records("question_webwork_derived","question_webwork",$question->id,'','id'); + $results = get_records("question_webwork_derived","question_webwork",$question->webworkid,'','id'); if(!$results) { print_error(get_string('error_db_webwork_derived','qtype_webwork')); return false; @@ -136,13 +265,17 @@ print_error(get_string('error_no_webwork_derived','qtype_webwork')); return false; } + //pick a random question based on time srand(time()); $random = rand(0,count($results)-1); $values = array_values($results); $derivedid = $values[$random]->id; + //more directory housekeeping + mkdir($CFG->dataroot.'/wwquestions/users/'.$USER->id.'/'.$derivedid); + //get the actual data - $results = get_record('question_webwork_derived',"id",$derivedid); + $results = get_record('question_webwork_derived','id',$derivedid); $state->responses['seed'] = $results->seed; $state->responses['derivedid'] = $derivedid; @@ -190,7 +323,7 @@ * @desc Prints the question. Calls the Webwork Server for appropriate HTML output and image paths. */ function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) { - global $CFG; + global $CFG,$USER; $readonly = empty($options->readonly) ? '' : 'disabled="disabled"'; //Formulate question image and text $questiontext = $this->format_text($question->questiontext, @@ -243,6 +376,8 @@ $qid = $question->id; $seed = $state->responses['seed']; + //if the student has answered + include("$CFG->dirroot/question/type/webwork/display.html"); } @@ -250,22 +385,33 @@ * @desc Assigns a grade for a student response. Currently a percentage right/total questions. Calls the Webwork Server to evaluate answers */ function grade_responses(&$question, &$state, $cmoptions) { - // TODO assign a grade to the response in state. - //get code - //echo "GRADE"; - //var_dump($state); + global $CFG,$USER; + //get code and seed of the students problem $code = base64_encode($question->code); $seed = $state->responses['seed']; - //get answers + $derivedid = $state->responses['derivedid']; + + //get answers & build answer request $answerarray = array(); foreach($state->responses as $key => $value) { array_push($answerarray, array('field' => $key, 'answer'=> $value)); } - $params = array('code'=>$code, 'seed'=>$seed, 'answers'=>$answerarray); + //build problem request + $problem = array(); + $problem['code'] = $code; + $problem['seed'] = $seed; + + //SOAP request + $params = array(); + $params['request'] = $problem; + $params['answers'] = $answerarray; + + //SOAP Call $client = new problemserver_client(); $response = $client->handler('checkAnswers',$params); + //process output from soap & calculate score $answers = $response; $state->raw_grade = 0; $total = 0; @@ -284,11 +430,33 @@ // mark the state as graded $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE; + //put the responses into the state to remember $state->responses['answers'] = array(); foreach ($answers as $answer) { + //parse and change the preview paths + $unparsedhtml = base64_decode($answer['preview']); + $ansparser = new HtmlParser($unparsedhtml); + $parsedhtml = ""; + while($ansparser->parse()) { + if($ansparser->iNodeType == NODE_TYPE_ELEMENT) { + $nodename = $ansparser->iNodeName; + if(($nodename == 'img') || ($nodename == 'IMG')) { + $srcpath = $ansparser->iNodeAttributes['src']; + $srcfilename = strrchr($srcpath,'/'); + $newpath = "/wwquestions/users/" . $USER->id . '/' . $derivedid . '' . $srcfilename; + $ansparser->iNodeAttributes['src'] = $CFG->wwwroot . "/question/type/webwork/file.php$newpath"; + //copy it + $err = copy($srcpath,$CFG->dataroot . $newpath); + if($err == false) { + print_error("copy operation failed src:'$srcpath' dest:'$newpath'"); + } + } + } + $parsedhtml .= $ansparser->printTag(); + $answer['preview'] = $parsedhtml; + } $state->responses['answers'][$answer['field']] = $answer; } - return true; //var_dump($state); } @@ -314,19 +482,31 @@ * @desc Gets the correct answers from the server for the seed in state. Places them into the state->responses array. */ function get_correct_responses(&$question, &$state) { - + //get code and seed of response $code = base64_encode($question->code); - $seed = $state->responses['seed']; - echo $seed; - //tricks checkAnswer into believing we are sending an anwer - $answerarray = array(array('field'=>'','answer'=>'')); - + //get empty answers & build answer request + $answerarray = array(); + foreach($state->responses as $key => $value) { + array_push($answerarray, array('field' => $key, 'answer'=> $value)); + } + + //build problem request + $problem = array(); + $problem['code'] = $code; + $problem['seed'] = $seed; + + //SOAP request + $params = array(); + $params['request'] = $problem; + $params['answers'] = $answerarray; - $params = array('code'=>$code, 'seed'=>$seed, 'answers'=>$answerarray); + //SOAP Call $client = new problemserver_client(); $response = $client->handler('checkAnswers',$params); + + //process correct answers into fields $answers = $response; $ret = array(); $ret['answers'] = array(); @@ -334,6 +514,7 @@ $ret['answers'][$answer['field']] = $answer; $ret['answers'][$answer['field']]['answer'] = $answer['correct']; } + //push the seed onto the answer array, keep track of what seed these are for. $ret['seed'] = $state->responses['seed']; $ret['derivedid'] = $state->responses['derivedid']; Index: edit_webwork_form.php =================================================================== RCS file: /webwork/cvs/system/wwmoodle/wwquestion/edit_webwork_form.php,v retrieving revision 1.3 retrieving revision 1.4 diff -Lwwquestion/edit_webwork_form.php -Lwwquestion/edit_webwork_form.php -u -r1.3 -r1.4 --- wwquestion/edit_webwork_form.php +++ wwquestion/edit_webwork_form.php @@ -9,6 +9,7 @@ *//** */ require_once($CFG->dirroot.'/question/type/edit_question_form.php'); +require_once($CFG->dirroot.'/question/type/webwork/questiontype.php'); /** * webwork editing form definition. @@ -22,6 +23,16 @@ //HEADER $mform->addElement('header', 'generalheader', get_string("edit_header", 'qtype_webwork')); + //CODECHECK + $codecheckoptions = array( + 0 => get_string('edit_codecheck0','qtype_webwork'), + 1 => get_string('edit_codecheck1','qtype_webwork'), + 2 => get_string('edit_codecheck2','qtype_webwork')); + $mform->addElement('select','codecheck', get_string('edit_codecheck', 'qtype_webwork'),$codecheckoptions); + $mform->setType('codecheck',PARAM_INT); + $mform->setHelpButton('codecheck', array('webwork', get_string('edit_codecheck', 'qtype_webwork'), 'webwork')); + $mform->setDefault('codecheck',2); + //CODE $mform->addElement('textarea', 'code', get_string('edit_code', 'qtype_webwork'), array('rows' => 10,'cols' => 60)); @@ -46,17 +57,99 @@ $mform->addRule('trials', null, 'required', null, 'client'); } - function set_data($question) { + function set_data($question) { parent::set_data($question); } - + function validation($data) { - $errors = array(); - if ($errors) { - return $errors; - } else { - return true; + //check that the code is valid + $err = $this->codecheck($data); + if($err != false) { + return $err; + } + } + + function codecheck($data) { + //codechecklevel + $codechecklevel = $data['codecheck']; + //here we construct a temp question object + $question = new stdClass; + $question->code = base64_encode(stripslashes($data['code'])); + $question->seed = $data['seed']; + $question->trials = $data['trials']; + + //one call to the server will return response for this code and keep it static in the function + $results = webwork_qtype::get_derived_questions($question); + //no code check + if($codechecklevel == 0) { + webwork_qtype::_derivedquestions($results); + return false; + } + + //init error array + $errorresults = array(); + $goodresults = array(); + + //see if we got errors (split) + foreach($results as $record) { + if((isset($record['errors'])) && ($record['errors'] != '') && ($record['errors'] != null)) { + array_push($errorresults,$record); + } else { + array_push($goodresults,$record); + } + } + + //if there are good seeds we use those + if((count($goodresults) > 0) && ($codechecklevel == 1)) { + webwork_qtype::_derivedquestions($goodresults); + return false; + } + + //if code check is strict + if(count($goodresults) == count($results)) { + webwork_qtype::_derivedquestions($results); + return false; + } + + $errormsgs = array(); + //at this point we are going to be invalid + + //this correlates seeds with certain error messages for better output + + foreach($errorresults as $record) { + $found = 0; + $candidate = $record['errors']; + $candidateseed = $record['seed']; + for($i=0;$i<count($errormsgs);$i++) { + if($candidate == $errormsgs[$i]['errors']) { + $found = 1; + $errormsgs[$i]['seeds'][] = $candidateseed; + } + } + if($found == 0) { + //new error message + $msg = array(); + $msg['errors'] = $candidate; + $msg['seeds'] = array(); + $msg['seeds'][] = $candidateseed; + $errormsgs[] = $msg; + } + } + $output = "Errors in PG Code on: " . count($errorresults) . " out of " . count($results) . " seeds tried:<br>"; + //construct error statement + $counter = 1; + foreach($errormsgs as $msg) { + $output .= "$counter) "; + $output .= "Seeds ("; + foreach ($msg['seeds'] as $seed) { + $output .= $seed . " "; + } + $output .= ") gave Error:" . $msg['errors'] . "<br><br>"; + $counter++; } + $returner =array(); + $returner['code'] = $output; + return $returner; } function qtype() { Index: display.html =================================================================== RCS file: /webwork/cvs/system/wwmoodle/wwquestion/display.html,v retrieving revision 1.5 retrieving revision 1.6 diff -Lwwquestion/display.html -Lwwquestion/display.html -u -r1.5 -r1.6 --- wwquestion/display.html +++ wwquestion/display.html @@ -39,7 +39,7 @@ <?php echo $answerobj['answer']; ?> </td> <td class="c0" style="width: 15%;"> - <?php echo base64_decode($answerobj['preview']); ?> + <?php echo $answerobj['preview']; ?> </td> <td class="c0" style="width: 15%;"> <?php echo $answerobj['evaluated']; ?> Index: install.xml =================================================================== RCS file: /webwork/cvs/system/wwmoodle/wwquestion/db/install.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -Lwwquestion/db/install.xml -Lwwquestion/db/install.xml -u -r1.2 -r1.3 --- wwquestion/db/install.xml +++ wwquestion/db/install.xml @@ -4,8 +4,9 @@ <TABLE NAME="question_webwork" COMMENT="Options for webwork questions" NEXT="question_webwork_derived"> <FIELDS> <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="question"/> - <FIELD NAME="question" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="code"/> - <FIELD NAME="code" TYPE="text" LENGTH="medium" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="question" NEXT="seed"/> + <FIELD NAME="question" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="codecheck"/> + <FIELD NAME="codecheck" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="question" NEXT="code"/> + <FIELD NAME="code" TYPE="text" LENGTH="medium" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="codecheck" NEXT="seed"/> <FIELD NAME="seed" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" ENUM="false" PREVIOUS="code" NEXT="trials"/> <FIELD NAME="trials" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" ENUM="false" PREVIOUS="seed"/> </FIELDS> Index: qtype_webwork.php =================================================================== RCS file: /webwork/cvs/system/wwmoodle/wwquestion/lang/en_utf8/qtype_webwork.php,v retrieving revision 1.2 retrieving revision 1.3 diff -Lwwquestion/lang/en_utf8/qtype_webwork.php -Lwwquestion/lang/en_utf8/qtype_webwork.php -u -r1.2 -r1.3 --- wwquestion/lang/en_utf8/qtype_webwork.php +++ wwquestion/lang/en_utf8/qtype_webwork.php @@ -8,13 +8,17 @@ * @package YOURPACKAGENAME *//** */ -$string['editingwebwork'] = 'Editing WeBWorK.'; -$string['webwork'] = 'WeBWorK.'; +$string['editingwebwork'] = 'Editing WeBWorK'; +$string['webwork'] = 'WeBWorK'; $string['error_client_construction'] = 'Error constructing client to Problem Server. Perhaps your WSDL Path is wrong?'; $string['error_client_call'] = 'Error in communication with the Problem Server.'; $string['edit_header'] = 'WeBWorK'; +$string['edit_codecheck'] = 'Code Checking'; + $string['edit_codecheck0'] = 'Turn Off (unadvised)'; + $string['edit_codecheck1'] = 'Reject Problem Seeds w/ Errors (silent)'; + $string['edit_codecheck2'] = 'Reject Question if any Errors (strict)'; $string['edit_code'] = 'Code'; $string['edit_seed'] = 'Seed'; $string['edit_trials'] = 'Trials'; |