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 * Backup user interface stages
19 *
20 * This file contains the classes required to manage the stages that make up the
21 * backup user interface.
22 * These will be primarily operated a {@link backup_ui} instance.
23 *
24 * @package   core_backup
25 * @copyright 2010 Sam Hemelryk
26 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 */
28
29/**
30 * Abstract stage class
31 *
32 * This class should be extended by all backup stages (a requirement of many backup ui functions).
33 * Each stage must then define two abstract methods
34 *  - process : To process the stage
35 *  - initialise_stage_form : To get a backup_moodleform instance for the stage
36 *
37 * @package   core_backup
38 * @copyright 2010 Sam Hemelryk
39 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 */
41abstract class backup_ui_stage extends base_ui_stage {
42
43    /**
44     * Constructor.
45     *
46     * @param backup_ui $ui
47     * @param array $params
48     */
49    public function __construct(backup_ui $ui, array $params = null) {
50        parent::__construct($ui, $params);
51    }
52
53    /**
54     * The backup id from the backup controller
55     * @return string
56     */
57    final public function get_backupid() {
58        return $this->get_uniqueid();
59    }
60}
61
62/**
63 * Class representing the initial stage of a backup.
64 *
65 * In this stage the user is required to set the root level settings.
66 *
67 * @package   core_backup
68 * @copyright 2010 Sam Hemelryk
69 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
70 */
71class backup_ui_stage_initial extends backup_ui_stage {
72
73    /**
74     * When set to true we skip all stages and jump to immediately processing the backup.
75     * @var bool
76     */
77    protected $oneclickbackup = false;
78
79    /**
80     * Initial backup stage constructor
81     * @param backup_ui $ui
82     * @param array $params
83     */
84    public function __construct(backup_ui $ui, array $params = null) {
85        $this->stage = backup_ui::STAGE_INITIAL;
86        parent::__construct($ui, $params);
87    }
88
89    /**
90     * Processes the initial backup stage
91     * @param base_moodleform $m
92     * @return int The number of changes
93     */
94    public function process(base_moodleform $m = null) {
95
96        $form = $this->initialise_stage_form();
97
98        if ($form->is_cancelled()) {
99            $this->ui->cancel_process();
100        }
101
102        $data = $form->get_data();
103        if ($data && confirm_sesskey()) {
104            if (isset($data->oneclickbackup)) {
105                $this->oneclickbackup = true;
106            }
107            $tasks = $this->ui->get_tasks();
108            $changes = 0;
109            foreach ($tasks as &$task) {
110                // We are only interesting in the backup root task for this stage.
111                if ($task instanceof backup_root_task) {
112                    // Get all settings into a var so we can iterate by reference.
113                    $settings = $task->get_settings();
114                    foreach ($settings as &$setting) {
115                        $name = $setting->get_ui_name();
116                        if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
117                            $setting->set_value($data->$name);
118                            $changes++;
119                        } else if (!isset($data->$name) && $setting->get_value() &&
120                                $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX &&
121                                $setting->get_status() !== backup_setting::LOCKED_BY_HIERARCHY) {
122                            $setting->set_value(0);
123                            $changes++;
124                        }
125                    }
126                }
127            }
128            // Return the number of changes the user made.
129            return $changes;
130        } else {
131            return false;
132        }
133    }
134
135    /**
136     * Gets the next stage for the backup.
137     *
138     * We override this function to implement the one click backup.
139     * When the user performs a one click backup we jump straight to the final stage.
140     *
141     * @return int
142     */
143    public function get_next_stage() {
144        if ($this->oneclickbackup) {
145            // Its a one click backup.
146            // The default filename is backup.mbz, this normally gets set to something useful in the confirmation stage.
147            // because we skipped that stage we must manually set this to a useful value.
148            $tasks = $this->ui->get_tasks();
149            foreach ($tasks as $task) {
150                if ($task instanceof backup_root_task) {
151                    // Find the filename setting.
152                    $setting = $task->get_setting('filename');
153                    if ($setting) {
154                        // Use the helper objects to get a useful name.
155                        $filename = backup_plan_dbops::get_default_backup_filename(
156                            $this->ui->get_format(),
157                            $this->ui->get_type(),
158                            $this->ui->get_controller_id(),
159                            $this->ui->get_setting_value('users'),
160                            $this->ui->get_setting_value('anonymize'),
161                            false,
162                            (bool)$this->ui->get_setting_value('files')
163                        );
164                        $setting->set_value($filename);
165                    }
166                }
167            }
168            return backup_ui::STAGE_FINAL;
169        }
170        return parent::get_next_stage();
171    }
172
173    /**
174     * Initialises the backup_moodleform instance for this stage
175     *
176     * @return backup_initial_form
177     */
178    protected function initialise_stage_form() {
179        global $PAGE;
180        if ($this->stageform === null) {
181            $form = new backup_initial_form($this, $PAGE->url);
182            // Store as a variable so we can iterate by reference.
183            $tasks = $this->ui->get_tasks();
184            // Iterate all tasks by reference.
185            $add_settings = array();
186            $dependencies = array();
187            foreach ($tasks as &$task) {
188                // For the initial stage we are only interested in the root settings.
189                if ($task instanceof backup_root_task) {
190                    if ($this->ui instanceof import_ui) {
191                        $form->add_heading('rootsettings', get_string('importrootsettings', 'backup'));
192                    } else {
193                        $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
194                    }
195                    $settings = $task->get_settings();
196                    // First add all settings except the filename setting.
197                    foreach ($settings as &$setting) {
198                        if ($setting->get_name() == 'filename') {
199                            continue;
200                        }
201                        $add_settings[] = array($setting, $task);
202                    }
203                    // Then add all dependencies.
204                    foreach ($settings as &$setting) {
205                        if ($setting->get_name() == 'filename') {
206                            continue;
207                        }
208                        $dependencies[] = $setting;
209                    }
210                }
211            }
212            // Add all settings at once.
213            $form->add_settings($add_settings);
214            // Add dependencies.
215            foreach ($dependencies as $depsetting) {
216                $form->add_dependencies($depsetting);
217            }
218            $this->stageform = $form;
219        }
220        // Return the form.
221        return $this->stageform;
222    }
223}
224
225/**
226 * Schema stage of backup process
227 *
228 * During the schema stage the user is required to set the settings that relate
229 * to the area that they are backing up as well as its children.
230 *
231 * @package   core_backup
232 * @copyright 2010 Sam Hemelryk
233 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
234 */
235class backup_ui_stage_schema extends backup_ui_stage {
236
237    /**
238     * @var int Maximum number of settings to add to form at once
239     */
240    const MAX_SETTINGS_BATCH = 1000;
241
242    /**
243     * Schema stage constructor
244     * @param backup_ui $ui
245     * @param array $params
246     */
247    public function __construct(backup_ui $ui, array $params = null) {
248        $this->stage = backup_ui::STAGE_SCHEMA;
249        parent::__construct($ui, $params);
250    }
251
252    /**
253     * Processes the schema stage
254     *
255     * @param base_moodleform $form
256     * @return int The number of changes the user made
257     */
258    public function process(base_moodleform $form = null) {
259        $form = $this->initialise_stage_form();
260        // Check it wasn't cancelled.
261        if ($form->is_cancelled()) {
262            $this->ui->cancel_process();
263        }
264
265        // Check it has been submit.
266        $data = $form->get_data();
267        if ($data && confirm_sesskey()) {
268            // Get the tasks into a var so we can iterate by reference.
269            $tasks = $this->ui->get_tasks();
270            $changes = 0;
271            // Iterate all tasks by reference.
272            foreach ($tasks as &$task) {
273                // We are only interested in schema settings.
274                if (!($task instanceof backup_root_task)) {
275                    // Store as a variable so we can iterate by reference.
276                    $settings = $task->get_settings();
277                    // Iterate by reference.
278                    foreach ($settings as &$setting) {
279                        $name = $setting->get_ui_name();
280                        if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
281                            $setting->set_value($data->$name);
282                            $changes++;
283                        } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
284                            $setting->set_value(0);
285                            $changes++;
286                        }
287                    }
288                }
289            }
290            // Return the number of changes the user made.
291            return $changes;
292        } else {
293            return false;
294        }
295    }
296
297    /**
298     * Creates the backup_schema_form instance for this stage
299     *
300     * @return backup_schema_form
301     */
302    protected function initialise_stage_form() {
303        global $PAGE;
304        if ($this->stageform === null) {
305            $form = new backup_schema_form($this, $PAGE->url);
306            $tasks = $this->ui->get_tasks();
307            $content = '';
308            $courseheading = false;
309            $add_settings = array();
310            $dependencies = array();
311
312            // Track progress through each stage.
313            $progress = $this->ui->get_controller()->get_progress();
314            $progress->start_progress('Initialise stage form', 3);
315
316            // Get settings for all tasks.
317            $progress->start_progress('', count($tasks));
318            $done = 1;
319            foreach ($tasks as $task) {
320                if (!($task instanceof backup_root_task)) {
321                    if (!$courseheading) {
322                        // If we haven't already display a course heading to group nicely.
323                        $form->add_heading('coursesettings', get_string('includeactivities', 'backup'));
324                        $courseheading = true;
325                    }
326                    // First add each setting.
327                    foreach ($task->get_settings() as $setting) {
328                        $add_settings[] = array($setting, $task);
329                    }
330                    // The add all the dependencies.
331                    foreach ($task->get_settings() as $setting) {
332                        $dependencies[] = $setting;
333                    }
334                } else if ($this->ui->enforce_changed_dependencies()) {
335                    // Only show these settings if dependencies changed them.
336                    // Add a root settings heading to group nicely.
337                    $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
338                    // Iterate all settings and add them to the form as a fixed
339                    // setting. We only want schema settings to be editable.
340                    foreach ($task->get_settings() as $setting) {
341                        if ($setting->get_name() != 'filename') {
342                            $form->add_fixed_setting($setting, $task);
343                        }
344                    }
345                }
346                // Update progress.
347                $progress->progress($done++);
348            }
349            $progress->end_progress();
350
351            // Add settings for tasks in batches of up to 1000. Adding settings
352            // in larger batches improves performance, but if it takes too long,
353            // we won't be able to update the progress bar so the backup might.
354            // time out. 1000 is chosen to balance this.
355            $numsettings = count($add_settings);
356            $progress->start_progress('', ceil($numsettings / self::MAX_SETTINGS_BATCH));
357            $start = 0;
358            $done = 1;
359            while ($start < $numsettings) {
360                $length = min(self::MAX_SETTINGS_BATCH, $numsettings - $start);
361                $form->add_settings(array_slice($add_settings, $start, $length));
362                $start += $length;
363                $progress->progress($done++);
364            }
365            $progress->end_progress();
366
367            $progress->start_progress('', count($dependencies));
368            $done = 1;
369            foreach ($dependencies as $depsetting) {
370                $form->add_dependencies($depsetting);
371                $progress->progress($done++);
372            }
373            $progress->end_progress();
374
375            // End overall progress through creating form.
376            $progress->end_progress();
377            $this->stageform = $form;
378        }
379        return $this->stageform;
380    }
381}
382
383/**
384 * Confirmation stage
385 *
386 * On this stage the user reviews the setting for the backup and can change the filename
387 * of the file that will be generated.
388 *
389 * @package   core_backup
390 * @copyright 2010 Sam Hemelryk
391 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
392 */
393class backup_ui_stage_confirmation extends backup_ui_stage {
394
395    /**
396     * Constructs the stage
397     * @param backup_ui $ui
398     * @param array $params
399     */
400    public function __construct($ui, array $params = null) {
401        $this->stage = backup_ui::STAGE_CONFIRMATION;
402        parent::__construct($ui, $params);
403    }
404
405    /**
406     * Processes the confirmation stage
407     *
408     * @param base_moodleform $form
409     * @return int The number of changes the user made
410     */
411    public function process(base_moodleform $form = null) {
412        $form = $this->initialise_stage_form();
413        // Check it hasn't been cancelled.
414        if ($form->is_cancelled()) {
415            $this->ui->cancel_process();
416        }
417
418        $data = $form->get_data();
419        if ($data && confirm_sesskey()) {
420            // Collect into a variable so we can iterate by reference.
421            $tasks = $this->ui->get_tasks();
422            $changes = 0;
423            // Iterate each task by reference.
424            foreach ($tasks as &$task) {
425                if ($task instanceof backup_root_task) {
426                    // At this stage all we are interested in is the filename setting.
427                    $setting = $task->get_setting('filename');
428                    $name = $setting->get_ui_name();
429                    if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
430                        $setting->set_value($data->$name);
431                        $changes++;
432                    }
433                }
434            }
435            // Return the number of changes the user made.
436            return $changes;
437        } else {
438            return false;
439        }
440    }
441
442    /**
443     * Creates the backup_confirmation_form instance this stage requires
444     *
445     * @return backup_confirmation_form
446     */
447    protected function initialise_stage_form() {
448        global $PAGE;
449        if ($this->stageform === null) {
450            // Get the form.
451            $form = new backup_confirmation_form($this, $PAGE->url);
452            $content = '';
453            $courseheading = false;
454
455            foreach ($this->ui->get_tasks() as $task) {
456                if ($setting = $task->get_setting('filename')) {
457                    $form->add_heading('filenamesetting', get_string('filename', 'backup'));
458                    if ($setting->get_value() == 'backup.mbz') {
459                        $format = $this->ui->get_format();
460                        $type = $this->ui->get_type();
461                        $id = $this->ui->get_controller_id();
462                        $users = $this->ui->get_setting_value('users');
463                        $anonymised = $this->ui->get_setting_value('anonymize');
464                        $files = (bool)$this->ui->get_setting_value('files');
465                        $filename = backup_plan_dbops::get_default_backup_filename(
466                                $format,
467                                $type,
468                                $id,
469                                $users,
470                                $anonymised,
471                                false,
472                                $files);
473                        $setting->set_value($filename);
474                    }
475                    $form->add_setting($setting, $task);
476                    break;
477                }
478            }
479
480            // Track progress through tasks.
481            $progress = $this->ui->get_controller()->get_progress();
482            $tasks = $this->ui->get_tasks();
483            $progress->start_progress('initialise_stage_form', count($tasks));
484            $done = 1;
485
486            foreach ($tasks as $task) {
487                if ($task instanceof backup_root_task) {
488                    // If its a backup root add a root settings heading to group nicely.
489                    if ($this->ui instanceof import_ui) {
490                        $form->add_heading('rootsettings', get_string('importrootsettings', 'backup'));
491                    } else {
492                        $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
493                    }
494                } else if (!$courseheading) {
495                    // We haven't already add a course heading.
496                    $form->add_heading('coursesettings', get_string('includeditems', 'backup'));
497                    $courseheading = true;
498                }
499                // Iterate all settings, doesnt need to happen by reference.
500                foreach ($task->get_settings() as $setting) {
501                    // For this stage only the filename setting should be editable.
502                    if ($setting->get_name() != 'filename') {
503                        $form->add_fixed_setting($setting, $task);
504                    }
505                }
506                // Update progress.
507                $progress->progress($done++);
508            }
509            $progress->end_progress();
510            $this->stageform = $form;
511        }
512        return $this->stageform;
513    }
514}
515
516/**
517 * Final stage of backup
518 *
519 * This stage is special in that it is does not make use of a form. The reason for
520 * this is the order of procession of backup at this stage.
521 * The processesion is:
522 * 1. The final stage will be intialise.
523 * 2. The confirmation stage will be processed.
524 * 3. The backup will be executed
525 * 4. The complete stage will be loaded by execution
526 * 5. The complete stage will be displayed
527 *
528 * This highlights that we neither need a form nor a display method for this stage
529 * we simply need to process.
530 *
531 * @package   core_backup
532 * @copyright 2010 Sam Hemelryk
533 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
534 */
535class backup_ui_stage_final extends backup_ui_stage {
536
537    /**
538     * Constructs the final stage
539     * @param backup_ui $ui
540     * @param array $params
541     */
542    public function __construct(backup_ui $ui, array $params = null) {
543        $this->stage = backup_ui::STAGE_FINAL;
544        parent::__construct($ui, $params);
545    }
546
547    /**
548     * Processes the final stage.
549     *
550     * In this case it ALWAYS passes processing to the previous stage (confirmation)
551     *
552     * @param base_moodleform $form
553     * @return bool
554     */
555    public function process(base_moodleform $form = null) {
556        return true;
557    }
558
559    /**
560     * should NEVER be called... throws an exception
561     */
562    protected function initialise_stage_form() {
563        throw new backup_ui_exception('backup_ui_must_execute_first');
564    }
565
566    /**
567     * should NEVER be called... throws an exception
568     *
569     * @throws backup_ui_exception always
570     * @param core_backup_renderer $renderer
571     * @return void
572     */
573    public function display(core_backup_renderer $renderer) {
574        throw new backup_ui_exception('backup_ui_must_execute_first');
575    }
576}
577
578/**
579 * The completed backup stage
580 *
581 * At this stage everything is done and the user will be redirected to view the
582 * backup file in the file browser.
583 *
584 * @package   core_backup
585 * @copyright 2010 Sam Hemelryk
586 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
587 */
588class backup_ui_stage_complete extends backup_ui_stage_final {
589
590    /**
591     * The results of the backup execution
592     * @var array
593     */
594    protected $results;
595
596    /**
597     * Constructs the complete backup stage
598     *
599     * @param backup_ui $ui
600     * @param array $params
601     * @param array $results
602     */
603    public function __construct(backup_ui $ui, array $params = null, array $results = null) {
604        $this->results = $results;
605        parent::__construct($ui, $params);
606        $this->stage = backup_ui::STAGE_COMPLETE;
607    }
608
609    /**
610     * Displays the completed backup stage.
611     *
612     * Currently this just involves redirecting to the file browser with an
613     * appropriate message.
614     *
615     * @param core_backup_renderer $renderer
616     * @return string HTML code to echo
617     */
618    public function display(core_backup_renderer $renderer) {
619
620        // Get the resulting stored_file record.
621        $type = $this->get_ui()->get_controller()->get_type();
622        $courseid = $this->get_ui()->get_controller()->get_courseid();
623        switch ($type) {
624            case 'activity':
625                $cmid = $this->get_ui()->get_controller()->get_id();
626                $cm = get_coursemodule_from_id(null, $cmid, $courseid);
627                $modcontext = context_module::instance($cm->id);
628                $restorerul = new moodle_url('/backup/restorefile.php', array('contextid' => $modcontext->id));
629                break;
630            case 'course':
631            default:
632                $coursecontext = context_course::instance($courseid);
633                $restorerul = new moodle_url('/backup/restorefile.php', array('contextid' => $coursecontext->id));
634        }
635
636        $output = '';
637        $output .= $renderer->box_start();
638        if (!empty($this->results['include_file_references_to_external_content'])) {
639            $output .= $renderer->notification(get_string('filereferencesincluded', 'backup'), 'notifyproblem');
640        }
641        if (!empty($this->results['missing_files_in_pool'])) {
642            $output .= $renderer->notification(get_string('missingfilesinpool', 'backup'), 'notifyproblem');
643        }
644        $output .= $renderer->get_samesite_notification();
645        $output .= $renderer->notification(get_string('executionsuccess', 'backup'), 'notifysuccess');
646        $output .= $renderer->continue_button($restorerul);
647        $output .= $renderer->box_end();
648
649        return $output;
650    }
651}
652