Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ jobs:
ci:
uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main
with:
extra_plugin_runners: 'moodle-plugin-ci add-plugin catalyst/moodle-local_aws'
extra_plugin_runners: 'git clone --branch MOODLE_33_STABLE https://github.com/catalyst/moodle-local_aws.git $GITHUB_WORKSPACE/moodle/local/aws'
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ These two scheduled tasks are created and run every 1 minute by default.
* Create backup adhoc tasks for course migration
* Create restore adhoc tasks for course migration

## Exclude certain activity types ##

The `excluded_mods` feature allows you to exclude specific activity types from the
backup on a per-course basis. This is useful when certain modules (e.g. third-party plugins
like `turnitintooltwo`) should not be included in the migrated
course backup.

To use this feature, add an optional `excluded_mods` column to your CSV file containing a
comma-separated list of activity types to exclude. For example:

```
courseid,categoryid,excluded_mods
101,5,turnitintooltwo
102,5,"turnitintooltwo, quiz"
103,5,
```

* If the `excluded_mods` column is omitted or left empty for a row, no activity type is excluded
and a standard backup is performed.
* Activity type names must match the plugin's short module name (e.g. `quiz`, `forum`,
`turnitintooltwo`), without the `mod_` prefix.
* An example CSV file with the `excluded_mods` column is available on the
`Upload course list` page.

## Quick start ##
* Install plugin on source and taget sites.
* Create a shared folder/disk accessible to both sites in their local file system.
Expand All @@ -74,6 +98,7 @@ These two scheduled tasks are created and run every 1 minute by default.
* Create a web service token _Site administration > Plugins > Web services > Manage tokens_
* Add the web service token to the source site configuration.
* Create a CSV file with course id and category id of courses to migrate.
* An optional `excluded_mods` column can be added to exclude specific activity types from backup.
* An example csv file is available on the `Upload course list` page.
* Upload the csv file at _Site administration > Plugins > Admin tools > Course migration > Upload course list_

