MDL-26319 moved cancel_backup() from backup_ui to cancel_process() in base_ui and...
[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     }
136     public function destroy() {
137         // Nothing to destroy here!
138     }
141 /**
142  * The confirmation stage.
143  *
144  * This is the first stage, it is independent.
145  */
146 class restore_ui_stage_confirm extends restore_ui_independent_stage {
147     protected $contextid;
148     protected $filename = null;
149     protected $filepath = null;
150     protected $details;
151     public function __construct($contextid) {
152         $this->contextid = $contextid;
153         $this->filename = required_param('filename', PARAM_FILE);
154     }
155     public function process() {
156         global $CFG;
157         if (!file_exists("$CFG->dataroot/temp/backup/".$this->filename)) {
158             throw new restore_ui_exception('invalidrestorefile');
159         }
160         $outcome = $this->extract_file_to_dir();
161         if ($outcome) {
162             fulldelete($this->filename);
163         }
164         return $outcome;
165     }
166     protected function extract_file_to_dir() {
167         global $CFG, $USER;
169         $this->filepath = restore_controller::get_tempdir_name($this->contextid, $USER->id);
171         $fb = get_file_packer();
172         return ($fb->extract_to_pathname("$CFG->dataroot/temp/backup/".$this->filename, "$CFG->dataroot/temp/backup/$this->filepath/"));
173     }
174     public function display($renderer) {
176         // TODO: Remove this when backup formats are better supported
177         $format = backup_general_helper::detect_backup_format($this->filepath);
178         if ($format !== 'moodle2') {
179             return $renderer->invalid_format($format);
180         }
182         $this->details = backup_general_helper::get_backup_information($this->filepath);
183         return $renderer->backup_details($this->details, new moodle_url('/backup/restore.php', array('contextid'=>$this->contextid, 'filepath'=>$this->filepath, 'stage'=>restore_ui::STAGE_DESTINATION)));
184     }
185     public function get_stage_name() {
186         return get_string('restorestage'.restore_ui::STAGE_CONFIRM, 'backup');
187     }
188     public function get_stage() {
189         return restore_ui::STAGE_CONFIRM;
190     }
193 /**
194  * This is the destination stage.
195  *
196  * This stage is the second stage and is also independent
197  */
198 class restore_ui_stage_destination extends restore_ui_independent_stage {
199     protected $contextid;
200     protected $filepath = null;
201     protected $details;
202     protected $courseid = null;
203     protected $target = backup::TARGET_NEW_COURSE;
204     protected $coursesearch = null;
205     protected $categorysearch = null;
206     public function __construct($contextid) {
207         global $PAGE;
208         $this->contextid = $contextid;
209         $this->filepath = required_param('filepath', PARAM_ALPHANUM);
210         $url = new moodle_url($PAGE->url, array(
211             'filepath'=>$this->filepath,
212             'contextid'=>$this->contextid,
213             'stage'=>restore_ui::STAGE_DESTINATION));
214         $this->coursesearch = new restore_course_search(array('url'=>$url), get_context_instance_by_id($contextid)->instanceid);
215         $this->categorysearch = new restore_category_search(array('url'=>$url));
216     }
217     public function process() {
218         global $CFG, $DB;
219         if (!file_exists("$CFG->dataroot/temp/backup/".$this->filepath) || !is_dir("$CFG->dataroot/temp/backup/".$this->filepath)) {
220             throw new restore_ui_exception('invalidrestorepath');
221         }
222         if (optional_param('searchcourses', false, PARAM_BOOL)) {
223             return false;
224         }
225         $this->target = optional_param('target', backup::TARGET_NEW_COURSE, PARAM_INT);
226         $targetid = optional_param('targetid', null, PARAM_INT);
227         if (!is_null($this->target) && !is_null($targetid) && confirm_sesskey()) {
228             if ($this->target == backup::TARGET_NEW_COURSE) {
229                 list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'), get_string('restoringcourseshortname', 'backup'));
230                 $this->courseid = restore_dbops::create_new_course($fullname, $shortname, $targetid);
231             } else {
232                 $this->courseid = $targetid;
233             }
234             return ($DB->record_exists('course', array('id'=>$this->courseid)));
235         }
236         return false;
237     }
238     /**
239      *
240      * @global moodle_database $DB
241      * @param core_backup_renderer $renderer
242      * @return string
243      */
244     public function display($renderer) {
245         global $DB, $USER, $PAGE;
247         $format = backup_general_helper::detect_backup_format($this->filepath);
248         if ($format !== 'moodle2') {
249             return $renderer->invalid_format($format);
250         }
252         $this->details = backup_general_helper::get_backup_information($this->filepath);
253         $url = new moodle_url('/backup/restore.php', array('contextid'=>$this->contextid, 'filepath'=>$this->filepath, 'stage'=>restore_ui::STAGE_SETTINGS));
254         
255         $context = get_context_instance_by_id($this->contextid);
256         $currentcourse = ($context->contextlevel == CONTEXT_COURSE && has_capability('moodle/restore:restorecourse', $context))?$context->instanceid:false;
258         $html = $renderer->course_selector($url, $this->details, $this->categorysearch, $this->coursesearch, $currentcourse);
259         return $html;
260     }
261     public function get_stage_name() {
262         return get_string('restorestage'.restore_ui::STAGE_DESTINATION, 'backup');
263     }
264     public function get_filepath() {
265         return $this->filepath;
266     }
267     public function get_course_id() {
268         return $this->courseid;
269     }
270     public function get_stage() {
271         return restore_ui::STAGE_DESTINATION;
272     }
273     public function get_target() {
274         return $this->target;
275     }
277 /**
278  * This stage is the settings stage.
279  *
280  * This stage is the third stage, it is dependent on a restore controller and
281  * is the first stage as such.
282  */
283 class restore_ui_stage_settings extends restore_ui_stage {
284     /**
285      * Initial restore stage constructor
286      * @param restore_ui $ui
287      */
288     public function __construct(restore_ui $ui, array $params=null) {
289         $this->stage = restore_ui::STAGE_SETTINGS;
290         parent::__construct($ui, $params);
291     }
293     public function process(base_moodleform $form=null) {
294         $form = $this->initialise_stage_form();
296         if ($form->is_cancelled()) {
297             $this->ui->cancel_restore();
298         }
300         $data = $form->get_data();
301         if ($data && confirm_sesskey()) {
302             $tasks = $this->ui->get_tasks();
303             $changes = 0;
304             foreach ($tasks as &$task) {
305                 // We are only interesting in the backup root task for this stage
306                 if ($task instanceof restore_root_task || $task instanceof restore_course_task) {
307                     // Get all settings into a var so we can iterate by reference
308                     $settings = $task->get_settings();
309                     foreach ($settings as &$setting) {
310                         $name = $setting->get_ui_name();
311                         if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
312                             $setting->set_value($data->$name);
313                             $changes++;
314                         } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
315                             $setting->set_value(0);
316                             $changes++;
317                         }
318                     }
319                 }
320             }
321             // Return the number of changes the user made
322             return $changes;
323         } else {
324             return false;
325         }
326     }
328     protected function initialise_stage_form() {
329         global $PAGE;
330         if ($this->stageform === null) {
331             $form = new restore_settings_form($this, $PAGE->url);
332             // Store as a variable so we can iterate by reference
333             $tasks = $this->ui->get_tasks();
334             $headingprinted = false;
335             // Iterate all tasks by reference
336             foreach ($tasks as &$task) {
337                 // For the initial stage we are only interested in the root settings
338                 if ($task instanceof restore_root_task) {
339                     if (!$headingprinted) {
340                         $form->add_heading('rootsettings', get_string('restorerootsettings', 'backup'));
341                         $headingprinted = true;
342                     }
343                     $settings = $task->get_settings();
344                     // First add all settings except the filename setting
345                     foreach ($settings as &$setting) {
346                         if ($setting->get_name() == 'filename') {
347                             continue;
348                         }
349                         $form->add_setting($setting, $task);
350                     }
351                     // Then add all dependencies
352                     foreach ($settings as &$setting) {
353                         if ($setting->get_name() == 'filename') {
354                             continue;
355                         }
356                         $form->add_dependencies($setting);
357                     }
358                 }
359             }
360             $this->stageform = $form;
361         }
362         // Return the form
363         return $this->stageform;
364     }
367 /**
368  * Schema stage of backup process
369  *
370  * During the schema stage the user is required to set the settings that relate
371  * to the area that they are backing up as well as its children.
372  *
373  * @copyright 2010 Sam Hemelryk
374  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
375  */
376 class restore_ui_stage_schema extends restore_ui_stage {
377     /**
378      * Schema stage constructor
379      * @param backup_moodleform $ui
380      */
381     public function __construct(restore_ui $ui, array $params=null) {
382         $this->stage = restore_ui::STAGE_SCHEMA;
383         parent::__construct($ui, $params);
384     }
385     /**
386      * Processes the schema stage
387      *
388      * @param backup_moodleform|null $form
389      * @return int The number of changes the user made
390      */
391     public function process(base_moodleform $form = null) {
392         $form = $this->initialise_stage_form();
393         // Check it wasn't cancelled
394         if ($form->is_cancelled()) {
395             $this->ui->cancel_process();
396         }
398         // Check it has been submit
399         $data = $form->get_data();
400         if ($data && confirm_sesskey()) {
401             // Get the tasks into a var so we can iterate by reference
402             $tasks = $this->ui->get_tasks();
403             $changes = 0;
404             // Iterate all tasks by reference
405             foreach ($tasks as &$task) {
406                 // We are only interested in schema settings
407                 if (!($task instanceof restore_root_task)) {
408                     // Store as a variable so we can iterate by reference
409                     $settings = $task->get_settings();
410                     // Iterate by reference
411                     foreach ($settings as &$setting) {
412                         $name = $setting->get_ui_name();
413                         if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
414                             $setting->set_value($data->$name);
415                             $changes++;
416                         } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
417                             $setting->set_value(0);
418                             $changes++;
419                         }
420                     }
421                 }
422             }
423             // Return the number of changes the user made
424             return $changes;
425         } else {
426             return false;
427         }
428     }
429     /**
430      * Creates the backup_schema_form instance for this stage
431      *
432      * @return backup_schema_form
433      */
434     protected function initialise_stage_form() {
435         global $PAGE;
436         if ($this->stageform === null) {
437             $form = new restore_schema_form($this, $PAGE->url);
438             $tasks = $this->ui->get_tasks();
439             $content = '';
440             $courseheading = false;
441             foreach ($tasks as $task) {
442                 if (!($task instanceof restore_root_task)) {
443                     if (!$courseheading) {
444                         // If we havn't already display a course heading to group nicely
445                         $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
446                         $courseheading = true;
447                     }
448                     // First add each setting
449                     foreach ($task->get_settings() as $setting) {
450                         $form->add_setting($setting, $task);
451                     }
452                     // The add all the dependencies
453                     foreach ($task->get_settings() as $setting) {
454                         $form->add_dependencies($setting);
455                     }
456                 } else if ($this->ui->enforce_changed_dependencies()) {
457                     // Only show these settings if dependencies changed them.
458                     // Add a root settings heading to group nicely
459                     $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
460                     // Iterate all settings and add them to the form as a fixed
461                     // setting. We only want schema settings to be editable
462                     foreach ($task->get_settings() as $setting) {
463                         if ($setting->get_name() != 'filename') {
464                             $form->add_fixed_setting($setting, $task);
465                         }
466                     }
467                 }
468             }
469             $this->stageform = $form;
470         }
471         return $this->stageform;
472     }
475 /**
476  * Confirmation stage
477  *
478  * On this stage the user reviews the setting for the backup and can change the filename
479  * of the file that will be generated.
480  *
481  * @copyright 2010 Sam Hemelryk
482  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
483  */
484 class restore_ui_stage_review extends restore_ui_stage {
485     /**
486      * Constructs the stage
487      * @param backup_ui $ui
488      */
489     public function __construct($ui, array $params=null) {
490         $this->stage = restore_ui::STAGE_REVIEW;
491         parent::__construct($ui, $params);
492     }
493     /**
494      * Processes the confirmation stage
495      *
496      * @param backup_moodleform $form
497      * @return int The number of changes the user made
498      */
499     public function process(base_moodleform $form = null) {
500         $form = $this->initialise_stage_form();
501         // Check it hasn't been cancelled
502         if ($form->is_cancelled()) {
503             $this->ui->cancel_process();
504         }
506         $data = $form->get_data();
507         if ($data && confirm_sesskey()) {
508             return 0;
509         } else {
510             return false;
511         }
512     }
513     /**
514      * Creates the backup_confirmation_form instance this stage requires
515      *
516      * @return backup_confirmation_form
517      */
518     protected function initialise_stage_form() {
519         global $PAGE;
520         if ($this->stageform === null) {
521             // Get the form
522             $form = new restore_review_form($this, $PAGE->url);
523             $content = '';
524             $courseheading = false;
526             foreach ($this->ui->get_tasks() as $task) {
527                 if ($task instanceof restore_root_task) {
528                     // If its a backup root add a root settings heading to group nicely
529                     $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
530                 } else if (!$courseheading) {
531                     // we havn't already add a course heading
532                     $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
533                     $courseheading = true;
534                 }
535                 // Iterate all settings, doesnt need to happen by reference
536                 foreach ($task->get_settings() as $setting) {
537                     $form->add_fixed_setting($setting, $task);
538                 }
539             }
540             $this->stageform = $form;
541         }
542         return $this->stageform;
543     }
546 /**
547  * Final stage of backup
548  *
549  * This stage is special in that it is does not make use of a form. The reason for
550  * this is the order of procession of backup at this stage.
551  * The processesion is:
552  * 1. The final stage will be intialise.
553  * 2. The confirmation stage will be processed.
554  * 3. The backup will be executed
555  * 4. The complete stage will be loaded by execution
556  * 5. The complete stage will be displayed
557  *
558  * This highlights that we neither need a form nor a display method for this stage
559  * we simply need to process.
560  *
561  * @copyright 2010 Sam Hemelryk
562  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
563  */
564 class restore_ui_stage_process extends restore_ui_stage {
566     const SUBSTAGE_NONE = 0;
567     const SUBSTAGE_CONVERT = 1;
568     const SUBSTAGE_PRECHECKS = 2;
570     protected $substage = 0;
572     /**
573      * Constructs the final stage
574      * @param backup_ui $ui
575      */
576     public function __construct(base_ui $ui, array $params=null) {
577         $this->stage = restore_ui::STAGE_PROCESS;
578         parent::__construct($ui, $params);
579     }
580     /**
581      * Processes the final stage.
582      *
583      * In this case it checks to see if there is a sub stage that we need to display
584      * before execution, if there is we gear up to display the subpage, otherwise
585      * we return true which will lead to execution of the restore and the loading
586      * of the completed stage.
587      */
588     public function process(base_moodleform $form=null) {
589         if (optional_param('cancel', false, PARAM_BOOL)) {
590             redirect(new moodle_url('/course/view.php', array('id'=>$this->get_ui()->get_controller()->get_courseid())));
591         }
593         // First decide whether a substage is needed
594         $rc = $this->ui->get_controller();
595         if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
596             $this->substage = self::SUBSTAGE_CONVERT;
597         } else {
598             if ($rc->get_status() == backup::STATUS_SETTING_UI) {
599                 $rc->finish_ui();
600             }
601             if ($rc->get_status() == backup::STATUS_NEED_PRECHECK) {
602                 if (!$rc->precheck_executed()) {
603                     $rc->execute_precheck(true);
604                 }
605                 $results = $rc->get_precheck_results();
606                 if (!empty($results)) {
607                     $this->substage = self::SUBSTAGE_PRECHECKS;
608                 }
609             }
610         }
612         $substage = optional_param('substage', null, PARAM_INT);
613         if (empty($this->substage) && !empty($substage)) {
614             $this->substage = $substage;
615             // Now check whether that substage has already been submit
616             if ($this->substage == self::SUBSTAGE_PRECHECKS && optional_param('sesskey', null, PARAM_RAW) == sesskey()) {
617                 $info = $rc->get_info();
618                 if (!empty($info->role_mappings->mappings)) {
619                     foreach ($info->role_mappings->mappings as $key=>&$mapping) {
620                         $mapping->targetroleid = optional_param('mapping'.$key, $mapping->targetroleid, PARAM_INT);
621                     }
622                     $info->role_mappings->modified = true;
623                 }
624                 // We've processed the substage now setting it back to none so we
625                 // can move to the next stage.
626                 $this->substage = self::SUBSTAGE_NONE;
627             }
628         }
630         return empty($this->substage);
631     }
632     /**
633      * should NEVER be called... throws an exception
634      */
635     protected function initialise_stage_form() {
636         throw new backup_ui_exception('backup_ui_must_execute_first');
637     }
638     /**
639      * should NEVER be called... throws an exception
640      */
641     public function display($renderer) {
642         global $PAGE;
643         $haserrors = false;
644         $url = new moodle_url($PAGE->url, array('restore'=>$this->get_uniqueid(), 'stage'=>restore_ui::STAGE_PROCESS, 'substage'=>$this->substage, 'sesskey'=>sesskey()));
645         echo html_writer::start_tag('form', array('action'=>$url->out_omit_querystring(), 'class'=>'backup-restore', 'method'=>'post'));
646         foreach ($url->params() as $name=>$value) {
647             echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$value));
648         }
649         switch ($this->substage) {
650             case self::SUBSTAGE_CONVERT :
651                 echo '<h2>Need to show the conversion screens here</h2>';
652                 break;
653             case self::SUBSTAGE_PRECHECKS :
654                 $results = $this->ui->get_controller()->get_precheck_results();
655                 $info = $this->ui->get_controller()->get_info();
656                 $haserrors = (!empty($results['errors']));
657                 echo $renderer->precheck_notices($results);
658                 if (!empty($info->role_mappings->mappings)) {
659                     $context = get_context_instance(CONTEXT_COURSE, $this->ui->get_controller()->get_courseid());
660                     $assignableroles = get_assignable_roles($context, ROLENAME_ALIAS, false);
661                     echo $renderer->role_mappings($info->role_mappings->mappings, $assignableroles);
662                 }
663                 break;
664             default:
665                 throw new restore_ui_exception('backup_ui_must_execute_first');
666         }
667         echo $renderer->substage_buttons($haserrors);
668         echo html_writer::end_tag('form');
669     }
671     public function has_sub_stages() {
672         return true;
673     }
676 /**
677  * This is the completed stage.
678  *
679  * Once this is displayed there is nothing more to do.
680  */
681 class restore_ui_stage_complete extends restore_ui_stage_process {
682     /**
683      * The results of the backup execution
684      * @var array
685      */
686     protected $results;
687     /**
688      * Constructs the complete backup stage
689      * @param backup_ui $ui
690      * @param array|null $params
691      * @param array $results
692      */
693     public function __construct(restore_ui $ui, array $params=null, array $results=null) {
694         $this->results = $results;
695         parent::__construct($ui, $params);
696         $this->stage = restore_ui::STAGE_COMPLETE;
697     }
698     /**
699      * Displays the completed backup stage.
700      *
701      * Currently this just envolves redirecting to the file browser with an
702      * appropriate message.
703      *
704      * @global core_renderer $OUTPUT
705      * @param core_backup_renderer $renderer
706      */
707     public function display(core_backup_renderer $renderer) {
708         global $OUTPUT;
709         echo $OUTPUT->box_start();
710         echo $OUTPUT->notification(get_string('restoreexecutionsuccess', 'backup'), 'notifysuccess');
711         echo $renderer->continue_button(new moodle_url('/course/view.php', array('id'=>$this->get_ui()->get_controller()->get_courseid())), 'get');
712         echo $OUTPUT->box_end();
713     }