Skip to content

Commit 3cfea25

Browse files
committed
MDL-84440 backup: Add configurable default backup filename format
1 parent 77a7d81 commit 3cfea25

File tree

5 files changed

+727
-51
lines changed

5 files changed

+727
-51
lines changed

admin/settings/courses.php

+32
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,38 @@
439439
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_legacyfiles',
440440
new lang_string('generallegacyfiles', 'backup'),
441441
new lang_string('configlegacyfiles', 'backup'), array('value' => 1, 'locked' => 0)));
442+
443+
// Filename defaults.
444+
$temp->add(new admin_setting_heading('defaultfilenamesettings', new lang_string('defaultfilenamesettings', 'backup'), ''));
445+
$temp->add(new admin_setting_description('defaultfilenamesettings_help', '', new lang_string('defaultfilenamesettings_help', 'backup'), ''));
446+
447+
$temp->add(new admin_setting_configtextarea('backup/backup_default_filename_template_course',
448+
new lang_string('defaultbackupfilenamecourse', 'backup'),
449+
new lang_string('defaultbackupfilenamecourse_desc', 'backup'),
450+
'{{#str}}backupfilename{{/str}}-{{format}}-{{type}}-{{id}}{{^useidonly}}-{{course.shortname}}{{/useidonly}}-{{date}}{{^users}}-nu{{/users}}{{#anonymised}}{{#users}}-an{{/users}}{{/anonymised}}{{^files}}-nf{{/files}}',
451+
PARAM_TEXT,
452+
'60',
453+
'3'
454+
));
455+
456+
$temp->add(new admin_setting_configtextarea('backup/backup_default_filename_template_section',
457+
new lang_string('defaultbackupfilenamesection', 'backup'),
458+
new lang_string('defaultbackupfilenamesection_desc', 'backup'),
459+
'{{#str}}backupfilename{{/str}}-{{format}}-{{type}}-{{id}}{{^useidonly}}{{#section.name}}-{{section.name}}{{/section.name}}{{^section.name}}-{{section.section}}{{/section.name}}{{/useidonly}}-{{date}}{{^users}}-nu{{/users}}{{#anonymised}}{{#users}}-an{{/users}}{{/anonymised}}{{^files}}-nf{{/files}}',
460+
PARAM_TEXT,
461+
'60',
462+
'3'
463+
));
464+
465+
$temp->add(new admin_setting_configtextarea('backup/backup_default_filename_template_activity',
466+
new lang_string('defaultbackupfilenameactivity', 'backup'),
467+
new lang_string('defaultbackupfilenameactivity_desc', 'backup'),
468+
'{{#str}}backupfilename{{/str}}-{{format}}-{{type}}-{{id}}{{^useidonly}}-{{activity.modname}}{{id}}{{/useidonly}}-{{date}}{{^users}}-nu{{/users}}{{#anonymised}}{{#users}}-an{{/users}}{{/anonymised}}{{^files}}-nf{{/files}}',
469+
PARAM_TEXT,
470+
'60',
471+
'3'
472+
));
473+
442474
$ADMIN->add('backups', $temp);
443475

444476
// Create a page for general import configuration and defaults.

backup/util/dbops/backup_plan_dbops.class.php

+84-50
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2323
*/
2424

