From: tjhunt <tjhunt>
Date: Thu, 2 Jul 2009 08:49:25 +0000 (+0000)
Subject: output: MDL-19690 icon_finder classes and $OUTPUT->mod/old_icon_url
X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=ebebf55cadeb187b1ad912706f0c382bbfa2b36b;p=moodle.git

output: MDL-19690 icon_finder classes and $OUTPUT->mod/old_icon_url

This is ready to replace $CFG->pixpath and $CFG->modpixpath soon.
---

diff --git a/lib/outputlib.php b/lib/outputlib.php
index 61f1b303ae..3b4a933670 100644
--- a/lib/outputlib.php
+++ b/lib/outputlib.php
@@ -35,8 +35,8 @@
  * Which renderer factory to use is chose by the current theme, and an instance
  * if created automatically when the theme is set up.
  *
- * A renderer factory must also have a constructor that takes a theme object and
- * a moodle_page object. (See {@link renderer_factory_base::__construct} for an example.)
+ * A renderer factory must also have a constructor that takes a theme_config object.
+ * (See {@link renderer_factory_base::__construct} for an example.)
  *
  * @copyright 2009 Tim Hunt
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -57,1647 +57,1875 @@ interface renderer_factory {
      * 'Duck typing'. For a tricky example, see {@link template_renderer} below.
      * renderer ob
      *
-     * @param $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
+     * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
+     * @param moodle_page $page the page the renderer is outputting content for.
      * @return object an object implementing the requested renderer interface.
      */
-    public function get_renderer($module, $page);
+    public function get_renderer($component, $page);
 }
 
 
 /**
- * This is a base class to help you implement the renderer_factory interface.
+ * An icon finder is responsible for working out the correct URL for an icon.
  *
- * It keeps a cache of renderers that have been constructed, so you only need
- * to construct each one once in you subclass.
+ * A icon finder must also have a constructor that takes a theme object.
+ * (See {@link standard_icon_finder::__construct} for an example.)
  *
- * It also has a method to get the name of, and include the renderer.php with
- * the definition of, the standard renderer class for a given module.
+ * Note that we are planning to change the Moodle icon naming convention before
+ * the Moodle 2.0 relase. Therefore, this API will probably change.
  *
  * @copyright 2009 Tim Hunt
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @since     Moodle 2.0
  */
-abstract class renderer_factory_base implements renderer_factory {
-    /** @var theme_config the theme we belong to. */
-    protected $theme;
-
+interface icon_finder {
     /**
-     * Constructor.
-     * @param theme_config $theme the theme we belong to.
+     * Return the URL for an icon indentifed 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 $iconname the name of the icon.
+     * @return string the URL for that icon.
      */
-    public function __construct($theme) {
-        $this->theme = $theme;
-    }
+    public function old_icon_url($iconname);
+
     /**
-     * For a given module name, return the name of the standard renderer class
-     * that defines the renderer interface for that module.
+     * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
      *
-     * Also, if it exists, include the renderer.php file for that module, so
-     * the class definition of the default renderer has been loaded.
+     * 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 $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
-     * @return string the name of the standard renderer class for that module.
+     * @param $iconname the name of the icon.
+     * @param $module the module the icon belongs to.
+     * @return string the URL for that icon.
      */
-    protected function standard_renderer_class_for_module($module) {
-        $pluginrenderer = get_plugin_dir($module) . '/renderer.php';
-        if (file_exists($pluginrenderer)) {
-            include_once($pluginrenderer);
-        }
-        $class = 'moodle_' . $module . '_renderer';
-        if (!class_exists($class)) {
-            throw new coding_exception('Request for an unknown renderer class ' . $class);
-        }
-        return $class;
-    }
+    public function mod_icon_url($iconname, $module);
 }
 
 
 /**
- * This is the default renderer factory for Moodle. It simply returns an instance
- * of the appropriate standard renderer class.
+ *This class represents the configuration variables of a Moodle theme.
+ *
+ * Normally, to create an instance of this class, you should use the
+ * {@link theme_config::load()} factory method to load a themes config.php file.
  *
  * @copyright 2009 Tim Hunt
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @since     Moodle 2.0
  */
-class standard_renderer_factory extends renderer_factory_base {
-    /* Implement the subclass method. */
-    public function get_renderer($module, $page) {
-        if ($module == 'core') {
-            return new moodle_core_renderer($page);
-        } else {
-            $class = $this->standard_renderer_class_for_module($module);
-            return new $class($page, $this->get_renderer('core', $page));
-        }
-    }
-}
+class theme_config {
+    /**
+     * @var array The names of all the stylesheets from this theme that you would
+     * like included, in order.
+     */
+    public $sheets = array('styles_layout', 'styles_fonts', 'styles_color');
 
+    public $standardsheets = true;  
 
-/**
- * This is a slight variation on the standard_renderer_factory used by CLI scripts.
- *
- * @copyright 2009 Tim Hunt
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since     Moodle 2.0
- */
-class cli_renderer_factory extends standard_renderer_factory {
-    /* Implement the subclass method. */
-    public function get_renderer($module, $page) {
-        if ($module == 'core') {
-            return new cli_core_renderer($page);
-        } else {
-            parent::get_renderer($module, $page);
-        }
-    }
-}
+/// This variable can be set to an array containing
+/// filenames from the *STANDARD* theme.  If the 
+/// array exists, it will be used to choose the 
+/// files to include in the standard style sheet.
+/// When false, then no files are used.
+/// When true or NON-EXISTENT, then ALL standard files are used.
+/// This parameter can be used, for example, to prevent 
+/// having to override too many classes.
+/// Note that the trailing .css should not be included
+/// eg $THEME->standardsheets = array('styles_layout','styles_fonts','styles_color');
+////////////////////////////////////////////////////////////////////////////////
 
 
-/**
- * This is renderer factory allows themes to override the standard renderers using
- * php code.
- *
- * It will load any code from theme/mytheme/renderers.php and
- * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for
- * a renderer for 'component', it will create a mytheme_component_renderer or a
- * parenttheme_component_renderer, instead of a moodle_component_renderer,
- * if either of those classes exist.
- *
- * This generates the slightly different HTML that the custom_corners theme expects.
- *
- * @copyright 2009 Tim Hunt
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since     Moodle 2.0
- */
-class theme_overridden_renderer_factory extends standard_renderer_factory {
-    protected $prefixes = array();
+    public $parent = null;  
 
-    /**
-     * Constructor.
-     * @param object $theme the theme we are rendering for.
-     * @param moodle_page $page the page we are doing output for.
-     */
-    public function __construct($theme) {
-        global $CFG;
-        parent::__construct($theme);
+/// This variable can be set to the name of a parent theme
+/// which you want to have included before the current theme.
+/// This can make it easy to make modifications to another 
+/// theme without having to actually change the files
+/// If this variable is empty or false then a parent theme 
+/// is not used.
+////////////////////////////////////////////////////////////////////////////////
 
-        // Initialise $this->prefixes.
-        $renderersfile = $theme->dir . '/renderers.php';
-        if (is_readable($renderersfile)) {
-            include_once($renderersfile);
-            $this->prefixes[] = $theme->name . '_';
-        }
-        if (!empty($theme->parent)) {
-            $renderersfile = $CFG->themedir .'/'. $theme->parent . '/renderers.php';
-            if (is_readable($renderersfile)) {
-                include_once($renderersfile);
-                $this->prefixes[] = $theme->parent . '_';
-            }
-        }
-    }
 
-    /* Implement the subclass method. */
-    public function get_renderer($module, $page) {
-        foreach ($this->prefixes as $prefix) {
-            $classname = $prefix . $module . '_renderer';
-            if (class_exists($classname)) {
-                if ($module == 'core') {
-                    return new $classname($page);
-                } else {
-                    return new $classname($page, $this->get_renderer('core', $page));
-                }
-            }
-        }
-        return parent::get_renderer($module, $page);
-    }
-}
+    public $parentsheets = false;  
 
+/// This variable can be set to an array containing
+/// filenames from a chosen *PARENT* theme.  If the 
+/// array exists, it will be used to choose the 
+/// files to include in the standard style sheet.
+/// When false, then no files are used.
+/// When true or NON-EXISTENT, then ALL standard files are used.
+/// This parameter can be used, for example, to prevent 
+/// having to override too many classes.
+/// Note that the trailing .css should not be included
+/// eg $THEME->parentsheets = array('styles_layout','styles_fonts','styles_color');
+////////////////////////////////////////////////////////////////////////////////
 
-/**
- * This is renderer factory that allows you to create templated themes.
- *
- * This should be considered an experimental proof of concept. In particular,
- * the performance is probably not very good. Do not try to use in on a busy site
- * without doing careful load testing first!
- *
- * This renderer factory returns instances of {@link template_renderer} class
- * which which implement the corresponding renderer interface in terms of
- * templates. To use this your theme must have a templates folder inside it.
- * Then suppose the method moodle_core_renderer::greeting($name = 'world');
- * exists. Then, a call to $OUTPUT->greeting() will cause the template
- * /theme/yourtheme/templates/core/greeting.php to be rendered, with the variable
- * $name available. The greeting.php template might contain
- *
- * <pre>
- * <h1>Hello <?php echo $name ?>!</h1>
- * </pre>
- *
- * @copyright 2009 Tim Hunt
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since     Moodle 2.0
- */
-class template_renderer_factory extends renderer_factory_base {
-    /**
-     * An array of paths of where to search for templates. Normally this theme,
-     * the parent theme then the standardtemplate theme. (If some of these do
-     * not exist, or are the same as each other, then the list will be shorter.
-     */
-    protected $searchpaths = array();
 
-    /**
-     * Constructor.
-     * @param object $theme the theme we are rendering for.
-     * @param moodle_page $page the page we are doing output for.
-     */
-    public function __construct($theme) {
-        global $CFG;
-        parent::__construct($theme);
+    public $modsheets = true;  
 
-        // Initialise $this->searchpaths.
-        if ($theme->name != 'standardtemplate') {
-            $templatesdir = $theme->dir . '/templates';
-            if (is_dir($templatesdir)) {
-                $this->searchpaths[] = $templatesdir;
-            }
-        }
-        if (!empty($theme->parent)) {
-            $templatesdir = $CFG->themedir .'/'. $theme->parent . '/templates';
-            if (is_dir($templatesdir)) {
-                $this->searchpaths[] = $templatesdir;
-            }
-        }
-        $this->searchpaths[] = $CFG->themedir .'/standardtemplate/templates';
-    }
+/// When this is enabled, then this theme will search for 
+/// files named "styles.php" inside all Activity modules and 
+/// include them.   This allows modules to provide some basic 
+/// layouts so they work out of the box.
+/// It is HIGHLY recommended to leave this enabled.
 
-    /* Implement the subclass method. */
-    public function get_renderer($module, $page) {
-        // Refine the list of search paths for this module.
-        $searchpaths = array();
-        foreach ($this->searchpaths as $rootpath) {
-            $path = $rootpath . '/' . $module;
-            if (is_dir($path)) {
-                $searchpaths[] = $path;
-            }
-        }
 
-        // 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, $page);
-    }
-}
+    public $blocksheets = true;  
 
+/// When this is enabled, then this theme will search for 
+/// files named "styles.php" inside all Block modules and 
+/// include them.   This allows Blocks to provide some basic 
+/// layouts so they work out of the box.
+/// It is HIGHLY recommended to leave this enabled.
 
-/**
- * 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 $opencontainers the xhtml_container_stack to use.
-     * @param moodle_page $page the page we are doing output for.
-     */
-    public function __construct($page) {
-        $this->opencontainers = $page->opencontainers;
-        $this->page = $page;
-    }
+    public $langsheets = false;  
 
-    /**
-     * 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;
-    }
+/// By setting this to true, then this theme will search for 
+/// a file named "styles.php" inside the current language
+/// directory.  This allows different languages to provide 
+/// different styles.
 
-    protected function output_tag($tagname, $attributes, $contents) {
-        return $this->output_start_tag($tagname, $attributes) . $contents .
-                $this->output_end_tag($tagname);
-    }
-    protected function output_start_tag($tagname, $attributes) {
-        return '<' . $tagname . $this->output_attributes($attributes) . '>';
-    }
-    protected function output_end_tag($tagname) {
-        return '</' . $tagname . '>';
-    }
-    protected function output_empty_tag($tagname, $attributes) {
-        return '<' . $tagname . $this->output_attributes($attributes) . ' />';
-    }
 
-    protected function output_attribute($name, $value) {
-        $value = trim($value);
-        if ($value || is_numeric($value)) { // We want 0 to be output.
-            return ' ' . $name . '="' . $value . '"';
-        }
-    }
-    protected function output_attributes($attributes) {
-        if (empty($attributes)) {
-            $attributes = array();
-        }
-        $output = '';
-        foreach ($attributes as $name => $value) {
-            $output .= $this->output_attribute($name, $value);
-        }
-        return $output;
-    }
-    public static function prepare_classes($classes) {
-        if (is_array($classes)) {
-            return implode(' ', array_unique($classes));
-        }
-        return $classes;
-    }
-}
+    public $courseformatsheets = true;
 
+/// When this is enabled, this theme will search for files 
+/// named "styles.php" inside all course formats and 
+/// include them.  This allows course formats to provide 
+/// their own default styles.
 
-/**
- * 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-#@-';
+    public $metainclude = false;
 
-    /**
-     * 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 $opencontainers the xhtml_container_stack to use.
-     * @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;
-    }
+/// When this is enabled (or not set!) then Moodle will try 
+/// to include a file meta.php from this theme into the 
+/// <head></head> part of the page.
 
-    /* PHP magic method implementation. */
-    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);
-        }
+    public $standardmetainclude = true;
 
