Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/backup/moodle2/restore_stepslib.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00032 class restore_create_and_clean_temp_stuff extends restore_execution_step {
00033 
00034     protected function define_execution() {
00035         $exists = restore_controller_dbops::create_restore_temp_tables($this->get_restoreid()); // temp tables conditionally
00036         // If the table already exists, it's because restore_prechecks have been executed in the same
00037         // request (without problems) and it already contains a bunch of preloaded information (users...)
00038         // that we aren't going to execute again
00039         if ($exists) { // Inform plan about preloaded information
00040             $this->task->set_preloaded_information();
00041         }
00042         // Create the old-course-ctxid to new-course-ctxid mapping, we need that available since the beginning
00043         $itemid = $this->task->get_old_contextid();
00044         $newitemid = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
00045         restore_dbops::set_backup_ids_record($this->get_restoreid(), 'context', $itemid, $newitemid);
00046         // Create the old-system-ctxid to new-system-ctxid mapping, we need that available since the beginning
00047         $itemid = $this->task->get_old_system_contextid();
00048         $newitemid = get_context_instance(CONTEXT_SYSTEM)->id;
00049         restore_dbops::set_backup_ids_record($this->get_restoreid(), 'context', $itemid, $newitemid);
00050         // Create the old-course-id to new-course-id mapping, we need that available since the beginning
00051         $itemid = $this->task->get_old_courseid();
00052         $newitemid = $this->get_courseid();
00053         restore_dbops::set_backup_ids_record($this->get_restoreid(), 'course', $itemid, $newitemid);
00054 
00055     }
00056 }
00057 
00062 class restore_drop_and_clean_temp_stuff extends restore_execution_step {
00063 
00064     protected function define_execution() {
00065         global $CFG;
00066         restore_controller_dbops::drop_restore_temp_tables($this->get_restoreid()); // Drop ids temp table
00067         backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60));              // Delete > 4 hours temp dirs
00068         if (empty($CFG->keeptempdirectoriesonbackup)) { // Conditionally
00069             backup_helper::delete_backup_dir($this->task->get_tempdir()); // Empty restore dir
00070         }
00071     }
00072 }
00073 
00077 class restore_gradebook_structure_step extends restore_structure_step {
00078 
00085      protected function execute_condition() {
00086         global $CFG, $DB;
00087 
00088         // No gradebook info found, don't execute
00089         $fullpath = $this->task->get_taskbasepath();
00090         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
00091         if (!file_exists($fullpath)) {
00092             return false;
00093         }
00094 
00095         // Some module present in backup file isn't available to restore
00096         // in this site, don't execute
00097         if ($this->task->is_missing_modules()) {
00098             return false;
00099         }
00100 
00101         // Some activity has been excluded to be restored, don't execute
00102         if ($this->task->is_excluding_activities()) {
00103             return false;
00104         }
00105 
00106         // There should only be one grade category (the 1 associated with the course itself)
00107         // If other categories already exist we're restoring into an existing course.
00108         // Restoring categories into a course with an existing category structure is unlikely to go well
00109         $category = new stdclass();
00110         $category->courseid  = $this->get_courseid();
00111         $catcount = $DB->count_records('grade_categories', (array)$category);
00112         if ($catcount>1) {
00113             return false;
00114         }
00115 
00116         // Arrived here, execute the step
00117         return true;
00118      }
00119 
00120     protected function define_structure() {
00121         $paths = array();
00122         $userinfo = $this->task->get_setting_value('users');
00123 
00124         $paths[] = new restore_path_element('gradebook', '/gradebook');
00125         $paths[] = new restore_path_element('grade_category', '/gradebook/grade_categories/grade_category');
00126         $paths[] = new restore_path_element('grade_item', '/gradebook/grade_items/grade_item');
00127         if ($userinfo) {
00128             $paths[] = new restore_path_element('grade_grade', '/gradebook/grade_items/grade_item/grade_grades/grade_grade');
00129         }
00130         $paths[] = new restore_path_element('grade_letter', '/gradebook/grade_letters/grade_letter');
00131         $paths[] = new restore_path_element('grade_setting', '/gradebook/grade_settings/grade_setting');
00132 
00133         return $paths;
00134     }
00135 
00136     protected function process_gradebook($data) {
00137     }
00138 
00139     protected function process_grade_item($data) {
00140         global $DB;
00141 
00142         $data = (object)$data;
00143 
00144         $oldid = $data->id;
00145         $data->course = $this->get_courseid();
00146 
00147         $data->courseid = $this->get_courseid();
00148 
00149         if ($data->itemtype=='manual') {
00150             // manual grade items store category id in categoryid
00151             $data->categoryid = $this->get_mappingid('grade_category', $data->categoryid, NULL);
00152         } else if ($data->itemtype=='course') {
00153             // course grade item stores their category id in iteminstance
00154             $coursecat = grade_category::fetch_course_category($this->get_courseid());
00155             $data->iteminstance = $coursecat->id;
00156         } else if ($data->itemtype=='category') {
00157             // category grade items store their category id in iteminstance
00158             $data->iteminstance = $this->get_mappingid('grade_category', $data->iteminstance, NULL);
00159         } else {
00160             throw new restore_step_exception('unexpected_grade_item_type', $data->itemtype);
00161         }
00162 
00163         $data->scaleid   = $this->get_mappingid('scale', $data->scaleid, NULL);
00164         $data->outcomeid = $this->get_mappingid('outcome', $data->outcomeid, NULL);
00165 
00166         $data->locktime     = $this->apply_date_offset($data->locktime);
00167         $data->timecreated  = $this->apply_date_offset($data->timecreated);
00168         $data->timemodified = $this->apply_date_offset($data->timemodified);
00169 
00170         $coursecategory = $newitemid = null;
00171         //course grade item should already exist so updating instead of inserting
00172         if($data->itemtype=='course') {
00173             //get the ID of the already created grade item
00174             $gi = new stdclass();
00175             $gi->courseid  = $this->get_courseid();
00176             $gi->itemtype  = $data->itemtype;
00177 
00178             //need to get the id of the grade_category that was automatically created for the course
00179             $category = new stdclass();
00180             $category->courseid  = $this->get_courseid();
00181             $category->parent  = null;
00182             //course category fullname starts out as ? but may be edited
00183             //$category->fullname  = '?';
00184             $coursecategory = $DB->get_record('grade_categories', (array)$category);
00185             $gi->iteminstance = $coursecategory->id;
00186 
00187             $existinggradeitem = $DB->get_record('grade_items', (array)$gi);
00188             if (!empty($existinggradeitem)) {
00189                 $data->id = $newitemid = $existinggradeitem->id;
00190                 $DB->update_record('grade_items', $data);
00191             }
00192         }
00193 
00194         if (empty($newitemid)) {
00195             //in case we found the course category but still need to insert the course grade item
00196             if ($data->itemtype=='course' && !empty($coursecategory)) {
00197                 $data->iteminstance = $coursecategory->id;
00198             }
00199 
00200             $newitemid = $DB->insert_record('grade_items', $data);
00201         }
00202         $this->set_mapping('grade_item', $oldid, $newitemid);
00203     }
00204 
00205     protected function process_grade_grade($data) {
00206         global $DB;
00207 
00208         $data = (object)$data;
00209         $oldid = $data->id;
00210 
00211         $data->itemid = $this->get_new_parentid('grade_item');
00212 
00213         $data->userid = $this->get_mappingid('user', $data->userid, NULL);
00214         $data->usermodified = $this->get_mappingid('user', $data->usermodified, NULL);
00215         $data->locktime     = $this->apply_date_offset($data->locktime);
00216         // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled?
00217         $data->overridden = $this->apply_date_offset($data->overridden);
00218         $data->timecreated  = $this->apply_date_offset($data->timecreated);
00219         $data->timemodified = $this->apply_date_offset($data->timemodified);
00220 
00221         $newitemid = $DB->insert_record('grade_grades', $data);
00222         //$this->set_mapping('grade_grade', $oldid, $newitemid);
00223     }
00224     protected function process_grade_category($data) {
00225         global $DB;
00226 
00227         $data = (object)$data;
00228         $oldid = $data->id;
00229 
00230         $data->course = $this->get_courseid();
00231         $data->courseid = $data->course;
00232 
00233         $data->timecreated  = $this->apply_date_offset($data->timecreated);
00234         $data->timemodified = $this->apply_date_offset($data->timemodified);
00235 
00236         $newitemid = null;
00237         //no parent means a course level grade category. That may have been created when the course was created
00238         if(empty($data->parent)) {
00239             //parent was being saved as 0 when it should be null
00240             $data->parent = null;
00241 
00242             //get the already created course level grade category
00243             $category = new stdclass();
00244             $category->courseid = $this->get_courseid();
00245             $category->parent = null;
00246 
00247             $coursecategory = $DB->get_record('grade_categories', (array)$category);
00248             if (!empty($coursecategory)) {
00249                 $data->id = $newitemid = $coursecategory->id;
00250                 $DB->update_record('grade_categories', $data);
00251             }
00252         }
00253 
00254         //need to insert a course category
00255         if (empty($newitemid)) {
00256             $newitemid = $DB->insert_record('grade_categories', $data);
00257         }
00258         $this->set_mapping('grade_category', $oldid, $newitemid);
00259     }
00260     protected function process_grade_letter($data) {
00261         global $DB;
00262 
00263         $data = (object)$data;
00264         $oldid = $data->id;
00265 
00266         $data->contextid = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
00267 
00268         $newitemid = $DB->insert_record('grade_letters', $data);
00269         $this->set_mapping('grade_letter', $oldid, $newitemid);
00270     }
00271     protected function process_grade_setting($data) {
00272         global $DB;
00273 
00274         $data = (object)$data;
00275         $oldid = $data->id;
00276 
00277         $data->courseid = $this->get_courseid();
00278 
00279         $newitemid = $DB->insert_record('grade_settings', $data);
00280         //$this->set_mapping('grade_setting', $oldid, $newitemid);
00281     }
00282 
00286     protected function after_execute() {
00287         global $DB;
00288 
00289         $conditions = array(
00290             'backupid' => $this->get_restoreid(),
00291             'itemname' => 'grade_item'//,
00292             //'itemid'   => $itemid
00293         );
00294         $rs = $DB->get_recordset('backup_ids_temp', $conditions);
00295 
00296         // We need this for calculation magic later on.
00297         $mappings = array();
00298 
00299         if (!empty($rs)) {
00300             foreach($rs as $grade_item_backup) {
00301 
00302                 // Store the oldid with the new id.
00303                 $mappings[$grade_item_backup->itemid] = $grade_item_backup->newitemid;
00304 
00305                 $updateobj = new stdclass();
00306                 $updateobj->id = $grade_item_backup->newitemid;
00307 
00308                 //if this is an activity grade item that needs to be put back in its correct category
00309                 if (!empty($grade_item_backup->parentitemid)) {
00310                     $oldcategoryid = $this->get_mappingid('grade_category', $grade_item_backup->parentitemid, null);
00311                     if (!is_null($oldcategoryid)) {
00312                         $updateobj->categoryid = $oldcategoryid;
00313                         $DB->update_record('grade_items', $updateobj);
00314                     }
00315                 } else {
00316                     //mark course and category items as needing to be recalculated
00317                     $updateobj->needsupdate=1;
00318                     $DB->update_record('grade_items', $updateobj);
00319                 }
00320             }
00321         }
00322         $rs->close();
00323 
00324         // We need to update the calculations for calculated grade items that may reference old
00325         // grade item ids using ##gi\d+##.
00326         // $mappings can be empty, use 0 if so (won't match ever)
00327         list($sql, $params) = $DB->get_in_or_equal(array_values($mappings), SQL_PARAMS_NAMED, 'param', true, 0);
00328         $sql = "SELECT gi.id, gi.calculation
00329                   FROM {grade_items} gi
00330                  WHERE gi.id {$sql} AND
00331                        calculation IS NOT NULL";
00332         $rs = $DB->get_recordset_sql($sql, $params);
00333         foreach ($rs as $gradeitem) {
00334             // Collect all of the used grade item id references
00335             if (preg_match_all('/##gi(\d+)##/', $gradeitem->calculation, $matches) < 1) {
00336                 // This calculation doesn't reference any other grade items... EASY!
00337                 continue;
00338             }
00339             // For this next bit we are going to do the replacement of id's in two steps:
00340             // 1. We will replace all old id references with a special mapping reference.
00341             // 2. We will replace all mapping references with id's
00342             // Why do we do this?
00343             // Because there potentially there will be an overlap of ids within the query and we
00344             // we substitute the wrong id.. safest way around this is the two step system
00345             $calculationmap = array();
00346             $mapcount = 0;
00347             foreach ($matches[1] as $match) {
00348                 // Check that the old id is known to us, if not it was broken to begin with and will
00349                 // continue to be broken.
00350                 if (!array_key_exists($match, $mappings)) {
00351                     continue;
00352                 }
00353                 // Our special mapping key
00354                 $mapping = '##MAPPING'.$mapcount.'##';
00355                 // The old id that exists within the calculation now
00356                 $oldid = '##gi'.$match.'##';
00357                 // The new id that we want to replace the old one with.
00358                 $newid = '##gi'.$mappings[$match].'##';
00359                 // Replace in the special mapping key
00360                 $gradeitem->calculation = str_replace($oldid, $mapping, $gradeitem->calculation);
00361                 // And record the mapping
00362                 $calculationmap[$mapping] = $newid;
00363                 $mapcount++;
00364             }
00365             // Iterate all special mappings for this calculation and replace in the new id's
00366             foreach ($calculationmap as $mapping => $newid) {
00367                 $gradeitem->calculation = str_replace($mapping, $newid, $gradeitem->calculation);
00368             }
00369             // Update the calculation now that its being remapped
00370             $DB->update_record('grade_items', $gradeitem);
00371         }
00372         $rs->close();
00373 
00374         // Need to correct the grade category path and parent
00375         $conditions = array(
00376             'courseid' => $this->get_courseid()
00377         );
00378 
00379         $rs = $DB->get_recordset('grade_categories', $conditions);
00380         // Get all the parents correct first as grade_category::build_path() loads category parents from the DB
00381         foreach ($rs as $gc) {
00382             if (!empty($gc->parent)) {
00383                 $grade_category = new stdClass();
00384                 $grade_category->id = $gc->id;
00385                 $grade_category->parent = $this->get_mappingid('grade_category', $gc->parent);
00386                 $DB->update_record('grade_categories', $grade_category);
00387             }
00388         }
00389         $rs->close();
00390 
00391         // Now we can rebuild all the paths
00392         $rs = $DB->get_recordset('grade_categories', $conditions);
00393         foreach ($rs as $gc) {
00394             $grade_category = new stdClass();
00395             $grade_category->id = $gc->id;
00396             $grade_category->path = grade_category::build_path($gc);
00397             $grade_category->depth = substr_count($grade_category->path, '/') - 1;
00398             $DB->update_record('grade_categories', $grade_category);
00399         }
00400         $rs->close();
00401 
00402         // Restore marks items as needing update. Update everything now.
00403         grade_regrade_final_grades($this->get_courseid());
00404     }
00405 }
00406 
00412 class restore_decode_interlinks extends restore_execution_step {
00413 
00414     protected function define_execution() {
00415         // Get the decoder (from the plan)
00416         $decoder = $this->task->get_decoder();
00417         restore_decode_processor::register_link_decoders($decoder); // Add decoder contents and rules
00418         // And launch it, everything will be processed
00419         $decoder->execute();
00420     }
00421 }
00422 
00427 class restore_rebuild_course_cache extends restore_execution_step {
00428 
00429     protected function define_execution() {
00430         global $DB;
00431 
00432         // Although there is some sort of auto-recovery of missing sections
00433         // present in course/formats... here we check that all the sections
00434         // from 0 to MAX(section->section) exist, creating them if necessary
00435         $maxsection = $DB->get_field('course_sections', 'MAX(section)', array('course' => $this->get_courseid()));
00436         // Iterate over all sections
00437         for ($i = 0; $i <= $maxsection; $i++) {
00438             // If the section $i doesn't exist, create it
00439             if (!$DB->record_exists('course_sections', array('course' => $this->get_courseid(), 'section' => $i))) {
00440                 $sectionrec = array(
00441                     'course' => $this->get_courseid(),
00442                     'section' => $i);
00443                 $DB->insert_record('course_sections', $sectionrec); // missing section created
00444             }
00445         }
00446 
00447         // Rebuild cache now that all sections are in place
00448         rebuild_course_cache($this->get_courseid());
00449     }
00450 }
00451 
00457 class restore_execute_after_restore extends restore_execution_step {
00458 
00459     protected function define_execution() {
00460 
00461         // Simply call to the execute_after_restore() method of the task
00462         // that always is the restore_final_task
00463         $this->task->launch_execute_after_restore();
00464     }
00465 }
00466 
00467 
00473 class restore_review_pending_block_positions extends restore_execution_step {
00474 
00475     protected function define_execution() {
00476         global $DB;
00477 
00478         // Get all the block_position objects pending to match
00479         $params = array('backupid' => $this->get_restoreid(), 'itemname' => 'block_position');
00480         $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid');
00481         // Process block positions, creating them or accumulating for final step
00482         foreach($rs as $posrec) {
00483             // Get the complete position object (stored as info)
00484             $position = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'block_position', $posrec->itemid)->info;
00485             // If position is for one already mapped (known) contextid
00486             // process it now, creating the position, else nothing to
00487             // do, position finally discarded
00488             if ($newctx = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $position->contextid)) {
00489                 $position->contextid = $newctx->newitemid;
00490                 // Create the block position
00491                 $DB->insert_record('block_positions', $position);
00492             }
00493         }
00494         $rs->close();
00495     }
00496 }
00497 
00504 class restore_process_course_modules_availability extends restore_execution_step {
00505 
00506     protected function define_execution() {
00507         global $CFG, $DB;
00508 
00509         // Site hasn't availability enabled
00510         if (empty($CFG->enableavailability)) {
00511             return;
00512         }
00513 
00514         // Get all the module_availability objects to process
00515         $params = array('backupid' => $this->get_restoreid(), 'itemname' => 'module_availability');
00516         $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid');
00517         // Process availabilities, creating them if everything matches ok
00518         foreach($rs as $availrec) {
00519             $allmatchesok = true;
00520             // Get the complete availabilityobject
00521             $availability = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'module_availability', $availrec->itemid)->info;
00522             // Map the sourcecmid if needed and possible
00523             if (!empty($availability->sourcecmid)) {
00524                 $newcm = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'course_module', $availability->sourcecmid);
00525                 if ($newcm) {
00526                     $availability->sourcecmid = $newcm->newitemid;
00527                 } else {
00528                     $allmatchesok = false; // Failed matching, we won't create this availability rule
00529                 }
00530             }
00531             // Map the gradeitemid if needed and possible
00532             if (!empty($availability->gradeitemid)) {
00533                 $newgi = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'grade_item', $availability->gradeitemid);
00534                 if ($newgi) {
00535                     $availability->gradeitemid = $newgi->newitemid;
00536                 } else {
00537                     $allmatchesok = false; // Failed matching, we won't create this availability rule
00538                 }
00539             }
00540             if ($allmatchesok) { // Everything ok, create the availability rule
00541                 $DB->insert_record('course_modules_availability', $availability);
00542             }
00543         }
00544         $rs->close();
00545     }
00546 }
00547 
00548 
00549 /*
00550  * Execution step that, *conditionally* (if there isn't preloaded information)
00551  * will load the inforef files for all the included course/section/activity tasks
00552  * to backup_temp_ids. They will be stored with "xxxxref" as itemname
00553  */
00554 class restore_load_included_inforef_records extends restore_execution_step {
00555 
00556     protected function define_execution() {
00557 
00558         if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
00559             return;
00560         }
00561 
00562         // Get all the included tasks
00563         $tasks = restore_dbops::get_included_tasks($this->get_restoreid());
00564         foreach ($tasks as $task) {
00565             // Load the inforef.xml file if exists
00566             $inforefpath = $task->get_taskbasepath() . '/inforef.xml';
00567             if (file_exists($inforefpath)) {
00568                 restore_dbops::load_inforef_to_tempids($this->get_restoreid(), $inforefpath); // Load each inforef file to temp_ids
00569             }
00570         }
00571     }
00572 }
00573 
00574 /*
00575  * Execution step that will load all the needed files into backup_files_temp
00576  *   - info: contains the whole original object (times, names...)
00577  * (all them being original ids as loaded from xml)
00578  */
00579 class restore_load_included_files extends restore_structure_step {
00580 
00581     protected function define_structure() {
00582 
00583         $file = new restore_path_element('file', '/files/file');
00584 
00585         return array($file);
00586     }
00587 
00588     // Processing functions go here
00589     public function process_file($data) {
00590 
00591         $data = (object)$data; // handy
00592 
00593         // load it if needed:
00594         //   - it it is one of the annotated inforef files (course/section/activity/block)
00595         //   - it is one "user", "group", "grouping", "grade", "question" or "qtype_xxxx" component file (that aren't sent to inforef ever)
00596         // TODO: qtype_xxx should be replaced by proper backup_qtype_plugin::get_components_and_fileareas() use,
00597         //       but then we'll need to change it to load plugins itself (because this is executed too early in restore)
00598         $isfileref   = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'fileref', $data->id);
00599         $iscomponent = ($data->component == 'user' || $data->component == 'group' ||
00600                         $data->component == 'grouping' || $data->component == 'grade' ||
00601                         $data->component == 'question' || substr($data->component, 0, 5) == 'qtype');
00602         if ($isfileref || $iscomponent) {
00603             restore_dbops::set_backup_files_record($this->get_restoreid(), $data);
00604         }
00605     }
00606 }
00607 
00618 class restore_load_and_map_roles extends restore_execution_step {
00619 
00620     protected function define_execution() {
00621         if ($this->task->get_preloaded_information()) { // if info is already preloaded
00622             return;
00623         }
00624 
00625         $file = $this->get_basepath() . '/roles.xml';
00626         // Load needed toles to temp_ids
00627         restore_dbops::load_roles_to_tempids($this->get_restoreid(), $file);
00628 
00629         // Process roles, mapping/skipping. Any error throws exception
00630         // Note we pass controller's info because it can contain role mapping information
00631         // about manual mappings performed by UI
00632         restore_dbops::process_included_roles($this->get_restoreid(), $this->task->get_courseid(), $this->task->get_userid(), $this->task->is_samesite(), $this->task->get_info()->role_mappings);
00633     }
00634 }
00635 
00642 class restore_load_included_users extends restore_execution_step {
00643 
00644     protected function define_execution() {
00645 
00646         if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
00647             return;
00648         }
00649         if (!$this->task->get_setting_value('users')) { // No userinfo being restored, nothing to do
00650             return;
00651         }
00652         $file = $this->get_basepath() . '/users.xml';
00653         restore_dbops::load_users_to_tempids($this->get_restoreid(), $file); // Load needed users to temp_ids
00654     }
00655 }
00656 
00664 class restore_process_included_users extends restore_execution_step {
00665 
00666     protected function define_execution() {
00667 
00668         if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
00669             return;
00670         }
00671         if (!$this->task->get_setting_value('users')) { // No userinfo being restored, nothing to do
00672             return;
00673         }
00674         restore_dbops::process_included_users($this->get_restoreid(), $this->task->get_courseid(), $this->task->get_userid(), $this->task->is_samesite());
00675     }
00676 }
00677 
00682 class restore_create_included_users extends restore_execution_step {
00683 
00684     protected function define_execution() {
00685 
00686         restore_dbops::create_included_users($this->get_basepath(), $this->get_restoreid(), $this->get_setting_value('user_files'), $this->task->get_userid());
00687     }
00688 }
00689 
00695 class restore_groups_structure_step extends restore_structure_step {
00696 
00697     protected function define_structure() {
00698 
00699         $paths = array(); // Add paths here
00700 
00701         $paths[] = new restore_path_element('group', '/groups/group');
00702         if ($this->get_setting_value('users')) {
00703             $paths[] = new restore_path_element('member', '/groups/group/group_members/group_member');
00704         }
00705         $paths[] = new restore_path_element('grouping', '/groups/groupings/grouping');
00706         $paths[] = new restore_path_element('grouping_group', '/groups/groupings/grouping/grouping_groups/grouping_group');
00707 
00708         return $paths;
00709     }
00710 
00711     // Processing functions go here
00712     public function process_group($data) {
00713         global $DB;
00714 
00715         $data = (object)$data; // handy
00716         $data->courseid = $this->get_courseid();
00717 
00718         $oldid = $data->id;    // need this saved for later
00719 
00720         $restorefiles = false; // Only if we end creating the group
00721 
00722         // Search if the group already exists (by name & description) in the target course
00723         $description_clause = '';
00724         $params = array('courseid' => $this->get_courseid(), 'grname' => $data->name);
00725         if (!empty($data->description)) {
00726             $description_clause = ' AND ' .
00727                                   $DB->sql_compare_text('description') . ' = ' . $DB->sql_compare_text(':description');
00728            $params['description'] = $data->description;
00729         }
00730         if (!$groupdb = $DB->get_record_sql("SELECT *
00731                                                FROM {groups}
00732                                               WHERE courseid = :courseid
00733                                                 AND name = :grname $description_clause", $params)) {
00734             // group doesn't exist, create
00735             $newitemid = $DB->insert_record('groups', $data);
00736             $restorefiles = true; // We'll restore the files
00737         } else {
00738             // group exists, use it
00739             $newitemid = $groupdb->id;
00740         }
00741         // Save the id mapping
00742         $this->set_mapping('group', $oldid, $newitemid, $restorefiles);
00743     }
00744 
00745     public function process_member($data) {
00746         global $DB;
00747 
00748         $data = (object)$data; // handy
00749 
00750         // get parent group->id
00751         $data->groupid = $this->get_new_parentid('group');
00752 
00753         // map user newitemid and insert if not member already
00754         if ($data->userid = $this->get_mappingid('user', $data->userid)) {
00755             if (!$DB->record_exists('groups_members', array('groupid' => $data->groupid, 'userid' => $data->userid))) {
00756                 $DB->insert_record('groups_members', $data);
00757             }
00758         }
00759     }
00760 
00761     public function process_grouping($data) {
00762         global $DB;
00763 
00764         $data = (object)$data; // handy
00765         $data->courseid = $this->get_courseid();
00766 
00767         $oldid = $data->id;    // need this saved for later
00768         $restorefiles = false; // Only if we end creating the grouping
00769 
00770         // Search if the grouping already exists (by name & description) in the target course
00771         $description_clause = '';
00772         $params = array('courseid' => $this->get_courseid(), 'grname' => $data->name);
00773         if (!empty($data->description)) {
00774             $description_clause = ' AND ' .
00775                                   $DB->sql_compare_text('description') . ' = ' . $DB->sql_compare_text(':description');
00776            $params['description'] = $data->description;
00777         }
00778         if (!$groupingdb = $DB->get_record_sql("SELECT *
00779                                                   FROM {groupings}
00780                                                  WHERE courseid = :courseid
00781                                                    AND name = :grname $description_clause", $params)) {
00782             // grouping doesn't exist, create
00783             $newitemid = $DB->insert_record('groupings', $data);
00784             $restorefiles = true; // We'll restore the files
00785         } else {
00786             // grouping exists, use it
00787             $newitemid = $groupingdb->id;
00788         }
00789         // Save the id mapping
00790         $this->set_mapping('grouping', $oldid, $newitemid, $restorefiles);
00791     }
00792 
00793     public function process_grouping_group($data) {
00794         global $DB;
00795 
00796         $data = (object)$data;
00797 
00798         $data->groupingid = $this->get_new_parentid('grouping'); // Use new parentid
00799         $data->groupid    = $this->get_mappingid('group', $data->groupid); // Get from mappings
00800 
00801         $params = array();
00802         $params['groupingid'] = $data->groupingid;
00803         $params['groupid']    = $data->groupid;
00804 
00805         if (!$DB->record_exists('groupings_groups', $params)) {
00806             $DB->insert_record('groupings_groups', $data);  // No need to set this mapping (no child info nor files)
00807         }
00808     }
00809 
00810     protected function after_execute() {
00811         // Add group related files, matching with "group" mappings
00812         $this->add_related_files('group', 'icon', 'group');
00813         $this->add_related_files('group', 'description', 'group');
00814         // Add grouping related files, matching with "grouping" mappings
00815         $this->add_related_files('grouping', 'description', 'grouping');
00816     }
00817 
00818 }
00819 
00824 class restore_scales_structure_step extends restore_structure_step {
00825 
00826     protected function define_structure() {
00827 
00828         $paths = array(); // Add paths here
00829         $paths[] = new restore_path_element('scale', '/scales_definition/scale');
00830         return $paths;
00831     }
00832 
00833     protected function process_scale($data) {
00834         global $DB;
00835 
00836         $data = (object)$data;
00837 
00838         $restorefiles = false; // Only if we end creating the group
00839 
00840         $oldid = $data->id;    // need this saved for later
00841 
00842         // Look for scale (by 'scale' both in standard (course=0) and current course
00843         // with priority to standard scales (ORDER clause)
00844         // scale is not course unique, use get_record_sql to suppress warning
00845         // Going to compare LOB columns so, use the cross-db sql_compare_text() in both sides
00846         $compare_scale_clause = $DB->sql_compare_text('scale')  . ' = ' . $DB->sql_compare_text(':scaledesc');
00847         $params = array('courseid' => $this->get_courseid(), 'scaledesc' => $data->scale);
00848         if (!$scadb = $DB->get_record_sql("SELECT *
00849                                             FROM {scale}
00850                                            WHERE courseid IN (0, :courseid)
00851                                              AND $compare_scale_clause
00852                                         ORDER BY courseid", $params, IGNORE_MULTIPLE)) {
00853             // Remap the user if possible, defaut to user performing the restore if not
00854             $userid = $this->get_mappingid('user', $data->userid);
00855             $data->userid = $userid ? $userid : $this->task->get_userid();
00856             // Remap the course if course scale
00857             $data->courseid = $data->courseid ? $this->get_courseid() : 0;
00858             // If global scale (course=0), check the user has perms to create it
00859             // falling to course scale if not
00860             $systemctx = get_context_instance(CONTEXT_SYSTEM);
00861             if ($data->courseid == 0 && !has_capability('moodle/course:managescales', $systemctx , $this->task->get_userid())) {
00862                 $data->courseid = $this->get_courseid();
00863             }
00864             // scale doesn't exist, create
00865             $newitemid = $DB->insert_record('scale', $data);
00866             $restorefiles = true; // We'll restore the files
00867         } else {
00868             // scale exists, use it
00869             $newitemid = $scadb->id;
00870         }
00871         // Save the id mapping (with files support at system context)
00872         $this->set_mapping('scale', $oldid, $newitemid, $restorefiles, $this->task->get_old_system_contextid());
00873     }
00874 
00875     protected function after_execute() {
00876         // Add scales related files, matching with "scale" mappings
00877         $this->add_related_files('grade', 'scale', 'scale', $this->task->get_old_system_contextid());
00878     }
00879 }
00880 
00881 
00886 class restore_outcomes_structure_step extends restore_structure_step {
00887 
00888     protected function define_structure() {
00889 
00890         $paths = array(); // Add paths here
00891         $paths[] = new restore_path_element('outcome', '/outcomes_definition/outcome');
00892         return $paths;
00893     }
00894 
00895     protected function process_outcome($data) {
00896         global $DB;
00897 
00898         $data = (object)$data;
00899 
00900         $restorefiles = false; // Only if we end creating the group
00901 
00902         $oldid = $data->id;    // need this saved for later
00903 
00904         // Look for outcome (by shortname both in standard (courseid=null) and current course
00905         // with priority to standard outcomes (ORDER clause)
00906         // outcome is not course unique, use get_record_sql to suppress warning
00907         $params = array('courseid' => $this->get_courseid(), 'shortname' => $data->shortname);
00908         if (!$outdb = $DB->get_record_sql('SELECT *
00909                                              FROM {grade_outcomes}
00910                                             WHERE shortname = :shortname
00911                                               AND (courseid = :courseid OR courseid IS NULL)
00912                                          ORDER BY COALESCE(courseid, 0)', $params, IGNORE_MULTIPLE)) {
00913             // Remap the user
00914             $userid = $this->get_mappingid('user', $data->usermodified);
00915             $data->usermodified = $userid ? $userid : $this->task->get_userid();
00916             // Remap the scale
00917             $data->scaleid = $this->get_mappingid('scale', $data->scaleid);
00918             // Remap the course if course outcome
00919             $data->courseid = $data->courseid ? $this->get_courseid() : null;
00920             // If global outcome (course=null), check the user has perms to create it
00921             // falling to course outcome if not
00922             $systemctx = get_context_instance(CONTEXT_SYSTEM);
00923             if (is_null($data->courseid) && !has_capability('moodle/grade:manageoutcomes', $systemctx , $this->task->get_userid())) {
00924                 $data->courseid = $this->get_courseid();
00925             }
00926             // outcome doesn't exist, create
00927             $newitemid = $DB->insert_record('grade_outcomes', $data);
00928             $restorefiles = true; // We'll restore the files
00929         } else {
00930             // scale exists, use it
00931             $newitemid = $outdb->id;
00932         }
00933         // Set the corresponding grade_outcomes_courses record
00934         $outcourserec = new stdclass();
00935         $outcourserec->courseid  = $this->get_courseid();
00936         $outcourserec->outcomeid = $newitemid;
00937         if (!$DB->record_exists('grade_outcomes_courses', (array)$outcourserec)) {
00938             $DB->insert_record('grade_outcomes_courses', $outcourserec);
00939         }
00940         // Save the id mapping (with files support at system context)
00941         $this->set_mapping('outcome', $oldid, $newitemid, $restorefiles, $this->task->get_old_system_contextid());
00942     }
00943 
00944     protected function after_execute() {
00945         // Add outcomes related files, matching with "outcome" mappings
00946         $this->add_related_files('grade', 'outcome', 'outcome', $this->task->get_old_system_contextid());
00947     }
00948 }
00949 
00957 class restore_load_categories_and_questions extends restore_execution_step {
00958 
00959     protected function define_execution() {
00960 
00961         if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
00962             return;
00963         }
00964         $file = $this->get_basepath() . '/questions.xml';
00965         restore_dbops::load_categories_and_questions_to_tempids($this->get_restoreid(), $file);
00966     }
00967 }
00968 
00976 class restore_process_categories_and_questions extends restore_execution_step {
00977 
00978     protected function define_execution() {
00979 
00980         if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
00981             return;
00982         }
00983         restore_dbops::process_categories_and_questions($this->get_restoreid(), $this->task->get_courseid(), $this->task->get_userid(), $this->task->is_samesite());
00984     }
00985 }
00986 
00991 class restore_section_structure_step extends restore_structure_step {
00992 
00993     protected function define_structure() {
00994         $section = new restore_path_element('section', '/section');
00995 
00996         // Apply for 'format' plugins optional paths at section level
00997         $this->add_plugin_structure('format', $section);
00998 
00999         return array($section);
01000     }
01001 
01002     public function process_section($data) {
01003         global $DB;
01004         $data = (object)$data;
01005         $oldid = $data->id; // We'll need this later
01006 
01007         $restorefiles = false;
01008 
01009         // Look for the section
01010         $section = new stdclass();
01011         $section->course  = $this->get_courseid();
01012         $section->section = $data->number;
01013         // Section doesn't exist, create it with all the info from backup
01014         if (!$secrec = $DB->get_record('course_sections', (array)$section)) {
01015             $section->name = $data->name;
01016             $section->summary = $data->summary;
01017             $section->summaryformat = $data->summaryformat;
01018             $section->sequence = '';
01019             $section->visible = $data->visible;
01020             $newitemid = $DB->insert_record('course_sections', $section);
01021             $restorefiles = true;
01022 
01023         // Section exists, update non-empty information
01024         } else {
01025             $section->id = $secrec->id;
01026             if (empty($secrec->name)) {
01027                 $section->name = $data->name;
01028             }
01029             if (empty($secrec->summary)) {
01030                 $section->summary = $data->summary;
01031                 $section->summaryformat = $data->summaryformat;
01032                 $restorefiles = true;
01033             }
01034             $DB->update_record('course_sections', $section);
01035             $newitemid = $secrec->id;
01036         }
01037 
01038         // Annotate the section mapping, with restorefiles option if needed
01039         $this->set_mapping('course_section', $oldid, $newitemid, $restorefiles);
01040 
01041         // set the new course_section id in the task
01042         $this->task->set_sectionid($newitemid);
01043 
01044 
01045         // Commented out. We never modify course->numsections as far as that is used
01046         // by a lot of people to "hide" sections on purpose (so this remains as used to be in Moodle 1.x)
01047         // Note: We keep the code here, to know about and because of the possibility of making this
01048         // optional based on some setting/attribute in the future
01049         // If needed, adjust course->numsections
01050         //if ($numsections = $DB->get_field('course', 'numsections', array('id' => $this->get_courseid()))) {
01051         //    if ($numsections < $section->section) {
01052         //        $DB->set_field('course', 'numsections', $section->section, array('id' => $this->get_courseid()));
01053         //    }
01054         //}
01055     }
01056 
01057     protected function after_execute() {
01058         // Add section related files, with 'course_section' itemid to match
01059         $this->add_related_files('course', 'section', 'course_section');
01060     }
01061 }
01062 
01063 
01070 class restore_course_structure_step extends restore_structure_step {
01071 
01072     protected function define_structure() {
01073 
01074         $course = new restore_path_element('course', '/course');
01075         $category = new restore_path_element('category', '/course/category');
01076         $tag = new restore_path_element('tag', '/course/tags/tag');
01077         $allowed_module = new restore_path_element('allowed_module', '/course/allowed_modules/module');
01078 
01079         // Apply for 'format' plugins optional paths at course level
01080         $this->add_plugin_structure('format', $course);
01081 
01082         // Apply for 'theme' plugins optional paths at course level
01083         $this->add_plugin_structure('theme', $course);
01084 
01085         // Apply for 'report' plugins optional paths at course level
01086         $this->add_plugin_structure('report', $course);
01087 
01088         // Apply for 'course report' plugins optional paths at course level
01089         $this->add_plugin_structure('coursereport', $course);
01090 
01091         // Apply for plagiarism plugins optional paths at course level
01092         $this->add_plugin_structure('plagiarism', $course);
01093 
01094         return array($course, $category, $tag, $allowed_module);
01095     }
01096 
01103     public function process_course($data) {
01104         global $CFG, $DB;
01105 
01106         $data = (object)$data;
01107 
01108         $fullname  = $this->get_setting_value('course_fullname');
01109         $shortname = $this->get_setting_value('course_shortname');
01110         $startdate = $this->get_setting_value('course_startdate');
01111 
01112         // Calculate final course names, to avoid dupes
01113         list($fullname, $shortname) = restore_dbops::calculate_course_names($this->get_courseid(), $fullname, $shortname);
01114 
01115         // Need to change some fields before updating the course record
01116         $data->id = $this->get_courseid();
01117         $data->fullname = $fullname;
01118         $data->shortname= $shortname;
01119 
01120         $context = get_context_instance_by_id($this->task->get_contextid());
01121         if (has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid())) {
01122             $data->idnumber = '';
01123         } else {
01124             unset($data->idnumber);
01125         }
01126 
01127         // Any empty value for course->hiddensections will lead to 0 (default, show collapsed).
01128         // It has been reported that some old 1.9 courses may have it null leading to DB error. MDL-31532
01129         if (empty($data->hiddensections)) {
01130             $data->hiddensections = 0;
01131         }
01132 
01133         // Only restrict modules if original course was and target site too for new courses
01134         $data->restrictmodules = $data->restrictmodules && !empty($CFG->restrictmodulesfor) && $CFG->restrictmodulesfor == 'all';
01135 
01136         $data->startdate= $this->apply_date_offset($data->startdate);
01137         if ($data->defaultgroupingid) {
01138             $data->defaultgroupingid = $this->get_mappingid('grouping', $data->defaultgroupingid);
01139         }
01140         if (empty($CFG->enablecompletion)) {
01141             $data->enablecompletion = 0;
01142             $data->completionstartonenrol = 0;
01143             $data->completionnotify = 0;
01144         }
01145         $languages = get_string_manager()->get_list_of_translations(); // Get languages for quick search
01146         if (!array_key_exists($data->lang, $languages)) {
01147             $data->lang = '';
01148         }
01149 
01150         $themes = get_list_of_themes(); // Get themes for quick search later
01151         if (!array_key_exists($data->theme, $themes) || empty($CFG->allowcoursethemes)) {
01152             $data->theme = '';
01153         }
01154 
01155         // Course record ready, update it
01156         $DB->update_record('course', $data);
01157 
01158         // Role name aliases
01159         restore_dbops::set_course_role_names($this->get_restoreid(), $this->get_courseid());
01160     }
01161 
01162     public function process_category($data) {
01163         // Nothing to do with the category. UI sets it before restore starts
01164     }
01165 
01166     public function process_tag($data) {
01167         global $CFG, $DB;
01168 
01169         $data = (object)$data;
01170 
01171         if (!empty($CFG->usetags)) { // if enabled in server
01172             // TODO: This is highly inneficient. Each time we add one tag
01173             // we fetch all the existing because tag_set() deletes them
01174             // so everything must be reinserted on each call
01175             $tags = array();
01176             $existingtags = tag_get_tags('course', $this->get_courseid());
01177             // Re-add all the existitng tags
01178             foreach ($existingtags as $existingtag) {
01179                 $tags[] = $existingtag->rawname;
01180             }
01181             // Add the one being restored
01182             $tags[] = $data->rawname;
01183             // Send all the tags back to the course
01184             tag_set('course', $this->get_courseid(), $tags);
01185         }
01186     }
01187 
01188     public function process_allowed_module($data) {
01189         global $CFG, $DB;
01190 
01191         $data = (object)$data;
01192 
01193         // only if enabled by admin setting
01194         if (!empty($CFG->restrictmodulesfor) && $CFG->restrictmodulesfor == 'all') {
01195             $available = get_plugin_list('mod');
01196             $mname = $data->modulename;
01197             if (array_key_exists($mname, $available)) {
01198                 if ($module = $DB->get_record('modules', array('name' => $mname, 'visible' => 1))) {
01199                     $rec = new stdclass();
01200                     $rec->course = $this->get_courseid();
01201                     $rec->module = $module->id;
01202                     if (!$DB->record_exists('course_allowed_modules', (array)$rec)) {
01203                         $DB->insert_record('course_allowed_modules', $rec);
01204                     }
01205                 }
01206             }
01207         }
01208     }
01209 
01210     protected function after_execute() {
01211         // Add course related files, without itemid to match
01212         $this->add_related_files('course', 'summary', null);
01213         $this->add_related_files('course', 'legacy', null);
01214     }
01215 }
01216 
01217 
01218 /*
01219  * Structure step that will read the roles.xml file (at course/activity/block levels)
01220  * containig all the role_assignments and overrides for that context. If corresponding to
01221  * one mapped role, they will be applied to target context. Will observe the role_assignments
01222  * setting to decide if ras are restored.
01223  * Note: only ras with component == null are restored as far as the any ra with component
01224  * is handled by one enrolment plugin, hence it will createt the ras later
01225  */
01226 class restore_ras_and_caps_structure_step extends restore_structure_step {
01227 
01228     protected function define_structure() {
01229 
01230         $paths = array();
01231 
01232         // Observe the role_assignments setting
01233         if ($this->get_setting_value('role_assignments')) {
01234             $paths[] = new restore_path_element('assignment', '/roles/role_assignments/assignment');
01235         }
01236         $paths[] = new restore_path_element('override', '/roles/role_overrides/override');
01237 
01238         return $paths;
01239     }
01240 
01249     public function process_assignment($data) {
01250         global $DB;
01251 
01252         $data = (object)$data;
01253 
01254         // Check roleid, userid are one of the mapped ones
01255         if (!$newroleid = $this->get_mappingid('role', $data->roleid)) {
01256             return;
01257         }
01258         if (!$newuserid = $this->get_mappingid('user', $data->userid)) {
01259             return;
01260         }
01261         if (!$DB->record_exists('user', array('id' => $newuserid, 'deleted' => 0))) {
01262             // Only assign roles to not deleted users
01263             return;
01264         }
01265         if (!$contextid = $this->task->get_contextid()) {
01266             return;
01267         }
01268 
01269         if (empty($data->component)) {
01270             // assign standard manual roles
01271             // TODO: role_assign() needs one userid param to be able to specify our restore userid
01272             role_assign($newroleid, $newuserid, $contextid);
01273 
01274         } else if ((strpos($data->component, 'enrol_') === 0)) {
01275             // Deal with enrolment roles
01276             if ($enrolid = $this->get_mappingid('enrol', $data->itemid)) {
01277                 if ($component = $DB->get_field('enrol', 'component', array('id'=>$enrolid))) {
01278                     //note: we have to verify component because it might have changed
01279                     if ($component === 'enrol_manual') {
01280                         // manual is a special case, we do not use components - this owudl happen when converting from other plugin
01281                         role_assign($newroleid, $newuserid, $contextid); //TODO: do we need modifierid?
01282                     } else {
01283                         role_assign($newroleid, $newuserid, $contextid, $component, $enrolid); //TODO: do we need modifierid?
01284                     }
01285                 }
01286             }
01287         }
01288     }
01289 
01290     public function process_override($data) {
01291         $data = (object)$data;
01292 
01293         // Check roleid is one of the mapped ones
01294         $newroleid = $this->get_mappingid('role', $data->roleid);
01295         // If newroleid and context are valid assign it via API (it handles dupes and so on)
01296         if ($newroleid && $this->task->get_contextid()) {
01297             // TODO: assign_capability() needs one userid param to be able to specify our restore userid
01298             // TODO: it seems that assign_capability() doesn't check for valid capabilities at all ???
01299             assign_capability($data->capability, $data->permission, $newroleid, $this->task->get_contextid());
01300         }
01301     }
01302 }
01303 
01308 class restore_enrolments_structure_step extends restore_structure_step {
01309 
01319     protected function execute_condition() {
01320 
01321         // Check it is included in the backup
01322         $fullpath = $this->task->get_taskbasepath();
01323         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
01324         if (!file_exists($fullpath)) {
01325             // Not found, can't restore enrolments info
01326             return false;
01327         }
01328 
01329         return true;
01330     }
01331 
01332     protected function define_structure() {
01333 
01334         $paths = array();
01335 
01336         $paths[] = new restore_path_element('enrol', '/enrolments/enrols/enrol');
01337         $paths[] = new restore_path_element('enrolment', '/enrolments/enrols/enrol/user_enrolments/enrolment');
01338 
01339         return $paths;
01340     }
01341 
01351     public function process_enrol($data) {
01352         global $DB;
01353 
01354         $data = (object)$data;
01355         $oldid = $data->id; // We'll need this later
01356 
01357         $restoretype = plugin_supports('enrol', $data->enrol, ENROL_RESTORE_TYPE, null);
01358 
01359         if ($restoretype !== ENROL_RESTORE_EXACT and $restoretype !== ENROL_RESTORE_NOUSERS) {
01360             // TODO: add complex restore support via custom class
01361             debugging("Skipping '{$data->enrol}' enrolment plugin. Will be implemented before 2.0 release", DEBUG_DEVELOPER);
01362             $this->set_mapping('enrol', $oldid, 0);
01363             return;
01364         }
01365 
01366         // Perform various checks to decide what to do with the enrol plugin
01367         if (!array_key_exists($data->enrol, enrol_get_plugins(false))) {
01368             // TODO: decide if we want to switch to manual enrol - we need UI for this
01369             debugging("Enrol plugin data can not be restored because it is not installed");
01370             $this->set_mapping('enrol', $oldid, 0);
01371             return;
01372 
01373         }
01374         if (!enrol_is_enabled($data->enrol)) {
01375             // TODO: decide if we want to switch to manual enrol - we need UI for this
01376             debugging("Enrol plugin data can not be restored because it is not enabled");
01377             $this->set_mapping('enrol', $oldid, 0);
01378             return;
01379         }
01380 
01381         // map standard fields - plugin has to process custom fields from own restore class
01382         $data->roleid = $this->get_mappingid('role', $data->roleid);
01383         //TODO: should we move the enrol start and end date here?
01384 
01385         // always add instance, if the course does not support multiple instances it just returns NULL
01386         $enrol = enrol_get_plugin($data->enrol);
01387         $courserec = $DB->get_record('course', array('id' => $this->get_courseid())); // Requires object, uses only id!!
01388         if ($newitemid = $enrol->add_instance($courserec, (array)$data)) {
01389             // ok
01390         } else {
01391             if ($instances = $DB->get_records('enrol', array('courseid'=>$courserec->id, 'enrol'=>$data->enrol))) {
01392                 // most probably plugin that supports only one instance
01393                 $newitemid = key($instances);
01394             } else {
01395                 debugging('Can not create new enrol instance or reuse existing');
01396                 $newitemid = 0;
01397             }
01398         }
01399 
01400         if ($restoretype === ENROL_RESTORE_NOUSERS) {
01401             // plugin requests to prevent restore of any users
01402             $newitemid = 0;
01403         }
01404 
01405         $this->set_mapping('enrol', $oldid, $newitemid);
01406     }
01407 
01417     public function process_enrolment($data) {
01418         global $DB;
01419 
01420         $data = (object)$data;
01421 
01422         // Process only if parent instance have been mapped
01423         if ($enrolid = $this->get_new_parentid('enrol')) {
01424             if ($instance = $DB->get_record('enrol', array('id'=>$enrolid))) {
01425                 // And only if user is a mapped one
01426                 if ($userid = $this->get_mappingid('user', $data->userid)) {
01427                     $enrol = enrol_get_plugin($instance->enrol);
01428                     //TODO: do we need specify modifierid?
01429                     $enrol->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status);
01430                     //note: roles are assigned in restore_ras_and_caps_structure_step::process_assignment() processing above
01431                 }
01432             }
01433         }
01434     }
01435 }
01436 
01437 
01441 class restore_fix_restorer_access_step extends restore_execution_step {
01442     protected function define_execution() {
01443         global $CFG, $DB;
01444 
01445         if (!$userid = $this->task->get_userid()) {
01446             return;
01447         }
01448 
01449         if (empty($CFG->restorernewroleid)) {
01450             // Bad luck, no fallback role for restorers specified
01451             return;
01452         }
01453 
01454         $courseid = $this->get_courseid();
01455         $context = context_course::instance($courseid);
01456 
01457         if (is_enrolled($context, $userid, 'moodle/course:update', true) or is_viewing($context, $userid, 'moodle/course:update')) {
01458             // Current user may access the course (admin, category manager or restored teacher enrolment usually)
01459             return;
01460         }
01461 
01462         // Try to add role only - we do not need enrolment if user has moodle/course:view or is already enrolled
01463         role_assign($CFG->restorernewroleid, $userid, $context);
01464 
01465         if (is_enrolled($context, $userid, 'moodle/course:update', true) or is_viewing($context, $userid, 'moodle/course:update')) {
01466             // Extra role is enough, yay!
01467             return;
01468         }
01469 
01470         // The last chance is to create manual enrol if it does not exist and and try to enrol the current user,
01471         // hopefully admin selected suitable $CFG->restorernewroleid ...
01472         if (!enrol_is_enabled('manual')) {
01473             return;
01474         }
01475         if (!$enrol = enrol_get_plugin('manual')) {
01476             return;
01477         }
01478         if (!$DB->record_exists('enrol', array('enrol'=>'manual', 'courseid'=>$courseid))) {
01479             $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
01480             $fields = array('status'=>ENROL_INSTANCE_ENABLED, 'enrolperiod'=>$enrol->get_config('enrolperiod', 0), 'roleid'=>$enrol->get_config('roleid', 0));
01481             $enrol->add_instance($course, $fields);
01482         }
01483 
01484         enrol_try_internal_enrol($courseid, $userid);
01485     }
01486 }
01487 
01488 
01492 class restore_filters_structure_step extends restore_structure_step {
01493 
01494     protected function define_structure() {
01495 
01496         $paths = array();
01497 
01498         $paths[] = new restore_path_element('active', '/filters/filter_actives/filter_active');
01499         $paths[] = new restore_path_element('config', '/filters/filter_configs/filter_config');
01500 
01501         return $paths;
01502     }
01503 
01504     public function process_active($data) {
01505 
01506         $data = (object)$data;
01507 
01508         if (!filter_is_enabled($data->filter)) { // Not installed or not enabled, nothing to do
01509             return;
01510         }
01511         filter_set_local_state($data->filter, $this->task->get_contextid(), $data->active);
01512     }
01513 
01514     public function process_config($data) {
01515 
01516         $data = (object)$data;
01517 
01518         if (!filter_is_enabled($data->filter)) { // Not installed or not enabled, nothing to do
01519             return;
01520         }
01521         filter_set_local_config($data->filter, $this->task->get_contextid(), $data->name, $data->value);
01522     }
01523 }
01524 
01525 
01531 class restore_comments_structure_step extends restore_structure_step {
01532 
01533     protected function define_structure() {
01534 
01535         $paths = array();
01536 
01537         $paths[] = new restore_path_element('comment', '/comments/comment');
01538 
01539         return $paths;
01540     }
01541 
01542     public function process_comment($data) {
01543         global $DB;
01544 
01545         $data = (object)$data;
01546 
01547         // First of all, if the comment has some itemid, ask to the task what to map
01548         $mapping = false;
01549         if ($data->itemid) {
01550             $mapping = $this->task->get_comment_mapping_itemname($data->commentarea);
01551             $data->itemid = $this->get_mappingid($mapping, $data->itemid);
01552         }
01553         // Only restore the comment if has no mapping OR we have found the matching mapping
01554         if (!$mapping || $data->itemid) {
01555             // Only if user mapping and context
01556             $data->userid = $this->get_mappingid('user', $data->userid);
01557             if ($data->userid && $this->task->get_contextid()) {
01558                 $data->contextid = $this->task->get_contextid();
01559                 // Only if there is another comment with same context/user/timecreated
01560                 $params = array('contextid' => $data->contextid, 'userid' => $data->userid, 'timecreated' => $data->timecreated);
01561                 if (!$DB->record_exists('comments', $params)) {
01562                     $DB->insert_record('comments', $data);
01563                 }
01564             }
01565         }
01566     }
01567 }
01568 
01569 class restore_course_completion_structure_step extends restore_structure_step {
01570 
01586     protected function execute_condition() {
01587         global $CFG;
01588 
01589         // First check course completion is enabled on this site
01590         if (empty($CFG->enablecompletion)) {
01591             // Disabled, don't restore course completion
01592             return false;
01593         }
01594 
01595         // Check it is included in the backup
01596         $fullpath = $this->task->get_taskbasepath();
01597         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
01598         if (!file_exists($fullpath)) {
01599             // Not found, can't restore course completion
01600             return false;
01601         }
01602 
01603         // Check we are able to restore all backed up modules
01604         if ($this->task->is_missing_modules()) {
01605             return false;
01606         }
01607 
01608         // Finally check all modules within the backup are being restored.
01609         if ($this->task->is_excluding_activities()) {
01610             return false;
01611         }
01612 
01613         return true;
01614     }
01615 
01621     protected function define_structure() {
01622 
01623         // To know if we are including user completion info
01624         $userinfo = $this->get_setting_value('userscompletion');
01625 
01626         $paths = array();
01627         $paths[] = new restore_path_element('course_completion_criteria', '/course_completion/course_completion_criteria');
01628         $paths[] = new restore_path_element('course_completion_notify', '/course_completion/course_completion_notify');
01629         $paths[] = new restore_path_element('course_completion_aggr_methd', '/course_completion/course_completion_aggr_methd');
01630 
01631         if ($userinfo) {
01632             $paths[] = new restore_path_element('course_completion_crit_compl', '/course_completion/course_completion_criteria/course_completion_crit_completions/course_completion_crit_compl');
01633             $paths[] = new restore_path_element('course_completions', '/course_completion/course_completions');
01634         }
01635 
01636         return $paths;
01637 
01638     }
01639 
01646     public function process_course_completion_criteria($data) {
01647         global $DB;
01648 
01649         $data = (object)$data;
01650         $data->course = $this->get_courseid();
01651 
01652         // Apply the date offset to the time end field
01653         $data->timeend = $this->apply_date_offset($data->timeend);
01654 
01655         // Map the role from the criteria
01656         if (!empty($data->role)) {
01657             $data->role = $this->get_mappingid('role', $data->role);
01658         }
01659 
01660         $skipcriteria = false;
01661 
01662         // If the completion criteria is for a module we need to map the module instance
01663         // to the new module id.
01664         if (!empty($data->moduleinstance) && !empty($data->module)) {
01665             $data->moduleinstance = $this->get_mappingid('course_module', $data->moduleinstance);
01666             if (empty($data->moduleinstance)) {
01667                 $skipcriteria = true;
01668             }
01669         } else {
01670             $data->module = null;
01671             $data->moduleinstance = null;
01672         }
01673 
01674         // We backup the course shortname rather than the ID so that we can match back to the course
01675         if (!empty($data->courseinstanceshortname)) {
01676             $courseinstanceid = $DB->get_field('course', 'id', array('shortname'=>$data->courseinstanceshortname));
01677             if (!$courseinstanceid) {
01678                 $skipcriteria = true;
01679             }
01680         } else {
01681             $courseinstanceid = null;
01682         }
01683         $data->courseinstance = $courseinstanceid;
01684 
01685         if (!$skipcriteria) {
01686             $params = array(
01687                 'course'         => $data->course,
01688                 'criteriatype'   => $data->criteriatype,
01689                 'enrolperiod'    => $data->enrolperiod,
01690                 'courseinstance' => $data->courseinstance,
01691                 'module'         => $data->module,
01692                 'moduleinstance' => $data->moduleinstance,
01693                 'timeend'        => $data->timeend,
01694                 'gradepass'      => $data->gradepass,
01695                 'role'           => $data->role
01696             );
01697             $newid = $DB->insert_record('course_completion_criteria', $params);
01698             $this->set_mapping('course_completion_criteria', $data->id, $newid);
01699         }
01700     }
01701 
01708     public function process_course_completion_crit_compl($data) {
01709         global $DB;
01710 
01711         $data = (object)$data;
01712 
01713         // This may be empty if criteria could not be restored
01714         $data->criteriaid = $this->get_mappingid('course_completion_criteria', $data->criteriaid);
01715 
01716         $data->course = $this->get_courseid();
01717         $data->userid = $this->get_mappingid('user', $data->userid);
01718 
01719         if (!empty($data->criteriaid) && !empty($data->userid)) {
01720             $params = array(
01721                 'userid' => $data->userid,
01722                 'course' => $data->course,
01723                 'criteriaid' => $data->criteriaid,
01724                 'timecompleted' => $this->apply_date_offset($data->timecompleted)
01725             );
01726             if (isset($data->gradefinal)) {
01727                 $params['gradefinal'] = $data->gradefinal;
01728             }
01729             if (isset($data->unenroled)) {
01730                 $params['unenroled'] = $data->unenroled;
01731             }
01732             if (isset($data->deleted)) {
01733                 $params['deleted'] = $data->deleted;
01734             }
01735             $DB->insert_record('course_completion_crit_compl', $params);
01736         }
01737     }
01738 
01745     public function process_course_completions($data) {
01746         global $DB;
01747 
01748         $data = (object)$data;
01749 
01750         $data->course = $this->get_courseid();
01751         $data->userid = $this->get_mappingid('user', $data->userid);
01752 
01753         if (!empty($data->userid)) {
01754             $params = array(
01755                 'userid' => $data->userid,
01756                 'course' => $data->course,
01757                 'deleted' => $data->deleted,
01758                 'timenotified' => $this->apply_date_offset($data->timenotified),
01759                 'timeenrolled' => $this->apply_date_offset($data->timeenrolled),
01760                 'timestarted' => $this->apply_date_offset($data->timestarted),
01761                 'timecompleted' => $this->apply_date_offset($data->timecompleted),
01762                 'reaggregate' => $data->reaggregate
01763             );
01764             $DB->insert_record('course_completions', $params);
01765         }
01766     }
01767 
01777     public function process_course_completion_notify($data) {
01778         global $DB;
01779 
01780         $data = (object)$data;
01781 
01782         $data->course = $this->get_courseid();
01783         if (!empty($data->role)) {
01784             $data->role = $this->get_mappingid('role', $data->role);
01785         }
01786 
01787         $params = array(
01788             'course' => $data->course,
01789             'role' => $data->role,
01790             'message' => $data->message,
01791             'timesent' => $this->apply_date_offset($data->timesent),
01792         );
01793         $DB->insert_record('course_completion_notify', $params);
01794     }
01795 
01802     public function process_course_completion_aggr_methd($data) {
01803         global $DB;
01804 
01805         $data = (object)$data;
01806 
01807         $data->course = $this->get_courseid();
01808 
01809         // Only create the course_completion_aggr_methd records if
01810         // the target course has not them defined. MDL-28180
01811         if (!$DB->record_exists('course_completion_aggr_methd', array(
01812                     'course' => $data->course,
01813                     'criteriatype' => $data->criteriatype))) {
01814             $params = array(
01815                 'course' => $data->course,
01816                 'criteriatype' => $data->criteriatype,
01817                 'method' => $data->method,
01818                 'value' => $data->value,
01819             );
01820             $DB->insert_record('course_completion_aggr_methd', $params);
01821         }
01822     }
01823 }
01824 
01825 
01841 class restore_course_logs_structure_step extends restore_structure_step {
01842 
01852     protected function execute_condition() {
01853 
01854         // Check it is included in the backup
01855         $fullpath = $this->task->get_taskbasepath();
01856         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
01857         if (!file_exists($fullpath)) {
01858             // Not found, can't restore course logs
01859             return false;
01860         }
01861 
01862         return true;
01863     }
01864 
01865     protected function define_structure() {
01866 
01867         $paths = array();
01868 
01869         // Simple, one plain level of information contains them
01870         $paths[] = new restore_path_element('log', '/logs/log');
01871 
01872         return $paths;
01873     }
01874 
01875     protected function process_log($data) {
01876         global $DB;
01877 
01878         $data = (object)($data);
01879 
01880         $data->time = $this->apply_date_offset($data->time);
01881         $data->userid = $this->get_mappingid('user', $data->userid);
01882         $data->course = $this->get_courseid();
01883         $data->cmid = 0;
01884 
01885         // For any reason user wasn't remapped ok, stop processing this
01886         if (empty($data->userid)) {
01887             return;
01888         }
01889 
01890         // Everything ready, let's delegate to the restore_logs_processor
01891 
01892         // Set some fixed values that will save tons of DB requests
01893         $values = array(
01894             'course' => $this->get_courseid());
01895         // Get instance and process log record
01896         $data = restore_logs_processor::get_instance($this->task, $values)->process_log_record($data);
01897 
01898         // If we have data, insert it, else something went wrong in the restore_logs_processor
01899         if ($data) {
01900             $DB->insert_record('log', $data);
01901         }
01902     }
01903 }
01904 
01909 class restore_activity_logs_structure_step extends restore_course_logs_structure_step {
01910 
01911     protected function process_log($data) {
01912         global $DB;
01913 
01914         $data = (object)($data);
01915 
01916         $data->time = $this->apply_date_offset($data->time);
01917         $data->userid = $this->get_mappingid('user', $data->userid);
01918         $data->course = $this->get_courseid();
01919         $data->cmid = $this->task->get_moduleid();
01920 
01921         // For any reason user wasn't remapped ok, stop processing this
01922         if (empty($data->userid)) {
01923             return;
01924         }
01925 
01926         // Everything ready, let's delegate to the restore_logs_processor
01927 
01928         // Set some fixed values that will save tons of DB requests
01929         $values = array(
01930             'course' => $this->get_courseid(),
01931             'course_module' => $this->task->get_moduleid(),
01932             $this->task->get_modulename() => $this->task->get_activityid());
01933         // Get instance and process log record
01934         $data = restore_logs_processor::get_instance($this->task, $values)->process_log_record($data);
01935 
01936         // If we have data, insert it, else something went wrong in the restore_logs_processor
01937         if ($data) {
01938             $DB->insert_record('log', $data);
01939         }
01940     }
01941 }
01942 
01943 
01947 class restore_activity_grading_structure_step extends restore_structure_step {
01948 
01952      protected function execute_condition() {
01953 
01954         $fullpath = $this->task->get_taskbasepath();
01955         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
01956         if (!file_exists($fullpath)) {
01957             return false;
01958         }
01959 
01960         return true;
01961     }
01962 
01963 
01967     protected function define_structure() {
01968 
01969         $paths = array();
01970         $userinfo = $this->get_setting_value('userinfo');
01971 
01972         $paths[] = new restore_path_element('grading_area', '/areas/area');
01973 
01974         $definition = new restore_path_element('grading_definition', '/areas/area/definitions/definition');
01975         $paths[] = $definition;
01976         $this->add_plugin_structure('gradingform', $definition);
01977 
01978         if ($userinfo) {
01979             $instance = new restore_path_element('grading_instance',
01980                 '/areas/area/definitions/definition/instances/instance');
01981             $paths[] = $instance;
01982             $this->add_plugin_structure('gradingform', $instance);
01983         }
01984 
01985         return $paths;
01986     }
01987 
01993     protected function process_grading_area($data) {
01994         global $DB;
01995 
01996         $task = $this->get_task();
01997         $data = (object)$data;
01998         $oldid = $data->id;
01999         $data->component = 'mod_'.$task->get_modulename();
02000         $data->contextid = $task->get_contextid();
02001 
02002         $newid = $DB->insert_record('grading_areas', $data);
02003         $this->set_mapping('grading_area', $oldid, $newid);
02004     }
02005 
02011     protected function process_grading_definition($data) {
02012         global $DB;
02013 
02014         $task = $this->get_task();
02015         $data = (object)$data;
02016         $oldid = $data->id;
02017         $data->areaid = $this->get_new_parentid('grading_area');
02018         $data->copiedfromid = null;
02019         $data->timecreated = time();
02020         $data->usercreated = $task->get_userid();
02021         $data->timemodified = $data->timecreated;
02022         $data->usermodified = $data->usercreated;
02023 
02024         $newid = $DB->insert_record('grading_definitions', $data);
02025         $this->set_mapping('grading_definition', $oldid, $newid, true);
02026     }
02027 
02033     protected function process_grading_instance($data) {
02034         global $DB;
02035 
02036         $data = (object)$data;
02037 
02038         // new form definition id
02039         $newformid = $this->get_new_parentid('grading_definition');
02040 
02041         // get the name of the area we are restoring to
02042         $sql = "SELECT ga.areaname
02043                   FROM {grading_definitions} gd
02044                   JOIN {grading_areas} ga ON gd.areaid = ga.id
02045                  WHERE gd.id = ?";
02046         $areaname = $DB->get_field_sql($sql, array($newformid), MUST_EXIST);
02047 
02048         // get the mapped itemid - the activity module is expected to define the mappings
02049         // for each gradable area
02050         $newitemid = $this->get_mappingid(restore_gradingform_plugin::itemid_mapping($areaname), $data->itemid);
02051 
02052         $oldid = $data->id;
02053         $data->definitionid = $newformid;
02054         $data->raterid = $this->get_mappingid('user', $data->raterid);
02055         $data->itemid = $newitemid;
02056 
02057         $newid = $DB->insert_record('grading_instances', $data);
02058         $this->set_mapping('grading_instance', $oldid, $newid);
02059     }
02060 
02064     protected function after_execute() {
02065         // Add files embedded into the definition description
02066         $this->add_related_files('grading', 'description', 'grading_definition');
02067     }
02068 }
02069 
02070 
02078 class restore_activity_grades_structure_step extends restore_structure_step {
02079 
02080     protected function define_structure() {
02081 
02082         $paths = array();
02083         $userinfo = $this->get_setting_value('userinfo');
02084 
02085         $paths[] = new restore_path_element('grade_item', '/activity_gradebook/grade_items/grade_item');
02086         $paths[] = new restore_path_element('grade_letter', '/activity_gradebook/grade_letters/grade_letter');
02087         if ($userinfo) {
02088             $paths[] = new restore_path_element('grade_grade',
02089                            '/activity_gradebook/grade_items/grade_item/grade_grades/grade_grade');
02090         }
02091         return $paths;
02092     }
02093 
02094     protected function process_grade_item($data) {
02095         global $DB;
02096 
02097         $data = (object)($data);
02098         $oldid       = $data->id;        // We'll need these later
02099         $oldparentid = $data->categoryid;
02100         $courseid = $this->get_courseid();
02101 
02102         // make sure top course category exists, all grade items will be associated
02103         // to it. Later, if restoring the whole gradebook, categories will be introduced
02104         $coursecat = grade_category::fetch_course_category($courseid);
02105         $coursecatid = $coursecat->id; // Get the categoryid to be used
02106 
02107         $idnumber = null;
02108         if (!empty($data->idnumber)) {
02109             // Don't get any idnumber from course module. Keep them as they are in grade_item->idnumber
02110             // Reason: it's not clear what happens with outcomes->idnumber or activities with multiple items (workshop)
02111             // so the best is to keep the ones already in the gradebook
02112             // Potential problem: duplicates if same items are restored more than once. :-(
02113             // This needs to be fixed in some way (outcomes & activities with multiple items)
02114             // $data->idnumber     = get_coursemodule_from_instance($data->itemmodule, $data->iteminstance)->idnumber;
02115             // In any case, verify always for uniqueness
02116             $sql = "SELECT cm.id
02117                       FROM {course_modules} cm
02118                      WHERE cm.course = :courseid AND
02119                            cm.idnumber = :idnumber AND
02120                            cm.id <> :cmid";
02121             $params = array(
02122                 'courseid' => $courseid,
02123                 'idnumber' => $data->idnumber,
02124                 'cmid' => $this->task->get_moduleid()
02125             );
02126             if (!$DB->record_exists_sql($sql, $params) && !$DB->record_exists('grade_items', array('courseid' => $courseid, 'idnumber' => $data->idnumber))) {
02127                 $idnumber = $data->idnumber;
02128             }
02129         }
02130 
02131         unset($data->id);
02132         $data->categoryid   = $coursecatid;
02133         $data->courseid     = $this->get_courseid();
02134         $data->iteminstance = $this->task->get_activityid();
02135         $data->idnumber     = $idnumber;
02136         $data->scaleid      = $this->get_mappingid('scale', $data->scaleid);
02137         $data->outcomeid    = $this->get_mappingid('outcome', $data->outcomeid);
02138         $data->timecreated  = $this->apply_date_offset($data->timecreated);
02139         $data->timemodified = $this->apply_date_offset($data->timemodified);
02140 
02141         $gradeitem = new grade_item($data, false);
02142         $gradeitem->insert('restore');
02143 
02144         //sortorder is automatically assigned when inserting. Re-instate the previous sortorder
02145         $gradeitem->sortorder = $data->sortorder;
02146         $gradeitem->update('restore');
02147 
02148         // Set mapping, saving the original category id into parentitemid
02149         // gradebook restore (final task) will need it to reorganise items
02150         $this->set_mapping('grade_item', $oldid, $gradeitem->id, false, null, $oldparentid);
02151     }
02152 
02153     protected function process_grade_grade($data) {
02154         $data = (object)($data);
02155 
02156         unset($data->id);
02157         $data->itemid = $this->get_new_parentid('grade_item');
02158         $data->userid = $this->get_mappingid('user', $data->userid);
02159         $data->usermodified = $this->get_mappingid('user', $data->usermodified);
02160         $data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid);
02161         // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled?
02162         $data->overridden = $this->apply_date_offset($data->overridden);
02163 
02164         $grade = new grade_grade($data, false);
02165         $grade->insert('restore');
02166         // no need to save any grade_grade mapping
02167     }
02168 
02175     protected function process_grade_letter($data) {
02176         global $DB;
02177 
02178         $data = (object)$data;
02179 
02180         $data->contextid = $this->task->get_contextid();
02181         $newitemid = $DB->insert_record('grade_letters', $data);
02182         // no need to save any grade_letter mapping
02183     }
02184 }
02185 
02186 
02193 class restore_block_instance_structure_step extends restore_structure_step {
02194 
02195     protected function define_structure() {
02196 
02197         $paths = array();
02198 
02199         $paths[] = new restore_path_element('block', '/block', true); // Get the whole XML together
02200         $paths[] = new restore_path_element('block_position', '/block/block_positions/block_position');
02201 
02202         return $paths;
02203     }
02204 
02205     public function process_block($data) {
02206         global $DB, $CFG;
02207 
02208         $data = (object)$data; // Handy
02209         $oldcontextid = $data->contextid;
02210         $oldid        = $data->id;
02211         $positions = isset($data->block_positions['block_position']) ? $data->block_positions['block_position'] : array();
02212 
02213         // Look for the parent contextid
02214         if (!$data->parentcontextid = $this->get_mappingid('context', $data->parentcontextid)) {
02215             throw new restore_step_exception('restore_block_missing_parent_ctx', $data->parentcontextid);
02216         }
02217 
02218         // TODO: it would be nice to use standard plugin supports instead of this instance_allow_multiple()
02219         // If there is already one block of that type in the parent context
02220         // and the block is not multiple, stop processing
02221         // Use blockslib loader / method executor
02222         if (!block_method_result($data->blockname, 'instance_allow_multiple')) {
02223             if ($DB->record_exists_sql("SELECT bi.id
02224                                           FROM {block_instances} bi
02225                                           JOIN {block} b ON b.name = bi.blockname
02226                                          WHERE bi.parentcontextid = ?
02227                                            AND bi.blockname = ?", array($data->parentcontextid, $data->blockname))) {
02228                 return false;
02229             }
02230         }
02231 
02232         // If there is already one block of that type in the parent context
02233         // with the same showincontexts, pagetypepattern, subpagepattern, defaultregion and configdata
02234         // stop processing
02235         $params = array(
02236             'blockname' => $data->blockname, 'parentcontextid' => $data->parentcontextid,
02237             'showinsubcontexts' => $data->showinsubcontexts, 'pagetypepattern' => $data->pagetypepattern,
02238             'subpagepattern' => $data->subpagepattern, 'defaultregion' => $data->defaultregion);
02239         if ($birecs = $DB->get_records('block_instances', $params)) {
02240             foreach($birecs as $birec) {
02241                 if ($birec->configdata == $data->configdata) {
02242                     return false;
02243                 }
02244             }
02245         }
02246 
02247         // Set task old contextid, blockid and blockname once we know them
02248         $this->task->set_old_contextid($oldcontextid);
02249         $this->task->set_old_blockid($oldid);
02250         $this->task->set_blockname($data->blockname);
02251 
02252         // Let's look for anything within configdata neededing processing
02253         // (nulls and uses of legacy file.php)
02254         if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
02255             $configdata = (array)unserialize(base64_decode($data->configdata));
02256             foreach ($configdata as $attribute => $value) {
02257                 if (in_array($attribute, $attrstotransform)) {
02258                     $configdata[$attribute] = $this->contentprocessor->process_cdata($value);
02259                 }
02260             }
02261             $data->configdata = base64_encode(serialize((object)$configdata));
02262         }
02263 
02264         // Create the block instance
02265         $newitemid = $DB->insert_record('block_instances', $data);
02266         // Save the mapping (with restorefiles support)
02267         $this->set_mapping('block_instance', $oldid, $newitemid, true);
02268         // Create the block context
02269         $newcontextid = get_context_instance(CONTEXT_BLOCK, $newitemid)->id;
02270         // Save the block contexts mapping and sent it to task
02271         $this->set_mapping('context', $oldcontextid, $newcontextid);
02272         $this->task->set_contextid($newcontextid);
02273         $this->task->set_blockid($newitemid);
02274 
02275         // Restore block fileareas if declared
02276         $component = 'block_' . $this->task->get_blockname();
02277         foreach ($this->task->get_fileareas() as $filearea) { // Simple match by contextid. No itemname needed
02278             $this->add_related_files($component, $filearea, null);
02279         }
02280 
02281         // Process block positions, creating them or accumulating for final step
02282         foreach($positions as $position) {
02283             $position = (object)$position;
02284             $position->blockinstanceid = $newitemid; // The instance is always the restored one
02285             // If position is for one already mapped (known) contextid
02286             // process it now, creating the position
02287             if ($newpositionctxid = $this->get_mappingid('context', $position->contextid)) {
02288                 $position->contextid = $newpositionctxid;
02289                 // Create the block position
02290                 $DB->insert_record('block_positions', $position);
02291 
02292             // The position belongs to an unknown context, send it to backup_ids
02293             // to process them as part of the final steps of restore. We send the
02294             // whole $position object there, hence use the low level method.
02295             } else {
02296                 restore_dbops::set_backup_ids_record($this->get_restoreid(), 'block_position', $position->id, 0, null, $position);
02297             }
02298         }
02299     }
02300 }
02301 
02311 class restore_module_structure_step extends restore_structure_step {
02312 
02313     protected function define_structure() {
02314         global $CFG;
02315 
02316         $paths = array();
02317 
02318         $module = new restore_path_element('module', '/module');
02319         $paths[] = $module;
02320         if ($CFG->enableavailability) {
02321             $paths[] = new restore_path_element('availability', '/module/availability_info/availability');
02322         }
02323 
02324         // Apply for 'format' plugins optional paths at module level
02325         $this->add_plugin_structure('format', $module);
02326 
02327         // Apply for 'plagiarism' plugins optional paths at module level
02328         $this->add_plugin_structure('plagiarism', $module);
02329 
02330         return $paths;
02331     }
02332 
02333     protected function process_module($data) {
02334         global $CFG, $DB;
02335 
02336         $data = (object)$data;
02337         $oldid = $data->id;
02338 
02339         $this->task->set_old_moduleversion($data->version);
02340 
02341         $data->course = $this->task->get_courseid();
02342         $data->module = $DB->get_field('modules', 'id', array('name' => $data->modulename));
02343         // Map section (first try by course_section mapping match. Useful in course and section restores)
02344         $data->section = $this->get_mappingid('course_section', $data->sectionid);
02345         if (!$data->section) { // mapping failed, try to get section by sectionnumber matching
02346             $params = array(
02347                 'course' => $this->get_courseid(),
02348                 'section' => $data->sectionnumber);
02349             $data->section = $DB->get_field('course_sections', 'id', $params);
02350         }
02351         if (!$data->section) { // sectionnumber failed, try to get first section in course
02352             $params = array(
02353                 'course' => $this->get_courseid());
02354             $data->section = $DB->get_field('course_sections', 'MIN(id)', $params);
02355         }
02356         if (!$data->section) { // no sections in course, create section 0 and 1 and assign module to 1
02357             $sectionrec = array(
02358                 'course' => $this->get_courseid(),
02359                 'section' => 0);
02360             $DB->insert_record('course_sections', $sectionrec); // section 0
02361             $sectionrec = array(
02362                 'course' => $this->get_courseid(),
02363                 'section' => 1);
02364             $data->section = $DB->insert_record('course_sections', $sectionrec); // section 1
02365         }
02366         $data->groupingid= $this->get_mappingid('grouping', $data->groupingid);      // grouping
02367         if (!$CFG->enablegroupmembersonly) {                                         // observe groupsmemberonly
02368             $data->groupmembersonly = 0;
02369         }
02370         if (!grade_verify_idnumber($data->idnumber, $this->get_courseid())) {        // idnumber uniqueness
02371             $data->idnumber = '';
02372         }
02373         if (empty($CFG->enablecompletion)) { // completion
02374             $data->completion = 0;
02375             $data->completiongradeitemnumber = null;
02376             $data->completionview = 0;
02377             $data->completionexpected = 0;
02378         } else {
02379             $data->completionexpected = $this->apply_date_offset($data->completionexpected);
02380         }
02381         if (empty($CFG->enableavailability)) {
02382             $data->availablefrom = 0;
02383             $data->availableuntil = 0;
02384             $data->showavailability = 0;
02385         } else {
02386             $data->availablefrom = $this->apply_date_offset($data->availablefrom);
02387             $data->availableuntil= $this->apply_date_offset($data->availableuntil);
02388         }
02389         // Backups that did not include showdescription, set it to default 0
02390         // (this is not totally necessary as it has a db default, but just to
02391         // be explicit).
02392         if (!isset($data->showdescription)) {
02393             $data->showdescription = 0;
02394         }
02395         $data->instance = 0; // Set to 0 for now, going to create it soon (next step)
02396 
02397         // course_module record ready, insert it
02398         $newitemid = $DB->insert_record('course_modules', $data);
02399         // save mapping
02400         $this->set_mapping('course_module', $oldid, $newitemid);
02401         // set the new course_module id in the task
02402         $this->task->set_moduleid($newitemid);
02403         // we can now create the context safely
02404         $ctxid = get_context_instance(CONTEXT_MODULE, $newitemid)->id;
02405         // set the new context id in the task
02406         $this->task->set_contextid($ctxid);
02407         // update sequence field in course_section
02408         if ($sequence = $DB->get_field('course_sections', 'sequence', array('id' => $data->section))) {
02409             $sequence .= ',' . $newitemid;
02410         } else {
02411             $sequence = $newitemid;
02412         }
02413         $DB->set_field('course_sections', 'sequence', $sequence, array('id' => $data->section));
02414     }
02415 
02416 
02417     protected function process_availability($data) {
02418         $data = (object)$data;
02419         // Simply going to store the whole availability record now, we'll process
02420         // all them later in the final task (once all actvivities have been restored)
02421         // Let's call the low level one to be able to store the whole object
02422         $data->coursemoduleid = $this->task->get_moduleid(); // Let add the availability cmid
02423         restore_dbops::set_backup_ids_record($this->get_restoreid(), 'module_availability', $data->id, 0, null, $data);
02424     }
02425 }
02426 
02433 class restore_userscompletion_structure_step extends restore_structure_step {
02440      protected function execute_condition() {
02441          global $CFG;
02442 
02443          // Completion disabled in this site, don't execute
02444          if (empty($CFG->enablecompletion)) {
02445              return false;
02446          }
02447 
02448          // No user completion info found, don't execute
02449         $fullpath = $this->task->get_taskbasepath();
02450         $fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
02451          if (!file_exists($fullpath)) {
02452              return false;
02453          }
02454 
02455          // Arrived here, execute the step
02456          return true;
02457      }
02458 
02459      protected function define_structure() {
02460 
02461         $paths = array();
02462 
02463         $paths[] = new restore_path_element('completion', '/completions/completion');
02464 
02465         return $paths;
02466     }
02467 
02468     protected function process_completion($data) {
02469         global $DB;
02470 
02471         $data = (object)$data;
02472 
02473         $data->coursemoduleid = $this->task->get_moduleid();
02474         $data->userid = $this->get_mappingid('user', $data->userid);
02475         $data->timemodified = $this->apply_date_offset($data->timemodified);
02476 
02477         // Find the existing record
02478         $existing = $DB->get_record('course_modules_completion', array(
02479                 'coursemoduleid' => $data->coursemoduleid,
02480                 'userid' => $data->userid), 'id, timemodified');
02481         // Check we didn't already insert one for this cmid and userid
02482         // (there aren't supposed to be duplicates in that field, but
02483         // it was possible until MDL-28021 was fixed).
02484         if ($existing) {
02485             // Update it to these new values, but only if the time is newer
02486             if ($existing->timemodified < $data->timemodified) {
02487                 $data->id = $existing->id;
02488                 $DB->update_record('course_modules_completion', $data);
02489             }
02490         } else {
02491             // Normal entry where it doesn't exist already
02492             $DB->insert_record('course_modules_completion', $data);
02493         }
02494     }
02495 }
02496 
02502 abstract class restore_activity_structure_step extends restore_structure_step {
02503 
02504     protected function add_subplugin_structure($subplugintype, $element) {
02505 
02506         global $CFG;
02507 
02508         // Check the requested subplugintype is a valid one
02509         $subpluginsfile = $CFG->dirroot . '/mod/' . $this->task->get_modulename() . '/db/subplugins.php';
02510         if (!file_exists($subpluginsfile)) {
02511              throw new restore_step_exception('activity_missing_subplugins_php_file', $this->task->get_modulename());
02512         }
02513         include($subpluginsfile);
02514         if (!array_key_exists($subplugintype, $subplugins)) {
02515              throw new restore_step_exception('incorrect_subplugin_type', $subplugintype);
02516         }
02517         // Get all the restore path elements, looking across all the subplugin dirs
02518         $subpluginsdirs = get_plugin_list($subplugintype);
02519         foreach ($subpluginsdirs as $name => $subpluginsdir) {
02520             $classname = 'restore_' . $subplugintype . '_' . $name . '_subplugin';
02521             $restorefile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
02522             if (file_exists($restorefile)) {
02523                 require_once($restorefile);
02524                 $restoresubplugin = new $classname($subplugintype, $name, $this);
02525                 // Add subplugin paths to the step
02526                 $this->prepare_pathelements($restoresubplugin->define_subplugin_structure($element));
02527             }
02528         }
02529     }
02530 
02536     public function get_task() {
02537         return $this->task;
02538     }
02539 
02544     protected function prepare_activity_structure($paths) {
02545 
02546         $paths[] = new restore_path_element('activity', '/activity');
02547 
02548         return $paths;
02549     }
02550 
02554     protected function process_activity($data) {
02555         $data = (object)$data;
02556         $this->task->set_old_contextid($data->contextid); // Save old contextid in task
02557         $this->set_mapping('context', $data->contextid, $this->task->get_contextid()); // Set the mapping
02558         $this->task->set_old_activityid($data->id); // Save old activityid in task
02559     }
02560 
02565     protected function apply_activity_instance($newitemid) {
02566         global $DB;
02567 
02568         $this->task->set_activityid($newitemid); // Save activity id in task
02569         // Apply the id to course_sections->instanceid
02570         $DB->set_field('course_modules', 'instance', $newitemid, array('id' => $this->task->get_moduleid()));
02571         // Do the mapping for modulename, preparing it for files by oldcontext
02572         $modulename = $this->task->get_modulename();
02573         $oldid = $this->task->get_old_activityid();
02574         $this->set_mapping($modulename, $oldid, $newitemid, true);
02575     }
02576 }
02577 
02584 class restore_create_categories_and_questions extends restore_structure_step {
02585 
02586     protected function define_structure() {
02587 
02588         $category = new restore_path_element('question_category', '/question_categories/question_category');
02589         $question = new restore_path_element('question', '/question_categories/question_category/questions/question');
02590         $hint = new restore_path_element('question_hint',
02591                 '/question_categories/question_category/questions/question/question_hints/question_hint');
02592 
02593         // Apply for 'qtype' plugins optional paths at question level
02594         $this->add_plugin_structure('qtype', $question);
02595 
02596         return array($category, $question, $hint);
02597     }
02598 
02599     protected function process_question_category($data) {
02600         global $DB;
02601 
02602         $data = (object)$data;
02603         $oldid = $data->id;
02604 
02605         // Check we have one mapping for this category
02606         if (!$mapping = $this->get_mapping('question_category', $oldid)) {
02607             return self::SKIP_ALL_CHILDREN; // No mapping = this category doesn't need to be created/mapped
02608         }
02609 
02610         // Check we have to create the category (newitemid = 0)
02611         if ($mapping->newitemid) {
02612             return; // newitemid != 0, this category is going to be mapped. Nothing to do
02613         }
02614 
02615         // Arrived here, newitemid = 0, we need to create the category
02616         // we'll do it at parentitemid context, but for CONTEXT_MODULE
02617         // categories, that will be created at CONTEXT_COURSE and moved
02618         // to module context later when the activity is created
02619         if ($mapping->info->contextlevel == CONTEXT_MODULE) {
02620             $mapping->parentitemid = $this->get_mappingid('context', $this->task->get_old_contextid());
02621         }
02622         $data->contextid = $mapping->parentitemid;
02623 
02624         // Let's create the question_category and save mapping
02625         $newitemid = $DB->insert_record('question_categories', $data);
02626         $this->set_mapping('question_category', $oldid, $newitemid);
02627         // Also annotate them as question_category_created, we need
02628         // that later when remapping parents
02629         $this->set_mapping('question_category_created', $oldid, $newitemid, false, null, $data->contextid);
02630     }
02631 
02632     protected function process_question($data) {
02633         global $DB;
02634 
02635         $data = (object)$data;
02636         $oldid = $data->id;
02637 
02638         // Check we have one mapping for this question
02639         if (!$questionmapping = $this->get_mapping('question', $oldid)) {
02640             return; // No mapping = this question doesn't need to be created/mapped
02641         }
02642 
02643         // Get the mapped category (cannot use get_new_parentid() because not
02644         // all the categories have been created, so it is not always available
02645         // Instead we get the mapping for the question->parentitemid because
02646         // we have loaded qcatids there for all parsed questions
02647         $data->category = $this->get_mappingid('question_category', $questionmapping->parentitemid);
02648 
02649         // In the past, there were some very sloppy values of penalty. Fix them.
02650         if ($data->penalty >= 0.33 && $data->penalty <= 0.34) {
02651             $data->penalty = 0.3333333;
02652         }
02653         if ($data->penalty >= 0.66 && $data->penalty <= 0.67) {
02654             $data->penalty = 0.6666667;
02655         }
02656         if ($data->penalty >= 1) {
02657             $data->penalty = 1;
02658         }
02659 
02660         $data->timecreated  = $this->apply_date_offset($data->timecreated);
02661         $data->timemodified = $this->apply_date_offset($data->timemodified);
02662 
02663         $userid = $this->get_mappingid('user', $data->createdby);
02664         $data->createdby = $userid ? $userid : $this->task->get_userid();
02665 
02666         $userid = $this->get_mappingid('user', $data->modifiedby);
02667         $data->modifiedby = $userid ? $userid : $this->task->get_userid();
02668 
02669         // With newitemid = 0, let's create the question
02670         if (!$questionmapping->newitemid) {
02671             $newitemid = $DB->insert_record('question', $data);
02672             $this->set_mapping('question', $oldid, $newitemid);
02673             // Also annotate them as question_created, we need
02674             // that later when remapping parents (keeping the old categoryid as parentid)
02675             $this->set_mapping('question_created', $oldid, $newitemid, false, null, $questionmapping->parentitemid);
02676         } else {
02677             // By performing this set_mapping() we make get_old/new_parentid() to work for all the
02678             // children elements of the 'question' one (so qtype plugins will know the question they belong to)
02679             $this->set_mapping('question', $oldid, $questionmapping->newitemid);
02680         }
02681 
02682         // Note, we don't restore any question files yet
02683         // as far as the CONTEXT_MODULE categories still
02684         // haven't their contexts to be restored to
02685         // The {@link restore_create_question_files}, executed in the final step
02686         // step will be in charge of restoring all the question files
02687     }
02688 
02689         protected function process_question_hint($data) {
02690         global $DB;
02691 
02692         $data = (object)$data;
02693         $oldid = $data->id;
02694 
02695         // Detect if the question is created or mapped
02696         $oldquestionid   = $this->get_old_parentid('question');
02697         $newquestionid   = $this->get_new_parentid('question');
02698         $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
02699 
02700         // If the question has been created by restore, we need to create its question_answers too
02701         if ($questioncreated) {
02702             // Adjust some columns
02703             $data->questionid = $newquestionid;
02704             // Insert record
02705             $newitemid = $DB->insert_record('question_hints', $data);
02706 
02707         // The question existed, we need to map the existing question_hints
02708         } else {
02709             // Look in question_hints by hint text matching
02710             $sql = 'SELECT id
02711                       FROM {question_hints}
02712                      WHERE questionid = ?
02713                        AND ' . $DB->sql_compare_text('hint', 255) . ' = ' . $DB->sql_compare_text('?', 255);
02714             $params = array($newquestionid, $data->hint);
02715             $newitemid = $DB->get_field_sql($sql, $params);
02716             // If we haven't found the newitemid, something has gone really wrong, question in DB
02717             // is missing hints, exception
02718             if (!$newitemid) {
02719                 $info = new stdClass();
02720                 $info->filequestionid = $oldquestionid;
02721                 $info->dbquestionid   = $newquestionid;
02722                 $info->hint           = $data->hint;
02723                 throw new restore_step_exception('error_question_hint_missing_in_db', $info);
02724             }
02725         }
02726         // Create mapping (I'm not sure if this is really needed?)
02727         $this->set_mapping('question_hint', $oldid, $newitemid);
02728     }
02729 
02730     protected function after_execute() {
02731         global $DB;
02732 
02733         // First of all, recode all the created question_categories->parent fields
02734         $qcats = $DB->get_records('backup_ids_temp', array(
02735                      'backupid' => $this->get_restoreid(),
02736                      'itemname' => 'question_category_created'));
02737         foreach ($qcats as $qcat) {
02738             $newparent = 0;
02739             $dbcat = $DB->get_record('question_categories', array('id' => $qcat->newitemid));
02740             // Get new parent (mapped or created, so we look in quesiton_category mappings)
02741             if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
02742                                  'backupid' => $this->get_restoreid(),
02743                                  'itemname' => 'question_category',
02744                                  'itemid'   => $dbcat->parent))) {
02745                 // contextids must match always, as far as we always include complete qbanks, just check it
02746                 $newparentctxid = $DB->get_field('question_categories', 'contextid', array('id' => $newparent));
02747                 if ($dbcat->contextid == $newparentctxid) {
02748                     $DB->set_field('question_categories', 'parent', $newparent, array('id' => $dbcat->id));
02749                 } else {
02750                     $newparent = 0; // No ctx match for both cats, no parent relationship
02751                 }
02752             }
02753             // Here with $newparent empty, problem with contexts or remapping, set it to top cat
02754             if (!$newparent) {
02755                 $DB->set_field('question_categories', 'parent', 0, array('id' => $dbcat->id));
02756             }
02757         }
02758 
02759         // Now, recode all the created question->parent fields
02760         $qs = $DB->get_records('backup_ids_temp', array(
02761                   'backupid' => $this->get_restoreid(),
02762                   'itemname' => 'question_created'));
02763         foreach ($qs as $q) {
02764             $newparent = 0;
02765             $dbq = $DB->get_record('question', array('id' => $q->newitemid));
02766             // Get new parent (mapped or created, so we look in question mappings)
02767             if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
02768                                  'backupid' => $this->get_restoreid(),
02769                                  'itemname' => 'question',
02770                                  'itemid'   => $dbq->parent))) {
02771                 $DB->set_field('question', 'parent', $newparent, array('id' => $dbq->id));
02772             }
02773         }
02774 
02775         // Note, we don't restore any question files yet
02776         // as far as the CONTEXT_MODULE categories still
02777         // haven't their contexts to be restored to
02778         // The {@link restore_create_question_files}, executed in the final step
02779         // step will be in charge of restoring all the question files
02780     }
02781 }
02782 
02788 class restore_move_module_questions_categories extends restore_execution_step {
02789 
02790     protected function define_execution() {
02791         global $DB;
02792 
02793         $contexts = restore_dbops::restore_get_question_banks($this->get_restoreid(), CONTEXT_MODULE);
02794         foreach ($contexts as $contextid => $contextlevel) {
02795             // Only if context mapping exists (i.e. the module has been restored)
02796             if ($newcontext = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $contextid)) {
02797                 // Update all the qcats having their parentitemid set to the original contextid
02798                 $modulecats = $DB->get_records_sql("SELECT itemid, newitemid
02799                                                       FROM {backup_ids_temp}
02800                                                      WHERE backupid = ?
02801                                                        AND itemname = 'question_category'
02802                                                        AND parentitemid = ?", array($this->get_restoreid(), $contextid));
02803                 foreach ($modulecats as $modulecat) {
02804                     $DB->set_field('question_categories', 'contextid', $newcontext->newitemid, array('id' => $modulecat->newitemid));
02805                     // And set new contextid also in question_category mapping (will be
02806                     // used by {@link restore_create_question_files} later
02807                     restore_dbops::set_backup_ids_record($this->get_restoreid(), 'question_category', $modulecat->itemid, $modulecat->newitemid, $newcontext->newitemid);
02808                 }
02809             }
02810         }
02811     }
02812 }
02813 
02823 class restore_create_question_files extends restore_execution_step {
02824 
02825     protected function define_execution() {
02826         global $DB;
02827 
02828         // Let's process only created questions
02829         $questionsrs = $DB->get_recordset_sql("SELECT bi.itemid, bi.newitemid, bi.parentitemid, q.qtype
02830                                                FROM {backup_ids_temp} bi
02831                                                JOIN {question} q ON q.id = bi.newitemid
02832                                               WHERE bi.backupid = ?
02833                                                 AND bi.itemname = 'question_created'", array($this->get_restoreid()));
02834         foreach ($questionsrs as $question) {
02835             // Get question_category mapping, it contains the target context for the question
02836             if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'question_category', $question->parentitemid)) {
02837                 // Something went really wrong, cannot find the question_category for the question
02838                 debugging('Error fetching target context for question', DEBUG_DEVELOPER);
02839                 continue;
02840             }
02841             // Calculate source and target contexts
02842             $oldctxid = $qcatmapping->info->contextid;
02843             $newctxid = $qcatmapping->parentitemid;
02844 
02845             // Add common question files (question and question_answer ones)
02846             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
02847                                               $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true);
02848             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
02849                                               $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true);
02850             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answer',
02851                                               $oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true);
02852             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
02853                                               $oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true);
02854             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'hint',
02855                                               $oldctxid, $this->task->get_userid(), 'question_hint', null, $newctxid, true);
02856             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'correctfeedback',
02857                                               $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true);
02858             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'partiallycorrectfeedback',
02859                                               $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true);
02860             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'incorrectfeedback',
02861                                               $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true);
02862             // Add qtype dependent files
02863             $components = backup_qtype_plugin::get_components_and_fileareas($question->qtype);
02864             foreach ($components as $component => $fileareas) {
02865                 foreach ($fileareas as $filearea => $mapping) {
02866                     // Use itemid only if mapping is question_created
02867                     $itemid = ($mapping == 'question_created') ? $question->itemid : null;
02868                     restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
02869                                                       $oldctxid, $this->task->get_userid(), $mapping, $itemid, $newctxid, true);
02870                 }
02871             }
02872         }
02873         $questionsrs->close();
02874     }
02875 }
02876 
02881 abstract class restore_questions_activity_structure_step extends restore_activity_structure_step {
02883     protected $qtypes = array();
02885     protected $newquestionids = array();
02886 
02891     protected function add_question_usages($element, &$paths) {
02892         // Check $element is restore_path_element
02893         if (! $element instanceof restore_path_element) {
02894             throw new restore_step_exception('element_must_be_restore_path_element', $element);
02895         }
02896         // Check $paths is one array
02897         if (!is_array($paths)) {
02898             throw new restore_step_exception('paths_must_be_array', $paths);
02899         }
02900         $paths[] = new restore_path_element('question_usage',
02901                 $element->get_path() . '/question_usage');
02902         $paths[] = new restore_path_element('question_attempt',
02903                 $element->get_path() . '/question_usage/question_attempts/question_attempt');
02904         $paths[] = new restore_path_element('question_attempt_step',
02905                 $element->get_path() . '/question_usage/question_attempts/question_attempt/steps/step',
02906                 true);
02907         $paths[] = new restore_path_element('question_attempt_step_data',
02908                 $element->get_path() . '/question_usage/question_attempts/question_attempt/steps/step/response/variable');
02909     }
02910 
02914     protected function process_question_usage($data) {
02915         global $DB;
02916 
02917         // Clear our caches.
02918         $this->qtypes = array();
02919         $this->newquestionids = array();
02920 
02921         $data = (object)$data;
02922         $oldid = $data->id;
02923 
02924         $oldcontextid = $this->get_task()->get_old_contextid();
02925         $data->contextid  = $this->get_mappingid('context', $this->task->get_old_contextid());
02926 
02927         // Everything ready, insert (no mapping needed)
02928         $newitemid = $DB->insert_record('question_usages', $data);
02929 
02930         $this->inform_new_usage_id($newitemid);
02931 
02932         $this->set_mapping('question_usage', $oldid, $newitemid, false);
02933     }
02934 
02941     abstract protected function inform_new_usage_id($newusageid);
02942 
02946     protected function process_question_attempt($data) {
02947         global $DB;
02948 
02949         $data = (object)$data;
02950         $oldid = $data->id;
02951         $question = $this->get_mapping('question', $data->questionid);
02952 
02953         $data->questionusageid = $this->get_new_parentid('question_usage');
02954         $data->questionid      = $question->newitemid;
02955         $data->timemodified    = $this->apply_date_offset($data->timemodified);
02956 
02957         $newitemid = $DB->insert_record('question_attempts', $data);
02958 
02959         $this->set_mapping('question_attempt', $oldid, $newitemid);
02960         $this->qtypes[$newitemid] = $question->info->qtype;
02961         $this->newquestionids[$newitemid] = $data->questionid;
02962     }
02963 
02967     protected function process_question_attempt_step($data) {
02968         global $DB;
02969 
02970         $data = (object)$data;
02971         $oldid = $data->id;
02972 
02973         // Pull out the response data.
02974         $response = array();
02975         if (!empty($data->response['variable'])) {
02976             foreach ($data->response['variable'] as $variable) {
02977                 $response[$variable['name']] = $variable['value'];
02978             }
02979         }
02980         unset($data->response);
02981 
02982         $data->questionattemptid = $this->get_new_parentid('question_attempt');
02983         $data->timecreated = $this->apply_date_offset($data->timecreated);
02984         $data->userid      = $this->get_mappingid('user', $data->userid);
02985 
02986         // Everything ready, insert and create mapping (needed by question_sessions)
02987         $newitemid = $DB->insert_record('question_attempt_steps', $data);
02988         $this->set_mapping('question_attempt_step', $oldid, $newitemid, true);
02989 
02990         // Now process the response data.
02991         $response = $this->questions_recode_response_data(
02992                 $this->qtypes[$data->questionattemptid],
02993                 $this->newquestionids[$data->questionattemptid],
02994                 $data->sequencenumber, $response);
02995         foreach ($response as $name => $value) {
02996             $row = new stdClass();
02997             $row->attemptstepid = $newitemid;
02998             $row->name = $name;
02999             $row->value = $value;
03000             $DB->insert_record('question_attempt_step_data', $row, false);
03001         }
03002     }
03003 
03011     public function questions_recode_response_data(
03012             $qtype, $newquestionid, $sequencenumber, array $response) {
03013         $qtyperestorer = $this->get_qtype_restorer($qtype);
03014         if ($qtyperestorer) {
03015             $response = $qtyperestorer->recode_response($newquestionid, $sequencenumber, $response);
03016         }
03017         return $response;
03018     }
03019 
03026     protected function questions_recode_layout($layout) {
03027         // Extracts question id from sequence
03028         if ($questionids = explode(',', $layout)) {
03029             foreach ($questionids as $id => $questionid) {
03030                 if ($questionid) { // If it is zero then this is a pagebreak, don't translate
03031                     $newquestionid = $this->get_mappingid('question', $questionid);
03032                     $questionids[$id] = $newquestionid;
03033                 }
03034             }
03035         }
03036         return implode(',', $questionids);
03037     }
03038 
03044     protected function get_qtype_restorer($qtype) {
03045         // Build one static cache to store {@link restore_qtype_plugin}
03046         // while we are needing them, just to save zillions of instantiations
03047         // or using static stuff that will break our nice API
03048         static $qtypeplugins = array();
03049 
03050         if (!isset($qtypeplugins[$qtype])) {
03051             $classname = 'restore_qtype_' . $qtype . '_plugin';
03052             if (class_exists($classname)) {
03053                 $qtypeplugins[$qtype] = new $classname('qtype', $qtype, $this);
03054             } else {
03055                 $qtypeplugins[$qtype] = null;
03056             }
03057         }
03058         return $qtypeplugins[$qtype];
03059     }
03060 
03061     protected function after_execute() {
03062         parent::after_execute();
03063 
03064         // Restore any files belonging to responses.
03065         foreach (question_engine::get_all_response_file_areas() as $filearea) {
03066             $this->add_related_files('question', $filearea, 'question_attempt_step');
03067         }
03068     }
03069 
03082     protected function add_legacy_question_attempt_data($element, &$paths) {
03083         global $CFG;
03084         require_once($CFG->dirroot . '/question/engine/upgrade/upgradelib.php');
03085 
03086         // Check $element is restore_path_element
03087         if (!($element instanceof restore_path_element)) {
03088             throw new restore_step_exception('element_must_be_restore_path_element', $element);
03089         }
03090         // Check $paths is one array
03091         if (!is_array($paths)) {
03092             throw new restore_step_exception('paths_must_be_array', $paths);
03093         }
03094 
03095         $paths[] = new restore_path_element('question_state',
03096                 $element->get_path() . '/states/state');
03097         $paths[] = new restore_path_element('question_session',
03098                 $element->get_path() . '/sessions/session');
03099     }
03100 
03101     protected function get_attempt_upgrader() {
03102         if (empty($this->attemptupgrader)) {
03103             $this->attemptupgrader = new question_engine_attempt_upgrader();
03104             $this->attemptupgrader->prepare_to_restore();
03105         }
03106         return $this->attemptupgrader;
03107     }
03108 
03117     protected function process_legacy_quiz_attempt_data($data, $quiz) {
03118         global $DB;
03119         $upgrader = $this->get_attempt_upgrader();
03120 
03121         $data = (object)$data;
03122 
03123         $layout = explode(',', $data->layout);
03124         $newlayout = $layout;
03125 
03126         // Convert each old question_session into a question_attempt.
03127         $qas = array();
03128         foreach (explode(',', $quiz->oldquestions) as $questionid) {
03129             if ($questionid == 0) {
03130                 continue;
03131             }
03132 
03133             $newquestionid = $this->get_mappingid('question', $questionid);
03134             if (!$newquestionid) {
03135                 throw new restore_step_exception('questionattemptreferstomissingquestion',
03136                         $questionid, $questionid);
03137             }
03138 
03139             $question = $upgrader->load_question($newquestionid, $quiz->id);
03140 
03141             foreach ($layout as $key => $qid) {
03142                 if ($qid == $questionid) {
03143                     $newlayout[$key] = $newquestionid;
03144                 }
03145             }
03146 
03147             list($qsession, $qstates) = $this->find_question_session_and_states(
03148                     $data, $questionid);
03149 
03150             if (empty($qsession) || empty($qstates)) {
03151                 throw new restore_step_exception('questionattemptdatamissing',
03152                         $questionid, $questionid);
03153             }
03154 
03155             list($qsession, $qstates) = $this->recode_legacy_response_data(
03156                     $question, $qsession, $qstates);
03157 
03158             $data->layout = implode(',', $newlayout);
03159             $qas[$newquestionid] = $upgrader->convert_question_attempt(
03160                     $quiz, $data, $question, $qsession, $qstates);
03161         }
03162 
03163         // Now create a new question_usage.
03164         $usage = new stdClass();
03165         $usage->component = 'mod_quiz';
03166         $usage->contextid = $this->get_mappingid('context', $this->task->get_old_contextid());
03167         $usage->preferredbehaviour = $quiz->preferredbehaviour;
03168         $usage->id = $DB->insert_record('question_usages', $usage);
03169 
03170         $this->inform_new_usage_id($usage->id);
03171 
03172         $data->uniqueid = $usage->id;
03173         $upgrader->save_usage($quiz->preferredbehaviour, $data, $qas, $quiz->questions);
03174     }
03175 
03176     protected function find_question_session_and_states($data, $questionid) {
03177         $qsession = null;
03178         foreach ($data->sessions['session'] as $session) {
03179             if ($session['questionid'] == $questionid) {
03180                 $qsession = (object) $session;
03181                 break;
03182             }
03183         }
03184 
03185         $qstates = array();
03186         foreach ($data->states['state'] as $state) {
03187             if ($state['question'] == $questionid) {
03188                 // It would be natural to use $state['seq_number'] as the array-key
03189                 // here, but it seems that buggy behaviour in 2.0 and early can
03190                 // mean that that is not unique, so we use id, which is guaranteed
03191                 // to be unique.
03192                 $qstates[$state['id']] = (object) $state;
03193             }
03194         }
03195         ksort($qstates);
03196         $qstates = array_values($qstates);
03197 
03198         return array($qsession, $qstates);
03199     }
03200 
03207     protected function recode_legacy_response_data($question, $qsession, $qstates) {
03208         $qsession->questionid = $question->id;
03209 
03210         foreach ($qstates as &$state) {
03211             $state->question = $question->id;
03212             $state->answer = $this->restore_recode_legacy_answer($state, $question->qtype);
03213         }
03214 
03215         return array($qsession, $qstates);
03216     }
03217 
03223     public function restore_recode_legacy_answer($state, $qtype) {
03224         $restorer = $this->get_qtype_restorer($qtype);
03225         if ($restorer) {
03226             return $restorer->recode_legacy_state_answer($state);
03227         } else {
03228             return $state->answer;
03229         }
03230     }
03231 }
 All Data Structures Namespaces Files Functions Variables Enumerations