From ebebf55cadeb187b1ad912706f0c382bbfa2b36b Mon Sep 17 00:00:00 2001
From: tjhunt
Date: Thu, 2 Jul 2009 08:49:25 +0000
Subject: [PATCH] output: MDL-19690 icon_finder classes and
$OUTPUT->mod/old_icon_url
This is ready to replace $CFG->pixpath and $CFG->modpixpath soon.
---
lib/outputlib.php | 2834 ++++++++++++++++--------------
lib/setup.php | 2 +-
lib/simpletest/testoutputlib.php | 93 +-
3 files changed, 1602 insertions(+), 1327 deletions(-)
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
- *
- *
- *
Hello !
- *
- *
- * @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
+/// 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
+/// 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
+/// 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 = '►' //OR '→';
+///$THEME->larrow = '◄' //OR '←';
+///$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 < > » - 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 )
- * before outputting the error message.
- * @return string the HTML requried to close any open containers inside .
+ * 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 = '' . "\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 = '' . "\n";
- if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
- // Firefox and other browsers that can cope natively with XHTML.
- $this->contenttype = 'application/xhtml+xml; charset=utf-8';
-
- } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
- // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet.
- $this->contenttype = 'application/xml; charset=utf-8';
- $prolog .= 'httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
-
- } else {
- $prolog = '';
+ // 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 .= '' . "\n";
- $output .= '' . "\n";
- if (!$this->page->cacheable) {
- $output .= '' . "\n";
- $output .= '' . "\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 = '';
- $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 .= '
';
- }
+ /* 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('&', '&', $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 = "Error output, so disabling automatic redirect.
" . $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 = ''."\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
+ *
+ *
+ *
Hello !
+ *
+ *
+ * @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('', self::END_HTML_TOKEN . '', $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
- //
, then we add in standard tags that make it look like a normal
- // page block including the h2 for accessibility
- if (strpos($bc->heading, '
') === 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