moodle-local_replication/import.php
Kumi f9ed77bcde
feat(replication): enhance course and category management
Implement enhanced category and course replication, improving the handling of both by ensuring new and existing categories are processed correctly. Introduce CLI support for bulk category imports using a dedicated flag and provide helper functions like `generateRandomString` and `removePath` for file management. Add a new `trigger_all.php` script to trigger replication for all courses and an `unenroll.php` script for unenrolling users with cleanup of related data.

Includes error handling improvements and directory structure checks.
2024-09-09 14:55:36 +02:00

202 lines
5.8 KiB
PHP

<?php
require_once("../../config.php");
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . "/backup/util/includes/restore_includes.php");
$timestamp = $_GET["timestamp"];
$replicationconfig = get_config('local_replication');
$directory = $replicationconfig->directory;
function local_replication_process_category($category, $parent_id = 0) {
global $DB;
$oldid = (int)$category['oldid'];
$name = (string)$category['name'];
$existing_category = \core_course_category::get($oldid, IGNORE_MISSING);
if ($existing_category->id) {
// Update name and parent if necessary
$updated_data = [];
if ($existing_category->name !== $name) {
$updated_data['name'] = $name;
}
if ($parent_id !== 0 && $existing_category->parent != $parent_id) {
$updated_data['parent'] = $parent_id;
}
if (!empty($updated_data)) {
// Update category
$existing_category->update($updated_data);
}
} else {
// Create categories up to the required ID
$max_id = $DB->get_field_sql("SELECT MAX(id) FROM {course_categories}");
while ($max_id < $oldid) {
$new_category_data = [
'name' => "Placeholder " . ($max_id + 1),
'parent' => $parent_id,
'idnumber' => '',
'visible' => 1
];
// Create placeholder category
\core_course_category::create($new_category_data);
$max_id = $DB->get_field_sql("SELECT MAX(id) FROM {course_categories}");
}
// Create target category
$new_category_data = [
'id' => $oldid,
'name' => $name,
'parent' => $parent_id,
'idnumber' => '',
'visible' => 1
];
\core_course_category::update($new_category_data);
}
// Process sub-categories
foreach ($category->category as $sub_category) {
local_replication_process_category($sub_category, $oldid);
}
}
if (isset($_GET["categories"])) {
$infile = $directory . DIRECTORY_SEPARATOR . "categories_" . $timestamp . ".xml";
$xml = simplexml_load_file($infile);
foreach ($xml->category as $category) {
local_replication_process_category($category);
}
echo("OK");
exit();
}
$courseid = $_GET["courseid"];
$categoryid = $_GET["categoryid"];
try {
$course = get_course($courseid);
} catch (Exception $ex) {
$course = null;
}
if (!$course) {
if (!get_course(((int) $courseid) - 1)) {
die("Cannot recreate course with this ID!");
}
}
if ($course && !$categoryid) {
$categoryid = $course->category;
}
$category = $DB->get_record('course_categories', array('id' => $categoryid), '*', MUST_EXIST);
$infile = $directory . DIRECTORY_SEPARATOR . "course_" . $courseid . "_" . $categoryid . "_" . $timestamp . ".mbz";
if (empty($CFG->tempdir)) {
$CFG->tempdir = $CFG->dataroot . DIRECTORY_SEPARATOR . 'temp';
}
if (!file_exists($infile)) {
die("Backup file '" . $infile . "' does not exist.");
}
if (!is_readable($infile)) {
die("Backup file '" . $infile . "' is not readable.");
}
$backupdir = "restore_" . uniqid();
if (isset($CFG->backuptempdir)){
$path = $CFG->backuptempdir . DIRECTORY_SEPARATOR . $backupdir;
}
else{
$path = $CFG->tempdir . DIRECTORY_SEPARATOR . "backup" . DIRECTORY_SEPARATOR . $backupdir;
}
$fp = get_file_packer('application/vnd.moodle.backup');
$fp->extract_to_pathname($infile, $path);
$xmlfile = $path . DIRECTORY_SEPARATOR . "course" . DIRECTORY_SEPARATOR . "course.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 (!$fullname) {
$fullname = $shortname;
}
if (!$course && $DB->get_record('course', array('category' => $category->id, 'shortname' => $shortname))) {
$matches = NULL;
preg_match('/(.*)_(\d+)$/', $shortname, $matches);
if ($matches) {
$base = $matches[1];
$number = $matches[2];
} else {
$base = $shortname;
$number = 1;
}
$shortname = $base . '_' . $number;
while ($DB->get_record('course', array('category' => $category->id, 'shortname' => $shortname))) {
$number++;
$shortname = $base . '_' . $number;
}
}
if ($course) {
echo "Overwriting current content of existing course -> Course ID: $courseid\n";
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
backup::MODE_GENERAL, $USER->id, 0);
} else {
echo "Creating new course to restore backup\n";
$courseid = restore_dbops::create_new_course($fullname, $shortname, $category->id);
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
backup::MODE_GENERAL, $USER->id, backup::TARGET_NEW_COURSE);
}
if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
$rc->convert();
}
$plan = $rc->get_plan();
if (!$rc->execute_precheck()){
$check = $rc->get_precheck_results();
echo("Restore pre-check failed!");
var_dump($check);
die();
}
$deletingoptions = array();
$deletingoptions['keep_roles_and_enrolments'] = 1;
$deletingoptions['keep_groups_and_groupings'] = 1;
restore_dbops::delete_course_content($courseid, $deletingoptions);
$rc->execute_plan();
$rc->destroy();
#unlink($infile);
$course = get_course($courseid);
$course->category = $categoryid;
$course->fullname = $fullname;
$course->shortname = $shortname;
$DB->update_record('course', $course);
echo "New course ID for '$shortname': $courseid in category {$category->id}\n";
echo "OK"