2 //This php script contains all the stuff to restore questions
5 // the restoration of the parent and sortorder fields in the category table needs
6 // a lot more thought. We should probably use a library function to add the category
7 // rather than just writing it to the database
9 // whereever it says "/// We have to recode the .... field" we should put in a check
10 // to see if the recoding was successful and throw an appropriate error otherwise
12 //This is the "graphical" structure of the question database:
13 //To see, put your terminal to 160cc
15 // The following holds student-independent information about the questions
17 // question_categories
21 // |.......................................
24 // | -------question_datasets------ .
25 // | | (CL,pk->id,fk->question, | .
26 // | | fk->dataset_definition) | .
30 // | | question_dataset_definitions
31 // | | (CL,pk->id,fk->category)
33 // (CL,pk->id,fk->category,files) |
34 // | question_dataset_items
35 // | (CL,pk->id,fk->definition)
36 // | question_rqp_type
39 // -------------------------------------------------------------------------------------------------------------- |
40 // | | | | | | | question_rqp
41 // | | | | | | |--(CL,pk->id,fk->question)
42 // | | | | question_calculated | |
43 // question_truefalse | question_multichoice | (CL,pl->id,fk->question) | |
44 // (CL,pk->id,fk->question) | (CL,pk->id,fk->question) | . | | question_randomsamatch
45 // . | . | . | |--(CL,pk->id,fk->question)
46 // . question_shortanswer . question_numerical . question_multianswer. |
47 // . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) |
48 // . . . . . . | question_match
49 // . . . . . . |--(CL,pk->id,fk->question)
53 // . . . . . . | question_match_sub
54 // ........................................................................................ |--(CL,pk->id,fk->question)
57 // . | question_numerical_units
58 // question_answers |--(CL,pk->id,fk->question)
59 // (CL,pk->id,fk->question)----------------------------------------------------------
62 // The following holds the information about student interaction with the questions
65 // (UL,pk->id,fk->attempt,question)
69 // (UL,pk->id,fk->attempt,question)
71 // question_rqp_states
72 // (UL,pk->id,fk->stateid)
74 // Meaning: pk->primary key field of the table
75 // fk->foreign key to link with parent
76 // nt->nested field (recursive data)
77 // SL->site level info
78 // CL->course level info
79 // UL->user level info
80 // files->table may have files
82 //-----------------------------------------------------------
84 include_once($CFG->libdir
.'/questionlib.php');
86 function restore_question_categories($category,$restore) {
92 //Hook to call Moodle < 1.5 Quiz Restore
93 if ($restore->backup_version
< 2005043000) {
94 include_once($CFG->dirroot
.'/mod/quiz/restorelibpre15.php');
95 return quiz_restore_pre15_question_categories($category,$restore);
98 //Get record from backup_ids
99 $data = backup_getid($restore->backup_unique_code
,"question_categories",$category->id
);
102 //Now get completed xmlized object
104 //traverse_xmlize($info); //Debug
105 //print_object ($GLOBALS['traverse_array']); //Debug
106 //$GLOBALS['traverse_array']=""; //Debug
108 //Now, build the question_categories record structure
109 $question_cat = new stdClass
;
110 $question_cat->course
= $restore->course_id
;
111 $question_cat->name
= backup_todb($info['QUESTION_CATEGORY']['#']['NAME']['0']['#']);
112 $question_cat->info
= backup_todb($info['QUESTION_CATEGORY']['#']['INFO']['0']['#']);
113 $question_cat->publish
= backup_todb($info['QUESTION_CATEGORY']['#']['PUBLISH']['0']['#']);
114 $question_cat->stamp
= backup_todb($info['QUESTION_CATEGORY']['#']['STAMP']['0']['#']);
115 $question_cat->parent
= backup_todb($info['QUESTION_CATEGORY']['#']['PARENT']['0']['#']);
116 $question_cat->sortorder
= backup_todb($info['QUESTION_CATEGORY']['#']['SORTORDER']['0']['#']);
118 if ($catfound = restore_get_best_question_category($question_cat, $restore->course_id
)) {
121 if (!$question_cat->stamp
) {
122 $question_cat->stamp
= make_unique_id_code();
124 $newid = insert_record ("question_categories",$question_cat);
129 if (!defined('RESTORE_SILENTLY')) {
130 echo "<li>".get_string('category', 'quiz')." \"".$question_cat->name
."\"<br />";
133 //We must never arrive here !!
134 if (!defined('RESTORE_SILENTLY')) {
135 echo "<li>".get_string('category', 'quiz')." \"".$question_cat->name
."\" Error!<br />";
141 //Here category has been created or selected, so save results in backup_ids and start with questions
142 if ($newid and $status) {
143 //We have the newid, update backup_ids
144 backup_putid($restore->backup_unique_code
,"question_categories",
145 $category->id
, $newid);
146 //Now restore question
147 $status = restore_questions ($category->id
, $newid,$info,$restore);
151 if (!defined('RESTORE_SILENTLY')) {
155 echo 'Could not get backup info for question category'. $category->id
;
161 function restore_questions ($old_category_id,$new_category_id,$info,$restore) {
163 global $CFG, $QTYPES;
166 $restored_questions = array();
168 //Get the questions array
169 if (!empty($info['QUESTION_CATEGORY']['#']['QUESTIONS'])) {
170 $questions = $info['QUESTION_CATEGORY']['#']['QUESTIONS']['0']['#']['QUESTION'];
172 $questions = array();
175 //Iterate over questions
176 for($i = 0; $i < sizeof($questions); $i++
) {
177 $que_info = $questions[$i];
178 //traverse_xmlize($que_info); //Debug
179 //print_object ($GLOBALS['traverse_array']); //Debug
180 //$GLOBALS['traverse_array']=""; //Debug
182 //We'll need this later!!
183 $oldid = backup_todb($que_info['#']['ID']['0']['#']);
185 //Now, build the question record structure
186 $question = new object;
187 $question->category
= $new_category_id;
188 $question->parent
= backup_todb($que_info['#']['PARENT']['0']['#']);
189 $question->name
= backup_todb($que_info['#']['NAME']['0']['#']);
190 $question->questiontext
= backup_todb($que_info['#']['QUESTIONTEXT']['0']['#']);
191 $question->questiontextformat
= backup_todb($que_info['#']['QUESTIONTEXTFORMAT']['0']['#']);
192 $question->image
= backup_todb($que_info['#']['IMAGE']['0']['#']);
193 $question->defaultgrade
= backup_todb($que_info['#']['DEFAULTGRADE']['0']['#']);
194 $question->penalty
= backup_todb($que_info['#']['PENALTY']['0']['#']);
195 $question->qtype
= backup_todb($que_info['#']['QTYPE']['0']['#']);
196 $question->length
= backup_todb($que_info['#']['LENGTH']['0']['#']);
197 $question->stamp
= backup_todb($que_info['#']['STAMP']['0']['#']);
198 $question->version
= backup_todb($que_info['#']['VERSION']['0']['#']);
199 $question->hidden
= backup_todb($que_info['#']['HIDDEN']['0']['#']);
201 if ($restore->backup_version
< 2006032200) {
202 // The qtype was an integer that now needs to be converted to the name
203 $qtypenames = array(1=>'shortanswer',2=>'truefalse',3=>'multichoice',4=>'random',5=>'match',
204 6=>'randomsamatch',7=>'description',8=>'numerical',9=>'multianswer',10=>'calculated',
205 11=>'rqp',12=>'essay');
206 $question->qtype
= $qtypenames[$question->qtype
];
209 //Check if the question exists
210 //by category, stamp, and version
211 $question_exists = get_record ("question","category",$question->category
,
212 "stamp",$question->stamp
,"version",$question->version
);
214 //If the question exists, only record its id
215 if ($question_exists) {
216 $newid = $question_exists->id
;
217 $creatingnewquestion = false;
218 //Else, create a new question
220 //The structure is equal to the db, so insert the question
221 $newid = insert_record ("question",$question);
222 $creatingnewquestion = true;
225 //Save newid to backup tables
227 //We have the newid, update backup_ids
228 backup_putid($restore->backup_unique_code
,"question",$oldid,
232 $restored_questions[$i] = new stdClass
;
233 $restored_questions[$i]->newid
= $newid;
234 $restored_questions[$i]->oldid
= $oldid;
235 $restored_questions[$i]->qtype
= $question->qtype
;
236 $restored_questions[$i]->parent
= $question->parent
;
237 $restored_questions[$i]->is_new
= $creatingnewquestion;
240 // Loop again, now all the question id mappings exist, so everything can
242 for($i = 0; $i < sizeof($questions); $i++
) {
243 $que_info = $questions[$i];
245 $newid = $restored_questions[$i]->newid
;
246 $oldid = $restored_questions[$i]->oldid
;
248 $question = new object;
249 $question->qtype
= $restored_questions[$i]->qtype
;
250 $question->parent
= $restored_questions[$i]->parent
;
253 //If it's a new question in the DB, restore it
254 if ($restored_questions[$i]->is_new
) {
256 ////We have to recode the parent field
257 if ($question->parent
) {
258 if ($parent = backup_getid($restore->backup_unique_code
,"question",$question->parent
)) {
259 $question->parent
= $parent->new_id
;
260 } elseif ($question->parent
= $oldid) {
261 $question->parent
= $newid;
263 echo 'Could not recode parent '.$question->parent
.' for question '.$oldid.'<br />';
267 //Now, restore every question_answers in this question
268 $status = question_restore_answers($oldid,$newid,$que_info,$restore);
269 // Restore questiontype specific data
270 if (array_key_exists($question->qtype
, $QTYPES)) {
271 $status = $QTYPES[$question->qtype
]->restore($oldid,$newid,$que_info,$restore);
273 echo 'Unknown question type '.$question->qtype
.' for question '.$oldid.'<br />';
277 //We are NOT creating the question, but we need to know every question_answers
278 //map between the XML file and the database to be able to restore the states
280 $status = question_restore_map_answers($oldid,$newid,$que_info,$restore);
281 // Do the questiontype specific mapping
282 if (array_key_exists($question->qtype
, $QTYPES)) {
283 $status = $QTYPES[$question->qtype
]->restore_map($oldid,$newid,$que_info,$restore);
285 echo 'Unknown question type '.$question->qtype
.' for question '.$oldid.'<br />';
291 if (($i+
1) %
2 == 0) {
292 if (!defined('RESTORE_SILENTLY')) {
294 if (($i+
1) %
40 == 0) {
304 function question_restore_answers ($old_question_id,$new_question_id,$info,$restore) {
310 //Get the answers array
311 if (isset($info['#']['ANSWERS']['0']['#']['ANSWER'])) {
312 $answers = $info['#']['ANSWERS']['0']['#']['ANSWER'];
314 //Iterate over answers
315 for($i = 0; $i < sizeof($answers); $i++
) {
316 $ans_info = $answers[$i];
317 //traverse_xmlize($ans_info); //Debug
318 //print_object ($GLOBALS['traverse_array']); //Debug
319 //$GLOBALS['traverse_array']=""; //Debug
321 //We'll need this later!!
322 $oldid = backup_todb($ans_info['#']['ID']['0']['#']);
324 //Now, build the question_answers record structure
325 $answer = new stdClass
;
326 $answer->question
= $new_question_id;
327 $answer->answer
= backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']);
328 $answer->fraction
= backup_todb($ans_info['#']['FRACTION']['0']['#']);
329 $answer->feedback
= backup_todb($ans_info['#']['FEEDBACK']['0']['#']);
331 //The structure is equal to the db, so insert the question_answers
332 $newid = insert_record ("question_answers",$answer);
335 if (($i+
1) %
50 == 0) {
336 if (!defined('RESTORE_SILENTLY')) {
338 if (($i+
1) %
1000 == 0) {
346 //We have the newid, update backup_ids
347 backup_putid($restore->backup_unique_code
,"question_answers",$oldid,
358 function question_restore_map_answers ($old_question_id,$new_question_id,$info,$restore) {
364 if (!isset($info['#']['ANSWERS'])) { // No answers in this question (eg random)
368 //Get the answers array
369 $answers = $info['#']['ANSWERS']['0']['#']['ANSWER'];
371 //Iterate over answers
372 for($i = 0; $i < sizeof($answers); $i++
) {
373 $ans_info = $answers[$i];
374 //traverse_xmlize($ans_info); //Debug
375 //print_object ($GLOBALS['traverse_array']); //Debug
376 //$GLOBALS['traverse_array']=""; //Debug
378 //We'll need this later!!
379 $oldid = backup_todb($ans_info['#']['ID']['0']['#']);
381 //Now, build the question_answers record structure
382 $answer->question
= $new_question_id;
383 $answer->answer
= backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']);
384 $answer->fraction
= backup_todb($ans_info['#']['FRACTION']['0']['#']);
385 $answer->feedback
= backup_todb($ans_info['#']['FEEDBACK']['0']['#']);
387 //If we are in this method is because the question exists in DB, so its
388 //answers must exist too.
389 //Now, we are going to look for that answer in DB and to create the
390 //mappings in backup_ids to use them later where restoring states (user level).
392 //Get the answer from DB (by question and answer)
393 $db_answer = get_record ("question_answers","question",$new_question_id,
394 "answer",$answer->answer
);
397 if (($i+
1) %
50 == 0) {
398 if (!defined('RESTORE_SILENTLY')) {
400 if (($i+
1) %
1000 == 0) {
408 //We have the database answer, update backup_ids
409 backup_putid($restore->backup_unique_code
,"question_answers",$oldid,
419 function question_restore_numerical_units ($old_question_id,$new_question_id,$info,$restore) {
425 //Get the numerical array
426 $numerical_units = $info['#']['NUMERICAL_UNITS']['0']['#']['NUMERICAL_UNIT'];
428 //Iterate over numerical_units
429 for($i = 0; $i < sizeof($numerical_units); $i++
) {
430 $nu_info = $numerical_units[$i];
431 //traverse_xmlize($nu_info); //Debug
432 //print_object ($GLOBALS['traverse_array']); //Debug
433 //$GLOBALS['traverse_array']=""; //Debug
435 //Now, build the question_numerical_UNITS record structure
436 $numerical_unit = new stdClass
;
437 $numerical_unit->question
= $new_question_id;
438 $numerical_unit->multiplier
= backup_todb($nu_info['#']['MULTIPLIER']['0']['#']);
439 $numerical_unit->unit
= backup_todb($nu_info['#']['UNIT']['0']['#']);
441 //The structure is equal to the db, so insert the question_numerical_units
442 $newid = insert_record ("question_numerical_units",$numerical_unit);
452 function question_restore_dataset_definitions ($old_question_id,$new_question_id,$info,$restore) {
458 //Get the dataset_definitions array
459 $dataset_definitions = $info['#']['DATASET_DEFINITIONS']['0']['#']['DATASET_DEFINITION'];
461 //Iterate over dataset_definitions
462 for($i = 0; $i < sizeof($dataset_definitions); $i++
) {
463 $dd_info = $dataset_definitions[$i];
464 //traverse_xmlize($dd_info); //Debug
465 //print_object ($GLOBALS['traverse_array']); //Debug
466 //$GLOBALS['traverse_array']=""; //Debug
468 //Now, build the question_dataset_DEFINITION record structure
469 $dataset_definition = new stdClass
;
470 $dataset_definition->category
= backup_todb($dd_info['#']['CATEGORY']['0']['#']);
471 $dataset_definition->name
= backup_todb($dd_info['#']['NAME']['0']['#']);
472 $dataset_definition->type
= backup_todb($dd_info['#']['TYPE']['0']['#']);
473 $dataset_definition->options
= backup_todb($dd_info['#']['OPTIONS']['0']['#']);
474 $dataset_definition->itemcount
= backup_todb($dd_info['#']['ITEMCOUNT']['0']['#']);
476 //We have to recode the category field (only if the category != 0)
477 if ($dataset_definition->category
!= 0) {
478 $category = backup_getid($restore->backup_unique_code
,"question_categories",$dataset_definition->category
);
480 $dataset_definition->category
= $category->new_id
;
482 echo 'Could not recode category id '.$dataset_definition->category
.' for dataset definition'.$dataset_definition->name
.'<br />';
486 //Now, we hace to decide when to create the new records or reuse an existing one
487 $create_definition = false;
489 //If the dataset_definition->category = 0, it's a individual question dataset_definition, so we'll create it
490 if ($dataset_definition->category
== 0) {
491 $create_definition = true;
493 //The category isn't 0, so it's a category question dataset_definition, we have to see if it exists
494 //Look for a definition with the same category, name and type
495 if ($definitionrec = get_record_sql("SELECT d.*
496 FROM {$CFG->prefix}question_dataset_definitions d
497 WHERE d.category = '$dataset_definition->category' AND
498 d.name = '$dataset_definition->name' AND
499 d.type = '$dataset_definition->type'")) {
500 //Such dataset_definition exist. Now we must check if it has enough itemcount
501 if ($definitionrec->itemcount
< $dataset_definition->itemcount
) {
502 //We haven't enough itemcount, so we have to create the definition as an individual question one.
503 $dataset_definition->category
= 0;
504 $create_definition = true;
506 //We have enough itemcount, so we'll reuse the existing definition
507 $create_definition = false;
508 $newid = $definitionrec->id
;
511 //Such dataset_definition doesn't exist. We'll create it.
512 $create_definition = true;
516 //If we've to create the definition, do it
517 if ($create_definition) {
518 //The structure is equal to the db, so insert the question_dataset_definitions
519 $newid = insert_record ("question_dataset_definitions",$dataset_definition);
521 //Restore question_dataset_items
522 $status = question_restore_dataset_items($newid,$dd_info,$restore);
526 //Now, we must have a definition (created o reused). Its id is in newid. Create the question_datasets record
527 //to join the question and the dataset_definition
529 $question_dataset = new stdClass
;
530 $question_dataset->question
= $new_question_id;
531 $question_dataset->datasetdefinition
= $newid;
532 $newid = insert_record ("question_datasets",$question_dataset);
543 function question_restore_dataset_items ($definitionid,$info,$restore) {
549 //Get the items array
550 $dataset_items = $info['#']['DATASET_ITEMS']['0']['#']['DATASET_ITEM'];
552 //Iterate over dataset_items
553 for($i = 0; $i < sizeof($dataset_items); $i++
) {
554 $di_info = $dataset_items[$i];
555 //traverse_xmlize($di_info); //Debug
556 //print_object ($GLOBALS['traverse_array']); //Debug
557 //$GLOBALS['traverse_array']=""; //Debug
559 //Now, build the question_dataset_ITEMS record structure
560 $dataset_item = new stdClass
;
561 $dataset_item->definition
= $definitionid;
562 $dataset_item->number
= backup_todb($di_info['#']['NUMBER']['0']['#']);
563 $dataset_item->value
= backup_todb($di_info['#']['VALUE']['0']['#']);
565 //The structure is equal to the db, so insert the question_dataset_items
566 $newid = insert_record ("question_dataset_items",$dataset_item);
577 //This function restores the question_states
578 function question_states_restore_mods($attempt_id,$info,$restore) {
580 global $CFG, $QTYPES;
584 //Get the question_states array
585 $states = $info['#']['STATES']['0']['#']['STATE'];
586 //Iterate over states
587 for($i = 0; $i < sizeof($states); $i++
) {
588 $res_info = $states[$i];
589 //traverse_xmlize($res_info); //Debug
590 //print_object ($GLOBALS['traverse_array']); //Debug
591 //$GLOBALS['traverse_array']=""; //Debug
593 //We'll need this later!!
594 $oldid = backup_todb($res_info['#']['ID']['0']['#']);
596 //Now, build the STATES record structure
597 $state = new stdClass
;
598 $state->attempt
= $attempt_id;
599 $state->question
= backup_todb($res_info['#']['QUESTION']['0']['#']);
600 $state->originalquestion
= backup_todb($res_info['#']['ORIGINALQUESTION']['0']['#']);
601 $state->seq_number
= backup_todb($res_info['#']['SEQ_NUMBER']['0']['#']);
602 $state->answer
= backup_todb($res_info['#']['ANSWER']['0']['#']);
603 $state->timestamp
= backup_todb($res_info['#']['TIMESTAMP']['0']['#']);
604 $state->event
= backup_todb($res_info['#']['EVENT']['0']['#']);
605 $state->grade
= backup_todb($res_info['#']['GRADE']['0']['#']);
606 $state->raw_grade
= backup_todb($res_info['#']['RAW_GRADE']['0']['#']);
607 $state->penalty
= backup_todb($res_info['#']['PENALTY']['0']['#']);
609 //We have to recode the question field
610 $question = backup_getid($restore->backup_unique_code
,"question",$state->question
);
612 $state->question
= $question->new_id
;
614 echo 'Could not recode question id '.$state->question
.' for state '.$oldid.'<br />';
617 //We have to recode the originalquestion field if it is nonzero
618 if ($state->originalquestion
) {
619 $question = backup_getid($restore->backup_unique_code
,"question",$state->originalquestion
);
621 $state->originalquestion
= $question->new_id
;
623 echo 'Could not recode originalquestion id '.$state->question
.' for state '.$oldid.'<br />';
627 //We have to recode the answer field
628 //It depends of the question type !!
629 //We get the question first
630 if (!$question = get_record("question","id",$state->question
)) {
631 error("Can't find the record for question $state->question for which I am trying to restore a state");
633 //Depending on the qtype, we make different recodes
634 if ($state->answer
) {
635 $state->answer
= $QTYPES[$question->qtype
]->restore_recode_answer($state, $restore);
638 //The structure is equal to the db, so insert the question_states
639 $newid = insert_record ("question_states",$state);
642 if (($i+
1) %
10 == 0) {
643 if (!defined('RESTORE_SILENTLY')) {
645 if (($i+
1) %
200 == 0) {
653 //We have the newid, update backup_ids
654 backup_putid($restore->backup_unique_code
,"question_states",$oldid,
656 //Now process question type specific state information
657 $qtype = get_field('question', 'qtype', 'id', $state->question
);
658 $status = $QTYPES[$qtype]->restore_state($newid,$res_info,$restore);
664 //Get the question_sessions array
665 $sessions = $info['#']['NEWEST_STATES']['0']['#']['NEWEST_STATE'];
666 //Iterate over question_sessions
667 for($i = 0; $i < sizeof($sessions); $i++
) {
668 $res_info = $sessions[$i];
669 //traverse_xmlize($res_info); //Debug
670 //print_object ($GLOBALS['traverse_array']); //Debug
671 //$GLOBALS['traverse_array']=""; //Debug
673 //Now, build the NEWEST_STATES record structure
674 $session = new stdClass
;
675 $session->attemptid
= $attempt_id;
676 $session->questionid
= backup_todb($res_info['#']['QUESTIONID']['0']['#']);
677 $session->newest
= backup_todb($res_info['#']['NEWEST']['0']['#']);
678 $session->newgraded
= backup_todb($res_info['#']['NEWGRADED']['0']['#']);
679 $session->sumpenalty
= backup_todb($res_info['#']['SUMPENALTY']['0']['#']);
681 //We have to recode the question field
682 $question = backup_getid($restore->backup_unique_code
,"question",$session->questionid
);
684 $session->questionid
= $question->new_id
;
686 echo 'Could not recode question id '.$session->questionid
.'<br />';
689 //We have to recode the newest field
690 $state = backup_getid($restore->backup_unique_code
,"question_states",$session->newest
);
692 $session->newest
= $state->new_id
;
694 echo 'Could not recode newest state id '.$session->newest
.'<br />';
697 //If the session has been graded we have to recode the newgraded field
698 if ($session->newgraded
) {
699 $state = backup_getid($restore->backup_unique_code
,"question_states",$session->newgraded
);
701 $session->newgraded
= $state->new_id
;
703 echo 'Could not recode newest graded state id '.$session->newgraded
.'<br />';
707 //The structure is equal to the db, so insert the question_sessions
708 $newid = insert_record ("question_sessions",$session);