MDL-54205 backup: loggers close() and destroy()
[moodle.git] / backup / util / helper / backup_helper.class.php
CommitLineData
69dd0c8c
EL
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * @package moodlecore
20 * @subpackage backup-helper
21 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25/**
26 * Base abstract class for all the helper classes providing various operations
27 *
28 * TODO: Finish phpdocs
29 */
30abstract class backup_helper {
31
32 /**
33 * Given one backupid, create all the needed dirs to have one backup temp dir available
34 */
35 static public function check_and_create_backup_dir($backupid) {
36 global $CFG;
7aa06e6d 37 if (!check_dir_exists($CFG->tempdir . '/backup/' . $backupid, true, true)) {
69dd0c8c
EL
38 throw new backup_helper_exception('cannot_create_backup_temp_dir');
39 }
40 }
41
42 /**
2de3539b 43 * Given one backupid, ensure its temp dir is completely empty
1cd39657 44 *
45 * If supplied, progress object should be ready to receive indeterminate
46 * progress reports.
47 *
48 * @param string $backupid Backup id
809fdb83 49 * @param \core\progress\base $progress Optional progress reporting object
69dd0c8c 50 */
809fdb83 51 static public function clear_backup_dir($backupid, \core\progress\base $progress = null) {
69dd0c8c 52 global $CFG;
1cd39657 53 if (!self::delete_dir_contents($CFG->tempdir . '/backup/' . $backupid, '', $progress)) {
69dd0c8c
EL
54 throw new backup_helper_exception('cannot_empty_backup_temp_dir');
55 }
2de3539b 56 return true;
69dd0c8c
EL
57 }
58
2de3539b
EL
59 /**
60 * Given one backupid, delete completely its temp dir
1cd39657 61 *
62 * If supplied, progress object should be ready to receive indeterminate
63 * progress reports.
64 *
65 * @param string $backupid Backup id
809fdb83 66 * @param \core\progress\base $progress Optional progress reporting object
2de3539b 67 */
809fdb83 68 static public function delete_backup_dir($backupid, \core\progress\base $progress = null) {
2de3539b 69 global $CFG;
1cd39657 70 self::clear_backup_dir($backupid, $progress);
7aa06e6d 71 return rmdir($CFG->tempdir . '/backup/' . $backupid);
2de3539b
EL
72 }
73
2589eb89 74 /**
69dd0c8c
EL
75 * Given one fullpath to directory, delete its contents recursively
76 * Copied originally from somewhere in the net.
77 * TODO: Modernise this
1cd39657 78 *
79 * If supplied, progress object should be ready to receive indeterminate
80 * progress reports.
81 *
82 * @param string $dir Directory to delete
83 * @param string $excludedir Exclude this directory
809fdb83 84 * @param \core\progress\base $progress Optional progress reporting object
69dd0c8c 85 */
809fdb83 86 static public function delete_dir_contents($dir, $excludeddir='', \core\progress\base $progress = null) {
4756e9c9
PS
87 global $CFG;
88
1cd39657 89 if ($progress) {
90 $progress->progress();
91 }
92
69dd0c8c
EL
93 if (!is_dir($dir)) {
94 // if we've been given a directory that doesn't exist yet, return true.
95 // this happens when we're trying to clear out a course that has only just
96 // been created.
97 return true;
98 }
99 $slash = "/";
100
101 // Create arrays to store files and directories
102 $dir_files = array();
103 $dir_subdirs = array();
104
105 // Make sure we can delete it
4756e9c9 106 chmod($dir, $CFG->directorypermissions);
69dd0c8c
EL
107
108 if ((($handle = opendir($dir))) == false) {
109 // The directory could not be opened
110 return false;
111 }
112
113 // Loop through all directory entries, and construct two temporary arrays containing files and sub directories
114 while (false !== ($entry = readdir($handle))) {
115 if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != "." && $entry != $excludeddir) {
116 $dir_subdirs[] = $dir. $slash .$entry;
117
118 } else if ($entry != ".." && $entry != "." && $entry != $excludeddir) {
119 $dir_files[] = $dir. $slash .$entry;
120 }
121 }
122
123 // Delete all files in the curent directory return false and halt if a file cannot be removed
124 for ($i=0; $i<count($dir_files); $i++) {
4756e9c9 125 chmod($dir_files[$i], $CFG->directorypermissions);
69dd0c8c
EL
126 if (((unlink($dir_files[$i]))) == false) {
127 return false;
128 }
129 }
130
131 // Empty sub directories and then remove the directory
132 for ($i=0; $i<count($dir_subdirs); $i++) {
4756e9c9 133 chmod($dir_subdirs[$i], $CFG->directorypermissions);
1cd39657 134 if (self::delete_dir_contents($dir_subdirs[$i], '', $progress) == false) {
69dd0c8c
EL
135 return false;
136 } else {
137 if (remove_dir($dir_subdirs[$i]) == false) {
138 return false;
139 }
140 }
141 }
142
143 // Close directory
144 closedir($handle);
145
146 // Success, every thing is gone return true
147 return true;
148 }
149
150 /**
1cd39657 151 * Delete all the temp dirs older than the time specified.
152 *
153 * If supplied, progress object should be ready to receive indeterminate
154 * progress reports.
155 *
156 * @param int $deletefrom Time to delete from
809fdb83 157 * @param \core\progress\base $progress Optional progress reporting object
69dd0c8c 158 */
809fdb83 159 static public function delete_old_backup_dirs($deletefrom, \core\progress\base $progress = null) {
69dd0c8c
EL
160 global $CFG;
161
162 $status = true;
163 // Get files and directories in the temp backup dir witout descend
7aa06e6d 164 $list = get_directory_list($CFG->tempdir . '/backup', '', false, true, true);
69dd0c8c 165 foreach ($list as $file) {
7aa06e6d 166 $file_path = $CFG->tempdir . '/backup/' . $file;
69dd0c8c
EL
167 $moddate = filemtime($file_path);
168 if ($status && $moddate < $deletefrom) {
169 //If directory, recurse
170 if (is_dir($file_path)) {
2589eb89 171 // $file is really the backupid
1cd39657 172 $status = self::delete_backup_dir($file, $progress);
69dd0c8c
EL
173 //If file
174 } else {
175 unlink($file_path);
176 }
177 }
178 }
179 if (!$status) {
180 throw new backup_helper_exception('problem_deleting_old_backup_temp_dirs');
181 }
182 }
183
184 /**
185 * This function will be invoked by any log() method in backup/restore, acting
186 * as a simple forwarder to the standard loggers but also, if the $display
187 * parameter is true, supporting translation via get_string() and sending to
188 * standard output.
189 */
190 static public function log($message, $level, $a, $depth, $display, $logger) {
191 // Send to standard loggers
192 $logmessage = $message;
193 $options = empty($depth) ? array() : array('depth' => $depth);
194 if (!empty($a)) {
195 $logmessage = $logmessage . ' ' . implode(', ', (array)$a);
196 }
197 $logger->process($logmessage, $level, $options);
198
199 // If $display specified, send translated string to output_controller
200 if ($display) {
201 output_controller::get_instance()->output($message, 'backup', $a, $depth);
202 }
203 }
ce937f99
EL
204
205 /**
206 * Given one backupid and the (FS) final generated file, perform its final storage
dc1e4cce 207 * into Moodle file storage. For stored files it returns the complete file_info object
096702f6
PS
208 *
209 * Note: the $filepath is deleted if the backup file is created successfully
210 *
e555a443 211 * If you specify the progress monitor, this will start a new progress section
212 * to track progress in processing (in case this task takes a long time).
213 *
096702f6
PS
214 * @param int $backupid
215 * @param string $filepath zip file containing the backup
809fdb83 216 * @param \core\progress\base $progress Optional progress monitor
096702f6
PS
217 * @return stored_file if created, null otherwise
218 *
219 * @throws moodle_exception in case of any problems
ce937f99 220 */
809fdb83 221 static public function store_backup_file($backupid, $filepath, \core\progress\base $progress = null) {
096702f6 222 global $CFG;
ce937f99
EL
223
224 // First of all, get some information from the backup_controller to help us decide
e555a443 225 list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
226 $backupid, $progress);
ce937f99
EL
227
228 // Extract useful information to decide
229 $hasusers = (bool)$sinfo['users']->value; // Backup has users
64f93798 230 $isannon = (bool)$sinfo['anonymize']->value; // Backup is anonymised
9eeaea5f 231 $filename = $sinfo['filename']->value; // Backup filename
ce937f99 232 $backupmode= $dinfo[0]->mode; // Backup mode backup::MODE_GENERAL/IMPORT/HUB
cd0034d8 233 $backuptype= $dinfo[0]->type; // Backup type backup::TYPE_1ACTIVITY/SECTION/COURSE
ce937f99 234 $userid = $dinfo[0]->userid; // User->id executing the backup
cd0034d8
EL
235 $id = $dinfo[0]->id; // Id of activity/section/course (depends of type)
236 $courseid = $dinfo[0]->courseid; // Id of the course
096702f6 237 $format = $dinfo[0]->format; // Type of backup file
ce937f99 238
cf10078d 239 // Quick hack. If for any reason, filename is blank, fix it here.
dc1e4cce 240 // TODO: This hack will be out once MDL-22142 - P26 gets fixed
cf10078d
EL
241 if (empty($filename)) {
242 $filename = backup_plan_dbops::get_default_backup_filename('moodle2', $backuptype, $id, $hasusers, $isannon);
243 }
244
ce937f99
EL
245 // Backups of type IMPORT aren't stored ever
246 if ($backupmode == backup::MODE_IMPORT) {
096702f6
PS
247 return null;
248 }
249
250 if (!is_readable($filepath)) {
251 // we have a problem if zip file does not exist
252 throw new coding_exception('backup_helper::store_backup_file() expects valid $filepath parameter');
253
ce937f99
EL
254 }
255
cd0034d8 256 // Calculate file storage options of id being backup
64f93798
PS
257 $ctxid = 0;
258 $filearea = '';
259 $component = '';
260 $itemid = 0;
cd0034d8
EL
261 switch ($backuptype) {
262 case backup::TYPE_1ACTIVITY:
a689cd1d 263 $ctxid = context_module::instance($id)->id;
64f93798
PS
264 $component = 'backup';
265 $filearea = 'activity';
266 $itemid = 0;
cd0034d8
EL
267 break;
268 case backup::TYPE_1SECTION:
a689cd1d 269 $ctxid = context_course::instance($courseid)->id;
64f93798
PS
270 $component = 'backup';
271 $filearea = 'section';
272 $itemid = $id;
cd0034d8
EL
273 break;
274 case backup::TYPE_1COURSE:
a689cd1d 275 $ctxid = context_course::instance($courseid)->id;
64f93798
PS
276 $component = 'backup';
277 $filearea = 'course';
278 $itemid = 0;
cd0034d8
EL
279 break;
280 }
281
bac233d3
SH
282 if ($backupmode == backup::MODE_AUTOMATED) {
283 // Automated backups have there own special area!
284 $filearea = 'automated';
096702f6
PS
285
286 // If we're keeping the backup only in a chosen path, just move it there now
287 // this saves copying from filepool to here later and filling trashdir.
288 $config = get_config('backup');
289 $dir = $config->backup_auto_destination;
290 if ($config->backup_auto_storage == 1 and $dir and is_dir($dir) and is_writable($dir)) {
291 $filedest = $dir.'/'.backup_plan_dbops::get_default_backup_filename($format, $backuptype, $courseid, $hasusers, $isannon, !$config->backup_shortname);
292 // first try to move the file, if it is not possible copy and delete instead
293 if (@rename($filepath, $filedest)) {
294 return null;
295 }
eb459f71 296 umask($CFG->umaskpermissions);
096702f6
PS
297 if (copy($filepath, $filedest)) {
298 @chmod($filedest, $CFG->filepermissions); // may fail because the permissions may not make sense outside of dataroot
299 unlink($filepath);
300 return null;
80a1dfe5
AA
301 } else {
302 $bc = backup_controller::load_controller($backupid);
303 $bc->log('Attempt to copy backup file to the specified directory using filesystem failed - ',
304 backup::LOG_WARNING, $dir);
53f95c99 305 $bc->destroy();
096702f6
PS
306 }
307 // bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
308 }
bac233d3
SH
309 }
310
ce937f99
EL
311 // Backups of type HUB (by definition never have user info)
312 // are sent to user's "user_tohub" file area. The upload process
313 // will be responsible for cleaning that filearea once finished
314 if ($backupmode == backup::MODE_HUB) {
a689cd1d 315 $ctxid = context_user::instance($userid)->id;
64f93798
PS
316 $component = 'user';
317 $filearea = 'tohub';
318 $itemid = 0;
cd0034d8
EL
319 }
320
64f93798 321 // Backups without user info or with the anonymise functionality
1386bc09 322 // enabled are sent to user's "user_backup"
cd0034d8
EL
323 // file area. Maintenance of such area is responsibility of
324 // the user via corresponding file manager frontend
1386bc09 325 if ($backupmode == backup::MODE_GENERAL && (!$hasusers || $isannon)) {
a689cd1d 326 $ctxid = context_user::instance($userid)->id;
64f93798
PS
327 $component = 'user';
328 $filearea = 'backup';
329 $itemid = 0;
cd0034d8
EL
330 }
331
332 // Let's send the file to file storage, everything already defined
333 $fs = get_file_storage();
334 $fr = array(
335 'contextid' => $ctxid,
64f93798 336 'component' => $component,
cd0034d8
EL
337 'filearea' => $filearea,
338 'itemid' => $itemid,
339 'filepath' => '/',
9eeaea5f 340 'filename' => $filename,
cd0034d8
EL
341 'userid' => $userid,
342 'timecreated' => time(),
343 'timemodified'=> time());
344 // If file already exists, delete if before
345 // creating it again. This is BC behaviour - copy()
346 // overwrites by default
64f93798
PS
347 if ($fs->file_exists($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename'])) {
348 $pathnamehash = $fs->get_pathname_hash($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename']);
cd0034d8
EL
349 $sf = $fs->get_file_by_hash($pathnamehash);
350 $sf->delete();
ce937f99 351 }
096702f6
PS
352 $file = $fs->create_file_from_pathname($fr, $filepath);
353 unlink($filepath);
354 return $file;
ce937f99 355 }
c0bd6249
EL
356
357 /**
358 * This function simply marks one param to be considered as straight sql
359 * param, so it won't be searched in the structure tree nor converted at
360 * all. Useful for better integration of definition of sources in structure
361 * and DB stuff
362 */
363 public static function is_sqlparam($value) {
364 return array('sqlparam' => $value);
365 }
482aac65
EL
366
367 /**
368 * This function returns one array of itemnames that are being handled by
369 * inforef.xml files. Used both by backup and restore
370 */
371 public static function get_inforef_itemnames() {
767cb7f0 372 return array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item', 'question_category');
482aac65 373 }
69dd0c8c
EL
374}
375
376/*
377 * Exception class used by all the @helper stuff
378 */
379class backup_helper_exception extends backup_exception {
380
381 public function __construct($errorcode, $a=NULL, $debuginfo=null) {
382 parent::__construct($errorcode, $a, $debuginfo);
383 }
384}