Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Helper functions for asynchronous backups and restores.
19
 *
20
 * @package    core
21
 * @copyright  2019 Matt Porritt <mattp@catalyst-au.net>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
require_once($CFG->dirroot . '/user/lib.php');
28
 
29
/**
30
 * Helper functions for asynchronous backups and restores.
31
 *
32
 * @package     core
33
 * @copyright   2019 Matt Porritt <mattp@catalyst-au.net>
34
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
class async_helper  {
37
 
38
    /**
39
     * @var string $type The type of async operation.
40
     */
41
    protected $type = 'backup';
42
 
43
    /**
44
     * @var string $backupid The id of the backup or restore.
45
     */
46
    protected $backupid;
47
 
48
    /**
49
     * @var object $user The user who created the backup record.
50
     */
51
    protected $user;
52
 
53
    /**
54
     * @var object $backuprec The backup controller record from the database.
55
     */
56
    protected $backuprec;
57
 
58
    /**
59
     * Class constructor.
60
     *
61
     * @param string $type The type of async operation.
62
     * @param string $id The id of the backup or restore.
63
     */
64
    public function __construct($type, $id) {
65
        $this->type = $type;
66
        $this->backupid = $id;
67
        $this->backuprec = self::get_backup_record($id);
68
        $this->user = $this->get_user();
69
    }
70
 
71
    /**
72
     * Given a backup id return a the record from the database.
73
     * We use this method rather than 'load_controller' as the controller may
74
     * not exist if this backup/restore has completed.
75
     *
76
     * @param int $id The backup id to get.
77
     * @return object $backuprec The backup controller record.
78
     */
79
    public static function get_backup_record($id) {
80
        global $DB;
81
 
82
        $backuprec = $DB->get_record('backup_controllers', array('backupid' => $id), '*', MUST_EXIST);
83
 
84
        return $backuprec;
85
    }
86
 
87
    /**
88
     * Given a user id return a user object.
89
     *
90
     * @return object $user The limited user record.
91
     */
92
    private function get_user() {
93
        $userid = $this->backuprec->userid;
94
        $user = core_user::get_user($userid, '*', MUST_EXIST);
95
 
96
        return $user;
97
    }
98
 
99
    /**
100
     * Return appropriate description for current async operation {@see async_helper::type}
101
     *
102
     * @return string
103
     */
104
    private function get_operation_description(): string {
105
        $operations = [
106
            'backup' => new lang_string('backup'),
107
            'copy' => new lang_string('copycourse'),
108
            'restore' => new lang_string('restore'),
109
        ];
110
 
111
        return (string) ($operations[$this->type] ?? $this->type);
112
    }
113
 
114
    /**
115
     * Callback for preg_replace_callback.
116
     * Replaces message placeholders with real values.
117
     *
118
     * @param array $matches The match array from from preg_replace_callback.
119
     * @return string $match The replaced string.
120
     */
121
    private function lookup_message_variables($matches) {
122
        $options = array(
123
                'operation' => $this->get_operation_description(),
124
                'backupid' => $this->backupid,
125
                'user_username' => $this->user->username,
126
                'user_email' => $this->user->email,
127
                'user_firstname' => $this->user->firstname,
128
                'user_lastname' => $this->user->lastname,
129
                'link' => $this->get_resource_link(),
130
        );
131
 
132
        $match = $options[$matches[1]] ?? $matches[1];
133
 
134
        return $match;
135
    }
136
 
137
    /**
138
     * Get the link to the resource that is being backuped or restored.
139
     *
140
     * @return moodle_url $url The link to the resource.
141
     */
142
    private function get_resource_link() {
143
        // Get activity context only for backups.
144
        if ($this->backuprec->type == 'activity' && $this->type == 'backup') {
145
            $context = context_module::instance($this->backuprec->itemid);
146
        } else { // Course or Section which have the same context getter.
147
            $context = context_course::instance($this->backuprec->itemid);
148
        }
149
 
150
        // Generate link based on operation type.
151
        if ($this->type == 'backup') {
152
            // For backups simply generate link to restore file area UI.
153
            $url = new moodle_url('/backup/restorefile.php', array('contextid' => $context->id));
154
        } else {
155
            // For restore generate link to the item itself.
156
            $url = $context->get_url();
157
        }
158
 
159
        return $url;
160
    }
161
 
162
    /**
163
     * Sends a confirmation message for an aynchronous process.
164
     *
165
     * @return int $messageid The id of the sent message.
166
     */
167
    public function send_message() {
168
        global $USER;
169
 
170
        $subjectraw = get_config('backup', 'backup_async_message_subject');
171
        $subjecttext = preg_replace_callback(
172
                '/\{([-_A-Za-z0-9]+)\}/u',
173
                array('async_helper', 'lookup_message_variables'),
174
                $subjectraw);
175
 
176
        $messageraw = get_config('backup', 'backup_async_message');
177
        $messagehtml = preg_replace_callback(
178
                '/\{([-_A-Za-z0-9]+)\}/u',
179
                array('async_helper', 'lookup_message_variables'),
180
                $messageraw);
181
        $messagetext = html_to_text($messagehtml);
182
 
183
        $message = new \core\message\message();
184
        $message->component = 'moodle';
185
        $message->name = 'asyncbackupnotification';
186
        $message->userfrom          = $USER;
187
        $message->userto            = $this->user;
188
        $message->subject           = $subjecttext;
189
        $message->fullmessage       = $messagetext;
190
        $message->fullmessageformat = FORMAT_HTML;
191
        $message->fullmessagehtml   = $messagehtml;
192
        $message->smallmessage      = '';
193
        $message->notification      = '1';
194
 
195
        $messageid = message_send($message);
196
 
197
        return $messageid;
198
    }
199
 
200
    /**
201
     * Check if asynchronous backup and restore mode is
202
     * enabled at system level.
203
     *
204
     * @return bool $async True if async mode enabled false otherwise.
205
     */
206
    public static function is_async_enabled() {
207
        global $CFG;
208
 
209
        $async = false;
210
        if (!empty($CFG->enableasyncbackup)) {
211
            $async = true;
212
        }
213
 
214
        return $async;
215
    }
216
 
217
    /**
218
     * Check if there is a pending async operation for given details.
219
     *
220
     * @param int $id The item id to check in the backup record.
221
     * @param string $type The type of operation: course, activity or section.
222
     * @param string $operation Operation backup or restore.
223
     * @return boolean $asyncpedning Is there a pending async operation.
224
     */
225
    public static function is_async_pending($id, $type, $operation) {
226
        global $DB, $USER, $CFG;
227
        $asyncpending = false;
228
 
229
        // Only check for pending async operations if async mode is enabled.
230
        require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
231
        require_once($CFG->dirroot . '/backup/backup.class.php');
232
 
233
        $select = 'userid = ? AND itemid = ? AND type = ? AND operation = ? AND execution = ? AND status < ? AND status > ?';
234
        $params = array(
235
            $USER->id,
236
            $id,
237
            $type,
238
            $operation,
239
            backup::EXECUTION_DELAYED,
240
            backup::STATUS_FINISHED_ERR,
241
            backup::STATUS_NEED_PRECHECK
242
        );
243
 
244
        $asyncrecord= $DB->get_record_select('backup_controllers', $select, $params);
245
 
246
        if ((self::is_async_enabled() && $asyncrecord) || ($asyncrecord && $asyncrecord->purpose == backup::MODE_COPY)) {
247
            $asyncpending = true;
248
        }
249
        return $asyncpending;
250
    }
251
 
252
    /**
253
     * Get the size, url and restore url for a backup file.
254
     *
255
     * @param string $filename The name of the file to get info for.
256
     * @param string $filearea The file area for the file.
257
     * @param int $contextid The context ID of the file.
258
     * @return array $results The result array containing the size, url and restore url of the file.
259
     */
260
    public static function get_backup_file_info($filename, $filearea, $contextid) {
261
        $fs = get_file_storage();
262
        $file = $fs->get_file($contextid, 'backup', $filearea, 0, '/', $filename);
263
        $filesize = display_size ($file->get_filesize());
264
        $fileurl = moodle_url::make_pluginfile_url(
265
            $file->get_contextid(),
266
            $file->get_component(),
267
            $file->get_filearea(),
268
            null,
269
            $file->get_filepath(),
270
            $file->get_filename(),
271
            true
272
            );
273
 
274
        $params = array();
275
        $params['action'] = 'choosebackupfile';
276
        $params['filename'] = $file->get_filename();
277
        $params['filepath'] = $file->get_filepath();
278
        $params['component'] = $file->get_component();
279
        $params['filearea'] = $file->get_filearea();
280
        $params['filecontextid'] = $file->get_contextid();
281
        $params['contextid'] = $contextid;
282
        $params['itemid'] = $file->get_itemid();
283
        $restoreurl = new moodle_url('/backup/restorefile.php', $params);
284
        $filesize = display_size ($file->get_filesize());
285
 
286
        $results = array(
287
            'filesize' => $filesize,
288
            'fileurl' => $fileurl->out(false),
289
            'restoreurl' => $restoreurl->out(false));
290
 
291
        return $results;
292
    }
293
 
294
    /**
295
     * Get the url of a restored backup item based on the backup ID.
296
     *
297
     * @param string $backupid The backup ID to get the restore location url.
298
     * @return array $urlarray The restored item URL as an array.
299
     */
300
    public static function get_restore_url($backupid) {
301
        global $DB;
302
 
303
        $backupitemid = $DB->get_field('backup_controllers', 'itemid', array('backupid' => $backupid), MUST_EXIST);
304
        $newcontext = context_course::instance($backupitemid);
305
 
306
        $restoreurl = $newcontext->get_url()->out();
307
        $urlarray = array('restoreurl' => $restoreurl);
308
 
309
        return $urlarray;
310
    }
311
 
312
    /**
313
     * Get markup for in progress async backups,
314
     * to use in backup table UI.
315
     *
316
     * @param string $filearea The filearea to get backup data for.
317
     * @param integer $instanceid The context id to get backup data for.
318
     * @return array $tabledata the rows of table data.
319
     */
320
    public static function get_async_backups($filearea, $instanceid) {
321
        global $DB;
322
 
323
        $backups = [];
324
 
325
        $table = 'backup_controllers';
326
        $select = 'execution = :execution AND status < :status1 AND status > :status2 ' .
327
            'AND operation = :operation';
328
        $params = [
329
            'execution' => backup::EXECUTION_DELAYED,
330
            'status1' => backup::STATUS_FINISHED_ERR,
331
            'status2' => backup::STATUS_NEED_PRECHECK,
332
            'operation' => 'backup',
333
        ];
334
        $sort = 'timecreated DESC';
335
        $fields = 'id, backupid, status, timecreated';
336
 
337
        if ($filearea == 'backup') {
338
            // Get relevant backup ids based on user id.
339
            $params['userid'] = $instanceid;
340
            $select = 'userid = :userid AND ' . $select;
341
            $records = $DB->get_records_select($table, $select, $params, $sort, $fields);
342
            foreach ($records as $record) {
343
                $bc = \backup_controller::load_controller($record->backupid);
344
 
345
                // Get useful info to render async status in correct area.
346
                list($hasusers, $isannon) = self::get_userdata_backup_settings($bc);
347
                // Backup has users and is not anonymised -> don't show it in users backup file area.
348
                if ($hasusers && !$isannon) {
349
                    continue;
350
                }
351
 
352
                $record->filename = $bc->get_plan()->get_setting('filename')->get_value();
353
                $bc->destroy();
354
                array_push($backups, $record);
355
            }
356
        } else {
357
            if ($filearea == 'course' || $filearea == 'activity') {
358
                // Get relevant backup ids based on context instance id.
359
                $params['itemid'] = $instanceid;
360
                $select = 'itemid = :itemid AND ' . $select;
361
                $records = $DB->get_records_select($table, $select, $params, $sort, $fields);
362
                foreach ($records as $record) {
363
                    $bc = \backup_controller::load_controller($record->backupid);
364
 
365
                    // Get useful info to render async status in correct area.
366
                    list($hasusers, $isannon) = self::get_userdata_backup_settings($bc);
367
                    // Backup has no user or is anonymised -> don't show it in course/activity backup file area.
368
                    if (!$hasusers || $isannon) {
369
                        continue;
370
                    }
371
 
372
                    $record->filename = $bc->get_plan()->get_setting('filename')->get_value();
373
                    $bc->destroy();
374
                    array_push($backups, $record);
375
                }
376
            }
377
        }
378
 
379
        return $backups;
380
    }
381
 
382
    /**
383
     * Get the user data settings for backups.
384
     *
385
     * @param \backup_controller $backupcontroller The backup controller object.
386
     * @return array Array of user data settings.
387
     */
388
    public static function get_userdata_backup_settings(\backup_controller $backupcontroller): array {
389
        $hasusers = (bool)$backupcontroller->get_plan()->get_setting('users')->get_value(); // Backup has users.
390
        $isannon = (bool)$backupcontroller->get_plan()->get_setting('anonymize')->get_value(); // Backup is anonymised.
391
        return [$hasusers, $isannon];
392
    }
393
 
394
    /**
395
     * Get the course name of the resource being restored.
396
     *
397
     * @param \context $context The Moodle context for the restores.
398
     * @return string $coursename The full name of the course.
399
     */
400
    public static function get_restore_name(\context $context) {
401
        global $DB;
402
        $instanceid = $context->instanceid;
403
 
404
        if ($context->contextlevel == CONTEXT_MODULE) {
405
            // For modules get the course name and module name.
406
            $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
407
            $coursename = $DB->get_field('course', 'fullname', array('id' => $cm->course));
408
            $itemname = $coursename . ' - ' . $cm->name;
409
        } else {
410
            $itemname = $DB->get_field('course', 'fullname', array('id' => $context->instanceid));
411
 
412
        }
413
 
414
        return $itemname;
415
    }
416
 
417
    /**
418
     * Get all the current in progress async restores for a user.
419
     *
420
     * @param int $userid Moodle user id.
421
     * @return array $restores List of current restores in progress.
422
     */
423
    public static function get_async_restores($userid) {
424
        global $DB;
425
 
426
        $select = 'userid = ? AND execution = ? AND status < ? AND status > ? AND operation = ?';
427
        $params = array($userid, backup::EXECUTION_DELAYED, backup::STATUS_FINISHED_ERR, backup::STATUS_NEED_PRECHECK, 'restore');
428
        $restores = $DB->get_records_select(
429
            'backup_controllers',
430
            $select,
431
            $params,
432
            'timecreated DESC',
433
            'id, backupid, status, itemid, timecreated');
434
 
435
            return $restores;
436
    }
437
 
438
}
439