-        // 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);
-            }
-        }
+/// When this is enabled (or not set!) then Moodle will try 
+/// to include a file meta.php from the standard theme into the 
+/// <head></head> part of the page.
 
-        // Actually render the template.
-        return $this->render_template($template, $namedarguments);
-    }
 
-    /**
-     * Actually do the work of rendering the template.
-     * @param $_template the full path to the template file.
-     * @param $_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.
+    public $parentmetainclude = false;
 
-        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 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
-        // rederer 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;
+/// When this is enabled (or not set!) then Moodle will try 
+/// to include a file meta.php from the parent theme into the 
+/// <head></head> part of the page.
 
-        // 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();
+    public $navmenuwidth = 50;
 
-        return $_result;
-    }
+/// You can use this to control the cutoff point for strings 
+/// in the navmenus (list of activities in popup menu etc)
+/// Default is 50 characters wide.
 
-    /**
-     * 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, pusing 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($template, $arguments) {
-        array_unshift($arguments, self::contentstoken);
-        $html = $this->process_template($template, $arguments);
-        list($start, $end) = explode(self::contentstoken, $html, 2);
-        $this->opencontainers->push($template, $end);
-        return $start;
-    }
+    public $makenavmenulist = false;
 
-    /**
-     * 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($template, $arguments) {
-        return $this->opencontainers->pop($template);
-    }
+/// By setting this to true, then you will have access to a
+/// new variable in your header.html and footer.html called
+/// $navmenulist ... this contains a simple XHTML menu of 
+/// all activities in the current course, mostly useful for 
+/// creating popup navigation menus and so on.
 
-    /**
-     * @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();
-    }
-}
 
+    public $resource_mp3player_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&font=Arial&fontColour=3333FF&buffer=10&waitForPlay=no&autoPlay=yes';
 
-/**
- * This class keeps track of which HTML tags are currently open.
- *
- * This makes it much easier to always generate well formed XHTML output, even
- * if execution terminates abruptly. Any time you output some opening HTML
- * without the matching closing HTML, you should push the neccessary close tags
- * onto the stack.
- *
- * @copyright 2009 Tim Hunt
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since     Moodle 2.0
- */
-class xhtml_container_stack {
-    /** @var array stores the list of open containers. */
-    protected $opencontainers = array();
+/// With this you can control the colours of the "big" MP3 player 
+/// that is used for MP3 resources.
+
+
+    public $filter_mediaplugin_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&waitForPlay=yes';
+
+/// ...And this controls the small embedded player
+
+
+    public $custompix = false;
+
+/// If true, then this theme must have a "pix" 
+/// subdirectory that contains copies of all 
+/// files from the moodle/pix directory, plus a
+/// "pix/mod" directory containing all the icons 
+/// for all the activity modules.
+
+
+///$THEME->rarrow = '&#x25BA;' //OR '&rarr;';
+///$THEME->larrow = '&#x25C4;' //OR '&larr;';
+///$CFG->block_search_button = link_arrow_right(get_string('search'), $url='', $accesshide=true);
+///
+/// Accessibility: Right and left arrow-like characters are
+/// used in the breadcrumb trail, course navigation menu 
+/// (previous/next activity), calendar, and search forum block.
+///
+/// If the theme does not set characters, appropriate defaults
+/// are set by (lib/weblib.php:check_theme_arrows). The suggestions
+/// above are 'silent' in a screen-reader like JAWS. Please DO NOT
+/// use &lt; &gt; &raquo; - these are confusing for blind users.
+////////////////////////////////////////////////////////////////////////////////
+
+
+    public $blockregions = array('side-pre', 'side-post');
+    public $defaultblockregion = 'side-post';
+/// Areas where blocks may appear on any page that uses this theme. For each
+/// region you list in $THEME->blockregions you must call blocks_print_group
+/// with that region id somewhere in header.html or footer.html.
+/// defaultblockregion is the region where new blocks will be added, and
+/// where any blocks in unrecognised regions will be shown. (Suppose someone
+/// added a block when anther theme was selected).
+////////////////////////////////////////////////////////////////////////////////
+
+    /** @var string the name of this theme. Set automatically. */
+    public $name;
+    /** @var string the folder where this themes fiels are stored. $CFG->themedir . '/' . $this->name */
+    public $dir;
+
+    /** @var string Name of the renderer factory class to use. */
+    public $rendererfactory = 'standard_renderer_factory';
+    /** @var renderer_factory Instance of the renderer_factory class. */
+    protected $rf = null;
+
+    /** @var string Name of the icon finder class to use. */
+    public $iconfinder = 'pix_icon_finder';
+    /** @var renderer_factory Instance of the renderer_factory class. */
+    protected $if = null;
 
     /**
-     * Push the close HTML for a recently opened container onto the stack.
-     * @param string $type The type of container. This is checked when {@link pop()}
-     *      is called and must match, otherwise a developer debug warning is output.
-     * @param string $closehtml The HTML required to close the container.
+     * If you want to do custom processing on the CSS before it is output (for
+     * example, to replace certain variable names with particular values) you can
+     * give the name of a function here.
+     *
+     * There are two functions avaiable that you may wish to use (defined in lib/outputlib.php):
+     *   output_css_replacing_constants
+     *   output_css_for_css_edit
+     * If you wish to write your own function, use those two as examples, and it
+     * should be clear what you have to do.
+     *
+     * @var string the name of a function.
      */
-    public function push($type, $closehtml) {
-        $container = new stdClass;
-        $container->type = $type;
-        $container->closehtml = $closehtml;
-        array_push($this->opencontainers, $container);
-    }
+    public $customcssoutputfunction = null;
 
     /**
-     * Pop the HTML for the next closing container from the stack. The $type
-     * must match the type passed when the container was opened, otherwise a
-     * warning will be output.
-     * @param string $type The type of container.
-     * @return string the HTML requried to close the container.
+     * Load the config.php file for a particular theme, and return an instance
+     * of this class. (That is, this is a factory method.)
+     *
+     * @param string $themename the name of the theme.
+     * @return theme_config an instance of this class.
      */
-    public function pop($type) {
-        if (empty($this->opencontainers)) {
-            debugging('There are no more open containers. This suggests there is a nesting problem.', DEBUG_DEVELOPER);
-            return;
-        }
+    public static function load($themename) {
+        global $CFG;
 
-        $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 .
-                    '). This suggests there is a nesting problem.', DEBUG_DEVELOPER);
+        // We have to use the variable name $THEME (upper case) becuase that
+        // is what is used in theme config.php files.
+
+        // Set some other standard properties of the theme.
+        $THEME = new theme_config;
+        $THEME->name = $themename;
+        $THEME->dir = $CFG->themedir . '/' . $themename;
+
+        // Load up the theme config
+        $configfile = $THEME->dir . '/config.php';
+        if (!is_readable($configfile)) {
+            throw new coding_exception('Cannot use theme ' . $themename .
+                    '. The file ' . $configfile . ' does not exist or is not readable.');
         }
-        return $container->closehtml;
+        include($configfile);
+
+        $THEME->update_legacy_information();
+
+        return $THEME;
     }
 
     /**
-     * Return how many containers are currently open.
-     * @return integer how many containers are currently open.
+     * Get the renderer for a part of Moodle for this theme.
+     * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
+     * @param moodle_page $page the page we are rendering
+     * @return moodle_renderer_base the requested renderer.
      */
-    public function count() {
-        return count($this->opencontainers);
+    public function get_renderer($module, $page) {
+        if (is_null($this->rf)) {
+            if (CLI_SCRIPT) {
+                $classname = 'cli_renderer_factory';
+            } else {
+                $classname = $this->rendererfactory;
+            }
+            $this->rf = new $classname($this);
+        }
+
+        return $this->rf->get_renderer($module, $page);
     }
 
     /**
-     * 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 <body>)
-     * before outputting the error message.
-     * @return string the HTML requried to close any open containers inside <body>.
+     * Get the renderer for a part of Moodle for this theme.
+     * @return moodle_renderer_base the requested renderer.
      */
-    public function pop_all_but_last() {
-        $output = '';
-        while (count($this->opencontainers) > 1) {
-            $container = array_pop($this->opencontainers);
-            $output .= $container->closehtml;
+    protected function get_icon_finder() {
+        if (is_null($this->if)) {
+            $classname = $this->iconfinder;
+            $this->if = new $classname($this);
         }
-        return $output;
+        return $this->if;
     }
 
     /**
-     * You can call this function if you want to throw away an instance of this
-     * class without properly emptying the stack (for example, in a unit test).
-     * Calling this method stops the destruct method from outputting a developer
-     * debug warning. After calling this method, the instance can no longer be used.
+     * Return the URL for an icon indentifed 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 $iconname the name of the icon.
+     * @return string the URL for that icon.
      */
-    public function discard() {
-        $this->opencontainers = null;
+    public function old_icon_url($iconname) {
+        return $this->if->old_icon_url($iconname);
     }
 
     /**
-     * Emergency fallback. If we get to the end of processing and not all
-     * containers have been closed, output the rest with a developer debug warning.
+     * Return the URL for an icon indentifed 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 $iconname the name of the icon.
+     * @param $module the module the icon belongs to.
+     * @return string the URL for that icon.
      */
-    public function __destruct() {
-        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->opencontainers);
-        echo $container->closehtml;
+    public function mod_icon_url($iconname, $module) {
+        return $this->if->mod_icon_url($iconname, $module);
     }
-}
-
-
-/**
- * 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 {
-    const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
-    const END_HTML_TOKEN = '%%ENDHTML%%';
-    const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
-    protected $contenttype;
-    protected $metarefreshtag = '';
 
-    public function doctype() {
+    /**
+     * Get the list of stylesheet URLs that need to go in the header for this theme.
+     * @return array of URLs.
+     */
+    public function get_stylesheet_urls() {
         global $CFG;
 
-        $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
-        $this->contenttype = 'text/html; charset=utf-8';
+        // Put together the parameters
+        $params = '?for=' . $this->name;
 
-        if (empty($CFG->xmlstrictheaders)) {
-            return $doctype;
+        // Stylesheets, in order (standard, parent, this - some of which may be the same).
+        $stylesheets = array();
+        if ($this->name != 'standard' && $this->standardsheets) {
+            $stylesheets[] = $CFG->httpsthemewww . '/standard/styles.php' . $params;
+        }
+        if (!empty($this->parent)) {
+            $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/styles.php' . $params;
         }
 
-        // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
-        $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\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 .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
-
-        } else {
-            $prolog = '';
+        // Pass on the current language, if it will be needed.
+        if (!empty($this->langsheets)) {
+            $params .= '&lang=' . current_language();
         }
+        $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/styles.php' . $params;
 
-        return $prolog . $doctype;
-    }
+        // Additional styles for right-to-left languages.
+        if (right_to_left()) {
+            $stylesheets[] = $CFG->httpsthemewww . '/standard/rtl.css';
 
-    public function htmlattributes() {
-        return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
-    }
+            if (!empty($this->parent) && file_exists($CFG->themedir . '/' . $this->parent . '/rtl.css')) {
+                $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/rtl.css';
+            }
 
-    public function standard_head_html() {
-        global $CFG, $THEME;
-        $output = '';
-        $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
-        $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
-        if (!$this->page->cacheable) {
-            $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
-            $output .= '<meta http-equiv="expires" content="0" />' . "\n";
+            if (file_exists($this->dir . '/rtl.css')) {
+                $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/rtl.css';
+            }
         }
-        // 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) {
-            $metarefesh = '<meta http-equiv="refresh" content="%d;url=%s" />';
-            $output .= sprintf($metarefesh, $this->page->periodicrefreshdelay, $this->page->url->out());
-        }
+        return $stylesheets;
+    }
 
-        // TODO get rid of $CFG->javascript. We should be able to do everything
-        // with $PAGE->requires.
-        ob_start();
-        include($CFG->javascript);
-        $output .= ob_get_contents();
-        ob_end_clean();
-        $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));
+    /**
+     * This methon looks a the settings that have been loaded, to see whether
+     * any legacy things are being used, and outputs warning and tries to update
+     * things to use equivalent newer settings.
+     */
+    protected function update_legacy_information() {
+        global $CFG;
+        if (!empty($this->customcorners)) {
+            // $THEME->customcorners is deprecated but we provide support for it via the
+            // custom_corners_renderer_factory class in lib/deprecatedlib.php
+            debugging('$THEME->customcorners is deprecated. Please use the new $THEME->rendererfactory ' .
+                    'to control HTML generation. Please use $this->rendererfactory = \'custom_corners_renderer_factory\'; ' .
+                    'in your config.php file instead.', DEBUG_DEVELOPER);
+            $this->rendererfactory = 'custom_corners_renderer_factory';
         }
 
-        // Add the meta page from the themes if any were requested
-        // TODO See if we can get rid of 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 (!empty($this->cssconstants)) {
+            debugging('$THEME->cssconstants is deprecated. Please use ' .
+                    '$THEME->customcssoutputfunction = \'output_css_replacing_constants\'; ' .
+                    'in your config.php file instead.', DEBUG_DEVELOPER);
+            $this->customcssoutputfunction = 'output_css_replacing_constants';
         }
-        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();
-            }
+
+        if (!empty($this->CSSEdit)) {
+            debugging('$THEME->CSSEdit is deprecated. Please use ' .
+                    '$THEME->customcssoutputfunction = \'output_css_for_css_edit\'; ' .
+                    'in your config.php file instead.', DEBUG_DEVELOPER);
+            $this->customcssoutputfunction = 'output_css_for_css_edit';
         }
 
-        return $output;
+        if ($CFG->smartpix) {
+            $this->iconfinder = 'smartpix_icon_finder';
+        } else if ($this->custompix) {
+            $this->iconfinder = 'theme_icon_finder';
+        }
     }
 
-    public function standard_top_of_body_html() {
-        return  $this->page->requires->get_top_of_body_code();
-    }
+    /**
+     * Set the variable $CFG->pixpath and $CFG->modpixpath to be the right
+     * ones for this theme.
+     */
+    public function setup_cfg_paths() {
+        global $CFG;
+        if (!empty($CFG->smartpix)) {
+            if ($CFG->slasharguments) {
+                // Use this method if possible for better caching
+                $extra = '';
+            } else {
+                $extra = '?file=';
+            }
+            $CFG->pixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name;
+            $CFG->modpixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name . '/mod';
 
-    public function standard_footer_html() {
-        $output = self::PERFORMANCE_INFO_TOKEN;
-        if (debugging()) {
-            $output .= '<div class="validators"><ul>
-              <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
-              <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
-              <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=0&amp;warnp2n3e=1&amp;url1=' . urlencode(qualified_me()) . '">WCAG 1 (2,3) Check</a></li>
-            </ul></div>';
+        } else if (empty($THEME->custompix)) {
+            $CFG->pixpath = $CFG->httpswwwroot . '/pix';
+            $CFG->modpixpath = $CFG->httpswwwroot . '/mod';
+
+        } else {
+            $CFG->pixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix';
+            $CFG->modpixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix/mod';
         }
-        return $output;
     }
+}
 
-    public function standard_end_of_body_html() {
-        echo self::END_HTML_TOKEN;
+
+/**
+ * This icon finder implements the old scheme that was used when themes that had
+ * $THEME->custompix = false.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class pix_icon_finder implements icon_finder {
+    /**
+     * Constructor
+     * @param theme_config $theme the theme we are finding icons for (which is irrelevant).
+     */
+    public function __construct($theme) {
     }
 
-    public function login_info() {
-        global $USER;
-        return user_login_string($this->page->course, $USER);
+    /* Implement interface method. */
+    public function old_icon_url($iconname) {
+        global $CFG;
+        return $CFG->httpswwwroot . '/pix/' . $iconname . '.gif';
     }
 
-    public function home_link() {
-        global $CFG, $SITE;
+    /* Implement interface method. */
+    public function mod_icon_url($iconname, $module) {
+        global $CFG;
+        return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.gif';
+    }
+}
 
-        if ($this->page->pagetype == 'site-index') {
-            // Special case for site home page - please do not remove
-            return '<div class="sitelink">' .
-                   '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
-                   '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
 
-        } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
-            // Special case for during install/upgrade.
-            return '<div class="sitelink">'.
-                   '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
-                   '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
+/**
+ * This icon finder implements the old scheme that was used for themes that had
+ * $THEME->custompix = true.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class theme_icon_finder implements icon_finder {
+    protected $themename;
+    /**
+     * Constructor
+     * @param theme_config $theme the theme we are finding icons for.
+     */
+    public function __construct($theme) {
+        $this->themename = $theme->name;
+    }
 
-        } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
-            return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
-                    get_string('home') . '</a></div>';
+    /* Implement interface method. */
+    public function old_icon_url($iconname) {
+        global $CFG;
+        return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.gif';
+    }
 
-        } else {
-            return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
-                    format_string($this->page->course->shortname) . '</a></div>';
-        }
+    /* Implement interface method. */
+    public function mod_icon_url($iconname, $module) {
+        global $CFG;
+        return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.gif';
     }
+}
+
+
+/**
+ * This icon finder implements the algorithm in pix/smartpix.php.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class smartpix_icon_finder extends pix_icon_finder {
+    protected $places = array();
 
     /**
-     * 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 string $messageclass The css class to put on the message that is
-     *         being displayed to the user
-     * @return string The HTML to display to the user before dying, may contain
-     *         meta refresh, javascript refresh, and may have set header redirects
+     * Constructor
+     * @param theme_config $theme the theme we are finding icons for.
      */
-    public function redirect($encodedurl, $message, $delay, $messageclass='notifyproblem') {
+    public function __construct($theme) {
         global $CFG;
-        $url = str_replace('&amp;', '&', $encodedurl);
-
-        $disableredirect = false;
+        $this->places[$CFG->themedir . '/' . $theme->name . '/pix/'] =
+                $CFG->httpsthemewww . '/' . $theme->name . '/pix/';
+        if (!empty($theme->parent)) {
+            $this->places[$CFG->themedir . '/' . $theme->parent . '/pix/'] =
+                    $CFG->httpsthemewww . '/' . $theme->parent . '/pix/';
+        }
+    }
 
-        if ($delay!=0) {
-            /// At developer debug level. Don't redirect if errors have been printed on screen.
-            /// Currenly only works in PHP 5.2+; we do not want strict PHP5 errors
-            $lasterror = error_get_last();
-            $error = defined('DEBUGGING_PRINTED') or (!empty($lasterror) && ($lasterror['type'] & DEBUG_DEVELOPER));
-            $errorprinted = debugging('', DEBUG_ALL) && $CFG->debugdisplay && $error;
-            if ($errorprinted) {
-                $disableredirect= true;
-                $message = "<strong>Error output, so disabling automatic redirect.</strong></p><p>" . $message;
+    /* Implement interface method. */
+    public function old_icon_url($iconname) {
+        foreach ($this->places as $dirroot => $urlroot) {
+            if (file_exists($dirroot . $iconname . '.gif')) {
+                return $iconname . $iconname . '.gif';
             }
         }
+        return parent::old_icon_url($iconname);
+    }
 
-        switch ($this->page->state) {
-            case moodle_page::STATE_BEFORE_HEADER :
-                // No output yet it is safe to delivery the full arsenol of redirect methods
-                if (!$disableredirect) {
-                    @header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other'); //302 might not work for POST requests, 303 is ignored by obsolete clients
-                    @header('Location: '.$url);
-                    $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
-                    $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay+3);
-                }
-                $output = $this->header();
-                $output .= $this->notification($message, $messageclass);
-                $output .= $this->footer();
-                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 (!$disableredirect) {
-                    $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay+3);
-                }
-                $output = $this->opencontainers->pop_all_but_last();
-                $output .= $this->notification($message, $messageclass);
-                $output .= $this->footer();
-                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;
+    /* Implement interface method. */
+    public function mod_icon_url($iconname, $module) {
+        foreach ($this->places as $dirroot => $urlroot) {
+            if (file_exists($dirroot . 'mod/' . $iconname . '.gif')) {
+                return $iconname . 'mod/' . $iconname . '.gif';
+            }
         }
-        return $output;
+        return parent::old_icon_url($iconname);
     }
+}
 
-    // 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);
+/**
+ * This is a base class to help you implement the renderer_factory interface.
+ *
+ * It keeps a cache of renderers that have been constructed, so you only need
+ * to construct each one once in you subclass.
+ *
+ * It also has a method to get the name of, and include the renderer.php with
+ * the definition of, the standard renderer class for a given module.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+abstract class renderer_factory_base implements renderer_factory {
+    /** @var theme_config the theme we belong to. */
+    protected $theme;
 
-        // 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);
+    /**
+     * Constructor.
+     * @param theme_config $theme the theme we belong to.
+     */
+    public function __construct($theme) {
+        $this->theme = $theme;
+    }
+    /**
+     * For a given module name, return the name of the standard renderer class
+     * that defines the renderer interface for that module.
+     *
+     * Also, if it exists, include the renderer.php file for that module, so
+     * the class definition of the default renderer has been loaded.
+     *
+     * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
+     * @return string the name of the standard renderer class for that module.
+     */
+    protected function standard_renderer_class_for_module($component) {
+        if ($component != 'core') {
+            $pluginrenderer = get_component_directory($component) . '/renderer.php';
+            if (file_exists($pluginrenderer)) {
+                include_once($pluginrenderer);
+            }
         }
-
-        // 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 . '".');
+        $class = 'moodle_' . $component . '_renderer';
+        if (!class_exists($class)) {
+            throw new coding_exception('Request for an unknown renderer class ' . $class);
         }
-        $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();
+        return $class;
     }
+}
 
-    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;
-            }
+/**
+ * This is the default renderer factory for Moodle. It simply returns an instance
+ * of the appropriate standard renderer class.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class standard_renderer_factory extends renderer_factory_base {
+    /* Implement the subclass method. */
+    public function get_renderer($module, $page) {
+        if ($module == 'core') {
+            return new moodle_core_renderer($page);
+        } else {
+            $class = $this->standard_renderer_class_for_module($module);
+            return new $class($page, $this->get_renderer('core', $page));
         }
+    }
+}
 
