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 * restore user interface stages
19 *
20 * This file contains the classes required to manage the stages that make up the
21 * restore user interface.
22 * These will be primarily operated a {@link restore_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 restore stages (a requirement of many restore ui functions).
33 * Each stage must then define two abstract methods
34 *  - process : To process the stage
35 *  - initialise_stage_form : To get a restore_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 restore_ui_stage extends base_ui_stage {
42    /**
43     * Constructor
44     * @param restore_ui $ui
45     * @param array $params
46     */
47    public function __construct(restore_ui $ui, array $params = null) {
48        $this->ui = $ui;
49        $this->params = $params;
50    }
51    /**
52     * The restore id from the restore controller
53     * @return string
54     */
55    final public function get_restoreid() {
56        return $this->get_uniqueid();
57    }
58
59    /**
60     * This is an independent stage
61     * @return int
62     */
63    final public function is_independent() {
64        return false;
65    }
66
67    /**
68     * No sub stages for this stage
69     * @return false
70     */
71    public function has_sub_stages() {
72        return false;
73    }
74
75    /**
76     * The name of this stage
77     * @return string
78     */
79    final public function get_name() {
80        return get_string('restorestage'.$this->stage, 'backup');
81    }
82
83    /**
84     * Returns true if this is the settings stage
85     * @return bool
86     */
87    final public function is_first_stage() {
88        return $this->stage == restore_ui::STAGE_SETTINGS;
89    }
90}
91
92/**
93 * Abstract class used to represent a restore stage that is indenependent.
94 *
95 * An independent stage is a judged to be so because it doesn't require, and has
96 * no use for the restore controller.
97 *
98 * @package   core_backup
99 * @copyright 2010 Sam Hemelryk
100 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
101 */
102abstract class restore_ui_independent_stage {
103    /**
104     * @var \core\progress\base Optional progress reporter
105     */
106    private $progressreporter;
107
108    /**
109     * Constructs the restore stage.
110     * @param int $contextid
111     */
112    abstract public function __construct($contextid);
113
114    /**
115     * Processes the current restore stage.
116     * @return mixed
117     */
118    abstract public function process();
119
120    /**
121     * Displays this restore stage.
122     * @param core_backup_renderer $renderer
123     * @return mixed
124     */
125    abstract public function display(core_backup_renderer $renderer);
126
127    /**
128     * Returns the current restore stage.
129     * @return int
130     */
131    abstract public function get_stage();
132
133    /**
134     * Gets the progress reporter object in use for this restore UI stage.
135     *
136     * IMPORTANT: This progress reporter is used only for UI progress that is
137     * outside the restore controller. The restore controller has its own
138     * progress reporter which is used for progress during the main restore.
139     * Use the restore controller's progress reporter to report progress during
140     * a restore operation, not this one.
141     *
142     * This extra reporter is necessary because on some restore UI screens,
143     * there are long-running tasks even though there is no restore controller
144     * in use. There is a similar function in restore_ui. but that class is not
145     * used on some stages.
146     *
147     * @return \core\progress\none
148     */
149    public function get_progress_reporter() {
150        if (!$this->progressreporter) {
151            $this->progressreporter = new \core\progress\none();
152        }
153        return $this->progressreporter;
154    }
155
156    /**
157     * Sets the progress reporter that will be returned by get_progress_reporter.
158     *
159     * @param \core\progress\base $progressreporter Progress reporter
160     */
161    public function set_progress_reporter(\core\progress\base $progressreporter) {
162        $this->progressreporter = $progressreporter;
163    }
164
165    /**
166     * Gets an array of progress bar items that can be displayed through the restore renderer.
167     * @return array Array of items for the progress bar
168     */
169    public function get_progress_bar() {
170        global $PAGE;
171        $stage = restore_ui::STAGE_COMPLETE;
172        $currentstage = $this->get_stage();
173        $items = array();
174        while ($stage > 0) {
175            $classes = array('backup_stage');
176            if (floor($stage / 2) == $currentstage) {
177                $classes[] = 'backup_stage_next';
178            } else if ($stage == $currentstage) {
179                $classes[] = 'backup_stage_current';
180            } else if ($stage < $currentstage) {
181                $classes[] = 'backup_stage_complete';
182            }
183            $item = array('text' => strlen(decbin($stage)).'. '.get_string('restorestage'.$stage, 'backup'), 'class' => join(' ', $classes));
184            if ($stage < $currentstage && $currentstage < restore_ui::STAGE_COMPLETE) {
185                // By default you can't go back to independent stages, if that changes in the future uncomment the next line.
186                // $item['link'] = new moodle_url($PAGE->url, array('restore' => $this->get_restoreid(), 'stage' => $stage));
187            }
188            array_unshift($items, $item);
189            $stage = floor($stage / 2);
190        }
191        return $items;
192    }
193
194    /**
195     * Returns the restore stage name.
196     * @return string
197     */
198    abstract public function get_stage_name();
199
200    /**
201     * Obviously true
202     * @return true
203     */
204    final public function is_independent() {
205        return true;
206    }
207
208    /**
209     * Handles the destruction of this object.
210     */
211    public function destroy() {
212        // Nothing to destroy here!.
213    }
214}
215
216/**
217 * The confirmation stage.
218 *
219 * This is the first stage, it is independent.
220 *
221 * @package   core_backup
222 * @copyright 2010 Sam Hemelryk
223 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
224 */
225class restore_ui_stage_confirm extends restore_ui_independent_stage implements file_progress {
226
227    /**
228     * The context ID.
229     * @var int
230     */
231    protected $contextid;
232
233    /**
234     * The file name.
235     * @var string
236     */
237    protected $filename = null;
238
239    /**
240     * The file path.
241     * @var string
242     */
243    protected $filepath = null;
244
245    /**
246     * @var string Content hash of archive file to restore (if specified by hash)
247     */
248    protected $contenthash = null;
249
250    /**
251     * @var string Pathname hash of stored_file object to restore
252     */
253    protected $pathnamehash = null;
254
255    /**
256     * @var array
257     */
258    protected $details;
259
260    /**
261     * @var bool True if we have started reporting progress
262     */
263    protected $startedprogress = false;
264
265    /**
266     * Constructor
267     * @param int $contextid
268     * @throws coding_exception
269     */
270    public function __construct($contextid) {
271        $this->contextid = $contextid;
272        $this->filename = optional_param('filename', null, PARAM_FILE);
273        if ($this->filename === null) {
274            // Identify file object by its pathname hash.
275            $this->pathnamehash = required_param('pathnamehash', PARAM_ALPHANUM);
276
277            // The file content hash is also passed for security; users
278            // cannot guess the content hash (unless they know the file contents),
279            // so this guarantees that either the system generated this link or
280            // else the user has access to the restore archive anyhow.
281            $this->contenthash = required_param('contenthash', PARAM_ALPHANUM);
282        }
283    }
284
285    /**
286     * Processes this restore stage
287     * @return bool
288     * @throws restore_ui_exception
289     */
290    public function process() {
291        $backuptempdir = make_backup_temp_directory('');
292        if ($this->filename) {
293            $archivepath = $backuptempdir . '/' . $this->filename;
294            if (!file_exists($archivepath)) {
295                throw new restore_ui_exception('invalidrestorefile');
296            }
297            $outcome = $this->extract_file_to_dir($archivepath);
298            if ($outcome) {
299                fulldelete($archivepath);
300            }
301        } else {
302            $fs = get_file_storage();
303            $storedfile = $fs->get_file_by_hash($this->pathnamehash);
304            if (!$storedfile || $storedfile->get_contenthash() !== $this->contenthash) {
305                throw new restore_ui_exception('invalidrestorefile');
306            }
307            $outcome = $this->extract_file_to_dir($storedfile);
308        }
309        return $outcome;
310    }
311
312    /**
313     * Extracts the file.
314     *
315     * @param string|stored_file $source Archive file to extract
316     * @return bool
317     */
318    protected function extract_file_to_dir($source) {
319        global $USER;
320
321        $this->filepath = restore_controller::get_tempdir_name($this->contextid, $USER->id);
322        $backuptempdir = make_backup_temp_directory('', false);
323
324        $fb = get_file_packer('application/vnd.moodle.backup');
325        $result = $fb->extract_to_pathname($source,
326                $backuptempdir . '/' . $this->filepath . '/', null, $this);
327
328        // If any progress happened, end it.
329        if ($this->startedprogress) {
330            $this->get_progress_reporter()->end_progress();
331        }
332        return $result;
333    }
334
335    /**
336     * Implementation for file_progress interface to display unzip progress.
337     *
338     * @param int $progress Current progress
339     * @param int $max Max value
340     */
341    public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
342        $reporter = $this->get_progress_reporter();
343
344        // Start tracking progress if necessary.
345        if (!$this->startedprogress) {
346            $reporter->start_progress('extract_file_to_dir',
347                    ($max == file_progress::INDETERMINATE) ? \core\progress\base::INDETERMINATE : $max);
348            $this->startedprogress = true;
349        }
350
351        // Pass progress through to whatever handles it.
352        $reporter->progress(
353                ($progress == file_progress::INDETERMINATE) ? \core\progress\base::INDETERMINATE : $progress);
354    }
355
356    /**
357     * Renders the confirmation stage screen
358     *
359     * @param core_backup_renderer $renderer renderer instance to use
360     * @return string HTML code
361     */
362    public function display(core_backup_renderer $renderer) {
363
364        $prevstageurl = new moodle_url('/backup/restorefile.php', array('contextid' => $this->contextid));
365        $nextstageurl = new moodle_url('/backup/restore.php', array(
366            'contextid' => $this->contextid,
367            'filepath'  => $this->filepath,
368            'stage'     => restore_ui::STAGE_DESTINATION));
369
370        $format = backup_general_helper::detect_backup_format($this->filepath);
371
372        if ($format === backup::FORMAT_UNKNOWN) {
373            // Unknown format - we can't do anything here.
374            return $renderer->backup_details_unknown($prevstageurl);
375
376        } else if ($format !== backup::FORMAT_MOODLE) {
377            // Non-standard format to be converted.
378            $details = array('format' => $format, 'type' => backup::TYPE_1COURSE); // todo type to be returned by a converter
379            return $renderer->backup_details_nonstandard($nextstageurl, $details);
380
381        } else {
382            // Standard MBZ backup, let us get information from it and display.
383            $this->details = backup_general_helper::get_backup_information($this->filepath);
384            return $renderer->backup_details($this->details, $nextstageurl);
385        }
386    }
387
388    /**
389     * The restore stage name.
390     * @return string
391     * @throws coding_exception
392     */
393    public function get_stage_name() {
394        return get_string('restorestage'.restore_ui::STAGE_CONFIRM, 'backup');
395    }
396
397    /**
398     * The restore stage this class is for.
399     * @return int
400     */
401    public function get_stage() {
402        return restore_ui::STAGE_CONFIRM;
403    }
404}
405
406/**
407 * This is the destination stage.
408 *
409 * This stage is the second stage and is also independent
410 *
411 * @package   core_backup
412 * @copyright 2010 Sam Hemelryk
413 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
414 */
415class restore_ui_stage_destination extends restore_ui_independent_stage {
416
417    /**
418     * The context ID.
419     * @var int
420     */
421    protected $contextid;
422
423    /**
424     * The backup file path.
425     * @var mixed|null
426     */
427    protected $filepath = null;
428
429    /**
430     * The course ID.
431     * @var null
432     */
433    protected $courseid = null;
434
435    /**
436     * The restore target. One of backup::TARGET_NEW
437     * @var int
438     */
439    protected $target = backup::TARGET_NEW_COURSE;
440
441    /**
442     * The course search component.
443     * @var null|restore_course_search
444     */
445    protected $coursesearch = null;
446
447    /**
448     * The category search component.
449     * @var null|restore_category_search
450     */
451    protected $categorysearch = null;
452
453    /**
454     * Constructs the destination stage.
455     * @param int $contextid
456     * @throws coding_exception
457     */
458    public function __construct($contextid) {
459        global $PAGE;
460        $this->contextid = $contextid;
461        $this->filepath = required_param('filepath', PARAM_ALPHANUM);
462        $url = new moodle_url($PAGE->url, array(
463            'filepath' => $this->filepath,
464            'contextid' => $this->contextid,
465            'stage' => restore_ui::STAGE_DESTINATION));
466        $this->coursesearch = new restore_course_search(array('url' => $url), context::instance_by_id($contextid)->instanceid);
467        $this->categorysearch = new restore_category_search(array('url' => $url));
468    }
469
470    /**
471     * Processes the destination stage.
472     * @return bool
473     * @throws coding_exception
474     * @throws restore_ui_exception
475     */
476    public function process() {
477        global $DB;
478        $filepathdir = make_backup_temp_directory($this->filepath, false);
479        if (!file_exists($filepathdir) || !is_dir($filepathdir)) {
480            throw new restore_ui_exception('invalidrestorepath');
481        }
482        if (optional_param('searchcourses', false, PARAM_BOOL)) {
483            return false;
484        }
485        $this->target = optional_param('target', backup::TARGET_NEW_COURSE, PARAM_INT);
486        $targetid = optional_param('targetid', null, PARAM_INT);
487        if (!is_null($this->target) && !is_null($targetid) && confirm_sesskey()) {
488            if ($this->target == backup::TARGET_NEW_COURSE) {
489                list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'), get_string('restoringcourseshortname', 'backup'));
490                $this->courseid = restore_dbops::create_new_course($fullname, $shortname, $targetid);
491            } else {
492                $this->courseid = $targetid;
493            }
494            return ($DB->record_exists('course', array('id' => $this->courseid)));
495        }
496        return false;
497    }
498
499    /**
500     * Renders the destination stage screen
501     *
502     * @param core_backup_renderer $renderer renderer instance to use
503     * @return string HTML code
504     */
505    public function display(core_backup_renderer $renderer) {
506
507        $format = backup_general_helper::detect_backup_format($this->filepath);
508
509        if ($format === backup::FORMAT_MOODLE) {
510            // Standard Moodle 2 format, let use get the type of the backup.
511            $details = backup_general_helper::get_backup_information($this->filepath);
512            if ($details->type === backup::TYPE_1COURSE) {
513                $wholecourse = true;
514            } else {
515                $wholecourse = false;
516            }
517
518        } else {
519            // Non-standard format to be converted. We assume it contains the
520            // whole course for now. However, in the future there might be a callback
521            // to the installed converters.
522            $wholecourse = true;
523        }
524
525        $nextstageurl = new moodle_url('/backup/restore.php', array(
526            'contextid' => $this->contextid,
527            'filepath'  => $this->filepath,
528            'stage'     => restore_ui::STAGE_SETTINGS));
529        $context = context::instance_by_id($this->contextid);
530
531        if ($context->contextlevel == CONTEXT_COURSE and has_capability('moodle/restore:restorecourse', $context)) {
532            $currentcourse = $context->instanceid;
533        } else {
534            $currentcourse = false;
535        }
536
537        return $renderer->course_selector($nextstageurl, $wholecourse, $this->categorysearch, $this->coursesearch, $currentcourse);
538    }
539
540    /**
541     * Returns the stage name.
542     * @return string
543     * @throws coding_exception
544     */
545    public function get_stage_name() {
546        return get_string('restorestage'.restore_ui::STAGE_DESTINATION, 'backup');
547    }
548
549    /**
550     * Returns the backup file path
551     * @return mixed|null
552     */
553    public function get_filepath() {
554        return $this->filepath;
555    }
556
557    /**
558     * Returns the course id.
559     * @return null
560     */
561    public function get_course_id() {
562        return $this->courseid;
563    }
564
565    /**
566     * Returns the current restore stage
567     * @return int
568     */
569    public function get_stage() {
570        return restore_ui::STAGE_DESTINATION;
571    }
572
573    /**
574     * Returns the target for this restore.
575     * One of backup::TARGET_*
576     * @return int
577     */
578    public function get_target() {
579        return $this->target;
580    }
581}
582
583/**
584 * This stage is the settings stage.
585 *
586 * This stage is the third stage, it is dependent on a restore controller and
587 * is the first stage as such.
588 *
589 * @package   core_backup
590 * @copyright 2010 Sam Hemelryk
591 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
592 */
593class restore_ui_stage_settings extends restore_ui_stage {
594    /**
595     * Initial restore stage constructor
596     * @param restore_ui $ui
597     * @param array $params
598     */
599    public function __construct(restore_ui $ui, array $params = null) {
600        $this->stage = restore_ui::STAGE_SETTINGS;
601        parent::__construct($ui, $params);
602    }
603
604    /**
605     * Process the settings stage.
606     *
607     * @param base_moodleform $form
608     * @return bool|int
609     */
610    public function process(base_moodleform $form = null) {
611        $form = $this->initialise_stage_form();
612
613        if ($form->is_cancelled()) {
614            $this->ui->cancel_process();
615        }
616
617        $data = $form->get_data();
618        if ($data && confirm_sesskey()) {
619            $tasks = $this->ui->get_tasks();
620            $changes = 0;
621            foreach ($tasks as &$task) {
622                // We are only interesting in the backup root task for this stage.
623                if ($task instanceof restore_root_task || $task instanceof restore_course_task) {
624                    // Get all settings into a var so we can iterate by reference.
625                    $settings = $task->get_settings();
626                    foreach ($settings as &$setting) {
627                        $name = $setting->get_ui_name();
628                        if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
629                            $setting->set_value($data->$name);
630                            $changes++;
631                        } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
632                            $setting->set_value(0);
633                            $changes++;
634                        }
635                    }
636                }
637            }
638            // Return the number of changes the user made.
639            return $changes;
640        } else {
641            return false;
642        }
643    }
644
645    /**
646     * Initialise the stage form.
647     *
648     * @return backup_moodleform|base_moodleform|restore_settings_form
649     * @throws coding_exception
650     */
651    protected function initialise_stage_form() {
652        global $PAGE;
653        if ($this->stageform === null) {
654            $form = new restore_settings_form($this, $PAGE->url);
655            // Store as a variable so we can iterate by reference.
656            $tasks = $this->ui->get_tasks();
657            $headingprinted = false;
658            // Iterate all tasks by reference.
659            foreach ($tasks as &$task) {
660                // For the initial stage we are only interested in the root settings.
661                if ($task instanceof restore_root_task) {
662                    if (!$headingprinted) {
663                        $form->add_heading('rootsettings', get_string('restorerootsettings', 'backup'));
664                        $headingprinted = true;
665                    }
666                    $settings = $task->get_settings();
667                    // First add all settings except the filename setting.
668                    foreach ($settings as &$setting) {
669                        if ($setting->get_name() == 'filename') {
670                            continue;
671                        }
672                        $form->add_setting($setting, $task);
673                    }
674                    // Then add all dependencies.
675                    foreach ($settings as &$setting) {
676                        if ($setting->get_name() == 'filename') {
677                            continue;
678                        }
679                        $form->add_dependencies($setting);
680                    }
681                }
682            }
683            $this->stageform = $form;
684        }
685        // Return the form.
686        return $this->stageform;
687    }
688}
689
690/**
691 * Schema stage of backup process
692 *
693 * During the schema stage the user is required to set the settings that relate
694 * to the area that they are backing up as well as its children.
695 *
696 * @package   core_backup
697 * @copyright 2010 Sam Hemelryk
698 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
699 */
700class restore_ui_stage_schema extends restore_ui_stage {
701    /**
702     * @var int Maximum number of settings to add to form at once
703     */
704    const MAX_SETTINGS_BATCH = 1000;
705
706    /**
707     * Schema stage constructor
708     * @param restore_ui $ui
709     * @param array $params
710     */
711    public function __construct(restore_ui $ui, array $params = null) {
712        $this->stage = restore_ui::STAGE_SCHEMA;
713        parent::__construct($ui, $params);
714    }
715
716    /**
717     * Processes the schema stage
718     *
719     * @param base_moodleform $form
720     * @return int The number of changes the user made
721     */
722    public function process(base_moodleform $form = null) {
723        $form = $this->initialise_stage_form();
724        // Check it wasn't cancelled.
725        if ($form->is_cancelled()) {
726            $this->ui->cancel_process();
727        }
728
729        // Check it has been submit.
730        $data = $form->get_data();
731        if ($data && confirm_sesskey()) {
732            // Get the tasks into a var so we can iterate by reference.
733            $tasks = $this->ui->get_tasks();
734            $changes = 0;
735            // Iterate all tasks by reference.
736            foreach ($tasks as &$task) {
737                // We are only interested in schema settings.
738                if (!($task instanceof restore_root_task)) {
739                    // Store as a variable so we can iterate by reference.
740                    $settings = $task->get_settings();
741                    // Iterate by reference.
742                    foreach ($settings as &$setting) {
743                        $name = $setting->get_ui_name();
744                        if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
745                            $setting->set_value($data->$name);
746                            $changes++;
747                        } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
748                            $setting->set_value(0);
749                            $changes++;
750                        }
751                    }
752                }
753            }
754            // Return the number of changes the user made.
755            return $changes;
756        } else {
757            return false;
758        }
759    }
760
761    /**
762     * Creates the backup_schema_form instance for this stage
763     *
764     * @return backup_schema_form
765     */
766    protected function initialise_stage_form() {
767        global $PAGE;
768        if ($this->stageform === null) {
769            $form = new restore_schema_form($this, $PAGE->url);
770            $tasks = $this->ui->get_tasks();
771            $courseheading = false;
772
773            // Track progress through each stage.
774            $progress = $this->ui->get_progress_reporter();
775            $progress->start_progress('Initialise schema stage form', 3);
776
777            $progress->start_progress('', count($tasks));
778            $done = 1;
779            $allsettings = array();
780            foreach ($tasks as $task) {
781                if (!($task instanceof restore_root_task)) {
782                    if (!$courseheading) {
783                        // If we haven't already display a course heading to group nicely.
784                        $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
785                        $courseheading = true;
786                    }
787                    // Put each setting into an array of settings to add. Adding
788                    // a setting individually is a very slow operation, so we add.
789                    // them all in a batch later on.
790                    foreach ($task->get_settings() as $setting) {
791                        $allsettings[] = array($setting, $task);
792                    }
793                } else if ($this->ui->enforce_changed_dependencies()) {
794                    // Only show these settings if dependencies changed them.
795                    // Add a root settings heading to group nicely.
796                    $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
797                    // Iterate all settings and add them to the form as a fixed
798                    // setting. We only want schema settings to be editable.
799                    foreach ($task->get_settings() as $setting) {
800                        if ($setting->get_name() != 'filename') {
801                            $form->add_fixed_setting($setting, $task);
802                        }
803                    }
804                }
805                // Update progress.
806                $progress->progress($done++);
807            }
808            $progress->end_progress();
809
810            // Add settings for tasks in batches of up to 1000. Adding settings
811            // in larger batches improves performance, but if it takes too long,
812            // we won't be able to update the progress bar so the backup might.
813            // time out. 1000 is chosen to balance this.
814            $numsettings = count($allsettings);
815            $progress->start_progress('', ceil($numsettings / self::MAX_SETTINGS_BATCH));
816            $start = 0;
817            $done = 1;
818            while ($start < $numsettings) {
819                $length = min(self::MAX_SETTINGS_BATCH, $numsettings - $start);
820                $form->add_settings(array_slice($allsettings, $start, $length));
821                $start += $length;
822                $progress->progress($done++);
823            }
824            $progress->end_progress();
825
826            // Add the dependencies for all the settings.
827            $progress->start_progress('', count($allsettings));
828            $done = 1;
829            foreach ($allsettings as $settingtask) {
830                $form->add_dependencies($settingtask[0]);
831                $progress->progress($done++);
832            }
833            $progress->end_progress();
834
835            $progress->end_progress();
836            $this->stageform = $form;
837        }
838        return $this->stageform;
839    }
840}
841
842/**
843 * Confirmation stage
844 *
845 * On this stage the user reviews the setting for the backup and can change the filename
846 * of the file that will be generated.
847 *
848 * @package   core_backup
849 * @copyright 2010 Sam Hemelryk
850 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
851 */
852class restore_ui_stage_review extends restore_ui_stage {
853
854    /**
855     * Constructs the stage
856     * @param restore_ui $ui
857     * @param array $params
858     */
859    public function __construct($ui, array $params = null) {
860        $this->stage = restore_ui::STAGE_REVIEW;
861        parent::__construct($ui, $params);
862    }
863
864    /**
865     * Processes the confirmation stage
866     *
867     * @param base_moodleform $form
868     * @return int The number of changes the user made
869     */
870    public function process(base_moodleform $form = null) {
871        $form = $this->initialise_stage_form();
872        // Check it hasn't been cancelled.
873        if ($form->is_cancelled()) {
874            $this->ui->cancel_process();
875        }
876
877        $data = $form->get_data();
878        if ($data && confirm_sesskey()) {
879            return 0;
880        } else {
881            return false;
882        }
883    }
884    /**
885     * Creates the backup_confirmation_form instance this stage requires
886     *
887     * @return backup_confirmation_form
888     */
889    protected function initialise_stage_form() {
890        global $PAGE;
891        if ($this->stageform === null) {
892            // Get the form.
893            $form = new restore_review_form($this, $PAGE->url);
894            $content = '';
895            $courseheading = false;
896
897            $progress = $this->ui->get_progress_reporter();
898            $tasks = $this->ui->get_tasks();
899            $progress->start_progress('initialise_stage_form', count($tasks));
900            $done = 1;
901            foreach ($tasks as $task) {
902                if ($task instanceof restore_root_task) {
903                    // If its a backup root add a root settings heading to group nicely.
904                    $form->add_heading('rootsettings', get_string('restorerootsettings', 'backup'));
905                } else if (!$courseheading) {
906                    // We haven't already add a course heading.
907                    $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
908                    $courseheading = true;
909                }
910                // Iterate all settings, doesnt need to happen by reference.
911                foreach ($task->get_settings() as $setting) {
912                    $form->add_fixed_setting($setting, $task);
913                }
914                // Update progress.
915                $progress->progress($done++);
916            }
917            $progress->end_progress();
918            $this->stageform = $form;
919        }
920        return $this->stageform;
921    }
922}
923
924/**
925 * Final stage of backup
926 *
927 * This stage is special in that it is does not make use of a form. The reason for
928 * this is the order of procession of backup at this stage.
929 * The processesion is:
930 * 1. The final stage will be intialise.
931 * 2. The confirmation stage will be processed.
932 * 3. The backup will be executed
933 * 4. The complete stage will be loaded by execution
934 * 5. The complete stage will be displayed
935 *
936 * This highlights that we neither need a form nor a display method for this stage
937 * we simply need to process.
938 *
939 * @package   core_backup
940 * @copyright 2010 Sam Hemelryk
941 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
942 */
943class restore_ui_stage_process extends restore_ui_stage {
944
945    /**
946     * There is no substage required.
947     */
948    const SUBSTAGE_NONE = 0;
949
950    /**
951     * The prechecks substage is required/the current substage.
952     */
953    const SUBSTAGE_PRECHECKS = 2;
954
955    /**
956     * The current substage.
957     * @var int
958     */
959    protected $substage = 0;
960
961    /**
962     * Constructs the final stage
963     * @param base_ui $ui
964     * @param array $params
965     */
966    public function __construct(base_ui $ui, array $params = null) {
967        $this->stage = restore_ui::STAGE_PROCESS;
968        parent::__construct($ui, $params);
969    }
970    /**
971     * Processes the final stage.
972     *
973     * In this case it checks to see if there is a sub stage that we need to display
974     * before execution, if there is we gear up to display the subpage, otherwise
975     * we return true which will lead to execution of the restore and the loading
976     * of the completed stage.
977     *
978     * @param base_moodleform $form
979     */
980    public function process(base_moodleform $form = null) {
981        if (optional_param('cancel', false, PARAM_BOOL)) {
982            redirect(new moodle_url('/course/view.php', array('id' => $this->get_ui()->get_controller()->get_courseid())));
983        }
984
985        // First decide whether a substage is needed.
986        $rc = $this->ui->get_controller();
987        if ($rc->get_status() == backup::STATUS_SETTING_UI) {
988            $rc->finish_ui();
989        }
990        if ($rc->get_status() == backup::STATUS_NEED_PRECHECK) {
991            if (!$rc->precheck_executed()) {
992                $rc->execute_precheck(true);
993            }
994            $results = $rc->get_precheck_results();
995            if (!empty($results)) {
996                $this->substage = self::SUBSTAGE_PRECHECKS;
997            }
998        }
999
1000        $substage = optional_param('substage', null, PARAM_INT);
1001        if (empty($this->substage) && !empty($substage)) {
1002            $this->substage = $substage;
1003            // Now check whether that substage has already been submit.
1004            if ($this->substage == self::SUBSTAGE_PRECHECKS && optional_param('sesskey', null, PARAM_RAW) == sesskey()) {
1005                $info = $rc->get_info();
1006                if (!empty($info->role_mappings->mappings)) {
1007                    foreach ($info->role_mappings->mappings as $key => &$mapping) {
1008                        $mapping->targetroleid = optional_param('mapping'.$key, $mapping->targetroleid, PARAM_INT);
1009                    }
1010                    $info->role_mappings->modified = true;
1011                }
1012                // We've processed the substage now setting it back to none so we
1013                // can move to the next stage.
1014                $this->substage = self::SUBSTAGE_NONE;
1015            }
1016        }
1017
1018        return empty($this->substage);
1019    }
1020    /**
1021     * should NEVER be called... throws an exception
1022     */
1023    protected function initialise_stage_form() {
1024        throw new backup_ui_exception('backup_ui_must_execute_first');
1025    }
1026
1027    /**
1028     * Renders the process stage screen
1029     *
1030     * @throws restore_ui_exception
1031     * @param core_backup_renderer $renderer renderer instance to use
1032     * @return string HTML code
1033     */
1034    public function display(core_backup_renderer $renderer) {
1035        global $PAGE;
1036
1037        $html = '';
1038        $haserrors = false;
1039        $url = new moodle_url($PAGE->url, array(
1040            'restore'   => $this->get_uniqueid(),
1041            'stage'     => restore_ui::STAGE_PROCESS,
1042            'substage'  => $this->substage,
1043            'sesskey'   => sesskey()));
1044        $html .= html_writer::start_tag('form', array(
1045            'action'    => $url->out_omit_querystring(),
1046            'class'     => 'backup-restore',
1047            'enctype'   => 'application/x-www-form-urlencoded', // Enforce compatibility with our max_input_vars hack.
1048            'method'    => 'post'));
1049        foreach ($url->params() as $name => $value) {
1050            $html .= html_writer::empty_tag('input', array(
1051                'type'  => 'hidden',
1052                'name'  => $name,
1053                'value' => $value));
1054        }
1055        switch ($this->substage) {
1056            case self::SUBSTAGE_PRECHECKS :
1057                $results = $this->ui->get_controller()->get_precheck_results();
1058                $info = $this->ui->get_controller()->get_info();
1059                $haserrors = (!empty($results['errors']));
1060                $html .= $renderer->precheck_notices($results);
1061                if (!empty($info->role_mappings->mappings)) {
1062                    $context = context_course::instance($this->ui->get_controller()->get_courseid());
1063                    $assignableroles = get_assignable_roles($context, ROLENAME_ALIAS, false);
1064
1065                    // Get current role mappings.
1066                    $currentroles = role_fix_names(get_all_roles(), $context);
1067                    // Get backup role mappings.
1068                    $rolemappings = $info->role_mappings->mappings;
1069
1070                    array_map(function($rolemapping) use ($currentroles) {
1071                        foreach ($currentroles as $role) {
1072                            // Find matching archetype to determine the backup's shortname for label display.
1073                            if ($rolemapping->archetype == $role->archetype) {
1074                                $rolemapping->name = $rolemapping->shortname;
1075                                break;
1076                            }
1077                        }
1078                        if ($rolemapping->name == null) {
1079                            $rolemapping->name = get_string('undefinedrolemapping', 'backup', $rolemapping->archetype);
1080                        }
1081                    }, $rolemappings);
1082
1083                    $html .= $renderer->role_mappings($rolemappings, $assignableroles);
1084                }
1085                break;
1086            default:
1087                throw new restore_ui_exception('backup_ui_must_execute_first');
1088        }
1089        $html .= $renderer->substage_buttons($haserrors);
1090        $html .= html_writer::end_tag('form');
1091
1092        return $html;
1093    }
1094
1095    /**
1096     * Returns true if this stage can have sub-stages.
1097     * @return bool|false
1098     */
1099    public function has_sub_stages() {
1100        return true;
1101    }
1102}
1103
1104/**
1105 * This is the completed stage.
1106 *
1107 * Once this is displayed there is nothing more to do.
1108 *
1109 * @package   core_backup
1110 * @copyright 2010 Sam Hemelryk
1111 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1112 */
1113class restore_ui_stage_complete extends restore_ui_stage_process {
1114
1115    /**
1116     * The results of the backup execution
1117     * @var array
1118     */
1119    protected $results;
1120
1121    /**
1122     * Constructs the complete backup stage
1123     * @param restore_ui $ui
1124     * @param array $params
1125     * @param array $results
1126     */
1127    public function __construct(restore_ui $ui, array $params = null, array $results = null) {
1128        $this->results = $results;
1129        parent::__construct($ui, $params);
1130        $this->stage = restore_ui::STAGE_COMPLETE;
1131    }
1132
1133    /**
1134     * Displays the completed backup stage.
1135     *
1136     * Currently this just envolves redirecting to the file browser with an
1137     * appropriate message.
1138     *
1139     * @param core_backup_renderer $renderer
1140     * @return string HTML code to echo
1141     */
1142    public function display(core_backup_renderer $renderer) {
1143
1144        $html  = '';
1145        if (!empty($this->results['file_aliases_restore_failures'])) {
1146            $html .= $renderer->box_start('generalbox filealiasesfailures');
1147            $html .= $renderer->heading_with_help(get_string('filealiasesrestorefailures', 'core_backup'),
1148                'filealiasesrestorefailures', 'core_backup');
1149            $html .= $renderer->container(get_string('filealiasesrestorefailuresinfo', 'core_backup'));
1150            $html .= $renderer->container_start('aliaseslist');
1151            $html .= html_writer::start_tag('ul');
1152            foreach ($this->results['file_aliases_restore_failures'] as $alias) {
1153                $html .= html_writer::tag('li', s($alias));
1154            }
1155            $html .= html_writer::end_tag('ul');
1156            $html .= $renderer->container_end();
1157            $html .= $renderer->box_end();
1158        }
1159        $html .= $renderer->box_start();
1160        if (array_key_exists('file_missing_in_backup', $this->results)) {
1161            $html .= $renderer->notification(get_string('restorefileweremissing', 'backup'), 'notifyproblem');
1162        }
1163        $html .= $renderer->notification(get_string('restoreexecutionsuccess', 'backup'), 'notifysuccess');
1164        $html .= $renderer->continue_button(new moodle_url('/course/view.php', array(
1165            'id' => $this->get_ui()->get_controller()->get_courseid())), 'get');
1166        $html .= $renderer->box_end();
1167
1168        return $html;
1169    }
1170}
1171