Skip to content

Commit a7bea61

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

File tree

5 files changed

+744
-66
lines changed

5 files changed

+744
-66
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('defaultbackupfilenamesettings', new lang_string('defaultbackupfilenamesettings', 'backup'), ''));
445+
$temp->add(new admin_setting_description('defaultbackupfilenamesettings_help', '', new lang_string('defaultbackupfilenamesettings_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

+101-65
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
*
@@ -194,81 +198,113 @@ public static function get_mnet_localhost_wwwroot() {
194198
}
195199

196200
/**
197-
* Returns the default backup filename, based in passed params.
198-
*
199-
* Default format is (see MDL-22145)
200-
* backup word - format - type - name - date - info . mbz
201-
* 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,
203-
* defaulting to false, allows to replace the course shortname by the course id (used
204-
* by automated backups, to avoid non-ascii chars in OS filesystem)
205-
*
206-
* @param string $format One of backup::FORMAT_
207-
* @param string $type One of backup::TYPE_
208-
* @param int $courseid/$sectionid/$cmid
209-
* @param bool $users Should be true is users were included in the backup
210-
* @param bool $anonymised Should be true is user information was anonymized.
211-
* @param bool $useidonly only use the ID in the file name
212-
* @return string The filename to use
213-
*/
201+
* Returns the default backup filename, based in passed params.
202+
*
203+
* Default format is (see MDL-22145)
204+
* backup word - format - type - name - date - info . mbz
205+
* where name is variable (course shortname, section name/id, activity modulename + cmid)
206+
* and info can be (nu = no user info, an = anonymized). The last param $useidonly,
207+
* defaulting to false, allows to replace the course shortname by the course id (used
208+
* by automated backups, to avoid non-ascii chars in OS filesystem)
209+
*
210+
* @param string $format One of backup::FORMAT_
211+
* @param string $type One of backup::TYPE_
212+
* @param int $courseid/$sectionid/$cmid
213+
* @param bool $users Should be true is users were included in the backup
214+
* @param bool $anonymised Should be true is user information was anonymized.
215+
* @param bool $useidonly only use the ID in the file name
216+
* @return string The filename to use
217+
*/
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],
249+
'shortname,fullname,startdate,enddate', MUST_EXIST);
250+
$context['course']['startdate'] = $formatdate($context['course']['startdate']);
251+
$context['course']['enddate'] = $formatdate($context['course']['enddate']);
252+
break;
253+
case backup::TYPE_1SECTION:
254+
$context['section'] = (array) $DB->get_record('course_sections', ['id' => $id], 'name,section', MUST_EXIST);
255+
break;
256+
case backup::TYPE_1ACTIVITY:
257+
$cm = get_coursemodule_from_id(null, $id, 0, false, MUST_EXIST);
258+
$context['activity'] = [
259+
'modname' => $cm->modname,
260+
'name' => $cm->name,
261+
];
262+
break;
263+
default:
264+
throw new coding_exception('Unknown backup type - cannot get template context');
263265
}
264266

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

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

274310
/**

0 commit comments

Comments
 (0)