/** * Destorys the backup controller and the loaded stage. */ public function destroy() { if ($this->controller) { $this->controller->destroy(); } unset($this->stage); }
/** * Backup a course and return its backup ID. * * @param int $courseid The course ID. * @param int $userid The user doing the backup. * @return string */ protected function backup_course($courseid, $userid = 2) { global $CFG; $packer = get_file_packer('application/vnd.moodle.backup'); $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid); $bc->execute_plan(); $results = $bc->get_results(); $results['backup_destination']->extract_to_pathname($packer, "{$CFG->tempdir}/backup/core_course_testcase"); $bc->destroy(); unset($bc); return 'core_course_testcase'; }
/** * test backup_plan class */ function test_backup_plan() { // We need one (non interactive) controller for instantiating plan $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid); // Instantiate one backup plan $bp = new backup_plan($bc); $this->assertTrue($bp instanceof backup_plan); $this->assertEquals($bp->get_name(), 'backup_plan'); // Calculate checksum and check it $checksum = $bp->calculate_checksum(); $this->assertTrue($bp->is_checksum_correct($checksum)); $bc->destroy(); }
public function execute() { global $USER; $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->cm_id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id); $bc->execute_plan(); $results = $bc->get_results(); $file = $results["backup_destination"]; $this->contextid = $file->get_contextid(); $this->fileid = $file->get_id(); $bc->destroy(); // $this->debug->trace(); // $this->prepareDir(); // $this->createXml(); // $temp_dir = $this->getTempDir().'/'; // moodle.xml 内を検索し、本体未対応や取りこぼしたリンクを // 独自形式に書き換え、バックアップ一時ディレクトリへコピー // $xml = file_get_contents($temp_dir.'moodle.xml'); // $xml = $this->encodeLinksAndBackupTargets($xml); // file_put_contents($temp_dir.'moodle.xml', $xml); // ユーザ指定のデータおよびファイルをZIPに追加 // foreach ($this->zip_userdata as $filename => $data) { // file_put_contents($temp_dir.$filename, $data); // } // foreach ($this->zip_userfile as $filename => $path) { // copy($path, $temp_dir.$filename); // } // $this->createZip(); // $this->cleanupDir(); // if (defined('BACKUP_SILENTLY')) { // if (empty($this->message_output)) { // if (is_callable('header_remove', FALSE, $header_remove)) { // 可能であれば出力済みのHTTPヘッダを除去し、 // 直接リダイレクトできる状態にする (PHP 5.3.x) // $header_remove(); // } // (進捗以外の)メッセージが出力されていなければ、成功フラグを立てる // $this->execute_succeeded = TRUE; // } // } else { // 非サイレントモードでは成功かどうかの判定が困難なのでHTTPヘッダで簡易判定する // if (!headers_sent()) { // $this->execute_succeeded = TRUE; // } // } $this->execute_succeeded = TRUE; }
/** * 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(); }
$PAGE->set_url(new moodle_url('/course/modduplicate.php', array('cmid' => $cm->id, 'courseid' => $course->id))); $PAGE->set_pagelayout('incourse'); $output = $PAGE->get_renderer('core', 'backup'); $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; }
/** * 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); }
public function test_get_restore_content_dir() { global $CFG; $this->resetAfterTest(true); $this->setAdminUser(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course((object) array('shortname' => 'Yay')); // Creating backup file. $bc = new backup_controller(backup::TYPE_1COURSE, $c1->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2); $bc->execute_plan(); $result = $bc->get_results(); $this->assertTrue(isset($result['backup_destination'])); $c1backupfile = $result['backup_destination']->copy_content_to_temp(); $bc->destroy(); unset($bc); // File logging is a mess, we can only try to rely on gc to close handles. // Creating backup file. $bc = new backup_controller(backup::TYPE_1COURSE, $c2->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2); $bc->execute_plan(); $result = $bc->get_results(); $this->assertTrue(isset($result['backup_destination'])); $c2backupfile = $result['backup_destination']->copy_content_to_temp(); $bc->destroy(); unset($bc); // File logging is a mess, we can only try to rely on gc to close handles. $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false; $CFG->keeptempdirectoriesonbackup = true; // Checking restore dir. $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c1->id); $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname); // Do it again, it should be the same directory. $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $this->assertEquals($dir, $dir2); // Get the second course. $dir = tool_uploadcourse_helper::get_restore_content_dir($c2backupfile, null); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c2->id); $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname); // Checking with a shortname. $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c1->id); $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname); // Do it again, it should be the same directory. $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $this->assertEquals($dir, $dir2); // Get the second course. $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c2->shortname); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c2->id); $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname); // Get a course that does not exist. $errors = array(); $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors); $this->assertFalse($dir); $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors); // Trying again without caching. $CFG->keeptempdirectoriesonbackup is required for caching. $CFG->keeptempdirectoriesonbackup = false; // Checking restore dir. $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $this->assertNotEquals($dir, $dir2); // Checking with a shortname. $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $this->assertNotEquals($dir, $dir2); // Get a course that does not exist. $errors = array(); $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors); $this->assertFalse($dir); $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors); $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors); $this->assertEquals($dir, $dir2); $CFG->keeptempdirectoriesonbackup = $oldcfg; }
/** * 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; }
/** * Store a course module in the recycle bin. * * @param \stdClass $cm Course module * @throws \moodle_exception */ public function store_item($cm) { global $CFG, $DB; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; // Get more information. $modinfo = get_fast_modinfo($cm->course); if (!isset($modinfo->cms[$cm->id])) { return; // Can't continue without the module information. } $cminfo = $modinfo->cms[$cm->id]; // Check backup/restore support. if (!plugin_supports('mod', $cminfo->modname, FEATURE_BACKUP_MOODLE2)) { return; } // Backup the activity. $user = get_admin(); $controller = new \backup_controller(\backup::TYPE_1ACTIVITY, $cm->id, \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $user->id); $controller->execute_plan(); // Grab the result. $result = $controller->get_results(); if (!isset($result['backup_destination'])) { throw new \moodle_exception('Failed to backup activity prior to deletion.'); } // Have finished with the controller, let's destroy it, freeing mem and resources. $controller->destroy(); // Grab the filename. $file = $result['backup_destination']; if (!$file->get_contenthash()) { throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).'); } // Record the activity, get an ID. $activity = new \stdClass(); $activity->courseid = $cm->course; $activity->section = $cm->section; $activity->module = $cm->module; $activity->name = $cminfo->name; $activity->timecreated = time(); $binid = $DB->insert_record('tool_recyclebin_course', $activity); // Create the location we want to copy this file to. $filerecord = array('contextid' => \context_course::instance($this->_courseid)->id, 'component' => 'tool_recyclebin', 'filearea' => TOOL_RECYCLEBIN_COURSE_BIN_FILEAREA, 'itemid' => $binid, 'timemodified' => time()); // Move the file to our own special little place. $fs = get_file_storage(); if (!$fs->create_file_from_storedfile($filerecord, $file)) { // Failed, cleanup first. $DB->delete_records('tool_recyclebin_course', array('id' => $binid)); throw new \moodle_exception("Failed to copy backup file to recyclebin."); } // Delete the old file. $file->delete(); // Fire event. $event = \tool_recyclebin\event\course_bin_item_created::create(array('objectid' => $binid, 'context' => \context_course::instance($cm->course))); $event->trigger(); }
/** * test backup_structure_step class */ function test_backup_structure_step() { global $CFG; $file = $CFG->tempdir . '/test/test_backup_structure_step.txt'; // Remove the test dir and any content @remove_dir(dirname($file)); // Recreate test dir if (!check_dir_exists(dirname($file), true, true)) { throw new moodle_exception('error_creating_temp_dir', 'error', dirname($file)); } // We need one (non interactive) controller for instatiating plan $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid); // We need one plan $bp = new backup_plan($bc); // We need one task with mocked basepath $bt = new mock_backup_task_basepath('taskname'); $bp->add_task($bt); // Instantiate backup_structure_step (and add it to task) $bs = new mock_backup_structure_step('steptest', basename($file), $bt); // Execute backup_structure_step $bs->execute(); // Test file has been created $this->assertTrue(file_exists($file)); // Some simple tests with contents $contents = file_get_contents($file); $this->assertTrue(strpos($contents, '<?xml version="1.0"') !== false); $this->assertTrue(strpos($contents, '<test id="1">') !== false); $this->assertTrue(strpos($contents, '<field1>value1</field1>') !== false); $this->assertTrue(strpos($contents, '<field2>value2</field2>') !== false); $this->assertTrue(strpos($contents, '</test>') !== false); $bc->destroy(); unlink($file); // delete file // Remove the test dir and any content @remove_dir(dirname($file)); }
function backup_template($courseid_from,$settings,$config,$admin) { $bc = new backup_controller(backup::TYPE_1COURSE, $courseid_from, backup::FORMAT_MOODLE, backup::INTERACTIVE_YES, backup::MODE_IMPORT,$admin->id); $backupid = $bc->get_backupid(); $bc->get_plan()->get_setting('users')->set_status(backup_setting::LOCKED_BY_CONFIG); foreach ($settings as $setting => $configsetting) { if ($bc->get_plan()->setting_exists($setting)) { $bc->get_plan()->get_setting($setting)->set_value($config->{$configsetting}); } } $bc->finish_ui(); $bc->execute_plan(); $bc->destroy(); unset($bc); return $backupid; }
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); }
/** * Get the restore content tempdir. * * The tempdir is the sub directory in which the backup has been extracted. * * This caches the result for better performance, but $CFG->keeptempdirectoriesonbackup * needs to be enabled, otherwise the cache is ignored. * * @param string $backupfile path to a backup file. * @param string $shortname shortname of a course. * @param array $errors will be populated with errors found. * @return string|false false when the backup couldn't retrieved. */ public static function get_restore_content_dir($backupfile = null, $shortname = null, &$errors = array()) { global $CFG, $DB, $USER; $cachekey = null; if (!empty($backupfile)) { $backupfile = realpath($backupfile); if (empty($backupfile) || !is_readable($backupfile)) { $errors['cannotreadbackupfile'] = new lang_string('cannotreadbackupfile', 'tool_uploadcourse'); return false; } $cachekey = 'backup_path:' . $backupfile; } else { if (!empty($shortname) || is_numeric($shortname)) { $cachekey = 'backup_sn:' . $shortname; } } if (empty($cachekey)) { return false; } // If $CFG->keeptempdirectoriesonbackup is not set to true, any restore happening would // automatically delete the backup directory... causing the cache to return an unexisting directory. $usecache = !empty($CFG->keeptempdirectoriesonbackup); if ($usecache) { $cache = cache::make('tool_uploadcourse', 'helper'); } // If we don't use the cache, or if we do and not set, or the directory doesn't exist any more. if (!$usecache || (($backupid = $cache->get($cachekey)) === false || !is_dir("{$CFG->tempdir}/backup/{$backupid}"))) { // Use null instead of false because it would consider that the cache key has not been set. $backupid = null; if (!empty($backupfile)) { // Extracting the backup file. $packer = get_file_packer('application/vnd.moodle.backup'); $backupid = restore_controller::get_tempdir_name(SITEID, $USER->id); $path = "{$CFG->tempdir}/backup/{$backupid}/"; $result = $packer->extract_to_pathname($backupfile, $path); if (!$result) { $errors['invalidbackupfile'] = new lang_string('invalidbackupfile', 'tool_uploadcourse'); } } else { if (!empty($shortname) || is_numeric($shortname)) { // Creating restore from an existing course. $courseid = $DB->get_field('course', 'id', array('shortname' => $shortname), IGNORE_MISSING); if (!empty($courseid)) { $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); $bc->execute_plan(); $backupid = $bc->get_backupid(); $bc->destroy(); } else { $errors['coursetorestorefromdoesnotexist'] = new lang_string('coursetorestorefromdoesnotexist', 'tool_uploadcourse'); } } } if ($usecache) { $cache->set($cachekey, $backupid); } } if ($backupid === null) { $backupid = false; } return $backupid; }
/** * */ public function create_preset_from_backup($userdata) { global $CFG, $USER, $SESSION; require_once "{$CFG->dirroot}/backup/util/includes/backup_includes.php"; $df = mod_dataform_dataform::instance($this->_dataformid); $users = 0; $anon = 0; switch ($userdata) { case 'dataanon': $anon = 1; case 'data': $users = 1; } // Store preset settings in $SESSION. $SESSION->{"dataform_{$df->cm->id}_preset"} = "{$users} {$anon}"; $bc = new backup_controller(backup::TYPE_1ACTIVITY, $df->cm->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id); // Clear preset settings from $SESSION. unset($SESSION->{"dataform_{$df->cm->id}_preset"}); // Set users and anon in plan. $bc->get_plan()->get_setting('users')->set_value($users); $bc->get_plan()->get_setting('anonymize')->set_value($anon); $bc->set_status(backup::STATUS_AWAITING); $bc->execute_plan(); $bc->destroy(); $fs = get_file_storage(); if ($users and !$anon) { $contextid = $df->context->id; $files = $fs->get_area_files($contextid, 'backup', 'activity', 0, 'timemodified', false); } else { $usercontext = context_user::instance($USER->id); $contextid = $usercontext->id; $files = $fs->get_area_files($contextid, 'user', 'backup', 0, 'timemodified', false); } if (!empty($files)) { $coursecontext = context_course::instance($df->course->id); foreach ($files as $file) { if ($file->get_contextid() != $contextid) { continue; } $preset = new object(); $preset->contextid = $coursecontext->id; $preset->component = 'mod_dataform'; $preset->filearea = self::PRESET_COURSEAREA; $preset->filepath = '/'; $preset->filename = clean_filename(str_replace(' ', '_', $df->name) . '-dataform-preset-' . gmdate("Ymd_Hi") . '-' . str_replace(' ', '-', get_string("preset{$userdata}", 'dataform')) . '.mbz'); $fs->create_file_from_storedfile($preset, $file); $file->delete(); return true; } } return false; }
/** * 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; }
/** * 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); }
/** * Launches a automated backup routine for the given course * * @param stdClass $course * @param int $starttime * @param int $userid * @return bool */ public static function launch_automated_backup($course, $starttime, $userid) { $outcome = self::BACKUP_STATUS_OK; $config = get_config('backup'); $dir = $config->backup_auto_destination; $storage = (int) $config->backup_auto_storage; $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid); try { $settings = array('users' => 'backup_auto_users', 'role_assignments' => 'backup_auto_role_assignments', 'activities' => 'backup_auto_activities', 'blocks' => 'backup_auto_blocks', 'filters' => 'backup_auto_filters', 'comments' => 'backup_auto_comments', 'badges' => 'backup_auto_badges', 'completion_information' => 'backup_auto_userscompletion', 'logs' => 'backup_auto_logs', 'histories' => 'backup_auto_histories', 'questionbank' => 'backup_auto_questionbank'); foreach ($settings as $setting => $configsetting) { if ($bc->get_plan()->setting_exists($setting)) { if (isset($config->{$configsetting})) { $bc->get_plan()->get_setting($setting)->set_value($config->{$configsetting}); } } } // Set the default filename. $format = $bc->get_format(); $type = $bc->get_type(); $id = $bc->get_id(); $users = $bc->get_plan()->get_setting('users')->get_value(); $anonymised = $bc->get_plan()->get_setting('anonymize')->get_value(); $bc->get_plan()->get_setting('filename')->set_value(backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised)); $bc->set_status(backup::STATUS_AWAITING); $bc->execute_plan(); $results = $bc->get_results(); $outcome = self::outcome_from_results($results); $file = $results['backup_destination']; // May be empty if file already moved to target location. if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) { $dir = null; } // Copy file only if there was no error. if ($file && !empty($dir) && $storage !== 0 && $outcome != self::BACKUP_STATUS_ERROR) { $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised, !$config->backup_shortname); if (!$file->copy_content_to($dir . '/' . $filename)) { $outcome = self::BACKUP_STATUS_ERROR; } if ($outcome != self::BACKUP_STATUS_ERROR && $storage === 1) { $file->delete(); } } } catch (moodle_exception $e) { $bc->log('backup_auto_failed_on_course', backup::LOG_ERROR, $course->shortname); // Log error header. $bc->log('Exception: ' . $e->errorcode, backup::LOG_ERROR, $e->a, 1); // Log original exception problem. $bc->log('Debug: ' . $e->debuginfo, backup::LOG_DEBUG, null, 1); // Log original debug information. $outcome = self::BACKUP_STATUS_ERROR; } // Delete the backup file immediately if something went wrong. if ($outcome === self::BACKUP_STATUS_ERROR) { // Delete the file from file area if exists. if (!empty($file)) { $file->delete(); } // Delete file from external storage if exists. if ($storage !== 0 && !empty($filename) && file_exists($dir . '/' . $filename)) { @unlink($dir . '/' . $filename); } } $bc->destroy(); unset($bc); return $outcome; }
/** * Return an array of owners and a list of each course they are teachers of. * * @param object $obj course obj * @param string $folder name of folder (optional) * @return bool of courses that match the search */ protected function archivecourse($obj, $folder = false) { global $CFG; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/controller/backup_controller.class.php'; if (empty($CFG->siteadmins)) { // Should not happen on an ordinary site. return false; } else { $admin = get_admin(); } $coursetobackup = $obj["course"]->id; // Set this to one existing choice cmid in your dev site. $userdoingthebackup = $admin->id; // Set this to the id of your admin account. try { // Prepare path. $matchers = array('/\\s/', '/\\//'); $safeshort = preg_replace($matchers, '-', $obj["course"]->shortname); if (empty($obj["course"]->idnumber)) { $suffix = '-ID-' . $obj["course"]->id; } else { $suffix = '-ID-' . $obj["course"]->id . '-IDNUM-' . $obj["course"]->idnumber; } $archivefile = date("Y-m-d") . "{$suffix}-{$safeshort}.zip"; $archivepath = str_replace(str_split('\\/:*?"<>|'), '', get_config('tool_coursearchiver', 'coursearchiverpath')); // Check for custom folder. if (!empty($folder)) { $folder = str_replace(str_split('\\/:*?"<>|'), '', $folder); } // If no custom folder is given, use the current year. if (empty($folder)) { $folder = date('Y'); } // Final full path of file. $path = $CFG->dataroot . '/' . $archivepath . '/' . $folder; // If the path doesn't exist, make it so! if (!is_dir($path)) { umask(00); // Create the directory for CourseArchival. if (!mkdir($path, $CFG->directorypermissions, true)) { throw new Exception('Archive path could not be created'); } @chmod($path, $dirpermissions); } // Perform Backup. $bc = new backup_controller(backup::TYPE_1COURSE, $coursetobackup, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userdoingthebackup); $bc->execute_plan(); // Execute backup. $results = $bc->get_results(); // Get the file information needed. $config = get_config('backup'); $dir = $config->backup_auto_destination; // The backup file will have already been moved, so I have to find it. if (!empty($dir)) { // Calculate backup filename regex, ignoring the date/time/info parts that can be // variable, depending of languages, formats and automated backup settings. $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $obj["course"]->id . '-'; $regex = '#' . preg_quote($filename, '#') . '.*\\.mbz#'; // Store all the matching files into filename => timemodified array. $files = array(); foreach (scandir($dir) as $file) { // Skip files not matching the naming convention. if (!preg_match($regex, $file, $matches)) { continue; } // Read the information contained in the backup itself. try { $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file); } catch (backup_helper_exception $e) { throw new Exception('Error: ' . $file . ' does not appear to be a valid backup (' . $e->errorcode . ')'); continue; } // Make sure this backup concerns the course and site we are looking for. if ($bcinfo->format === backup::FORMAT_MOODLE && $bcinfo->type === backup::TYPE_1COURSE && $bcinfo->original_course_id == $obj["course"]->id && backup_general_helper::backup_is_samesite($bcinfo)) { $files[$file] = $bcinfo->backup_date; } } // Sort by values descending (newer to older filemodified). arsort($files); foreach ($files as $filename => $backupdate) { // Make sure the backup is from today. if (date('m/d/Y', $backupdate) == date('m/d/Y')) { rename($dir . '/' . $filename, $path . '/' . $archivefile); } break; // Just the last backup...thanks! } } else { $file = $results['backup_destination']; if (!empty($file)) { $file->copy_content_to($path . '/' . $archivefile); } else { throw new Exception('Backup failed'); } } $bc->destroy(); unset($bc); if (file_exists($path . '/' . $archivefile)) { // Make sure file got moved. // Remove Course. delete_course($obj["course"]->id, false); } else { throw new Exception('Course archive file does not exist'); } } catch (Exception $e) { return false; } return true; }
/** * 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; }
/** * Store a course in the recycle bin. * * @param \stdClass $course Course * @throws \moodle_exception */ public function store_item($course) { global $CFG, $DB; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; // Backup the course. $user = get_admin(); $controller = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $user->id); $controller->execute_plan(); // Grab the result. $result = $controller->get_results(); if (!isset($result['backup_destination'])) { throw new \moodle_exception('Failed to backup activity prior to deletion.'); } // Have finished with the controller, let's destroy it, freeing mem and resources. $controller->destroy(); // Grab the filename. $file = $result['backup_destination']; if (!$file->get_contenthash()) { throw new \moodle_exception('Failed to backup activity prior to deletion (invalid file).'); } // Record the activity, get an ID. $item = new \stdClass(); $item->categoryid = $course->category; $item->shortname = $course->shortname; $item->fullname = $course->fullname; $item->timecreated = time(); $binid = $DB->insert_record('tool_recyclebin_category', $item); // Create the location we want to copy this file to. $filerecord = array('contextid' => \context_coursecat::instance($course->category)->id, 'component' => 'tool_recyclebin', 'filearea' => TOOL_RECYCLEBIN_COURSECAT_BIN_FILEAREA, 'itemid' => $binid, 'timemodified' => time()); // Move the file to our own special little place. $fs = get_file_storage(); if (!$fs->create_file_from_storedfile($filerecord, $file)) { // Failed, cleanup first. $DB->delete_records('tool_recyclebin_category', array('id' => $binid)); throw new \moodle_exception("Failed to copy backup file to recyclebin."); } // Delete the old file. $file->delete(); // Fire event. $event = \tool_recyclebin\event\category_bin_item_created::create(array('objectid' => $binid, 'context' => \context_coursecat::instance($course->category))); $event->trigger(); }
/** * Launches a automated backup routine for the given course * * @param stdClass $course * @param int $starttime * @param int $userid * @return bool */ public static function launch_automated_backup($course, $starttime, $userid) { $config = get_config('backup'); $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid); try { $settings = array('users' => 'backup_auto_users', 'role_assignments' => 'backup_auto_role_assignments', 'activities' => 'backup_auto_activities', 'blocks' => 'backup_auto_blocks', 'filters' => 'backup_auto_filters', 'comments' => 'backup_auto_comments', 'completion_information' => 'backup_auto_userscompletion', 'logs' => 'backup_auto_logs', 'histories' => 'backup_auto_histories'); foreach ($settings as $setting => $configsetting) { if ($bc->get_plan()->setting_exists($setting)) { $bc->get_plan()->get_setting($setting)->set_value($config->{$configsetting}); } } // Set the default filename $format = $bc->get_format(); $type = $bc->get_type(); $id = $bc->get_id(); $users = $bc->get_plan()->get_setting('users')->get_value(); $anonymised = $bc->get_plan()->get_setting('anonymize')->get_value(); $bc->get_plan()->get_setting('filename')->set_value(backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised)); $bc->set_status(backup::STATUS_AWAITING); $outcome = $bc->execute_plan(); $results = $bc->get_results(); $file = $results['backup_destination']; $dir = $config->backup_auto_destination; $storage = (int) $config->backup_auto_storage; if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) { $dir = null; } if (!empty($dir) && $storage !== 0) { $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised, true); $outcome = $file->copy_content_to($dir . '/' . $filename); if ($outcome && $storage === 1) { $file->delete(); } } $outcome = true; } catch (backup_exception $e) { $bc->log('backup_auto_failed_on_course', backup::LOG_WARNING, $course->shortname); $outcome = false; } $bc->destroy(); unset($bc); return true; }
/** * Takes the original course object as an argument, backs up the * course and returns a backupid which can be used for restoring the course. * * @param object $course Original course object * @param array $activityids all cm ids to backup * @param array $activityids all section ids to backup * @throws coding_exception Moodle coding_exception * @retun string backupid Backupid */ private function backup($course, $activityids, $sectionids) { global $CFG, $DB, $USER; // MODE_IMPORT option is required to prevent it from zipping up the // result at the end. Unfortunately this breaks the system in some // other ways (see other comments). // NOTE: We cannot use MODE_SAMESITE here because that option will // zip the result and anyway, MODE_IMPORT already turns off the files. $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT, $USER->id); // Setup custom logger that prints dots. $logger = new logger(\backup::LOG_INFO, false, true); self::$currentlogger = $logger; $logger->potential_dot(); $bc->get_logger()->set_next($logger); $bc->set_progress(new progress()); foreach ($bc->get_plan()->get_tasks() as $taskindex => $task) { $settings = $task->get_settings(); foreach ($settings as $settingindex => $setting) { $setting->set_status(\backup_setting::NOT_LOCKED); // Modify the values of the intial backup settings. if ($taskindex == 0) { if (isset($this->backupsettings[$setting->get_name()])) { $setting->set_value($this->backupsettings[$setting->get_name()]); } } else { list($name, $id, $type) = $this->parse_backup_course_module($setting->get_name()); // Include user data on glossary if the role 'contributingstudent' // does not have the capability mod/glossary:write on that glossary. // This is so that we include course-team glossary data but not // glossaries that are written by students. if ($name === 'glossary' && $type === 'userinfo') { // Find the contributing student role id. If there // isn't one, use the student role id, for dev servers etc. $roles = $DB->get_records_select('role', "shortname IN ('contributingstudent', 'student')", null, 'shortname', 'id'); if (!$roles) { continue; } $roleid = reset($roles)->id; // Get the list of roles which have the capability in the context // of the glossary. $context = \context_module::instance($id); list($allowed, $forbidden) = get_roles_with_cap_in_context($context, 'mod/glossary:write'); // If student has this capability then the user data is false. if (!empty($allowed[$roleid]) && empty($forbidden[$roleid])) { $setting->set_value(false); } else { $setting->set_value(true); } } // Ignone any sections not in subpage. if ($name === 'section' && $type === 'included' && !in_array($id, $sectionids)) { $setting->set_value(false); } // Ignone any activities not in subpage. if ($name !== 'section' && $type === 'included' && !in_array($id, $activityids)) { $setting->set_value(false); } } } } $logger->potential_dot(); $bc->save_controller(); $backupid = $bc->get_backupid(); $this->backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $bc->destroy(); return $backupid; }
/** * 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; }
/** * 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; }
/** * 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; }
/** * 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; }
/** * Launches a automated backup routine for the given course * * @param stdClass $course * @param int $starttime * @param int $userid * @return bool */ public static function launch_automated_backup($course, $starttime, $userid) { $outcome = self::BACKUP_STATUS_OK; $config = get_config('backup'); $dir = $config->backup_auto_destination; $storage = (int) $config->backup_auto_storage; $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid); try { // Set the default filename. $format = $bc->get_format(); $type = $bc->get_type(); $id = $bc->get_id(); $users = $bc->get_plan()->get_setting('users')->get_value(); $anonymised = $bc->get_plan()->get_setting('anonymize')->get_value(); $bc->get_plan()->get_setting('filename')->set_value(backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised)); $bc->set_status(backup::STATUS_AWAITING); $bc->execute_plan(); $results = $bc->get_results(); $outcome = self::outcome_from_results($results); $file = $results['backup_destination']; // May be empty if file already moved to target location. if (empty($dir) && $storage !== 0) { // This is intentionally left as a warning instead of an error because of the current behaviour of backup settings. // See MDL-48266 for details. $bc->log('No directory specified for automated backups', backup::LOG_WARNING); $outcome = self::BACKUP_STATUS_WARNING; } else { if ($storage !== 0 && (!file_exists($dir) || !is_dir($dir) || !is_writable($dir))) { // If we need to copy the backup file to an external dir and it is not writable, change status to error. $bc->log('Specified backup directory is not writable - ', backup::LOG_ERROR, $dir); $dir = null; $outcome = self::BACKUP_STATUS_ERROR; } } // Copy file only if there was no error. if ($file && !empty($dir) && $storage !== 0 && $outcome != self::BACKUP_STATUS_ERROR) { $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised, !$config->backup_shortname); if (!$file->copy_content_to($dir . '/' . $filename)) { $bc->log('Attempt to copy backup file to the specified directory failed - ', backup::LOG_ERROR, $dir); $outcome = self::BACKUP_STATUS_ERROR; } if ($outcome != self::BACKUP_STATUS_ERROR && $storage === 1) { if (!$file->delete()) { $outcome = self::BACKUP_STATUS_WARNING; $bc->log('Attempt to delete the backup file from course automated backup area failed - ', backup::LOG_WARNING, $file->get_filename()); } } } } catch (moodle_exception $e) { $bc->log('backup_auto_failed_on_course', backup::LOG_ERROR, $course->shortname); // Log error header. $bc->log('Exception: ' . $e->errorcode, backup::LOG_ERROR, $e->a, 1); // Log original exception problem. $bc->log('Debug: ' . $e->debuginfo, backup::LOG_DEBUG, null, 1); // Log original debug information. $outcome = self::BACKUP_STATUS_ERROR; } // Delete the backup file immediately if something went wrong. if ($outcome === self::BACKUP_STATUS_ERROR) { // Delete the file from file area if exists. if (!empty($file)) { $file->delete(); } // Delete file from external storage if exists. if ($storage !== 0 && !empty($filename) && file_exists($dir . '/' . $filename)) { @unlink($dir . '/' . $filename); } } $bc->destroy(); unset($bc); return $outcome; }
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)); }