MDL-21432 backup - clean temps after execution
[moodle.git] / backup / util / helper / backup_helper.class.php
1 <?php
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/>.
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  */
25 /**
26  * Base abstract class for all the helper classes providing various operations
27  *
28  * TODO: Finish phpdocs
29  */
30 abstract class backup_helper {
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;
37         if (!check_dir_exists($CFG->dataroot . '/temp/backup/' . $backupid, true, true)) {
38             throw new backup_helper_exception('cannot_create_backup_temp_dir');
39         }
40     }
42     /**
43      * Given one backupid, ensure its temp dir is completely empty
44      */
45     static public function clear_backup_dir($backupid) {
46         global $CFG;
47         if (!self::delete_dir_contents($CFG->dataroot . '/temp/backup/' . $backupid)) {
48             throw new backup_helper_exception('cannot_empty_backup_temp_dir');
49         }
50         return true;
51     }
53     /**
54      * Given one backupid, delete completely its temp dir
55      */
56      static public function delete_backup_dir($backupid) {
57          global $CFG;
58          self::clear_backup_dir($backupid);
59          return rmdir($CFG->dataroot . '/temp/backup/' . $backupid);
60      }
62     /**
63      * Given one fullpath to directory, delete its contents recursively
64      * Copied originally from somewhere in the net.
65      * TODO: Modernise this
66      */
67     static public function delete_dir_contents($dir, $excludeddir='') {
68         if (!is_dir($dir)) {
69             // if we've been given a directory that doesn't exist yet, return true.
70             // this happens when we're trying to clear out a course that has only just
71             // been created.
72             return true;
73         }
74         $slash = "/";
76         // Create arrays to store files and directories
77         $dir_files      = array();
78         $dir_subdirs    = array();
80         // Make sure we can delete it
81         chmod($dir, 0777);
83         if ((($handle = opendir($dir))) == false) {
84             // The directory could not be opened
85             return false;
86         }
88         // Loop through all directory entries, and construct two temporary arrays containing files and sub directories
89         while (false !== ($entry = readdir($handle))) {
90             if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != "." && $entry != $excludeddir) {
91                 $dir_subdirs[] = $dir. $slash .$entry;
93             } else if ($entry != ".." && $entry != "." && $entry != $excludeddir) {
94                 $dir_files[] = $dir. $slash .$entry;
95             }
96         }
98         // Delete all files in the curent directory return false and halt if a file cannot be removed
99         for ($i=0; $i<count($dir_files); $i++) {
100             chmod($dir_files[$i], 0777);
101             if (((unlink($dir_files[$i]))) == false) {
102                 return false;
103             }
104         }
106         // Empty sub directories and then remove the directory
107         for ($i=0; $i<count($dir_subdirs); $i++) {
108             chmod($dir_subdirs[$i], 0777);
109             if (self::delete_dir_contents($dir_subdirs[$i]) == false) {
110                 return false;
111             } else {
112                 if (remove_dir($dir_subdirs[$i]) == false) {
113                     return false;
114                 }
115             }
116         }
118         // Close directory
119         closedir($handle);
121         // Success, every thing is gone return true
122         return true;
123     }
125     /**
126      * Delete all the temp dirs older than the time specified
127      */
128     static public function delete_old_backup_dirs($deletefrom) {
129         global $CFG;
131         $status = true;
132         // Get files and directories in the temp backup dir witout descend
133         $list = get_directory_list($CFG->dataroot . '/temp/backup', '', false, true, true);
134         foreach ($list as $file) {
135             $file_path = $CFG->dataroot . '/temp/backup/' . $file;
136             $moddate = filemtime($file_path);
137             if ($status && $moddate < $deletefrom) {
138                 //If directory, recurse
139                 if (is_dir($file_path)) {
140                     $status = self::delete_backup_dir($file_path);
141                 //If file
142                 } else {
143                     unlink($file_path);
144                 }
145             }
146         }
147         if (!$status) {
148             throw new backup_helper_exception('problem_deleting_old_backup_temp_dirs');
149         }
150     }
152     /**
153      * This function will be invoked by any log() method in backup/restore, acting
154      * as a simple forwarder to the standard loggers but also, if the $display
155      * parameter is true, supporting translation via get_string() and sending to
156      * standard output.
157      */
158     static public function log($message, $level, $a, $depth, $display, $logger) {
159         // Send to standard loggers
160         $logmessage = $message;
161         $options = empty($depth) ? array() : array('depth' => $depth);
162         if (!empty($a)) {
163             $logmessage = $logmessage . ' ' . implode(', ', (array)$a);
164         }
165         $logger->process($logmessage, $level, $options);
167         // If $display specified, send translated string to output_controller
168         if ($display) {
169             output_controller::get_instance()->output($message, 'backup', $a, $depth);
170         }
171     }
173     /**
174      * Given one backupid and the (FS) final generated file, perform its final storage
175      * into Moodle file storage
176      */
177     static public function store_backup_file($backupid, $filepath) {
179         // First of all, get some information from the backup_controller to help us decide
180         list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($backupid);
182         // Extract useful information to decide
183         $hasusers  = (bool)$sinfo['users']->value;     // Backup has users
184         $isannon   = (bool)$sinfo['anonymize']->value; // Backup is annonymzed
185         $backupmode= $dinfo[0]->mode;                  // Backup mode backup::MODE_GENERAL/IMPORT/HUB
186         $backuptype= $dinfo[0]->type;                  // Backup type backup::TYPE_1ACTIVITY/SECTION/COURSE
187         $userid    = $dinfo[0]->userid;                // User->id executing the backup
188         $id        = $dinfo[0]->id;                    // Id of activity/section/course (depends of type)
189         $courseid  = $dinfo[0]->courseid;              // Id of the course
191         // Backups of type IMPORT aren't stored ever
192         if ($backupmode == backup::MODE_IMPORT) {
193             return true;
194         }
196         // Calculate file storage options of id being backup
197         $ctxid    = 0;
198         $filearea = '';
199         $itemid   = 0;
200         switch ($backuptype) {
201             case backup::TYPE_1ACTIVITY:
202                 $ctxid    = get_context_instance(CONTEXT_MODULE, $id)->id;
203                 $filearea = 'activity_backup';
204                 $itemid   = 0;
205                 break;
206             case backup::TYPE_1SECTION:
207                 $ctxid    = get_context_instance(CONTEXT_COURSE, $courseid)->id;
208                 $filearea = 'section_backup';
209                 $itemid   = $id;
210                 break;
211             case backup::TYPE_1COURSE:
212                 $ctxid    = get_context_instance(CONTEXT_COURSE, $courseid)->id;
213                 $filearea = 'course_backup';
214                 $itemid   = 0;
215                 break;
216         }
218         // Backups of type HUB (by definition never have user info)
219         // are sent to user's "user_tohub" file area. The upload process
220         // will be responsible for cleaning that filearea once finished
221         if ($backupmode == backup::MODE_HUB) {
222             $ctxid = get_context_instance(CONTEXT_USER, $userid)->id;
223             $filearea = 'user_tohub';
224             $itemid   = 0;
225         }
227         // Backups without user info are sent to user's "user_backup"
228         // file area. Maintenance of such area is responsibility of
229         // the user via corresponding file manager frontend
230         if ($backupmode == backup::MODE_GENERAL && !$hasusers) {
231             $ctxid = get_context_instance(CONTEXT_USER, $userid)->id;
232             $filearea = 'user_backup';
233             $itemid   = 0;
234         }
236         // Let's send the file to file storage, everything already defined
237         $fs = get_file_storage();
238         $fr = array(
239             'contextid'   => $ctxid,
240             'filearea'    => $filearea,
241             'itemid'      => $itemid,
242             'filepath'    => '/',
243             'filename'    => basename($filepath),
244             'userid'      => $userid,
245             'timecreated' => time(),
246             'timemodified'=> time());
247         // If file already exists, delete if before
248         // creating it again. This is BC behaviour - copy()
249         // overwrites by default
250         if ($fs->file_exists($fr['contextid'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename'])) {
251             $pathnamehash = $fs->get_pathname_hash($fr['contextid'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename']);
252             $sf = $fs->get_file_by_hash($pathnamehash);
253             $sf->delete();
254         }
255         return $fs->create_file_from_pathname($fr, $filepath);
256     }
259 /*
260  * Exception class used by all the @helper stuff
261  */
262 class backup_helper_exception extends backup_exception {
264     public function __construct($errorcode, $a=NULL, $debuginfo=null) {
265         parent::__construct($errorcode, $a, $debuginfo);
266     }