25+
use core\exception\coding_exception;
26+
use core\output\mustache_engine;
27+
use core\output\mustache_string_helper;
28+
2529
/**
2630
* Non instantiable helper class providing DB support to the @backup_plan class
2731
*
@@ -199,7 +203,7 @@ public static function get_mnet_localhost_wwwroot() {
199203
* Default format is (see MDL-22145)
200204
* backup word - format - type - name - date - info . mbz
201205
* where name is variable (course shortname, section name/id, activity modulename + cmid)
202-
* and info can be (nu = no user info, an = anonymized). The last param $useidasname,
206+
* and info can be (nu = no user info, an = anonymized). The last param $useidonly,
203207
* defaulting to false, allows to replace the course shortname by the course id (used
204208
* by automated backups, to avoid non-ascii chars in OS filesystem)
205209
*
@@ -212,63 +216,94 @@ public static function get_mnet_localhost_wwwroot() {
212216
* @return string The filename to use
213217
*/
214218
public static function get_default_backup_filename($format, $type, $id, $users, $anonymised,
215-
$useidonly = false, $files = true) {
219+
$useidonly = false, $files = true, $time = null) {
216220
global $DB;
217221

218-
// Calculate backup word
219-
$backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename')));
220-
$backupword = trim(clean_filename($backupword), '_');
221-
222-
// Not $useidonly, lets fetch the name
223-
$shortname = '';
224-
if (!$useidonly) {
225-
// Calculate proper name element (based on type)
226-
switch ($type) {
227-
case backup::TYPE_1COURSE:
228-
$shortname = $DB->get_field('course', 'shortname', array('id' => $id));
229-
$context = context_course::instance($id);
230-
$shortname = format_string($shortname, true, array('context' => $context));
231-
break;
232-
case backup::TYPE_1SECTION:
233-
if (!$shortname = $DB->get_field('course_sections', 'name', array('id' => $id))) {
234-
$shortname = $DB->get_field('course_sections', 'section', array('id' => $id));
235-
}
236-
break;
237-
case backup::TYPE_1ACTIVITY:
238-
$cm = get_coursemodule_from_id(null, $id);
239-
$shortname = $cm->modname . $id;
240-
break;
241-
}
242-
$shortname = str_replace(' ', '_', $shortname);
243-
$shortname = core_text::strtolower(trim(clean_filename($shortname), '_'));
244-
}
245-
246-
// The name will always contain the ID, but we append the course short name if requested.
247-
$name = $id;
248-
if (!$useidonly && $shortname != '') {
249-
$name .= '-' . $shortname;
222+
if ($time === null) {
223+
$time = time();
250224
}
251225

252-
// Calculate date
253226
$backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
254-
$date = userdate(time(), $backupdateformat, 99, false);
255-
$date = core_text::strtolower(trim(clean_filename($date), '_'));
256-
257-
// Calculate info
258-
$info = '';
259-
if (!$users) {
260-
$info = '-nu';
261-
} else if ($anonymised) {
262-
$info = '-an';
227+
$formatdate = function(int $date) use ($backupdateformat): string {
228+
$date = userdate($date, $backupdateformat, 99, false);
229+
return core_text::strtolower(trim(clean_filename($date), '_'));
230+
};
231+
232+
$context = [
233+
'format' => $format,
234+
'type' => $type,
235+
'id' => $id,
236+
'users' => $users,
237+
'anonymised' => $anonymised,
238+
'files' => $files,
239+
'useidonly' => $useidonly,
240+
'time' => $time,
241+
'date' => $formatdate($time),
242+
];
243+
244+
// Add extra context based on the type of backup.
245+
// It is important to use array and not stdClass here, otherwise array_walk_recursive will not work.
246+
switch ($type) {
247+
case backup::TYPE_1COURSE:
248+
$context['course'] = (array) $DB->get_record('course', ['id' => $id], 'shortname,fullname,startdate,enddate', MUST_EXIST);
249+
$context['course']['startdate'] = $formatdate($context['course']['startdate']);
250+
$context['course']['enddate'] = $formatdate($context['course']['enddate']);
251+
break;
252+
case backup::TYPE_1SECTION:
253+
$context['section'] = (array) $DB->get_record('course_sections', ['id' => $id], 'name,section', MUST_EXIST);
254+
break;
255+
case backup::TYPE_1ACTIVITY:
256+
$cm = get_coursemodule_from_id(null, $id, 0, false, MUST_EXIST);
257+
$context['activity'] = [
258+
'modname' => $cm->modname,
259+
'name' => $cm->name,
260+
];
261+
break;
262+
default:
263+
throw new coding_exception('Unknown backup type - cannot get template context');
263264
}
264265

265-
// Indicate if backup doesn't contain files.
266-
if (!$files) {
267-
$info .= '-nf';
266+
$instancecontext = null;
267+
switch ($type) {
268+
case backup::TYPE_1COURSE:
269+
$instancecontext = context_course::instance($id);
270+
break;
271+
case backup::TYPE_1SECTION:
272+
// A section is still course context, but needs an extra step to find the course id.
273+
$courseid = $DB->get_field('course_sections', 'course', ['id' => $id], MUST_EXIST);
274+
$instancecontext = context_course::instance($courseid);
275+
break;
276+
case backup::TYPE_1ACTIVITY:
277+
$instancecontext = context_module::instance($id);
278+
break;
279+
default:
280+
throw new coding_exception('Unknown backup type - cannot get instance context');
268281
}
269282

270-
return $backupword . '-' . $format . '-' . $type . '-' .
271-
$name . '-' . $date . $info . '.mbz';
283+
// Recursively format all the strings and trim any extra whitespace.
284+
array_walk_recursive($context, function(&$item) use ($instancecontext) {
285+
if (is_string($item)) {
286+
// Update by reference.
287+
$item = trim(format_string($item, true, ['context' => $instancecontext]));
288+
}
289+
});
290+
291+
$templates = [
292+
backup::TYPE_1COURSE => get_config('backup', 'backup_default_filename_template_course'),
293+
backup::TYPE_1SECTION => get_config('backup', 'backup_default_filename_template_section'),
294+
backup::TYPE_1ACTIVITY => get_config('backup', 'backup_default_filename_template_activity'),
295+
];
296+
$template = $templates[$type];
297+
$mustache = new mustache_engine([
298+
'helpers' => [
299+
'str' => [new mustache_string_helper(), 'str'],
300+
],
301+
]);
302+
$new = $mustache->render($template, $context);
303+
304+
// Clean as filename, remove spaces, and trim to max 251 chars (filename limit, 255 including .mbz extension).
305+
$cleaned = substr(str_replace(' ', '_', clean_filename($new)),0, 251);
306+
return $cleaned . '.mbz';
272307
}
273308

274309
/**
@@ -289,7 +324,6 @@ public static function require_gradebook_backup($courseid, $backupid) {
289324
AND bi.backupid = :backupid)";
290325
$params = array('courseid'=>$courseid, 'backupid'=>$backupid);
291326

292-
293327
$count = $DB->count_records_sql($sql, $params);
294328

295329
//if there are 0 activity grade items not already included in the backup

0 commit comments

Comments
 (0)