diff --git a/lib/classes/output/named_templatable.php b/lib/classes/output/named_templatable.php new file mode 100644 index 00000000000..2404e509c95 --- /dev/null +++ b/lib/classes/output/named_templatable.php @@ -0,0 +1,37 @@ +. + +namespace core\output; + +use templatable; + +/** + * A subset of templatable which provides the name of the template to use. + * + * @package core + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface named_templatable extends templatable { + + /** + * Get the name of the template to use for this templatable. + * + * @param \renderer_base $renderer The renderer requesting the template name + * @return string + */ + public function get_template_name(\renderer_base $renderer): string; +} diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 8eb6643379f..219c167ac9f 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -35,6 +35,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\output\named_templatable; use core_completion\cm_completion_details; use core_course\output\activity_information; @@ -227,14 +228,29 @@ class renderer_base { $classparts = explode('\\', get_class($widget)); // Strip namespaces. $classname = array_pop($classparts); - // Remove _renderable suffixes + // Remove _renderable suffixes. $classname = preg_replace('/_renderable$/', '', $classname); - $rendermethod = 'render_'.$classname; + $rendermethod = "render_{$classname}"; if (method_exists($this, $rendermethod)) { + // Call the render_[widget_name] function. + // Note: This has a higher priority than the named_templatable to allow the theme to override the template. return $this->$rendermethod($widget); } + + if ($widget instanceof named_templatable) { + // This is a named templatable. + // Fetch the template name from the get_template_name function instead. + // Note: This has higher priority than the guessed template name. + return $this->render_from_template( + $widget->get_template_name($this), + $widget->export_for_template($this) + ); + } + if ($widget instanceof templatable) { + // Guess the templat ename based on the class name. + // Note: There's no benefit to moving this aboved the named_templatable and this approach is more costly. $component = array_shift($classparts); if (!$component) { $component = 'core'; @@ -243,7 +259,7 @@ class renderer_base { $context = $widget->export_for_template($this); return $this->render_from_template($template, $context); } - throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.'); + throw new coding_exception("Can not render widget, renderer method ('{$rendermethod}') not found."); } /** @@ -455,17 +471,33 @@ class plugin_renderer_base extends renderer_base { */ public function render(renderable $widget) { $classname = get_class($widget); + // Strip namespaces. $classname = preg_replace('/^.*\\\/', '', $classname); - // Keep a copy at this point, we may need to look for a deprecated method. - $deprecatedmethod = 'render_'.$classname; - // Remove _renderable suffixes - $classname = preg_replace('/_renderable$/', '', $classname); - $rendermethod = 'render_'.$classname; + // Keep a copy at this point, we may need to look for a deprecated method. + $deprecatedmethod = "render_{$classname}"; + + // Remove _renderable suffixes. + $classname = preg_replace('/_renderable$/', '', $classname); + $rendermethod = "render_{$classname}"; + if (method_exists($this, $rendermethod)) { + // Call the render_[widget_name] function. + // Note: This has a higher priority than the named_templatable to allow the theme to override the template. return $this->$rendermethod($widget); } + + if ($widget instanceof named_templatable) { + // This is a named templatable. + // Fetch the template name from the get_template_name function instead. + // Note: This has higher priority than the deprecated method which is not overridable by themes anyway. + return $this->render_from_template( + $widget->get_template_name($this), + $widget->export_for_template($this) + ); + } + if ($rendermethod !== $deprecatedmethod && method_exists($this, $deprecatedmethod)) { // This is exactly where we don't want to be. // If you have arrived here you have a renderable component within your plugin that has the name @@ -475,15 +507,20 @@ class plugin_renderer_base extends renderer_base { // You need to change your renderers render_blah_renderable to render_blah. // Until you do this it will not be possible for a theme to override the renderer to override your method. // Please do it ASAP. - static $debugged = array(); + static $debugged = []; if (!isset($debugged[$deprecatedmethod])) { - debugging(sprintf('Deprecated call. Please rename your renderables render method from %s to %s.', - $deprecatedmethod, $rendermethod), DEBUG_DEVELOPER); + debugging(sprintf( + 'Deprecated call. Please rename your renderables render method from %s to %s.', + $deprecatedmethod, + $rendermethod + ), DEBUG_DEVELOPER); $debugged[$deprecatedmethod] = true; } return $this->$deprecatedmethod($widget); } - // pass to core renderer if method not found here + + // Pass to core renderer if method not found here. + // Note: this is not a parent. This is _new_ renderer which respects the requested format, and output type. return $this->output->render($widget); } diff --git a/lib/upgrade.txt b/lib/upgrade.txt index d8d0e597ac2..bc537e740a2 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -233,6 +233,9 @@ value to get the list of blocks that won't be displayed for a theme. $strength passed to it. * A new method get_page() has been added to the settings_navigation class. This method can be used to obtain the moodle_page object associated to the settings navigation. +* A new interface, `core\output\named_templatable` has been created to allow renderable classes to define a + `get_template_name(\renderer_base): string` function which will inform the default render() function with a template + name. === 3.11.4 === * A new option dontforcesvgdownload has been added to the $options parameter of the send_file() function.