diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml
new file mode 100644
index 0000000..8374965
--- /dev/null
+++ b/.github/workflows/moodle-ci.yml
@@ -0,0 +1,95 @@
+name: Moodle Plugin CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-22.04
+
+ services:
+ mariadb:
+ image: mariadb:10.11
+ env:
+ MYSQL_ALLOW_EMPTY_PASSWORD: "true"
+ MYSQL_CHARACTER_SET_SERVER: "utf8mb4"
+ MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci"
+ ports:
+ - 3306:3306
+ options: >-
+ --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - moodle-branch: 'MOODLE_405_STABLE'
+ php: '8.1'
+ database: 'mariadb'
+ extensions: 'mbstring, intl, xml, soap, zip, gd, opcache, sodium'
+ - moodle-branch: 'MOODLE_405_STABLE'
+ php: '8.3'
+ database: 'mariadb'
+ extensions: 'mbstring, intl, xml, soap, zip, gd, opcache, sodium'
+ - moodle-branch: 'MOODLE_501_STABLE'
+ php: '8.2'
+ database: 'mariadb'
+ extensions: 'mbstring, intl, xml, soap, zip, gd, opcache, sodium'
+ - moodle-branch: 'MOODLE_501_STABLE'
+ php: '8.4'
+ database: 'mariadb'
+ extensions: 'mbstring, intl, xml, soap, zip, gd, opcache, sodium'
+
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+ with:
+ path: plugin
+
+ - name: Setup PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: ${{ matrix.extensions }}
+ ini-values: max_input_vars=5000
+ coverage: none
+
+ - name: Initialise moodle-plugin-ci
+ run: |
+ composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4
+ echo $(cd ci/bin; pwd) >> $GITHUB_PATH
+ echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH
+ sudo locale-gen en_AU.UTF-8
+ echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV
+
+ - name: Install moodle-plugin-ci
+ run: |
+ moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1
+ env:
+ DB: ${{ matrix.database }}
+ MOODLE_BRANCH: ${{ matrix.moodle-branch }}
+
+ - name: PHP Lint
+ if: ${{ !cancelled() }}
+ run: moodle-plugin-ci phplint
+
+ - name: Moodle Code Checker
+ if: ${{ !cancelled() }}
+ run: moodle-plugin-ci phpcs --max-warnings 0
+
+ - name: Moodle PHPDoc Checker
+ if: ${{ !cancelled() }}
+ run: moodle-plugin-ci phpdoc --max-warnings 0
+
+ - name: Validating
+ if: ${{ !cancelled() }}
+ run: moodle-plugin-ci validate
+
+ - name: PHPUnit tests
+ if: ${{ !cancelled() }}
+ run: moodle-plugin-ci phpunit
+
+ - name: Mark cancelled jobs as failed
+ if: ${{ cancelled() }}
+ run: exit 1
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/brcli.iml b/.idea/brcli.iml
new file mode 100644
index 0000000..f5ccc9f
--- /dev/null
+++ b/.idea/brcli.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..ae0fe90
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php.xml b/.idea/php.xml
new file mode 100644
index 0000000..133d465
--- /dev/null
+++ b/.idea/php.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..8306744
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 4c68c56..5c6b91f 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ Please type the commands below to know how to use this plugin:
`sudo -u www-data /usr/bin/php admin/tool/brcli/restore.php --help`
## Release notes
+* v1.3 - additional options for backupping (neglect users, log, histories etc.) added
* v1.2 - the description of the plugin was improved.
* v1.1 - it is mandatory to inform the destination folder.
* v1.0 - the restore feature is available.
diff --git a/backup.php b/backup.php
index 956c95a..af2e427 100644
--- a/backup.php
+++ b/backup.php
@@ -1,24 +1,72 @@
.
+
/**
- * admin tool brcli
- * Backup & restore command line interface
- * @package admin
- * @subpackage tool
- * @author Paulo Júnior based on /admin/cli/backup.php
+ * Backup courses of a category via CLI.
+ *
+ * @package tool_brcli
+ * @copyright 2019 Paulo Júnior
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define('CLI_SCRIPT', 1);
-require(__DIR__.'/../../../config.php');
-require_once($CFG->libdir.'/clilib.php');
+define('CLI_SCRIPT', true);
+
+require(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir . '/clilib.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
-// Now get cli options.
-list($options, $unrecognized) = cli_get_params(array(
- 'categoryid' => false,
- 'destination' => '',
- 'help' => false,
- ), array('h' => 'help'));
+/**
+ * Safely set a backup plan setting if it exists.
+ *
+ * Moodle versions and backup modes may expose different settings,
+ * so we set only when present.
+ *
+ * @param backup_plan $plan The backup plan instance.
+ * @param string $settingname The name of the setting to set.
+ * @param mixed $value The value to assign.
+ * @return void
+ */
+function tool_brcli_backup_set_if_exists(backup_plan $plan, string $settingname, $value): void {
+ try {
+ $setting = $plan->get_setting($settingname);
+ $setting->set_value($value);
+ } catch (\Exception $e) {
+ // Setting does not exist in this Moodle version or mode.
+ $e; // Prevent unused variable warning.
+ }
+}
+
+// Now get CLI options.
+[$options, $unrecognized] = cli_get_params(
+ [
+ 'categoryid' => false,
+ 'destination' => '',
+ 'preset' => 'full',
+ 'users' => null,
+ 'questionbank' => null,
+ 'calendarevents' => null,
+ 'competencies' => null,
+ 'histories' => null,
+ 'logs' => null,
+ 'help' => false,
+ ],
+ [
+ 'h' => 'help',
+ ]
+);
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
@@ -35,54 +83,90 @@
cli_error(get_string('noadminaccount', 'tool_brcli'));
}
-// Do we need to store backup somewhere else?
-$dir = rtrim($options['destination'], '/');
+// Normalise and validate destination.
+$dir = rtrim($options['destination'], "/\\");
if (empty($dir) || !file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
cli_error(get_string('directoryerror', 'tool_brcli'));
}
// Check that the category exists.
-if ($DB->count_records('course_categories', array('id'=>$options['categoryid'])) == 0) {
+if ($DB->count_records('course_categories', ['id' => $options['categoryid']]) == 0) {
cli_error(get_string('nocategory', 'tool_brcli'));
-}
+}
-$courses = $DB->get_records('course', array('category'=>$options['categoryid']));
-$amount_of_courses = count($courses);
+$categoryid = (int) $options['categoryid'];
+$courses = $DB->get_records('course', ['category' => $categoryid]);
+$amountofcourses = count($courses);
$index = 1;
foreach ($courses as $cs) {
- $bc = new backup_controller(backup::TYPE_1COURSE, $cs->id, backup::FORMAT_MOODLE,
- backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
-
- mtrace(get_string('performingbck', 'tool_brcli', $index . '/' . $amount_of_courses));
+ $bc = new backup_controller(
+ backup::TYPE_1COURSE,
+ $cs->id,
+ backup::FORMAT_MOODLE,
+ backup::INTERACTIVE_YES,
+ backup::MODE_GENERAL,
+ $admin->id
+ );
+
+ mtrace(get_string('performingbck', 'tool_brcli', $index . '/' . $amountofcourses));
+
+ // Apply preset and overrides.
+ $plan = $bc->get_plan();
+ $overrides = [];
+ foreach (['users', 'questionbank', 'calendarevents', 'competencies', 'histories', 'logs'] as $name) {
+ if ($options[$name] !== null) {
+ $overrides[$name] = (int) $options[$name];
+ }
+ }
+
+ try {
+ $settings = \tool_brcli\local\preset::build_settings((string) $options['preset'], $overrides);
+ } catch (\InvalidArgumentException $e) {
+ $bc->destroy();
+ cli_error(get_string('invalidpreset', 'tool_brcli', $options['preset']));
+ }
+
+ foreach ($settings as $name => $value) {
+ tool_brcli_backup_set_if_exists($plan, $name, $value);
+ }
// 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();
+ $users = 1;
+ $anonymised = 0;
+ try {
+ $users = (int) $plan->get_setting('users')->get_value();
+ $anonymised = (int) $plan->get_setting('anonymize')->get_value();
+ } catch (\Exception $e) {
+ // Settings may not exist; use defaults.
+ $e; // Prevent unused variable warning.
+ }
$filename = backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised);
- $bc->get_plan()->get_setting('filename')->set_value($filename);
+ tool_brcli_backup_set_if_exists($plan, 'filename', $filename);
// Execution.
$bc->finish_ui();
$bc->execute_plan();
$results = $bc->get_results();
- $file = $results['backup_destination']; // May be empty if file already moved to target location.
+ $file = $results['backup_destination'] ?? null;
- // Do we need to store backup somewhere else?
+ // Store backup to the destination directory if needed.
if ($file) {
- if ($file->copy_content_to($dir.'/'.$filename)) {
+ $target = $dir . '/' . $filename;
+ if ($file->copy_content_to($target)) {
$file->delete();
} else {
mtrace(get_string('directoryerror', 'tool_brcli'));
}
}
$bc->destroy();
- $index = $index + 1;
+ $index++;
}
+
mtrace(get_string('operationdone', 'tool_brcli'));
-exit(0);
\ No newline at end of file
+exit(0);
diff --git a/classes/local/preset.php b/classes/local/preset.php
new file mode 100644
index 0000000..3547f45
--- /dev/null
+++ b/classes/local/preset.php
@@ -0,0 +1,92 @@
+.
+
+/**
+ * Preset and overrides handling for backup/restore plans.
+ *
+ * Extracted to make CLI behaviour testable and stable across Moodle versions.
+ *
+ * @package tool_brcli
+ * @copyright 2026 Ralf Erlebach
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_brcli\local;
+
+/**
+ * Preset helper for backup and restore CLI plans.
+ *
+ * @package tool_brcli
+ * @copyright 2026 Ralf Erlebach
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+final class preset {
+ /** @var string Full backup preset name. */
+ public const PRESET_FULL = 'full';
+
+ /** @var string Content-only backup preset name. */
+ public const PRESET_CONTENTONLY = 'contentonly';
+
+ /**
+ * Returns plan settings based on preset and overrides.
+ *
+ * @param string $preset Preset name ('full' or 'contentonly').
+ * @param array $overrides Key/value overrides, e.g. ['users' => 0]. Values are cast to int.
+ * @param array|null $available If provided, only settings in this whitelist are returned.
+ * @return array
+ * @throws \InvalidArgumentException If the preset name is not recognised.
+ */
+ public static function build_settings(string $preset, array $overrides = [], ?array $available = null): array {
+ $preset = strtolower(trim($preset));
+ if (!in_array($preset, [self::PRESET_FULL, self::PRESET_CONTENTONLY], true)) {
+ // Use a core PHP exception type here to avoid depending on Moodle exception class names.
+ throw new \InvalidArgumentException('Invalid preset');
+ }
+
+ $settings = [];
+
+ if ($preset === self::PRESET_CONTENTONLY) {
+ $settings = [
+ 'users' => 0,
+ 'role_assignments' => 0,
+ 'groups' => 0,
+ 'comments' => 0,
+ 'badges' => 0,
+ 'calendarevents' => 0,
+ 'userscompletion' => 0,
+ 'histories' => 0,
+ 'logs' => 0,
+ 'questionbank' => 0,
+ 'competencies' => 0,
+ 'contentbankcontent' => 0,
+ ];
+ }
+
+ foreach ($overrides as $name => $value) {
+ if ($value === null) {
+ continue;
+ }
+ $settings[(string) $name] = (int) $value;
+ }
+
+ if ($available !== null) {
+ $available = array_flip($available);
+ $settings = array_intersect_key($settings, $available);
+ }
+
+ return $settings;
+ }
+}
diff --git a/lang/en/tool_brcli.php b/lang/en/tool_brcli.php
index 7a4b66c..eee04a4 100644
--- a/lang/en/tool_brcli.php
+++ b/lang/en/tool_brcli.php
@@ -1,40 +1,76 @@
.
+
/**
- * admin tool brcli
- * Backup & restore command line interface
- * @package admin
- * @subpackage tool
- * @author Paulo Júnior
+ * Language strings for tool_brcli (English).
+ *
+ * @package tool_brcli
+ * @copyright 2019 Paulo Júnior
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-$string['pluginname'] = 'Backup and Restore Command-Line Interface';
-$string['unknowoption'] = 'Unknow option: {$a}';
-$string['noadminaccount'] = 'Error: No admin account was found!';
-$string['directoryerror'] = 'Error: Destination directory does not exists or not writable!';
-$string['nocategory'] = 'Error: No category was found!';
-$string['performingbck'] = 'Performing backup of the {$a} course...';
-$string['performingres'] = 'Restoring backup of the {$a} course...';
-$string['operationdone'] = 'Done!';
-$string['invalidbackupfile'] = 'Invalid backup file: {$a}';
-$string['helpoptionbck'] =
-'Perform backup of the courses of a specific category.
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['directoryerror'] = 'Error: Destination directory does not exist or is not writable!';
+$string['helpoptionbck'] = 'Perform backup of the courses of a specific category.
Options:
--categoryid=INTEGER Category ID for backup.
---destination=STRING Path where to store backup file.
+--destination=STRING Path where to store backup file.
+--preset=STRING Backup preset. full (default) or contentonly.
+--users=0|1 Override: include user data.
+--questionbank=0|1 Override: include question bank.
+--calendarevents=0|1 Override: include calendar events.
+--competencies=0|1 Override: include competencies.
+--histories=0|1 Override: include grade histories.
+--logs=0|1 Override: include logs.
-h, --help Print out this help.
Example:
sudo -u www-data /usr/bin/php admin/tool/brcli/backup.php --categoryid=1 --destination=/moodle/backup/
+
+ # Content-only backups (no users, question bank, calendar, competencies, logs, histories, etc.)
+ sudo -u www-data /usr/bin/php admin/tool/brcli/backup.php --categoryid=1 --destination=/moodle/backup/ --preset=contentonly
';
-$string['helpoptionres'] =
-'Restore all backup files belong to a specific folder.
+$string['helpoptionres'] = 'Restore all backup files belonging to a specific folder.
Options:
--categoryid=INTEGER Category ID where the backup must be restored.
---source=STRING Path where the backup files (.mbz) are.
+--source=STRING Path where the backup files (.mbz) are.
+--preset=STRING Restore preset. full (default) or contentonly.
+--users=0|1 Override: restore user data.
+--questionbank=0|1 Override: restore question bank.
+--calendarevents=0|1 Override: restore calendar events.
+--competencies=0|1 Override: restore competencies.
+--histories=0|1 Override: restore grade histories.
+--logs=0|1 Override: restore logs.
-h, --help Print out this help.
Example:
sudo -u www-data /usr/bin/php admin/tool/brcli/restore.php --categoryid=1 --source=/moodle/backup/
+
+ # Restore as content-only (ignore user data, question bank, calendar, competencies, logs, histories, etc.)
+ sudo -u www-data /usr/bin/php admin/tool/brcli/restore.php --categoryid=1 --source=/moodle/backup/ --preset=contentonly
';
+$string['invalidbackupfile'] = 'Invalid backup file: {$a}';
+$string['invalidpreset'] = 'Invalid preset: {$a}. Supported values: full, contentonly.';
+$string['noadminaccount'] = 'Error: No admin account was found!';
+$string['nocategory'] = 'Error: No category was found!';
+$string['operationdone'] = 'Done!';
+$string['performingbck'] = 'Performing backup of the {$a} course...';
+$string['performingres'] = 'Restoring backup of the {$a} course...';
+$string['pluginname'] = 'Backup and Restore Command-Line Interface';
+$string['unknowoption'] = 'Unknown option: {$a}';
diff --git a/lang/pt_br/tool_brcli.php b/lang/pt_br/tool_brcli.php
index 8b1e3fe..57165a9 100644
--- a/lang/pt_br/tool_brcli.php
+++ b/lang/pt_br/tool_brcli.php
@@ -1,41 +1,76 @@
.
+
/**
- * admin tool brcli
- * Backup & restore command line interface
- * @package admin
- * @subpackage tool
- * @author Paulo Júnior
+ * Language strings for tool_brcli (Brazilian Portuguese).
+ *
+ * @package tool_brcli
+ * @copyright 2019 Paulo Júnior
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-$string['pluginname'] = 'Interface de linha de comando para backup e restauração';
-$string['unknowoption'] = 'Opção inválida: {$a}';
-$string['noadminaccount'] = 'Erro: Não há uma conta de administrador cadastrada!';
+
+defined('MOODLE_INTERNAL') || die();
+
$string['directoryerror'] = 'Erro: O diretório de destino informado não existe ou não pode ser escrito!';
-$string['nocategory'] = 'Erro: A categoria informada não existe!';
-$string['performingbck'] = 'Iniciando backup do curso {$a}...';
-$string['performingres'] = 'Restaurando backup do curso {$a}...';
-$string['operationdone'] = 'Finalizado!';
-$string['invalidbackupfile'] = 'Arquivo de backup inválido: {$a}';
-$string['helpoptionbck'] =
-'Realiza o backup de todos os cursos de uma categoria.
+$string['helpoptionbck'] = 'Realiza o backup de todos os cursos de uma categoria.
Opções:
--categoryid=INTEGER ID da categoria cujo backup será feito.
---destination=STRING Caminho onde serão armazenados os arquivos de backup.
+--destination=STRING Caminho onde serão armazenados os arquivos de backup.
+--preset=STRING Preset do backup. full (padrão) ou contentonly.
+--users=0|1 Override: incluir dados de usuários.
+--questionbank=0|1 Override: incluir banco de questões.
+--calendarevents=0|1 Override: incluir eventos do calendário.
+--competencies=0|1 Override: incluir competências.
+--histories=0|1 Override: incluir histórico de notas.
+--logs=0|1 Override: incluir logs.
-h, --help Exibe a ajuda.
Exemplo:
sudo -u www-data /usr/bin/php admin/tool/brcli/backup.php --categoryid=1 --destination=/moodle/backup/
-';
-$string['helpoptionres'] =
-'Restaura todos os arquivos de backup contidos em um diretório.
+ # Backup apenas do conteúdo (sem usuários, banco de questões, calendário, competências, logs, históricos, etc.)
+ sudo -u www-data /usr/bin/php admin/tool/brcli/backup.php --categoryid=1 --destination=/moodle/backup/ --preset=contentonly
+';
+$string['helpoptionres'] = 'Restaura todos os arquivos de backup contidos em um diretório.
-Options:
+Opções:
--categoryid=INTEGER ID da categoria onde os backup serão restaurados.
--source=STRING Caminho onde os arquivos de backup (.mbz) estão armazenados.
+--preset=STRING Preset da restauração. full (padrão) ou contentonly.
+--users=0|1 Override: restaurar dados de usuários.
+--questionbank=0|1 Override: restaurar banco de questões.
+--calendarevents=0|1 Override: restaurar eventos do calendário.
+--competencies=0|1 Override: restaurar competências.
+--histories=0|1 Override: restaurar histórico de notas.
+--logs=0|1 Override: restaurar logs.
-h, --help Exibe a ajuda.
Exemplo:
sudo -u www-data /usr/bin/php admin/tool/brcli/restore.php --categoryid=1 --source=/moodle/backup/
-';
\ No newline at end of file
+
+ # Restaura apenas o conteúdo (ignora usuários, banco de questões, calendário, competências, logs, históricos, etc.)
+ sudo -u www-data /usr/bin/php admin/tool/brcli/restore.php --categoryid=1 --source=/moodle/backup/ --preset=contentonly
+';
+$string['invalidbackupfile'] = 'Arquivo de backup inválido: {$a}';
+$string['invalidpreset'] = 'Preset inválido: {$a}. Valores suportados: full, contentonly.';
+$string['noadminaccount'] = 'Erro: Não há uma conta de administrador cadastrada!';
+$string['nocategory'] = 'Erro: A categoria informada não existe!';
+$string['operationdone'] = 'Finalizado!';
+$string['performingbck'] = 'Iniciando backup do curso {$a}...';
+$string['performingres'] = 'Restaurando backup do curso {$a}...';
+$string['pluginname'] = 'Interface de linha de comando para backup e restauração';
+$string['unknowoption'] = 'Opção inválida: {$a}';
diff --git a/restore.php b/restore.php
index caf65d7..13cce64 100644
--- a/restore.php
+++ b/restore.php
@@ -1,24 +1,69 @@
.
+
/**
- * admin tool brcli
- * Backup & restore command line interface
- * @package admin
- * @subpackage tool
- * @author Paulo Júnior based on https://github.com/mudrd8mz/moodle-toolbox/blob/master/mbz/mbz-restorecourses.php
+ * Restore all .mbz backups from a folder into new courses within a category.
+ *
+ * @package tool_brcli
+ * @copyright 2019 Paulo Júnior
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define('CLI_SCRIPT', 1);
-require(__DIR__.'/../../../config.php');
-require_once($CFG->libdir.'/clilib.php');
+define('CLI_SCRIPT', true);
+
+require(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir . '/clilib.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
-// Now get cli options.
-list($options, $unrecognized) = cli_get_params(array(
- 'categoryid' => false,
- 'source' => '',
- 'help' => false,
- ), array('h' => 'help'));
+/**
+ * Safely set a restore plan setting if it exists.
+ *
+ * @param restore_plan $plan The restore plan instance.
+ * @param string $settingname The name of the setting to set.
+ * @param mixed $value The value to assign.
+ * @return void
+ */
+function tool_brcli_restore_set_if_exists(restore_plan $plan, string $settingname, $value): void {
+ try {
+ $setting = $plan->get_setting($settingname);
+ $setting->set_value($value);
+ } catch (\Exception $e) {
+ // Setting does not exist in this Moodle version or mode.
+ $e; // Prevent unused variable warning.
+ }
+}
+
+// Now get CLI options.
+[$options, $unrecognized] = cli_get_params(
+ [
+ 'categoryid' => false,
+ 'source' => '',
+ 'preset' => 'full',
+ 'users' => null,
+ 'questionbank' => null,
+ 'calendarevents' => null,
+ 'competencies' => null,
+ 'histories' => null,
+ 'logs' => null,
+ 'help' => false,
+ ],
+ [
+ 'h' => 'help',
+ ]
+);
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
@@ -35,70 +80,121 @@
cli_error(get_string('noadminaccount', 'tool_brcli'));
}
-$dir = rtrim($options['source'], '/');
-if (empty($dir) || !file_exists($dir) || !is_dir($dir)) {
+$dir = rtrim($options['source'], "/\\");
+if (empty($dir) || !file_exists($dir) || !is_dir($dir)) {
cli_error(get_string('directoryerror', 'tool_brcli'));
}
// Check that the category exists.
-if ($DB->count_records('course_categories', array('id'=>$options['categoryid'])) == 0) {
+if ($DB->count_records('course_categories', ['id' => $options['categoryid']]) == 0) {
cli_error(get_string('nocategory', 'tool_brcli'));
-}
+}
+
+$preset = (string) $options['preset'];
$index = 1;
-$sourcefiles = new FilesystemIterator($dir, FilesystemIterator::SKIP_DOTS);
-$amount_of_courses = iterator_count($sourcefiles);
+$sourcefiles = new \FilesystemIterator($dir, \FilesystemIterator::SKIP_DOTS);
+
+// Count only .mbz files for progress reporting.
+$amountofcourses = 0;
+foreach ($sourcefiles as $f) {
+ if (strtolower((string) $f->getExtension()) === 'mbz') {
+ $amountofcourses++;
+ }
+}
+
+// Rewind iterator.
+$sourcefiles = new \FilesystemIterator($dir, \FilesystemIterator::SKIP_DOTS);
foreach ($sourcefiles as $sourcefile) {
- if ($sourcefile->getExtension() !== 'mbz') {
+ if (strtolower((string) $sourcefile->getExtension()) !== 'mbz') {
continue;
}
- mtrace(get_string('performingres', 'tool_brcli', $index . '/' . $amount_of_courses));
+ mtrace(get_string('performingres', 'tool_brcli', $index . '/' . $amountofcourses));
// Extract the file.
$packer = get_file_packer('application/vnd.moodle.backup');
$backupid = restore_controller::get_tempdir_name(SITEID, $admin->id);
- $path = "$CFG->tempdir/backup/$backupid/";
+ $path = $CFG->tempdir . '/backup/' . $backupid . '/';
if (!$packer->extract_to_pathname($sourcefile->getPathname(), $path)) {
mtrace(get_string('invalidbackupfile', 'tool_brcli', $sourcefile->getFilename()));
+ $index++;
+ continue;
}
// Transaction.
$transaction = $DB->start_delegated_transaction();
-
+
// Create new course.
- $folder = $backupid; // as found in: $CFG->dataroot . '/temp/backup/'
- $categoryid = $options['categoryid']; // e.g. 1 == Miscellaneous
- $userdoingrestore = $admin->id; // e.g. 2 == admin
- $courseid = restore_dbops::create_new_course('', '', $categoryid);
-
+ $folder = $backupid;
+ $categoryid = (int) $options['categoryid'];
+ $userdoingrestore = $admin->id;
+ $courseid = restore_dbops::create_new_course('', '', $categoryid);
+
// Restore backup into course.
- $controller = new restore_controller($folder, $courseid,
- backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userdoingrestore,
- backup::TARGET_NEW_COURSE);
- if ($controller->execute_precheck()) {
- $controller->execute_plan();
- } else {
+ $controller = new restore_controller(
+ $folder,
+ $courseid,
+ backup::INTERACTIVE_NO,
+ backup::MODE_GENERAL,
+ $userdoingrestore,
+ backup::TARGET_NEW_COURSE
+ );
+
+ // Apply preset and overrides.
+ $plan = $controller->get_plan();
+ $overrides = [];
+ foreach (['users', 'questionbank', 'calendarevents', 'competencies', 'histories', 'logs'] as $name) {
+ if ($options[$name] !== null) {
+ $overrides[$name] = (int) $options[$name];
+ }
+ }
+
+ try {
+ $settings = \tool_brcli\local\preset::build_settings($preset, $overrides);
+ } catch (\InvalidArgumentException $e) {
+ cli_error(get_string('invalidpreset', 'tool_brcli', $preset));
+ }
+
+ foreach ($settings as $name => $value) {
+ tool_brcli_restore_set_if_exists($plan, $name, $value);
+ }
+
+ $precheck = $controller->execute_precheck();
+ if ($precheck !== true) {
try {
- $transaction->rollback(new Exception('Prechecked failed'));
- } catch (Exception $e) {
- unset($transaction);
- $controller->destroy();
- unset($controller);
- continue;
+ $transaction->rollback(new \Exception('Precheck failed'));
+ } catch (\Exception $e) {
+ // Ignore rollback exceptions.
+ $e; // Prevent unused variable warning.
}
+ unset($transaction);
+ $controller->destroy();
+ unset($controller);
+ $index++;
+ continue;
}
- $index = $index + 1;
+ $controller->execute_plan();
+
+ $index++;
-// Commit and clean up.
+ // Commit and clean up.
$transaction->allow_commit();
unset($transaction);
$controller->destroy();
unset($controller);
+
+ // Remove extracted temp backup.
+ if (!empty($backupid)) {
+ $temppath = $CFG->tempdir . '/backup/' . $backupid;
+ if (file_exists($temppath)) {
+ fulldelete($temppath);
+ }
+ }
}
mtrace(get_string('operationdone', 'tool_brcli'));
-exit(0);
\ No newline at end of file
+exit(0);
diff --git a/tests/preset_test.php b/tests/preset_test.php
new file mode 100644
index 0000000..4f34885
--- /dev/null
+++ b/tests/preset_test.php
@@ -0,0 +1,99 @@
+.
+
+/**
+ * Unit tests for tool_brcli presets.
+ *
+ * @package tool_brcli
+ * @copyright 2026 Ralf Erlebach
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+declare(strict_types=1);
+
+namespace tool_brcli;
+
+use tool_brcli\local\preset;
+
+/**
+ * Preset unit tests.
+ *
+ * @package tool_brcli
+ * @copyright 2026 Ralf Erlebach
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @coversDefaultClass \tool_brcli\local\preset
+ */
+final class preset_test extends \basic_testcase {
+ /**
+ * Test that the full preset returns an empty settings array by default.
+ *
+ * @covers ::build_settings
+ * @return void
+ */
+ public function test_full_preset_is_empty_by_default(): void {
+ $settings = preset::build_settings('full');
+ $this->assertSame([], $settings);
+ }
+
+ /**
+ * Test that the contentonly preset contains the expected core flags.
+ *
+ * @covers ::build_settings
+ * @return void
+ */
+ public function test_contentonly_preset_contains_expected_core_flags(): void {
+ $settings = preset::build_settings('contentonly');
+ $this->assertArrayHasKey('users', $settings);
+ $this->assertSame(0, $settings['users']);
+ $this->assertSame(0, $settings['questionbank']);
+ $this->assertSame(0, $settings['calendarevents']);
+ $this->assertSame(0, $settings['competencies']);
+ }
+
+ /**
+ * Test that overrides take precedence over preset defaults.
+ *
+ * @covers ::build_settings
+ * @return void
+ */
+ public function test_overrides_take_precedence(): void {
+ $settings = preset::build_settings('contentonly', ['users' => 1, 'questionbank' => 1]);
+ $this->assertSame(1, $settings['users']);
+ $this->assertSame(1, $settings['questionbank']);
+ }
+
+ /**
+ * Test that filtering by available settings works correctly.
+ *
+ * @covers ::build_settings
+ * @return void
+ */
+ public function test_available_filtering_works(): void {
+ $settings = preset::build_settings('contentonly', [], ['users', 'questionbank']);
+ $this->assertSame(['users' => 0, 'questionbank' => 0], $settings);
+ }
+
+ /**
+ * Test that an invalid preset name throws an exception.
+ *
+ * @covers ::build_settings
+ * @return void
+ */
+ public function test_invalid_preset_throws(): void {
+ $this->expectException(\InvalidArgumentException::class);
+ preset::build_settings('nope');
+ }
+}
diff --git a/version.php b/version.php
index 25b1a56..7cd5a1a 100644
--- a/version.php
+++ b/version.php
@@ -1,15 +1,32 @@
.
+
/**
- * admin tool brcli
- * Backup & restore command line interface
- * @package admin
- * @subpackage tool
- * @author Paulo Júnior
+ * Plugin version and other meta-data are defined here.
+ *
+ * @package tool_brcli
+ * @copyright 2019 Paulo Júnior
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2019031500; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires = 2015111605; // Requires this Moodle version
-$plugin->component = 'tool_brcli'; // Full name of the plugin (used for diagnostics)
-$plugin->maturity = MATURITY_STABLE;
-$plugin->release = 'v1.2';
\ No newline at end of file
+
+$plugin->component = 'tool_brcli';
+$plugin->version = 2026030500;
+$plugin->requires = 2024100700;
+$plugin->supported = [405, 501];
+$plugin->maturity = MATURITY_STABLE;
+$plugin->release = 'v1.3';