From e4a8ed5cc2130a6a4e1769065b54bd10b13c9352 Mon Sep 17 00:00:00 2001 From: Andrew Nicols <andrew@nicols.co.uk> Date: Thu, 7 Mar 2024 10:55:17 +0800 Subject: [PATCH] MDL-81144 core: Convert standard_after_main_region_html to hook --- ...r_standard_main_region_html_generation.php | 81 +++++++++++++++++++ lib/db/hooks.php | 5 ++ lib/outputrenderers.php | 34 ++++---- lib/tests/core_renderer_test.php | 33 ++++++++ ..._main_region_html_generation_callbacks.php | 39 +++++++++ ...dard_main_region_html_generation_hooks.php | 34 ++++++++ lib/upgrade.txt | 1 + message/classes/hook_callbacks.php | 39 +++++++++ message/lib.php | 9 --- 9 files changed, 247 insertions(+), 28 deletions(-) create mode 100644 lib/classes/hook/output/after_standard_main_region_html_generation.php create mode 100644 lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_callbacks.php create mode 100644 lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_hooks.php create mode 100644 message/classes/hook_callbacks.php diff --git a/lib/classes/hook/output/after_standard_main_region_html_generation.php b/lib/classes/hook/output/after_standard_main_region_html_generation.php new file mode 100644 index 00000000000..1be1efbeb9b --- /dev/null +++ b/lib/classes/hook/output/after_standard_main_region_html_generation.php @@ -0,0 +1,81 @@ +<?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\hook\output; + +/** + * Hook to allow subscribers to add HTML content after the main region content has been generated. + * + * @package core + * @copyright 2024 Andrew Lyons <andrew@nicols.co.uk> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @property-read \renderer_base $renderer The page renderer object + */ +#[\core\attribute\tags('output')] +#[\core\attribute\label('Allows plugins to add any elements to the footer before JS is finalized')] +#[\core\attribute\hook\replaces_callbacks('standard_after_main_region_html')] +final class after_standard_main_region_html_generation { + /** + * Hook to allow subscribers to add HTML content after the main region content has been generated. + * + * @param renderer_base $renderer + * @param string $output Initial output + */ + public function __construct( + /** @var \renderer_base The page renderer object */ + public readonly \renderer_base $renderer, + /** @var string The collected output */ + private string $output = '', + ) { + } + + /** + * Plugins implementing callback can add any HTML to the top of the body. + * + * Must be a string containing valid html head content. + * + * @param null|string $output + */ + public function add_html(?string $output): void { + if ($output) { + $this->output .= $output; + } + } + + /** + * Returns all HTML added by the plugins + * + * @return string + */ + public function get_output(): string { + return $this->output; + } + + /** + * Process legacy callbacks. + */ + public function process_legacy_callbacks(): void { + $pluginswithfunction = get_plugins_with_function(function: 'standard_after_main_region_html', migratedtohook: true); + foreach ($pluginswithfunction as $plugins) { + foreach ($plugins as $function) { + $extrafooter = $function(); + if (is_string($extrafooter)) { + $this->add_html($extrafooter); + } + } + } + } +} diff --git a/lib/db/hooks.php b/lib/db/hooks.php index 948397fabfc..be4a4788c5c 100644 --- a/lib/db/hooks.php +++ b/lib/db/hooks.php @@ -97,4 +97,9 @@ $callbacks = [ 'hook' => \core\hook\output\before_standard_footer_html_generation::class, 'callback' => \core_userfeedback::class . '::before_standard_footer_html_generation', ], + [ + 'hook' => \core\hook\output\after_standard_main_region_html_generation::class, + 'callback' => \core_message\hook_callbacks::class . '::add_messaging_widget', + 'priority' => 0, + ], ]; diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 062c8a86707..b83aa161c77 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -37,6 +37,8 @@ use core\di; use core\hook\manager as hook_manager; +use core\hook\output\after_standard_main_region_html_generation; +use core\hook\output\before_html_attributes; use core\hook\output\before_standard_footer_html_generation; use core\hook\output\before_standard_top_of_body_html_generation; use core\output\named_templatable; @@ -1105,29 +1107,23 @@ class core_renderer extends renderer_base { */ public function standard_after_main_region_html() { global $CFG; - $output = ''; + + // Ensure that the callback exists prior to cache purge. + // This is a critical page path. + // TODO MDL-81134 Remove after LTS+1. + require_once(__DIR__ . '/classes/hook/output/after_standard_main_region_html_generation.php'); + + $hook = new after_standard_main_region_html_generation($this); + if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlbottomofbody)) { - $output .= "\n".$CFG->additionalhtmlbottomofbody; + $hook->add_html("\n"); + $hook->add_html($CFG->additionalhtmlbottomofbody); } - // Give subsystems an opportunity to inject extra html content. The callback - // must always return a string containing valid html. - foreach (\core_component::get_core_subsystems() as $name => $path) { - if ($path) { - $output .= component_callback($name, 'standard_after_main_region_html', [], ''); - } - } + di::get(hook_manager::class)->dispatch($hook); + $hook->process_legacy_callbacks(); - // Give plugins an opportunity to inject extra html content. The callback - // must always return a string containing valid html. - $pluginswithfunction = get_plugins_with_function('standard_after_main_region_html', 'lib.php'); - foreach ($pluginswithfunction as $plugins) { - foreach ($plugins as $function) { - $output .= $function(); - } - } - - return $output; + return $hook->get_output(); } /** diff --git a/lib/tests/core_renderer_test.php b/lib/tests/core_renderer_test.php index 16cc3690cfa..cbc4af204ac 100644 --- a/lib/tests/core_renderer_test.php +++ b/lib/tests/core_renderer_test.php @@ -95,6 +95,39 @@ final class core_renderer_test extends \advanced_testcase { $this->assertStringContainsString('A heading can be added', $html); } + /** + * @covers \core\hook\after_standard_main_region_html_generation + */ + public function test_after_standard_main_region_html_generation(): void { + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_after_main_region_html(); + $this->assertIsString($html); + $this->assertStringNotContainsString('A heading can be added', $html); + } + + /** + * @covers \core\hook\after_standard_main_region_html_generation + */ + public function test_after_standard_main_region_html_generation_hooked(): void { + require_once(__DIR__ . '/fixtures/core_renderer/after_standard_main_region_html_generation_callbacks.php'); + + \core\di::set( + \core\hook\manager::class, + \core\hook\manager::phpunit_get_instance([ + 'test_plugin1' => __DIR__ . '/fixtures/core_renderer/after_standard_main_region_html_generation_hooks.php', + ]), + ); + + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_after_main_region_html(); + $this->assertIsString($html); + $this->assertStringContainsString('A heading can be added', $html); + } + /** * @covers \core\hook\before_html_attributes */ diff --git a/lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_callbacks.php b/lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_callbacks.php new file mode 100644 index 00000000000..0c25e568461 --- /dev/null +++ b/lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_callbacks.php @@ -0,0 +1,39 @@ +<?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 test_fixtures\core_renderer; + +/** + * Hook fixture for \core_renderer::after_standard_main_region_html_generation. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons <andrew@nicols.co.uk> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +final class after_standard_main_region_html_generation_callbacks { + /** + * Fixture for adding a heading after the standard main region HTML generation. + * + * @param \core\hook\output\after_standard_main_region_html_generation $hook + */ + public static function after_standard_main_region_html_generation( + \core\hook\output\after_standard_main_region_html_generation $hook, + ): void { + $hook->add_html("<h1>A heading can be added</h1>"); + } +} diff --git a/lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_hooks.php b/lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_hooks.php new file mode 100644 index 00000000000..8c17715a471 --- /dev/null +++ b/lib/tests/fixtures/core_renderer/after_standard_main_region_html_generation_hooks.php @@ -0,0 +1,34 @@ +<?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/>. + +/** + * Hook fixture for \core_renderer::after_standard_main_region_html_generation. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons <andrew@nicols.co.uk> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\after_standard_main_region_html_generation::class, + 'callback' => \test_fixtures\core_renderer\after_standard_main_region_html_generation_callbacks::class + . '::after_standard_main_region_html_generation', + ], +]; diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 7e501e73389..e4ff83a489d 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -40,6 +40,7 @@ information provided here is intended especially for developers. - before_standard_html_head() -> core\hook\output\before_standard_head_html_generation - bulk_user_actions() -> core_user\hook\extend_bulk_user_actions - before_standard_top_of_body_html() -> core\hook\output\before_standard_top_of_body_html_generation + - standard_after_main_region_html() -> core\hook\output\after_standard_main_region_html_generation - standard_footer_html() -> core\hook\output\before_standard_footer_html_generation - add_htmlattributes() -> core\hook\output\before_html_attributes * Deprecated PARAM_ types with the exception of PARAM_CLEAN now emit a deprecation exception. These were all deprecated in Moodle 2.0. diff --git a/message/classes/hook_callbacks.php b/message/classes/hook_callbacks.php new file mode 100644 index 00000000000..88338ef4a2a --- /dev/null +++ b/message/classes/hook_callbacks.php @@ -0,0 +1,39 @@ +<?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_message; + +/** + * Class hook_callbacks + * + * @package core_message + * @copyright 2024 Andrew Lyons <andrew@nicols.co.uk> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class hook_callbacks { + /** + * Add messaging widgets after the main region content. + * + * @param \core\hook\output\after_standard_main_region_html_generation $hook + */ + public static function add_messaging_widget( + \core\hook\output\after_standard_main_region_html_generation $hook, + ): void { + $hook->add_html(\core_message\helper::render_messaging_widget( + isdrawer: true, + )); + } +} diff --git a/message/lib.php b/message/lib.php index d4e179289a6..c1fb3838cec 100644 --- a/message/lib.php +++ b/message/lib.php @@ -798,12 +798,3 @@ function core_message_user_preferences() { }); return $preferences; } - -/** - * Render the message drawer to be included in the top of the body of each page. - * - * @return string HTML - */ -function core_message_standard_after_main_region_html() { - return \core_message\helper::render_messaging_widget(true, null, null); -}