Commit | Line | Data |
---|---|---|
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 | */ | |
30 | abstract 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 | */ | |
379 | class backup_helper_exception extends backup_exception { | |
380 | ||
381 | public function __construct($errorcode, $a=NULL, $debuginfo=null) { | |
382 | parent::__construct($errorcode, $a, $debuginfo); | |
383 | } | |
384 | } |