MDL-21938 support for json renderer and error handler
[moodle.git] / lib / outputfactories.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Interface and classes for creating appropriate renderers for various
20  * parts of Moodle.
21  *
22  * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
23  * for an overview.
24  *
25  * @package   moodlecore
26  * @copyright 2009 Tim Hunt
27  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28  */
30 /** General rendering target, usually normal browser page */
31 define('RENDERER_TARGET_GENERAL', 'general');
33 /** Plain text rendering for CLI scripts and cron */
34 define('RENDERER_TARGET_CLI', 'cli');
36 /** Plain text rendering for Ajax scripts*/
37 define('RENDERER_TARGET_AJAX', 'ajax');
39 /** Plain text rendering intended for sending via email */
40 define('RENDERER_TARGET_TEXTEMAIL', 'textemail');
42 /** Rich text html rendering intended for sending via email */
43 define('RENDERER_TARGET_HTMLEMAIL', 'htmlemail');
45 /* note: maybe we could define portfolio export target too */
48 /**
49  * A renderer factory is just responsible for creating an appropriate renderer
50  * for any given part of Moodle.
51  *
52  * Which renderer factory to use is chose by the current theme, and an instance
53  * if created automatically when the theme is set up.
54  *
55  * A renderer factory must also have a constructor that takes a theme_config object.
56  * (See {@link renderer_factory_base::__construct} for an example.)
57  *
58  * @copyright 2009 Tim Hunt
59  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
60  * @since     Moodle 2.0
61  */
62 interface renderer_factory {
63     /**
64      * Return the renderer for a particular part of Moodle.
65      *
66      * The renderer interfaces are defined by classes called {plugin}_renderer
67      * where {plugin} is the name of the component. The renderers for core Moodle are
68      * defined in lib/renderer.php. For plugins, they will be defined in a file
69      * called renderer.php inside the plugin.
70      *
71      * Renderers will normally want to subclass the renderer_base class.
72      * (However, if you really know what you are doing, you don't have to do that.)
73      *
74      * There is no separate interface definition for renderers. The default
75      * {plugin}_renderer implementation also serves to define the API for
76      * other implementations of the interface, whether or not they subclass it.
77      *
78      * A particular plugin can define multiple renderers if it wishes, using the
79      * $subtype parameter. For example workshop_renderer,
80      * workshop_allocation_manual_renderer etc.
81      *
82      * @param moodle_page $page the page the renderer is outputting content for.
83      * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
84      * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
85      * @param string $target one of rendering target constants
86      * @return object an object implementing the requested renderer interface.
87      */
88     public function get_renderer(moodle_page $page, $component, $subtype=null, $target=null);
89 }
92 /**
93  * This is a base class to help you implement the renderer_factory interface.
94  *
95  * It keeps a cache of renderers that have been constructed, so you only need
96  * to construct each one once in you subclass.
97  *
98  * It also has a method to get the name of, and include the renderer.php with
99  * the definition of, the standard renderer class for a given module.
100  *
101  * @copyright 2009 Tim Hunt
102  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
103  * @since     Moodle 2.0
104  */
105 abstract class renderer_factory_base implements renderer_factory {
106     /** @var theme_config the theme we belong to. */
107     protected $theme;
109     /** @var hardcoded list of core subtypes and their locations, add more if ßneeded */
110     protected $core_subtypes = array('webservice' => 'webservice');
112     /**
113      * Constructor.
114      * @param theme_config $theme the theme we belong to.
115      */
116     public function __construct(theme_config $theme) {
117         $this->theme = $theme;
118     }
120     /**
121      * Returns suffix of renderer class expected for given target.
122      * @param string $target one of the renderer target constants, target is guessed if null used
123      * @return array two element array, first element is target, second the target suffix string
124      */
125     protected function get_target_suffix($target) {
126         if (empty($target)) {
127             // automatically guessed defaults
128             if (CLI_SCRIPT) {
129                 $target = RENDERER_TARGET_CLI;
130             } else if (AJAX_SCRIPT) {
131                 $target = RENDERER_TARGET_AJAX;
132             }
133         }
135         switch ($target) {
136             case RENDERER_TARGET_CLI: $suffix = '_cli'; break;
137             case RENDERER_TARGET_AJAX: $suffix = '_ajax'; break;
138             case RENDERER_TARGET_TEXTEMAIL: $suffix = '_textemail'; break;
139             case RENDERER_TARGET_HTMLEMAIL: $suffix = '_htmlemail'; break;
140             default: $target = RENDERER_TARGET_GENERAL; $suffix = '';
141         }
143         return array($target, $suffix);
144     }
146     /**
147      * For a given module name, return the name of the standard renderer class
148      * that defines the renderer interface for that module.
149      *
150      * Also, if it exists, include the renderer.php file for that module, so
151      * the class definition of the default renderer has been loaded.
152      *
153      * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
154      * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
155      * @return string the name of the standard renderer class for that module.
156      */
157     protected function standard_renderer_classname($component, $subtype = null) {
158         global $CFG; // needed in incldued files
160         if ($component !== 'core') {
161             // standardize component names
162             if (strpos($component, '_') === false) {
163                 $component = 'mod_' . $component;
164             }
165             // renderers are stored in renderer.php files
166             if (!$compdirectory = get_component_directory($component)) {
167                 throw new coding_exception('Invalid component specified in renderer request');
168             }
169             $rendererfile = $compdirectory . '/renderer.php';
170             if (file_exists($rendererfile)) {
171                 include_once($rendererfile);
172             }
174         } else if (!empty($subtype)) {
175             if (!isset($this->core_subtypes[$subtype])) {
176                 throw new coding_exception('Invalid core subtype "' . $subtype . '" in renderer request');
177             }
178             $rendererfile = $CFG->dirroot . '/' . $this->core_subtypes[$subtype] . '/renderer.php';
179             if (file_exists($rendererfile)) {
180                 include_once($rendererfile);
181             }
182         }
184         if (empty($subtype)) {
185             $class = $component . '_renderer';
186         } else {
187             $class = $component . '_' . $subtype . '_renderer';
188         }
189         return $class;
190     }
194 /**
195  * This is the default renderer factory for Moodle. It simply returns an instance
196  * of the appropriate standard renderer class.
197  *
198  * @copyright 2009 Tim Hunt
199  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
200  * @since     Moodle 2.0
201  */
202 class standard_renderer_factory extends renderer_factory_base {
203     /**
204      * Implement the subclass method
205      * @param moodle_page $page the page the renderer is outputting content for.
206      * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
207      * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
208      * @param string $target one of rendering target constants
209      * @return object an object implementing the requested renderer interface.
210      */
211     public function get_renderer(moodle_page $page, $component, $subtype = null, $target = null) {
212         $classname = $this->standard_renderer_classname($component, $subtype);
213         if (!class_exists($classname)) {
214             throw new coding_exception('Request for an unknown renderer class ' . $classname);
215         }
217         list($target, $suffix) = $this->get_target_suffix($target);
218         if (class_exists($classname . $suffix)) {
219             // use the specialised renderer for given target, default renderer might also decide
220             // to implement support for more targets
221             $classname = $classname . $suffix;
222         }
224         return new $classname($page, $target);
225     }
229 /**
230  * This is renderer factory allows themes to override the standard renderers using
231  * php code.
232  *
233  * It will load any code from theme/mytheme/renderers.php and
234  * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for
235  * a renderer for 'component', it will create a mytheme_component_renderer or a
236  * parenttheme_component_renderer, instead of a component_renderer,
237  * if either of those classes exist.
238  *
239  * @copyright 2009 Tim Hunt
240  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
241  * @since     Moodle 2.0
242  */
243 class theme_overridden_renderer_factory extends renderer_factory_base {
245     protected $prefixes = array();
247     /**
248      * Constructor.
249      * @param object $theme the theme we are rendering for.
250      */
251     public function __construct(theme_config $theme) {
252         parent::__construct($theme);
253         // Initialise $this->prefixes.
254         $this->prefixes = $theme->renderer_prefixes();
255     }
257     /**
258      * Implement the subclass method
259      * @param moodle_page $page the page the renderer is outputting content for.
260      * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
261      * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
262      * @param string $target one of rendering target constants
263      * @return object an object implementing the requested renderer interface.
264      */
265     public function get_renderer(moodle_page $page, $component, $subtype = null, $target = null) {
266         $classname = $this->standard_renderer_classname($component, $subtype);
267         if (!class_exists($classname)) {
268             // standard renderer must always exist
269             throw new coding_exception('Request for an unknown renderer class ' . $classname);
270         }
272         list($target, $suffix) = $this->get_target_suffix($target);
274         // theme lib.php and renderers.php files are loaded automatically
275         // when loading the theme configs
277         // first try the renderers with correct suffix
278         foreach ($this->prefixes as $prefix) {
279             if (class_exists($prefix . '_' . $classname . $suffix)) {
280                 $classname = $prefix . '_' . $classname . $suffix;
281                 return new $classname($page, $target);
282             }
283         }
284         if (class_exists($classname . $suffix)) {
285             // use the specialised renderer for given target, default renderer might also decide
286             // to implement support for more targets
287             $classname = $classname . $suffix;
288             return new $classname($page, $target);
289         }
291         // then try general renderer
292         foreach ($this->prefixes as $prefix) {
293             if (class_exists($prefix . '_' . $classname)) {
294                 $classname = $prefix . '_' . $classname;
295                 return new $classname($page, $target);
296             }
297         }
299         return new $classname($page, $target);
300     }