Skip to content

Commit aa20378

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

File tree

5 files changed

+759
-67
lines changed

5 files changed

+759
-67
lines changed

admin/settings/courses.php

+38
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,44 @@
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',
445+
new lang_string('defaultbackupfilenamesettings', 'backup'), ''));
446+
$temp->add(new admin_setting_description('defaultbackupfilenamesettings_help', '',
447+
new lang_string('defaultbackupfilenamesettings_help', 'backup'), ''));
448+
449+
$temp->add(new admin_setting_configtextarea('backup/backup_default_filename_template_course',
450+
new lang_string('defaultbackupfilenamecourse', 'backup'),
451+
new lang_string('defaultbackupfilenamecourse_desc', 'backup'),
452+
'{{#str}}backupfilename{{/str}}-{{format}}-{{type}}-{{id}}{{^useidonly}}-{{course.shortname}}{{/useidonly}}-{{date}}' .
453+
'{{^users}}-nu{{/users}}{{#anonymised}}{{#users}}-an{{/users}}{{/anonymised}}{{^files}}-nf{{/files}}',
454+
PARAM_TEXT,
455+
'60',
456+
'3'
457+
));
458+
459+
$temp->add(new admin_setting_configtextarea('backup/backup_default_filename_template_section',
460+
new lang_string('defaultbackupfilenamesection', 'backup'),
461+
new lang_string('defaultbackupfilenamesection_desc', 'backup'),
462+
'{{#str}}backupfilename{{/str}}-{{format}}-{{type}}-{{id}}{{^useidonly}}{{#section.name}}-{{section.name}}' .
463+
'{{/section.name}}{{^section.name}}-{{section.section}}{{/section.name}}{{/useidonly}}-{{date}}{{^users}}' .
464+
'-nu{{/users}}{{#anonymised}}{{#users}}-an{{/users}}{{/anonymised}}{{^files}}-nf{{/files}}',
465+
PARAM_TEXT,
466+
'60',
467+
'3'
468+
));
469+
470+
$temp->add(new admin_setting_configtextarea('backup/backup_default_filename_template_activity',
471+
new lang_string('defaultbackupfilenameactivity', 'backup'),
472+
new lang_string('defaultbackupfilenameactivity_desc', 'backup'),
473+
'{{#str}}backupfilename{{/str}}-{{format}}-{{type}}-{{id}}{{^useidonly}}-{{activity.modname}}{{id}}{{/useidonly}}' .
474+
'-{{date}}{{^users}}-nu{{/users}}{{#anonymised}}{{#users}}-an{{/users}}{{/anonymised}}{{^files}}-nf{{/files}}',
475+
PARAM_TEXT,
476+
'60',
477+
'3'
478+
));
479+
442480
$ADMIN->add('backups', $temp);
443481

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

backup/util/dbops/backup_plan_dbops.class.php

+104-66
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,115 @@ 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-
*/
214-
public static function get_default_backup_filename($format, $type, $id, $users, $anonymised,
215-
$useidonly = false, $files = true) {
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 $id course id, section id, or course module id
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+
* @param bool $files if files are included
217+
* @param int|null $time time to use in any dates, if not given uses current time
218+
* @return string The filename to use
219+
*/
220+
public static function get_default_backup_filename(string $format, string $type, int $id, bool $users, bool $anonymised,
221+
bool $useidonly = false, bool $files = true, ?int $time = null): string {
216222
global $DB;
217223

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;
224+
if ($time === null) {
225+
$time = time();
250226
}
251227

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

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

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

274312
/**

0 commit comments

Comments
 (0)