MDL-41398 output: implemented maintenance renderer

This commit is contained in:
Sam Hemelryk 2013-09-12 09:51:58 +12:00
parent 56cc9b387e
commit 166ac0a35d
5 changed files with 335 additions and 9 deletions

View File

@ -31,6 +31,9 @@ defined('MOODLE_INTERNAL') || die();
/** General rendering target, usually normal browser page */ /** General rendering target, usually normal browser page */
define('RENDERER_TARGET_GENERAL', 'general'); define('RENDERER_TARGET_GENERAL', 'general');
/** General rendering target, usually normal browser page, but with limited capacity to avoid API use */
define('RENDERER_TARGET_MAINTENANCE', 'maintenance');
/** Plain text rendering for CLI scripts and cron */ /** Plain text rendering for CLI scripts and cron */
define('RENDERER_TARGET_CLI', 'cli'); define('RENDERER_TARGET_CLI', 'cli');
@ -130,8 +133,10 @@ abstract class renderer_factory_base implements renderer_factory {
* @return array two element array, first element is target, second the target suffix string * @return array two element array, first element is target, second the target suffix string
*/ */
protected function get_target_suffix($target) { protected function get_target_suffix($target) {
if (empty($target)) { if (empty($target) || $target === RENDERER_TARGET_MAINTENANCE) {
// automatically guessed defaults // If the target hasn't been specified we need to guess the defaults.
// We also override the target with the default if the maintenance target has been provided.
// This ensures we don't use the maintenance renderer if we are processing a special target.
if (CLI_SCRIPT) { if (CLI_SCRIPT) {
$target = RENDERER_TARGET_CLI; $target = RENDERER_TARGET_CLI;
} else if (AJAX_SCRIPT) { } else if (AJAX_SCRIPT) {
@ -144,6 +149,7 @@ abstract class renderer_factory_base implements renderer_factory {
case RENDERER_TARGET_AJAX: $suffix = '_ajax'; break; case RENDERER_TARGET_AJAX: $suffix = '_ajax'; break;
case RENDERER_TARGET_TEXTEMAIL: $suffix = '_textemail'; break; case RENDERER_TARGET_TEXTEMAIL: $suffix = '_textemail'; break;
case RENDERER_TARGET_HTMLEMAIL: $suffix = '_htmlemail'; break; case RENDERER_TARGET_HTMLEMAIL: $suffix = '_htmlemail'; break;
case RENDERER_TARGET_MAINTENANCE: $suffix = '_maintenance'; break;
default: $target = RENDERER_TARGET_GENERAL; $suffix = ''; default: $target = RENDERER_TARGET_GENERAL; $suffix = '';
} }
@ -160,11 +166,12 @@ abstract class renderer_factory_base implements renderer_factory {
* @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'. * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
* @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
* @return string the name of the standard renderer class for that module. * @return string the name of the standard renderer class for that module.
* @throws coding_exception
*/ */
protected function standard_renderer_classname($component, $subtype = null) { protected function standard_renderer_classname($component, $subtype = null) {
global $CFG; // needed in included files global $CFG; // Needed in included files.
// standardize component name ala frankenstyle // Standardize component name ala frankenstyle.
list($plugin, $type) = core_component::normalize_component($component); list($plugin, $type) = core_component::normalize_component($component);
if ($type === null) { if ($type === null) {
$component = $plugin; $component = $plugin;
@ -173,9 +180,9 @@ abstract class renderer_factory_base implements renderer_factory {
} }
if ($component !== 'core') { if ($component !== 'core') {
// renderers are stored in renderer.php files // Renderers are stored in renderer.php files.
if (!$compdirectory = core_component::get_component_directory($component)) { if (!$compdirectory = core_component::get_component_directory($component)) {
throw new coding_exception('Invalid component specified in renderer request'); throw new coding_exception('Invalid component specified in renderer request', $component);
} }
$rendererfile = $compdirectory . '/renderer.php'; $rendererfile = $compdirectory . '/renderer.php';
if (file_exists($rendererfile)) { if (file_exists($rendererfile)) {
@ -185,7 +192,7 @@ abstract class renderer_factory_base implements renderer_factory {
} else if (!empty($subtype)) { } else if (!empty($subtype)) {
$coresubsystems = core_component::get_core_subsystems(); $coresubsystems = core_component::get_core_subsystems();
if (!array_key_exists($subtype, $coresubsystems)) { // There may be nulls. if (!array_key_exists($subtype, $coresubsystems)) { // There may be nulls.
throw new coding_exception('Invalid core subtype "' . $subtype . '" in renderer request'); throw new coding_exception('Invalid core subtype "' . $subtype . '" in renderer request', $subtype);
} }
if ($coresubsystems[$subtype]) { if ($coresubsystems[$subtype]) {
$rendererfile = $coresubsystems[$subtype] . '/renderer.php'; $rendererfile = $coresubsystems[$subtype] . '/renderer.php';

View File

@ -199,6 +199,12 @@ class plugin_renderer_base extends renderer_base {
* @param string $target one of rendering target constants * @param string $target one of rendering target constants
*/ */
public function __construct(moodle_page $page, $target) { public function __construct(moodle_page $page, $target) {
if (empty($target) && $page->pagelayout === 'maintenance') {
// If the page is using the maintenance layout then we're going to force the target to maintenance.
// This way we'll get a special maintenance renderer that is designed to block access to API's that are likely
// unavailable for this page layout.
$target = RENDERER_TARGET_MAINTENANCE;
}
$this->output = $page->get_renderer('core', null, $target); $this->output = $page->get_renderer('core', null, $target);
parent::__construct($page, $target); parent::__construct($page, $target);
} }
@ -3764,3 +3770,201 @@ class core_media_renderer extends plugin_renderer_base {
return $this->embeddablemarkers; return $this->embeddablemarkers;
} }
} }
/**
* The maintenance renderer.
*
* The purpose of this renderer is to block out the core renderer methods that are not usable when the site
* is running a maintenance related task.
* It must always extend the core_renderer as we switch from the core_renderer to this renderer in a couple of places.
*
* @since 2.6
* @package core
* @category output
* @copyright 2013 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_renderer_maintenance extends core_renderer {
/**
* Initialises the renderer instance.
* @param moodle_page $page
* @param string $target
* @throws coding_exception
*/
public function __construct(moodle_page $page, $target) {
if ($target !== RENDERER_TARGET_MAINTENANCE || $page->pagelayout !== 'maintenance') {
throw new coding_exception('Invalid request for the maintenance renderer.');
}
parent::__construct($page, $target);
}
/**
* Does nothing. The maintenance renderer cannot produce blocks.
*
* @param block_contents $bc
* @param string $region
* @return string
*/
public function block(block_contents $bc, $region) {
// Computer says no blocks.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce blocks.
*
* @param string $region
* @param array $classes
* @param string $tag
* @return string
*/
public function blocks($region, $classes = array(), $tag = 'aside') {
// Computer says no blocks.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce blocks.
*
* @param string $region
* @return string
*/
public function blocks_for_region($region) {
// Computer says no blocks for region.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce a course content header.
*
* @param bool $onlyifnotcalledbefore
* @return string
*/
public function course_content_header($onlyifnotcalledbefore = false) {
// Computer says no course content header.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce a course content footer.
*
* @param bool $onlyifnotcalledbefore
* @return string
*/
public function course_content_footer($onlyifnotcalledbefore = false) {
// Computer says no course content footer.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce a course header.
*
* @return string
*/
public function course_header() {
// Computer says no course header.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce a course footer.
*
* @return string
*/
public function course_footer() {
// Computer says no course footer.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce a custom menu.
*
* @param string $custommenuitems
* @return string
*/
public function custom_menu($custommenuitems = '') {
// Computer says no custom menu.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce a file picker.
*
* @param array $options
* @return string
*/
public function file_picker($options) {
// Computer says no file picker.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce and HTML file tree.
*
* @param array $dir
* @return string
*/
public function htmllize_file_tree($dir) {
// Hell no we don't want no htmllized file tree.
// Also why on earth is this function on the core renderer???
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer does not support JS.
*
* @param block_contents $bc
*/
public function init_block_hider_js(block_contents $bc) {
// Computer says no JavaScript.
// Do nothing, ridiculous method.
}
/**
* Does nothing. The maintenance renderer cannot produce language menus.
*
* @return string
*/
public function lang_menu() {
// Computer says no lang menu.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer has no need for login information.
*
* @param null $withlinks
* @return string
*/
public function login_info($withlinks = null) {
// Computer says no login info.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
/**
* Does nothing. The maintenance renderer cannot produce user pictures.
*
* @param stdClass $user
* @param array $options
* @return string
*/
public function user_picture(stdClass $user, array $options = null) {
// Computer says no user pictures.
// debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
return '';
}
}

View File

@ -769,6 +769,13 @@ class moodle_page {
* @return renderer_base * @return renderer_base
*/ */
public function get_renderer($component, $subtype = null, $target = null) { public function get_renderer($component, $subtype = null, $target = null) {
$target = null;
if ($this->pagelayout === 'maintenance') {
// If the page is using the maintenance layout then we're going to force target to maintenance.
// This leads to a special core renderer that is designed to block access to API's that are likely unavailable for this
// page layout.
$target = RENDERER_TARGET_MAINTENANCE;
}
return $this->magic_get_theme()->get_renderer($this, $component, $subtype, $target); return $this->magic_get_theme()->get_renderer($this, $component, $subtype, $target);
} }
@ -1475,7 +1482,14 @@ class moodle_page {
} }
if ($this === $PAGE) { if ($this === $PAGE) {
$OUTPUT = $this->get_renderer('core'); $target = null;
if ($this->pagelayout === 'maintenance') {
// If the page is using the maintenance layout then we're going to force target to maintenance.
// This leads to a special core renderer that is designed to block access to API's that are likely unavailable for this
// page layout.
$target = RENDERER_TARGET_MAINTENANCE;
}
$OUTPUT = $this->get_renderer('core', null, $target);
} }
$this->_wherethemewasinitialised = debug_backtrace(); $this->_wherethemewasinitialised = debug_backtrace();

View File

@ -31,6 +31,10 @@ require_once($CFG->libdir . '/blocklib.php');
class core_moodle_page_testcase extends advanced_testcase { class core_moodle_page_testcase extends advanced_testcase {
/**
* @var testable_moodle_page
*/
protected $testpage; protected $testpage;
public function setUp() { public function setUp() {
@ -570,8 +574,80 @@ class core_moodle_page_testcase extends advanced_testcase {
$expectedcaps = array('moodle/course:manageactivities', 'moodle/site:other', 'moodle/site:manageblocks'); $expectedcaps = array('moodle/course:manageactivities', 'moodle/site:other', 'moodle/site:manageblocks');
$this->assertEquals(array_values($expectedcaps), array_values($actualcaps)); $this->assertEquals(array_values($expectedcaps), array_values($actualcaps));
} }
}
/**
* Test getting a renderer.
*/
public function test_get_renderer() {
global $OUTPUT, $PAGE;
$oldoutput = $OUTPUT;
$oldpage = $PAGE;
$PAGE = $this->testpage;
$this->testpage->set_pagelayout('standard');
$this->assertEquals('standard', $this->testpage->pagelayout);
// Initialise theme and output for the next tests.
$this->testpage->initialise_theme_and_output();
// Check the generated $OUTPUT object is a core renderer.
$this->assertInstanceOf('core_renderer', $OUTPUT);
// Check we can get a core renderer if we explicitly request one (no component).
$this->assertInstanceOf('core_renderer', $this->testpage->get_renderer('core'));
// Check we get a CLI renderer if we request a maintenance renderer. The CLI target should take precedence.
$this->assertInstanceOf('core_renderer_cli',
$this->testpage->get_renderer('core', null, RENDERER_TARGET_MAINTENANCE));
// Check we can get a coures renderer if we explicitly request one (valid component).
$this->assertInstanceOf('core_course_renderer', $this->testpage->get_renderer('core', 'course'));
// Check a properly invalid component.
try {
$this->testpage->get_renderer('core', 'monkeys');
$this->fail('Request for renderer with invalid component didn\'t throw expected exception.');
} catch (coding_exception $exception) {
$this->assertEquals('monkeys', $exception->debuginfo);
}
$PAGE = $oldpage;
$OUTPUT = $oldoutput;
}
/**
* Tests getting a renderer with a maintenance layout.
*
* This layout has special hacks in place in order to deliver a "maintenance" renderer.
*/
public function test_get_renderer_maintenance() {
global $OUTPUT, $PAGE;
$oldoutput = $OUTPUT;
$oldpage = $PAGE;
$PAGE = $this->testpage;
$this->testpage->set_pagelayout('maintenance');
$this->assertEquals('maintenance', $this->testpage->pagelayout);
// Initialise theme and output for the next tests.
$this->testpage->initialise_theme_and_output();
// Check the generated $OUTPUT object is a core cli renderer.
// It shouldn't be maintenance because there the cli target should take greater precedence.
$this->assertInstanceOf('core_renderer_cli', $OUTPUT);
// Check we can get a core renderer if we explicitly request one (no component).
$this->assertInstanceOf('core_renderer', $this->testpage->get_renderer('core'));
// Check we get a CLI renderer if we request a maintenance renderer. The CLI target should take precedence.
$this->assertInstanceOf('core_renderer_cli',
$this->testpage->get_renderer('core', null, RENDERER_TARGET_MAINTENANCE));
// Check we can get a coures renderer if we explicitly request one (valid component).
$this->assertInstanceOf('core_course_renderer', $this->testpage->get_renderer('core', 'course'));
try {
$this->testpage->get_renderer('core', 'monkeys');
$this->fail('Request for renderer with invalid component didn\'t throw expected exception.');
} catch (coding_exception $exception) {
$this->assertEquals('monkeys', $exception->debuginfo);
}
$PAGE = $oldpage;
$OUTPUT = $oldoutput;
}
}
/** /**
* Test-specific subclass to make some protected things public. * Test-specific subclass to make some protected things public.

View File

@ -15,6 +15,11 @@ Notes:
* For the themes based on bootstrapbase please also read theme/bootstrapbase/upgrade.txt * For the themes based on bootstrapbase please also read theme/bootstrapbase/upgrade.txt
* A new component action_menu is now used to display editing icons for courses and blocks within a drop down. If you have a theme that doesn't * A new component action_menu is now used to display editing icons for courses and blocks within a drop down. If you have a theme that doesn't
extend base, canvas, or clean then you will need to style for this new component within your theme. extend base, canvas, or clean then you will need to style for this new component within your theme.
* The maintenance layout now has a special renderer that extends the core_renderer in order to prevent some methods from interacting
with the database. Please be aware that for the maintenance layout some methods now always return an empty string.
This has been done because it is important that during maintenance routines we don't show any links, interact with the database,
or interact with caches as doing so may lead to errors,
Please see the maintenance renderer notes below for details on the functions affected.
Renderer changes: Renderer changes:
* core_course_renderer::course_section_cm_edit_actions has two new optional arguments and now uses and action_menu component. * core_course_renderer::course_section_cm_edit_actions has two new optional arguments and now uses and action_menu component.
@ -31,6 +36,26 @@ Selector changes:
* Classes ".header and .headingblock" were removed from all front page content headings. * Classes ".header and .headingblock" were removed from all front page content headings.
* Classes ".headingblock .header .tag-header" were removed from the tag index page * Classes ".headingblock .header .tag-header" were removed from the tag index page
Maintenance renderer notes:
When the maintenance layout is being used $OUTPUT will be an instance of core_renderer_maintenance.
This renderer mimics the core_renderer except that the following functions always return an empty string.
* core_maintenance_renderer::block
* core_maintenance_renderer::blocks
* core_maintenance_renderer::blocks_for_regions
* core_maintenance_renderer::course_header
* core_maintenance_renderer::course_footer
* core_maintenance_renderer::course_content_header
* core_maintenance_renderer::course_content_footer
* core_maintenance_renderer::custom_menu
* core_maintenance_renderer::file_picker
* core_maintenance_renderer::htmllize_file_tree
* core_maintenance_renderer::lang_menu
* core_maintenance_renderer::login_info
* core_maintenance_renderer::user_picture
If you have overridden methods of the core_renderer in your theme and want those changes to be shown during times of maintenance you
will also need to override the core_renderer_maintenance and copy your customisations from the core_renderer to that.
=== 2.5.1 === === 2.5.1 ===
Notes: Notes: