Commit | Line | Data |
---|---|---|
c6cc9726 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-controller | |
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 | * Class implementing the controller of any backup process | |
27 | * | |
28 | * This final class is in charge of controlling all the backup architecture, for any | |
29 | * type of backup. Based in type, format, interactivity and target, it stores the | |
30 | * whole execution plan and settings that will be used later by the @backup_worker, | |
31 | * applies all the defaults, performs all the security contraints and is in charge | |
32 | * of handling the ui if necessary. Also logging strategy is defined here. | |
33 | * | |
34 | * Note the class is 100% neutral and usable for *any* backup. It just stores/requests | |
35 | * all the needed information from other backup classes in order to have everything well | |
36 | * structured in order to allow the @backup_worker classes to do their job. | |
37 | * | |
38 | * In other words, a mammoth class, but don't worry, practically everything is delegated/ | |
39 | * aggregated!) | |
40 | * | |
41 | * TODO: Finish phpdocs | |
42 | */ | |
77b3d9df | 43 | class backup_controller extends base_controller { |
c6cc9726 EL |
44 | |
45 | protected $backupid; // Unique identificator for this backup | |
46 | ||
47 | protected $type; // Type of backup (activity, section, course) | |
48 | protected $id; // Course/section/course_module id to backup | |
49 | protected $courseid; // courseid where the id belongs to | |
50 | protected $format; // Format of backup (moodle, imscc) | |
51 | protected $interactive; // yes/no | |
52 | protected $mode; // Purpose of the backup (default settings) | |
53 | protected $userid; // user id executing the backup | |
f60f4666 | 54 | protected $operation; // Type of operation (backup/restore) |
c6cc9726 EL |
55 | |
56 | protected $status; // Current status of the controller (created, planned, configured...) | |
57 | ||
58 | protected $plan; // Backup execution plan | |
b1850c12 | 59 | protected $includefiles; // Whether this backup includes files or not. |
c6cc9726 EL |
60 | |
61 | protected $execution; // inmediate/delayed | |
62 | protected $executiontime; // epoch time when we want the backup to be executed (requires cron to run) | |
63 | ||
64 | protected $destination; // Destination chain object (fs_moodle, fs_os, db, email...) | |
16cd7088 | 65 | |
c6cc9726 EL |
66 | protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses |
67 | ||
b50bea4a SH |
68 | /** |
69 | * Constructor for the backup controller class. | |
70 | * | |
71 | * @param int $type Type of the backup; One of backup::TYPE_1COURSE, TYPE_1SECTION, TYPE_1ACTIVITY | |
72 | * @param int $id The ID of the item to backup; e.g the course id | |
73 | * @param int $format The backup format to use; Most likely backup::FORMAT_MOODLE | |
74 | * @param bool $interactive Whether this backup will require user interaction; backup::INTERACTIVE_YES or INTERACTIVE_NO | |
bac233d3 | 75 | * @param int $mode One of backup::MODE_GENERAL, MODE_IMPORT, MODE_SAMESITE, MODE_HUB, MODE_AUTOMATED |
b50bea4a SH |
76 | * @param int $userid The id of the user making the backup |
77 | */ | |
c6cc9726 EL |
78 | public function __construct($type, $id, $format, $interactive, $mode, $userid){ |
79 | $this->type = $type; | |
80 | $this->id = $id; | |
81 | $this->courseid = backup_controller_dbops::get_courseid_from_type_id($this->type, $this->id); | |
82 | $this->format = $format; | |
83 | $this->interactive = $interactive; | |
84 | $this->mode = $mode; | |
85 | $this->userid = $userid; | |
86 | ||
87 | // Apply some defaults | |
88 | $this->execution = backup::EXECUTION_INMEDIATE; | |
f60f4666 | 89 | $this->operation = backup::OPERATION_BACKUP; |
c6cc9726 EL |
90 | $this->executiontime = 0; |
91 | $this->checksum = ''; | |
92 | ||
93 | // Apply current backup version and release if necessary | |
94 | backup_controller_dbops::apply_version_and_release(); | |
95 | ||
96 | // Check format and type are correct | |
97 | backup_check::check_format_and_type($this->format, $this->type); | |
98 | ||
99 | // Check id is correct | |
100 | backup_check::check_id($this->type, $this->id); | |
101 | ||
102 | // Check user is correct | |
103 | backup_check::check_user($this->userid); | |
104 | ||
105 | // Calculate unique $backupid | |
106 | $this->calculate_backupid(); | |
107 | ||
108 | // Default logger chain (based on interactive/execution) | |
109 | $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid); | |
110 | ||
16cd7088 | 111 | // By default there is no progress reporter. Interfaces that wish to |
112 | // display progress must set it. | |
303936aa | 113 | $this->progress = new \core\progress\none(); |
16cd7088 | 114 | |
c6cc9726 EL |
115 | // Instantiate the output_controller singleton and active it if interactive and inmediate |
116 | $oc = output_controller::get_instance(); | |
117 | if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) { | |
118 | $oc->set_active(true); | |
119 | } | |
120 | ||
121 | $this->log('instantiating backup controller', backup::LOG_INFO, $this->backupid); | |
122 | ||
123 | // Default destination chain (based on type/mode/execution) | |
124 | $this->destination = backup_factory::get_destination_chain($this->type, $this->id, $this->mode, $this->execution); | |
125 | ||
126 | // Set initial status | |
127 | $this->set_status(backup::STATUS_CREATED); | |
128 | ||
129 | // Load plan (based on type/format) | |
130 | $this->load_plan(); | |
131 | ||
132 | // Apply all default settings (based on type/format/mode) | |
133 | $this->apply_defaults(); | |
134 | ||
135 | // Perform all initial security checks and apply (2nd param) them to settings automatically | |
136 | backup_check::check_security($this, true); | |
137 | ||
138 | // Set status based on interactivity | |
139 | if ($this->interactive == backup::INTERACTIVE_YES) { | |
140 | $this->set_status(backup::STATUS_SETTING_UI); | |
141 | } else { | |
142 | $this->set_status(backup::STATUS_AWAITING); | |
143 | } | |
144 | } | |
145 | ||
ab1c069e EL |
146 | /** |
147 | * Clean structures used by the backup_controller | |
148 | * | |
149 | * This method clean various structures used by the backup_controller, | |
150 | * destroying them in an ordered way, so their memory will be gc properly | |
151 | * by PHP (mainly circular references). | |
152 | * | |
153 | * Note that, while it's not mandatory to execute this method, it's highly | |
154 | * recommended to do so, specially in scripts performing multiple operations | |
155 | * (like the automated backups) or the system will run out of memory after | |
156 | * a few dozens of backups) | |
157 | */ | |
158 | public function destroy() { | |
159 | // Only need to destroy circulars under the plan. Delegate to it. | |
160 | $this->plan->destroy(); | |
53f95c99 EL |
161 | // Loggers may have also chained references, destroy them. Also closing resources when needed. |
162 | $this->logger->destroy(); | |
ab1c069e EL |
163 | } |
164 | ||
c6cc9726 EL |
165 | public function finish_ui() { |
166 | if ($this->status != backup::STATUS_SETTING_UI) { | |
167 | throw new backup_controller_exception('cannot_finish_ui_if_not_setting_ui'); | |
168 | } | |
169 | $this->set_status(backup::STATUS_AWAITING); | |
170 | } | |
171 | ||
172 | public function process_ui_event() { | |
173 | ||
174 | // Perform security checks throwing exceptions (2nd param) if something is wrong | |
175 | backup_check::check_security($this, false); | |
176 | } | |
177 | ||
178 | public function set_status($status) { | |
6e803b9f EL |
179 | // Note: never save_controller() with the object info after STATUS_EXECUTING or the whole controller, |
180 | // containing all the steps will be sent to DB. 100% (monster) useless. | |
c6cc9726 | 181 | $this->log('setting controller status to', backup::LOG_DEBUG, $status); |
6e803b9f | 182 | // TODO: Check it's a correct status. |
c6cc9726 | 183 | $this->status = $status; |
6e803b9f | 184 | // Ensure that, once set to backup::STATUS_AWAITING, controller is stored in DB. |
c6cc9726 EL |
185 | if ($status == backup::STATUS_AWAITING) { |
186 | $this->save_controller(); | |
ab1c069e EL |
187 | $tbc = self::load_controller($this->backupid); |
188 | $this->logger = $tbc->logger; // wakeup loggers | |
53f95c99 | 189 | $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive. |
6e803b9f EL |
190 | |
191 | } else if ($status == backup::STATUS_FINISHED_OK) { | |
192 | // If the operation has ended without error (backup::STATUS_FINISHED_OK) | |
193 | // proceed by cleaning the object from database. MDL-29262. | |
194 | $this->save_controller(false, true); | |
c6cc9726 EL |
195 | } |
196 | } | |
197 | ||
198 | public function set_execution($execution, $executiontime = 0) { | |
199 | $this->log('setting controller execution', backup::LOG_DEBUG); | |
200 | // TODO: Check valid execution mode | |
201 | // TODO: Check time in future | |
202 | // TODO: Check time = 0 if inmediate | |
203 | $this->execution = $execution; | |
204 | $this->executiontime = $executiontime; | |
205 | ||
206 | // Default destination chain (based on type/mode/execution) | |
207 | $this->destination = backup_factory::get_destination_chain($this->type, $this->id, $this->mode, $this->execution); | |
208 | ||
209 | // Default logger chain (based on interactive/execution) | |
210 | $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid); | |
211 | } | |
212 | ||
213 | // checksumable interface methods | |
214 | ||
215 | public function calculate_checksum() { | |
216 | // Reset current checksum to take it out from calculations! | |
217 | $this->checksum = ''; | |
218 | // Init checksum | |
219 | $tempchecksum = md5('backupid-' . $this->backupid . | |
220 | 'type-' . $this->type . | |
221 | 'id-' . $this->id . | |
222 | 'format-' . $this->format . | |
223 | 'interactive-'. $this->interactive . | |
224 | 'mode-' . $this->mode . | |
225 | 'userid-' . $this->userid . | |
f60f4666 | 226 | 'operation-' . $this->operation . |
c6cc9726 EL |
227 | 'status-' . $this->status . |
228 | 'execution-' . $this->execution . | |
229 | 'plan-' . backup_general_helper::array_checksum_recursive(array($this->plan)) . | |
230 | 'destination-'. backup_general_helper::array_checksum_recursive(array($this->destination)) . | |
231 | 'logger-' . backup_general_helper::array_checksum_recursive(array($this->logger))); | |
232 | $this->log('calculating controller checksum', backup::LOG_DEBUG, $tempchecksum); | |
233 | return $tempchecksum; | |
234 | } | |
235 | ||
236 | public function is_checksum_correct($checksum) { | |
237 | return $this->checksum === $checksum; | |
238 | } | |
239 | ||
240 | public function get_backupid() { | |
241 | return $this->backupid; | |
242 | } | |
243 | ||
244 | public function get_type() { | |
245 | return $this->type; | |
246 | } | |
247 | ||
b1850c12 AN |
248 | /** |
249 | * Returns the current value of the include_files setting. | |
250 | * This setting is intended to ensure that files are not included in | |
251 | * generated backups. | |
252 | * | |
253 | * @return int Indicates whether files should be included in backups. | |
254 | */ | |
255 | public function get_include_files() { | |
256 | return $this->includefiles; | |
257 | } | |
258 | ||
f60f4666 EL |
259 | public function get_operation() { |
260 | return $this->operation; | |
261 | } | |
262 | ||
c6cc9726 EL |
263 | public function get_id() { |
264 | return $this->id; | |
265 | } | |
266 | ||
267 | public function get_courseid() { | |
268 | return $this->courseid; | |
269 | } | |
270 | ||
271 | public function get_format() { | |
272 | return $this->format; | |
273 | } | |
274 | ||
275 | public function get_interactive() { | |
276 | return $this->interactive; | |
277 | } | |
278 | ||
279 | public function get_mode() { | |
280 | return $this->mode; | |
281 | } | |
282 | ||
283 | public function get_userid() { | |
284 | return $this->userid; | |
285 | } | |
286 | ||
287 | public function get_status() { | |
288 | return $this->status; | |
289 | } | |
290 | ||
291 | public function get_execution() { | |
292 | return $this->execution; | |
293 | } | |
294 | ||
295 | public function get_executiontime() { | |
296 | return $this->executiontime; | |
297 | } | |
298 | ||
61ebef8f SH |
299 | /** |
300 | * @return backup_plan | |
301 | */ | |
c6cc9726 EL |
302 | public function get_plan() { |
303 | return $this->plan; | |
304 | } | |
305 | ||
b50bea4a SH |
306 | /** |
307 | * Executes the backup | |
308 | * @return void Throws and exception of completes | |
309 | */ | |
c6cc9726 | 310 | public function execute_plan() { |
b7a45fa7 | 311 | // Basic/initial prevention against time/memory limits |
3ef7279f | 312 | core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted |
b7a45fa7 | 313 | raise_memory_limit(MEMORY_EXTRA); |
184acafa EL |
314 | // If this is not a course backup, inform the plan we are not |
315 | // including all the activities for sure. This will affect any | |
316 | // task/step executed conditionally to stop including information | |
317 | // for section and activity backup. MDL-28180. | |
318 | if ($this->get_type() !== backup::TYPE_1COURSE) { | |
319 | $this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG); | |
320 | $this->plan->set_excluding_activities(); | |
321 | } | |
c6cc9726 EL |
322 | return $this->plan->execute(); |
323 | } | |
324 | ||
ce937f99 EL |
325 | public function get_results() { |
326 | return $this->plan->get_results(); | |
327 | } | |
328 | ||
6e803b9f EL |
329 | /** |
330 | * Save controller information | |
331 | * | |
332 | * @param bool $includeobj to decide if the object itself must be updated (true) or no (false) | |
333 | * @param bool $cleanobj to decide if the object itself must be cleaned (true) or no (false) | |
334 | */ | |
335 | public function save_controller($includeobj = true, $cleanobj = false) { | |
c6cc9726 | 336 | // Going to save controller to persistent storage, calculate checksum for later checks and save it |
2de3539b | 337 | // TODO: flag the controller as NA. Any operation on it should be forbidden util loaded back |
c6cc9726 | 338 | $this->log('saving controller to db', backup::LOG_DEBUG); |
6e803b9f EL |
339 | if ($includeobj ) { // Only calculate checksum if we are going to include the object. |
340 | $this->checksum = $this->calculate_checksum(); | |
341 | } | |
342 | backup_controller_dbops::save_controller($this, $this->checksum, $includeobj, $cleanobj); | |
c6cc9726 EL |
343 | } |
344 | ||
345 | public static function load_controller($backupid) { | |
346 | // Load controller from persistent storage | |
2de3539b | 347 | // TODO: flag the controller as available. Operations on it can continue |
c6cc9726 EL |
348 | $controller = backup_controller_dbops::load_controller($backupid); |
349 | $controller->log('loading controller from db', backup::LOG_DEBUG); | |
350 | return $controller; | |
351 | } | |
352 | ||
f60f4666 EL |
353 | // Protected API starts here |
354 | ||
355 | protected function calculate_backupid() { | |
356 | // Current epoch time + type + id + format + interactive + mode + userid + operation | |
357 | // should be unique enough. Add one random part at the end | |
358 | $this->backupid = md5(time() . '-' . $this->type . '-' . $this->id . '-' . $this->format . '-' . | |
359 | $this->interactive . '-' . $this->mode . '-' . $this->userid . '-' . | |
360 | $this->operation . '-' . random_string(20)); | |
361 | } | |
362 | ||
c6cc9726 EL |
363 | protected function load_plan() { |
364 | $this->log('loading controller plan', backup::LOG_DEBUG); | |
365 | $this->plan = new backup_plan($this); | |
366 | $this->plan->build(); // Build plan for this controller | |
367 | $this->set_status(backup::STATUS_PLANNED); | |
368 | } | |
369 | ||
370 | protected function apply_defaults() { | |
371 | $this->log('applying plan defaults', backup::LOG_DEBUG); | |
59fc0cbd | 372 | backup_controller_dbops::apply_config_defaults($this); |
c6cc9726 | 373 | $this->set_status(backup::STATUS_CONFIGURED); |
b1850c12 AN |
374 | $this->set_include_files(); |
375 | } | |
376 | ||
377 | /** | |
378 | * Set the initial value for the include_files setting. | |
379 | * | |
380 | * @see backup_controller::get_include_files for further information on the purpose of this setting. | |
381 | * @return int Indicates whether files should be included in backups. | |
382 | */ | |
383 | protected function set_include_files() { | |
384 | // We normally include files. | |
385 | $includefiles = true; | |
386 | ||
387 | // In an import, we don't need to include files. | |
388 | if ($this->get_mode() === backup::MODE_IMPORT) { | |
389 | $includefiles = false; | |
390 | } | |
391 | ||
392 | // When a backup is intended for the same site, we don't need to include the files. | |
393 | // Note, this setting is only used for duplication of an entire course. | |
394 | if ($this->get_mode() === backup::MODE_SAMESITE) { | |
395 | $includefiles = false; | |
396 | } | |
397 | ||
398 | $this->includefiles = (int) $includefiles; | |
399 | $this->log("setting file inclusion to {$this->includefiles}", backup::LOG_DEBUG); | |
400 | return $this->includefiles; | |
c6cc9726 EL |
401 | } |
402 | } | |
403 | ||
404 | /* | |
405 | * Exception class used by all the @backup_controller stuff | |
406 | */ | |
407 | class backup_controller_exception extends backup_exception { | |
408 | ||
409 | public function __construct($errorcode, $a=NULL, $debuginfo=null) { | |
410 | parent::__construct($errorcode, $a, $debuginfo); | |
411 | } | |
412 | } |