2b0501fc5014acc0bf951e3c149aae9ade0e9afb
[moodle.git] / backup / util / ui / restore_ui_stage.class.php
1 <?php
4 // This file is part of Moodle - http://moodle.org/
5 //
6 // Moodle is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // Moodle is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
19 /**
20  * restore user interface stages
21  *
22  * This file contains the classes required to manage the stages that make up the
23  * restore user interface.
24  * These will be primarily operated a {@see restore_ui} instance.
25  *
26  * @package   moodlecore
27  * @copyright 2010 Sam Hemelryk
28  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29  */
31 /**
32  * Abstract stage class
33  *
34  * This class should be extended by all restore stages (a requirement of many restore ui functions).
35  * Each stage must then define two abstract methods
36  *  - process : To process the stage
37  *  - initialise_stage_form : To get a restore_moodleform instance for the stage
38  *
39  * @copyright 2010 Sam Hemelryk
40  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 abstract class restore_ui_stage extends base_ui_stage {
43     /**
44      * Constructor
45      * @param restore_ui $ui
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      * This is an independent stage
60      * @return int
61      */
62     final public function is_independent() {
63         return false;
64     }
66     /**
67      * No sub stages for this stage
68      * @return false
69      */
70     public function has_sub_stages() {
71         return false;
72     }
74     /**
75      * The name of this stage
76      * @return string
77      */
78     final public function get_name() {
79         return get_string('restorestage'.$this->stage,'backup');
80     }
81     /**
82      * Returns true if this is the settings stage
83      * @return bool
84      */
85     final public function is_first_stage() {
86         return $this->stage == restore_ui::STAGE_SETTINGS;
87     }
88 }
90 /**
91  * Abstract class used to represent a restore stage that is indenependent.
92  *
93  * An independent stage is a judged to be so because it doesn't require, and has
94  * no use for the restore controller.
95  */
96 abstract class restore_ui_independent_stage {
97     abstract public function __construct($contextid);
98     abstract public function process();
99     abstract public function display($renderer);
100     abstract public function get_stage();
101     /**
102      * Gets an array of progress bar items that can be displayed through the restore renderer.
103      * @return array Array of items for the progress bar
104      */
105     public function get_progress_bar() {
106         global $PAGE;
107         $stage = restore_ui::STAGE_COMPLETE;
108         $currentstage = $this->get_stage();
109         $items = array();
110         while ($stage > 0) {
111             $classes = array('backup_stage');
112             if (floor($stage/2) == $currentstage) {
113                 $classes[] = 'backup_stage_next';
114             } else if ($stage == $currentstage) {
115                 $classes[] = 'backup_stage_current';
116             } else if ($stage < $currentstage) {
117                 $classes[] = 'backup_stage_complete';
118             }
119             $item = array('text' => strlen(decbin($stage)).'. '.get_string('restorestage'.$stage, 'backup'),'class' => join(' ', $classes));
120             if ($stage < $currentstage && $currentstage < restore_ui::STAGE_COMPLETE) {
121                 //$item['link'] = new moodle_url($PAGE->url, array('restore'=>$this->get_restoreid(), 'stage'=>$stage));
122             }
123             array_unshift($items, $item);
124             $stage = floor($stage/2);
125         }
126         return $items;
127     }
128     abstract public function get_stage_name();
129     /**
130      * Obviously true
131      * @return true
132      */
133     final public function is_independent() {
134         return true;
135     }
138 /**
139  * The confirmation stage.
140  *
141  * This is the first stage, it is independent.
142  */
143 class restore_ui_stage_confirm extends restore_ui_independent_stage {
144     protected $contextid;
145     protected $filename = null;
146     protected $filepath = null;
147     protected $details;
148     public function __construct($contextid) {
149         $this->contextid = $contextid;
150         $this->filename = required_param('filename', PARAM_FILE);
151     }
152     public function process() {
153         global $CFG;
154         if (!file_exists("$CFG->dataroot/temp/backup/".$this->filename)) {
155             throw new restore_ui_exception('invalidrestorefile');
156         }
157         $outcome = $this->extract_file_to_dir();
158         if ($outcome) {
159             fulldelete($this->filename);
160         }
161         return $outcome;
162     }
163     protected function extract_file_to_dir() {
164         global $CFG, $USER;
166         $this->filepath = restore_controller::get_tempdir_name($this->contextid, $USER->id);
168         $fb = get_file_packer();
169         return ($fb->extract_to_pathname("$CFG->dataroot/temp/backup/".$this->filename, "$CFG->dataroot/temp/backup/$this->filepath/"));
170     }
171     public function display($renderer) {
173         // TODO: Remove this when backup formats are better supported
174         $format = backup_general_helper::detect_backup_format($this->filepath);
175         if ($format !== 'moodle2') {
176             return $renderer->invalid_format($format);
177         }
179         $this->details = backup_general_helper::get_backup_information($this->filepath);
180         return $renderer->backup_details($this->details, new moodle_url('/backup/restore.php', array('contextid'=>$this->contextid, 'filepath'=>$this->filepath, 'stage'=>restore_ui::STAGE_DESTINATION)));
181     }
182     public function get_stage_name() {
183         return get_string('restorestage'.restore_ui::STAGE_CONFIRM, 'backup');
184     }
185     public function get_stage() {
186         return restore_ui::STAGE_CONFIRM;
187     }
190 /**
191  * This is the destination stage.
192  *
193  * This stage is the second stage and is also independent
194  */
195 class restore_ui_stage_destination extends restore_ui_independent_stage {
196     protected $contextid;
197     protected $filepath = null;
198     protected $details;
199     protected $courseid = null;
200     protected $target = backup::TARGET_NEW_COURSE;
201     protected $coursesearch = null;
202     protected $categorysearch = null;
203     public function __construct($contextid) {
204         global $PAGE;
205         $this->contextid = $contextid;
206         $this->filepath = required_param('filepath', PARAM_ALPHANUM);
207         $url = new moodle_url($PAGE->url, array(
208             'filepath'=>$this->filepath,
209             'contextid'=>$this->contextid,
210             'stage'=>restore_ui::STAGE_DESTINATION));
211         $this->coursesearch = new restore_course_search(array('url'=>$url), get_context_instance_by_id($contextid)->instanceid);
212         $this->categorysearch = new restore_category_search(array('url'=>$url));
213     }
214     public function process() {
215         global $CFG, $DB;
216         if (!file_exists("$CFG->dataroot/temp/backup/".$this->filepath) || !is_dir("$CFG->dataroot/temp/backup/".$this->filepath)) {
217             throw new restore_ui_exception('invalidrestorepath');
218         }
219         if (optional_param('searchcourses', false, PARAM_BOOL)) {
220             return false;
221         }
222         $this->target = optional_param('target', backup::TARGET_NEW_COURSE, PARAM_INT);
223         $targetid = optional_param('targetid', null, PARAM_INT);
224         if (!is_null($this->target) && !is_null($targetid) && confirm_sesskey()) {
225             if ($this->target == backup::TARGET_NEW_COURSE) {
226                 list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'), get_string('restoringcourseshortname', 'backup'));
227                 $this->courseid = restore_dbops::create_new_course($fullname, $shortname, $targetid);
228             } else {
229                 $this->courseid = $targetid;
230             }
231             return ($DB->record_exists('course', array('id'=>$this->courseid)));
232         }
233         return false;
234     }
235     /**
236      *
237      * @global moodle_database $DB
238      * @param core_backup_renderer $renderer
239      * @return string
240      */
241     public function display($renderer) {
242         global $DB, $USER, $PAGE;
244         $format = backup_general_helper::detect_backup_format($this->filepath);
245         if ($format !== 'moodle2') {
246             return $renderer->invalid_format($format);
247         }
249         $this->details = backup_general_helper::get_backup_information($this->filepath);
250         $url = new moodle_url('/backup/restore.php', array('contextid'=>$this->contextid, 'filepath'=>$this->filepath, 'stage'=>restore_ui::STAGE_SETTINGS));
251         
252         $context = get_context_instance_by_id($this->contextid);
253         $currentcourse = ($context->contextlevel == CONTEXT_COURSE && has_capability('moodle/restore:restorecourse', $context))?$context->instanceid:false;
255         $html = $renderer->course_selector($url, $this->details, $this->categorysearch, $this->coursesearch, $currentcourse);
256         return $html;
257     }
258     public function get_stage_name() {
259         return get_string('restorestage'.restore_ui::STAGE_DESTINATION, 'backup');
260     }
261     public function get_filepath() {
262         return $this->filepath;
263     }
264     public function get_course_id() {
265         return $this->courseid;
266     }
267     public function get_stage() {
268         return restore_ui::STAGE_DESTINATION;
269     }
270     public function get_target() {
271         return $this->target;
272     }
274 /**
275  * This stage is the settings stage.
276  *
277  * This stage is the third stage, it is dependent on a restore controller and
278  * is the first stage as such.
279  */
280 class restore_ui_stage_settings extends restore_ui_stage {
281     /**
282      * Initial restore stage constructor
283      * @param restore_ui $ui
284      */
285     public function __construct(restore_ui $ui, array $params=null) {
286         $this->stage = restore_ui::STAGE_SETTINGS;
287         parent::__construct($ui, $params);
288     }
290     public function process(base_moodleform $form=null) {
291         $form = $this->initialise_stage_form();
293         if ($form->is_cancelled()) {
294             $this->ui->cancel_restore();
295         }
297         $data = $form->get_data();
298         if ($data && confirm_sesskey()) {
299             $tasks = $this->ui->get_tasks();
300             $changes = 0;
301             foreach ($tasks as &$task) {
302                 // We are only interesting in the backup root task for this stage
303                 if ($task instanceof restore_root_task || $task instanceof restore_course_task) {
304                     // Get all settings into a var so we can iterate by reference
305                     $settings = $task->get_settings();
306                     foreach ($settings as &$setting) {
307                         $name = $setting->get_ui_name();
308                         if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
309                             $setting->set_value($data->$name);
310                             $changes++;
311                         } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
312                             $setting->set_value(0);
313                             $changes++;
314                         }
315                     }
316                 }
317             }
318             // Return the number of changes the user made
319             return $changes;
320         } else {
321             return false;
322         }
323     }
325     protected function initialise_stage_form() {
326         global $PAGE;
327         if ($this->stageform === null) {
328             $form = new restore_settings_form($this, $PAGE->url);
329             // Store as a variable so we can iterate by reference
330             $tasks = $this->ui->get_tasks();
331             $headingprinted = false;
332             // Iterate all tasks by reference
333             foreach ($tasks as &$task) {
334                 // For the initial stage we are only interested in the root settings
335                 if ($task instanceof restore_root_task) {
336                     if (!$headingprinted) {
337                         $form->add_heading('rootsettings', get_string('restorerootsettings', 'backup'));
338                         $headingprinted = true;
339                     }
340                     $settings = $task->get_settings();
341                     // First add all settings except the filename setting
342                     foreach ($settings as &$setting) {
343                         if ($setting->get_name() == 'filename') {
344                             continue;
345                         }
346                         $form->add_setting($setting, $task);
347                     }
348                     // Then add all dependencies
349                     foreach ($settings as &$setting) {
350                         if ($setting->get_name() == 'filename') {
351                             continue;
352                         }
353                         $form->add_dependencies($setting);
354                     }
355                 }
356             }
357             $this->stageform = $form;
358         }
359         // Return the form
360         return $this->stageform;
361     }
364 /**
365  * Schema stage of backup process
366  *
367  * During the schema stage the user is required to set the settings that relate
368  * to the area that they are backing up as well as its children.
369  *
370  * @copyright 2010 Sam Hemelryk
371  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
372  */
373 class restore_ui_stage_schema extends restore_ui_stage {
374     /**
375      * Schema stage constructor
376      * @param backup_moodleform $ui
377      */
378     public function __construct(restore_ui $ui, array $params=null) {
379         $this->stage = restore_ui::STAGE_SCHEMA;
380         parent::__construct($ui, $params);
381     }
382     /**
383      * Processes the schema stage
384      *
385      * @param backup_moodleform|null $form
386      * @return int The number of changes the user made
387      */
388     public function process(base_moodleform $form = null) {
389         $form = $this->initialise_stage_form();
390         // Check it wasn't cancelled
391         if ($form->is_cancelled()) {
392             $this->ui->cancel_backup();
393         }
395         // Check it has been submit
396         $data = $form->get_data();
397         if ($data && confirm_sesskey()) {
398             // Get the tasks into a var so we can iterate by reference
399             $tasks = $this->ui->get_tasks();
400             $changes = 0;
401             // Iterate all tasks by reference
402             foreach ($tasks as &$task) {
403                 // We are only interested in schema settings
404                 if (!($task instanceof restore_root_task)) {
405                     // Store as a variable so we can iterate by reference
406                     $settings = $task->get_settings();
407                     // Iterate by reference
408                     foreach ($settings as &$setting) {
409                         $name = $setting->get_ui_name();
410                         if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
411                             $setting->set_value($data->$name);
412                             $changes++;
413                         } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
414                             $setting->set_value(0);
415                             $changes++;
416                         }
417                     }
418                 }
419             }
420             // Return the number of changes the user made
421             return $changes;
422         } else {
423             return false;
424         }
425     }
426     /**
427      * Creates the backup_schema_form instance for this stage
428      *
429      * @return backup_schema_form
430      */
431     protected function initialise_stage_form() {
432         global $PAGE;
433         if ($this->stageform === null) {
434             $form = new restore_schema_form($this, $PAGE->url);
435             $tasks = $this->ui->get_tasks();
436             $content = '';
437             $courseheading = false;
438             foreach ($tasks as $task) {
439                 if (!($task instanceof restore_root_task)) {
440                     if (!$courseheading) {
441                         // If we havn't already display a course heading to group nicely
442                         $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
443                         $courseheading = true;
444                     }
445                     // First add each setting
446                     foreach ($task->get_settings() as $setting) {
447                         $form->add_setting($setting, $task);
448                     }
449                     // The add all the dependencies
450                     foreach ($task->get_settings() as $setting) {
451                         $form->add_dependencies($setting);
452                     }
453                 } else if ($this->ui->enforce_changed_dependencies()) {
454                     // Only show these settings if dependencies changed them.
455                     // Add a root settings heading to group nicely
456                     $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
457                     // Iterate all settings and add them to the form as a fixed
458                     // setting. We only want schema settings to be editable
459                     foreach ($task->get_settings() as $setting) {
460                         if ($setting->get_name() != 'filename') {
461                             $form->add_fixed_setting($setting, $task);
462                         }
463                     }
464                 }
465             }
466             $this->stageform = $form;
467         }
468         return $this->stageform;
469     }
472 /**
473  * Confirmation stage
474  *
475  * On this stage the user reviews the setting for the backup and can change the filename
476  * of the file that will be generated.
477  *
478  * @copyright 2010 Sam Hemelryk
479  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
480  */
481 class restore_ui_stage_review extends restore_ui_stage {
482     /**
483      * Constructs the stage
484      * @param backup_ui $ui
485      */
486     public function __construct($ui, array $params=null) {
487         $this->stage = restore_ui::STAGE_REVIEW;
488         parent::__construct($ui, $params);
489     }
490     /**
491      * Processes the confirmation stage
492      *
493      * @param backup_moodleform $form
494      * @return int The number of changes the user made
495      */
496     public function process(base_moodleform $form = null) {
497         $form = $this->initialise_stage_form();
498         // Check it hasn't been cancelled
499         if ($form->is_cancelled()) {
500             $this->ui->cancel_backup();
501         }
503         $data = $form->get_data();
504         if ($data && confirm_sesskey()) {
505             return 0;
506         } else {
507             return false;
508         }
509     }
510     /**
511      * Creates the backup_confirmation_form instance this stage requires
512      *
513      * @return backup_confirmation_form
514      */
515     protected function initialise_stage_form() {
516         global $PAGE;
517         if ($this->stageform === null) {
518             // Get the form
519             $form = new restore_review_form($this, $PAGE->url);
520             $content = '';
521             $courseheading = false;
523             foreach ($this->ui->get_tasks() as $task) {
524                 if ($task instanceof restore_root_task) {
525                     // If its a backup root add a root settings heading to group nicely
526                     $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
527                 } else if (!$courseheading) {
528                     // we havn't already add a course heading
529                     $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
530                     $courseheading = true;
531                 }
532                 // Iterate all settings, doesnt need to happen by reference
533                 foreach ($task->get_settings() as $setting) {
534                     $form->add_fixed_setting($setting, $task);
535                 }
536             }
537             $this->stageform = $form;
538         }
539         return $this->stageform;
540     }
543 /**
544  * Final stage of backup
545  *
546  * This stage is special in that it is does not make use of a form. The reason for
547  * this is the order of procession of backup at this stage.
548  * The processesion is:
549  * 1. The final stage will be intialise.
550  * 2. The confirmation stage will be processed.
551  * 3. The backup will be executed
552  * 4. The complete stage will be loaded by execution
553  * 5. The complete stage will be displayed
554  *
555  * This highlights that we neither need a form nor a display method for this stage
556  * we simply need to process.
557  *
558  * @copyright 2010 Sam Hemelryk
559  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
560  */
561 class restore_ui_stage_process extends restore_ui_stage {
563     const SUBSTAGE_NONE = 0;
564     const SUBSTAGE_CONVERT = 1;
565     const SUBSTAGE_PRECHECKS = 2;
567     protected $substage = 0;
569     /**
570      * Constructs the final stage
571      * @param backup_ui $ui
572      */
573     public function __construct(base_ui $ui, array $params=null) {
574         $this->stage = restore_ui::STAGE_PROCESS;
575         parent::__construct($ui, $params);
576     }
577     /**
578      * Processes the final stage.
579      *
580      * In this case it checks to see if there is a sub stage that we need to display
581      * before execution, if there is we gear up to display the subpage, otherwise
582      * we return true which will lead to execution of the restore and the loading
583      * of the completed stage.
584      */
585     public function process(base_moodleform $form=null) {
586         if (optional_param('cancel', false, PARAM_BOOL)) {
587             redirect(new moodle_url('/course/view.php', array('id'=>$this->get_ui()->get_controller()->get_courseid())));
588         }
590         // First decide whether a substage is needed
591         $rc = $this->ui->get_controller();
592         if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
593             $this->substage = self::SUBSTAGE_CONVERT;
594         } else {
595             if ($rc->get_status() == backup::STATUS_SETTING_UI) {
596                 $rc->finish_ui();
597             }
598             if ($rc->get_status() == backup::STATUS_NEED_PRECHECK) {
599                 if (!$rc->precheck_executed()) {
600                     $rc->execute_precheck(true);
601                 }
602                 $results = $rc->get_precheck_results();
603                 if (!empty($results)) {
604                     $this->substage = self::SUBSTAGE_PRECHECKS;
605                 }
606             }
607         }
609         $substage = optional_param('substage', null, PARAM_INT);
610         if (empty($this->substage) && !empty($substage)) {
611             $this->substage = $substage;
612             // Now check whether that substage has already been submit
613             if ($this->substage == self::SUBSTAGE_PRECHECKS && optional_param('sesskey', null, PARAM_RAW) == sesskey()) {
614                 $info = $rc->get_info();
615                 if (!empty($info->role_mappings->mappings)) {
616                     foreach ($info->role_mappings->mappings as $key=>&$mapping) {
617                         $mapping->targetroleid = optional_param('mapping'.$key, $mapping->targetroleid, PARAM_INT);
618                     }
619                     $info->role_mappings->modified = true;
620                 }
621                 // We've processed the substage now setting it back to none so we
622                 // can move to the next stage.
623                 $this->substage = self::SUBSTAGE_NONE;
624             }
625         }
627         return empty($this->substage);
628     }
629     /**
630      * should NEVER be called... throws an exception
631      */
632     protected function initialise_stage_form() {
633         throw new backup_ui_exception('backup_ui_must_execute_first');
634     }
635     /**
636      * should NEVER be called... throws an exception
637      */
638     public function display($renderer) {
639         global $PAGE;
640         $haserrors = false;
641         $url = new moodle_url($PAGE->url, array('restore'=>$this->get_uniqueid(), 'stage'=>restore_ui::STAGE_PROCESS, 'substage'=>$this->substage, 'sesskey'=>sesskey()));
642         echo html_writer::start_tag('form', array('action'=>$url->out_omit_querystring(), 'class'=>'backup-restore', 'method'=>'post'));
643         foreach ($url->params() as $name=>$value) {
644             echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$value));
645         }
646         switch ($this->substage) {
647             case self::SUBSTAGE_CONVERT :
648                 echo '<h2>Need to show the conversion screens here</h2>';
649                 break;
650             case self::SUBSTAGE_PRECHECKS :
651                 $results = $this->ui->get_controller()->get_precheck_results();
652                 $info = $this->ui->get_controller()->get_info();
653                 $haserrors = (!empty($results['errors']));
654                 echo $renderer->precheck_notices($results);
655                 if (!empty($info->role_mappings->mappings)) {
656                     $context = get_context_instance(CONTEXT_COURSE, $this->ui->get_controller()->get_courseid());
657                     $assignableroles = get_assignable_roles($context, ROLENAME_ALIAS, false);
658                     echo $renderer->role_mappings($info->role_mappings->mappings, $assignableroles);
659                 }
660                 break;
661             default:
662                 throw new restore_ui_exception('backup_ui_must_execute_first');
663         }
664         echo $renderer->substage_buttons($haserrors);
665         echo html_writer::end_tag('form');
666     }
668     public function has_sub_stages() {
669         return true;
670     }
673 /**
674  * This is the completed stage.
675  *
676  * Once this is displayed there is nothing more to do.
677  */
678 class restore_ui_stage_complete extends restore_ui_stage_process {
679     /**
680      * The results of the backup execution
681      * @var array
682      */
683     protected $results;
684     /**
685      * Constructs the complete backup stage
686      * @param backup_ui $ui
687      * @param array|null $params
688      * @param array $results
689      */
690     public function __construct(restore_ui $ui, array $params=null, array $results=null) {
691         $this->results = $results;
692         parent::__construct($ui, $params);
693         $this->stage = restore_ui::STAGE_COMPLETE;
694     }
695     /**
696      * Displays the completed backup stage.
697      *
698      * Currently this just envolves redirecting to the file browser with an
699      * appropriate message.
700      *
701      * @global core_renderer $OUTPUT
702      * @param core_backup_renderer $renderer
703      */
704     public function display(core_backup_renderer $renderer) {
705         global $OUTPUT;
706         echo $OUTPUT->box_start();
707         echo $OUTPUT->notification(get_string('restoreexecutionsuccess', 'backup'), 'notifysuccess');
708         echo $renderer->continue_button(new moodle_url('/course/view.php', array('id'=>$this->get_ui()->get_controller()->get_courseid())), 'get');
709         echo $OUTPUT->box_end();
710     }