/** * Duplicates a Moodle module in an existing course * @param int $cmid Course module id * @param int $courseid Course id * @return int New course module id */ function local_ltiprovider_duplicate_module($cmid, $courseid, $newidnumber, $lticontext) { global $CFG, $DB, $USER; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; require_once $CFG->libdir . '/filelib.php'; if (empty($USER)) { // Emulate session. cron_setup_user(); } $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); $cm = get_coursemodule_from_id('', $cmid, 0, true, MUST_EXIST); $cmcontext = context_module::instance($cm->id); $context = context_course::instance($course->id); if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) { $url = course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)); print_error('duplicatenosupport', 'error', $url, $a); } // backup the activity $admin = get_admin(); $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id); $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); // restore the backup immediately $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id, backup::TARGET_CURRENT_ADDING); if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } print_r($precheckresults); die; } } $rc->execute_plan(); $newcmid = null; $tasks = $rc->get_plan()->get_tasks(); foreach ($tasks as $task) { if (is_subclass_of($task, 'restore_activity_task')) { if ($task->get_old_contextid() == $cmcontext->id) { $newcmid = $task->get_moduleid(); break; } } } $rc->destroy(); if ($module = $DB->get_record('course_modules', array('id' => $newcmid))) { $module->idnumber = $newidnumber; $DB->update_record('course_modules', $module); } $newtoolid = 0; if ($tools = $DB->get_records('local_ltiprovider', array('contextid' => $cmcontext->id))) { $newcmcontext = context_module::instance($newcmid); foreach ($tools as $tool) { $tool->courseid = $course->id; $tool->contextid = $newcmcontext->id; $newtoolid = $DB->insert_record('local_ltiprovider', $tool); } } if (!$newtoolid) { $tool = local_ltiprovider_create_tool($course->id, $newcmcontext->id, $lticontext); } if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } return $newcmid; }
public function test_backup_restore() { // TODO this test does not check if userids are correctly mapped global $CFG, $DB; core_php_time_limit::raise(); // Set to admin user. $this->setAdminUser(); $gen_mod = new mod_ratingallocate_generated_module($this); $course1 = $gen_mod->course; // Create backup file and save it to the backup location. $bc = new backup_controller(backup::TYPE_1ACTIVITY, $gen_mod->mod_db->cmid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2); $bc->execute_plan(); $results = $bc->get_results(); $file = $results['backup_destination']; //TODO: Necessary to ensure backward compatibility if (tgz_packer::is_tgz_file($file)) { $fp = get_file_packer('application/x-gzip'); } else { $fp = get_file_packer(); } $filepath = $CFG->dataroot . '/temp/backup/test-restore-course'; $file->extract_to_pathname($fp, $filepath); $bc->destroy(); unset($bc); // Create a course that we are going to restore the other course to. $course2 = $this->getDataGenerator()->create_course(); // Now restore the course. $rc = new restore_controller('test-restore-course', $course2->id, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2, backup::TARGET_NEW_COURSE); $rc->execute_precheck(); $rc->execute_plan(); $unset_values = function ($elem1, $elem2, $varname) { $this->assertNotEquals($elem1->{$varname}, $elem2->{$varname}); $result = array($elem1->{$varname}, $elem2->{$varname}); unset($elem1->{$varname}); unset($elem2->{$varname}); return $result; }; $ratingallocate1 = $DB->get_record(this_db\ratingallocate::TABLE, array(this_db\ratingallocate::COURSE => $course1->id)); $ratingallocate2 = $DB->get_record(this_db\ratingallocate::TABLE, array(this_db\ratingallocate::COURSE => $course2->id)); list($rating_id1, $rating_id2) = $unset_values($ratingallocate1, $ratingallocate2, this_db\ratingallocate::ID); $unset_values($ratingallocate1, $ratingallocate2, this_db\ratingallocate::COURSE); $this->assertEquals($ratingallocate1, $ratingallocate2); $choices1 = $DB->get_records(this_db\ratingallocate_choices::TABLE, array(this_db\ratingallocate_choices::RATINGALLOCATEID => $rating_id1), this_db\ratingallocate_choices::TITLE); $choices2 = $DB->get_records(this_db\ratingallocate_choices::TABLE, array(this_db\ratingallocate_choices::RATINGALLOCATEID => $rating_id2), this_db\ratingallocate_choices::TITLE); $this->assertCount(2, $choices1); $this->assertCount(2, array_values($choices2)); $choice2_copy = $choices2; foreach ($choices1 as $choice1) { //work with copies $choice2 = json_decode(json_encode(array_shift($choice2_copy))); $choice1 = json_decode(json_encode($choice1)); list($choiceid1, $choiceid2) = $unset_values($choice1, $choice2, this_db\ratingallocate_choices::ID); $unset_values($choice1, $choice2, this_db\ratingallocate_choices::RATINGALLOCATEID); $this->assertEquals($choice1, $choice2); // compare ratings for this choice $ratings1 = array_values($DB->get_records(this_db\ratingallocate_ratings::TABLE, array(this_db\ratingallocate_ratings::CHOICEID => $choiceid1), this_db\ratingallocate_ratings::USERID)); $ratings2 = array_values($DB->get_records(this_db\ratingallocate_ratings::TABLE, array(this_db\ratingallocate_ratings::CHOICEID => $choiceid2), this_db\ratingallocate_ratings::USERID)); $this->assertEquals(count($ratings1), count($ratings2)); $ratings2_copy = $ratings2; foreach ($ratings1 as $rating1) { $rating2 = json_decode(json_encode(array_shift($ratings2_copy))); $rating1 = json_decode(json_encode($rating1)); $unset_values($rating1, $rating2, this_db\ratingallocate_ratings::CHOICEID); $unset_values($rating1, $rating2, this_db\ratingallocate_ratings::ID); $this->assertEquals($rating1, $rating2); } } // compare allocations $allocations1 = $DB->get_records(this_db\ratingallocate_allocations::TABLE, array(this_db\ratingallocate_allocations::RATINGALLOCATEID => $rating_id1), this_db\ratingallocate_allocations::USERID); $allocations2 = $DB->get_records(this_db\ratingallocate_allocations::TABLE, array(this_db\ratingallocate_allocations::RATINGALLOCATEID => $rating_id2), this_db\ratingallocate_allocations::USERID); // number of allocations is equal //$this->assertCount(count($allocations1), $allocations2); $this->assertCount(count($gen_mod->allocations), $allocations2); // create function that can be used to replace $map_allocation_to_choice_title = function (&$alloc, $choices) { $alloc->{'choice_title'} = $choices[$alloc->{this_db\ratingallocate_allocations::CHOICEID}]->{this_db\ratingallocate_choices::TITLE}; }; // compare allocations in detail! $alloc2 = reset($allocations2); foreach ($allocations1 as &$alloc1) { $map_allocation_to_choice_title($alloc1, $choices1); $map_allocation_to_choice_title($alloc2, $choices2); $unset_values($alloc1, $alloc2, this_db\ratingallocate_allocations::RATINGALLOCATEID); $unset_values($alloc1, $alloc2, this_db\ratingallocate_allocations::CHOICEID); $unset_values($alloc1, $alloc2, this_db\ratingallocate_allocations::ID); $alloc2 = next($allocations2); } $this->assertEquals(array_values($allocations1), array_values($allocations2)); }
/** * */ public function apply_preset($userpreset, $torestorer = true) { global $DB, $CFG, $USER; $df = mod_dataform_dataform::instance($this->_dataformid); // Extract the backup file to the temp folder. $folder = 'tmp-' . $df->context->id . '-' . time(); $backuptempdir = make_temp_directory("backup/{$folder}"); $fs = get_file_storage(); $file = $fs->get_file_by_id($userpreset); $zipper = get_file_packer($file->get_mimetype()); $file->extract_to_pathname($zipper, $backuptempdir); require_once "{$CFG->dirroot}/backup/util/includes/restore_includes.php"; // Required preparation due to restorer assumption that this should be a new activity // Anonymous users cleanup. $DB->delete_records_select('user', $DB->sql_like('firstname', '?'), array('%anonfirstname%')); // Grading area removal. $DB->delete_records('grading_areas', array('contextid' => $df->context->id)); $transaction = $DB->start_delegated_transaction(); $rc = new restore_controller($folder, $df->course->id, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, backup::TARGET_CURRENT_ADDING); if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backuptempdir); } } } // Get the dataform restore activity task. $tasks = $rc->get_plan()->get_tasks(); $dataformtask = null; foreach ($tasks as &$task) { if ($task instanceof restore_dataform_activity_task) { $dataformtask =& $task; break; } } if ($dataformtask) { $dataformtask->set_activityid($df->id); $dataformtask->set_moduleid($df->cm->id); $dataformtask->set_contextid($df->context->id); if ($torestorer) { $dataformtask->set_ownerid($USER->id); } $rc->set_status(backup::STATUS_AWAITING); $rc->execute_plan(); $transaction->allow_commit(); // Rc cleanup. $rc->destroy(); // Anonymous users cleanup. $DB->delete_records_select('user', $DB->sql_like('firstname', '?'), array('%anonfirstname%')); return true; } else { $rc->destroy(); } return false; }
/** * Duplicate a module on the course. * * @param object $course The course * @param object $cm The course module to duplicate * @throws moodle_exception if the plugin doesn't support duplication * @return Object containing: * - fullcontent: The HTML markup for the created CM * - cmid: The CMID of the newly created CM * - redirect: Whether to trigger a redirect following this change */ function mod_duplicate_activity($course, $cm, $sr = null) { global $CFG, $USER, $PAGE, $DB; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; require_once $CFG->libdir . '/filelib.php'; $a = new stdClass(); $a->modtype = get_string('modulename', $cm->modname); $a->modname = format_string($cm->name); if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) { throw new moodle_exception('duplicatenosupport', 'error'); } // backup the activity $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); // restore the backup immediately $rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); $cmcontext = context_module::instance($cm->id); if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } } } $rc->execute_plan(); // now a bit hacky part follows - we try to get the cmid of the newly // restored copy of the module $newcmid = null; $tasks = $rc->get_plan()->get_tasks(); foreach ($tasks as $task) { error_log("Looking at a task"); if (is_subclass_of($task, 'restore_activity_task')) { error_log("Looking at a restore_activity_task task"); if ($task->get_old_contextid() == $cmcontext->id) { error_log("Contexts match"); $newcmid = $task->get_moduleid(); break; } } } // if we know the cmid of the new course module, let us move it // right below the original one. otherwise it will stay at the // end of the section if ($newcmid) { $info = get_fast_modinfo($course); $newcm = $info->get_cm($newcmid); $section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course)); moveto_module($newcm, $section, $cm); moveto_module($cm, $section, $newcm); // Trigger course module created event. We can trigger the event only if we know the newcmid. $event = \core\event\course_module_created::create_from_cm($newcm); $event->trigger(); } rebuild_course_cache($cm->course); $rc->destroy(); if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } $resp = new stdClass(); if ($newcm) { $courserenderer = $PAGE->get_renderer('core', 'course'); $completioninfo = new completion_info($course); $modulehtml = $courserenderer->course_section_cm($course, $completioninfo, $newcm, null, array()); $resp->fullcontent = $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sr); $resp->cmid = $newcm->id; } else { // Trigger a redirect $resp->redirect = true; } return $resp; }
/** * Imports a course * * @param int $importfrom The id of the course we are importing from * @param int $importto The id of the course we are importing to * @param bool $deletecontent Whether to delete the course we are importing to content * @param array $options List of backup options * @return null * @since Moodle 2.4 */ public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) { global $CFG, $USER, $DB; require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); // Parameter validation. $params = self::validate_parameters( self::import_course_parameters(), array( 'importfrom' => $importfrom, 'importto' => $importto, 'deletecontent' => $deletecontent, 'options' => $options ) ); if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) { throw new moodle_exception('invalidextparam', 'webservice', '', $params['deletecontent']); } // Context validation. if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) { throw new moodle_exception('invalidcourseid', 'error'); } if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) { throw new moodle_exception('invalidcourseid', 'error'); } $importfromcontext = context_course::instance($importfrom->id); self::validate_context($importfromcontext); $importtocontext = context_course::instance($importto->id); self::validate_context($importtocontext); $backupdefaults = array( 'activities' => 1, 'blocks' => 1, 'filters' => 1 ); $backupsettings = array(); // Check for backup and restore options. if (!empty($params['options'])) { foreach ($params['options'] as $option) { // Strict check for a correct value (allways 1 or 0, true or false). $value = clean_param($option['value'], PARAM_INT); if ($value !== 0 and $value !== 1) { throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); } if (!isset($backupdefaults[$option['name']])) { throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); } $backupsettings[$option['name']] = $value; } } // Capability checking. require_capability('moodle/backup:backuptargetimport', $importfromcontext); require_capability('moodle/restore:restoretargetimport', $importtocontext); $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); foreach ($backupsettings as $name => $value) { $bc->get_plan()->get_setting($name)->set_value($value); } $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); // Restore the backup immediately. // Check if we must delete the contents of the destination course. if ($params['deletecontent']) { $restoretarget = backup::TARGET_EXISTING_DELETING; } else { $restoretarget = backup::TARGET_EXISTING_ADDING; } $rc = new restore_controller($backupid, $importto->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget); foreach ($backupsettings as $name => $value) { $rc->get_plan()->get_setting($name)->set_value($value); } if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } $errorinfo = ''; foreach ($precheckresults['errors'] as $error) { $errorinfo .= $error; } if (array_key_exists('warnings', $precheckresults)) { foreach ($precheckresults['warnings'] as $warning) { $errorinfo .= $warning; } } throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo); } } else { if ($restoretarget == backup::TARGET_EXISTING_DELETING) { restore_dbops::delete_course_content($importto->id); } } $rc->execute_plan(); $rc->destroy(); if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } return null; }
/** * Restore a course. * * @param int $backupid The backup ID. * @param int $courseid The course ID to restore in, or 0. * @param int $userid The ID of the user performing the restore. * @return stdClass The updated course object. */ protected function restore_course($backupid, $courseid, $userid) { global $DB; $target = backup::TARGET_CURRENT_ADDING; if (!$courseid) { $target = backup::TARGET_NEW_COURSE; $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}"); $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid); } $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, $target); $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true); $rc->execute_precheck(); $rc->execute_plan(); $course = $DB->get_record('course', array('id' => $rc->get_courseid())); $rc->destroy(); unset($rc); return $course; }
/** * Test that triggering a course_restored event works as expected. */ public function test_course_restored_event() { global $CFG; // Get the necessary files to perform backup and restore. require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; $this->resetAfterTest(); // Set to admin user. $this->setAdminUser(); // The user id is going to be 2 since we are the admin user. $userid = 2; // Create a course. $course = $this->getDataGenerator()->create_course(); // Create backup file and save it to the backup location. $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid); $bc->execute_plan(); $results = $bc->get_results(); $file = $results['backup_destination']; $fp = get_file_packer('application/vnd.moodle.backup'); $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event'; $file->extract_to_pathname($fp, $filepath); $bc->destroy(); unset($bc); // Now we want to catch the restore course event. $sink = $this->redirectEvents(); // Now restore the course to trigger the event. $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE); $rc->execute_precheck(); $rc->execute_plan(); // Capture the event. $events = $sink->get_events(); $sink->close(); // Validate the event. $event = array_pop($events); $this->assertInstanceOf('\\core\\event\\course_restored', $event); $this->assertEquals('course', $event->objecttable); $this->assertEquals($rc->get_courseid(), $event->objectid); $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid); $this->assertEquals('course_restored', $event->get_legacy_eventname()); $legacydata = (object) array('courseid' => $rc->get_courseid(), 'userid' => $rc->get_userid(), 'type' => $rc->get_type(), 'target' => $rc->get_target(), 'mode' => $rc->get_mode(), 'operation' => $rc->get_operation(), 'samesite' => $rc->is_samesite()); $url = new moodle_url('/course/view.php', array('id' => $event->objectid)); $this->assertEquals($url, $event->get_url()); $this->assertEventLegacyData($legacydata, $event); $this->assertEventContextNotUsed($event); // Destroy the resource controller since we are done using it. $rc->destroy(); unset($rc); }
/** * https://github.com/tmuras/moosh/blob/master/Moosh/Command/Moodle23/Course/CourseRestore.php * @param $bkpfile * @param $categoryId */ public static function execute($bkpfile, $categoryId) { global $CFG, $DB, $USER; require_once $CFG->dirroot . "/backup/util/includes/backup_includes.php"; require_once $CFG->dirroot . "/backup/util/includes/restore_includes.php"; if (empty($CFG->tempdir)) { $CFG->tempdir = $CFG->dataroot . DIRECTORY_SEPARATOR . 'temp'; } //unzip into $CFG->tempdir / "backup" / "auto_restore_" . $split[1]; $backupdir = "moosh_restore_" . uniqid(); $path = $CFG->tempdir . DIRECTORY_SEPARATOR . "backup" . DIRECTORY_SEPARATOR . $backupdir; /** @var $fp file_packer */ $fp = get_file_packer('application/vnd.moodle.backup'); $fp->extract_to_pathname($bkpfile, $path); //extract original full & short names $xmlfile = $path . DIRECTORY_SEPARATOR . "course" . DIRECTORY_SEPARATOR . "course.xml"; // Different XML file in Moodle 1.9 backup if (!file_exists($xmlfile)) { $xmlfile = $path . DIRECTORY_SEPARATOR . "moodle.xml"; } $xml = simplexml_load_file($xmlfile); $fullname = $xml->xpath('/course/fullname'); if (!$fullname) { $fullname = $xml->xpath('/MOODLE_BACKUP/COURSE/HEADER/FULLNAME'); } $shortname = $xml->xpath('/course/shortname'); if (!$shortname) { $shortname = $xml->xpath('/MOODLE_BACKUP/COURSE/HEADER/SHORTNAME'); } $fullname = (string) $fullname[0]; $shortname = (string) $shortname[0]; if (!$shortname) { cli_error('No shortname in the backup file.'); } if (!$fullname) { $fullname = $shortname; } $courseid = \restore_dbops::create_new_course($fullname, $shortname, $categoryId); $rc = new \restore_controller($backupdir, $courseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, 2, \backup::TARGET_NEW_COURSE); echo "Restoring (new course id,shortname,destination category): {$courseid}, {$shortname}," . $categoryId . "\n"; if ($rc->get_status() == \backup::STATUS_REQUIRE_CONV) { $rc->convert(); } $plan = $rc->get_plan(); //TODO: valider les options réquises. $restopt = array('activities' => 1, 'blocks' => 1, 'filters' => 1, 'users' => 0, 'role_assignments' => 1, 'comments' => 0, 'logs' => 0, 'grade_histories' => 0); foreach ($restopt as $name => $value) { $setting = $plan->get_setting($name); if ($setting->get_status() == \backup_setting::NOT_LOCKED) { $setting->set_value($value); } } $rc->execute_precheck(); $rc->execute_plan(); $rc->destroy(); echo "New course ID for '{$shortname}': {$courseid} in {$categoryId}\n"; // Ajouter le idnumber dans le nouveau cours. $c = $DB->get_record('course', array('id' => $courseid)); $c->idnumber = self::get_idnumber($shortname); $DB->update_record('course', $c); }
/** * Duplicate a course * * @param int $courseid * @param string $fullname Duplicated course fullname * @param string $shortname Duplicated course shortname * @param int $categoryid Duplicated course parent category id * @param int $visible Duplicated course availability * @param array $options List of backup options * @return array New course info * @since Moodle 2.3 */ public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible, $options) { global $CFG, $USER, $DB; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; // Parameter validation. $params = self::validate_parameters(self::duplicate_course_parameters(), array('courseid' => $courseid, 'fullname' => $fullname, 'shortname' => $shortname, 'categoryid' => $categoryid, 'visible' => $visible, 'options' => $options)); // Context validation. if (!($course = $DB->get_record('course', array('id' => $params['courseid'])))) { throw new moodle_exception('invalidcourseid', 'error', '', $params['courseid']); } // Category where duplicated course is going to be created. $categorycontext = context_coursecat::instance($params['categoryid']); self::validate_context($categorycontext); // Course to be duplicated. $coursecontext = context_course::instance($course->id); self::validate_context($coursecontext); $backupdefaults = array('activities' => 1, 'blocks' => 1, 'filters' => 1, 'users' => 0, 'role_assignments' => 0, 'user_files' => 0, 'comments' => 0, 'completion_information' => 0, 'logs' => 0, 'histories' => 0); $backupsettings = array(); // Check for backup and restore options. if (!empty($params['options'])) { foreach ($params['options'] as $option) { // Strict check for a correct value (allways 1 or 0, true or false). $value = clean_param($option['value'], PARAM_INT); if ($value !== 0 and $value !== 1) { throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); } if (!isset($backupdefaults[$option['name']])) { throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); } $backupsettings[$option['name']] = $value; } } // Capability checking. // The backup controller check for this currently, this may be redundant. require_capability('moodle/course:create', $categorycontext); require_capability('moodle/restore:restorecourse', $categorycontext); require_capability('moodle/backup:backupcourse', $coursecontext); if (!empty($backupsettings['users'])) { require_capability('moodle/backup:userinfo', $coursecontext); require_capability('moodle/restore:userinfo', $categorycontext); } // Check if the shortname is used. if ($foundcourses = $DB->get_records('course', array('shortname' => $shortname))) { foreach ($foundcourses as $foundcourse) { $foundcoursenames[] = $foundcourse->fullname; } $foundcoursenamestring = implode(',', $foundcoursenames); throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring); } // Backup the course. $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id); foreach ($backupsettings as $name => $value) { $bc->get_plan()->get_setting($name)->set_value($value); } $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $results = $bc->get_results(); $file = $results['backup_destination']; $bc->destroy(); // Restore the backup immediately. // Check if we need to unzip the file because the backup temp dir does not contains backup files. if (!file_exists($backupbasepath . "/moodle_backup.xml")) { $file->extract_to_pathname(get_file_packer(), $backupbasepath); } // Create new course. $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']); $rc = new restore_controller($backupid, $newcourseid, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE); foreach ($backupsettings as $name => $value) { $setting = $rc->get_plan()->get_setting($name); if ($setting->get_status() == backup_setting::NOT_LOCKED) { $setting->set_value($value); } } if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } $errorinfo = ''; foreach ($precheckresults['errors'] as $error) { $errorinfo .= $error; } if (array_key_exists('warnings', $precheckresults)) { foreach ($precheckresults['warnings'] as $warning) { $errorinfo .= $warning; } } throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo); } } $rc->execute_plan(); $rc->destroy(); $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST); $course->fullname = $params['fullname']; $course->shortname = $params['shortname']; $course->visible = $params['visible']; // Set shortname and fullname back. $DB->update_record('course', $course); if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } // Delete the course backup file created by this WebService. Originally located in the course backups area. $file->delete(); return array('id' => $course->id, 'shortname' => $course->shortname); }
function create_cwsp_course($fullname, $shortname, $categoryid = 2) { global $CFG, $USER, $DB; //need this // $CFG->keeptempdirectoriesonbackup = true; // in config.php to keep the directory available //$backupdir = 'fde3a9c54543ef236d60ba0fa4aba028'; //CWSP $backupdir = 'ef521c9d85221eac4b596380655f0918'; //departmental $backupdir = 'e993969ee7787cdc1c5cd4f309c1902d'; //bi-weekly $transaction = $DB->start_delegated_transaction(); echo $fullname . ' ' . $shortname . "\n"; // Create new course $courseid = restore_dbops::create_new_course($fullname, $shortname, $categoryid); // Restore backup into course $controller = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, 2, backup::TARGET_NEW_COURSE); $controller->execute_precheck(); $controller->execute_plan(); // Commit $transaction->allow_commit(); return $courseid; }
function restore_to_course($courseid, $backupid, $restoretarget, $admin) { global $CFG; // Check whether the backup directory still exists. If missing, something // went really wrong in backup, throw error. Note that backup::MODE_IMPORT // backups don't store resulting files ever $tempdestination = $CFG->tempdir . '/backup/' . $backupid; if (!file_exists($tempdestination) || !is_dir($tempdestination)) { print_error('unknownbackupexporterror'); // shouldn't happen ever } $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_YES, backup::MODE_IMPORT,$admin->id,$restoretarget); // Convert the backup if required.... it should NEVER happed if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) { $rc->convert(); } // Mark the UI finished. $rc->finish_ui(); // Execute prechecks $rc->execute_precheck(); //if ($restoretarget == backup::TARGET_CURRENT_DELETING || $restoretarget == backup::TARGET_EXISTING_DELETING) { // restore_dbops::delete_course_content($courseid); //} // Execute the restore. $rc->execute_plan(); $rc->destroy(); unset($rc); // Delete the temp directory now fulldelete($tempdestination); }
public function test_mod_jclic_duplicate() { global $USER, $DB, $CFG; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; $this->resetAfterTest(true); $this->setUser(2); // Admin user. // Create a course with some availability data set. $generator = $this->getDataGenerator(); $course = $generator->create_course(array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => COMPLETION_ENABLED), array('createsections' => true)); $jclic = $generator->create_module('jclic', array('course' => $course->id)); $cmid = $jclic->cmid; // Do backup. $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cmid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $backupid = $bc->get_backupid(); $bc->execute_plan(); $bc->destroy(); // Do restore. $rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); $this->assertTrue($rc->execute_precheck()); $rc->execute_plan(); // Find cmid. $tasks = $rc->get_plan()->get_tasks(); $cmcontext = context_module::instance($cmid); $newcmid = 0; foreach ($tasks as $task) { if (is_subclass_of($task, 'restore_activity_task')) { if ($task->get_old_contextid() == $cmcontext->id) { $newcmid = $task->get_moduleid(); break; } } } $rc->destroy(); if (!$newcmid) { throw new coding_exception('Unexpected: failure to find restored cmid'); } // Check settings in new course. $newjclic = new jclic(context_module::instance($newcmid), false, false); $newjclicmodule = $newjclic->get_instance(); $this->assertNotEmpty($newjclic); $jclic = new jclic(context_module::instance($jclic->cmid), false, false); $jclicmodule = $jclic->get_instance(); $fields = (array) $jclicmodule; unset($fields['id']); unset($fields['course']); foreach ($fields as $key => $unused) { $this->assertEquals($newjclicmodule->{$key}, $jclicmodule->{$key}, "Failed on field {$key}"); } // Avoid errors... ini_set('max_execution_time', 0); }
/** * Tests the restore_controller. */ public function test_restore_controller_is_executing() { global $CFG; // Make a backup. check_dir_exists($CFG->tempdir . '/backup'); $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $this->userid); $backupid = $bc->get_backupid(); $bc->execute_plan(); $bc->destroy(); // The progress class will get called during restore, so we can use that // to check the executing flag is true. $progress = new core_backup_progress_restore_is_executing(); // Set up restore. $rc = new restore_controller($backupid, $this->courseid, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $this->userid, backup::TARGET_EXISTING_ADDING); $this->assertTrue($rc->execute_precheck()); // Check restore is NOT executing. $this->assertFalse(restore_controller::is_executing()); // Execute restore. $rc->set_progress($progress); $rc->execute_plan(); // Check restore is NOT executing afterward either. $this->assertFalse(restore_controller::is_executing()); $rc->destroy(); // During restore, check that executing was true. $this->assertTrue(count($progress->executing) > 0); $alltrue = true; foreach ($progress->executing as $executing) { if (!$executing) { $alltrue = false; break; } } $this->assertTrue($alltrue); }
/** * Restore an item from the recycle bin. * * @param \stdClass $item The item database record * @throws \moodle_exception */ public function restore_item($item) { global $CFG, $OUTPUT, $PAGE; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; $user = get_admin(); // Grab the course context. $context = \context_course::instance($this->_courseid); // Get the files.. $fs = get_file_storage(); $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, $item->id, 'itemid, filepath, filename', false); if (empty($files)) { throw new \moodle_exception('Invalid recycle bin item!'); } if (count($files) > 1) { throw new \moodle_exception('Too many files found!'); } // Get the backup file. $file = reset($files); // Get a temp directory name and create it. $tempdir = \restore_controller::get_tempdir_name($context->id, $user->id); $fulltempdir = make_temp_directory('/backup/' . $tempdir); // Extract the backup to tempdir. $fb = get_file_packer('application/vnd.moodle.backup'); $fb->extract_to_pathname($file, $fulltempdir); // Define the import. $controller = new \restore_controller($tempdir, $this->_courseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $user->id, \backup::TARGET_EXISTING_ADDING); // Prechecks. if (!$controller->execute_precheck()) { $results = $controller->get_precheck_results(); // If errors are found then delete the file we created. if (!empty($results['errors'])) { fulldelete($fulltempdir); echo $OUTPUT->header(); $backuprenderer = $PAGE->get_renderer('core', 'backup'); echo $backuprenderer->precheck_notices($results); echo $OUTPUT->continue_button(new \moodle_url('/course/view.php', array('id' => $this->_courseid))); echo $OUTPUT->footer(); exit; } } // Run the import. $controller->execute_plan(); // Fire event. $event = \tool_recyclebin\event\course_bin_item_restored::create(array('objectid' => $item->id, 'context' => $context)); $event->add_record_snapshot('tool_recyclebin_course', $item); $event->trigger(); // Cleanup. fulldelete($fulltempdir); $this->delete_item($item); }
/** * Api to duplicate a module. * * @param object $course course object. * @param object $cm course module object to be duplicated. * @since Moodle 2.8 * * @throws Exception * @throws coding_exception * @throws moodle_exception * @throws restore_controller_exception * * @return cm_info|null cminfo object if we sucessfully duplicated the mod and found the new cm. */ function duplicate_module($course, $cm) { global $CFG, $DB, $USER; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; require_once $CFG->libdir . '/filelib.php'; $a = new stdClass(); $a->modtype = get_string('modulename', $cm->modname); $a->modname = format_string($cm->name); if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) { throw new moodle_exception('duplicatenosupport', 'error', '', $a); } // Backup the activity. $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); // Restore the backup immediately. $rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); $cmcontext = context_module::instance($cm->id); if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } } } $rc->execute_plan(); // Now a bit hacky part follows - we try to get the cmid of the newly // restored copy of the module. $newcmid = null; $tasks = $rc->get_plan()->get_tasks(); foreach ($tasks as $task) { if (is_subclass_of($task, 'restore_activity_task')) { if ($task->get_old_contextid() == $cmcontext->id) { $newcmid = $task->get_moduleid(); break; } } } // If we know the cmid of the new course module, let us move it // right below the original one. otherwise it will stay at the // end of the section. if ($newcmid) { $info = get_fast_modinfo($course); $newcm = $info->get_cm($newcmid); $section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course)); moveto_module($newcm, $section, $cm); moveto_module($cm, $section, $newcm); // Update calendar events with the duplicated module. $refresheventsfunction = $newcm->modname . '_refresh_events'; if (function_exists($refresheventsfunction)) { call_user_func($refresheventsfunction, $newcm->course); } // Trigger course module created event. We can trigger the event only if we know the newcmid. $event = \core\event\course_module_created::create_from_cm($newcm); $event->trigger(); } rebuild_course_cache($cm->course); $rc->destroy(); if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } return isset($newcm) ? $newcm : null; }
/** * Execute scheduled task * * @return boolean */ public function execute() { global $CFG, $DB; require_once $CFG->libdir . '/moodlelib.php'; require_once $CFG->libdir . '/filestorage/zip_packer.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; // Get plugin config $local_sandbox_config = get_config('local_sandbox'); // Counter for restored courses $count = 0; // Do only when sandbox directory is configured if ($local_sandbox_config->coursebackupsdirectory != '') { // Do only when sandbox directory exists if (is_dir($local_sandbox_config->coursebackupsdirectory)) { // Open directory and get all .mbz files if ($handle = @opendir($local_sandbox_config->coursebackupsdirectory)) { while (false !== ($file = readdir($handle))) { if (substr($file, -4) == '.mbz' && $file != '.' && $file != '..') { // Get course shortname from filename $shortname = substr($file, 0, -4); echo "\n\t" . get_string('nowprocessing', 'local_sandbox', $shortname) . "\n"; // Get existing course information if ($oldcourse = $DB->get_record('course', array('shortname' => $shortname))) { $oldcourseid = $oldcourse->id; $categoryid = $oldcourse->category; $fullname = $oldcourse->fullname; } else { // Output error message for cron listing echo "\n\t" . get_string('skippingnocourse', 'local_sandbox', $shortname) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('skippingnocourse', 'local_sandbox', $shortname), SANDBOX_LEVEL_WARNING); continue; } // Delete existing course if (!delete_course($oldcourseid, false)) { // Output error message for cron listing echo "\n\t" . get_string('skippingdeletionfailed', 'local_sandbox', $shortname) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('skippingdeletionfailed', 'local_sandbox', $shortname), SANDBOX_LEVEL_WARNING); continue; } // Unzip course backup file to temp directory $filepacker = get_file_packer('application/vnd.moodle.backup'); check_dir_exists($CFG->dataroot . '/temp/backup'); if (!$filepacker->extract_to_pathname($local_sandbox_config->coursebackupsdirectory . '/' . $file, $CFG->dataroot . '/temp/backup/' . $shortname)) { // Output error message for cron listing echo "\n\t" . get_string('skippingunzipfailed', 'local_sandbox', $file) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('skippingunzipfailed', 'local_sandbox', $shortname), SANDBOX_LEVEL_WARNING); continue; } // Create new course if (!($newcourseid = \restore_dbops::create_new_course($shortname, $shortname, $categoryid))) { // Output error message for cron listing echo "\n\t" . get_string('skippingcreatefailed', 'local_sandbox', $shortname) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('skippingcreatefailed', 'local_sandbox', $shortname), SANDBOX_LEVEL_WARNING); continue; } // Get admin user for restore $admin = get_admin(); $restoreuser = $admin->id; // Restore course backup file into new course if ($controller = new \restore_controller($shortname, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_SAMESITE, $restoreuser, \backup::TARGET_NEW_COURSE)) { $controller->get_logger()->set_next(new \output_indented_logger(\backup::LOG_INFO, false, true)); $controller->execute_precheck(); $controller->execute_plan(); } else { // Output error message for cron listing echo "\n\t" . get_string('skippingrestorefailed', 'local_sandbox', $shortname) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('skippingrestorefailed', 'local_sandbox', $shortname), SANDBOX_LEVEL_WARNING); continue; } // Adjust course start date if ($local_sandbox_config->adjustcoursestartdate == true) { if (!$DB->update_record('course', (object) array('id' => $newcourseid, 'startdate' => time()))) { // Output error message for cron listing echo "\n\t" . get_string('skippingadjuststartdatefailed', 'local_sandbox', $shortname) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('skippingadjuststartdatefailed', 'local_sandbox', $shortname), SANDBOX_LEVEL_WARNING); continue; } } // Set shortname and fullname back if ($DB->update_record('course', (object) array('id' => $newcourseid, 'shortname' => $shortname, 'fullname' => $fullname))) { // Output info message for cron listing echo "\n\t" . get_string('successrestored', 'local_sandbox', $shortname) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('successrestored', 'local_sandbox', $shortname), SANDBOX_LEVEL_NOTICE); // Log the event $logevent = \local_sandbox\event\course_restored::create(array('objectid' => $newcourseid, 'context' => \context_course::instance($newcourseid))); $logevent->trigger(); // Fire course_updated event $course = $DB->get_record('course', array('id' => $newcourseid)); $ccevent = \core\event\course_created::create(array('objectid' => $course->id, 'context' => \context_course::instance($course->id), 'other' => array('shortname' => $course->shortname, 'fullname' => $course->fullname))); $ccevent->trigger(); // Count successfully restored course $count++; } else { // Output error message for cron listing echo "\n\t" . get_string('skippingdbupdatedfailed', 'local_sandbox', $shortname) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('skippingdbupdatefailed', 'local_sandbox', $shortname), SANDBOX_LEVEL_WARNING); continue; } } } closedir($handle); // Output info message for cron listing echo "\n\t" . get_string('noticerestorecount', 'local_sandbox', $count) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('noticerestorecount', 'local_sandbox', $count), SANDBOX_LEVEL_NOTICE); return true; } else { // Output error message for cron listing echo "\n\t" . get_string('errordirectorynotreadable', 'local_sandbox', $local_sandbox_config->coursebackupsdirectory) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('errordirectorynotreadable', 'local_sandbox', $local_sandbox_config->coursebackupsdirectory), SANDBOX_LEVEL_ERROR); return false; } } else { // Output error message for cron listing echo "\n\t" . get_string('errordirectorynotexist', 'local_sandbox', $local_sandbox_config->coursebackupsdirectory) . "\n"; // Inform admin local_sandbox_inform_admin(get_string('errordirectorynotexist', 'local_sandbox', $local_sandbox_config->coursebackupsdirectory), SANDBOX_LEVEL_ERROR); return false; } } else { // Output info message for cron listing echo "\n\t" . get_string('noticedirectorynotconfigured', 'local_sandbox') . "\n"; // Inform admin local_sandbox_inform_admin(get_string('noticedirectorynotconfigured', 'local_sandbox'), SANDBOX_LEVEL_NOTICE); return true; } }
/** * Proceed with the import of the course. * * @return void */ public function proceed() { global $CFG, $USER; if (!$this->prepared) { throw new coding_exception('The course has not been prepared.'); } else { if ($this->has_errors()) { throw new moodle_exception('Cannot proceed, errors were detected.'); } else { if ($this->processstarted) { throw new coding_exception('The process has already been started.'); } } } $this->processstarted = true; if ($this->do === self::DO_DELETE) { if ($this->delete()) { $this->status('coursedeleted', new lang_string('coursedeleted', 'tool_uploadcourse')); } else { $this->error('errorwhiledeletingcourse', new lang_string('errorwhiledeletingcourse', 'tool_uploadcourse')); } return true; } else { if ($this->do === self::DO_CREATE) { $course = create_course((object) $this->data); $this->id = $course->id; $this->status('coursecreated', new lang_string('coursecreated', 'tool_uploadcourse')); } else { if ($this->do === self::DO_UPDATE) { $course = (object) $this->data; update_course($course); $this->id = $course->id; $this->status('courseupdated', new lang_string('courseupdated', 'tool_uploadcourse')); } else { // Strangely the outcome has not been defined, or is unknown! throw new coding_exception('Unknown outcome!'); } } } // Restore a course. if (!empty($this->restoredata)) { $rc = new restore_controller($this->restoredata, $course->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); // Check if the format conversion must happen first. if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) { $rc->convert(); } if ($rc->execute_precheck()) { $rc->execute_plan(); $this->status('courserestored', new lang_string('courserestored', 'tool_uploadcourse')); } else { $this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringthecourse', 'tool_uploadcourse')); } $rc->destroy(); } // Proceed with enrolment data. $this->process_enrolment_data($course); // Reset the course. if ($this->importoptions['reset'] || $this->options['reset']) { if ($this->do === self::DO_UPDATE && $this->can_reset()) { $this->reset($course); $this->status('coursereset', new lang_string('coursereset', 'tool_uploadcourse')); } } // Mark context as dirty. $context = context_course::instance($course->id); $context->mark_dirty(); }
/** * Restore an item from the recycle bin. * * @param \stdClass $item The item database record * @throws \moodle_exception */ public function restore_item($item) { global $CFG, $OUTPUT, $PAGE; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; require_once $CFG->dirroot . '/course/lib.php'; $user = get_admin(); // Grab the course category context. $context = \context_coursecat::instance($this->_categoryid); // Get the backup file. $fs = get_file_storage(); $files = $fs->get_area_files($context->id, 'tool_recyclebin', TOOL_RECYCLEBIN_COURSECAT_BIN_FILEAREA, $item->id, 'itemid, filepath, filename', false); if (empty($files)) { throw new \moodle_exception('Invalid recycle bin item!'); } if (count($files) > 1) { throw new \moodle_exception('Too many files found!'); } // Get the backup file. $file = reset($files); // Get a temp directory name and create it. $tempdir = \restore_controller::get_tempdir_name($context->id, $user->id); $fulltempdir = make_temp_directory('/backup/' . $tempdir); // Extract the backup to tmpdir. $fb = get_file_packer('application/vnd.moodle.backup'); $fb->extract_to_pathname($file, $fulltempdir); // Build a course. $course = new \stdClass(); $course->category = $this->_categoryid; $course->shortname = $item->shortname; $course->fullname = $item->fullname; $course->summary = ''; // Create a new course. $course = create_course($course); if (!$course) { throw new \moodle_exception("Could not create course to restore into."); } // Define the import. $controller = new \restore_controller($tempdir, $course->id, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $user->id, \backup::TARGET_NEW_COURSE); // Prechecks. if (!$controller->execute_precheck()) { $results = $controller->get_precheck_results(); // Check if errors have been found. if (!empty($results['errors'])) { // Delete the temporary file we created. fulldelete($fulltempdir); // Delete the course we created. delete_course($course, false); echo $OUTPUT->header(); $backuprenderer = $PAGE->get_renderer('core', 'backup'); echo $backuprenderer->precheck_notices($results); echo $OUTPUT->continue_button(new \moodle_url('/course/index.php', array('categoryid' => $this->_categoryid))); echo $OUTPUT->footer(); exit; } } // Run the import. $controller->execute_plan(); // Have finished with the controller, let's destroy it, freeing mem and resources. $controller->destroy(); // Fire event. $event = \tool_recyclebin\event\category_bin_item_restored::create(array('objectid' => $item->id, 'context' => $context)); $event->add_record_snapshot('tool_recyclebin_category', $item); $event->trigger(); // Cleanup. fulldelete($fulltempdir); $this->delete_item($item); }
/** * Duplicates a single activity within a course. * * This is based on the code from course/modduplicate.php, but reduced for * simplicity. * * @param stdClass $course Course object * @param int $cmid Activity to duplicate * @return int ID of new activity */ protected function duplicate($course, $cmid) { global $USER; // Do backup. $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cmid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $backupid = $bc->get_backupid(); $bc->execute_plan(); $bc->destroy(); // Do restore. $rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); $this->assertTrue($rc->execute_precheck()); $rc->execute_plan(); // Find cmid. $tasks = $rc->get_plan()->get_tasks(); $cmcontext = context_module::instance($cmid); $newcmid = 0; foreach ($tasks as $task) { if (is_subclass_of($task, 'restore_activity_task')) { if ($task->get_old_contextid() == $cmcontext->id) { $newcmid = $task->get_moduleid(); break; } } } $rc->destroy(); if (!$newcmid) { throw new coding_exception('Unexpected: failure to find restored cmid'); } return $newcmid; }
/** * Takes backupid and original course object as arguments and returns a new courseid. * * @param string $backupid The backupid * @param object $newcourse Target course object * @throws coding_exception Moodle coding exception */ private function restore($backupid, $newcourse) { global $CFG, $DB, $USER; // Call restore. $rc = new \restore_controller($backupid, $newcourse->id, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id, \backup::TARGET_EXISTING_ADDING); // Setup custom logger that prints dots. $logger = new logger(\backup::LOG_INFO, false, true); self::$currentlogger = $logger; $logger->potential_dot(); $rc->get_logger()->set_next($logger); $rc->set_progress(new progress()); foreach ($rc->get_plan()->get_tasks() as $taskindex => $task) { $settings = $task->get_settings(); foreach ($settings as $settingindex => $setting) { // Set userinfo true for activity, since we controlled it // more accurately (i.e. true only for glossary) in backup. if (preg_match('/^glossary_([0-9])*_userinfo$$/', $setting->get_name())) { $setting->set_value(true); } if ($taskindex == 0 && isset($this->backupsettings[$setting->get_name()])) { $setting->set_value($this->backupsettings[$setting->get_name()]); } } } if (!$rc->execute_precheck()) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($this->backupbasepath); } $results = print_r($rc->get_precheck_results(), true); print \html_writer::tag('pre', s($results)); throw new \coding_exception('Restore precheck error.'); } $logger->potential_dot(); $rc->execute_plan(); $rc->destroy(); // Delete backup file. if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($this->backupbasepath); } }
<?php define('CLI_SCRIPT', true); chdir('/var/www/ae.ket.org'); require_once 'config.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; // Transaction $transaction = $DB->start_delegated_transaction(); // Create new course $folder = '9706ff11ee909cc426e66fff74926faa'; // as found in: $CFG->dataroot . '/temp/backup/' $categoryid = 1; // e.g. 1 == Miscellaneous $user_doing_the_restore = 4; // e.g. 2 == admin $courseid = restore_dbops::create_new_course('Z', 'Z', $categoryid); // Restore backup into course $controller = new restore_controller($folder, $courseid, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $user_doing_the_restore, backup::TARGET_NEW_COURSE); $controller->execute_precheck(); $controller->execute_plan(); // Commit $transaction->allow_commit();
/** * Duplicates a single dataform within a course. * * This is based on the code from course/modduplicate.php, but reduced for * simplicity. * * @param stdClass $course Course object * @param int $cmid Dataform to duplicate * @return stdClass The new dataform instance with cmid */ public function duplicate_instance($course, $cmid) { global $DB, $USER; // Do backup. $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cmid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); // Do restore. $rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } } } $rc->execute_plan(); // Find cmid. $tasks = $rc->get_plan()->get_tasks(); $cmcontext = context_module::instance($cmid); $newcmid = 0; $newactivityid = 0; foreach ($tasks as $task) { if (is_subclass_of($task, 'restore_activity_task')) { if ($task->get_old_contextid() == $cmcontext->id) { $newcmid = $task->get_moduleid(); $newactivityid = $task->get_activityid(); break; } } } $rc->destroy(); if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } if (!$newcmid) { throw new coding_exception('Unexpected: failure to find restored cmid'); } if (!($instance = $DB->get_record('dataform', array('id' => $newactivityid)))) { throw new coding_exception('Unexpected: failure to find restored activityid'); } $instance->cmid = $newcmid; // Clear the time limit, otherwise phpunit complains. set_time_limit(0); return $instance; }
$a = new stdClass(); $a->modtype = get_string('modulename', $cm->modname); $a->modname = format_string($cm->name); if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) { $url = course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)); print_error('duplicatenosupport', 'error', $url, $a); } // backup the activity $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); // restore the backup immediately $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } echo $output->header(); echo $output->precheck_notices($precheckresults); $url = course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)); echo $output->continue_button($url); echo $output->footer(); die; } } $rc->execute_plan(); // now a bit hacky part follows - we try to get the cmid of the newly
/** * Backs a course up and restores it. * * @param stdClass $srccourse Course object to backup * @param stdClass $dstcourse Course object to restore into * @return int ID of newly restored course */ protected function backup_and_restore($srccourse, $dstcourse = null) { global $USER, $CFG; // Turn off file logging, otherwise it can't delete the file (Windows). $CFG->backup_file_logger_level = backup::LOG_NONE; // Do backup with default settings. MODE_IMPORT means it will just // create the directory and not zip it. $bc = new backup_controller(backup::TYPE_1COURSE, $srccourse->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $backupid = $bc->get_backupid(); $bc->execute_plan(); $bc->destroy(); // Do restore to new course with default settings. if ($dstcourse !== null) { $newcourseid = $dstcourse->id; } else { $newcourseid = restore_dbops::create_new_course($srccourse->fullname, $srccourse->shortname . '_2', $srccourse->category); } $rc = new restore_controller($backupid, $newcourseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, backup::TARGET_NEW_COURSE); $this->assertTrue($rc->execute_precheck()); $rc->execute_plan(); $rc->destroy(); return $newcourseid; }
/** * This function tests that the functions responsible for moving questions to * different contexts also updates the tag instances associated with the questions. */ public function test_altering_tag_instance_context() { global $CFG, $DB; // Set to admin user. $this->setAdminUser(); // Create two course categories - we are going to delete one of these later and will expect // all the questions belonging to the course in the deleted category to be moved. $coursecat1 = $this->getDataGenerator()->create_category(); $coursecat2 = $this->getDataGenerator()->create_category(); // Create a couple of categories and questions. $context1 = context_coursecat::instance($coursecat1->id); $context2 = context_coursecat::instance($coursecat2->id); $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); $questioncat1 = $questiongenerator->create_question_category(array('contextid' => $context1->id)); $questioncat2 = $questiongenerator->create_question_category(array('contextid' => $context2->id)); $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id)); $question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id)); $question3 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id)); $question4 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id)); // Now lets tag these questions. core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $context1, array('tag 1', 'tag 2')); core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $context1, array('tag 3', 'tag 4')); core_tag_tag::set_item_tags('core_question', 'question', $question3->id, $context2, array('tag 5', 'tag 6')); core_tag_tag::set_item_tags('core_question', 'question', $question4->id, $context2, array('tag 7', 'tag 8')); // Test moving the questions to another category. question_move_questions_to_category(array($question1->id, $question2->id), $questioncat2->id); // Test that all tag_instances belong to one context. $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question', 'contextid' => $questioncat2->contextid))); // Test moving them back. question_move_questions_to_category(array($question1->id, $question2->id), $questioncat1->id); // Test that all tag_instances are now reset to how they were initially. $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 'contextid' => $questioncat1->contextid))); $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 'contextid' => $questioncat2->contextid))); // Now test moving a whole question category to another context. question_move_category_to_context($questioncat1->id, $questioncat1->contextid, $questioncat2->contextid); // Test that all tag_instances belong to one context. $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question', 'contextid' => $questioncat2->contextid))); // Now test moving them back. question_move_category_to_context($questioncat1->id, $questioncat2->contextid, context_coursecat::instance($coursecat1->id)->id); // Test that all tag_instances are now reset to how they were initially. $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 'contextid' => $questioncat1->contextid))); $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 'contextid' => $questioncat2->contextid))); // Now we want to test deleting the course category and moving the questions to another category. question_delete_course_category($coursecat1, $coursecat2, false); // Test that all tag_instances belong to one context. $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question', 'contextid' => $questioncat2->contextid))); // Create a course. $course = $this->getDataGenerator()->create_course(); // Create some question categories and questions in this course. $coursecontext = context_course::instance($course->id); $questioncat = $questiongenerator->create_question_category(array('contextid' => $coursecontext->id)); $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id)); $question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id)); // Add some tags to these questions. core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, array('tag 1', 'tag 2')); core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, array('tag 1', 'tag 2')); // Create a course that we are going to restore the other course to. $course2 = $this->getDataGenerator()->create_course(); // Create backup file and save it to the backup location. $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2); $bc->execute_plan(); $results = $bc->get_results(); $file = $results['backup_destination']; $fp = get_file_packer('application/vnd.moodle.backup'); $filepath = $CFG->dataroot . '/temp/backup/test-restore-course'; $file->extract_to_pathname($fp, $filepath); $bc->destroy(); // Now restore the course. $rc = new restore_controller('test-restore-course', $course2->id, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2, backup::TARGET_NEW_COURSE); $rc->execute_precheck(); $rc->execute_plan(); // Get the created question category. $restoredcategory = $DB->get_record('question_categories', array('contextid' => context_course::instance($course2->id)->id), '*', MUST_EXIST); // Check that there are two questions in the restored to course's context. $this->assertEquals(2, $DB->count_records('question', array('category' => $restoredcategory->id))); $rc->destroy(); }
public static function restore_course($emptycourseid, $backupfilename) { global $DB, $CFG; try { $courseid = $emptycourseid; $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); $restoretarget = 0; $fb = get_file_packer(); $fb->extract_to_pathname("{$CFG->dataroot}/temp/backup/" . $backupfilename, "{$CFG->dataroot}/temp/backup/" . md5($backupfilename) . "/"); restore_dbops::delete_course_content($course->id); $rc = new restore_controller(md5($backupfilename), $course->id, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2, $restoretarget); $rc->execute_precheck(); $rc->execute_plan(); return 'success'; } catch (Exception $e) { if (extension_loaded('newrelic')) { newrelic_notice_error($e->getMessage() . "{$CFG->dataroot}/temp/backup/" . $backupfilename, $e); } return 'Exception ' . $e->getMessage() . "{$CFG->dataroot}/temp/backup/" . $backupfilename; } }