.
/**
* Classes for rendering HTML output for Moodle.
*
* Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
* for an overview.
*
* @package moodlecore
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Simple base class for Moodle renderers.
*
* Tracks the xhtml_container_stack to use, which is passed in in the constructor.
*
* Also has methods to facilitate generating HTML output.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*/
class moodle_renderer_base {
/** @var xhtml_container_stack the xhtml_container_stack to use. */
protected $opencontainers;
/** @var moodle_page the page we are rendering for. */
protected $page;
/**
* Constructor
* @param moodle_page $page the page we are doing output for.
*/
public function __construct($page) {
$this->opencontainers = $page->opencontainers;
$this->page = $page;
}
/**
* Have we started output yet?
* @return boolean true if the header has been printed.
*/
public function has_started() {
return $this->page->state >= moodle_page::STATE_IN_BODY;
}
/**
* Outputs a tag with attributes and contents
* @param string $tagname The name of tag ('a', 'img', 'span' etc.)
* @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
* @param string $contents What goes between the opening and closing tags
* @return string HTML fragment
*/
protected function output_tag($tagname, $attributes, $contents) {
return $this->output_start_tag($tagname, $attributes) . $contents .
$this->output_end_tag($tagname);
}
/**
* Outputs an opening tag with attributes
* @param string $tagname The name of tag ('a', 'img', 'span' etc.)
* @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
* @return string HTML fragment
*/
protected function output_start_tag($tagname, $attributes) {
return '<' . $tagname . $this->output_attributes($attributes) . '>';
}
/**
* Outputs a closing tag
* @param string $tagname The name of tag ('a', 'img', 'span' etc.)
* @return string HTML fragment
*/
protected function output_end_tag($tagname) {
return '' . $tagname . '>';
}
/**
* Outputs an empty tag with attributes
* @param string $tagname The name of tag ('input', 'img', 'br' etc.)
* @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
* @return string HTML fragment
*/
protected function output_empty_tag($tagname, $attributes) {
return '<' . $tagname . $this->output_attributes($attributes) . ' />';
}
/**
* Outputs a HTML attribute and value
* @param string $name The name of the attribute ('src', 'href', 'class' etc.)
* @param string $value The value of the attribute. The value will be escaped with {@link s()}
* @return string HTML fragment
*/
protected function output_attribute($name, $value) {
if (is_array($value)) {
debugging("Passed an array for the HTML attribute $name", DEBUG_DEVELOPER);
}
$value = trim($value);
if ($value == HTML_ATTR_EMPTY) {
return ' ' . $name . '=""';
} else if ($value || is_numeric($value)) { // We want 0 to be output.
return ' ' . $name . '="' . s($value) . '"';
}
}
/**
* Outputs a list of HTML attributes and values
* @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
* The values will be escaped with {@link s()}
* @return string HTML fragment
*/
protected function output_attributes($attributes) {
if (empty($attributes)) {
$attributes = array();
}
$output = '';
foreach ($attributes as $name => $value) {
$output .= $this->output_attribute($name, $value);
}
return $output;
}
/**
* Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
* @param mixed $classes Space-separated string or array of classes
* @return string HTML class attribute value
*/
public static function prepare_classes($classes) {
if (is_array($classes)) {
return implode(' ', array_unique($classes));
}
return $classes;
}
/**
* Return the URL for an icon identified as in pre-Moodle 2.0 code.
*
* Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
* then old_icon_url('i/course'); will return the equivalent URL that is correct now.
*
* @param string $iconname the name of the icon.
* @return string the URL for that icon.
*/
public function old_icon_url($iconname) {
return $this->page->theme->old_icon_url($iconname);
}
/**
* Return the URL for an icon identified as in pre-Moodle 2.0 code.
*
* Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
* then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
*
* @param string $iconname the name of the icon.
* @param string $module the module the icon belongs to.
* @return string the URL for that icon.
*/
public function mod_icon_url($iconname, $module) {
return $this->page->theme->mod_icon_url($iconname, $module);
}
/**
* A helper function that takes a moodle_html_component subclass as param.
* If that component has an id attribute and an array of valid component_action objects,
* it sets up the appropriate event handlers.
*
* @param moodle_html_component $component
* @return void;
*/
protected function prepare_event_handlers(&$component) {
$actions = $component->get_actions();
if (!empty($actions) && is_array($actions) && $actions[0] instanceof component_action) {
foreach ($actions as $action) {
if (!empty($action->jsfunction)) {
$this->page->requires->event_handler($component->id, $action->event, $action->jsfunction, $action->jsfunctionargs);
}
}
}
}
/**
* Given a moodle_html_component with height and/or width set, translates them
* to appropriate CSS rules.
*
* @param moodle_html_component $component
* @return string CSS rules
*/
protected function prepare_legacy_width_and_height($component) {
$output = '';
if (!empty($component->height)) {
// We need a more intelligent way to handle these warnings. If $component->height have come from
// somewhere in deprecatedlib.php, then there is no point outputting a warning here.
// debugging('Explicit height given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER);
$output .= "height: {$component->height}px;";
}
if (!empty($component->width)) {
// debugging('Explicit width given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER);
$output .= "width: {$component->width}px;";
}
return $output;
}
}
/**
* This is the templated renderer which copies the API of another class, replacing
* all methods calls with instantiation of a template.
*
* When the method method_name is called, this class will search for a template
* called method_name.php in the folders in $searchpaths, taking the first one
* that it finds. Then it will set up variables for each of the arguments of that
* method, and render the template. This is implemented in the {@link __call()}
* PHP magic method.
*
* Methods like print_box_start and print_box_end are handles specially, and
* implemented in terms of the print_box.php method.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*/
class template_renderer extends moodle_renderer_base {
/** @var ReflectionClass information about the class whose API we are copying. */
protected $copiedclass;
/** @var array of places to search for templates. */
protected $searchpaths;
protected $rendererfactory;
/**
* Magic word used when breaking apart container templates to implement
* _start and _end methods.
*/
const CONTENTSTOKEN = '-@#-Contents-go-here-#@-';
/**
* Constructor
* @param string $copiedclass the name of a class whose API we should be copying.
* @param array $searchpaths a list of folders to search for templates in.
* @param moodle_page $page the page we are doing output for.
*/
public function __construct($copiedclass, $searchpaths, $page) {
parent::__construct($page);
$this->copiedclass = new ReflectionClass($copiedclass);
$this->searchpaths = $searchpaths;
}
/**
* PHP magic method implementation. Do not use this method directly.
* @param string $method The method to call
* @param array $arguments The arguments to pass to the method
* @return mixed The return value of the called method
*/
public function __call($method, $arguments) {
if (substr($method, -6) == '_start') {
return $this->process_start(substr($method, 0, -6), $arguments);
} else if (substr($method, -4) == '_end') {
return $this->process_end(substr($method, 0, -4), $arguments);
} else {
return $this->process_template($method, $arguments);
}
}
/**
* Render the template for a given method of the renderer class we are copying,
* using the arguments passed.
* @param string $method the method that was called.
* @param array $arguments the arguments that were passed to it.
* @return string the HTML to be output.
*/
protected function process_template($method, $arguments) {
if (!$this->copiedclass->hasMethod($method) ||
!$this->copiedclass->getMethod($method)->isPublic()) {
throw new coding_exception('Unknown method ' . $method);
}
// Find the template file for this method.
$template = $this->find_template($method);
// Use the reflection API to find out what variable names the arguments
// should be stored in, and fill in any missing ones with the defaults.
$namedarguments = array();
$expectedparams = $this->copiedclass->getMethod($method)->getParameters();
foreach ($expectedparams as $param) {
$paramname = $param->getName();
if (!empty($arguments)) {
$namedarguments[$paramname] = array_shift($arguments);
} else if ($param->isDefaultValueAvailable()) {
$namedarguments[$paramname] = $param->getDefaultValue();
} else {
throw new coding_exception('Missing required argument ' . $paramname);
}
}
// Actually render the template.
return $this->render_template($template, $namedarguments);
}
/**
* Actually do the work of rendering the template.
* @param string $_template the full path to the template file.
* @param array $_namedarguments an array variable name => value, the variables
* that should be available to the template.
* @return string the HTML to be output.
*/
protected function render_template($_template, $_namedarguments) {
// Note, we intentionally break the coding guidelines with regards to
// local variable names used in this function, so that they do not clash
// with the names of any variables being passed to the template.
global $CFG, $SITE, $THEME, $USER;
// The next lines are a bit tricky. The point is, here we are in a method
// of a renderer class, and this object may, or may not, be the same as
// the global $OUTPUT object. When rendering the template, we want to use
// this object. However, people writing Moodle code expect the current
// renderer to be called $OUTPUT, not $this, so define a variable called
// $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
$OUTPUT = $this;
$PAGE = $this->page;
$COURSE = $this->page->course;
// And the parameters from the function call.
extract($_namedarguments);
// Include the template, capturing the output.
ob_start();
include($_template);
$_result = ob_get_contents();
ob_end_clean();
return $_result;
}
/**
* Searches the folders in {@link $searchpaths} to try to find a template for
* this method name. Throws an exception if one cannot be found.
* @param string $method the method name.
* @return string the full path of the template to use.
*/
protected function find_template($method) {
foreach ($this->searchpaths as $path) {
$filename = $path . '/' . $method . '.php';
if (file_exists($filename)) {
return $filename;
}
}
throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method);
}
/**
* Handle methods like print_box_start by using the print_box template,
* splitting the result, pushing the end onto the stack, then returning the start.
* @param string $method the method that was called, with _start stripped off.
* @param array $arguments the arguments that were passed to it.
* @return string the HTML to be output.
*/
protected function process_start($method, $arguments) {
array_unshift($arguments, self::CONTENTSTOKEN);
$html = $this->process_template($method, $arguments);
list($start, $end) = explode(self::CONTENTSTOKEN, $html, 2);
$this->opencontainers->push($method, $end);
return $start;
}
/**
* Handle methods like print_box_end, we just need to pop the end HTML from
* the stack.
* @param string $method the method that was called, with _end stripped off.
* @param array $arguments not used. Assumed to be irrelevant.
* @return string the HTML to be output.
*/
protected function process_end($method, $arguments) {
return $this->opencontainers->pop($method);
}
/**
* @return array the list of paths where this class searches for templates.
*/
public function get_search_paths() {
return $this->searchpaths;
}
/**
* @return string the name of the class whose API we are copying.
*/
public function get_copied_class() {
return $this->copiedclass->getName();
}
}
/**
* The standard implementation of the moodle_core_renderer interface.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*/
class moodle_core_renderer extends moodle_renderer_base {
/** @var string used in {@link header()}. */
const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
/** @var string used in {@link header()}. */
const END_HTML_TOKEN = '%%ENDHTML%%';
/** @var string used in {@link header()}. */
const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
/** @var string used to pass information from {@link doctype()} to {@link standard_head_html()}. */
protected $contenttype;
/** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */
protected $metarefreshtag = '';
/**
* Get the DOCTYPE declaration that should be used with this page. Designed to
* be called in theme layout.php files.
* @return string the DOCTYPE declaration (and any XML prologue) that should be used.
*/
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;
}
/**
* The attributes that should be added to the tag. Designed to
* be called in theme layout.php files.
* @return string HTML fragment.
*/
public function htmlattributes() {
return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
}
/**
* The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
* that should be included in the
tag. Designed to be called in theme
* layout.php files.
* @return string HTML fragment.
*/
public function standard_head_html() {
global $CFG;
$output = '';
$output .= '' . "\n";
$output .= '' . "\n";
if (!$this->page->cacheable) {
$output .= '' . "\n";
$output .= '' . "\n";
}
// This is only set by the {@link redirect()} method
$output .= $this->metarefreshtag;
// Check if a periodic refresh delay has been set and make sure we arn't
// already meta refreshing
if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
$output .= '';
}
$this->page->requires->js('lib/javascript-static.js')->in_head();
$this->page->requires->js('lib/javascript-deprecated.js')->in_head();
$this->page->requires->js('lib/javascript-mod.php')->in_head();
$this->page->requires->js('lib/overlib/overlib.js')->in_head();
$this->page->requires->js('lib/overlib/overlib_cssstyle.js')->in_head();
$this->page->requires->js('lib/cookies.js')->in_head();
$this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
$focus = $this->page->focuscontrol;
if (!empty($focus)) {
if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
// This is a horrifically bad way to handle focus but it is passed in
// through messy formslib::moodleform
$this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
} else if (strpos($focus, '.')!==false) {
// Old style of focus, bad way to do it
debugging('This code is using the old style focus event, Please update this code to focus on an element id or the moodleform focus method.', DEBUG_DEVELOPER);
$this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
} else {
// Focus element with given id
$this->page->requires->js_function_call('focuscontrol', array($focus));
}
}
// Add the meta tags from the themes if any were requested.
$output .= $this->page->theme->get_meta_tags($this->page);
// Get any HTML from the page_requirements_manager.
$output .= $this->page->requires->get_head_code();
// List alternate versions.
foreach ($this->page->alternateversions as $type => $alt) {
$output .= $this->output_empty_tag('link', array('rel' => 'alternate',
'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
}
return $output;
}
/**
* The standard tags (typically skip links) that should be output just inside
* the start of the tag. Designed to be called in theme layout.php files.
* @return string HTML fragment.
*/
public function standard_top_of_body_html() {
return $this->page->requires->get_top_of_body_code();
}
/**
* The standard tags (typically performance information and validation links,
* if we are in developer debug mode) that should be output in the footer area
* of the page. Designed to be called in theme layout.php files.
* @return string HTML fragment.
*/
public function standard_footer_html() {
global $CFG;
// This function is normally called from a layout.php file in {@link header()}
// but some of the content won't be known until later, so we return a placeholder
// for now. This will be replaced with the real content in {@link footer()}.
$output = self::PERFORMANCE_INFO_TOKEN;
if (!empty($CFG->debugpageinfo)) {
$output .= '
This page is: ' . $this->page->debug_summary() . '
';
}
if (!empty($CFG->debugvalidators)) {
$output .= '
';
}
return $output;
}
/**
* The standard tags (typically script tags that are not needed earlier) that
* should be output after everything else, . Designed to be called in theme layout.php files.
* @return string HTML fragment.
*/
public function standard_end_of_body_html() {
// This function is normally called from a layout.php file in {@link header()}
// but some of the content won't be known until later, so we return a placeholder
// for now. This will be replaced with the real content in {@link footer()}.
echo self::END_HTML_TOKEN;
}
/**
* Return the standard string that says whether you are logged in (and switched
* roles/logged in as another user).
* @return string HTML fragment.
*/
public function login_info() {
global $USER;
return user_login_string($this->page->course, $USER);
}
/**
* Return the 'back' link that normally appears in the footer.
* @return string HTML fragment.
*/
public function home_link() {
global $CFG, $SITE;
if ($this->page->pagetype == 'site-index') {
// Special case for site home page - please do not remove
return '
';
}
}
/**
* Redirects the user by any means possible given the current state
*
* This function should not be called directly, it should always be called using
* the redirect function in lib/weblib.php
*
* The redirect function should really only be called before page output has started
* however it will allow itself to be called during the state STATE_IN_BODY
*
* @param string $encodedurl The URL to send to encoded if required
* @param string $message The message to display to the user if any
* @param int $delay The delay before redirecting a user, if $message has been
* set this is a requirement and defaults to 3, set to 0 no delay
* @param boolean $debugdisableredirect this redirect has been disabled for
* debugging purposes. Display a message that explains, and don't
* trigger the redirect.
* @return string The HTML to display to the user before dying, may contain
* meta refresh, javascript refresh, and may have set header redirects
*/
public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
global $CFG;
$url = str_replace('&', '&', $encodedurl);
switch ($this->page->state) {
case moodle_page::STATE_BEFORE_HEADER :
// No output yet it is safe to delivery the full arsenal of redirect methods
if (!$debugdisableredirect) {
// Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
$this->metarefreshtag = ''."\n";
$this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3);
}
$output = $this->header();
break;
case moodle_page::STATE_PRINTING_HEADER :
// We should hopefully never get here
throw new coding_exception('You cannot redirect while printing the page header');
break;
case moodle_page::STATE_IN_BODY :
// We really shouldn't be here but we can deal with this
debugging("You should really redirect before you start page output");
if (!$debugdisableredirect) {
$this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay);
}
$output = $this->opencontainers->pop_all_but_last();
break;
case moodle_page::STATE_DONE :
// Too late to be calling redirect now
throw new coding_exception('You cannot redirect after the entire page has been generated');
break;
}
$output .= $this->notification($message, 'redirectmessage');
$output .= ''. get_string('continue') .'';
if ($debugdisableredirect) {
$output .= '
Error output, so disabling automatic redirect.
';
}
$output .= $this->footer();
return $output;
}
/**
* Start output by sending the HTTP headers, and printing the HTML
* and the start of the .
*
* To control what is printed, you should set properties on $PAGE. If you
* are familiar with the old {@link print_header()} function from Moodle 1.9
* you will find that there are properties on $PAGE that correspond to most
* of the old parameters to could be passed to print_header.
*
* Not that, in due course, the remaining $navigation, $menu parameters here
* will be replaced by more properties of $PAGE, but that is still to do.
*
* @return string HTML that you must output this, preferably immediately.
*/
public function header() {
// TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
global $USER, $CFG;
$this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
// Find the appropriate page template, based on $this->page->generaltype.
$templatefile = $this->page->theme->template_for_page($this->page->generaltype);
if ($templatefile) {
// Render the template.
$template = $this->render_page_template($templatefile);
} else {
// New style template not found, fall back to using header.html and footer.html.
$template = $this->handle_legacy_theme();
}
// 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));
if (empty($this->contenttype)) {
debugging('The layout template did not call $OUTPUT->doctype()');
$this->doctype();
}
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();
}
/**
* Renders and outputs the page template.
* @param string $templatefile The name of the template's file
* @param array $menu The menu that will be used in the included file
* @param array $navigation The navigation that will be used in the included file
* @return string HTML code
*/
protected function render_page_template($templatefile) {
global $CFG, $SITE, $THEME, $USER;
// The next lines are a bit tricky. The point is, here we are in a method
// of a renderer class, and this object may, or may not, be the same as
// the global $OUTPUT object. When rendering the template, we want to use
// this object. However, people writing Moodle code expect the current
// renderer to be called $OUTPUT, not $this, so define a variable called
// $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
$OUTPUT = $this;
$PAGE = $this->page;
$COURSE = $this->page->course;
// Required for legacy template uses
$navigation = $this->has_navbar();
ob_start();
include($templatefile);
$template = ob_get_contents();
ob_end_clean();
return $template;
}
/**
* Renders and outputs a legacy template.
* @param array $navigation The navigation that will be used in the included file
* @param array $menu The menu that will be used in the included file
* @return string HTML code
*/
protected function handle_legacy_theme() {
global $CFG, $SITE, $USER;
// Set a pretend global from the properties of this class.
// See the comment in render_page_template for a fuller explanation.
$COURSE = $this->page->course;
$THEME = $this->page->theme;
$OUTPUT = $this;
// 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';
$menu = $this->page->headingmenu;
$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;
$navigation = $this->has_navbar();
if (!$menu && $navigation) {
$menu = $loggedinas;
}
if (!empty($this->page->theme->layouttable)) {
$lt = $this->page->theme->layouttable;
} else {
$lt = array('left', 'middle', 'right');
}
if (!empty($this->page->theme->block_l_max_width)) {
$preferredwidthleft = $this->page->theme->block_l_max_width;
} else {
$preferredwidthleft = 210;
}
if (!empty($this->page->theme->block_r_max_width)) {
$preferredwidthright = $this->page->theme->block_r_max_width;
} else {
$preferredwidthright = 210;
}
ob_start();
include($this->page->theme->dir . '/header.html');
echo '
';
foreach ($lt as $column) {
if ($column == 'left' && $this->page->blocks->region_has_content(BLOCK_POS_LEFT, $this)) {
echo '
';
$menu = str_replace('navmenu', 'navmenufooter', $menu);
include($THEME->dir . '/footer.html');
$output = ob_get_contents();
ob_end_clean();
// Put in the start of body code. Bit of a hack, put it in before the first
//