Expand Down
5 changes: 5 additions & 0 deletions classes/coursemigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ protected static function define_properties() {
'null' => NULL_ALLOWED,
'default' => null,
],
'excluded_mods' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => null,
],
'error' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
Expand Down
14 changes: 14 additions & 0 deletions classes/output/coursemigration_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public function __construct(moodle_url $url, stdClass $filters, int $page = 0, i
'destinationcategory',
'status',
'filename',
'excluded_mods',
'timecreated',
'timemodified',
'error',
Expand All @@ -74,6 +75,7 @@ public function __construct(moodle_url $url, stdClass $filters, int $page = 0, i
get_string('destinationcategory', 'tool_coursemigration'),
get_string('status'),
get_string('filename', 'tool_coursemigration'),
get_string('excluded_mods', 'tool_coursemigration'),
get_string('timecreated', 'tool_coursemigration'),
get_string('timemodified', 'tool_coursemigration'),
get_string('error'),
Expand All @@ -88,6 +90,7 @@ public function __construct(moodle_url $url, stdClass $filters, int $page = 0, i
$this->no_sorting('destinationcategory');
$this->no_sorting('status');
$this->no_sorting('filename');
$this->no_sorting('excluded_mods');
$this->no_sorting('error');

$this->pageable(!empty($pagesize));
Expand Down Expand Up @@ -257,6 +260,17 @@ public function col_filename(stdClass $row): string {
return $row->filename ?? '';
}

/**
* Excluded mods column.
*
* @param stdClass $row
* @return string
*/
public function col_excluded_mods(stdClass $row): string {
$value = $row->excluded_mods ?? '';
return $this->is_downloading() ? $value : s($value);
}

/**
* Error column.
*
Expand Down
4 changes: 3 additions & 1 deletion classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public static function get_metadata(collection $collection): collection {
'action' => 'privacy:metadata:tool_coursemigration:action',
'courseid' => 'privacy:metadata:tool_coursemigration:courseid',
'destinationcategoryid' => 'privacy:metadata:tool_coursemigration:destinationcategoryid',
'excluded_mods' => 'privacy:metadata:tool_coursemigration:excluded_mods',
'usermodified' => 'privacy:metadata:tool_coursemigration:usermodified',
],
'privacy:metadata:tool_coursemigration'
Expand Down Expand Up @@ -100,14 +101,15 @@ public static function export_user_data(approved_contextlist $contextlist) {
'tool_coursemigration',
['usermodified' => $user->id],
'',
'action,courseid,destinationcategoryid'
'action,courseid,destinationcategoryid,excluded_mods'
);

foreach ($recordset as $record) {
$coursemigrations[] = [
'action' => $record->action,
'courseid' => $record->courseid,
'destinationcategoryid' => $record->destinationcategoryid,
'excluded_mods' => $record->excluded_mods,
];
}
$recordset->close();
Expand Down
16 changes: 16 additions & 0 deletions classes/task/course_backup.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
namespace tool_coursemigration\task;

use backup;
use backup_activity_task;
use backup_controller;
use backup_plan_dbops;
use core\task\adhoc_task;
Expand Down Expand Up @@ -103,6 +104,21 @@ public function execute() {
$bc->get_plan()->get_setting('users')->set_value(0);
$bc->get_plan()->get_setting('anonymize')->set_value(0);

// Apply module exclusions if specified.
$excludedmods = $coursemigration->get('excluded_mods');
if (!empty($excludedmods)) {
$exclusions = array_map('trim', explode(',', $excludedmods));
$exclusions = array_map('strtolower', array_filter($exclusions));
foreach ($bc->get_plan()->get_tasks() as $task) {
if ($task instanceof backup_activity_task) {
$modulename = strtolower($task->get_modulename());
if (in_array($modulename, $exclusions)) {
$task->get_setting('included')->set_value(0);
}
}
}
}

$format = $bc->get_format();
$type = $bc->get_type();
$id = $bc->get_id();
Expand Down
23 changes: 23 additions & 0 deletions classes/upload_course_list.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ class upload_course_list {
'destinationcategoryid' => ['categoryid'],
];

/**
* Optional columns that may be present in the CSV but are not required.
*/
const OPTIONAL_COLUMNS = [
'excluded_mods',
];

/**
* Function to process upload courses form.
*
Expand Down Expand Up @@ -150,6 +157,14 @@ private static function process_row(array $row, array $fields, int $rownumber):
}
}

// Handle optional columns.
foreach (self::OPTIONAL_COLUMNS as $optcol) {
if (isset($fields[$optcol])) {
$value = trim($row[$fields[$optcol]['columnindex']]);
$data[$optcol] = !empty($value) ? $value : null;
}
}

return [$status, (object) $data, $message];
}

Expand Down Expand Up @@ -191,6 +206,14 @@ private static function csv_required_columns(array $columns): ?array {
}

$message = empty($errors) ? null : implode(" AND ", $errors);

// Detect optional columns present in the CSV.
foreach (self::OPTIONAL_COLUMNS as $optcol) {
if (in_array($optcol, $columns)) {
$fields[$optcol] = ['columnname' => $optcol, 'columnindex' => array_search($optcol, $columns)];
}
}

return [$status, $message, $fields];
}

Expand Down
3 changes: 2 additions & 1 deletion db/install.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="admin/tool/coursemigration/db" VERSION="20230615" COMMENT="XMLDB file for Moodle admin/tool/coursemigration"
<XMLDB PATH="admin/tool/coursemigration/db" VERSION="20260805" COMMENT="XMLDB file for Moodle admin/tool/coursemigration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
Expand All @@ -12,6 +12,7 @@
<FIELD NAME="destinationcategoryid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="destination category when restoring"/>
<FIELD NAME="status" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="0 - failed, 1 - not started, 2 - in progress, 3 - completed"/>
<FIELD NAME="filename" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="The full Unicode name of this file (case sensitive)"/>
<FIELD NAME="excluded_mods" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Comma-separated list of module types to exclude from backup, e.g. turnitintooltwo, quiz"/>
<FIELD NAME="error" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="in case if the status is failed we need to save an error"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
Expand Down
14 changes: 14 additions & 0 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,19 @@ function xmldb_tool_coursemigration_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2023080800, 'tool', 'coursemigration');
}

if ($oldversion < 2023081602) {
// Define field excluded_mods to be added to tool_coursemigration.
$table = new xmldb_table('tool_coursemigration');
$field = new xmldb_field('excluded_mods', XMLDB_TYPE_TEXT, null, null, null, null, null, 'filename');

// Conditionally launch add field excluded_mods.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Coursemigration savepoint reached.
upgrade_plugin_savepoint(true, 2023081602, 'tool', 'coursemigration');
}

return true;
}
8 changes: 4 additions & 4 deletions example.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
courseid,categoryid
1,2
2,2
3,4
courseid,categoryid,excluded_mods
2,2,
3,2,"turnitintooltwo, quiz"
4,4,forum
3 changes: 3 additions & 0 deletions lang/en/tool_coursemigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@
$string['filename'] = 'Filename';
$string['filters'] = 'Filters';
$string['migrationid'] = 'ID';
$string['excluded_mods'] = 'Excluded activity types';
$string['excluded_mods_help'] = 'Comma-separated list of activity types to exclude from backup (e.g. turnitintooltwo, quiz).';
$string['pluginname'] = 'Course migration';
$string['privacy:metadata:tool_coursemigration'] = 'Data relating users for the tool coursemigration plugin';
$string['privacy:metadata:tool_coursemigration:action'] = 'The action type for course migration';
$string['privacy:metadata:tool_coursemigration:courseid'] = 'The source/destination courseid';
$string['privacy:metadata:tool_coursemigration:destinationcategoryid'] = 'The destination categoryid';
$string['privacy:metadata:tool_coursemigration:excluded_mods'] = 'Comma-separated list of activity types excluded from backup';
$string['privacy:metadata:tool_coursemigration:usermodified'] = 'The ID of the user who modified the record';
$string['settings:awss3'] = 'Amazon S3 Settings';
$string['settings:awss3_bucket'] = 'Bucket';
Expand Down
3 changes: 3 additions & 0 deletions tests/privacy/provider_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function setup_test_data(): void {
$data->action = coursemigration::ACTION_RESTORE;
$data->courseid = 123456;
$data->destinationcategoryid = 654321;
$data->excluded_mods = 'turnitintooltwo,quiz';
$coursemigration1 = new coursemigration(0, $data);
$coursemigration1->save();

Expand Down Expand Up @@ -168,6 +169,7 @@ public function test_export_user_data() {
$this->assertEquals(coursemigration::ACTION_RESTORE, $coursemigrations['action']);
$this->assertEquals(123456, $coursemigrations['courseid']);
$this->assertEquals(654321, $coursemigrations['destinationcategoryid']);
$this->assertEquals('turnitintooltwo,quiz', $coursemigrations['excluded_mods']);

writer::reset();
$writer = writer::with_context($context);
Expand All @@ -187,6 +189,7 @@ public function test_export_user_data() {
$this->assertEquals(coursemigration::ACTION_BACKUP, $coursemigrations['action']);
$this->assertEquals(112233, $coursemigrations['courseid']);
$this->assertEquals(332211, $coursemigrations['destinationcategoryid']);
$this->assertNull($coursemigrations['excluded_mods']);
}

/**
Expand Down
Loading
Loading