-        // Otherwise look for the general template.
-        $templatefile = $THEME->dir . '/layout.php';
-        if (is_readable($templatefile)) {
-            return $templatefile;
-        }
 
-        return false;
+/**
+ * This is a slight variation on the standard_renderer_factory used by CLI scripts.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class cli_renderer_factory extends standard_renderer_factory {
+    /* Implement the subclass method. */
+    public function get_renderer($module, $page) {
+        if ($module == 'core') {
+            return new cli_core_renderer($page);
+        } else {
+            parent::get_renderer($module, $page);
+        }
     }
+}
 
-    protected function render_page_template($templatefile, $menu, $navigation) {
-        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 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
-        // rederer 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;
 
-        ob_start();
-        include($templatefile);
-        $template = ob_get_contents();
-        ob_end_clean();
-        return $template;
+/**
+ * This is renderer factory allows themes to override the standard renderers using
+ * php code.
+ *
+ * It will load any code from theme/mytheme/renderers.php and
+ * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for
+ * a renderer for 'component', it will create a mytheme_component_renderer or a
+ * parenttheme_component_renderer, instead of a moodle_component_renderer,
+ * if either of those classes exist.
+ *
+ * This generates the slightly different HTML that the custom_corners theme expects.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class theme_overridden_renderer_factory extends standard_renderer_factory {
+    protected $prefixes = array();
+
+    /**
+     * Constructor.
+     * @param object $theme the theme we are rendering for.
+     * @param moodle_page $page the page we are doing output for.
+     */
+    public function __construct($theme) {
+        global $CFG;
+        parent::__construct($theme);
+
+        // Initialise $this->prefixes.
+        $renderersfile = $theme->dir . '/renderers.php';
+        if (is_readable($renderersfile)) {
+            include_once($renderersfile);
+            $this->prefixes[] = $theme->name . '_';
+        }
+        if (!empty($theme->parent)) {
+            $renderersfile = $CFG->themedir .'/'. $theme->parent . '/renderers.php';
+            if (is_readable($renderersfile)) {
+                include_once($renderersfile);
+                $this->prefixes[] = $theme->parent . '_';
+            }
+        }
     }
 
-    protected function handle_legacy_theme($navigation, $menu) {
-        global $CFG, $SITE, $THEME, $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;
+    /* Implement the subclass method. */
+    public function get_renderer($module, $page) {
+        foreach ($this->prefixes as $prefix) {
+            $classname = $prefix . $module . '_renderer';
+            if (class_exists($classname)) {
+                if ($module == 'core') {
+                    return new $classname($page);
+                } else {
+                    return new $classname($page, $this->get_renderer('core', $page));
+                }
+            }
+        }
+        return parent::get_renderer($module, $page);
+    }
+}
 
-        // 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';
+/**
+ * This is renderer factory that allows you to create templated themes.
+ *
+ * This should be considered an experimental proof of concept. In particular,
+ * the performance is probably not very good. Do not try to use in on a busy site
+ * without doing careful load testing first!
+ *
+ * This renderer factory returns instances of {@link template_renderer} class
+ * which which implement the corresponding renderer interface in terms of
+ * templates. To use this your theme must have a templates folder inside it.
+ * Then suppose the method moodle_core_renderer::greeting($name = 'world');
+ * exists. Then, a call to $OUTPUT->greeting() will cause the template
+ * /theme/yourtheme/templates/core/greeting.php to be rendered, with the variable
+ * $name available. The greeting.php template might contain
+ *
+ * <pre>
+ * <h1>Hello <?php echo $name ?>!</h1>
+ * </pre>
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class template_renderer_factory extends renderer_factory_base {
+    /**
+     * An array of paths of where to search for templates. Normally this theme,
+     * the parent theme then the standardtemplate theme. (If some of these do
+     * not exist, or are the same as each other, then the list will be shorter.
+     */
+    protected $searchpaths = array();
 
-        // 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;
+    /**
+     * Constructor.
+     * @param object $theme the theme we are rendering for.
+     * @param moodle_page $page the page we are doing output for.
+     */
+    public function __construct($theme) {
+        global $CFG;
+        parent::__construct($theme);
 
-        if (!$menu && $navigation) {
-            $menu = $loggedinas;
+        // Initialise $this->searchpaths.
+        if ($theme->name != 'standardtemplate') {
+            $templatesdir = $theme->dir . '/templates';
+            if (is_dir($templatesdir)) {
+                $this->searchpaths[] = $templatesdir;
+            }
         }
+        if (!empty($theme->parent)) {
+            $templatesdir = $CFG->themedir .'/'. $theme->parent . '/templates';
+            if (is_dir($templatesdir)) {
+                $this->searchpaths[] = $templatesdir;
+            }
+        }
+        $this->searchpaths[] = $CFG->themedir .'/standardtemplate/templates';
+    }
 
-        ob_start();
-        include($THEME->dir . '/header.html');
-        $this->page->requires->get_top_of_body_code();
-        echo self::MAIN_CONTENT_TOKEN;
+    /* Implement the subclass method. */
+    public function get_renderer($module, $page) {
+        // Refine the list of search paths for this module.
+        $searchpaths = array();
+        foreach ($this->searchpaths as $rootpath) {
+            $path = $rootpath . '/' . $module;
+            if (is_dir($path)) {
+                $searchpaths[] = $path;
+            }
+        }
 
-        $menu = str_replace('navmenu', 'navmenufooter', $menu);
-        include($THEME->dir . '/footer.html');
+        // 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, $page);
+    }
+}
 
-        $output = ob_get_contents();
-        ob_end_clean();
 
-        $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
+/**
+ * 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;
 
-        return $output;
+    /**
+     * Constructor
+     * @param $opencontainers the xhtml_container_stack to use.
+     * @param moodle_page $page the page we are doing output for.
+     */
+    public function __construct($page) {
+        $this->opencontainers = $page->opencontainers;
+        $this->page = $page;
     }
 
-    public function footer() {
+    /**
+     * 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;
+    }
+
+    protected function output_tag($tagname, $attributes, $contents) {
+        return $this->output_start_tag($tagname, $attributes) . $contents .
+                $this->output_end_tag($tagname);
+    }
+    protected function output_start_tag($tagname, $attributes) {
+        return '<' . $tagname . $this->output_attributes($attributes) . '>';
+    }
+    protected function output_end_tag($tagname) {
+        return '</' . $tagname . '>';
+    }
+    protected function output_empty_tag($tagname, $attributes) {
+        return '<' . $tagname . $this->output_attributes($attributes) . ' />';
+    }
+
+    protected function output_attribute($name, $value) {
+        $value = trim($value);
+        if ($value || is_numeric($value)) { // We want 0 to be output.
+            return ' ' . $name . '="' . $value . '"';
+        }
+    }
+    protected function output_attributes($attributes) {
+        if (empty($attributes)) {
+            $attributes = array();
+        }
         $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();
+        foreach ($attributes as $name => $value) {
+            $output .= $this->output_attribute($name, $value);
         }
+        return $output;
+    }
+    public static function prepare_classes($classes) {
+        if (is_array($classes)) {
+            return implode(' ', array_unique($classes));
+        }
+        return $classes;
+    }
 
-        $footer = $this->opencontainers->pop('header/footer');
+    /**
+     * Return the URL for an icon indentifed 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 $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);
+    }
 
-        // 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);
+    /**
+     * Return the URL for an icon indentifed 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 $iconname the name of the icon.
+     * @param $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);
+    }
+}
 
-        $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
 
-        $this->page->set_state(moodle_page::STATE_DONE);
+/**
+ * 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;
 
-        return $output . $footer;
+    /**
+     * 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 $searchpaths a list of folders to search for templates in.
+     * @param $opencontainers the xhtml_container_stack to use.
+     * @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. */
+    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);
+        }
     }
 
     /**
-     * Prints a nice side block with an optional header.
-     *
-     * The content is described
-     * by a {@link block_contents} object.
-     *
-     * @param block $content HTML for the content
+     * 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.
      */
