Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
    public static function check_and_create_backup_dir($backupid) {
36
        $backupiddir = make_backup_temp_directory($backupid, false);
37
        if (empty($backupiddir)) {
38
            throw new backup_helper_exception('cannot_create_backup_temp_dir');
39
        }
40
    }
41
 
42
    /**
43
     * Given one backupid, ensure its temp dir is completely empty
44
     *
45
     * If supplied, progress object should be ready to receive indeterminate
46
     * progress reports.
47
     *
48
     * @param string $backupid Backup id
49
     * @param \core\progress\base $progress Optional progress reporting object
50
     */
51
    public static function clear_backup_dir($backupid, \core\progress\base $progress = null) {
52
        $backupiddir = make_backup_temp_directory($backupid, false);
53
        if (!self::delete_dir_contents($backupiddir, '', $progress)) {
54
            throw new backup_helper_exception('cannot_empty_backup_temp_dir');
55
        }
56
        return true;
57
    }
58
 
59
    /**
60
     * Given one backupid, delete completely its temp dir
61
     *
62
     * If supplied, progress object should be ready to receive indeterminate
63
     * progress reports.
64
     *
65
     * @param string $backupid Backup id
66
     * @param \core\progress\base $progress Optional progress reporting object
67
     */
68
     public static function delete_backup_dir($backupid, \core\progress\base $progress = null) {
69
         $backupiddir = make_backup_temp_directory($backupid, false);
70
         self::clear_backup_dir($backupid, $progress);
71
         return rmdir($backupiddir);
72
     }
73
 
74
     /**
75
     * Given one fullpath to directory, delete its contents recursively
76
     * Copied originally from somewhere in the net.
77
     * TODO: Modernise this
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
84
     * @param \core\progress\base $progress Optional progress reporting object
85
     */
