PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/lesson/renderer.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 603 lines | 346 code | 82 blank | 175 comment | 41 complexity | 8f6359d989c372a3f17389f45dfe7b2f MD5 | raw file
  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. * Moodle renderer used to display special elements of the lesson module
  18. *
  19. * @package mod
  20. * @subpackage lesson
  21. * @copyright 2009 Sam Hemelryk
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. **/
  24. defined('MOODLE_INTERNAL') || die();
  25. class mod_lesson_renderer extends plugin_renderer_base {
  26. /**
  27. * Returns the header for the lesson module
  28. *
  29. * @param lesson $lesson a lesson object.
  30. * @param string $currenttab current tab that is shown.
  31. * @param bool $extraeditbuttons if extra edit buttons should be displayed.
  32. * @param int $lessonpageid id of the lesson page that needs to be displayed.
  33. * @param string $extrapagetitle String to appent to the page title.
  34. * @return string
  35. */
  36. public function header($lesson, $cm, $currenttab = '', $extraeditbuttons = false, $lessonpageid = null, $extrapagetitle = null) {
  37. global $CFG;
  38. $activityname = format_string($lesson->name, true, $lesson->course);
  39. if (empty($extrapagetitle)) {
  40. $title = $this->page->course->shortname.": ".$activityname;
  41. } else {
  42. $title = $this->page->course->shortname.": ".$activityname.": ".$extrapagetitle;
  43. }
  44. // Build the buttons
  45. $context = context_module::instance($cm->id);
  46. /// Header setup
  47. $this->page->set_title($title);
  48. $this->page->set_heading($this->page->course->fullname);
  49. lesson_add_header_buttons($cm, $context, $extraeditbuttons, $lessonpageid);
  50. $output = $this->output->header();
  51. if (has_capability('mod/lesson:manage', $context)) {
  52. $output .= $this->output->heading_with_help($activityname, 'overview', 'lesson');
  53. if (!empty($currenttab)) {
  54. ob_start();
  55. include($CFG->dirroot.'/mod/lesson/tabs.php');
  56. $output .= ob_get_contents();
  57. ob_end_clean();
  58. }
  59. } else {
  60. $output .= $this->output->heading($activityname);
  61. }
  62. foreach ($lesson->messages as $message) {
  63. $output .= $this->output->notification($message[0], $message[1], $message[2]);
  64. }
  65. return $output;
  66. }
  67. /**
  68. * Returns the footer
  69. * @return string
  70. */
  71. public function footer() {
  72. return $this->output->footer();
  73. }
  74. /**
  75. * Returns HTML for a lesson inaccessible message
  76. *
  77. * @param string $message
  78. * @return <type>
  79. */
  80. public function lesson_inaccessible($message) {
  81. global $CFG;
  82. $output = $this->output->box_start('generalbox boxaligncenter');
  83. $output .= $this->output->box_start('center');
  84. $output .= $message;
  85. $output .= $this->output->box('<a href="'.$CFG->wwwroot.'/course/view.php?id='. $this->page->course->id .'">'. get_string('returnto', 'lesson', format_string($this->page->course->fullname, true)) .'</a>', 'lessonbutton standardbutton');
  86. $output .= $this->output->box_end();
  87. $output .= $this->output->box_end();
  88. return $output;
  89. }
  90. /**
  91. * Returns HTML to prompt the user to log in
  92. * @param lesson $lesson
  93. * @param bool $failedattempt
  94. * @return string
  95. */
  96. public function login_prompt(lesson $lesson, $failedattempt = false) {
  97. global $CFG;
  98. $output = $this->output->box_start('password-form');
  99. $output .= $this->output->box_start('generalbox boxaligncenter');
  100. $output .= '<form id="password" method="post" action="'.$CFG->wwwroot.'/mod/lesson/view.php" autocomplete="off">';
  101. $output .= '<fieldset class="invisiblefieldset center">';
  102. $output .= '<input type="hidden" name="id" value="'. $this->page->cm->id .'" />';
  103. if ($failedattempt) {
  104. $output .= $this->output->notification(get_string('loginfail', 'lesson'));
  105. }
  106. $output .= get_string('passwordprotectedlesson', 'lesson', format_string($lesson->name)).'<br /><br />';
  107. $output .= get_string('enterpassword', 'lesson')." <input type=\"password\" name=\"userpassword\" /><br /><br />";
  108. $output .= "<div class='lessonbutton standardbutton submitbutton'><input type='submit' value='".get_string('continue', 'lesson')."' /></div>";
  109. $output .= " <div class='lessonbutton standardbutton submitbutton'><input type='submit' name='backtocourse' value='".get_string('cancel', 'lesson')."' /></div>";
  110. $output .= '</fieldset></form>';
  111. $output .= $this->output->box_end();
  112. $output .= $this->output->box_end();
  113. return $output;
  114. }
  115. /**
  116. * Returns HTML to display dependancy errors
  117. *
  118. * @param object $dependentlesson
  119. * @param array $errors
  120. * @return string
  121. */
  122. public function dependancy_errors($dependentlesson, $errors) {
  123. $output = $this->output->box_start('generalbox boxaligncenter');
  124. $output .= get_string('completethefollowingconditions', 'lesson', $dependentlesson->name);
  125. $output .= $this->output->box(implode('<br />'.get_string('and', 'lesson').'<br />', $errors),'center');
  126. $output .= $this->output->box_end();
  127. return $output;
  128. }
  129. /**
  130. * Returns HTML to display a message
  131. * @param string $message
  132. * @param single_button $button
  133. * @return string
  134. */
  135. public function message($message, single_button $button = null) {
  136. $output = $this->output->box_start('generalbox boxaligncenter');
  137. $output .= $message;
  138. if ($button !== null) {
  139. $output .= $this->output->box($this->output->render($button), 'lessonbutton standardbutton');
  140. }
  141. $output .= $this->output->box_end();
  142. return $output;
  143. }
  144. /**
  145. * Returns HTML to display a continue button
  146. * @param lesson $lesson
  147. * @param int $lastpageseen
  148. * @return string
  149. */
  150. public function continue_links(lesson $lesson, $lastpageseenid) {
  151. global $CFG;
  152. $output = $this->output->box(get_string('youhaveseen','lesson'), 'generalbox boxaligncenter');
  153. $output .= $this->output->box_start('center');
  154. $yeslink = html_writer::link(new moodle_url('/mod/lesson/view.php', array('id'=>$this->page->cm->id, 'pageid'=>$lastpageseenid, 'startlastseen'=>'yes')), get_string('yes'));
  155. $output .= html_writer::tag('span', $yeslink, array('class'=>'lessonbutton standardbutton'));
  156. $output .= '&nbsp;';
  157. $nolink = html_writer::link(new moodle_url('/mod/lesson/view.php', array('id'=>$this->page->cm->id, 'pageid'=>$lesson->firstpageid, 'startlastseen'=>'no')), get_string('no'));
  158. $output .= html_writer::tag('span', $nolink, array('class'=>'lessonbutton standardbutton'));
  159. $output .= $this->output->box_end();
  160. return $output;
  161. }
  162. /**
  163. * Returns HTML to display a page to the user
  164. * @param lesson $lesson
  165. * @param lesson_page $page
  166. * @param object $attempt
  167. * @return string
  168. */
  169. public function display_page(lesson $lesson, lesson_page $page, $attempt) {
  170. // We need to buffer here as there is an mforms display call
  171. ob_start();
  172. echo $page->display($this, $attempt);
  173. $output = ob_get_contents();
  174. ob_end_clean();
  175. return $output;
  176. }
  177. /**
  178. * Returns HTML to display a collapsed edit form
  179. *
  180. * @param lesson $lesson
  181. * @param int $pageid
  182. * @return string
  183. */
  184. public function display_edit_collapsed(lesson $lesson, $pageid) {
  185. global $DB, $CFG;
  186. $manager = lesson_page_type_manager::get($lesson);
  187. $qtypes = $manager->get_page_type_strings();
  188. $npages = count($lesson->load_all_pages());
  189. $table = new html_table();
  190. $table->head = array(get_string('pagetitle', 'lesson'), get_string('qtype', 'lesson'), get_string('jumps', 'lesson'), get_string('actions', 'lesson'));
  191. $table->align = array('left', 'left', 'left', 'center');
  192. $table->wrap = array('', 'nowrap', '', 'nowrap');
  193. $table->tablealign = 'center';
  194. $table->cellspacing = 0;
  195. $table->cellpadding = '2px';
  196. $table->width = '80%';
  197. $table->data = array();
  198. $canedit = has_capability('mod/lesson:edit', context_module::instance($this->page->cm->id));
  199. while ($pageid != 0) {
  200. $page = $lesson->load_page($pageid);
  201. $data = array();
  202. $data[] = "<a href=\"$CFG->wwwroot/mod/lesson/edit.php?id=".$this->page->cm->id."&amp;mode=single&amp;pageid=".$page->id."\">".format_string($page->title,true).'</a>';
  203. $data[] = $qtypes[$page->qtype];
  204. $data[] = implode("<br />\n", $page->jumps);
  205. if ($canedit) {
  206. $data[] = $this->page_action_links($page, $npages, true);
  207. } else {
  208. $data[] = '';
  209. }
  210. $table->data[] = $data;
  211. $pageid = $page->nextpageid;
  212. }
  213. return html_writer::table($table);
  214. }
  215. /**
  216. * Returns HTML to display the full edit page
  217. *
  218. * @param lesson $lesson
  219. * @param int $pageid
  220. * @param int $prevpageid
  221. * @param bool $single
  222. * @return string
  223. */
  224. public function display_edit_full(lesson $lesson, $pageid, $prevpageid, $single=false) {
  225. global $DB, $CFG;
  226. $manager = lesson_page_type_manager::get($lesson);
  227. $qtypes = $manager->get_page_type_strings();
  228. $npages = count($lesson->load_all_pages());
  229. $canedit = has_capability('mod/lesson:edit', context_module::instance($this->page->cm->id));
  230. $content = '';
  231. if ($canedit) {
  232. $content = $this->add_page_links($lesson, $prevpageid);
  233. }
  234. $options = new stdClass;
  235. $options->noclean = true;
  236. while ($pageid != 0 && $single!=='stop') {
  237. $page = $lesson->load_page($pageid);
  238. $pagetable = new html_table();
  239. $pagetable->align = array('right','left');
  240. $pagetable->width = '100%';
  241. $pagetable->tablealign = 'center';
  242. $pagetable->cellspacing = 0;
  243. $pagetable->cellpadding = '5px';
  244. $pagetable->data = array();
  245. $pageheading = new html_table_cell();
  246. $pageheading->text = format_string($page->title);
  247. if ($canedit) {
  248. $pageheading->text .= ' '.$this->page_action_links($page, $npages);
  249. }
  250. $pageheading->style = 'text-align:center';
  251. $pageheading->colspan = 2;
  252. $pageheading->scope = 'col';
  253. $pagetable->head = array($pageheading);
  254. $cell = new html_table_cell();
  255. $cell->colspan = 2;
  256. $cell->style = 'text-align:left';
  257. $cell->text = $page->contents;
  258. $pagetable->data[] = new html_table_row(array($cell));
  259. $cell = new html_table_cell();
  260. $cell->colspan = 2;
  261. $cell->style = 'text-align:center';
  262. $cell->text = '<strong>'.$qtypes[$page->qtype] . $page->option_description_string().'</strong>';
  263. $pagetable->data[] = new html_table_row(array($cell));
  264. $pagetable = $page->display_answers($pagetable);
  265. $content .= html_writer::table($pagetable);
  266. if ($canedit) {
  267. $content .= $this->add_page_links($lesson, $pageid);
  268. }
  269. // check the prev links - fix (silently) if necessary - there was a bug in
  270. // versions 1 and 2 when add new pages. Not serious then as the backwards
  271. // links were not used in those versions
  272. if ($page->prevpageid != $prevpageid) {
  273. // fix it
  274. $DB->set_field("lesson_pages", "prevpageid", $prevpageid, array("id" => $page->id));
  275. debugging("<p>***prevpageid of page $page->id set to $prevpageid***");
  276. }
  277. $prevpageid = $page->id;
  278. $pageid = $page->nextpageid;
  279. if ($single === true) {
  280. $single = 'stop';
  281. }
  282. }
  283. return $this->output->box($content, 'edit_pages_box');
  284. }
  285. /**
  286. * Returns HTML to display the add page links
  287. *
  288. * @param lesson $lesson
  289. * @param int $prevpageid
  290. * @return string
  291. */
  292. public function add_page_links(lesson $lesson, $prevpageid=false) {
  293. global $CFG;
  294. $links = array();
  295. $importquestionsurl = new moodle_url('/mod/lesson/import.php',array('id'=>$this->page->cm->id, 'pageid'=>$prevpageid));
  296. $links[] = html_writer::link($importquestionsurl, get_string('importquestions', 'lesson'));
  297. $manager = lesson_page_type_manager::get($lesson);
  298. foreach($manager->get_add_page_type_links($prevpageid) as $link) {
  299. $links[] = html_writer::link($link['addurl'], $link['name']);
  300. }
  301. $addquestionurl = new moodle_url('/mod/lesson/editpage.php', array('id'=>$this->page->cm->id, 'pageid'=>$prevpageid));
  302. $links[] = html_writer::link($addquestionurl, get_string('addaquestionpagehere', 'lesson'));
  303. return $this->output->box(implode(" | \n", $links), 'addlinks');
  304. }
  305. /**
  306. * Return HTML to display add first page links
  307. * @param lesson $lesson
  308. * @return string
  309. */
  310. public function add_first_page_links(lesson $lesson) {
  311. global $CFG;
  312. $prevpageid = 0;
  313. $output = $this->output->heading(get_string("whatdofirst", "lesson"), 3);
  314. $links = array();
  315. $importquestionsurl = new moodle_url('/mod/lesson/import.php',array('id'=>$this->page->cm->id, 'pageid'=>$prevpageid));
  316. $links[] = html_writer::link($importquestionsurl, get_string('importquestions', 'lesson'));
  317. $manager = lesson_page_type_manager::get($lesson);
  318. foreach ($manager->get_add_page_type_links($prevpageid) as $link) {
  319. $link['addurl']->param('firstpage', 1);
  320. $links[] = html_writer::link($link['addurl'], $link['name']);
  321. }
  322. $addquestionurl = new moodle_url('/mod/lesson/editpage.php', array('id'=>$this->page->cm->id, 'pageid'=>$prevpageid, 'firstpage'=>1));
  323. $links[] = html_writer::link($addquestionurl, get_string('addaquestionpage', 'lesson'));
  324. return $this->output->box($output.'<p>'.implode('</p><p>', $links).'</p>', 'generalbox firstpageoptions');
  325. }
  326. /**
  327. * Returns HTML to display action links for a page
  328. *
  329. * @param lesson_page $page
  330. * @param bool $printmove
  331. * @param bool $printaddpage
  332. * @return string
  333. */
  334. public function page_action_links(lesson_page $page, $printmove, $printaddpage=false) {
  335. global $CFG;
  336. $actions = array();
  337. if ($printmove) {
  338. $printmovehtml = new moodle_url('/mod/lesson/lesson.php', array('id'=>$this->page->cm->id, 'action'=>'move', 'pageid'=>$page->id, 'sesskey'=>sesskey()));
  339. $actions[] = html_writer::link($printmovehtml, '<img src="'.$this->output->pix_url('t/move').'" class="iconsmall" alt="'.get_string('move').'" />');
  340. }
  341. $url = new moodle_url('/mod/lesson/editpage.php', array('id'=>$this->page->cm->id, 'pageid'=>$page->id, 'edit'=>1));
  342. $actions[] = html_writer::link($url, '<img src="'.$this->output->pix_url('t/edit').'" class="iconsmall" alt="'.get_string('update').'" />');
  343. $url = new moodle_url('/mod/lesson/view.php', array('id'=>$this->page->cm->id, 'pageid'=>$page->id));
  344. $actions[] = html_writer::link($url, '<img src="'.$this->output->pix_url('t/preview').'" class="iconsmall" alt="'.get_string('preview').'" />');
  345. $url = new moodle_url('/mod/lesson/lesson.php', array('id'=>$this->page->cm->id, 'action'=>'confirmdelete', 'pageid'=>$page->id, 'sesskey'=>sesskey()));
  346. $actions[] = html_writer::link($url, '<img src="'.$this->output->pix_url('t/delete').'" class="iconsmall" alt="'.get_string('delete').'" />');
  347. if ($printaddpage) {
  348. $options = array();
  349. $manager = lesson_page_type_manager::get($page->lesson);
  350. $links = $manager->get_add_page_type_links($page->id);
  351. foreach ($links as $link) {
  352. $options[$link['type']] = $link['name'];
  353. }
  354. $options[0] = get_string('question', 'lesson');
  355. $addpageurl = new moodle_url('/mod/lesson/editpage.php', array('id'=>$this->page->cm->id, 'pageid'=>$page->id, 'sesskey'=>sesskey()));
  356. $addpageselect = new single_select($addpageurl, 'qtype', $options, null, array(''=>get_string('addanewpage', 'lesson').'...'), 'addpageafter'.$page->id);
  357. $addpageselector = $this->output->render($addpageselect);
  358. }
  359. if (isset($addpageselector)) {
  360. $actions[] = $addpageselector;
  361. }
  362. return implode(' ', $actions);
  363. }
  364. /**
  365. * Prints the on going message to the user.
  366. *
  367. * With custom grading On, displays points
  368. * earned out of total points possible thus far.
  369. * With custom grading Off, displays number of correct
  370. * answers out of total attempted.
  371. *
  372. * @param object $lesson The lesson that the user is taking.
  373. * @return void
  374. **/
  375. /**
  376. * Prints the on going message to the user.
  377. *
  378. * With custom grading On, displays points
  379. * earned out of total points possible thus far.
  380. * With custom grading Off, displays number of correct
  381. * answers out of total attempted.
  382. *
  383. * @param lesson $lesson
  384. * @return string
  385. */
  386. public function ongoing_score(lesson $lesson) {
  387. global $USER, $DB;
  388. $context = context_module::instance($this->page->cm->id);
  389. if (has_capability('mod/lesson:manage', $context)) {
  390. return $this->output->box(get_string('teacherongoingwarning', 'lesson'), "ongoing center");
  391. } else {
  392. $ntries = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
  393. if (isset($USER->modattempts[$lesson->id])) {
  394. $ntries--;
  395. }
  396. $gradeinfo = lesson_grade($lesson, $ntries);
  397. $a = new stdClass;
  398. if ($lesson->custom) {
  399. $a->score = $gradeinfo->earned;
  400. $a->currenthigh = $gradeinfo->total;
  401. return $this->output->box(get_string("ongoingcustom", "lesson", $a), "ongoing center");
  402. } else {
  403. $a->correct = $gradeinfo->earned;
  404. $a->viewed = $gradeinfo->attempts;
  405. return $this->output->box(get_string("ongoingnormal", "lesson", $a), "ongoing center");
  406. }
  407. }
  408. }
  409. /**
  410. * Returns HTML to display a progress bar of progression through a lesson
  411. *
  412. * @param lesson $lesson
  413. * @return string
  414. */
  415. public function progress_bar(lesson $lesson) {
  416. global $CFG, $USER, $DB;
  417. $context = context_module::instance($this->page->cm->id);
  418. // lesson setting to turn progress bar on or off
  419. if (!$lesson->progressbar) {
  420. return '';
  421. }
  422. // catch teachers
  423. if (has_capability('mod/lesson:manage', $context)) {
  424. return $this->output->notification(get_string('progressbarteacherwarning2', 'lesson'));
  425. }
  426. if (!isset($USER->modattempts[$lesson->id])) {
  427. // all of the lesson pages
  428. $pages = $lesson->load_all_pages();
  429. foreach ($pages as $page) {
  430. if ($page->prevpageid == 0) {
  431. $pageid = $page->id; // find the first page id
  432. break;
  433. }
  434. }
  435. // current attempt number
  436. if (!$ntries = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id))) {
  437. $ntries = 0; // may not be necessary
  438. }
  439. $viewedpageids = array();
  440. if ($attempts = $lesson->get_attempts($ntries, false)) {
  441. foreach($attempts as $attempt) {
  442. $viewedpageids[$attempt->pageid] = $attempt;
  443. }
  444. }
  445. $viewedbranches = array();
  446. // collect all of the branch tables viewed
  447. if ($branches = $DB->get_records("lesson_branch", array ("lessonid"=>$lesson->id, "userid"=>$USER->id, "retry"=>$ntries), 'timeseen ASC', 'id, pageid')) {
  448. foreach($branches as $branch) {
  449. $viewedbranches[$branch->pageid] = $branch;
  450. }
  451. $viewedpageids = array_merge($viewedpageids, $viewedbranches);
  452. }
  453. // Filter out the following pages:
  454. // End of Cluster
  455. // End of Branch
  456. // Pages found inside of Clusters
  457. // Do not filter out Cluster Page(s) because we count a cluster as one.
  458. // By keeping the cluster page, we get our 1
  459. $validpages = array();
  460. while ($pageid != 0) {
  461. $pageid = $pages[$pageid]->valid_page_and_view($validpages, $viewedpageids);
  462. }
  463. // progress calculation as a percent
  464. $progress = round(count($viewedpageids)/count($validpages), 2) * 100;
  465. } else {
  466. $progress = 100;
  467. }
  468. // print out the Progress Bar. Attempted to put as much as possible in the style sheets.
  469. $content = '<br />' . html_writer::tag('div', $progress . '%', array('class' => 'progress_bar_completed', 'style' => 'width: '. $progress . '%;'));
  470. $printprogress = html_writer::tag('div', get_string('progresscompleted', 'lesson', $progress) . $content, array('class' => 'progress_bar'));
  471. return $this->output->box($printprogress, 'progress_bar');
  472. }
  473. /**
  474. * Returns HTML to show the start of a slideshow
  475. * @param lesson $lesson
  476. */
  477. public function slideshow_start(lesson $lesson) {
  478. $attributes = array();
  479. $attributes['class'] = 'slideshow';
  480. $attributes['style'] = 'background-color:'.$lesson->bgcolor.';height:'.$lesson->height.'px;width:'.$lesson->width.'px;';
  481. $output = html_writer::start_tag('div', $attributes);
  482. }
  483. /**
  484. * Returns HTML to show the end of a slideshow
  485. */
  486. public function slideshow_end() {
  487. $output = html_writer::end_tag('div');
  488. }
  489. /**
  490. * Returns a P tag containing contents
  491. * @param string $contents
  492. * @param string $class
  493. */
  494. public function paragraph($contents, $class='') {
  495. $attributes = array();
  496. if ($class !== '') {
  497. $attributes['class'] = $class;
  498. }
  499. $output = html_writer::tag('p', $contents, $attributes);
  500. }
  501. /**
  502. * Returns HTML to display add_highscores_form
  503. * @param lesson $lesson
  504. * @return string
  505. */
  506. public function add_highscores_form(lesson $lesson) {
  507. global $CFG;
  508. $output = $this->output->box_start('generalbox boxaligncenter');
  509. $output .= $this->output->box_start('mdl-align');
  510. $output .= '<form id="nickname" method ="post" action="'.$CFG->wwwroot.'/mod/lesson/highscores.php" autocomplete="off">
  511. <input type="hidden" name="id" value="'.$this->page->cm->id.'" />
  512. <input type="hidden" name="mode" value="save" />
  513. <input type="hidden" name="sesskey" value="'.sesskey().'" />';
  514. $output .= get_string("entername", "lesson").": <input type=\"text\" name=\"name\" size=\"7\" maxlength=\"5\" />";
  515. $output .= $this->output->box("<input type='submit' value='".get_string('submitname', 'lesson')."' />", 'lessonbutton center');
  516. $output .= "</form>";
  517. $output .= $this->output->box_end();
  518. $output .= $this->output->box_end();
  519. return $output;
  520. }
  521. }