-    function block($bc) {
-        $bc = clone($bc);
-        $bc->prepare();
-
-        $title = strip_tags($bc->title);
-        if (empty($title)) {
-            $output = '';
-            $skipdest = '';
-        } else {
-            $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
-                    get_string('skipa', 'access', $title));
-            $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
+    protected function process_template($method, $arguments) {
+        if (!$this->copiedclass->hasMethod($method) ||
+                !$this->copiedclass->getMethod($method)->isPublic()) {
+            throw new coding_exception('Unknown method ' . $method);
         }
 
-        $bc->attributes['id'] = $bc->id;
-        $bc->attributes['class'] = $bc->get_classes_string();
-        $output .= $this->output_start_tag('div', $bc->attributes);
+        // Find the template file for this method.
+        $template = $this->find_template($method);
 
-        if ($bc->heading) {
-            // Some callers pass in complete html for the heading, which may include
-            // complicated things such as the 'hide block' button; some just pass in
-            // text. If they only pass in plain text i.e. it doesn't include a
-            // <div>, then we add in standard tags that make it look like a normal
-            // page block including the h2 for accessibility
-            if (strpos($bc->heading, '</div>') === false) {
-                $bc->heading = $this->output_tag('div', array('class' => 'title'),
-                        $this->output_tag('h2', null, $bc->heading));
+        // 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);
             }
-
-            $output .= $this->output_tag('div', array('class' => 'header'), $bc->heading);
         }
 
-        $output .= $this->output_start_tag('div', array('class' => 'content'));
-
-        if ($bc->content) {
-            $output .= $bc->content;
+        // Actually render the template.
+        return $this->render_template($template, $namedarguments);
+    }
 
-        } else if ($bc->list) {
-            $row = 0;
-            $items = array();
-            foreach ($bc->list as $key => $string) {
-                $item = $this->output_start_tag('li', array('class' => 'r' . $row));
-                if ($bc->icons) {
-                    $item .= $this->output_tag('div', array('class' => 'icon column c0'), $bc->icons[$key]);
-                }
-                $item .= $this->output_tag('div', array('class' => 'column c1'), $string);
-                $item .= $this->output_end_tag('li');
-                $items[] = $item;
-                $row = 1 - $row; // Flip even/odd.
-            }
-            $output .= $this->output_tag('ul', array('class' => 'list'), implode("\n", $items));
-        }
+    /**
+     * Actually do the work of rendering the template.
+     * @param $_template the full path to the template file.
+     * @param $_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.
 
-        if ($bc->footer) {
-            $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
-        }
+        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 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
+        // rederer 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;
 
-        $output .= $this->output_end_tag('div');
-        $output .= $this->output_end_tag('div');
-        $output .= $skipdest;
+        // And the parameters from the function call.
+        extract($_namedarguments);
 
-        if (!empty($CFG->allowuserblockhiding) && isset($attributes['id'])) {
-            $strshow = addslashes_js(get_string('showblocka', 'access', $title));
-            $strhide = addslashes_js(get_string('hideblocka', 'access', $title));
-            $output .= $this->page->requires->js_function_call('elementCookieHide', array(
-                    $bc->id, $strshow, $strhide))->asap();
-        }
+        // Include the template, capturing the output.
+        ob_start();
+        include($_template);
+        $_result = ob_get_contents();
+        ob_end_clean();
 
-        return $output;
+        return $_result;
     }
 
-    public function link_to_popup_window() {
-
+    /**
+     * 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);
     }
 
-    public function button_to_popup_window() {
-
+    /**
+     * Handle methods like print_box_start by using the print_box template,
+     * splitting the result, pusing 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($template, $arguments) {
+        array_unshift($arguments, self::contentstoken);
+        $html = $this->process_template($template, $arguments);
+        list($start, $end) = explode(self::contentstoken, $html, 2);
+        $this->opencontainers->push($template, $end);
+        return $start;
     }
 
-    public function close_window_button($buttontext = null, $reloadopener = false) {
-        if (empty($buttontext)) {
-            $buttontext = get_string('closewindow');
-        }
-        // TODO
+    /**
+     * 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($template, $arguments) {
+        return $this->opencontainers->pop($template);
     }
 
-    public function close_window($delay = 0, $reloadopener = false) {
-        // TODO
+    /**
+     * @return array the list of paths where this class searches for templates.
+     */
+    public function get_search_paths() {
+        return $this->searchpaths;
     }
 
     /**
-     * Output a <select> menu.
-     *
-     * You can either call this function with a single moodle_select_menu argument
-     * or, with a list of parameters, in which case those parameters are sent to
-     * the moodle_select_menu constructor.
-     *
-     * @param moodle_select_menu $selectmenu a moodle_select_menu that describes
-     *      the select menu you want output.
-     * @return string the HTML for the <select>
+     * @return string the name of the class whose API we are copying.
      */
-    public function select_menu($selectmenu) {
-        $selectmenu = clone($selectmenu);
-        $selectmenu->prepare();
+    public function get_copied_class() {
+        return $this->copiedclass->getName();
+    }
+}
 
-        if ($selectmenu->nothinglabel) {
-            $selectmenu->options = array($selectmenu->nothingvalue => $selectmenu->nothinglabel) +
-                    $selectmenu->options;
-        }
 
-        if (empty($selectmenu->id)) {
-            $selectmenu->id = 'menu' . str_replace(array('[', ']'), '', $selectmenu->name);
-        }
+/**
+ * This class keeps track of which HTML tags are currently open.
+ *
+ * This makes it much easier to always generate well formed XHTML output, even
+ * if execution terminates abruptly. Any time you output some opening HTML
+ * without the matching closing HTML, you should push the neccessary close tags
+ * onto the stack.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since     Moodle 2.0
+ */
+class xhtml_container_stack {
+    /** @var array stores the list of open containers. */
+    protected $opencontainers = array();
 
-        $attributes = array(
-            'name' => $selectmenu->name,
-            'id' => $selectmenu->id,
-            'class' => $selectmenu->get_classes_string(),
-            'onchange' => $selectmenu->script,
-        );
-        if ($selectmenu->disabled) {
-            $attributes['disabled'] = 'disabled';
-        }
-        if ($selectmenu->tabindex) {
-            $attributes['tabindex'] = $tabindex;
-        }
+    /**
+     * Push the close HTML for a recently opened container onto the stack.
+     * @param string $type The type of container. This is checked when {@link pop()}
+     *      is called and must match, otherwise a developer debug warning is output.
+     * @param string $closehtml The HTML required to close the container.
+     */
+    public function push($type, $closehtml) {
+        $container = new stdClass;
+        $container->type = $type;
+        $container->closehtml = $closehtml;
+        array_push($this->opencontainers, $container);
+    }
 
-        if ($selectmenu->listbox) {
-            if (is_integer($selectmenu->listbox)) {
-                $size = $selectmenu->listbox;
-            } else {
-                $size = min($selectmenu->maxautosize, count($selectmenu->options));
-            }
-            $attributes['size'] = $size;
-            if ($selectmenu->multiple) {
-                $attributes['multiple'] = 'multiple';
-            }
+    /**
+     * Pop the HTML for the next closing container from the stack. The $type
+     * must match the type passed when the container was opened, otherwise a
+     * warning will be output.
+     * @param string $type The type of container.
+     * @return string the HTML requried to close the container.
+     */
+    public function pop($type) {
+        if (empty($this->opencontainers)) {
+            debugging('There are no more open containers. This suggests there is a nesting problem.', DEBUG_DEVELOPER);
+            return;
         }
 
-        $html = $this->output_start_tag('select', $attributes) . "\n";
-        foreach ($selectmenu->options as $value => $label) {
-            $attributes = array('value' => $value);
-            if ((string)$value == (string)$selectmenu->selectedvalue ||
-                    (is_array($selectmenu->selectedvalue) && in_array($value, $selectmenu->selectedvalue))) {
-                $attributes['selected'] = 'selected';
-            }
-            $html .= '    ' . $this->output_tag('option', $attributes, s($label)) . "\n";
+        $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 .
+                    '). This suggests there is a nesting problem.', DEBUG_DEVELOPER);
         }
-        $html .= $this->output_end_tag('select') . "\n";
-
-        return $html;
+        return $container->closehtml;
     }
 
-    // TODO choose_from_menu_nested
-
-    // TODO choose_from_radio
+    /**
+     * Return how many containers are currently open.
+     * @return integer how many containers are currently open.
+     */
+    public function count() {
+        return count($this->opencontainers);
+    }
 
     /**
-     * Output an error message. By default wraps the error message in <span class="error">.
-     * If the error message is blank, nothing is output.
-     * @param $message the error message.
-     * @return string the HTML to output.
+     * 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 <body>)
+     * before outputting the error message.
+     * @return string the HTML requried to close any open containers inside <body>.
      */
-    public function error_text($message) {
-        if (empty($message)) {
-            return '';
+    public function pop_all_but_last() {
+        $output = '';
+        while (count($this->opencontainers) > 1) {
+            $container = array_pop($this->opencontainers);
+            $output .= $container->closehtml;
         }
-        return $this->output_tag('span', array('class' => 'error'), $message);
+        return $output;
     }
 
     /**
-     * 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.
+     * You can call this function if you want to throw away an instance of this
+     * class without properly emptying the stack (for example, in a unit test).
+     * Calling this method stops the destruct method from outputting a developer
+     * debug warning. After calling this method, the instance can no longer be used.
      */
-    public function fatal_error($message, $moreinfourl, $link, $backtrace,
-                $debuginfo = null, $showerrordebugwarning = false) {
-
-        $output = '';
+    public function discard() {
+        $this->opencontainers = null;
+    }
 
-        if ($this->has_started()) {
-            $output .= $this->opencontainers->pop_all_but_last();
-        } else {
-            // Header not yet printed
-            @header('HTTP/1.0 404 Not Found');
-            $this->page->set_title(get_string('error'));
-            $output .= $this->header();
+    /**
+     * Emergency fallback. If we get to the end of processing and not all
+     * containers have been closed, output the rest with a developer debug warning.
+     */
+    public function __destruct() {
+        if (empty($this->opencontainers)) {
+            return;
         }
 
-        $message = '<p class="errormessage">' . $message . '</p>'.
-                '<p class="errorcode"><a href="' . $moreinfourl . '">' .
-                get_string('moreinformation') . '</a></p>';
-        $output .= $this->box($message, 'errorbox');
+        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->opencontainers);
+        echo $container->closehtml;
+    }
+}
 
-        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), 'notifytiny');
-            }
-        }
 
