| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php // $Id: restorelib.php,v 1.30.2.5 2008/05/09 15:10:42 tjhunt Exp $ 2 /** 3 * Question bank restore code. 4 * 5 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 6 * @package questionbank 7 *//** */ 8 9 // Todo: 10 // the restoration of the parent and sortorder fields in the category table needs 11 // a lot more thought. We should probably use a library function to add the category 12 // rather than just writing it to the database 13 14 // whereever it says "/// We have to recode the .... field" we should put in a check 15 // to see if the recoding was successful and throw an appropriate error otherwise 16 17 //This is the "graphical" structure of the question database: 18 //To see, put your terminal to 160cc 19 20 // The following holds student-independent information about the questions 21 // 22 // question_categories 23 // (CL,pk->id) 24 // | 25 // | 26 // |....................................... 27 // | . 28 // | . 29 // | -------question_datasets------ . 30 // | | (CL,pk->id,fk->question, | . 31 // | | fk->dataset_definition) | . 32 // | | | . 33 // | | | . 34 // | | | . 35 // | | question_dataset_definitions 36 // | | (CL,pk->id,fk->category) 37 // question | 38 // (CL,pk->id,fk->category,files) | 39 // | question_dataset_items 40 // | (CL,pk->id,fk->definition) 41 // | 42 // | 43 // | 44 // -------------------------------------------------------------------------------------------------------------- 45 // | | | | | | | 46 // | | | | | | | 47 // | | | | question_calculated | | 48 // question_truefalse | question_multichoice | (CL,pl->id,fk->question) | | 49 // (CL,pk->id,fk->question) | (CL,pk->id,fk->question) | . | | question_randomsamatch 50 // . | . | . | |--(CL,pk->id,fk->question) 51 // . question_shortanswer . question_numerical . question_multianswer. | 52 // . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) | 53 // . . . . . . | question_match 54 // . . . . . . |--(CL,pk->id,fk->question) 55 // . . . . . . | . 56 // . . . . . . | . 57 // . . . . . . | . 58 // . . . . . . | question_match_sub 59 // ........................................................................................ |--(CL,pk->id,fk->question) 60 // . | 61 // . | 62 // . | question_numerical_units 63 // question_answers |--(CL,pk->id,fk->question) 64 // (CL,pk->id,fk->question)---------------------------------------------------------- 65 // 66 // 67 // The following holds the information about student interaction with the questions 68 // 69 // question_sessions 70 // (UL,pk->id,fk->attempt,question) 71 // . 72 // . 73 // question_states 74 // (UL,pk->id,fk->attempt,question) 75 // 76 // Meaning: pk->primary key field of the table 77 // fk->foreign key to link with parent 78 // nt->nested field (recursive data) 79 // SL->site level info 80 // CL->course level info 81 // UL->user level info 82 // files->table may have files 83 // 84 //----------------------------------------------------------- 85 86 include_once($CFG->libdir.'/questionlib.php'); 87 88 /** 89 * Returns the best question category (id) found to restore one 90 * question category from a backup file. Works by stamp. 91 * 92 * @param object $restore preferences for restoration 93 * @param array $contextinfo fragment of decoded xml 94 * @return object best context instance for this category to be in 95 */ 96 function restore_question_get_best_category_context($restore, $contextinfo) { 97 switch ($contextinfo['LEVEL'][0]['#']) { 98 case 'module': 99 if (!$instanceinfo = backup_getid($restore->backup_unique_code, 'course_modules', $contextinfo['INSTANCE'][0]['#'])){ 100 //module has not been restored, probably not selected for restore 101 return false; 102 } 103 $tocontext = get_context_instance(CONTEXT_MODULE, $instanceinfo->new_id); 104 break; 105 case 'course': 106 $tocontext = get_context_instance(CONTEXT_COURSE, $restore->course_id); 107 break; 108 case 'coursecategory': 109 //search COURSECATEGORYLEVEL steps up the course cat tree or 110 //to the top of the tree if steps are exhausted. 111 $catno = $contextinfo['COURSECATEGORYLEVEL'][0]['#']; 112 $catid = get_field('course', 'category', 'id', $restore->course_id); 113 while ($catno > 1){ 114 $nextcatid = get_field('course_categories', 'parent', 'id', $catid); 115 if ($nextcatid == 0){ 116 break; 117 } 118 $catid == $nextcatid; 119 $catno--; 120 } 121 $tocontext = get_context_instance(CONTEXT_COURSECAT, $catid); 122 break; 123 case 'system': 124 $tocontext = get_context_instance(CONTEXT_SYSTEM); 125 break; 126 } 127 return $tocontext; 128 } 129 130 function restore_question_categories($info, $restore) { 131 $status = true; 132 //Iterate over each category 133 foreach ($info as $category) { 134 $status = $status && restore_question_category($category, $restore); 135 } 136 $status = $status && restore_recode_category_parents($restore); 137 return $status; 138 } 139 140 function restore_question_category($category, $restore){ 141 $status = true; 142 //Skip empty categories (some backups can contain them) 143 if (!empty($category->id)) { 144 //Get record from backup_ids 145 $data = backup_getid($restore->backup_unique_code, "question_categories", $category->id); 146 147 if ($data) { 148 //Now get completed xmlized object 149 $info = $data->info; 150 //traverse_xmlize($info); //Debug 151 //print_object ($GLOBALS['traverse_array']); //Debug 152 //$GLOBALS['traverse_array']=""; //Debug 153 154 //Now, build the question_categories record structure 155 $question_cat = new stdClass; 156 $question_cat->name = backup_todb($info['QUESTION_CATEGORY']['#']['NAME']['0']['#']); 157 $question_cat->info = backup_todb($info['QUESTION_CATEGORY']['#']['INFO']['0']['#']); 158 $question_cat->stamp = backup_todb($info['QUESTION_CATEGORY']['#']['STAMP']['0']['#']); 159 //parent is fixed after all categories are restored and we know all the new ids. 160 $question_cat->parent = backup_todb($info['QUESTION_CATEGORY']['#']['PARENT']['0']['#']); 161 $question_cat->sortorder = backup_todb($info['QUESTION_CATEGORY']['#']['SORTORDER']['0']['#']); 162 if (!$question_cat->stamp) { 163 $question_cat->stamp = make_unique_id_code(); 164 } 165 if (isset($info['QUESTION_CATEGORY']['#']['PUBLISH'])) { 166 $course = $restore->course_id; 167 $publish = backup_todb($info['QUESTION_CATEGORY']['#']['PUBLISH']['0']['#']); 168 if ($publish){ 169 $tocontext = get_context_instance(CONTEXT_SYSTEM); 170 } else { 171 $tocontext = get_context_instance(CONTEXT_COURSE, $course); 172 } 173 } else { 174 if (!$tocontext = restore_question_get_best_category_context($restore, $info['QUESTION_CATEGORY']['#']['CONTEXT']['0']['#'])){ 175 return $status; // context doesn't exist - a module has not been restored 176 } 177 } 178 $question_cat->contextid = $tocontext->id; 179 180 //does cat exist ?? if it does we check if the cat and questions already exist whether we have 181 //add permission or not if we have no permission to add questions to SYSTEM or COURSECAT context 182 //AND the question does not already exist then we create questions in COURSE context. 183 if (!$fcat = get_record('question_categories','contextid', $question_cat->contextid, 'stamp', $question_cat->stamp)){ 184 //no preexisting cat 185 if ((($tocontext->contextlevel == CONTEXT_SYSTEM) || ($tocontext->contextlevel == CONTEXT_COURSECAT)) 186 && !has_capability('moodle/question:add', $tocontext)){ 187 //no preexisting cat and no permission to create questions here 188 //must restore to course. 189 $tocontext = get_context_instance(CONTEXT_COURSE, $restore->course_id); 190 } 191 $question_cat->contextid = $tocontext->id; 192 if (!$fcat = get_record('question_categories','contextid', $question_cat->contextid, 'stamp', $question_cat->stamp)){ 193 $question_cat->id = insert_record ("question_categories", $question_cat); 194 } else { 195 $question_cat = $fcat; 196 } 197 //we'll be restoring all questions here. 198 backup_putid($restore->backup_unique_code, "question_categories", $category->id, $question_cat->id); 199 } else { 200 $question_cat = $fcat; 201 //we found an existing best category 202 //but later if context is above course need to check if there are questions need creating in category 203 //if we do need to create questions and permissions don't allow it create new category in course 204 } 205 206 //Do some output 207 if (!defined('RESTORE_SILENTLY')) { 208 echo "<li>".get_string('category', 'quiz')." \"".$question_cat->name."\"<br />"; 209 } 210 211 backup_flush(300); 212 213 //start with questions 214 if ($question_cat->id) { 215 //We have the newid, update backup_ids 216 //Now restore question 217 $status = restore_questions($category->id, $question_cat, $info, $restore); 218 } else { 219 $status = false; 220 } 221 if (!defined('RESTORE_SILENTLY')) { 222 echo '</li>'; 223 } 224 } else { 225 echo 'Could not get backup info for question category'. $category->id; 226 } 227 } 228 return $status; 229 } 230 231 function restore_recode_category_parents($restore){ 232 global $CFG; 233 $status = true; 234 //Now we have to recode the parent field of each restored category 235 $categories = get_records_sql("SELECT old_id, new_id 236 FROM {$CFG->prefix}backup_ids 237 WHERE backup_code = $restore->backup_unique_code AND 238 table_name = 'question_categories'"); 239 if ($categories) { 240 //recode all parents to point at their old parent cats no matter what context the parent is now in 241 foreach ($categories as $category) { 242 $restoredcategory = get_record('question_categories','id',$category->new_id); 243 if ($restoredcategory && $restoredcategory->parent != 0) { 244 $updateobj = new object(); 245 $updateobj->id = $restoredcategory->id; 246 $idcat = backup_getid($restore->backup_unique_code,'question_categories',$restoredcategory->parent); 247 if ($idcat->new_id) { 248 $updateobj->parent = $idcat->new_id; 249 } else { 250 $updateobj->parent = 0; 251 } 252 $status = $status && update_record('question_categories', $updateobj); 253 } 254 } 255 //now we have recoded all parents, check through all parents and set parent to be 256 //grand parent / great grandparent etc where there is one in same context 257 //or else set parent to 0 (top level category). 258 $toupdate = array(); 259 foreach ($categories as $category) { 260 $restoredcategory = get_record('question_categories','id',$category->new_id); 261 if ($restoredcategory && $restoredcategory->parent != 0) { 262 $nextparentid = $restoredcategory->parent; 263 do { 264 if (!$parent = get_record('question_categories', 'id', $nextparentid)){ 265 if (!defined('RESTORE_SILENTLY')) { 266 echo 'Could not find parent for question category '. $category->id.' recoding as top category item.<br />'; 267 } 268 break;//record fetch failed finish loop 269 } else { 270 $nextparentid = $parent->parent; 271 } 272 } while (($nextparentid != 0) && ($parent->contextid != $restoredcategory->contextid)); 273 if (!$parent || ($parent->id != $restoredcategory->parent)){ 274 //change needs to be made to the parent field. 275 if ($parent && ($parent->contextid == $restoredcategory->contextid)){ 276 $toupdate[$restoredcategory->id] = $parent->id; 277 } else { 278 //searched up the tree till we came to the top and did not find cat in same 279 //context or there was an error getting next parent record 280 $toupdate[$restoredcategory->id] = 0; 281 } 282 } 283 } 284 } 285 //now finally do the changes to parent field. 286 foreach ($toupdate as $id => $parent){ 287 $updateobj = new object(); 288 $updateobj->id = $id; 289 $updateobj->parent = $parent; 290 $status = $status && update_record('question_categories', $updateobj); 291 } 292 } 293 return $status; 294 } 295 296 function restore_questions ($old_category_id, $best_question_cat, $info, $restore) { 297 298 global $CFG, $QTYPES; 299 300 $status = true; 301 $restored_questions = array(); 302 303 //Get the questions array 304 if (!empty($info['QUESTION_CATEGORY']['#']['QUESTIONS'])) { 305 $questions = $info['QUESTION_CATEGORY']['#']['QUESTIONS']['0']['#']['QUESTION']; 306 } else { 307 $questions = array(); 308 } 309 310 //Iterate over questions 311 for($i = 0; $i < sizeof($questions); $i++) { 312 $que_info = $questions[$i]; 313 //traverse_xmlize($que_info); //Debug 314 //print_object ($GLOBALS['traverse_array']); //Debug 315 //$GLOBALS['traverse_array']=""; //Debug 316 317 //We'll need this later!! 318 $oldid = backup_todb($que_info['#']['ID']['0']['#']); 319 320 //Now, build the question record structure 321 $question = new object; 322 $question->parent = backup_todb($que_info['#']['PARENT']['0']['#']); 323 $question->name = backup_todb($que_info['#']['NAME']['0']['#']); 324 $question->questiontext = backup_todb($que_info['#']['QUESTIONTEXT']['0']['#']); 325 $question->questiontextformat = backup_todb($que_info['#']['QUESTIONTEXTFORMAT']['0']['#']); 326 $question->image = backup_todb($que_info['#']['IMAGE']['0']['#']); 327 $question->generalfeedback = backup_todb_optional_field($que_info, 'GENERALFEEDBACK', ''); 328 $question->defaultgrade = backup_todb($que_info['#']['DEFAULTGRADE']['0']['#']); 329 $question->penalty = backup_todb($que_info['#']['PENALTY']['0']['#']); 330 $question->qtype = backup_todb($que_info['#']['QTYPE']['0']['#']); 331 $question->length = backup_todb($que_info['#']['LENGTH']['0']['#']); 332 $question->stamp = backup_todb($que_info['#']['STAMP']['0']['#']); 333 $question->version = backup_todb($que_info['#']['VERSION']['0']['#']); 334 $question->hidden = backup_todb($que_info['#']['HIDDEN']['0']['#']); 335 $question->timecreated = backup_todb_optional_field($que_info, 'TIMECREATED', 0); 336 $question->timemodified = backup_todb_optional_field($que_info, 'TIMEMODIFIED', 0); 337 $question->createdby = backup_todb_optional_field($que_info, 'CREATEDBY', null); 338 $question->modifiedby = backup_todb_optional_field($que_info, 'MODIFIEDBY', null); 339 340 if ($restore->backup_version < 2006032200) { 341 // The qtype was an integer that now needs to be converted to the name 342 $qtypenames = array(1=>'shortanswer',2=>'truefalse',3=>'multichoice',4=>'random',5=>'match', 343 6=>'randomsamatch',7=>'description',8=>'numerical',9=>'multianswer',10=>'calculated', 344 11=>'rqp',12=>'essay'); 345 $question->qtype = $qtypenames[$question->qtype]; 346 } 347 348 //Check if the question exists by category, stamp, and version 349 //first check for the question in the context specified in backup 350 $existingquestion = get_record ("question", "category", $best_question_cat->id, "stamp", $question->stamp,"version",$question->version); 351 //If the question exists, only record its id 352 //always use existing question, no permissions check here 353 if ($existingquestion) { 354 $question = $existingquestion; 355 $creatingnewquestion = false; 356 } else { 357 //then if context above course level check permissions and if no permission 358 //to restore above course level then restore to cat in course context. 359 $bestcontext = get_context_instance_by_id($best_question_cat->contextid); 360 if (($bestcontext->contextlevel == CONTEXT_SYSTEM || $bestcontext->contextlevel == CONTEXT_COURSECAT) 361 && !has_capability('moodle/question:add', $bestcontext)){ 362 if (!isset($course_question_cat)) { 363 $coursecontext = get_context_instance(CONTEXT_COURSE, $restore->course_id); 364 $course_question_cat = clone($best_question_cat); 365 $course_question_cat->contextid = $coursecontext->id; 366 //create cat if it doesn't exist 367 if (!$fcat = get_record('question_categories','contextid', $course_question_cat->contextid, 'stamp', $course_question_cat->stamp)){ 368 $course_question_cat->id = insert_record ("question_categories", $course_question_cat); 369 backup_putid($restore->backup_unique_code, "question_categories", $old_category_id, $course_question_cat->id); 370 } else { 371 $course_question_cat = $fcat; 372 } 373 //will fix category parents after all questions and categories restored. Will set parent to 0 if 374 //no parent in same context. 375 } 376 $question->category = $course_question_cat->id; 377 //does question already exist in course cat 378 $existingquestion = get_record ("question", "category", $question->category, "stamp", $question->stamp, "version", $question->version); 379 } else { 380 //permissions ok, restore to best cat 381 $question->category = $best_question_cat->id; 382 } 383 if (!$existingquestion){ 384 //The structure is equal to the db, so insert the question 385 $question->id = insert_record ("question", $question); 386 $creatingnewquestion = true; 387 } else { 388 $question = $existingquestion; 389 $creatingnewquestion = false; 390 } 391 } 392 393 // Fixing bug #5482: random questions have parent field set to its own id, 394 // see: $QTYPES['random']->get_question_options() 395 if ($question->qtype == 'random' && $creatingnewquestion) { 396 $question->parent = $question->id; 397 $status = set_field('question', 'parent', $question->parent, 'id', $question->id); 398 } 399 400 //Save newid to backup tables 401 if ($question->id) { 402 //We have the newid, update backup_ids 403 backup_putid($restore->backup_unique_code, "question", $oldid, $question->id); 404 } 405 406 $restored_questions[$i] = new stdClass; 407 $restored_questions[$i]->newid = $question->id; 408 $restored_questions[$i]->oldid = $oldid; 409 $restored_questions[$i]->qtype = $question->qtype; 410 $restored_questions[$i]->parent = $question->parent; 411 $restored_questions[$i]->is_new = $creatingnewquestion; 412 } 413 backup_flush(300); 414 415 // Loop again, now all the question id mappings exist, so everything can 416 // be restored. 417 for($i = 0; $i < sizeof($questions); $i++) { 418 $que_info = $questions[$i]; 419 420 $newid = $restored_questions[$i]->newid; 421 $oldid = $restored_questions[$i]->oldid; 422 423 $question = new object; 424 $question->qtype = $restored_questions[$i]->qtype; 425 $question->parent = $restored_questions[$i]->parent; 426 427 428 /// If it's a new question in the DB, restore it 429 if ($restored_questions[$i]->is_new) { 430 431 /// We have to recode the parent field 432 if ($question->parent && $question->qtype != 'random') { 433 /// If the parent field needs to be changed, do it here. Random questions are dealt with above. 434 if ($parent = backup_getid($restore->backup_unique_code,"question",$question->parent)) { 435 $question->parent = $parent->new_id; 436 if ($question->parent != $restored_questions[$i]->parent) { 437 if (!set_field('question', 'parent', $question->parent, 'id', $newid)) { 438 echo 'Could not update parent '.$question->parent.' for question '.$oldid.'<br />'; 439 $status = false; 440 } 441 } 442 } else { 443 echo 'Could not recode parent '.$question->parent.' for question '.$oldid.'<br />'; 444 $status = false; 445 } 446 } 447 448 //Now, restore every question_answers in this question 449 $status = question_restore_answers($oldid,$newid,$que_info,$restore); 450 // Restore questiontype specific data 451 if (array_key_exists($question->qtype, $QTYPES)) { 452 $status = $QTYPES[$question->qtype]->restore($oldid,$newid,$que_info,$restore); 453 } else { 454 echo 'Unknown question type '.$question->qtype.' for question '.$oldid.'<br />'; 455 $status = false; 456 } 457 } else { 458 //We are NOT creating the question, but we need to know every question_answers 459 //map between the XML file and the database to be able to restore the states 460 //in each attempt. 461 $status = question_restore_map_answers($oldid,$newid,$que_info,$restore); 462 // Do the questiontype specific mapping 463 if (array_key_exists($question->qtype, $QTYPES)) { 464 $status = $QTYPES[$question->qtype]->restore_map($oldid,$newid,$que_info,$restore); 465 } else { 466 echo 'Unknown question type '.$question->qtype.' for question '.$oldid.'<br />'; 467 $status = false; 468 } 469 } 470 471 //Do some output 472 if (($i+1) % 2 == 0) { 473 if (!defined('RESTORE_SILENTLY')) { 474 echo "."; 475 if (($i+1) % 40 == 0) { 476 echo "<br />"; 477 } 478 } 479 backup_flush(300); 480 } 481 } 482 return $status; 483 } 484 485 function backup_todb_optional_field($data, $field, $default) { 486 if (array_key_exists($field, $data['#'])) { 487 return backup_todb($data['#'][$field]['0']['#']); 488 } else { 489 return $default; 490 } 491 } 492 493 function question_restore_answers ($old_question_id,$new_question_id,$info,$restore) { 494 495 global $CFG; 496 497 $status = true; 498 $qtype = backup_todb($info['#']['QTYPE']['0']['#']); 499 500 //Get the answers array 501 if (isset($info['#']['ANSWERS']['0']['#']['ANSWER'])) { 502 $answers = $info['#']['ANSWERS']['0']['#']['ANSWER']; 503 504 //Iterate over answers 505 for($i = 0; $i < sizeof($answers); $i++) { 506 $ans_info = $answers[$i]; 507 //traverse_xmlize($ans_info); //Debug 508 //print_object ($GLOBALS['traverse_array']); //Debug 509 //$GLOBALS['traverse_array']=""; //Debug 510 511 //We'll need this later!! 512 $oldid = backup_todb($ans_info['#']['ID']['0']['#']); 513 514 //Now, build the question_answers record structure 515 $answer = new stdClass; 516 $answer->question = $new_question_id; 517 $answer->answer = backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']); 518 $answer->fraction = backup_todb($ans_info['#']['FRACTION']['0']['#']); 519 $answer->feedback = backup_todb($ans_info['#']['FEEDBACK']['0']['#']); 520 521 // Update 'match everything' answers for numerical questions coming from old backup files. 522 if ($qtype == 'numerical' && $answer->answer == '') { 523 $answer->answer = '*'; 524 } 525 526 //The structure is equal to the db, so insert the question_answers 527 $newid = insert_record ("question_answers",$answer); 528 529 //Do some output 530 if (($i+1) % 50 == 0) { 531 if (!defined('RESTORE_SILENTLY')) { 532 echo "."; 533 if (($i+1) % 1000 == 0) { 534 echo "<br />"; 535 } 536 } 537 backup_flush(300); 538 } 539 540 if ($newid) { 541 //We have the newid, update backup_ids 542 backup_putid($restore->backup_unique_code,"question_answers",$oldid, 543 $newid); 544 } else { 545 $status = false; 546 } 547 } 548 } 549 550 return $status; 551 } 552 553 function question_restore_map_answers ($old_question_id,$new_question_id,$info,$restore) { 554 555 global $CFG; 556 557 $status = true; 558 559 if (!isset($info['#']['ANSWERS'])) { // No answers in this question (eg random) 560 return $status; 561 } 562 563 //Get the answers array 564 $answers = $info['#']['ANSWERS']['0']['#']['ANSWER']; 565 566 //Iterate over answers 567 for($i = 0; $i < sizeof($answers); $i++) { 568 $ans_info = $answers[$i]; 569 //traverse_xmlize($ans_info); //Debug 570 //print_object ($GLOBALS['traverse_array']); //Debug 571 //$GLOBALS['traverse_array']=""; //Debug 572 573 //We'll need this later!! 574 $oldid = backup_todb($ans_info['#']['ID']['0']['#']); 575 576 //Now, build the question_answers record structure 577 $answer->question = $new_question_id; 578 $answer->answer = backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']); 579 $answer->fraction = backup_todb($ans_info['#']['FRACTION']['0']['#']); 580 $answer->feedback = backup_todb($ans_info['#']['FEEDBACK']['0']['#']); 581 582 //If we are in this method is because the question exists in DB, so its 583 //answers must exist too. 584 //Now, we are going to look for that answer in DB and to create the 585 //mappings in backup_ids to use them later where restoring states (user level). 586 587 //Get the answer from DB (by question and answer) 588 $db_answer = get_record ("question_answers","question",$new_question_id, 589 "answer",$answer->answer); 590 591 //Do some output 592 if (($i+1) % 50 == 0) { 593 if (!defined('RESTORE_SILENTLY')) { 594 echo "."; 595 if (($i+1) % 1000 == 0) { 596 echo "<br />"; 597 } 598 } 599 backup_flush(300); 600 } 601 602 if ($db_answer) { 603 //We have the database answer, update backup_ids 604 backup_putid($restore->backup_unique_code,"question_answers",$oldid, 605 $db_answer->id); 606 } else { 607 $status = false; 608 } 609 } 610 611 return $status; 612 } 613 614 function question_restore_numerical_units($old_question_id,$new_question_id,$info,$restore) { 615 616 global $CFG; 617 618 $status = true; 619 620 //Get the numerical array 621 if (!empty($info['#']['NUMERICAL_UNITS'])) { 622 $numerical_units = $info['#']['NUMERICAL_UNITS']['0']['#']['NUMERICAL_UNIT']; 623 } else { 624 $numerical_units = array(); 625 } 626 627 //Iterate over numerical_units 628 for($i = 0; $i < sizeof($numerical_units); $i++) { 629 $nu_info = $numerical_units[$i]; 630 //traverse_xmlize($nu_info); //Debug 631 //print_object ($GLOBALS['traverse_array']); //Debug 632 //$GLOBALS['traverse_array']=""; //Debug 633 634 // Check to see if this until already exists in the database, which it might, for 635 // Historical reasons. 636 $unit = backup_todb($nu_info['#']['UNIT']['0']['#']); 637 if (!record_exists('question_numerical_units', 'question', $new_question_id, 'unit', $unit)) { 638 639 //Now, build the question_numerical_UNITS record structure. 640 $numerical_unit = new stdClass; 641 $numerical_unit->question = $new_question_id; 642 $numerical_unit->multiplier = backup_todb($nu_info['#']['MULTIPLIER']['0']['#']); 643 $numerical_unit->unit = $unit; 644 645 //The structure is equal to the db, so insert the question_numerical_units 646 $newid = insert_record("question_numerical_units", $numerical_unit); 647 648 if (!$newid) { 649 $status = false; 650 } 651 } 652 } 653 654 return $status; 655 } 656 657 function question_restore_dataset_definitions ($old_question_id,$new_question_id,$info,$restore) { 658 659 global $CFG; 660 661 $status = true; 662 663 //Get the dataset_definitions array 664 $dataset_definitions = $info['#']['DATASET_DEFINITIONS']['0']['#']['DATASET_DEFINITION']; 665 666 //Iterate over dataset_definitions 667 for($i = 0; $i < sizeof($dataset_definitions); $i++) { 668 $dd_info = $dataset_definitions[$i]; 669 //traverse_xmlize($dd_info); //Debug 670 //print_object ($GLOBALS['traverse_array']); //Debug 671 //$GLOBALS['traverse_array']=""; //Debug 672 673 //Now, build the question_dataset_DEFINITION record structure 674 $dataset_definition = new stdClass; 675 $dataset_definition->category = backup_todb($dd_info['#']['CATEGORY']['0']['#']); 676 $dataset_definition->name = backup_todb($dd_info['#']['NAME']['0']['#']); 677 $dataset_definition->type = backup_todb($dd_info['#']['TYPE']['0']['#']); 678 $dataset_definition->options = backup_todb($dd_info['#']['OPTIONS']['0']['#']); 679 $dataset_definition->itemcount = backup_todb($dd_info['#']['ITEMCOUNT']['0']['#']); 680 681 //We have to recode the category field (only if the category != 0) 682 if ($dataset_definition->category != 0) { 683 $category = backup_getid($restore->backup_unique_code,"question_categories",$dataset_definition->category); 684 if ($category) { 685 $dataset_definition->category = $category->new_id; 686 } else { 687 echo 'Could not recode category id '.$dataset_definition->category.' for dataset definition'.$dataset_definition->name.'<br />'; 688 } 689 } 690 691 //Now, we hace to decide when to create the new records or reuse an existing one 692 $create_definition = false; 693 694 //If the dataset_definition->category = 0, it's a individual question dataset_definition, so we'll create it 695 if ($dataset_definition->category == 0) { 696 $create_definition = true; 697 } else { 698 //The category isn't 0, so it's a category question dataset_definition, we have to see if it exists 699 //Look for a definition with the same category, name and type 700 if ($definitionrec = get_record_sql("SELECT d.* 701 FROM {$CFG->prefix}question_dataset_definitions d 702 WHERE d.category = '$dataset_definition->category' AND 703 d.name = '$dataset_definition->name' AND 704 d.type = '$dataset_definition->type'")) { 705 //Such dataset_definition exist. Now we must check if it has enough itemcount 706 if ($definitionrec->itemcount < $dataset_definition->itemcount) { 707 //We haven't enough itemcount, so we have to create the definition as an individual question one. 708 $dataset_definition->category = 0; 709 $create_definition = true; 710 } else { 711 //We have enough itemcount, so we'll reuse the existing definition 712 $create_definition = false; 713 $newid = $definitionrec->id; 714 } 715 } else { 716 //Such dataset_definition doesn't exist. We'll create it. 717 $create_definition = true; 718 } 719 } 720 721 //If we've to create the definition, do it 722 if ($create_definition) { 723 //The structure is equal to the db, so insert the question_dataset_definitions 724 $newid = insert_record ("question_dataset_definitions",$dataset_definition); 725 if ($newid) { 726 //Restore question_dataset_items 727 $status = question_restore_dataset_items($newid,$dd_info,$restore); 728 } 729 } 730 731 //Now, we must have a definition (created o reused). Its id is in newid. Create the question_datasets record 732 //to join the question and the dataset_definition 733 if ($newid) { 734 $question_dataset = new stdClass; 735 $question_dataset->question = $new_question_id; 736 $question_dataset->datasetdefinition = $newid; 737 $newid = insert_record ("question_datasets",$question_dataset); 738 } 739 740 if (!$newid) { 741 $status = false; 742 } 743 } 744 745 return $status; 746 } 747 748 function question_restore_dataset_items ($definitionid,$info,$restore) { 749 750 global $CFG; 751 752 $status = true; 753 754 //Get the items array 755 $dataset_items = $info['#']['DATASET_ITEMS']['0']['#']['DATASET_ITEM']; 756 757 //Iterate over dataset_items 758 for($i = 0; $i < sizeof($dataset_items); $i++) { 759 $di_info = $dataset_items[$i]; 760 //traverse_xmlize($di_info); //Debug 761 //print_object ($GLOBALS['traverse_array']); //Debug 762 //$GLOBALS['traverse_array']=""; //Debug 763 764 //Now, build the question_dataset_ITEMS record structure 765 $dataset_item = new stdClass; 766 $dataset_item->definition = $definitionid; 767 $dataset_item->itemnumber = backup_todb($di_info['#']['NUMBER']['0']['#']); 768 $dataset_item->value = backup_todb($di_info['#']['VALUE']['0']['#']); 769 770 //The structure is equal to the db, so insert the question_dataset_items 771 $newid = insert_record ("question_dataset_items",$dataset_item); 772 773 if (!$newid) { 774 $status = false; 775 } 776 } 777 778 return $status; 779 } 780 781 782 //This function restores the question_states 783 function question_states_restore_mods($attempt_id,$info,$restore) { 784 785 global $CFG, $QTYPES; 786 787 $status = true; 788 789 //Get the question_states array 790 $states = $info['#']['STATES']['0']['#']['STATE']; 791 //Iterate over states 792 for($i = 0; $i < sizeof($states); $i++) { 793 $res_info = $states[$i]; 794 //traverse_xmlize($res_info); //Debug 795 //print_object ($GLOBALS['traverse_array']); //Debug 796 //$GLOBALS['traverse_array']=""; //Debug 797 798 //We'll need this later!! 799 $oldid = backup_todb($res_info['#']['ID']['0']['#']); 800 801 //Now, build the STATES record structure 802 $state = new stdClass; 803 $state->attempt = $attempt_id; 804 $state->question = backup_todb($res_info['#']['QUESTION']['0']['#']); 805 $state->originalquestion = backup_todb($res_info['#']['ORIGINALQUESTION']['0']['#']); 806 $state->seq_number = backup_todb($res_info['#']['SEQ_NUMBER']['0']['#']); 807 $state->answer = backup_todb($res_info['#']['ANSWER']['0']['#']); 808 $state->timestamp = backup_todb($res_info['#']['TIMESTAMP']['0']['#']); 809 $state->event = backup_todb($res_info['#']['EVENT']['0']['#']); 810 $state->grade = backup_todb($res_info['#']['GRADE']['0']['#']); 811 $state->raw_grade = backup_todb($res_info['#']['RAW_GRADE']['0']['#']); 812 $state->penalty = backup_todb($res_info['#']['PENALTY']['0']['#']); 813 $state->oldid = $oldid; // So it is available to restore_recode_answer. 814 815 //We have to recode the question field 816 $question = backup_getid($restore->backup_unique_code,"question",$state->question); 817 if ($question) { 818 $state->question = $question->new_id; 819 } else { 820 echo 'Could not recode question id '.$state->question.' for state '.$oldid.'<br />'; 821 } 822 823 //We have to recode the originalquestion field if it is nonzero 824 if ($state->originalquestion) { 825 $question = backup_getid($restore->backup_unique_code,"question",$state->originalquestion); 826 if ($question) { 827 $state->originalquestion = $question->new_id; 828 } else { 829 echo 'Could not recode originalquestion id '.$state->question.' for state '.$oldid.'<br />'; 830 } 831 } 832 833 //We have to recode the answer field 834 //It depends of the question type !! 835 //We get the question first 836 if (!$question = get_record("question","id",$state->question)) { 837 error("Can't find the record for question $state->question for which I am trying to restore a state"); 838 } 839 //Depending on the qtype, we make different recodes 840 if ($state->answer) { 841 $state->answer = $QTYPES[$question->qtype]->restore_recode_answer($state, $restore); 842 } 843 844 //The structure is equal to the db, so insert the question_states 845 $newid = insert_record ("question_states",$state); 846 847 //Do some output 848 if (($i+1) % 10 == 0) { 849 if (!defined('RESTORE_SILENTLY')) { 850 echo "."; 851 if (($i+1) % 200 == 0) { 852 echo "<br />"; 853 } 854 } 855 backup_flush(300); 856 } 857 858 if ($newid) { 859 //We have the newid, update backup_ids 860 backup_putid($restore->backup_unique_code, 'question_states', $oldid, $newid); 861 } else { 862 $status = false; 863 } 864 } 865 866 //Get the question_sessions array 867 $sessions = $info['#']['NEWEST_STATES']['0']['#']['NEWEST_STATE']; 868 //Iterate over question_sessions 869 for($i = 0; $i < sizeof($sessions); $i++) { 870 $res_info = $sessions[$i]; 871 //traverse_xmlize($res_info); //Debug 872 //print_object ($GLOBALS['traverse_array']); //Debug 873 //$GLOBALS['traverse_array']=""; //Debug 874 875 //Now, build the NEWEST_STATES record structure 876 $session = new stdClass; 877 $session->attemptid = $attempt_id; 878 $session->questionid = backup_todb($res_info['#']['QUESTIONID']['0']['#']); 879 $session->newest = backup_todb($res_info['#']['NEWEST']['0']['#']); 880 $session->newgraded = backup_todb($res_info['#']['NEWGRADED']['0']['#']); 881 $session->sumpenalty = backup_todb($res_info['#']['SUMPENALTY']['0']['#']); 882 883 if (isset($res_info['#']['MANUALCOMMENT']['0']['#'])) { 884 $session->manualcomment = backup_todb($res_info['#']['MANUALCOMMENT']['0']['#']); 885 } else { // pre 1.7 backups 886 $session->manualcomment = backup_todb($res_info['#']['COMMENT']['0']['#']); 887 } 888 889 //We have to recode the question field 890 $question = backup_getid($restore->backup_unique_code,"question",$session->questionid); 891 if ($question) { 892 $session->questionid = $question->new_id; 893 } else { 894 echo 'Could not recode question id '.$session->questionid.'<br />'; 895 } 896 897 //We have to recode the newest field 898 $state = backup_getid($restore->backup_unique_code,"question_states",$session->newest); 899 if ($state) { 900 $session->newest = $state->new_id; 901 } else { 902 echo 'Could not recode newest state id '.$session->newest.'<br />'; 903 } 904 905 //If the session has been graded we have to recode the newgraded field 906 if ($session->newgraded) { 907 $state = backup_getid($restore->backup_unique_code,"question_states",$session->newgraded); 908 if ($state) { 909 $session->newgraded = $state->new_id; 910 } else { 911 echo 'Could not recode newest graded state id '.$session->newgraded.'<br />'; 912 } 913 } 914 915 //The structure is equal to the db, so insert the question_sessions 916 $newid = insert_record ("question_sessions",$session); 917 918 } 919 920 return $status; 921 } 922 923 /** 924 * Recode content links in question texts. 925 * @param object $restore the restore metadata object. 926 * @return boolean whether the operation succeeded. 927 */ 928 function question_decode_content_links_caller($restore) { 929 global $CFG, $QTYPES; 930 $status = true; 931 $i = 1; //Counter to send some output to the browser to avoid timeouts 932 933 // Get a list of which question types have custom field that will need decoding. 934 $qtypeswithextrafields = array(); 935 $qtypeswithhtmlanswers = array(); 936 foreach ($QTYPES as $qtype => $qtypeclass) { 937 $qtypeswithextrafields[$qtype] = method_exists($qtypeclass, 'decode_content_links_caller'); 938 $qtypeswithhtmlanswers[$qtype] = $qtypeclass->has_html_answers(); 939 } 940 $extraprocessing = array(); 941 942 $coursemodulecontexts = array(); 943 $context = get_context_instance(CONTEXT_COURSE, $restore->course_id); 944 $coursemodulecontexts[] = $context->id; 945 $cms = get_records('course_modules', 'course', $restore->course_id, '', 'id'); 946 if ($cms){ 947 foreach ($cms as $cm){ 948 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 949 $coursemodulecontexts[] = $context->id; 950 } 951 } 952 $coursemodulecontextslist = join($coursemodulecontexts, ','); 953 // Decode links in questions. 954 if ($questions = get_records_sql('SELECT q.id, q.qtype, q.questiontext, q.generalfeedback '. 955 'FROM ' . $CFG->prefix . 'question q, '. 956 $CFG->prefix . 'question_categories qc '. 957 'WHERE q.category = qc.id '. 958 'AND qc.contextid IN (' .$coursemodulecontextslist.')')) { 959 960 foreach ($questions as $question) { 961 $questiontext = restore_decode_content_links_worker($question->questiontext, $restore); 962 $generalfeedback = restore_decode_content_links_worker($question->generalfeedback, $restore); 963 if ($questiontext != $question->questiontext || $generalfeedback != $question->generalfeedback) { 964 $question->questiontext = addslashes($questiontext); 965 $question->generalfeedback = addslashes($generalfeedback); 966 if (!update_record('question', $question)) { 967 $status = false; 968 } 969 } 970 971 // Do some output. 972 if (++$i % 5 == 0 && !defined('RESTORE_SILENTLY')) { 973 echo "."; 974 if ($i % 100 == 0) { 975 echo "<br />"; 976 } 977 backup_flush(300); 978 } 979 980 // Decode any questiontype specific fields. 981 if ($qtypeswithextrafields[$question->qtype]) { 982 if (!array_key_exists($question->qtype, $extraprocessing)) { 983 $extraprocessing[$question->qtype] = array(); 984 } 985 $extraprocessing[$question->qtype][] = $question->id; 986 } 987 } 988 } 989 990 // Decode links in answers. 991 if ($answers = get_records_sql('SELECT qa.id, qa.answer, qa.feedback, q.qtype 992 FROM ' . $CFG->prefix . 'question_answers qa, 993 ' . $CFG->prefix . 'question q, 994 ' . $CFG->prefix . 'question_categories qc 995 WHERE qa.question = q.id 996 AND q.category = qc.id '. 997 'AND qc.contextid IN ('.$coursemodulecontextslist.')')) { 998 999 foreach ($answers as $answer) { 1000 $feedback = restore_decode_content_links_worker($answer->feedback, $restore); 1001 if ($qtypeswithhtmlanswers[$answer->qtype]) { 1002 $answertext = restore_decode_content_links_worker($answer->answer, $restore); 1003 } else { 1004 $answertext = $answer->answer; 1005 } 1006 if ($feedback != $answer->feedback || $answertext != $answer->answer) { 1007 unset($answer->qtype); 1008 $answer->feedback = addslashes($feedback); 1009 $answer->answer = addslashes($answertext); 1010 if (!update_record('question_answers', $answer)) { 1011 $status = false; 1012 } 1013 } 1014 1015 // Do some output. 1016 if (++$i % 5 == 0 && !defined('RESTORE_SILENTLY')) { 1017 echo "."; 1018 if ($i % 100 == 0) { 1019 echo "<br />"; 1020 } 1021 backup_flush(300); 1022 } 1023 } 1024 } 1025 1026 // Do extra work for certain question types. 1027 foreach ($extraprocessing as $qtype => $questionids) { 1028 if (!$QTYPES[$qtype]->decode_content_links_caller($questionids, $restore, $i)) { 1029 $status = false; 1030 } 1031 } 1032 1033 return $status; 1034 } 1035 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |