From: tjhunt Date: Fri, 26 Jun 2009 09:06:16 +0000 (+0000) Subject: themes: MDL-19077 new $OUTPUT->header/footer to replace print_header/footer. X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=34a2777ccb973f2fc093249fcc3a8bb09c062b5d;p=moodle.git themes: MDL-19077 new $OUTPUT->header/footer to replace print_header/footer. Also, part of the change from weblib.php functions to $OUTPUT-> methods. This is part of http://docs.moodle.org/en/Development:Theme_engines_for_Moodle%3F This is a big change, and the result is not perfect yet. Expect some debugging output on some pages. The main part of these changes are that $OUTPUT->header now looks for a file in the theme called layout.php, rather than header.html and footer.html. Also you can have special templates for certain pages like layout-home.php. There is fallback code for Moodle 1.9 themes, so they still work. A few of the old arguments to print_header are no longer supported. (You get an exception if you try to use them.) Sam H will be cleaning those up. All the weblib functions that have been replaced with $OUTPUT-> have version in deprecatedlib, so existing code will go on working for the foreseeable future. --- diff --git a/admin/report/unittest/test_tables.php b/admin/report/unittest/test_tables.php index c0baa05a36..730a70871b 100644 --- a/admin/report/unittest/test_tables.php +++ b/admin/report/unittest/test_tables.php @@ -35,7 +35,6 @@ die;die;die; $CFG->config_php_settings = $real_cfg->config_php_settings; $CFG->frametarget = $real_cfg->frametarget; $CFG->framename = $real_cfg->framename; - $CFG->footer = $real_cfg->footer; $CFG->debug = 0; $DB = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary); diff --git a/course/view.php b/course/view.php index 15edd623cc..a3e58eea48 100644 --- a/course/view.php +++ b/course/view.php @@ -255,6 +255,6 @@ } - print_footer(NULL, $course); + print_footer(); ?> diff --git a/grade/lib.php b/grade/lib.php index 81c63f6bbb..3ab3bb2b21 100644 --- a/grade/lib.php +++ b/grade/lib.php @@ -811,7 +811,7 @@ class grade_plugin_info { * @return string HTML code or nothing if $return == false */ function print_grade_page_head($courseid, $active_type, $active_plugin=null, - $heading = false, $return=false, $bodytags='', + $heading = false, $return=false, $buttons=false, $extracss=array()) { global $CFG, $COURSE; $strgrades = get_string('grades'); @@ -867,7 +867,7 @@ function print_grade_page_head($courseid, $active_type, $active_plugin=null, } $returnval = print_header_simple($strgrades . ': ' . $stractive_type, $title, $navigation, '', - $bodytags, true, $buttons, navmenu($COURSE), false, '', $return); + '', true, $buttons, navmenu($COURSE), false, '', $return); // Guess heading if not given explicitly if (!$heading) { diff --git a/grade/report/grader/index.php b/grade/report/grader/index.php index 1a47a201bb..ec95ae0d87 100644 --- a/grade/report/grader/index.php +++ b/grade/report/grader/index.php @@ -130,7 +130,7 @@ if ($report->get_pref('enableajax')) { // make sure separate group does not prevent view if ($report->currentgroup == -2) { - print_grade_page_head($COURSE->id, 'report', 'grader', $reportname, false, null, $buttons); + print_grade_page_head($COURSE->id, 'report', 'grader', $reportname, false, $buttons); print_heading(get_string("notingroup")); print_footer($course); exit; diff --git a/index.php b/index.php index d40f73e052..0930fd5d2e 100644 --- a/index.php +++ b/index.php @@ -96,15 +96,14 @@ $PAGE->set_other_editing_capability('moodle/course:manageactivities'); $PAGE->set_url(''); $PAGE->set_docs_path(''); + $PAGE->set_generaltype('home'); $pageblocks = blocks_setup($PAGE); $editing = $PAGE->user_is_editing(); $preferred_width_left = bounded_number(BLOCK_L_MIN_WIDTH, blocks_preferred_width($pageblocks[BLOCK_POS_LEFT]), BLOCK_L_MAX_WIDTH); $preferred_width_right = bounded_number(BLOCK_R_MIN_WIDTH, blocks_preferred_width($pageblocks[BLOCK_POS_RIGHT]), BLOCK_R_MAX_WIDTH); - print_header($SITE->fullname, $SITE->fullname, 'home', '', - '', - true, '', user_login_string($SITE).$langmenu); + print_header($SITE->fullname, $SITE->fullname, 'home', '', '', true, '', user_login_string($SITE).$langmenu); ?> @@ -277,5 +276,5 @@ diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php index e314c3e111..27cef30285 100644 --- a/lib/deprecatedlib.php +++ b/lib/deprecatedlib.php @@ -202,6 +202,7 @@ function get_recent_enrolments($courseid, $timestart) { * parameters remain. If possible, $align, $width and $color should not be defined at all. * Preferably just use print_box() in weblib.php * + * @deprecated * @param string $message The message to display * @param string $align alignment of the box, not the text (default center, left, right). * @param string $width width of the box, including units %, for example '100%'. @@ -242,6 +243,7 @@ function print_simple_box($message, $align='', $width='', $color='', $padding=5, * @return string|void Depending on $return */ function print_simple_box_start($align='', $width='', $color='', $padding=5, $class='generalbox', $id='', $return=false) { + debugging('print_simple_box(_star/_end) is deprecated. Please use $OUTPUT->box(_star/_end) instead', DEBUG_DEVELOPER); $output = ''; @@ -1746,3 +1748,365 @@ function formerr($error) { echo $OUTPUT->error_text($error); } +/** + * Return the markup for the destination of the 'Skip to main content' links. + * Accessibility improvement for keyboard-only users. + * + * Used in course formats, /index.php and /course/index.php + * + * @deprecated use $OUTPUT->skip_link_target() in instead. + * @return string HTML element. + */ +function skip_main_destination() { + global $OUTPUT; + return $OUTPUT->skip_link_target(); +} + +/** + * Prints a string in a specified size (retained for backward compatibility) + * + * @deprecated + * @param string $text The text to be displayed + * @param int $size The size to set the font for text display. + * @param bool $return If set to true output is returned rather than echoed Default false + * @return string|void String if return is true + */ +function print_headline($text, $size=2, $return=false) { + global $OUTPUT; + $output = $OUTPUT->heading($text, $size); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Prints text in a format for use in headings. + * + * @deprecated + * @param string $text The text to be displayed + * @param string $deprecated No longer used. (Use to do alignment.) + * @param int $size The size to set the font for text display. + * @param string $class + * @param bool $return If set to true output is returned rather than echoed, default false + * @param string $id The id to use in the element + * @return string|void String if return=true nothing otherwise + */ +function print_heading($text, $deprecated = '', $size = 2, $class = 'main', $return = false, $id = '') { + global $OUTPUT; + if (!empty($deprecated)) { + debugging('Use of deprecated align attribute of print_heading. ' . + 'Please do not specify styling in PHP code like that.', DEBUG_DEVELOPER); + } + $output = $OUTPUT->heading($text, $size, $class, $id); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Output a standard heading block + * + * @deprecated + * @param string $heading The text to write into the heading + * @param string $class An additional Class Attr to use for the heading + * @param bool $return If set to true output is returned rather than echoed, default false + * @return string|void HTML String if return=true nothing otherwise + */ +function print_heading_block($heading, $class='', $return=false) { + global $OUTPUT; + $output = $OUTPUT->heading($heading, 2, 'headingblock header ' . moodle_renderer_base::prepare_classes($class)); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Print a message in a standard themed box. + * Replaces print_simple_box (see deprecatedlib.php) + * + * @deprecated + * @param string $message, the content of the box + * @param string $classes, space-separated class names. + * @param string $ids + * @param boolean $return, return as string or just print it + * @return string|void mixed string or void + */ +function print_box($message, $classes='generalbox', $ids='', $return=false) { + global $OUTPUT; + $output = $OUTPUT->box($message, $classes, $ids); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Starts a box using divs + * Replaces print_simple_box_start (see deprecatedlib.php) + * + * @deprecated + * @param string $classes, space-separated class names. + * @param string $ids + * @param boolean $return, return as string or just print it + * @return string|void string or void + */ +function print_box_start($classes='generalbox', $ids='', $return=false) { + global $OUTPUT; + $output = $OUTPUT->box_start($classes, $ids); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Simple function to end a box (see above) + * Replaces print_simple_box_end (see deprecatedlib.php) + * + * @deprecated + * @param boolean $return, return as string or just print it + * @return string|void Depending on value of return + */ +function print_box_end($return=false) { + global $OUTPUT; + $output = $OUTPUT->box_end(); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Print a message in a standard themed container. + * + * @deprecated + * @param string $message, the content of the container + * @param boolean $clearfix clear both sides + * @param string $classes, space-separated class names. + * @param string $idbase + * @param boolean $return, return as string or just print it + * @return string|void Depending on value of $return + */ +function print_container($message, $clearfix=false, $classes='', $idbase='', $return=false) { + global $OUTPUT; + if ($clearfix) { + $classes .= ' clearfix'; + } + $output = $OUTPUT->container($message, $classes, $idbase); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Starts a container using divs + * + * @deprecated + * @param boolean $clearfix clear both sides + * @param string $classes, space-separated class names. + * @param string $idbase + * @param boolean $return, return as string or just print it + * @return string|void Based on value of $return + */ +function print_container_start($clearfix=false, $classes='', $idbase='', $return=false) { + global $OUTPUT; + if ($clearfix) { + $classes .= ' clearfix'; + } + $output = $OUTPUT->container_start($classes, $idbase); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Simple function to end a container (see above) + * + * @deprecated + * @param boolean $return, return as string or just print it + * @return string|void Based on $return + */ +function print_container_end($return=false) { + global $OUTPUT; + $output = $OUTPUT->container_end(); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Print a bold message in an optional color. + * + * @deprecated use $OUTPUT->notification instead. + * @param string $message The message to print out + * @param string $style Optional style to display message text in + * @param string $align Alignment option + * @param bool $return whether to return an output string or echo now + * @return string|bool Depending on $result + */ +function notify($message, $classes = 'notifyproblem', $align = 'center', $return = false) { + global $OUTPUT; + + if ($classes == 'green') { + debugging('Use of deprecated class name "green" in notify. Please change to "notifysuccess".', DEBUG_DEVELOPER); + $classes = 'notifysuccess'; // Backward compatible with old color system + } + + $output = $OUTPUT->notification($message, $classes); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Print a continue button that goes to a particular URL. + * + * @param string $link The url to create a link to. + * @param bool $return If set to true output is returned rather than echoed, default false + * @return string|void HTML String if return=true nothing otherwise + */ +function print_continue($link, $return = false) { + global $CFG, $OUTPUT; + + if ($link == '') { + if (!empty($_SERVER['HTTP_REFERER'])) { + $link = $_SERVER['HTTP_REFERER']; + $link = str_replace('&', '&', $link); // make it valid XHTML + } else { + $link = $CFG->wwwroot .'/'; + } + } + + $output = $OUTPUT->continue_button($link); + if ($return) { + return $output; + } else { + echo $output; + } +} + +/** + * Returns a string containing a link to the user documentation for the current + * page. Also contains an icon by default. Shown to teachers and admin only. + * + * @global object + * @global object + * @param string $text The text to be displayed for the link + * @param string $iconpath The path to the icon to be displayed + * @return string The link to user documentation for this current page + */ +function page_doc_link($text='', $iconpath='') { + global $CFG, $PAGE; + + if (empty($CFG->docroot) || during_initial_install()) { + return ''; + } + if (!has_capability('moodle/site:doclinks', $PAGE->context)) { + return ''; + } + + $path = $PAGE->docspath; + if (!$path) { + return ''; + } + return doc_link($path, $text, $iconpath); +} + +/** + * Print a standard header + * + * @param string $title Appears at the top of the window + * @param string $heading Appears at the top of the page + * @param string $navigation Array of $navlinks arrays (keys: name, link, type) for use as breadcrumbs links + * @param string $focus Indicates form element to get cursor focus on load eg inputform.password + * @param string $meta Meta tags to be added to the header + * @param boolean $cache Should this page be cacheable? + * @param string $button HTML code for a button (usually for module editing) + * @param string $menu HTML code for a popup menu + * @param boolean $usexml use XML for this page + * @param string $bodytags This text will be included verbatim in the tag (useful for onload() etc) + * @param bool $return If true, return the visible elements of the header instead of echoing them. + * @return string|void If return=true then string else void + */ +function print_header($title='', $heading='', $navigation='', $focus='', + $meta='', $cache=true, $button=' ', $menu='', + $usexml=false, $bodytags='', $return=false) { + global $PAGE, $OUTPUT; + + $PAGE->set_title($title); + $PAGE->set_heading($heading); + $PAGE->set_cacheable($cache); + $PAGE->set_focuscontrol($focus); + if ($button == '') { + $button = ' '; + } + $PAGE->set_button($button); + + if ($navigation == 'home') { + $navigation = ''; + } + if (gettype($navigation) == 'string' && strlen($navigation) != 0 && $navigation != 'home') { + debugging("print_header() was sent a string as 3rd ($navigation) parameter. " + . "This is deprecated in favour of an array built by build_navigation(). Please upgrade your code.", DEBUG_DEVELOPER); + } + + // TODO $navigation + // TODO $menu + + if ($meta) { + throw new coding_exception('The $meta parameter to print_header is no longer supported. '. + 'You should be able to do weverything you want with $PAGE->requires and other such mechanisms.'); + } + if ($usexml) { + throw new coding_exception('The $usexml parameter to print_header is no longer supported.'); + } + if ($bodytags) { + throw new coding_exception('The $bodytags parameter to print_header is no longer supported.'); + } + + $output = $OUTPUT->header($navigation, $menu); + + if ($return) { + return $output; + } else { + echo $output; + } +} + +function print_footer($course = NULL, $usercourse = NULL, $return = false) { + global $PAGE, $OUTPUT; + // TODO check arguments. + if (is_string($course)) { + debugging("Magic values like 'home', 'empty' passed to print_footer no longer have any effect. " . + 'To achieve a similar effect, call $PAGE->set_generaltype before you call print_header.', DEBUG_DEVELOPER); + } else if (!empty($course->id) && $course->id != $PAGE->course->id) { + throw new coding_exception('The $course object you passed to print_footer does not match $PAGE->course.'); + } + if (!is_null($usercourse)) { + debugging('The second parameter ($usercourse) to print_footer is no longer supported. ' . + '(I did not think it was being used anywhere.)', DEBUG_DEVELOPER); + } + $output = $OUTPUT->footer(); + if ($return) { + return $output; + } else { + echo $output; + } +} \ No newline at end of file diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php index 2223f05b29..c1e1e4e5ef 100644 --- a/lib/dml/moodle_database.php +++ b/lib/dml/moodle_database.php @@ -400,7 +400,7 @@ abstract class moodle_database { $log->sqlparams = var_export((array)$this->last_params, true); $log->error = (int)$iserror; $log->info = $iserror ? $error : null; - $log->backtrace = print_backtrace($backtrace, true, true); + $log->backtrace = format_backtrace($backtrace, true); $log->exectime = $time; $log->timelogged = time(); $this->insert_record('log_queries', $log); diff --git a/lib/emptyfile.php b/lib/emptyfile.php new file mode 100644 index 0000000000..eca71850d1 --- /dev/null +++ b/lib/emptyfile.php @@ -0,0 +1,21 @@ +. + +/** + * This is an empty file. For those times when you want something you can request + * to get an empty response. + */ \ No newline at end of file diff --git a/lib/javascript-static.js b/lib/javascript-static.js index bd42ac72b0..27afefac73 100644 --- a/lib/javascript-static.js +++ b/lib/javascript-static.js @@ -1029,4 +1029,15 @@ function hide_item(itemid) { if (item) { item.style.display = "none"; } +} + +/** + * Tranfer keyboard focus to the HTML element with the given id, if it exists. + * @param controlid the control id. + */ +function focuscontrol(controlid) { + var control = document.getElementById(controlid); + if (control) { + control.focus(); + } } \ No newline at end of file diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 0fa87bbf8b..8e38f1fb6e 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -8483,36 +8483,62 @@ function moodle_request_shutdown() { } /** - * If new messages are waiting for the current user, then return - * Javascript code to create a popup window - * - * @global object - * @global object - * @return string Javascript code + * This function is called when output is started. This is a chance for Moodle core + * to check things like whether the messages popup should be shown. + */ +function output_starting_hook() { + global $CFG, $PAGE; + + // If maintenance mode is on, change the page header. + if (!empty($CFG->maintenance_enabled)) { + $PAGE->set_button('' . get_string('maintenancemode', 'admin') . + ' ' . $PAGE->button); + + $title = $PAGE->title; + if ($title) { + $title .= ' - '; + } + $PAGE->set_title($title . get_string('maintenancemode', 'admin')); + } + + // Show the messaging popup, if there are messages. + message_popup_window(); +} + +/** + * If new messages are waiting for the current user, then load the + * JavaScript required to pop up the messaging window. */ function message_popup_window() { global $USER, $DB, $PAGE; + if (defined('MESSAGE_WINDOW') || empty($CFG->messaging)) { + return; + } + + if (!isset($USER->id) || isguestuser()) { + return; + } + + if (!isset($USER->message_lastpopup)) { + $USER->message_lastpopup = 0; + } + $popuplimit = 30; // Minimum seconds between popups + if ((time() - $USER->message_lastpopup) <= $popuplimit) { /// It's been long enough + return; + } - if (!defined('MESSAGE_WINDOW')) { - if (isset($USER->id) and !isguestuser()) { - if (!isset($USER->message_lastpopup)) { - $USER->message_lastpopup = 0; - } - if ((time() - $USER->message_lastpopup) > $popuplimit) { /// It's been long enough - if (get_user_preferences('message_showmessagewindow', 1) == 1) { - if ($DB->count_records_select('message', 'useridto = ? AND timecreated > ?', array($USER->id, $USER->message_lastpopup))) { - $USER->message_lastpopup = time(); - $PAGE->requires->js_function_call('openpopup', array('/message/index.php', 'message', - 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0)); - } - } - } - } + if (!get_user_preferences('message_showmessagewindow', 1)) { + return; } - return ''; + if ($DB->count_records_select('message', 'useridto = ? AND timecreated > ?', array($USER->id, $USER->message_lastpopup))) { + $USER->message_lastpopup = time(); + $PAGE->requires->js_function_call('openpopup', array('/message/index.php', 'message', + 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0)); + } } /** diff --git a/lib/outputlib.php b/lib/outputlib.php index adb781e5c6..423d2e170c 100644 --- a/lib/outputlib.php +++ b/lib/outputlib.php @@ -28,6 +28,20 @@ */ +/** + * Set up a preliminary $OUTPUT. This will be changed later once the correct theme + * for this page is known. Must be called after $PAGE is setup. + */ +function setup_bootstrat_output() { + global $OUTPUT, $PAGE; + if (CLI_SCRIPT) { + $OUTPUT = new cli_core_renderer(new xhtml_container_stack(), $PAGE); + } else { + $OUTPUT = new moodle_core_renderer(new xhtml_container_stack(), $PAGE); + } +} + + /** * A renderer factory is just responsible for creating an appropriate renderer * for any given part of Moodle. @@ -87,6 +101,8 @@ abstract class renderer_factory_base implements renderer_factory { /** Used to cache renderers as they are created. */ protected $renderers = array(); + protected $opencontainers; + /** * Constructor. * @param object $theme the theme we are rendering for. @@ -95,6 +111,7 @@ abstract class renderer_factory_base implements renderer_factory { public function __construct($theme, $page) { $this->theme = $theme; $this->page = $page; + $this->opencontainers = new xhtml_container_stack(); } /* Implement the interface method. */ @@ -164,10 +181,10 @@ class standard_renderer_factory extends renderer_factory_base { /* Implement the subclass method. */ public function create_renderer($module) { if ($module == 'core') { - return new moodle_core_renderer($this->page->opencontainers); + return new moodle_core_renderer($this->opencontainers, $this->page); } else { $class = $this->standard_renderer_class_for_module($module); - return new $class($this->page->opencontainers, $this->get_renderer('core')); + return new $class($this->opencontainers, $this->get_renderer('core'), $this->page); } } } @@ -191,7 +208,7 @@ class custom_corners_renderer_factory extends standard_renderer_factory { */ public function __construct($theme, $page) { parent::__construct($theme, $page); - $this->renderers = array('core' => new custom_corners_core_renderer($this->page->opencontainers)); + $this->renderers = array('core' => new custom_corners_core_renderer($this->opencontainers, $this->page)); } } @@ -245,9 +262,9 @@ class theme_overridden_renderer_factory extends standard_renderer_factory { $classname = $prefix . $module . '_renderer'; if (class_exists($classname)) { if ($module == 'core') { - return new $classname($this->page->opencontainers); + return new $classname($this->opencontainers, $this->page); } else { - return new $classname($this->page->opencontainers, $this->get_renderer('core')); + return new $classname($this->opencontainers, $this->get_renderer('core'), $this->page); } } } @@ -325,7 +342,7 @@ class template_renderer_factory extends renderer_factory_base { // Create a template_renderer that copies the API of the standard renderer. $copiedclass = $this->standard_renderer_class_for_module($module); - return new template_renderer($copiedclass, $searchpaths, $this->page->opencontainers); + return new template_renderer($copiedclass, $searchpaths, $this->opencontainers, $this->page); } } @@ -343,14 +360,18 @@ class template_renderer_factory extends renderer_factory_base { */ class moodle_renderer_base { /** @var xhtml_container_stack the xhtml_container_stack to use. */ - protected $containerstack; + protected $opencontainers; + /** @var moodle_page the page we are rendering for. */ + protected $page; /** * Constructor - * @param $containerstack the xhtml_container_stack to use. + * @param $opencontainers the xhtml_container_stack to use. + * @param moodle_page $page the page we are doing output for. */ - public function __construct($containerstack) { - $this->containerstack = $containerstack; + public function __construct($opencontainers, $page) { + $this->opencontainers = $opencontainers; + $this->page = $page; } protected function output_tag($tagname, $attributes, $contents) { @@ -368,6 +389,7 @@ class moodle_renderer_base { } protected function output_attribute($name, $value) { + $value = trim($value); if ($value || is_numeric($value)) { // We want 0 to be output. return ' ' . $name . '="' . $value . '"'; } @@ -379,8 +401,11 @@ class moodle_renderer_base { } return $output; } - protected function output_class_attribute($classes) { - return $this->output_attribute('class', implode(' ', $classes)); + public static function prepare_classes($classes) { + if (is_array($classes)) { + return implode(' ', array_unique($classes)); + } + return $classes; } } @@ -418,10 +443,11 @@ class template_renderer extends moodle_renderer_base { * Constructor * @param string $copiedclass the name of a class whose API we should be copying. * @param $searchpaths a list of folders to search for templates in. - * @param $containerstack the xhtml_container_stack to use. + * @param $opencontainers the xhtml_container_stack to use. + * @param moodle_page $page the page we are doing output for. */ - public function __construct($copiedclass, $searchpaths, $containerstack) { - parent::__construct($containerstack); + public function __construct($copiedclass, $searchpaths, $opencontainers, $page) { + parent::__construct($opencontainers, $page); $this->copiedclass = new ReflectionClass($copiedclass); $this->searchpaths = $searchpaths; } @@ -485,7 +511,10 @@ class template_renderer extends moodle_renderer_base { // with the names of any variables being passed to the template. // Set up the global variables that the template may wish to access. - global $CFG, $PAGE, $THEME; + global $CFG, $THEME; + $PAGE = $this->page; // Also, make $PAGE and $OUTPUT point to us. + $OUTPUT = $this; + $COURSE = $this->page->course; // And the parameters from the function call. extract($_namedarguments); @@ -526,7 +555,7 @@ class template_renderer extends moodle_renderer_base { array_unshift($arguments, self::contentstoken); $html = $this->process_template($template, $arguments); list($start, $end) = explode(self::contentstoken, $html, 2); - $this->containerstack->push($template, $end); + $this->opencontainers->push($template, $end); return $start; } @@ -538,7 +567,7 @@ class template_renderer extends moodle_renderer_base { * @return string the HTML to be output. */ protected function process_end($template, $arguments) { - return $this->containerstack->pop($template); + return $this->opencontainers->pop($template); } /** @@ -571,7 +600,7 @@ class template_renderer extends moodle_renderer_base { */ class xhtml_container_stack { /** @var array stores the list of open containers. */ - protected $opencontainsers = array(); + protected $opencontainers = array(); /** * Push the close HTML for a recently opened container onto the stack. @@ -583,7 +612,7 @@ class xhtml_container_stack { $container = new stdClass; $container->type = $type; $container->closehtml = $closehtml; - array_push($this->opencontainsers, $container); + array_push($this->opencontainers, $container); } /** @@ -594,12 +623,12 @@ class xhtml_container_stack { * @return string the HTML requried to close the container. */ public function pop($type) { - if (empty($this->opencontainsers)) { + if (empty($this->opencontainers)) { debugging('There are no more open containers. This suggests there is a nesting problem.', DEBUG_DEVELOPER); return; } - $container = array_pop($this->opencontainsers); + $container = array_pop($this->opencontainers); if ($container->type != $type) { debugging('The type of container to be closed (' . $container->type . ') does not match the type of the next open container (' . $type . @@ -608,6 +637,14 @@ class xhtml_container_stack { return $container->closehtml; } + /** + * Return how many containers are currently open. + * @return integer how many containers are currently open. + */ + public function count() { + return count($this->opencontainers); + } + /** * Close all but the last open container. This is useful in places like error * handling, where you want to close all the open containers (apart from ) @@ -616,8 +653,8 @@ class xhtml_container_stack { */ public function pop_all_but_last() { $output = ''; - while (count($this->opencontainsers) > 1) { - $container = array_pop($this->opencontainsers); + while (count($this->opencontainers) > 1) { + $container = array_pop($this->opencontainers); $output .= $container->closehtml; } return $output; @@ -630,7 +667,7 @@ class xhtml_container_stack { * debug warning. After calling this method, the instance can no longer be used. */ public function discard() { - $this->opencontainsers = null; + $this->opencontainers = null; } /** @@ -638,13 +675,13 @@ class xhtml_container_stack { * containers have been closed, output the rest with a developer debug warning. */ public function __destruct() { - if (empty($this->opencontainsers)) { + if (empty($this->opencontainers)) { return; } debugging('Some containers were left open. This suggests there is a nesting problem.', DEBUG_DEVELOPER); echo $this->pop_all_but_last(); - $container = array_pop($this->opencontainsers); + $container = array_pop($this->opencontainers); echo $container->closehtml; } } @@ -658,6 +695,292 @@ class xhtml_container_stack { * @since Moodle 2.0 */ class moodle_core_renderer extends moodle_renderer_base { + const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%'; + const END_HTML_TOKEN = '%%ENDHTML%%'; + const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]'; + protected $contenttype; + + public function doctype() { + global $CFG; + + $doctype = '' . "\n"; + $this->contenttype = 'text/html; charset=utf-8'; + + if (empty($CFG->xmlstrictheaders)) { + return $doctype; + } + + // We want to serve the page with an XML content type, to force well-formedness errors to be reported. + $prolog = '' . "\n"; + if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) { + // Firefox and other browsers that can cope natively with XHTML. + $this->contenttype = 'application/xhtml+xml; charset=utf-8'; + + } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) { + // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet. + $this->contenttype = 'application/xml; charset=utf-8'; + $prolog .= 'httpswwwroot . '/lib/xhtml.xsl"?>' . "\n"; + + } else { + $prolog = ''; + } + + return $prolog . $doctype; + } + + public function htmlattributes() { + return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"'; + } + + public function standard_head_html() { + global $CFG, $THEME; + $output = ''; + $output .= '' . "\n"; + $output .= '' . "\n"; + if (!$this->page->cacheable) { + $output .= '' . "\n"; + $output .= '' . "\n"; + } + ob_start(); + include($CFG->javascript); + $output .= ob_get_contents(); + ob_end_clean(); + $output .= $this->page->requires->get_head_code(); + + foreach ($this->page->alternateversions as $type => $alt) { + $output .= $this->output_empty_tag('link', array('rel' => 'alternate', + 'type' => $type, 'title' => $alt->title, 'href' => $alt->url)); + } + + // Add the meta page from the themes if any were requested + // TODO kill this. + $PAGE = $this->page; + $metapage = ''; + if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) { + ob_start(); + include_once($CFG->dirroot.'/theme/standard/meta.php'); + $output .= ob_get_contents(); + ob_end_clean(); + } + if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) { + if (file_exists($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php')) { + ob_start(); + include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php'); + $output .= ob_get_contents(); + ob_end_clean(); + } + } + if (!isset($THEME->metainclude) || $THEME->metainclude) { + if (file_exists($CFG->dirroot.'/theme/'.current_theme().'/meta.php')) { + ob_start(); + include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php'); + $output .= ob_get_contents(); + ob_end_clean(); + } + } + + return $output; + } + + public function standard_top_of_body_html() { + return $this->page->requires->get_top_of_body_code(); + } + + public function standard_footer_html() { + $output = self::PERFORMANCE_INFO_TOKEN; + if (debugging()) { + $output .= '
'; + } + return $output; + } + + public function standard_end_of_body_html() { + echo self::END_HTML_TOKEN; + } + + public function login_info() { + global $USER; + return user_login_string($this->page->course, $USER); + } + + public function home_link() { + global $CFG, $SITE; + + if ($this->page->pagetype == 'site-index') { + // Special case for site home page - please do not remove + return ''; + + } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) { + // Special case for during install/upgrade. + return ''; + + } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) { + return ''; + + } else { + return ''; + } + } + + // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation + public function header($navigation = '', $menu='') { + global $USER, $CFG; + + output_starting_hook(); + $this->page->set_state(moodle_page::STATE_PRINTING_HEADER); + + // Add any stylesheets required using the horrible legacy mechanism. TODO kill this. + foreach ($CFG->stylesheets as $stylesheet) { + $this->page->requires->css($stylesheet, true); + } + + // Find the appropriate page template, based on $this->page->generaltype. + $templatefile = $this->find_page_template(); + if ($templatefile) { + // Render the template. + $template = $this->render_page_template($templatefile, $menu, $navigation); + } else { + // New style template not found, fall back to using header.html and footer.html. + $template = $this->handle_legacy_theme($navigation, $menu); + } + + // Slice the template output into header and footer. + $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN); + if ($cutpos === false) { + throw new coding_exception('Layout template ' . $templatefile . + ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".'); + } + $header = substr($template, 0, $cutpos); + $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN)); + + send_headers($this->contenttype, $this->page->cacheable); + $this->opencontainers->push('header/footer', $footer); + $this->page->set_state(moodle_page::STATE_IN_BODY); + return $header . $this->skip_link_target(); + } + + protected function find_page_template() { + global $THEME; + + // If this is a particular page type, look for a specific template. + $type = $this->page->generaltype; + if ($type != 'normal') { + $templatefile = $THEME->dir . '/layout-' . $type . '.php'; + if (is_readable($templatefile)) { + return $templatefile; + } + } + + // Otherwise look for the general template. + $templatefile = $THEME->dir . '/layout.php'; + if (is_readable($templatefile)) { + return $templatefile; + } + + return false; + } + + protected function render_page_template($templatefile, $menu, $navigation) { + global $CFG, $SITE, $THEME, $USER; + // Set some pretend globals from the properties of this class. + $OUTPUT = $this; + $PAGE = $this->page; + $COURSE = $this->page->course; + + ob_start(); + include($templatefile); + $template = ob_get_contents(); + ob_end_clean(); + return $template; + } + + protected function handle_legacy_theme($navigation, $menu) { + global $CFG, $SITE, $THEME, $USER; + // Set a pretend global from the properties of this class. + $COURSE = $this->page->course; + + // Set up local variables that header.html expects. + $direction = $this->htmlattributes(); + $title = $this->page->title; + $heading = $this->page->heading; + $focus = $this->page->focuscontrol; + $button = $this->page->button; + $pageid = $this->page->pagetype; + $pageclass = $this->page->bodyclasses; + $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"'; + $home = $this->page->generaltype == 'home'; + + $meta = $this->standard_head_html(); + // The next line is a nasty hack. having set $meta to standard_head_html, we have already + // got the contents of include($CFG->javascript). However, legacy themes are going to + // include($CFG->javascript) again. We want to make sure that when they do, nothing is output. + $CFG->javascript = $CFG->libdir . '/emptyfile.php'; + + // Set up local variables that footer.html expects. + $homelink = $this->home_link(); + $loggedinas = $this->login_info(); + $course = $this->page->course; + $performanceinfo = self::PERFORMANCE_INFO_TOKEN; + + if (!$menu && $navigation) { + $menu = $loggedinas; + } + + ob_start(); + include($THEME->dir . '/header.html'); + $this->page->requires->get_top_of_body_code(); + echo self::MAIN_CONTENT_TOKEN; + + $menu = str_replace('navmenu', 'navmenufooter', $menu); + include($THEME->dir . '/footer.html'); + + $output = ob_get_contents(); + ob_end_clean(); + + $output = str_replace('', self::END_HTML_TOKEN . '', $output); + + return $output; + } + + public function footer() { + $output = ''; + if ($this->opencontainers->count() != 1) { + debugging('Some HTML tags were opened in the body of the page but not closed.', DEBUG_DEVELOPER); + $output .= $this->opencontainers->pop_all_but_last(); + } + + $footer = $this->opencontainers->pop('header/footer'); + + // Provide some performance info if required + $performanceinfo = ''; + if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { + $perf = get_performance_info(); + if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) { + error_log("PERF: " . $perf['txt']); + } + if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) { + $performanceinfo = $perf['html']; + } + } + $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer); + + $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer); + + $this->page->set_state(moodle_page::STATE_DONE); + + return $output . $footer; + } + public function link_to_popup_window() { } @@ -756,6 +1079,138 @@ class moodle_core_renderer extends moodle_renderer_base { } return $this->output_tag('span', array('class' => 'error'), $message); } + + /** + * Do not call this function directly. + * + * To terminate the current script with a fatal error, call the {@link print_error} + * function, or throw an exception. Doing either of those things will then call this + * funciton to display the error, before terminating the exection. + * + * @param string $message + * @param string $moreinfourl + * @param string $link + * @param array $backtrace + * @param string $debuginfo + * @param bool $showerrordebugwarning + * @return string the HTML to output. + */ + public function fatal_error($message, $moreinfourl, $link, $backtrace, + $debuginfo = null, $showerrordebugwarning = false) { + + $output = ''; + + if ($this->opencontainers->count() == 0) { + // Header not yet printed + @header('HTTP/1.0 404 Not Found'); + print_header(get_string('error')); + } else { + $output .= $this->opencontainers->pop_all_but_last(); + } + + $message = '

' . $message . '

'. + '

' . + get_string('moreinformation') . '

'; + $output .= $this->box($message, 'errorbox'); + + if (debugging('', DEBUG_DEVELOPER)) { + if ($showerrordebugwarning) { + $output .= $this->notification('error() is a deprecated function. ' . + 'Please call print_error() instead of error()', 'notifytiny'); + } + if (!empty($debuginfo)) { + $output .= $this->notification($debuginfo, 'notifytiny'); + } + if (!empty($backtrace)) { + $output .= $this->notification('Stack trace: ' . + format_backtrace($backtrace, true), 'notifytiny'); + } + } + + if (!empty($link)) { + $output .= $this->continue_button($link); + } + + print_footer(); + + // Padding to encourage IE to display our error page, rather than its own. + $output .= str_repeat(' ', 512); + + return $output; + } + + /** + * Output a notification (that is, a status message about something that has + * just happened). + * + * @param string $message the message to print out + * @param string $classes normally 'notifyproblem' or 'notifysuccess'. + * @return string the HTML to output. + */ + public function notification($message, $classes = 'notifyproblem') { + return $this->output_tag('div', array('class' => + moodle_renderer_base::prepare_classes($classes)), clean_text($message)); + } + + /** + * Print a continue button that goes to a particular URL. + * + * @param string|moodle_url $link The url the button goes to. + * @return string the HTML to output. + */ + public function continue_button($link) { + if (!is_a($link, 'moodle_url')) { + $link = new moodle_url($link); + } + return $this->output_tag('div', array('class' => 'continuebutton'), + print_single_button($link->out(true), $link->params(), get_string('continue'), 'get', '', true)); + } + + /** + * Output the place a skip link goes to. + * @param $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call. + * @return string the HTML to output. + */ + public function skip_link_target($id = 'maincontent') { + return $this->output_tag('span', array('id' => $id), ''); + } + + public function heading($text, $level, $classes = 'main', $id = '') { + $level = (integer) $level; + if ($level < 1 or $level > 6) { + throw new coding_exception('Heading level must be an integer between 1 and 6.'); + } + return $this->output_tag('h' . $level, + array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text); + } + + public function box($contents, $classes = 'generalbox', $id = '') { + return $this->box_start($classes, $id) . $contents . $this->box_end(); + } + + public function box_start($classes = 'generalbox', $id = '') { + $this->opencontainers->push('box', $this->output_end_tag('div')); + return $this->output_start_tag('div', array('id' => $id, + 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes))); + } + + public function box_end() { + return $this->opencontainers->pop('box'); + } + + public function container($contents, $classes = '', $id = '') { + return $this->container_start($classes, $id) . $contents . $this->container_end(); + } + + public function container_start($classes = '', $id = '') { + $this->opencontainers->push('container', $this->output_end_tag('div')); + return $this->output_start_tag('div', array('id' => $id, + 'class' => moodle_renderer_base::prepare_classes($classes))); + } + + public function container_end() { + return $this->opencontainers->pop('container'); + } } @@ -838,7 +1293,7 @@ class moodle_html_component { /** * This class hold all the information required to describe a