-        if (!empty($link)) {
-            $output .= $this->continue_button($link);
+/**
+ * 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 {
+    const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
+    const END_HTML_TOKEN = '%%ENDHTML%%';
+    const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
+    protected $contenttype;
+    protected $metarefreshtag = '';
+
+    public function doctype() {
+        global $CFG;
+
+        $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
+        $this->contenttype = 'text/html; charset=utf-8';
+
+        if (empty($CFG->xmlstrictheaders)) {
+            return $doctype;
         }
 
-        $output .= $this->footer();
+        // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
+        $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\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';
 
-        // Padding to encourage IE to display our error page, rather than its own.
-        $output .= str_repeat(' ', 512);
+        } 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 .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
 
-        return $output;
+        } else {
+            $prolog = '';
+        }
+
+        return $prolog . $doctype;
     }
 
-    /**
-     * 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));
+    public function htmlattributes() {
+        return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
     }
 
-    /**
-     * 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);
+    public function standard_head_html() {
+        global $CFG, $THEME;
+        $output = '';
+        $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
+        $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
+        if (!$this->page->cacheable) {
+            $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
+            $output .= '<meta http-equiv="expires" content="0" />' . "\n";
         }
-        return $this->output_tag('div', array('class' => 'continuebutton'),
-                print_single_button($link->out(true), $link->params(), get_string('continue'), 'get', '', true));
+        // 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) {
+            $metarefesh = '<meta http-equiv="refresh" content="%d;url=%s" />';
+            $output .= sprintf($metarefesh, $this->page->periodicrefreshdelay, $this->page->url->out());
+        }
+
+        // TODO get rid of $CFG->javascript. We should be able to do everything
+        // with $PAGE->requires.
+        ob_start();
+        include($CFG->javascript);
+        $output .= ob_get_contents();
+        ob_end_clean();
+        $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));
+        }
+
+        // Add the meta page from the themes if any were requested
+        // TODO See if we can get rid of 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;
     }
 
-    /**
-     * 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 standard_top_of_body_html() {
+        return  $this->page->requires->get_top_of_body_code();
     }
 
-    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.');
+    public function standard_footer_html() {
+        $output = self::PERFORMANCE_INFO_TOKEN;
+        if (debugging()) {
+            $output .= '<div class="validators"><ul>
+              <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
+              <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
+              <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=0&amp;warnp2n3e=1&amp;url1=' . urlencode(qualified_me()) . '">WCAG 1 (2,3) Check</a></li>
+            </ul></div>';
         }
-        return $this->output_tag('h' . $level,
-                array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
+        return $output;
     }
 
-    public function box($contents, $classes = 'generalbox', $id = '') {
-        return $this->box_start($classes, $id) . $contents . $this->box_end();
+    public function standard_end_of_body_html() {
+        echo self::END_HTML_TOKEN;
     }
 
-    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 login_info() {
+        global $USER;
+        return user_login_string($this->page->course, $USER);
     }
 
-    public function box_end() {
-        return $this->opencontainers->pop('box');
-    }
+    public function home_link() {
+        global $CFG, $SITE;
 
-    public function container($contents, $classes = '', $id = '') {
-        return $this->container_start($classes, $id) . $contents . $this->container_end();
-    }
+        if ($this->page->pagetype == 'site-index') {
+            // Special case for site home page - please do not remove
+            return '<div class="sitelink">' .
+                   '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
+                   '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
 
-    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)));
-    }
+        } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
+            // Special case for during install/upgrade.
+            return '<div class="sitelink">'.
+                   '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
+                   '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
 
-    public function container_end() {
-        return $this->opencontainers->pop('container');
+        } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
+            return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
+                    get_string('home') . '</a></div>';
+
+        } else {
+            return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
+                    format_string($this->page->course->shortname) . '</a></div>';
+        }
     }
 
     /**
-     * At the moment we frequently have a problem with $CFG->pixpath not being
-     * initialised when it is needed. Unfortunately, there is no nice way to handle
-     * this. I think we need to replace $CFG->pixpath with something like $OUTPUT->icon(...).
-     * However, until then, we need a way to force $CFG->pixpath to be initialised,
-     * to fix the error messages, and that is what this function if for.
+     * 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 string $messageclass The css class to put on the message that is
+     *         being displayed to the user
+     * @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 initialise_deprecated_cfg_pixpath() {
-        // Actually, we don't have to do anything here. Just calling any method
-        // of $OBJECT  is enough. However, if the only reason you are calling
-        // an $OUTPUT method is to get $CFG->pixpath initialised, please use this
-        // method, so we can find them and clean them up later once we have
-        // found a better replacement for $CFG->pixpath.
-    }
-}
+    public function redirect($encodedurl, $message, $delay, $messageclass='notifyproblem') {
+        global $CFG;
+        $url = str_replace('&amp;', '&', $encodedurl);
 
+        $disableredirect = false;
 
-/**
- *This class represents the configuration variables of a Moodle theme.
- *
- * Normally, to create an instance of this class, you should use the
- * {@link theme_config::load()} factory method to load a themes config.php file.
- *
- * @copyright 2009 Tim Hunt
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since     Moodle 2.0
- */
-class theme_config {
-    /**
-     * @var array The names of all the stylesheets from this theme that you would
-     * like included, in order.
-     */
-    public $sheets = array('styles_layout', 'styles_fonts', 'styles_color');
+        if ($delay!=0) {
+            /// At developer debug level. Don't redirect if errors have been printed on screen.
+            /// Currenly only works in PHP 5.2+; we do not want strict PHP5 errors
+            $lasterror = error_get_last();
+            $error = defined('DEBUGGING_PRINTED') or (!empty($lasterror) && ($lasterror['type'] & DEBUG_DEVELOPER));
+            $errorprinted = debugging('', DEBUG_ALL) && $CFG->debugdisplay && $error;
+            if ($errorprinted) {
+                $disableredirect= true;
+                $message = "<strong>Error output, so disabling automatic redirect.</strong></p><p>" . $message;
+            }
+        }
 
-    public $standardsheets = true;  
+        switch ($this->page->state) {
+            case moodle_page::STATE_BEFORE_HEADER :
+                // No output yet it is safe to delivery the full arsenol of redirect methods
+                if (!$disableredirect) {
+                    @header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other'); //302 might not work for POST requests, 303 is ignored by obsolete clients
+                    @header('Location: '.$url);
+                    $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
+                    $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay+3);
+                }
+                $output = $this->header();
+                $output .= $this->notification($message, $messageclass);
+                $output .= $this->footer();
+                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 (!$disableredirect) {
+                    $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay+3);
+                }
+                $output = $this->opencontainers->pop_all_but_last();
+                $output .= $this->notification($message, $messageclass);
+                $output .= $this->footer();
+                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;
+        }
+        return $output;
+    }
 
-/// This variable can be set to an array containing
-/// filenames from the *STANDARD* theme.  If the 
-/// array exists, it will be used to choose the 
-/// files to include in the standard style sheet.
-/// When false, then no files are used.
-/// When true or NON-EXISTENT, then ALL standard files are used.
-/// This parameter can be used, for example, to prevent 
-/// having to override too many classes.
-/// Note that the trailing .css should not be included
-/// eg $THEME->standardsheets = array('styles_layout','styles_fonts','styles_color');
-////////////////////////////////////////////////////////////////////////////////
+    // 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);
 
-    public $parent = null;  
+        // 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);
+        }
 
-/// This variable can be set to the name of a parent theme
-/// which you want to have included before the current theme.
-/// This can make it easy to make modifications to another 
-/// theme without having to actually change the files
-/// If this variable is empty or false then a parent theme 
-/// is not used.
-////////////////////////////////////////////////////////////////////////////////
+        // 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();
+    }
 
-    public $parentsheets = false;  
+    protected function find_page_template() {
+        global $THEME;
 
-/// This variable can be set to an array containing
-/// filenames from a chosen *PARENT* theme.  If the 
-/// array exists, it will be used to choose the 
-/// files to include in the standard style sheet.
-/// When false, then no files are used.
-/// When true or NON-EXISTENT, then ALL standard files are used.
-/// This parameter can be used, for example, to prevent 
-/// having to override too many classes.
-/// Note that the trailing .css should not be included
-/// eg $THEME->parentsheets = array('styles_layout','styles_fonts','styles_color');
-////////////////////////////////////////////////////////////////////////////////
+        // 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;
+        }
 
-    public $modsheets = true;  
+        return false;
+    }
 
-/// When this is enabled, then this theme will search for 
-/// files named "styles.php" inside all Activity modules and 
-/// include them.   This allows modules to provide some basic 
-/// layouts so they work out of the box.
-/// It is HIGHLY recommended to leave this enabled.
+    protected function render_page_template($templatefile, $menu, $navigation) {
+        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 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
+        // rederer 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;
 
+        ob_start();
+        include($templatefile);
+        $template = ob_get_contents();
+        ob_end_clean();
+        return $template;
+    }
 
-    public $blocksheets = true;  
+    protected function handle_legacy_theme($navigation, $menu) {
+        global $CFG, $SITE, $THEME, $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;
+
+        // 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';
 
-/// When this is enabled, then this theme will search for 
-/// files named "styles.php" inside all Block modules and 
-/// include them.   This allows Blocks to provide some basic 
-/// layouts so they work out of the box.
-/// It is HIGHLY recommended to leave this enabled.
+        // 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;
+        }
 
-    public $langsheets = false;  
+        ob_start();
+        include($THEME->dir . '/header.html');
+        $this->page->requires->get_top_of_body_code();
+        echo self::MAIN_CONTENT_TOKEN;
 
-/// By setting this to true, then this theme will search for 
-/// a file named "styles.php" inside the current language
-/// directory.  This allows different languages to provide 
-/// different styles.
+        $menu = str_replace('navmenu', 'navmenufooter', $menu);
+        include($THEME->dir . '/footer.html');
 
+        $output = ob_get_contents();
+        ob_end_clean();
 
-    public $courseformatsheets = true;
+        $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
 
-/// When this is enabled, this theme will search for files 
-/// named "styles.php" inside all course formats and 
-/// include them.  This allows course formats to provide 
-/// their own default styles.
+        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();
+        }
 
-    public $metainclude = false;
+        $footer = $this->opencontainers->pop('header/footer');
 
-/// When this is enabled (or not set!) then Moodle will try 
-/// to include a file meta.php from this theme into the 
-/// <head></head> part of the page.
+        // 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);
 
-    public $standardmetainclude = true;
+        $this->page->set_state(moodle_page::STATE_DONE);
 
+        return $output . $footer;
+    }
 
-/// When this is enabled (or not set!) then Moodle will try 
-/// to include a file meta.php from the standard theme into the 
-/// <head></head> part of the page.
+    /**
+     * Prints a nice side block with an optional header.
+     *
+     * The content is described
+     * by a {@link block_contents} object.
+     *
+     * @param block $content HTML for the content
+     * @return string the HTML to be output.
+     */
+    function block($bc) {
+        $bc = clone($bc);
+        $bc->prepare();
 
+        $title = strip_tags($bc->title);
+        if (empty($title)) {
+            $output = '';
+            $skipdest = '';
+        } else {
+            $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
+                    get_string('skipa', 'access', $title));
+            $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
+        }
 
-    public $parentmetainclude = false;
+        $bc->attributes['id'] = $bc->id;
+        $bc->attributes['class'] = $bc->get_classes_string();
+        $output .= $this->output_start_tag('div', $bc->attributes);
 
-/// When this is enabled (or not set!) then Moodle will try 
-/// to include a file meta.php from the parent theme into the 
-/// <head></head> part of the page.
+        if ($bc->heading) {
+            // Some callers pass in complete html for the heading, which may include
+            // complicated things such as the 'hide block' button; some just pass in
+            // text. If they only pass in plain text i.e. it doesn't include a
+            // <div>, then we add in standard tags that make it look like a normal
+            // page block including the h2 for accessibility
+            if (strpos($bc->heading, '</div>') === false) {
+                $bc->heading = $this->output_tag('div', array('class' => 'title'),
+                        $this->output_tag('h2', null, $bc->heading));
+            }
 
+            $output .= $this->output_tag('div', array('class' => 'header'), $bc->heading);
+        }
 
-    public $navmenuwidth = 50;
+        $output .= $this->output_start_tag('div', array('class' => 'content'));
 
