MDL-73679 core: Add a named_templatable interface

This interface allows a templatable to provide a template name via the
'get_template_name(): string' function and have it automatically
rendered via a standard 'render()' call.
This commit is contained in:
Andrew Nicols 2022-02-02 10:16:48 +08:00
parent af0e3bcadc
commit b43d729c85
3 changed files with 89 additions and 12 deletions

View File

@ -0,0 +1,37 @@
<?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/>.
namespace core\output;
use templatable;
/**
* A subset of templatable which provides the name of the template to use.
*
* @package core
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @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;
}

View File

@ -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);
}

View File

@ -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.