86
    public static function delete_dir_contents($dir, $excludeddir='', \core\progress\base $progress = null) {
87
        global $CFG;
88
 
89
        if ($progress) {
90
            $progress->progress();
91
        }
92
 
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
106
        chmod($dir, $CFG->directorypermissions);
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++) {
125
            chmod($dir_files[$i], $CFG->directorypermissions);
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++) {
133
            chmod($dir_subdirs[$i], $CFG->directorypermissions);
134
            if (self::delete_dir_contents($dir_subdirs[$i], '', $progress) == false) {
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
    /**
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 $deletebefore Delete files and directories older than this time
157
     * @param \core\progress\base $progress Optional progress reporting object
158
     */
159
    public static function delete_old_backup_dirs($deletebefore, \core\progress\base $progress = null) {
160
        $status = true;
161
        // Get files and directories in the backup temp dir.
162
        $backuptempdir = make_backup_temp_directory('');
163
        $items = new DirectoryIterator($backuptempdir);
164
        foreach ($items as $item) {
165
            if ($item->isDot()) {
166
                continue;
167
            }
168
            if ($item->getMTime() < $deletebefore) {
169
                if ($item->isDir()) {
170
                    // The item is a directory for some backup.
171
                    if (!self::delete_backup_dir($item->getFilename(), $progress)) {
172
                        // Something went wrong. Finish the list of items and then throw an exception.
173
                        $status = false;
174
                    }
175
                } else if ($item->isFile()) {
176
                    unlink($item->getPathname());
177
                }
178
            }
179
        }
180
        if (!$status) {
181
            throw new backup_helper_exception('problem_deleting_old_backup_temp_dirs');
182
        }
183
    }
184
 
185
    /**
186
     * This function will be invoked by any log() method in backup/restore, acting
187
     * as a simple forwarder to the standard loggers but also, if the $display
188
     * parameter is true, supporting translation via get_string() and sending to
189
     * standard output.
190
     */
191
    public static function log($message, $level, $a, $depth, $display, $logger) {
192
        // Send to standard loggers
193
        $logmessage = $message;
194
        $options = empty($depth) ? array() : array('depth' => $depth);
195
        if (!empty($a)) {
196
            $logmessage = $logmessage . ' ' . implode(', ', (array)$a);
197
        }
198
        $logger->process($logmessage, $level, $options);
199
 
200
        // If $display specified, send translated string to output_controller
201
        if ($display) {
202
            output_controller::get_instance()->output($message, 'backup', $a, $depth);
203
        }
204
    }
205
 
206
    /**
207
     * Given one backupid and the (FS) final generated file, perform its final storage
208
     * into Moodle file storage. For stored files it returns the complete file_info object
209
     *
210
     * Note: the $filepath is deleted if the backup file is created successfully
211
     *
212
     * If you specify the progress monitor, this will start a new progress section
213
     * to track progress in processing (in case this task takes a long time).
214
     *
215
     * @param int $backupid
216
     * @param string $filepath zip file containing the backup
217
     * @param \core\progress\base $progress Optional progress monitor
218
     * @return stored_file if created, null otherwise
219
     *
220
     * @throws moodle_exception in case of any problems
221
     */
222
    public static function store_backup_file($backupid, $filepath, \core\progress\base $progress = null) {
223
        global $CFG;
224
 
225
        // First of all, get some information from the backup_controller to help us decide
226
        list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
227
                $backupid, $progress);
228
 
229
        // Extract useful information to decide
230
        $hasusers  = (bool)$sinfo['users']->value;     // Backup has users
231
        $isannon   = (bool)$sinfo['anonymize']->value; // Backup is anonymised
232
        $filename  = $sinfo['filename']->value;        // Backup filename
233
        $backupmode= $dinfo[0]->mode;                  // Backup mode backup::MODE_GENERAL/IMPORT/HUB
234
        $backuptype= $dinfo[0]->type;                  // Backup type backup::TYPE_1ACTIVITY/SECTION/COURSE
235
        $userid    = $dinfo[0]->userid;                // User->id executing the backup
236
        $id        = $dinfo[0]->id;                    // Id of activity/section/course (depends of type)
237
        $courseid  = $dinfo[0]->courseid;              // Id of the course
238
        $format    = $dinfo[0]->format;                // Type of backup file
239
 
240
        // Quick hack. If for any reason, filename is blank, fix it here.
241
        // TODO: This hack will be out once MDL-22142 - P26 gets fixed
242
        if (empty($filename)) {
243
            $filename = backup_plan_dbops::get_default_backup_filename('moodle2', $backuptype, $id, $hasusers, $isannon);
244
        }
245
 
246
        // Backups of type IMPORT aren't stored ever
247
        if ($backupmode == backup::MODE_IMPORT) {
248
            return null;
249
        }
250
 
251
        if (!is_readable($filepath)) {
252
            // we have a problem if zip file does not exist
253
            throw new coding_exception('backup_helper::store_backup_file() expects valid $filepath parameter');
254
 
255
        }
256
 
257
        // Calculate file storage options of id being backup
258
        $ctxid     = 0;
259
        $filearea  = '';
260
        $component = '';
261
        $itemid    = 0;
262
        switch ($backuptype) {
263
            case backup::TYPE_1ACTIVITY:
264
                $ctxid     = context_module::instance($id)->id;
265
                $component = 'backup';
266
                $filearea  = 'activity';
267
                $itemid    = 0;
268
                break;
269
            case backup::TYPE_1SECTION:
270
                $ctxid     = context_course::instance($courseid)->id;
271
                $component = 'backup';
272
                $filearea  = 'section';
273
                $itemid    = $id;
274
                break;
275
            case backup::TYPE_1COURSE:
276
                $ctxid     = context_course::instance($courseid)->id;
277
                $component = 'backup';
278
                $filearea  = 'course';
279
                $itemid    = 0;
280
                break;
281
        }
282
 
283
        if ($backupmode == backup::MODE_AUTOMATED) {
284
            // Automated backups have there own special area!
285
            $filearea  = 'automated';
286
 
287
            // If we're keeping the backup only in a chosen path, just move it there now
288
            // this saves copying from filepool to here later and filling trashdir.
289
            $config = get_config('backup');
290
            $dir = $config->backup_auto_destination;
291
            if ($config->backup_auto_storage == 1 and $dir and is_dir($dir) and is_writable($dir)) {
292
                $filedest = $dir.'/'
293
                        .backup_plan_dbops::get_default_backup_filename(
294
                                $format,
295
                                $backuptype,
296
                                $courseid,
297
                                $hasusers,
298
                                $isannon,
299
                                !$config->backup_shortname,
300
                                (bool)$config->backup_auto_files);
301
                // first try to move the file, if it is not possible copy and delete instead
302
                if (@rename($filepath, $filedest)) {
303
                    return null;
304
                }
305
                umask($CFG->umaskpermissions);
306
                if (copy($filepath, $filedest)) {
307
                    @chmod($filedest, $CFG->filepermissions); // may fail because the permissions may not make sense outside of dataroot
308
                    unlink($filepath);
309
                    return null;
310
                } else {
311
                    $bc = backup_controller::load_controller($backupid);
312
                    $bc->log('Attempt to copy backup file to the specified directory using filesystem failed - ',
313
                            backup::LOG_WARNING, $dir);
314
                    $bc->destroy();
315
                }
316
                // bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
317
            }
318
        }
319
 
320
        // Backups of type HUB (by definition never have user info)
321
        // are sent to user's "user_tohub" file area. The upload process
322
        // will be responsible for cleaning that filearea once finished
323
        if ($backupmode == backup::MODE_HUB) {
324
            $ctxid     = context_user::instance($userid)->id;
325
            $component = 'user';
326
            $filearea  = 'tohub';
327
            $itemid    = 0;
328
        }
329
 
330
        // Backups without user info or with the anonymise functionality
331
        // enabled are sent to user's "user_backup"
332
        // file area. Maintenance of such area is responsibility of
333
        // the user via corresponding file manager frontend
334
        if (($backupmode == backup::MODE_GENERAL  || $backupmode == backup::MODE_ASYNC) && (!$hasusers || $isannon)) {
335
            $ctxid     = context_user::instance($userid)->id;
336
            $component = 'user';
337
            $filearea  = 'backup';
338
            $itemid    = 0;
339
        }
340
 
341
        // Let's send the file to file storage, everything already defined
342
        $fs = get_file_storage();
343
        $fr = array(
344
            'contextid'   => $ctxid,
345
            'component'   => $component,
346
            'filearea'    => $filearea,
347
            'itemid'      => $itemid,
348
            'filepath'    => '/',
349
            'filename'    => $filename,
350
            'userid'      => $userid,
351
            'timecreated' => time(),
352
            'timemodified'=> time());
353
        // If file already exists, delete if before
354
        // creating it again. This is BC behaviour - copy()
355
        // overwrites by default
356
        if ($fs->file_exists($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename'])) {
357
            $pathnamehash = $fs->get_pathname_hash($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename']);
358
            $sf = $fs->get_file_by_hash($pathnamehash);
359
            $sf->delete();
360
        }
361
        $file = $fs->create_file_from_pathname($fr, $filepath);
362
        unlink($filepath);
363
        return $file;
364
    }
365
 
366
    /**
367
     * This function simply marks one param to be considered as straight sql
368
     * param, so it won't be searched in the structure tree nor converted at
369
     * all. Useful for better integration of definition of sources in structure
370
     * and DB stuff
371
     */
372
    public static function is_sqlparam($value) {
373
        return array('sqlparam' => $value);
374
    }
375
 
376
    /**
377
     * This function returns one array of itemnames that are being handled by
378
     * inforef.xml files. Used both by backup and restore
379
     */
380
    public static function get_inforef_itemnames() {
381
        return array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item', 'question_category');
382
    }
383
 
384
    /**
385
     * Print the course reuse dropdown.
386
     *
387
     * @param string $current The current course reuse option where the header is modified
388
     */
389
    public static function print_coursereuse_selector(string $current): void {
390
        global $OUTPUT, $PAGE;
391
 
392
        if ($coursereusenode = $PAGE->settingsnav->find('coursereuse', \navigation_node::TYPE_CONTAINER)) {
393
 
394
            $menuarray = \core\navigation\views\secondary::create_menu_element([$coursereusenode]);
395
            if (empty($menuarray)) {
396
                return;
397
            }
398
 
399
            $coursereuse = get_string('coursereuse');
400
            $activeurl = '';
401
            if (isset($menuarray[0])) {
402
                // Remove the "Course reuse" entry.
403
                $result = array_search($coursereuse, $menuarray[0][$coursereuse]);
404
                unset($menuarray[0][$coursereuse][$result]);
405
 
406
                // Find the active node.
407
                foreach ($menuarray[0] as $key => $value) {
408
                    $check = array_search($current, $value);
409
                    if ($check !== false) {
410
                        $activeurl = $check;
411
                    }
412
                }
413
            } else {
414
                $result = array_search($coursereuse, $menuarray);
415
                unset($menuarray[$result]);
416
 
417
                $check = array_search(get_string($current), $menuarray);
418
                if ($check !== false) {
419
                    $activeurl = $check;
420
                }
421
 
422
            }
423
 
424
            $selectmenu = new \core\output\select_menu('coursereusetype', $menuarray, $activeurl);
425
            $selectmenu->set_label(get_string('coursereusenavigationmenu'), ['class' => 'sr-only']);
426
            $options = \html_writer::tag(
427
                'div',
428
                $OUTPUT->render_from_template('core/tertiary_navigation_selector', $selectmenu->export_for_template($OUTPUT)),
429
                ['class' => 'row pb-3']
430
            );
431
            echo \html_writer::tag(
432
                'div',
433
                $options,
434
                ['class' => 'container-fluid tertiary-navigation full-width-bottom-border', 'id' => 'tertiary-navigation']);
435
        } else {
11 efrain 436
            echo $OUTPUT->heading(get_string($current), 2, 'mb-3');
1 efrain 437
        }
438
    }
439
}
440
 
441
/*
442
 * Exception class used by all the @helper stuff
443
 */
444
class backup_helper_exception extends backup_exception {
445
 
446
    public function __construct($errorcode, $a=NULL, $debuginfo=null) {
447
        parent::__construct($errorcode, $a, $debuginfo);
448
    }
449
}