-/// You can use this to control the cutoff point for strings 
-/// in the navmenus (list of activities in popup menu etc)
-/// Default is 50 characters wide.
+        if ($bc->content) {
+            $output .= $bc->content;
 
+        } else if ($bc->list) {
+            $row = 0;
+            $items = array();
+            foreach ($bc->list as $key => $string) {
+                $item = $this->output_start_tag('li', array('class' => 'r' . $row));
+                if ($bc->icons) {
+                    $item .= $this->output_tag('div', array('class' => 'icon column c0'), $bc->icons[$key]);
+                }
+                $item .= $this->output_tag('div', array('class' => 'column c1'), $string);
+                $item .= $this->output_end_tag('li');
+                $items[] = $item;
+                $row = 1 - $row; // Flip even/odd.
+            }
+            $output .= $this->output_tag('ul', array('class' => 'list'), implode("\n", $items));
+        }
 
-    public $makenavmenulist = false;
+        if ($bc->footer) {
+            $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
+        }
 
-/// By setting this to true, then you will have access to a
-/// new variable in your header.html and footer.html called
-/// $navmenulist ... this contains a simple XHTML menu of 
-/// all activities in the current course, mostly useful for 
-/// creating popup navigation menus and so on.
+        $output .= $this->output_end_tag('div');
+        $output .= $this->output_end_tag('div');
+        $output .= $skipdest;
 
+        if (!empty($CFG->allowuserblockhiding) && isset($attributes['id'])) {
+            $strshow = addslashes_js(get_string('showblocka', 'access', $title));
+            $strhide = addslashes_js(get_string('hideblocka', 'access', $title));
+            $output .= $this->page->requires->js_function_call('elementCookieHide', array(
+                    $bc->id, $strshow, $strhide))->asap();
+        }
 
+        return $output;
+    }
 
-    public $resource_mp3player_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&font=Arial&fontColour=3333FF&buffer=10&waitForPlay=no&autoPlay=yes';
+    public function link_to_popup_window() {
 
-/// With this you can control the colours of the "big" MP3 player 
-/// that is used for MP3 resources.
+    }
 
+    public function button_to_popup_window() {
 
-    public $filter_mediaplugin_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&waitForPlay=yes';
+    }
 
-/// ...And this controls the small embedded player
+    public function close_window_button($buttontext = null, $reloadopener = false) {
+        if (empty($buttontext)) {
+            $buttontext = get_string('closewindow');
+        }
+        // TODO
+    }
 
+    public function close_window($delay = 0, $reloadopener = false) {
+        // TODO
+    }
 
-    public $custompix = false;
+    /**
+     * Output a <select> menu.
+     *
+     * You can either call this function with a single moodle_select_menu argument
+     * or, with a list of parameters, in which case those parameters are sent to
+     * the moodle_select_menu constructor.
+     *
+     * @param moodle_select_menu $selectmenu a moodle_select_menu that describes
+     *      the select menu you want output.
+     * @return string the HTML for the <select>
+     */
+    public function select_menu($selectmenu) {
+        $selectmenu = clone($selectmenu);
+        $selectmenu->prepare();
 
-/// If true, then this theme must have a "pix" 
-/// subdirectory that contains copies of all 
-/// files from the moodle/pix directory, plus a
-/// "pix/mod" directory containing all the icons 
-/// for all the activity modules.
+        if ($selectmenu->nothinglabel) {
+            $selectmenu->options = array($selectmenu->nothingvalue => $selectmenu->nothinglabel) +
+                    $selectmenu->options;
+        }
 
+        if (empty($selectmenu->id)) {
+            $selectmenu->id = 'menu' . str_replace(array('[', ']'), '', $selectmenu->name);
+        }
 
-///$THEME->rarrow = '&#x25BA;' //OR '&rarr;';
-///$THEME->larrow = '&#x25C4;' //OR '&larr;';
-///$CFG->block_search_button = link_arrow_right(get_string('search'), $url='', $accesshide=true);
-///
-/// Accessibility: Right and left arrow-like characters are
-/// used in the breadcrumb trail, course navigation menu 
-/// (previous/next activity), calendar, and search forum block.
-///
-/// If the theme does not set characters, appropriate defaults
-/// are set by (lib/weblib.php:check_theme_arrows). The suggestions
-/// above are 'silent' in a screen-reader like JAWS. Please DO NOT
-/// use &lt; &gt; &raquo; - these are confusing for blind users.
-////////////////////////////////////////////////////////////////////////////////
+        $attributes = array(
+            'name' => $selectmenu->name,
+            'id' => $selectmenu->id,
+            'class' => $selectmenu->get_classes_string(),
+            'onchange' => $selectmenu->script,
+        );
+        if ($selectmenu->disabled) {
+            $attributes['disabled'] = 'disabled';
+        }
+        if ($selectmenu->tabindex) {
+            $attributes['tabindex'] = $tabindex;
+        }
 
+        if ($selectmenu->listbox) {
+            if (is_integer($selectmenu->listbox)) {
+                $size = $selectmenu->listbox;
+            } else {
+                $size = min($selectmenu->maxautosize, count($selectmenu->options));
+            }
+            $attributes['size'] = $size;
+            if ($selectmenu->multiple) {
+                $attributes['multiple'] = 'multiple';
+            }
+        }
 
-    public $blockregions = array('side-pre', 'side-post');
-    public $defaultblockregion = 'side-post';
-/// Areas where blocks may appear on any page that uses this theme. For each
-/// region you list in $THEME->blockregions you must call blocks_print_group
-/// with that region id somewhere in header.html or footer.html.
-/// defaultblockregion is the region where new blocks will be added, and
-/// where any blocks in unrecognised regions will be shown. (Suppose someone
-/// added a block when anther theme was selected).
-////////////////////////////////////////////////////////////////////////////////
+        $html = $this->output_start_tag('select', $attributes) . "\n";
+        foreach ($selectmenu->options as $value => $label) {
+            $attributes = array('value' => $value);
+            if ((string)$value == (string)$selectmenu->selectedvalue ||
+                    (is_array($selectmenu->selectedvalue) && in_array($value, $selectmenu->selectedvalue))) {
+                $attributes['selected'] = 'selected';
+            }
+            $html .= '    ' . $this->output_tag('option', $attributes, s($label)) . "\n";
+        }
+        $html .= $this->output_end_tag('select') . "\n";
 
-    /** @var string the name of this theme. Set automatically. */
-    public $name;
-    /** @var string the folder where this themes fiels are stored. $CFG->themedir . '/' . $this->name */
-    public $dir;
+        return $html;
+    }
 
-    /** @var string Name of the renderer factory class to use. */
-    public $rendererfactory = 'standard_renderer_factory';
-    /** @var renderer_factory Instance of the renderer_factory class. */
-    protected $rf = null;
+    // TODO choose_from_menu_nested
+
+    // TODO choose_from_radio
 
     /**
-     * If you want to do custom processing on the CSS before it is output (for
-     * example, to replace certain variable names with particular values) you can
-     * give the name of a function here.
-     *
-     * There are two functions avaiable that you may wish to use (defined in lib/outputlib.php):
-     *   output_css_replacing_constants
-     *   output_css_for_css_edit
-     * If you wish to write your own function, use those two as examples, and it
-     * should be clear what you have to do.
-     *
-     * @var string the name of a function.
+     * Output an error message. By default wraps the error message in <span class="error">.
+     * If the error message is blank, nothing is output.
+     * @param $message the error message.
+     * @return string the HTML to output.
      */
-    public $customcssoutputfunction = null;
+    public function error_text($message) {
+        if (empty($message)) {
+            return '';
+        }
+        return $this->output_tag('span', array('class' => 'error'), $message);
+    }
 
     /**
-     * Load the config.php file for a particular theme, and return an instance
-     * of this class. (That is, this is a factory method.)
+     * Do not call this function directly.
      *
-     * @param string $themename the name of the theme.
-     * @return theme_config an instance of this class.
+     * 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 static function load($themename) {
-        global $CFG;
+    public function fatal_error($message, $moreinfourl, $link, $backtrace,
+                $debuginfo = null, $showerrordebugwarning = false) {
 
-        // We have to use the variable name $THEME (upper case) becuase that
-        // is what is used in theme config.php files.
+        $output = '';
 
-        // Set some other standard properties of the theme.
-        $THEME = new theme_config;
-        $THEME->name = $themename;
-        $THEME->dir = $CFG->themedir . '/' . $themename;
+        if ($this->has_started()) {
+            $output .= $this->opencontainers->pop_all_but_last();
+        } else {
+            // Header not yet printed
+            @header('HTTP/1.0 404 Not Found');
+            $this->page->set_title(get_string('error'));
+            $output .= $this->header();
+        }
 
-        // Load up the theme config
-        $configfile = $THEME->dir . '/config.php';
-        if (!is_readable($configfile)) {
-            throw new coding_exception('Cannot use theme ' . $themename .
-                    '. The file ' . $configfile . ' does not exist or is not readable.');
+        $message = '<p class="errormessage">' . $message . '</p>'.
+                '<p class="errorcode"><a href="' . $moreinfourl . '">' .
+                get_string('moreinformation') . '</a></p>';
+        $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), 'notifytiny');
+            }
         }
-        include($configfile);
 
-        $THEME->update_legacy_information();
+        if (!empty($link)) {
+            $output .= $this->continue_button($link);
+        }
 
-        return $THEME;
+        $output .= $this->footer();
+
+        // Padding to encourage IE to display our error page, rather than its own.
+        $output .= str_repeat(' ', 512);
+
+        return $output;
     }
 
     /**
-     * Get the renderer for a part of Moodle for this theme.
-     * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
-     * @param moodle_page $page the page we are rendering
-     * @return moodle_renderer_base the requested renderer.
+     * 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 get_renderer($module, $page) {
-        if (is_null($this->rf)) {
-            if (CLI_SCRIPT) {
-                $classname = 'cli_renderer_factory';
-            } else {
-                $classname = $this->rendererfactory;
-            }
-            $this->rf = new $classname($this);
-        }
-
-        return $this->rf->get_renderer($module, $page);
+    public function notification($message, $classes = 'notifyproblem') {
+        return $this->output_tag('div', array('class' =>
+                moodle_renderer_base::prepare_classes($classes)), clean_text($message));
     }
 
     /**
-     * Set the variable $CFG->pixpath and $CFG->modpixpath to be the right
-     * ones for this theme.
+     * 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 setup_cfg_paths() {
-        global $CFG;
-        if (!empty($CFG->smartpix)) {
-            if ($CFG->slasharguments) {
-                // Use this method if possible for better caching
-                $extra = '';
-            } else {
-                $extra = '?file=';
-            }
-            $CFG->pixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name;
-            $CFG->modpixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name . '/mod';
-
-        } else if (empty($THEME->custompix)) {
-            $CFG->pixpath = $CFG->httpswwwroot . '/pix';
-            $CFG->modpixpath = $CFG->httpswwwroot . '/mod';
-
-        } else {
-            $CFG->pixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix';
-            $CFG->modpixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix/mod';
+    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));
     }
 
     /**
-     * Get the list of stylesheet URLs that need to go in the header for this theme.
-     * @return array of URLs.
+     * 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 get_stylesheet_urls() {
-        global $CFG;
-
-        // Put together the parameters
-        $params = '?for=' . $this->name;
+    public function skip_link_target($id = 'maincontent') {
+        return $this->output_tag('span', array('id' => $id), '');
+    }
 
-        // Stylesheets, in order (standard, parent, this - some of which may be the same).
-        $stylesheets = array();
-        if ($this->name != 'standard' && $this->standardsheets) {
-            $stylesheets[] = $CFG->httpsthemewww . '/standard/styles.php' . $params;
-        }
-        if (!empty($this->parent)) {
-            $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/styles.php' . $params;
+    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);
+    }
 
-        // Pass on the current language, if it will be needed.
-        if (!empty($this->langsheets)) {
-            $params .= '&lang=' . current_language();
-        }
-        $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/styles.php' . $params;
+    public function box($contents, $classes = 'generalbox', $id = '') {
+        return $this->box_start($classes, $id) . $contents . $this->box_end();
+    }
 
-        // Additional styles for right-to-left languages.
-        if (right_to_left()) {
-            $stylesheets[] = $CFG->httpsthemewww . '/standard/rtl.css';
+    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)));
+    }
 
-            if (!empty($this->parent) && file_exists($CFG->themedir . '/' . $this->parent . '/rtl.css')) {
-                $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/rtl.css';
-            }
+    public function box_end() {
+        return $this->opencontainers->pop('box');
+    }
 
-            if (file_exists($this->dir . '/rtl.css')) {
-                $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/rtl.css';
-            }
-        }
+    public function container($contents, $classes = '', $id = '') {
+        return $this->container_start($classes, $id) . $contents . $this->container_end();
+    }
 
-        return $stylesheets;
+    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');
     }
 
     /**
-     * This methon looks a the settings that have been loaded, to see whether
-     * any legacy things are being used, and outputs warning and tries to update
-     * things to use equivalent newer settings.
+     * At the moment we frequently have a problem with $CFG->pixpath not being
+     * initialised when it is needed. Unfortunately, there is no nice way to handle
+     * this. I think we need to replace $CFG->pixpath with something like $OUTPUT->icon(...).
+     * However, until then, we need a way to force $CFG->pixpath to be initialised,
+     * to fix the error messages, and that is what this function if for.
      */
