mirror of
https://github.com/moodle/moodle.git
synced 2025-04-22 08:55:15 +02:00
themes: MDL-19077 implement the renderer_factory instrastructure.
This is part of http://docs.moodle.org/en/Development:Theme_engines_for_Moodle%3F The concept is that all the print_... functions in weblib get replaced by methods on a moodle_core_renderer class. Then, the theme can choose whether to use the standard moodle_core_renderer class, or another implemenatation of its own choosing, to generate different HTML. Also, becuase Moodle is modular, we may need a moodle_mod_forum_renderer and so on. In order for the theme to be able to choose which renderers get created, we introduce the concept of a renderer factory, as in the factory design pattern. The theme will choose which factory should be used, and that then creates the renderer objects based on the module name. This commit includes 4 types of factory: * standard_renderer_factory * custom_corners_renderer_factory * theme_overridden_renderer_factory * template_renderer_factory All this with unit tests and PHP doc comments. Note, this new code is not actually used yet. Still todo: 1. actually define the moodle_core_renderer class, and deprecate a lot of weblib functions. 2. make theme_setup initialise everything, so it is used.
This commit is contained in:
parent
78ff29832d
commit
571fa82824
647
lib/outputlib.php
Normal file
647
lib/outputlib.php
Normal file
@ -0,0 +1,647 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
/**
|
||||
* Functions for generating the HTML that Moodle should output.
|
||||
*
|
||||
* Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
|
||||
* for an overview.
|
||||
*
|
||||
* @package moodlecore
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later (5)
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A renderer factory is just responsible for creating an appropriate renderer
|
||||
* for any given part of Moodle.
|
||||
*
|
||||
* 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.)
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 2.0
|
||||
*/
|
||||
interface renderer_factory {
|
||||
/**
|
||||
* Return the renderer for a particular part of Moodle.
|
||||
*
|
||||
* The renderer interfaces are defined by classes called moodle_..._renderer
|
||||
* where ... is the name of the module, which, will be defined in this file
|
||||
* for core parts of Moodle, and in a file called renderer.php for plugins.
|
||||
*
|
||||
* There is no separate interface definintion for renderers. Instead we
|
||||
* take advantage of PHP being a dynamic languages. The renderer returned
|
||||
* does not need to be a subclass of the moodle_..._renderer base class, it
|
||||
* just needs to impmenent the same interface. This is sometimes called
|
||||
* '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'.
|
||||
* @return object an object implementing the requested renderer interface.
|
||||
*/
|
||||
public function get_renderer($module);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/** The theme we are rendering for. */
|
||||
protected $theme;
|
||||
|
||||
/** The page we are doing output for. */
|
||||
protected $page;
|
||||
|
||||
/** Used to cache renderers as they are created. */
|
||||
protected $renderers = 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, $page) {
|
||||
$this->theme = $theme;
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/* Implement the interface method. */
|
||||
public function get_renderer($module) {
|
||||
// Cache the renderers by module name, and delegate the actual
|
||||
// construction to the create_renderer method.
|
||||
if (!array_key_exists($module, $this->renderers)) {
|
||||
$this->renderers[$module] = $this->create_renderer($module);
|
||||
}
|
||||
|
||||
return $this->renderers[$module];
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should override this method to actually create an instance of
|
||||
* the appropriate renderer class, based on the module name. That is,
|
||||
* this method should implement the same contract as
|
||||
* {@link renderer_factory::get_renderer}.
|
||||
*
|
||||
* @param $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
|
||||
* @return object an object implementing the requested renderer interface.
|
||||
*/
|
||||
abstract public function create_renderer($module);
|
||||
|
||||
/**
|
||||
* 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 $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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* 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, $page) {
|
||||
parent::__construct($theme, $page);
|
||||
}
|
||||
|
||||
/* Implement the subclass method. */
|
||||
public function create_renderer($module) {
|
||||
if ($module == 'core') {
|
||||
return new moodle_core_renderer($this->page->opencontainers);
|
||||
} else {
|
||||
$class = $this->standard_renderer_class_for_module($module);
|
||||
return new $class($this->page->opencontainers, $this->get_renderer('core'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a slight variatoin on the standard_renderer_factory that uses
|
||||
* custom_corners_core_renderer instead of moodle_core_renderer.
|
||||
*
|
||||
* 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 custom_corners_renderer_factory extends standard_renderer_factory {
|
||||
/**
|
||||
* 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, $page) {
|
||||
parent::__construct($theme, $page);
|
||||
$this->renderers = array('core' => new custom_corners_core_renderer($this->page->opencontainers));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, $page) {
|
||||
global $CFG;
|
||||
parent::__construct($theme, $page);
|
||||
|
||||
// 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 create_renderer($module) {
|
||||
foreach ($this->prefixes as $prefix) {
|
||||
$classname = $prefix . $module . '_renderer';
|
||||
if (class_exists($classname)) {
|
||||
if ($module == 'core') {
|
||||
return new $classname($this->page->opencontainers);
|
||||
} else {
|
||||
return new $classname($this->page->opencontainers, $this->get_renderer('core'));
|
||||
}
|
||||
}
|
||||
}
|
||||
return parent::create_renderer($module);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, $page) {
|
||||
global $CFG;
|
||||
parent::__construct($theme, $page);
|
||||
|
||||
// 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';
|
||||
}
|
||||
|
||||
/* Implement the subclass method. */
|
||||
public function create_renderer($module) {
|
||||
// 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, $this->page->opencontainers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple base class for Moodle renderers.
|
||||
*
|
||||
* Tracks the xhtml_container_stack to use, which is passed in in the constructor.
|
||||
*
|
||||
* @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 $containerstack;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param $containerstack the xhtml_container_stack to use.
|
||||
*/
|
||||
public function __construct($containerstack) {
|
||||
$this->containerstack = $containerstack;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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 $containerstack the xhtml_container_stack to use.
|
||||
*/
|
||||
public function __construct($copiedclass, $searchpaths, $containerstack) {
|
||||
parent::__construct($containerstack);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the template for a given method of the renderer class we are copying,
|
||||
* using the arguments passed.
|
||||
* @param string $method the method that was called.
|
||||
* @param array $arguments the arguments that were passed to it.
|
||||
* @return string the HTML to be output.
|
||||
*/
|
||||
protected function process_template($method, $arguments) {
|
||||
if (!$this->copiedclass->hasMethod($method) ||
|
||||
!$this->copiedclass->getMethod($method)->isPublic()) {
|
||||
throw new coding_exception('Unknown method ' . $method);
|
||||
}
|
||||
|
||||
// Find the template file for this method.
|
||||
$template = $this->find_template($method);
|
||||
|
||||
// Use the reflection API to find out what variable names the arguments
|
||||
// should be stored in, and fill in any missing ones with the defaults.
|
||||
$namedarguments = array();
|
||||
$expectedparams = $this->copiedclass->getMethod($method)->getParameters();
|
||||
foreach ($expectedparams as $param) {
|
||||
$paramname = $param->getName();
|
||||
if (!empty($arguments)) {
|
||||
$namedarguments[$paramname] = array_shift($arguments);
|
||||
} else if ($param->isDefaultValueAvailable()) {
|
||||
$namedarguments[$paramname] = $param->getDefaultValue();
|
||||
} else {
|
||||
throw new coding_exception('Missing required argument ' . $paramname);
|
||||
}
|
||||
}
|
||||
|
||||
// Actually render the template.
|
||||
return $this->render_template($template, $namedarguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually do the work of rendering the template.
|
||||
* @param $_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.
|
||||
|
||||
// Set up the global variables that the template may wish to access.
|
||||
global $CFG, $PAGE, $THEME;
|
||||
|
||||
// And the parameters from the function call.
|
||||
extract($_namedarguments);
|
||||
|
||||
// Include the template, capturing the output.
|
||||
ob_start();
|
||||
include($_template);
|
||||
$_result = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the folders in {@link $searchpaths} to try to find a template for
|
||||
* this method name. Throws an exception if one cannot be found.
|
||||
* @param string $method the method name.
|
||||
* @return string the full path of the template to use.
|
||||
*/
|
||||
protected function find_template($method) {
|
||||
foreach ($this->searchpaths as $path) {
|
||||
$filename = $path . '/' . $method . '.php';
|
||||
if (file_exists($filename)) {
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle methods like print_box_start by using the print_box template,
|
||||
* splitting the result, 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->containerstack->push($template, $end);
|
||||
return $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle methods like print_box_end, we just need to pop the end HTML from
|
||||
* the stack.
|
||||
* @param string $method the method that was called, with _end stripped off.
|
||||
* @param array $arguments not used. Assumed to be irrelevant.
|
||||
* @return string the HTML to be output.
|
||||
*/
|
||||
protected function process_end($template, $arguments) {
|
||||
return $this->containerstack->pop($template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 $opencontainsers = array();
|
||||
|
||||
/**
|
||||
* 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->opencontainsers, $container);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->opencontainsers)) {
|
||||
debugging('There are no more open containers. This suggests there is a nesting problem.', DEBUG_DEVELOPER);
|
||||
return;
|
||||
}
|
||||
|
||||
$container = array_pop($this->opencontainsers);
|
||||
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);
|
||||
}
|
||||
return $container->closehtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 pop_all_but_last() {
|
||||
$output = '';
|
||||
while (count($this->opencontainsers) > 1) {
|
||||
$container = array_pop($this->opencontainsers);
|
||||
$output .= $container->closehtml;
|
||||
}
|
||||
return $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 discard() {
|
||||
$this->opencontainsers = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->opencontainsers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugging('Some containers were left open. This suggests there is a nesting problem.', DEBUG_DEVELOPER);
|
||||
echo $this->pop_all_but_last();
|
||||
$container = array_pop($this->opencontainsers);
|
||||
echo $container->closehtml;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A renderer for the custom corner theme, and other themes based on it.
|
||||
*
|
||||
* Generates the slightly different HTML that the custom corners theme wants.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 2.0
|
||||
*/
|
||||
class custom_corners_core_renderer extends moodle_core_renderer {
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
* @since Moodle 2.0
|
||||
*
|
||||
* @property-read page_requirements_manager $requires Tracks resources (for example required .css and .js files) required by this page.
|
||||
* @property-read xhtml_container_stack $opencontainers Tracks open XHTML tags. Helps us generate well-formed XML, even in the face of errors.
|
||||
*/
|
||||
class moodle_page {
|
||||
/**#@+ Tracks the where we are in the generation of the page. */
|
||||
@ -106,6 +107,8 @@ class moodle_page {
|
||||
|
||||
protected $_othereditingcaps = array();
|
||||
|
||||
protected $_opencontainers = null;
|
||||
|
||||
/**
|
||||
* This is simply to improve backwards compatability. If old code relies on
|
||||
* a page class that implements print_header, or complex logic in
|
||||
@ -297,8 +300,8 @@ class moodle_page {
|
||||
}
|
||||
|
||||
/**
|
||||
* Please do not call this method directly, use the ->blocks syntax. @see __get().
|
||||
* @return blocks_manager the blocks manager object for this page.
|
||||
* Please do not call this method directly, use the ->requires syntax. @see __get().
|
||||
* @return page_requirements_manager tracks the JavaScript, CSS files, etc. required by this page.
|
||||
*/
|
||||
public function get_requires() {
|
||||
global $CFG;
|
||||
@ -308,6 +311,18 @@ class moodle_page {
|
||||
return $this->_requires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Please do not call this method directly, use the ->opencontainers syntax. @see __get().
|
||||
* @return xhtml_container_stack Tracks the open XHTML tags on this page.
|
||||
*/
|
||||
public function get_opencontainers() {
|
||||
global $CFG;
|
||||
if (is_null($this->_opencontainers)) {
|
||||
$this->_opencontainers = new xhtml_container_stack();
|
||||
}
|
||||
return $this->_opencontainers;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP overloading magic to make the $PAGE->course syntax work by redirecting
|
||||
* it to the corresponding $PAGE->get_course() method if there is one, and
|
||||
|
775
lib/simpletest/testoutputlib.php
Normal file
775
lib/simpletest/testoutputlib.php
Normal file
@ -0,0 +1,775 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for (some of) ../outputlib.php.
|
||||
*
|
||||
* @package moodlecore
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later (5)
|
||||
*/
|
||||
|
||||
if (!defined('MOODLE_INTERNAL')) {
|
||||
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subclass of renderer_factory_base for testing. Implement abstract method and
|
||||
* count calls, so we can test caching behaviour.
|
||||
*/
|
||||
class testable_renderer_factory extends renderer_factory_base {
|
||||
public $createcalls = array();
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(null, null);
|
||||
}
|
||||
|
||||
public function create_renderer($module) {
|
||||
$this->createcalls[] = $module;
|
||||
return new moodle_core_renderer(new xhtml_container_stack());
|
||||
}
|
||||
|
||||
public function standard_renderer_class_for_module($module) {
|
||||
return parent::standard_renderer_class_for_module($module);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renderer class for testing.
|
||||
*/
|
||||
class moodle_test_renderer extends moodle_core_renderer {
|
||||
public function __construct($containerstack) {
|
||||
parent::__construct($containerstack);
|
||||
}
|
||||
|
||||
public function greeting($name = 'world') {
|
||||
return '<h1>Hello ' . $name . '!</h1>';
|
||||
}
|
||||
|
||||
public function box($content, $id = '') {
|
||||
return box_start($id) . $content . box_end();
|
||||
}
|
||||
|
||||
public function box_start($id = '') {
|
||||
if ($id) {
|
||||
$id = ' id="' . $id . '"';
|
||||
}
|
||||
$this->containerstack->push('box', '</div>');
|
||||
return '<div' . $id . '>';
|
||||
}
|
||||
|
||||
public function box_end() {
|
||||
return $this->containerstack->pop('box');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for the requriement_base base class.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class renderer_factory_base_test extends UnitTestCase {
|
||||
public function test_get_calls_create() {
|
||||
// Set up.
|
||||
$factory = new testable_renderer_factory();
|
||||
// Exercise SUT.
|
||||
$renderer = $factory->get_renderer('modulename');
|
||||
// Verify outcome
|
||||
$this->assertEqual(array('modulename'), $factory->createcalls);
|
||||
}
|
||||
|
||||
public function test_get_caches_repeat_calls() {
|
||||
// Set up.
|
||||
$factory = new testable_renderer_factory();
|
||||
// Exercise SUT.
|
||||
$renderer1 = $factory->get_renderer('modulename');
|
||||
$renderer2 = $factory->get_renderer('modulename');
|
||||
// Verify outcome
|
||||
$this->assertEqual(array('modulename'), $factory->createcalls);
|
||||
$this->assertIdentical($renderer1, $renderer2);
|
||||
}
|
||||
|
||||
public function test_standard_renderer_class_for_module_core() {
|
||||
// Set up.
|
||||
$factory = new testable_renderer_factory();
|
||||
// Exercise SUT.
|
||||
$classname = $factory->standard_renderer_class_for_module('core');
|
||||
// Verify outcome
|
||||
$this->assertEqual('moodle_core_renderer', $classname);
|
||||
}
|
||||
|
||||
public function test_standard_renderer_class_for_module_test() {
|
||||
// Set up.
|
||||
$factory = new testable_renderer_factory();
|
||||
// Exercise SUT.
|
||||
$classname = $factory->standard_renderer_class_for_module('test');
|
||||
// Verify outcome
|
||||
$this->assertEqual('moodle_test_renderer', $classname);
|
||||
}
|
||||
|
||||
public function test_standard_renderer_class_for_module_unknown() {
|
||||
// Set up.
|
||||
$factory = new testable_renderer_factory();
|
||||
$this->expectException();
|
||||
// Exercise SUT.
|
||||
$classname = $factory->standard_renderer_class_for_module('something_that_does_not_exist');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 standard_renderer_factory_test extends UnitTestCase {
|
||||
protected $factory;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$page = new stdClass;
|
||||
$page->opencontainers = new xhtml_container_stack();
|
||||
$this->factory = new standard_renderer_factory(null, $page);
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
$this->factory = null;
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_get_core_renderer() {
|
||||
$renderer = $this->factory->get_renderer('core');
|
||||
$this->assertIsA($renderer, 'moodle_core_renderer');
|
||||
}
|
||||
|
||||
public function test_get_test_renderer() {
|
||||
$renderer = $this->factory->get_renderer('test');
|
||||
$this->assertIsA($renderer, 'moodle_test_renderer');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for the custom_corners_renderer_factory class.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class custom_corners_renderer_factory_test extends UnitTestCase {
|
||||
protected $factory;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$page = new stdClass;
|
||||
$page->opencontainers = new xhtml_container_stack();
|
||||
$this->factory = new custom_corners_renderer_factory(null, $page);
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
$this->factory = null;
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_get_core_renderer() {
|
||||
$renderer = $this->factory->get_renderer('core');
|
||||
$this->assertIsA($renderer, 'custom_corners_core_renderer');
|
||||
}
|
||||
|
||||
public function test_get_test_renderer() {
|
||||
$renderer = $this->factory->get_renderer('test');
|
||||
$this->assertIsA($renderer, 'moodle_test_renderer');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test-specific subclass that implements a getter for $prefixes.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class testable_theme_overridden_renderer_factory extends theme_overridden_renderer_factory {
|
||||
public function get_prefixes() {
|
||||
return $this->prefixes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for the theme_overridden_renderer_factory class.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class theme_overridden_renderer_factory_test extends UnitTestCase {
|
||||
protected $originalcfgthemedir;
|
||||
protected $workspace;
|
||||
protected $page;
|
||||
protected $foldertocleanup = null;
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
parent::setUp();
|
||||
$this->originalcfgthemedir = $CFG->themedir;
|
||||
|
||||
$this->workspace = 'temp/theme_overridden_renderer_factory_fixtures';
|
||||
make_upload_directory($this->workspace);
|
||||
$CFG->themedir = $CFG->dataroot . '/' . $this->workspace;
|
||||
$this->foldertocleanup = $CFG->themedir;
|
||||
|
||||
$this->page = new stdClass;
|
||||
$this->page->opencontainers = new xhtml_container_stack();
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
global $CFG;
|
||||
if (!empty($this->foldertocleanup)) {
|
||||
fulldelete($this->foldertocleanup);
|
||||
$this->foldertocleanup = null;
|
||||
}
|
||||
$CFG->themedir = $this->originalcfgthemedir;
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function make_theme($name) {
|
||||
global $CFG;
|
||||
$theme = new stdClass;
|
||||
$theme->name = $name;
|
||||
$theme->dir = $CFG->themedir . '/' . $name;
|
||||
make_upload_directory($this->workspace . '/' . $name);
|
||||
return $theme;
|
||||
}
|
||||
|
||||
protected function write_renderers_file($theme, $code) {
|
||||
$filename = $theme->dir . '/renderers.php';
|
||||
file_put_contents($filename, "<?php\n" . $code);
|
||||
}
|
||||
|
||||
public function test_constructor_theme_with_renderes() {
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
$this->write_renderers_file($theme, '');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array('mytheme_'), $factory->get_prefixes());
|
||||
}
|
||||
|
||||
public function test_constructor_theme_without_renderes() {
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array(), $factory->get_prefixes());
|
||||
}
|
||||
|
||||
public function test_constructor_theme_with_parent() {
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
$theme->parent = 'parenttheme';
|
||||
$parenttheme = $this->make_theme('parenttheme');
|
||||
$this->write_renderers_file($parenttheme, '');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array('parenttheme_'), $factory->get_prefixes());
|
||||
}
|
||||
|
||||
public function test_get_renderer_not_overridden() {
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
$this->write_renderers_file($theme, '');
|
||||
$factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
|
||||
|
||||
// Exercise SUT.
|
||||
$renderer = $factory->get_renderer('test');
|
||||
|
||||
// Verify outcome
|
||||
$this->assertIsA($renderer, 'moodle_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 {
|
||||
}');
|
||||
$factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
|
||||
|
||||
// Exercise SUT.
|
||||
$renderer = $factory->get_renderer('test');
|
||||
|
||||
// Verify outcome
|
||||
$this->assertIsA($renderer, 'testrenderertheme_test_renderer');
|
||||
}
|
||||
|
||||
public function test_get_renderer_overridden_in_parent() {
|
||||
// Set up.
|
||||
$theme = $this->make_theme('childtheme');
|
||||
$theme->parent = 'parentrenderertheme';
|
||||
$parenttheme = $this->make_theme('parentrenderertheme');
|
||||
$this->write_renderers_file($theme, '');
|
||||
$this->write_renderers_file($parenttheme, '
|
||||
class parentrenderertheme_core_renderer extends moodle_core_renderer {
|
||||
}');
|
||||
$factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
|
||||
|
||||
// Exercise SUT.
|
||||
$renderer = $factory->get_renderer('core');
|
||||
|
||||
// Verify outcome
|
||||
$this->assertIsA($renderer, 'parentrenderertheme_core_renderer');
|
||||
}
|
||||
|
||||
public function test_get_renderer_overridden_in_both() {
|
||||
// Set up.
|
||||
$theme = $this->make_theme('ctheme');
|
||||
$theme->parent = 'ptheme';
|
||||
$parenttheme = $this->make_theme('ptheme');
|
||||
$this->write_renderers_file($theme, '
|
||||
class ctheme_core_renderer extends moodle_core_renderer {
|
||||
}');
|
||||
$this->write_renderers_file($parenttheme, '
|
||||
class ptheme_core_renderer extends moodle_core_renderer {
|
||||
}');
|
||||
$factory = new testable_theme_overridden_renderer_factory($theme, $this->page);
|
||||
|
||||
// Exercise SUT.
|
||||
$renderer = $factory->get_renderer('core');
|
||||
|
||||
// Verify outcome
|
||||
$this->assertIsA($renderer, 'ctheme_core_renderer');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test-specific subclass that implements a getter for $searchpaths.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class testable_template_renderer_factory extends template_renderer_factory {
|
||||
public function get_search_paths() {
|
||||
return $this->searchpaths;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for the template_renderer_factory class.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class template_renderer_factory_test extends UnitTestCase {
|
||||
protected $originalcfgthemedir;
|
||||
protected $workspace;
|
||||
protected $page;
|
||||
protected $foldertocleanup = null;
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
parent::setUp();
|
||||
$this->originalcfgthemedir = $CFG->themedir;
|
||||
|
||||
$this->workspace = 'temp/template_renderer_factory_fixtures';
|
||||
make_upload_directory($this->workspace);
|
||||
$CFG->themedir = $CFG->dataroot . '/' . $this->workspace;
|
||||
$this->foldertocleanup = $CFG->themedir;
|
||||
|
||||
$this->page = new stdClass;
|
||||
$this->page->opencontainers = new xhtml_container_stack();
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
global $CFG;
|
||||
if (!empty($this->foldertocleanup)) {
|
||||
fulldelete($this->foldertocleanup);
|
||||
$this->foldertocleanup = null;
|
||||
}
|
||||
$CFG->themedir = $this->originalcfgthemedir;
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function make_theme($name) {
|
||||
global $CFG;
|
||||
$theme = new stdClass;
|
||||
$theme->name = $name;
|
||||
$theme->dir = $CFG->themedir . '/' . $name;
|
||||
make_upload_directory($this->workspace . '/' . $name);
|
||||
return $theme;
|
||||
}
|
||||
|
||||
protected function make_theme_template_dir($name, $module = '') {
|
||||
$path = $this->workspace . '/' . $name . '/templates';
|
||||
if ($module) {
|
||||
$path .= '/' . $module;
|
||||
}
|
||||
make_upload_directory($path);
|
||||
}
|
||||
|
||||
public function test_constructor_standardtemplate() {
|
||||
global $CFG;
|
||||
// Set up.
|
||||
$theme = $this->make_theme('standardtemplate');
|
||||
$this->make_theme_template_dir('standardtemplate');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_template_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array($CFG->themedir . '/standardtemplate/templates'),
|
||||
$factory->get_search_paths());
|
||||
}
|
||||
|
||||
public function test_constructor_mytheme() {
|
||||
global $CFG;
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
$this->make_theme_template_dir('mytheme');
|
||||
$this->make_theme_template_dir('standardtemplate');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_template_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array(
|
||||
$CFG->themedir . '/mytheme/templates',
|
||||
$CFG->themedir . '/standardtemplate/templates'),
|
||||
$factory->get_search_paths());
|
||||
}
|
||||
|
||||
public function test_constructor_mytheme_no_templates() {
|
||||
global $CFG;
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
$this->make_theme_template_dir('standardtemplate');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_template_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array($CFG->themedir . '/standardtemplate/templates'),
|
||||
$factory->get_search_paths());
|
||||
}
|
||||
|
||||
public function test_constructor_mytheme_with_parent() {
|
||||
global $CFG;
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
$theme->parent = 'parenttheme';
|
||||
$this->make_theme_template_dir('mytheme');
|
||||
$this->make_theme_template_dir('parenttheme');
|
||||
$this->make_theme_template_dir('standardtemplate');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_template_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array(
|
||||
$CFG->themedir . '/mytheme/templates',
|
||||
$CFG->themedir . '/parenttheme/templates',
|
||||
$CFG->themedir . '/standardtemplate/templates'),
|
||||
$factory->get_search_paths());
|
||||
}
|
||||
|
||||
public function test_constructor_mytheme_with_parent_no_templates() {
|
||||
global $CFG;
|
||||
// Set up.
|
||||
$theme = $this->make_theme('mytheme');
|
||||
$theme->parent = 'parenttheme';
|
||||
$this->make_theme_template_dir('mytheme');
|
||||
$this->make_theme_template_dir('standardtemplate');
|
||||
|
||||
// Exercise SUT.
|
||||
$factory = new testable_template_renderer_factory($theme, $this->page);
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual(array(
|
||||
$CFG->themedir . '/mytheme/templates',
|
||||
$CFG->themedir . '/standardtemplate/templates'),
|
||||
$factory->get_search_paths());
|
||||
}
|
||||
|
||||
public function test_get_renderer() {
|
||||
global $CFG;
|
||||
// Set up.
|
||||
$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');
|
||||
$factory = new testable_template_renderer_factory($theme, $this->page);
|
||||
|
||||
// Exercise SUT.
|
||||
$renderer = $factory->get_renderer('test');
|
||||
|
||||
// Verify outcome
|
||||
$this->assertEqual('moodle_test_renderer', $renderer->get_copied_class());
|
||||
$this->assertEqual(array(
|
||||
$CFG->themedir . '/parenttheme/templates/test',
|
||||
$CFG->themedir . '/standardtemplate/templates/test'),
|
||||
$renderer->get_search_paths());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for the xhtml_container_stack class.
|
||||
*
|
||||
* These tests assume that developer debug mode is on, which, at the time of
|
||||
* writing, is true. admin/report/unittest/index.php forces it on.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class xhtml_container_stack_test extends UnitTestCase {
|
||||
protected function start_capture() {
|
||||
ob_start();
|
||||
}
|
||||
|
||||
protected function end_capture() {
|
||||
$result = ob_get_contents();
|
||||
ob_end_clean();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function test_push_then_pop() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$stack->push('testtype', '</div>');
|
||||
$html = $stack->pop('testtype');
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('</div>', $html);
|
||||
$this->assertEqual('', $errors);
|
||||
}
|
||||
|
||||
public function test_mismatched_pop_prints_warning() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
$stack->push('testtype', '</div>');
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$html = $stack->pop('mismatch');
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('</div>', $html);
|
||||
$this->assertNotEqual('', $errors);
|
||||
}
|
||||
|
||||
public function test_pop_when_empty_prints_warning() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$html = $stack->pop('testtype');
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('', $html);
|
||||
$this->assertNotEqual('', $errors);
|
||||
}
|
||||
|
||||
public function test_correct_nesting() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$stack->push('testdiv', '</div>');
|
||||
$stack->push('testp', '</p>');
|
||||
$html2 = $stack->pop('testp');
|
||||
$html1 = $stack->pop('testdiv');
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('</p>', $html2);
|
||||
$this->assertEqual('</div>', $html1);
|
||||
$this->assertEqual('', $errors);
|
||||
}
|
||||
|
||||
public function test_pop_all_but_last() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
$stack->push('test1', '</h1>');
|
||||
$stack->push('test2', '</h2>');
|
||||
$stack->push('test3', '</h3>');
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$html = $stack->pop_all_but_last();
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('</h3></h2>', $html);
|
||||
$this->assertEqual('', $errors);
|
||||
// Tear down.
|
||||
$stack->discard();
|
||||
}
|
||||
|
||||
public function test_pop_all_but_last_only_one() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
$stack->push('test1', '</h1>');
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$html = $stack->pop_all_but_last();
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('', $html);
|
||||
$this->assertEqual('', $errors);
|
||||
// Tear down.
|
||||
$stack->discard();
|
||||
}
|
||||
|
||||
public function test_pop_all_but_last_empty() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$html = $stack->pop_all_but_last();
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('', $html);
|
||||
$this->assertEqual('', $errors);
|
||||
}
|
||||
|
||||
public function test_destruct() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
$stack->push('test1', '</somethingdistinctive>');
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$stack = null;
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertPattern('/<\/somethingdistinctive>/', $errors);
|
||||
}
|
||||
|
||||
public function test_destruct_empty() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$stack = null;
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('', $errors);
|
||||
}
|
||||
|
||||
public function test_discard() {
|
||||
// Set up.
|
||||
$stack = new xhtml_container_stack();
|
||||
$stack->push('test1', '</somethingdistinctive>');
|
||||
$stack->discard();
|
||||
// Exercise SUT.
|
||||
$this->start_capture();
|
||||
$stack = null;
|
||||
$errors = $this->end_capture();
|
||||
// Verify outcome
|
||||
$this->assertEqual('', $errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for the template_renderer class.
|
||||
*
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class template_renderer_test extends UnitTestCase {
|
||||
protected $renderer;
|
||||
protected $templatefolder;
|
||||
protected $savedtemplates;
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
parent::setUp();
|
||||
$this->templatefolder = $CFG->dataroot . '/temp/template_renderer_fixtures/test';
|
||||
make_upload_directory('temp/template_renderer_fixtures/test');
|
||||
$this->renderer = new template_renderer('moodle_test_renderer',
|
||||
array($this->templatefolder), new xhtml_container_stack());
|
||||
$this->savedtemplates = array();
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
$this->renderer = null;
|
||||
foreach ($this->savedtemplates as $template) {
|
||||
unlink($template);
|
||||
}
|
||||
$this->savedtemplates = array();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function save_template($name, $contents) {
|
||||
$filename = $this->templatefolder . '/' . $name . '.php';
|
||||
$this->savedtemplates[] = $filename;
|
||||
file_put_contents($filename, $contents);
|
||||
}
|
||||
|
||||
public function test_simple_template() {
|
||||
$this->save_template('greeting', '<p>Hello <?php echo $name ?>!</p>');
|
||||
|
||||
$html = $this->renderer->greeting('Moodle');
|
||||
$this->assertEqual('<p>Hello Moodle!</p>', $html);
|
||||
}
|
||||
|
||||
public function test_simple_template_default_argument_value() {
|
||||
$this->save_template('greeting', '<p>Hello <?php echo $name ?>!</p>');
|
||||
|
||||
$html = $this->renderer->greeting();
|
||||
$this->assertEqual('<p>Hello world!</p>', $html);
|
||||
}
|
||||
|
||||
public function test_box_template() {
|
||||
$this->save_template('box', '<div class="box"<?php echo $id ?>><?php echo $content ?></div>');
|
||||
|
||||
$html = $this->renderer->box('This is a message in a box', 'messagediv');
|
||||
$this->assertEqual('<div class="box"messagediv>This is a message in a box</div>', $html);
|
||||
}
|
||||
|
||||
public function test_box_start_end_templates() {
|
||||
$this->save_template('box', '<div class="box"<?php echo $id ?>><?php echo $content ?></div>');
|
||||
|
||||
$html = $this->renderer->box_start('messagediv');
|
||||
$this->assertEqual('<div class="box"messagediv>', $html);
|
||||
|
||||
$html = $this->renderer->box_end();
|
||||
$this->assertEqual('</div>', $html);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user