-    protected function update_legacy_information() {
-        if (!empty($this->customcorners)) {
-            // $THEME->customcorners is deprecated but we provide support for it via the
-            // custom_corners_renderer_factory class in lib/deprecatedlib.php
-            debugging('$THEME->customcorners is deprecated. Please use the new $THEME->rendererfactory ' .
-                    'to control HTML generation. Please use $this->rendererfactory = \'custom_corners_renderer_factory\'; ' .
-                    'in your config.php file instead.', DEBUG_DEVELOPER);
-            $this->rendererfactory = 'custom_corners_renderer_factory';
-        }
-
-        if (!empty($this->cssconstants)) {
-            debugging('$THEME->cssconstants is deprecated. Please use ' .
-                    '$THEME->customcssoutputfunction = \'output_css_replacing_constants\'; ' .
-                    'in your config.php file instead.', DEBUG_DEVELOPER);
-            $this->customcssoutputfunction = 'output_css_replacing_constants';
-        }
-
-        if (!empty($this->CSSEdit)) {
-            debugging('$THEME->CSSEdit is deprecated. Please use ' .
-                    '$THEME->customcssoutputfunction = \'output_css_for_css_edit\'; ' .
-                    'in your config.php file instead.', DEBUG_DEVELOPER);
-            $this->customcssoutputfunction = 'output_css_for_css_edit';
-        }
+    public function initialise_deprecated_cfg_pixpath() {
+        // Actually, we don't have to do anything here. Just calling any method
+        // of $OBJECT  is enough. However, if the only reason you are calling
+        // an $OUTPUT method is to get $CFG->pixpath initialised, please use this
+        // method, so we can find them and clean them up later once we have
+        // found a better replacement for $CFG->pixpath.
     }
 }
 
diff --git a/lib/setup.php b/lib/setup.php
index 98785ab461..7230c99853 100644
--- a/lib/setup.php
+++ b/lib/setup.php
@@ -110,7 +110,7 @@ global $OUTPUT;
 /**
  * $THEME is a global that defines the current theme.
  *
- * @global object $THEME
+ * @global theme_config $THEME
  * @name THEME
  */
 global $THEME;
diff --git a/lib/simpletest/testoutputlib.php b/lib/simpletest/testoutputlib.php
index 509e2b4e80..dfa5f0ed9c 100644
--- a/lib/simpletest/testoutputlib.php
+++ b/lib/simpletest/testoutputlib.php
@@ -30,16 +30,60 @@ if (!defined('MOODLE_INTERNAL')) {
 require_once($CFG->libdir . '/outputlib.php');
 
 
-// TODO this is needed until MDL-16438 is committed.
-function get_plugin_dir($module) {
-    global $CFG;
-    return $CFG->dirroot;
+/**
+ * Unit tests for the pix_icon_finder class.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class pix_icon_finder_test extends UnitTestCase {
+    public function test_old_icon_url() {
+        global $CFG;
+        $if = new pix_icon_finder(new theme_config());
+        $this->assertEqual($CFG->httpswwwroot . '/pix/i/course.gif', $if->old_icon_url('i/course'));
+    }
+
+    /* Implement interface method. */
+    public function test_mod_icon_url() {
+        global $CFG;
+        $if = new pix_icon_finder(new theme_config());
+        $this->assertEqual($CFG->httpswwwroot . '/mod/quiz/icon.gif', $if->mod_icon_url('icon', 'quiz'));
+    }
+}
+
+
+/**
+ * Unit tests for the standard_renderer_factory class.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class theme_icon_finder_test extends UnitTestCase {
+    public function test_old_icon_url_test() {
+        global $CFG;
+        $theme = new theme_config();
+        $theme->name = 'test';
+        $if = new theme_icon_finder($theme);
+        $this->assertEqual($CFG->httpsthemewww . '/test/pix/i/course.gif', $if->old_icon_url('i/course'));
+    }
+
+    /* Implement interface method. */
+    public function test_mod_icon_url() {
+        global $CFG;
+        $theme = new theme_config();
+        $theme->name = 'test';
+        $if = new theme_icon_finder($theme);
+        $this->assertEqual($CFG->httpsthemewww . '/test/pix/mod/quiz/icon.gif', $if->mod_icon_url('icon', 'quiz'));
+    }
 }
 
 
 /**
  * Subclass of renderer_factory_base for testing. Implement abstract method and
  * count calls, so we can test caching behaviour.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class testable_renderer_factory extends renderer_factory_base {
     public $createcalls = array();
@@ -61,8 +105,11 @@ class testable_renderer_factory extends renderer_factory_base {
 
 /**
  * Renderer class for testing.
+ *
+ * @copyright 2009 Tim Hunt
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class moodle_test_renderer extends moodle_core_renderer {
+class moodle_mod_test_renderer extends moodle_core_renderer {
     public function __construct($containerstack, $page) {
         parent::__construct($containerstack, $page, null);
     }
@@ -121,9 +168,9 @@ class renderer_factory_base_test extends UnitTestCase {
         // Set up.
         $factory = new testable_renderer_factory();
         // Exercise SUT.
-        $classname = $factory->standard_renderer_class_for_module('test');
+        $classname = $factory->standard_renderer_class_for_module('mod_test');
         // Verify outcome
-        $this->assertEqual('moodle_test_renderer', $classname);
+        $this->assertEqual('moodle_mod_test_renderer', $classname);
     }
 
     public function test_standard_renderer_class_for_module_unknown() {
@@ -161,8 +208,8 @@ class standard_renderer_factory_test extends UnitTestCase {
     }
 
     public function test_get_test_renderer() {
-        $renderer = $this->factory->get_renderer('test', new moodle_page);
-        $this->assertIsA($renderer, 'moodle_test_renderer');
+        $renderer = $this->factory->get_renderer('mod_test', new moodle_page);
+        $this->assertIsA($renderer, 'moodle_mod_test_renderer');
     }
 }
 
@@ -192,8 +239,8 @@ class custom_corners_renderer_factory_test extends UnitTestCase {
     }
 
     public function test_get_test_renderer() {
-        $renderer = $this->factory->get_renderer('test', new moodle_page);
-        $this->assertIsA($renderer, 'moodle_test_renderer');
+        $renderer = $this->factory->get_renderer('mod_test', new moodle_page);
+        $this->assertIsA($renderer, 'moodle_mod_test_renderer');
     }
 }
 
@@ -304,25 +351,25 @@ class theme_overridden_renderer_factory_test extends UnitTestCase {
         $factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
 
         // Exercise SUT.
-        $renderer = $factory->get_renderer('test', new moodle_page);
+        $renderer = $factory->get_renderer('mod_test', new moodle_page);
 
         // Verify outcome
-        $this->assertIsA($renderer, 'moodle_test_renderer');
+        $this->assertIsA($renderer, 'moodle_mod_test_renderer');
     }
 
     public function test_get_renderer_overridden() {
         // Set up - be very careful because the class under test uses require-once. Pick a unique theme name.
         $theme = $this->make_theme('testrenderertheme');
         $this->write_renderers_file($theme, '
-        class testrenderertheme_test_renderer extends moodle_test_renderer {
+        class testrenderertheme_mod_test_renderer extends moodle_mod_test_renderer {
         }');
         $factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
 
         // Exercise SUT.
-        $renderer = $factory->get_renderer('test', new moodle_page);
+        $renderer = $factory->get_renderer('mod_test', new moodle_page);
 
         // Verify outcome
-        $this->assertIsA($renderer, 'testrenderertheme_test_renderer');
+        $this->assertIsA($renderer, 'testrenderertheme_mod_test_renderer');
     }
 
     public function test_get_renderer_overridden_in_parent() {
@@ -519,18 +566,18 @@ class template_renderer_factory_test extends UnitTestCase {
         $theme = $this->make_theme('mytheme');
         $theme->parent = 'parenttheme';
         $this->make_theme_template_dir('mytheme', 'core');
-        $this->make_theme_template_dir('parenttheme', 'test');
-        $this->make_theme_template_dir('standardtemplate', 'test');
+        $this->make_theme_template_dir('parenttheme', 'mod_test');
+        $this->make_theme_template_dir('standardtemplate', 'mod_test');
         $factory = new testable_template_renderer_factory($theme);
 
         // Exercise SUT.
-        $renderer = $factory->get_renderer('test', $this->page);
+        $renderer = $factory->get_renderer('mod_test', $this->page);
 
         // Verify outcome
-        $this->assertEqual('moodle_test_renderer', $renderer->get_copied_class());
+        $this->assertEqual('moodle_mod_test_renderer', $renderer->get_copied_class());
         $this->assertEqual(array(
-                $CFG->themedir . '/parenttheme/templates/test',
-                $CFG->themedir . '/standardtemplate/templates/test'),
+                $CFG->themedir . '/parenttheme/templates/mod_test',
+                $CFG->themedir . '/standardtemplate/templates/mod_test'),
                 $renderer->get_search_paths());
     }
 }
@@ -709,7 +756,7 @@ class template_renderer_test extends UnitTestCase {
         $this->templatefolder = $CFG->dataroot . '/temp/template_renderer_fixtures/test';
         make_upload_directory('temp/template_renderer_fixtures/test');
         $page = new moodle_page;
-        $this->renderer = new template_renderer('moodle_test_renderer',
+        $this->renderer = new template_renderer('moodle_mod_test_renderer',
                 array($this->templatefolder), $page);
     }