From 3105ea7dc2e26dac54e0f7f6ba4018ff615b4a58 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 6 Mar 2024 22:06:15 +0800 Subject: [PATCH 1/9] MDL-81144 core: Convert before_standard_top_of_body_html to hook --- admin/tool/policy/classes/hook_callbacks.php | 62 +++++++++++ admin/tool/policy/db/hooks.php | 33 ++++++ admin/tool/policy/lib.php | 28 +---- ...e_standard_top_of_body_html_generation.php | 101 ++++++++++++++++++ lib/outputrenderers.php | 25 ++--- lib/tests/core_renderer_test.php | 64 +++++++++++ ..._top_of_body_html_generation_callbacks.php | 38 +++++++ ...dard_top_of_body_html_generation_hooks.php | 34 ++++++ lib/upgrade.txt | 1 + 9 files changed, 344 insertions(+), 42 deletions(-) create mode 100644 admin/tool/policy/classes/hook_callbacks.php create mode 100644 admin/tool/policy/db/hooks.php create mode 100644 lib/classes/hook/output/before_standard_top_of_body_html_generation.php create mode 100644 lib/tests/core_renderer_test.php create mode 100644 lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_callbacks.php create mode 100644 lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_hooks.php diff --git a/admin/tool/policy/classes/hook_callbacks.php b/admin/tool/policy/classes/hook_callbacks.php new file mode 100644 index 00000000000..2aa24ef0c5b --- /dev/null +++ b/admin/tool/policy/classes/hook_callbacks.php @@ -0,0 +1,62 @@ +. + +namespace tool_policy; + +use core\hook\output\before_standard_top_of_body_html_generation; + +/** + * Allows the plugin to add any elements to the footer. + * + * @package tool_policy + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class hook_callbacks { + /** + * Add the guest consent form to the top of the body. + * + * @param before_standard_top_of_body_html_generation $hook + */ + public static function before_standard_top_of_body_html_generation(before_standard_top_of_body_html_generation $hook): void { + global $CFG, $PAGE, $USER; + + if (empty($CFG->sitepolicyhandler)) { + return; + } + + if ($CFG->sitepolicyhandler !== 'tool_policy') { + return; + } + + if (!empty($USER->policyagreed)) { + return; + } + + if (!isguestuser() && isloggedin()) { + return; + } + + $output = $PAGE->get_renderer('tool_policy'); + try { + $page = new \tool_policy\output\guestconsent(); + $hook->add_html($output->render($page)); + } catch (\dml_read_exception $e) { + // During upgrades, the new plugin code with new SQL could be in place but the DB not upgraded yet. + return; + } + } +} diff --git a/admin/tool/policy/db/hooks.php b/admin/tool/policy/db/hooks.php new file mode 100644 index 00000000000..954d9fb8a9f --- /dev/null +++ b/admin/tool/policy/db/hooks.php @@ -0,0 +1,33 @@ +. + +/** + * Hook callbacks for Policies + * + * @package tool_policy + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_standard_top_of_body_html_generation::class, + 'callback' => \tool_policy\hook_callbacks::class . '::before_standard_top_of_body_html_generation', + 'priority' => 0, + ], +]; diff --git a/admin/tool/policy/lib.php b/admin/tool/policy/lib.php index 9709519aa53..e4594927e0f 100644 --- a/admin/tool/policy/lib.php +++ b/admin/tool/policy/lib.php @@ -71,32 +71,6 @@ function tool_policy_myprofile_navigation(tree $tree, $user, $iscurrentuser, $co return true; } -/** - * Load policy message for guests. - * - * @return string The HTML code to insert before the head. - */ -function tool_policy_before_standard_top_of_body_html() { - global $CFG, $PAGE, $USER; - - $message = null; - if (!empty($CFG->sitepolicyhandler) - && $CFG->sitepolicyhandler == 'tool_policy' - && empty($USER->policyagreed) - && (isguestuser() || !isloggedin())) { - $output = $PAGE->get_renderer('tool_policy'); - try { - $page = new \tool_policy\output\guestconsent(); - $message = $output->render($page); - } catch (dml_read_exception $e) { - // During upgrades, the new plugin code with new SQL could be in place but the DB not upgraded yet. - $message = null; - } - } - - return $message; -} - /** * Callback to add footer elements. * @@ -235,4 +209,4 @@ function tool_policy_output_fragment_accept_on_behalf($args) { } return $mform->render(); -} \ No newline at end of file +} diff --git a/lib/classes/hook/output/before_standard_top_of_body_html_generation.php b/lib/classes/hook/output/before_standard_top_of_body_html_generation.php new file mode 100644 index 00000000000..d0adce58241 --- /dev/null +++ b/lib/classes/hook/output/before_standard_top_of_body_html_generation.php @@ -0,0 +1,101 @@ +. + +namespace core\hook\output; + +/** + * Hook to allow subscribers to add HTML content to the top of the page body. + * + * @package core + * @copyright 2024 Andrew Lyons + * @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 page <head> html tag.')] +#[\core\attribute\hook\replaces_callbacks('before_standard_html_head')] +final class before_standard_top_of_body_html_generation { + /** + * Hook to allow subscribers to add HTML content to the top of the page body. + * + * @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. + * + * Legacy callback 'before_standard_top_of_body_html' is deprecated since Moodle 4.4 + */ + public function process_legacy_callbacks(): void { + // 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) { + $this->add_html( + component_callback( + component: $name, + function: 'before_standard_top_of_body_html', + default: '', + migratedtohook: true, + ), + ); + } + } + + // Give plugins an opportunity to inject extra html content. The callback + // must always return a string containing valid html. + $pluginswithfunction = get_plugins_with_function( + function: 'before_standard_top_of_body_html', + migratedtohook: true, + ); + foreach ($pluginswithfunction as $plugins) { + foreach ($plugins as $function) { + $this->add_html($function() ?? ''); + } + } + } +} diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 07ecbeb4219..14bfdb74306 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -37,6 +37,7 @@ use core\di; use core\hook\manager as hook_manager; +use core\hook\output\before_standard_top_of_body_html_generation; use core\output\named_templatable; use core_completion\cm_completion_details; use core_course\output\activity_information; @@ -811,22 +812,16 @@ class core_renderer extends renderer_base { $output .= "\n".$CFG->additionalhtmltopofbody; } - // 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, 'before_standard_top_of_body_html', [], ''); - } - } + // 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/before_standard_top_of_body_html_generation.php'); - // Give plugins an opportunity to inject extra html content. The callback - // must always return a string containing valid html. - $pluginswithfunction = get_plugins_with_function('before_standard_top_of_body_html', 'lib.php'); - foreach ($pluginswithfunction as $plugins) { - foreach ($plugins as $function) { - $output .= $function(); - } - } + // Allow components to add content to the top of the body. + $hook = new before_standard_top_of_body_html_generation($this, $output); + di::get(hook_manager::class)->dispatch($hook); + $hook->process_legacy_callbacks(); + $output = $hook->get_output(); $output .= $this->maintenance_warning(); diff --git a/lib/tests/core_renderer_test.php b/lib/tests/core_renderer_test.php new file mode 100644 index 00000000000..00b06558015 --- /dev/null +++ b/lib/tests/core_renderer_test.php @@ -0,0 +1,64 @@ +. + +namespace core; + +use core_renderer; +use moodle_page; + +/** + * Tests for \core_renderer. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core_renderer + */ +final class core_renderer_test extends \advanced_testcase { + /** + * @covers \core\hook\before_standard_top_of_body_html_generation + */ + public function test_standard_top_of_body_html(): void { + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_top_of_body_html(); + $this->assertIsString($html); + $this->assertStringNotContainsString('A heading can be added to the top of the body HTML', $html); + } + + /** + * @covers \core\hook\before_standard_top_of_body_html_generation + */ + public function test_before_standard_top_of_body_html_generation_hooked(): void { + require_once(__DIR__ . '/fixtures/core_renderer/before_standard_top_of_body_html_generation_callbacks.php'); + + \core\di::set( + \core\hook\manager::class, + \core\hook\manager::phpunit_get_instance([ + 'test_plugin1' => __DIR__ . '/fixtures/core_renderer/before_standard_top_of_body_html_generation_hooks.php', + ]), + ); + + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_top_of_body_html(); + $this->assertIsString($html); + $this->assertStringContainsString('A heading can be added to the top of the body HTML', $html); + } +} diff --git a/lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_callbacks.php b/lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_callbacks.php new file mode 100644 index 00000000000..01c192f0a85 --- /dev/null +++ b/lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_callbacks.php @@ -0,0 +1,38 @@ +. + +namespace test_fixtures\core_renderer; + +/** + * Hook fixture for \core_renderer::htmlattributes. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class before_standard_top_of_body_html_generation_callbacks { + /** + * Fixture for adding a heading to the top of the body HTML. + * + * @param \core\hook\output\before_standard_top_of_body_html_generation $hook + */ + public static function before_standard_top_of_body_html_generation( + \core\hook\output\before_standard_top_of_body_html_generation $hook, + ): void { + $hook->add_html("

A heading can be added to the top of the body HTML

"); + } +} diff --git a/lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_hooks.php b/lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_hooks.php new file mode 100644 index 00000000000..6daf5a09b20 --- /dev/null +++ b/lib/tests/fixtures/core_renderer/before_standard_top_of_body_html_generation_hooks.php @@ -0,0 +1,34 @@ +. + +/** + * Hook fixture for before_standard_top_of_body_html_generation. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_standard_top_of_body_html_generation::class, + 'callback' => \test_fixtures\core_renderer\before_standard_top_of_body_html_generation_callbacks::class + . '::before_standard_top_of_body_html_generation', + ], +]; diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 163bde8bb81..9158eb66635 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -39,6 +39,7 @@ information provided here is intended especially for developers. * The following callbacks have been migrated to hooks: - 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 * Deprecated PARAM_ types with the exception of PARAM_CLEAN now emit a deprecation exception. These were all deprecated in Moodle 2.0. * A new \core\attribute\deprecated attribute can be used to more clearly describe deprecated methods. * A new \core\deprecation class can be used to inspect for deprecated attributes: From 770e6b49f4362f49dd87e8a3f8f8cd92a783b478 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 6 Mar 2024 22:17:56 +0800 Subject: [PATCH 2/9] MDL-81144 core: Convert standard_footer_html to hook --- .../dataprivacy/classes/hook_callbacks.php | 52 ++++++++++++ admin/tool/dataprivacy/db/hooks.php | 32 +++++++ admin/tool/dataprivacy/lib.php | 23 ----- admin/tool/mobile/classes/hook_callbacks.php | 53 ++++++++++++ admin/tool/mobile/db/hooks.php | 5 ++ admin/tool/mobile/lib.php | 15 ---- admin/tool/policy/classes/hook_callbacks.php | 24 ++++++ admin/tool/policy/db/hooks.php | 5 ++ admin/tool/policy/lib.php | 22 ----- ...before_standard_footer_html_generation.php | 83 +++++++++++++++++++ lib/classes/userfeedback.php | 22 ++++- lib/db/hooks.php | 4 + lib/outputrenderers.php | 23 +++-- lib/tests/core_renderer_test.php | 33 ++++++++ ...ndard_footer_html_generation_callbacks.php | 39 +++++++++ ..._standard_footer_html_generation_hooks.php | 36 ++++++++ lib/upgrade.txt | 2 + 17 files changed, 400 insertions(+), 73 deletions(-) create mode 100644 admin/tool/dataprivacy/classes/hook_callbacks.php create mode 100644 admin/tool/dataprivacy/db/hooks.php create mode 100644 admin/tool/mobile/classes/hook_callbacks.php create mode 100644 lib/classes/hook/output/before_standard_footer_html_generation.php create mode 100644 lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_callbacks.php create mode 100644 lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_hooks.php diff --git a/admin/tool/dataprivacy/classes/hook_callbacks.php b/admin/tool/dataprivacy/classes/hook_callbacks.php new file mode 100644 index 00000000000..70674ad8e68 --- /dev/null +++ b/admin/tool/dataprivacy/classes/hook_callbacks.php @@ -0,0 +1,52 @@ +. + +namespace tool_dataprivacy; + +use html_writer; +use moodle_url; + +/** + * Hook callbacks for tool_dataprivacy. + * + * @package tool_dataprivacy + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class hook_callbacks { + /** + * Add the privacy summary to the footer. + * + * @param \core\hook\output\before_standard_footer_html_generation $hook + */ + public static function standard_footer_html(\core\hook\output\before_standard_footer_html_generation $hook): void { + // A returned 0 means that the setting was set and disabled, false means that there is no value for the provided setting. + $showsummary = get_config('tool_dataprivacy', 'showdataretentionsummary'); + if ($showsummary === false) { + // This means that no value is stored in db. We use the default value in this case. + $showsummary = true; + } + + if ($showsummary) { + $url = new moodle_url('/admin/tool/dataprivacy/summary.php'); + $hook->add_html( + html_writer::div( + html_writer::link($url, get_string('dataretentionsummary', 'tool_dataprivacy')), + ), + ); + } + } +} diff --git a/admin/tool/dataprivacy/db/hooks.php b/admin/tool/dataprivacy/db/hooks.php new file mode 100644 index 00000000000..6a9e1ff91f0 --- /dev/null +++ b/admin/tool/dataprivacy/db/hooks.php @@ -0,0 +1,32 @@ +. + +/** + * Hook callbacks for Data privacy + * + * @package tool_dataprivacy + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_standard_footer_html_generation::class, + 'callback' => \tool_dataprivacy\hook_callbacks::class . '::standard_footer_html', + ], +]; diff --git a/admin/tool/dataprivacy/lib.php b/admin/tool/dataprivacy/lib.php index 060db1b9db6..3a78e0869cf 100644 --- a/admin/tool/dataprivacy/lib.php +++ b/admin/tool/dataprivacy/lib.php @@ -116,29 +116,6 @@ function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser return false; } -/** - * Callback to add footer elements. - * - * @return string HTML footer content - */ -function tool_dataprivacy_standard_footer_html() { - $output = ''; - - // A returned 0 means that the setting was set and disabled, false means that there is no value for the provided setting. - $showsummary = get_config('tool_dataprivacy', 'showdataretentionsummary'); - if ($showsummary === false) { - // This means that no value is stored in db. We use the default value in this case. - $showsummary = true; - } - - if ($showsummary) { - $url = new moodle_url('/admin/tool/dataprivacy/summary.php'); - $output = html_writer::link($url, get_string('dataretentionsummary', 'tool_dataprivacy')); - $output = html_writer::div($output, 'tool_dataprivacy'); - } - return $output; -} - /** * Fragment to add a new purpose. * diff --git a/admin/tool/mobile/classes/hook_callbacks.php b/admin/tool/mobile/classes/hook_callbacks.php new file mode 100644 index 00000000000..61865e6624c --- /dev/null +++ b/admin/tool/mobile/classes/hook_callbacks.php @@ -0,0 +1,53 @@ +. + +namespace tool_mobile; + +use html_writer; + +/** + * Allows plugins to add any elements to the footer. + * + * @package tool_mobile + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class hook_callbacks { + /** + * Callback to add head elements. + * + * @param \core\hook\output\before_standard_footer_html_generation $hook + */ + public static function before_standard_footer_html_generation( + \core\hook\output\before_standard_footer_html_generation $hook, + ): void { + global $CFG; + + require_once(__DIR__ . '/../lib.php'); + + if (empty($CFG->enablemobilewebservice)) { + return; + } + + $url = tool_mobile_create_app_download_url(); + if (empty($url)) { + return; + } + $hook->add_html( + html_writer::link($url, get_string('getmoodleonyourmobile', 'tool_mobile'), ['class' => 'mobilelink']), + ); + } +} diff --git a/admin/tool/mobile/db/hooks.php b/admin/tool/mobile/db/hooks.php index 111aa4517e2..601a1ce29bf 100644 --- a/admin/tool/mobile/db/hooks.php +++ b/admin/tool/mobile/db/hooks.php @@ -30,4 +30,9 @@ $callbacks = [ 'callback' => [\tool_mobile\local\hook\output\before_standard_head_html_generation::class, 'callback'], 'priority' => 0, ], + [ + 'hook' => \core\hook\output\before_standard_footer_html_generation::class, + 'callback' => [\tool_mobile\hook_callbacks::class, 'before_standard_footer_html_generation'], + 'priority' => 0, + ], ]; diff --git a/admin/tool/mobile/lib.php b/admin/tool/mobile/lib.php index a7c5125048f..4424c6f3e51 100644 --- a/admin/tool/mobile/lib.php +++ b/admin/tool/mobile/lib.php @@ -191,21 +191,6 @@ function tool_mobile_myprofile_navigation(\core_user\output\myprofile\tree $tree } } -/** - * Callback to add footer elements. - * - * @return str valid html footer content - * @since Moodle 3.4 - */ -function tool_mobile_standard_footer_html() { - global $CFG; - $output = ''; - if (!empty($CFG->enablemobilewebservice) && $url = tool_mobile_create_app_download_url()) { - $output .= html_writer::link($url, get_string('getmoodleonyourmobile', 'tool_mobile'), ['class' => 'mobilelink']); - } - return $output; -} - /** * Callback to be able to change a message/notification data per processor. * diff --git a/admin/tool/policy/classes/hook_callbacks.php b/admin/tool/policy/classes/hook_callbacks.php index 2aa24ef0c5b..fa2a95346a0 100644 --- a/admin/tool/policy/classes/hook_callbacks.php +++ b/admin/tool/policy/classes/hook_callbacks.php @@ -16,7 +16,10 @@ namespace tool_policy; +use core\hook\output\before_standard_footer_html_generation; use core\hook\output\before_standard_top_of_body_html_generation; +use html_writer; +use moodle_url; /** * Allows the plugin to add any elements to the footer. @@ -59,4 +62,25 @@ class hook_callbacks { return; } } + + /** + * Add the user policy settings link to the footer. + * + * @param before_standard_footer_html_generation $hook + */ + public static function before_standard_footer_html_generation(before_standard_footer_html_generation $hook): void { + global $CFG, $PAGE; + + if (empty($CFG->sitepolicyhandler) || $CFG->sitepolicyhandler !== 'tool_policy') { + return; + } + + $policies = api::get_current_versions_ids(); + if (!empty($policies)) { + $url = new moodle_url('/admin/tool/policy/viewall.php', ['returnurl' => $PAGE->url]); + $hook->add_html( + html_writer::link($url, get_string('userpolicysettings', 'tool_policy'), ['class' => 'policiesfooter']), + ); + } + } } diff --git a/admin/tool/policy/db/hooks.php b/admin/tool/policy/db/hooks.php index 954d9fb8a9f..258aa2ac321 100644 --- a/admin/tool/policy/db/hooks.php +++ b/admin/tool/policy/db/hooks.php @@ -30,4 +30,9 @@ $callbacks = [ 'callback' => \tool_policy\hook_callbacks::class . '::before_standard_top_of_body_html_generation', 'priority' => 0, ], + [ + 'hook' => \core\hook\output\before_standard_footer_html_generation::class, + 'callback' => [\tool_policy\hook_callbacks::class, 'before_standard_footer_html_generation'], + 'priority' => 0, + ], ]; diff --git a/admin/tool/policy/lib.php b/admin/tool/policy/lib.php index e4594927e0f..722e4433797 100644 --- a/admin/tool/policy/lib.php +++ b/admin/tool/policy/lib.php @@ -71,28 +71,6 @@ function tool_policy_myprofile_navigation(tree $tree, $user, $iscurrentuser, $co return true; } -/** - * Callback to add footer elements. - * - * @return string HTML footer content - */ -function tool_policy_standard_footer_html() { - global $CFG, $PAGE; - - $output = ''; - if (!empty($CFG->sitepolicyhandler) - && $CFG->sitepolicyhandler == 'tool_policy') { - $policies = api::get_current_versions_ids(); - if (!empty($policies)) { - $url = new moodle_url('/admin/tool/policy/viewall.php', ['returnurl' => $PAGE->url]); - $output .= html_writer::link($url, get_string('userpolicysettings', 'tool_policy')); - $output = html_writer::div($output, 'policiesfooter'); - } - } - - return $output; -} - /** * Hooks redirection to policy acceptance pages before sign up. */ diff --git a/lib/classes/hook/output/before_standard_footer_html_generation.php b/lib/classes/hook/output/before_standard_footer_html_generation.php new file mode 100644 index 00000000000..723f86a7729 --- /dev/null +++ b/lib/classes/hook/output/before_standard_footer_html_generation.php @@ -0,0 +1,83 @@ +. + +namespace core\hook\output; + +use renderer_base; + +/** + * Hook to allow subscribers to add HTML content to the footer. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +#[\core\attribute\tags('output')] +#[\core\attribute\label('Allows plugins to add any elements to the page footer.')] +#[\core\attribute\hook\replaces_callbacks('standard_footer_html')] +final class before_standard_footer_html_generation { + /** + * Hook to allow subscribers to add HTML content before the footer. + * + * @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. + * + * Legacy callback 'standard_footer_html' is deprecated since Moodle 4.4 + */ + public function process_legacy_callbacks(): void { + // Give plugins an opportunity to add any footer elements. + // The callback must always return a string containing valid html footer content. + $pluginswithfunction = get_plugins_with_function(function: 'standard_footer_html', migratedtohook: true); + foreach ($pluginswithfunction as $plugins) { + foreach ($plugins as $function) { + $this->add_html($function()); + } + } + } +} diff --git a/lib/classes/userfeedback.php b/lib/classes/userfeedback.php index 1d6417fffb7..67e85054e75 100644 --- a/lib/classes/userfeedback.php +++ b/lib/classes/userfeedback.php @@ -22,7 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -defined('MOODLE_INTERNAL') || die(); +use core\hook\output\before_standard_footer_html_generation; /** * This Class contains helper functions for user feedback functionality. @@ -146,6 +146,26 @@ class core_userfeedback { return $url; } + /** + * Callback for the before_standard_footer_html_generation hook to add a user feedback footer link if configured. + * + * @param before_standard_footer_html_generation $hook + */ + public static function before_standard_footer_html_generation( + before_standard_footer_html_generation $hook, + ): void { + if (self::can_give_feedback()) { + $hook->add_html(html_writer::div( + $hook->renderer->render_from_template( + 'core/userfeedback_footer_link', + [ + 'url' => self::make_link()->out(false), + ] + ) + )); + } + } + /** * Whether the current can give feedback. * diff --git a/lib/db/hooks.php b/lib/db/hooks.php index 1e2dc77d6bc..948397fabfc 100644 --- a/lib/db/hooks.php +++ b/lib/db/hooks.php @@ -93,4 +93,8 @@ $callbacks = [ 'hook' => \core_enrol\hook\before_user_enrolment_remove::class, 'callback' => \core_communication\hook_listener::class . '::remove_communication_membership_for_unenrolled_user', ], + [ + 'hook' => \core\hook\output\before_standard_footer_html_generation::class, + 'callback' => \core_userfeedback::class . '::before_standard_footer_html_generation', + ], ]; diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 14bfdb74306..7f1440a69ac 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -37,6 +37,7 @@ use core\di; use core\hook\manager as hook_manager; +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; use core_completion\cm_completion_details; @@ -872,23 +873,21 @@ class core_renderer extends renderer_base { * @return string HTML fragment. */ public function standard_footer_html() { - global $CFG; - - $output = ''; if (during_initial_install()) { // Debugging info can not work before install is finished, // in any case we do not want any links during installation! - return $output; + return ''; } - // Give plugins an opportunity to add any footer elements. - // The callback must always return a string containing valid html footer content. - $pluginswithfunction = get_plugins_with_function('standard_footer_html', 'lib.php'); - foreach ($pluginswithfunction as $plugins) { - foreach ($plugins as $function) { - $output .= $function(); - } - } + // 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/before_standard_footer_html_generation.php'); + + $hook = new before_standard_footer_html_generation($this); + di::get(hook_manager::class)->dispatch($hook); + $hook->process_legacy_callbacks(); + $output = $hook->get_output(); if (core_userfeedback::can_give_feedback()) { $output .= html_writer::div( diff --git a/lib/tests/core_renderer_test.php b/lib/tests/core_renderer_test.php index 00b06558015..7c17183937c 100644 --- a/lib/tests/core_renderer_test.php +++ b/lib/tests/core_renderer_test.php @@ -61,4 +61,37 @@ final class core_renderer_test extends \advanced_testcase { $this->assertIsString($html); $this->assertStringContainsString('A heading can be added to the top of the body HTML', $html); } + + /** + * @covers \core\hook\before_standard_footer_html_generation + */ + public function before_standard_footer_html_generation(): void { + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_footer_html(); + $this->assertIsString($html); + $this->assertStringNotContainsString('A heading can be added', $html); + } + + /** + * @covers \core\hook\before_standard_footer_html_generation + */ + public function test_before_standard_footer_html_generation_hooked(): void { + require_once(__DIR__ . '/fixtures/core_renderer/before_standard_footer_html_generation_callbacks.php'); + + \core\di::set( + \core\hook\manager::class, + \core\hook\manager::phpunit_get_instance([ + 'test_plugin1' => __DIR__ . '/fixtures/core_renderer/before_standard_footer_html_generation_hooks.php', + ]), + ); + + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_footer_html(); + $this->assertIsString($html); + $this->assertStringContainsString('A heading can be added', $html); + } } diff --git a/lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_callbacks.php b/lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_callbacks.php new file mode 100644 index 00000000000..41de8114f1d --- /dev/null +++ b/lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_callbacks.php @@ -0,0 +1,39 @@ +. + +namespace test_fixtures\core_renderer; + +/** + * Hook fixture for \core_renderer::standard_footer_html. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class before_standard_footer_html_generation_callbacks { + + /** + * Fixture for adding a heading before the standard footer HTML generation. + * + * @param \core\hook\output\before_standard_footer_html_generation $hook + */ + public static function before_standard_footer_html_generation( + \core\hook\output\before_standard_footer_html_generation $hook, + ): void { + $hook->add_html("

A heading can be added

"); + } +} diff --git a/lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_hooks.php b/lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_hooks.php new file mode 100644 index 00000000000..66495a837a1 --- /dev/null +++ b/lib/tests/fixtures/core_renderer/before_standard_footer_html_generation_hooks.php @@ -0,0 +1,36 @@ +. + +/** + * Hook fixture for \core_renderer::standard_footer_html. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_standard_footer_html_generation::class, + 'callback' => [ + \test_fixtures\core_renderer\before_standard_footer_html_generation_callbacks::class, + 'before_standard_footer_html_generation', + ], + ], +]; diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 9158eb66635..7e501e73389 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -40,6 +40,8 @@ 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_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. * A new \core\attribute\deprecated attribute can be used to more clearly describe deprecated methods. * A new \core\deprecation class can be used to inspect for deprecated attributes: From e3f2b03685111005d77c734bcbf672a9e61d74c3 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 6 Mar 2024 22:19:12 +0800 Subject: [PATCH 3/9] MDL-81144 core: Migrate non-hook footer usages --- lib/outputrenderers.php | 6 --- lib/tests/core_userfeedback_test.php | 73 ++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 lib/tests/core_userfeedback_test.php diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 7f1440a69ac..dc36da8b0e0 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -889,12 +889,6 @@ class core_renderer extends renderer_base { $hook->process_legacy_callbacks(); $output = $hook->get_output(); - if (core_userfeedback::can_give_feedback()) { - $output .= html_writer::div( - $this->render_from_template('core/userfeedback_footer_link', ['url' => core_userfeedback::make_link()->out(false)]) - ); - } - if ($this->page->devicetypeinuse == 'legacy') { // The legacy theme is in use print the notification $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse')); diff --git a/lib/tests/core_userfeedback_test.php b/lib/tests/core_userfeedback_test.php new file mode 100644 index 00000000000..0b91fafed7d --- /dev/null +++ b/lib/tests/core_userfeedback_test.php @@ -0,0 +1,73 @@ +. + +namespace core; + +/** + * Tests for \core_userfeedback + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core_userfeedback + */ +final class core_userfeedback_test extends \advanced_testcase { + public function test_footer_not_added_if_disabled(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $page = new \moodle_page(); + $renderer = new \core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_footer_html(); + $this->assertStringNotContainsString( + get_string('calltofeedback_give', 'core'), + $html, + ); + } + + public function test_footer_added_if_enabled_loggedin(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + set_config('enableuserfeedback', 1);; + + $page = new \moodle_page(); + $renderer = new \core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_footer_html(); + $this->assertStringContainsString( + get_string('calltofeedback_give', 'core'), + $html, + ); + } + + public function test_footer_not_added_if_loggedout(): void { + $this->resetAfterTest(); + + set_config('enableuserfeedback', 1);; + + $page = new \moodle_page(); + $renderer = new \core_renderer($page, RENDERER_TARGET_GENERAL); + + $html = $renderer->standard_footer_html(); + $this->assertStringNotContainsString( + get_string('calltofeedback_give', 'core'), + $html, + ); + } +} From 6ad9f2606f961debecd0e76d0ee62796eeb43228 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 7 Mar 2024 10:44:42 +0800 Subject: [PATCH 4/9] MDL-81144 tool_mobile: Standarise hook locations --- admin/tool/mobile/classes/hook_callbacks.php | 28 ++++++++++ .../before_standard_head_html_generation.php | 52 ------------------- admin/tool/mobile/db/hooks.php | 3 +- 3 files changed, 29 insertions(+), 54 deletions(-) delete mode 100644 admin/tool/mobile/classes/local/hook/output/before_standard_head_html_generation.php diff --git a/admin/tool/mobile/classes/hook_callbacks.php b/admin/tool/mobile/classes/hook_callbacks.php index 61865e6624c..8708cc477d6 100644 --- a/admin/tool/mobile/classes/hook_callbacks.php +++ b/admin/tool/mobile/classes/hook_callbacks.php @@ -26,6 +26,34 @@ use html_writer; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class hook_callbacks { + /** + * Callback to add head elements. + * + * @param \core\hook\output\before_standard_head_html_generation $hook + */ + public static function before_standard_head_html_generation( + \core\hook\output\before_standard_head_html_generation $hook, + ): void { + global $CFG, $PAGE; + // Smart App Banners meta tag is only displayed if mobile services are enabled and configured. + if (!empty($CFG->enablemobilewebservice)) { + $mobilesettings = get_config('tool_mobile'); + if (!empty($mobilesettings->enablesmartappbanners)) { + if (!empty($mobilesettings->iosappid)) { + $hook->add_html( + '' + ); + } + + if (!empty($mobilesettings->androidappid)) { + $mobilemanifesturl = "$CFG->wwwroot/$CFG->admin/tool/mobile/mobile.webmanifest.php"; + $hook->add_html(''); + } + } + } + } + /** * Callback to add head elements. * diff --git a/admin/tool/mobile/classes/local/hook/output/before_standard_head_html_generation.php b/admin/tool/mobile/classes/local/hook/output/before_standard_head_html_generation.php deleted file mode 100644 index 7d4ddbb4dcc..00000000000 --- a/admin/tool/mobile/classes/local/hook/output/before_standard_head_html_generation.php +++ /dev/null @@ -1,52 +0,0 @@ -. - -namespace tool_mobile\local\hook\output; - -/** - * Allows plugins to add any elements to the page html tag - * - * @package tool_mobile - * @copyright 2023 Marina Glancy - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class before_standard_head_html_generation { - /** - * Callback to add head elements. - * - * @param \core\hook\output\before_standard_head_html_generation $hook - */ - public static function callback(\core\hook\output\before_standard_head_html_generation $hook): void { - global $CFG, $PAGE; - // Smart App Banners meta tag is only displayed if mobile services are enabled and configured. - if (!empty($CFG->enablemobilewebservice)) { - $mobilesettings = get_config('tool_mobile'); - if (!empty($mobilesettings->enablesmartappbanners)) { - if (!empty($mobilesettings->iosappid)) { - $hook->add_html( - '' - ); - } - - if (!empty($mobilesettings->androidappid)) { - $mobilemanifesturl = "$CFG->wwwroot/$CFG->admin/tool/mobile/mobile.webmanifest.php"; - $hook->add_html(''); - } - } - } - } -} diff --git a/admin/tool/mobile/db/hooks.php b/admin/tool/mobile/db/hooks.php index 601a1ce29bf..d490bff8294 100644 --- a/admin/tool/mobile/db/hooks.php +++ b/admin/tool/mobile/db/hooks.php @@ -27,8 +27,7 @@ defined('MOODLE_INTERNAL') || die(); $callbacks = [ [ 'hook' => \core\hook\output\before_standard_head_html_generation::class, - 'callback' => [\tool_mobile\local\hook\output\before_standard_head_html_generation::class, 'callback'], - 'priority' => 0, + 'callback' => [\tool_mobile\hook_callbacks::class, 'before_standard_head_html_generation'], ], [ 'hook' => \core\hook\output\before_standard_footer_html_generation::class, From ecd5274e74d20572006ee58b62377f5d5c4d78ba Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 6 Mar 2024 22:33:21 +0800 Subject: [PATCH 5/9] MDL-81144 core: Convert add_htmlattributes to hook --- .../hook/output/before_html_attributes.php | 98 +++++++++++++++++++ lib/outputrenderers.php | 28 +++--- lib/tests/core_renderer_test.php | 33 +++++++ .../htmlattributes_callbacks.php | 36 +++++++ .../core_renderer/htmlattributes_hooks.php | 33 +++++++ 5 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 lib/classes/hook/output/before_html_attributes.php create mode 100644 lib/tests/fixtures/core_renderer/htmlattributes_callbacks.php create mode 100644 lib/tests/fixtures/core_renderer/htmlattributes_hooks.php diff --git a/lib/classes/hook/output/before_html_attributes.php b/lib/classes/hook/output/before_html_attributes.php new file mode 100644 index 00000000000..e1f46ebc286 --- /dev/null +++ b/lib/classes/hook/output/before_html_attributes.php @@ -0,0 +1,98 @@ +. + +namespace core\hook\output; + +/** + * Class before_html_attributes + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @property-read \renderer_base $renderer The page renderer object + * @property array $attributes The list of HTML attributes to be added to the tag. + */ +#[\core\attribute\tags('output')] +#[\core\attribute\label('Allows plugins to add, remove or modify any attributes of the html tag.')] +#[\core\attribute\hook\replaces_callbacks('add_htmlattributes')] +final class before_html_attributes { + /** + * Constructor for the before_html_attributes hook. + * + * @param \renderer_base $renderer The page renderer object + * @param array $attributes The list of HTML attributes initially on the tag + */ + public function __construct( + /** @var \renderer_base The page renderer */ + public readonly \renderer_base $renderer, + /** @var array The list of HTML attributes initially on the tag */ + private array $attributes = [], + ) { + } + + /** + * Add an HTML attribute to the list. + * + * @param string $name + * @param string $value + */ + public function add_attribute(string $name, string $value): void { + $this->attributes[$name] = $value; + } + + /** + * Get the list of attributes. + * + * @return array + */ + public function get_attributes(): array { + return $this->attributes; + } + + /** + * Remove an HTML attribute from the list. + * + * @param string $name + */ + public function remove_attribute(string $name): void { + unset($this->attributes[$name]); + } + + /** + * Process legacy callbacks. + */ + public function process_legacy_callbacks(): void { + // Legacy callback 'add_htmlattributes' is deprecated since Moodle 4.4. + + // This function should return an array of html attribute names => values. + $pluginswithfunction = get_plugins_with_function( + function: 'add_htmlattributes', + migratedtohook: true, + ); + foreach ($pluginswithfunction as $plugins) { + foreach ($plugins as $function) { + $newattrs = $function(); + unset($newattrs['dir']); + unset($newattrs['lang']); + unset($newattrs['xmlns']); + unset($newattrs['xml:lang']); + foreach ($newattrs as $name => $value) { + $this->add_attribute($name, $value); + } + } + } + } +} diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index dc36da8b0e0..062c8a86707 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -656,26 +656,22 @@ class core_renderer extends renderer_base { */ public function htmlattributes() { $return = get_html_lang(true); - $attributes = array(); + + // 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/before_html_attributes.php'); + + $hook = new before_html_attributes($this); + if ($this->page->theme->doctype !== 'html5') { - $attributes['xmlns'] = 'http://www.w3.org/1999/xhtml'; + $hook->add_attribute('xmlns', 'http://www.w3.org/1999/xhtml'); } - // Give plugins an opportunity to add things like xml namespaces to the html element. - // This function should return an array of html attribute names => values. - $pluginswithfunction = get_plugins_with_function('add_htmlattributes', 'lib.php'); - foreach ($pluginswithfunction as $plugins) { - foreach ($plugins as $function) { - $newattrs = $function(); - unset($newattrs['dir']); - unset($newattrs['lang']); - unset($newattrs['xmlns']); - unset($newattrs['xml:lang']); - $attributes += $newattrs; - } - } + di::get(hook_manager::class)->dispatch($hook); + $hook->process_legacy_callbacks(); - foreach ($attributes as $key => $val) { + foreach ($hook->get_attributes() as $key => $val) { $val = s($val); $return .= " $key=\"$val\""; } diff --git a/lib/tests/core_renderer_test.php b/lib/tests/core_renderer_test.php index 7c17183937c..16cc3690cfa 100644 --- a/lib/tests/core_renderer_test.php +++ b/lib/tests/core_renderer_test.php @@ -94,4 +94,37 @@ final class core_renderer_test extends \advanced_testcase { $this->assertIsString($html); $this->assertStringContainsString('A heading can be added', $html); } + + /** + * @covers \core\hook\before_html_attributes + */ + public function test_htmlattributes(): void { + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $attributes = $renderer->htmlattributes(); + $this->assertIsString($attributes); + $this->assertStringNotContainsString('data-test="test"', $attributes); + } + + /** + * @covers \core\hook\before_html_attributes + */ + public function test_htmlattributes_hooked(): void { + require_once(__DIR__ . '/fixtures/core_renderer/htmlattributes_callbacks.php'); + + \core\di::set( + \core\hook\manager::class, + \core\hook\manager::phpunit_get_instance([ + 'test_plugin1' => __DIR__ . '/fixtures/core_renderer/htmlattributes_hooks.php', + ]), + ); + + $page = new moodle_page(); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $attributes = $renderer->htmlattributes(); + $this->assertIsString($attributes); + $this->assertStringContainsString('data-test="test"', $attributes); + } } diff --git a/lib/tests/fixtures/core_renderer/htmlattributes_callbacks.php b/lib/tests/fixtures/core_renderer/htmlattributes_callbacks.php new file mode 100644 index 00000000000..c5a1f2b7f14 --- /dev/null +++ b/lib/tests/fixtures/core_renderer/htmlattributes_callbacks.php @@ -0,0 +1,36 @@ +. + +namespace test_fixtures\core_renderer; + +/** + * Hook fixture for \core_renderer::htmlattributes. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class htmlattributes { + /** + * Fixture for adding a data attribute to the HTML element. + * + * @param \core\hook\output\before_html_attributes $hook + */ + public static function before_html_attributes(\core\hook\output\before_html_attributes $hook): void { + $hook->add_attribute('data-test', 'test'); + } +} diff --git a/lib/tests/fixtures/core_renderer/htmlattributes_hooks.php b/lib/tests/fixtures/core_renderer/htmlattributes_hooks.php new file mode 100644 index 00000000000..fa8bf33279f --- /dev/null +++ b/lib/tests/fixtures/core_renderer/htmlattributes_hooks.php @@ -0,0 +1,33 @@ +. + +/** + * Hook fixture for \core_renderer::htmlattributes. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_html_attributes::class, + 'callback' => \test_fixtures\core_renderer\htmlattributes::class . '::before_html_attributes', + ], +]; From e4a8ed5cc2130a6a4e1769065b54bd10b13c9352 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 7 Mar 2024 10:55:17 +0800 Subject: [PATCH 6/9] 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 @@ +. + +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 + * @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 @@ +. + +namespace test_fixtures\core_renderer; + +/** + * Hook fixture for \core_renderer::after_standard_main_region_html_generation. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @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("

A heading can be added

"); + } +} 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 @@ +. + +/** + * Hook fixture for \core_renderer::after_standard_main_region_html_generation. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @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 @@ +. + +namespace core_message; + +/** + * Class hook_callbacks + * + * @package core_message + * @copyright 2024 Andrew Lyons + * @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); -} From 679001e729da480946e57d0228f4976855c803f3 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 7 Mar 2024 11:12:48 +0800 Subject: [PATCH 7/9] MDL-81144 core: Convert before_footer to hook --- .../tool/usertours/classes/hook_callbacks.php | 37 +++++++++ admin/tool/usertours/db/hooks.php | 33 ++++++++ admin/tool/usertours/lib.php | 7 -- .../output/before_footer_html_generation.php | 81 +++++++++++++++++++ lib/outputrenderers.php | 24 +++--- lib/tests/core_renderer_test.php | 41 ++++++++++ ...efore_footer_html_generation_callbacks.php | 36 +++++++++ .../before_footer_html_generation_hooks.php | 36 +++++++++ lib/upgrade.txt | 5 ++ 9 files changed, 280 insertions(+), 20 deletions(-) create mode 100644 admin/tool/usertours/classes/hook_callbacks.php create mode 100644 admin/tool/usertours/db/hooks.php create mode 100644 lib/classes/hook/output/before_footer_html_generation.php create mode 100644 lib/tests/fixtures/core_renderer/before_footer_html_generation_callbacks.php create mode 100644 lib/tests/fixtures/core_renderer/before_footer_html_generation_hooks.php diff --git a/admin/tool/usertours/classes/hook_callbacks.php b/admin/tool/usertours/classes/hook_callbacks.php new file mode 100644 index 00000000000..3770f29fa1e --- /dev/null +++ b/admin/tool/usertours/classes/hook_callbacks.php @@ -0,0 +1,37 @@ +. + +namespace tool_usertours; + +use core\hook\output\before_footer_html_generation; + +/** + * Hook callbacks for usertours. + * + * @package tool_usertours + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class hook_callbacks { + /** + * Bootstrap the usertours library. + * + * @param before_footer_html_generation $hook + */ + public static function before_footer_html_generation(before_footer_html_generation $hook): void { + \tool_usertours\helper::bootstrap(); + } +} diff --git a/admin/tool/usertours/db/hooks.php b/admin/tool/usertours/db/hooks.php new file mode 100644 index 00000000000..e85cef7f6e5 --- /dev/null +++ b/admin/tool/usertours/db/hooks.php @@ -0,0 +1,33 @@ +. + +/** + * Hook callbacks for User tours + * + * @package tool_usertours + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_footer_html_generation::class, + 'callback' => \tool_usertours\hook_callbacks::class . '::before_footer_html_generation', + 'priority' => 0, + ], +]; diff --git a/admin/tool/usertours/lib.php b/admin/tool/usertours/lib.php index 7ff8bbd6339..36f516c2416 100644 --- a/admin/tool/usertours/lib.php +++ b/admin/tool/usertours/lib.php @@ -67,13 +67,6 @@ function tool_usertours_extend_navigation_user() { \tool_usertours\helper::bootstrap(); } -/** - * Add JS to bootstrap tours. Only in Moodle 3.3+ - */ -function tool_usertours_before_footer() { - \tool_usertours\helper::bootstrap(); -} - /** * Map icons for font-awesome themes. */ diff --git a/lib/classes/hook/output/before_footer_html_generation.php b/lib/classes/hook/output/before_footer_html_generation.php new file mode 100644 index 00000000000..21d977840be --- /dev/null +++ b/lib/classes/hook/output/before_footer_html_generation.php @@ -0,0 +1,81 @@ +. + +namespace core\hook\output; + +/** + * Hook to allow subscribers to add HTML content to the footer. + * + * @package core + * @copyright 2024 Andrew Lyons + * @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('before_footer')] +final class before_footer_html_generation { + /** + * Hook to allow subscribers to add HTML content to the footer. + * + * @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: 'before_footer', migratedtohook: true); + foreach ($pluginswithfunction as $plugins) { + foreach ($plugins as $function) { + $extrafooter = $function(); + if (is_string($extrafooter)) { + $this->add_html($extrafooter); + } + } + } + } +} diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index b83aa161c77..047fd7666cf 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -38,6 +38,7 @@ 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_footer_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; @@ -1408,6 +1409,7 @@ class core_renderer extends renderer_base { if ($cutpos === false) { throw new coding_exception('page layout file ' . $layoutfile . ' does not contain the main content placeholder, please include "main_content() ?>" in theme layout file.'); } + $header = substr($rendered, 0, $cutpos); $footer = substr($rendered, $cutpos + strlen($token)); @@ -1482,20 +1484,16 @@ class core_renderer extends renderer_base { public function footer() { global $CFG, $DB, $PERF; - $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/before_footer_html_generation.php'); - // Give plugins an opportunity to touch the page before JS is finalized. - $pluginswithfunction = get_plugins_with_function('before_footer', 'lib.php'); - foreach ($pluginswithfunction as $plugins) { - foreach ($plugins as $function) { - $extrafooter = $function(); - if (is_string($extrafooter)) { - $output .= $extrafooter; - } - } - } - - $output .= $this->container_end_all(true); + $hook = new before_footer_html_generation($this); + di::get(hook_manager::class)->dispatch($hook); + $hook->process_legacy_callbacks(); + $hook->add_html($this->container_end_all(true)); + $output = $hook->get_output(); $footer = $this->opencontainers->pop('header/footer'); diff --git a/lib/tests/core_renderer_test.php b/lib/tests/core_renderer_test.php index cbc4af204ac..7d07b48d217 100644 --- a/lib/tests/core_renderer_test.php +++ b/lib/tests/core_renderer_test.php @@ -62,6 +62,47 @@ final class core_renderer_test extends \advanced_testcase { $this->assertStringContainsString('A heading can be added to the top of the body HTML', $html); } + /** + * @covers \core\hook\before_footer_html_generation + */ + public function test_before_footer_html_generation(): void { + $this->resetAfterTest(); + $page = new moodle_page(); + $page->set_state(moodle_page::STATE_PRINTING_HEADER); + $page->set_state(moodle_page::STATE_IN_BODY); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $page->opencontainers->push('header/footer', ''); + $html = $renderer->footer(); + $this->assertIsString($html); + $this->assertStringNotContainsString('A heading can be added', $html); + } + + /** + * @covers \core\hook\before_footer_html_generation + */ + public function test_before_footer_html_generation_hooked(): void { + $this->resetAfterTest(); + require_once(__DIR__ . '/fixtures/core_renderer/before_footer_html_generation_callbacks.php'); + + \core\di::set( + \core\hook\manager::class, + \core\hook\manager::phpunit_get_instance([ + 'test_plugin1' => __DIR__ . '/fixtures/core_renderer/before_footer_html_generation_hooks.php', + ]), + ); + + $page = new moodle_page(); + $page->set_state(moodle_page::STATE_PRINTING_HEADER); + $page->set_state(moodle_page::STATE_IN_BODY); + $renderer = new core_renderer($page, RENDERER_TARGET_GENERAL); + + $page->opencontainers->push('header/footer', ''); + $html = $renderer->footer(); + $this->assertIsString($html); + $this->assertStringContainsString('A heading can be added', $html); + } + /** * @covers \core\hook\before_standard_footer_html_generation */ diff --git a/lib/tests/fixtures/core_renderer/before_footer_html_generation_callbacks.php b/lib/tests/fixtures/core_renderer/before_footer_html_generation_callbacks.php new file mode 100644 index 00000000000..f4c99c145ca --- /dev/null +++ b/lib/tests/fixtures/core_renderer/before_footer_html_generation_callbacks.php @@ -0,0 +1,36 @@ +. + +namespace test_fixtures\core_renderer; + +/** + * Hook fixture for \core_renderer::footer. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class before_footer_html_generation_callbacks { + /** + * Fixture for adding a heading before the footer HTML generation. + * + * @param \core\hook\output\before_footer_html_generation $hook + */ + public static function before_footer_html_generation(\core\hook\output\before_footer_html_generation $hook): void { + $hook->add_html("

A heading can be added

"); + } +} diff --git a/lib/tests/fixtures/core_renderer/before_footer_html_generation_hooks.php b/lib/tests/fixtures/core_renderer/before_footer_html_generation_hooks.php new file mode 100644 index 00000000000..09452e8ba10 --- /dev/null +++ b/lib/tests/fixtures/core_renderer/before_footer_html_generation_hooks.php @@ -0,0 +1,36 @@ +. + +/** + * Hook fixture for \core_renderer::standard_footer_html. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_footer_html_generation::class, + 'callback' => [ + \test_fixtures\core_renderer\before_footer_html_generation_callbacks::class, + 'before_footer_html_generation', + ], + ], +]; diff --git a/lib/upgrade.txt b/lib/upgrade.txt index e4ff83a489d..cf71d0662e7 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -41,7 +41,12 @@ information provided here is intended especially for developers. - 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 +<<<<<<< HEAD - standard_footer_html() -> core\hook\output\before_standard_footer_html_generation +======= + - before_footer() -> core\hook\output\before_footer_html_generation + - standard_footer_html() -> core\hook\output\before_standard_footer_html_generation +>>>>>>> 3516753f3a (MDL-81144 core: Convert before_footer to hook) - 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. * A new \core\attribute\deprecated attribute can be used to more clearly describe deprecated methods. From 305248f41d2eb3358e67c018e573c37e416be831 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 28 Mar 2024 15:03:03 +0800 Subject: [PATCH 8/9] MDL-81144 core: Convert before_http_headers --- .../hook/output/before_http_headers.php | 54 +++++++++++++++++++ lib/outputrenderers.php | 15 +++--- lib/upgrade.txt | 5 +- 3 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 lib/classes/hook/output/before_http_headers.php diff --git a/lib/classes/hook/output/before_http_headers.php b/lib/classes/hook/output/before_http_headers.php new file mode 100644 index 00000000000..47ad38e11e1 --- /dev/null +++ b/lib/classes/hook/output/before_http_headers.php @@ -0,0 +1,54 @@ +. + +namespace core\hook\output; + +/** + * Class before_http_headers + * + * @package core + * @copyright 2024 Andrew Lyons + * @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 make changes before headers are sent')] +#[\core\attribute\hook\replaces_callbacks('before_http_headers')] +class before_http_headers { + /** + * Hook to allow subscribers to modify the process before headers are sent. + * + * @param \renderer_base $renderer + */ + public function __construct( + /** @var \renderer_base The page renderer object */ + public readonly \renderer_base $renderer, + ) { + } + + + /** + * Process legacy callbacks. + */ + public function process_legacy_callbacks(): void { + $pluginswithfunction = get_plugins_with_function(function: 'before_http_headers', migratedtohook: true); + foreach ($pluginswithfunction as $plugins) { + foreach ($plugins as $function) { + $function(); + } + } + } +} diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 047fd7666cf..0fef2261c53 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -40,6 +40,7 @@ use core\hook\manager as hook_manager; use core\hook\output\after_standard_main_region_html_generation; use core\hook\output\before_footer_html_generation; use core\hook\output\before_html_attributes; +use core\hook\output\before_http_headers; 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; @@ -1359,14 +1360,12 @@ class core_renderer extends renderer_base { public function header() { global $USER, $CFG, $SESSION; - // Give plugins an opportunity touch things before the http headers are sent - // such as adding additional headers. The return value is ignored. - $pluginswithfunction = get_plugins_with_function('before_http_headers', 'lib.php'); - foreach ($pluginswithfunction as $plugins) { - foreach ($plugins as $function) { - $function(); - } - } + // 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/before_http_headers.php'); + + di::get(hook_manager::class)->dispatch(new before_http_headers($this)); if (\core\session\manager::is_loggedinas()) { $this->page->add_body_class('userloggedinas'); diff --git a/lib/upgrade.txt b/lib/upgrade.txt index cf71d0662e7..e952bc0923c 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -39,14 +39,11 @@ information provided here is intended especially for developers. * The following callbacks have been migrated to hooks: - before_standard_html_head() -> core\hook\output\before_standard_head_html_generation - bulk_user_actions() -> core_user\hook\extend_bulk_user_actions + - before_http_headers() -> core\hook\output\before_http_headers - 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 -<<<<<<< HEAD - - standard_footer_html() -> core\hook\output\before_standard_footer_html_generation -======= - before_footer() -> core\hook\output\before_footer_html_generation - standard_footer_html() -> core\hook\output\before_standard_footer_html_generation ->>>>>>> 3516753f3a (MDL-81144 core: Convert before_footer to hook) - 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. * A new \core\attribute\deprecated attribute can be used to more clearly describe deprecated methods. From 9bd3228d69b6786efb4e89f6c0956b07ce82fc33 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 7 Mar 2024 11:24:59 +0800 Subject: [PATCH 9/9] MDL-81144 core: Standardise init of standard_head_html_prepend hook --- .../hook/output/before_standard_head_html_generation.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/classes/hook/output/before_standard_head_html_generation.php b/lib/classes/hook/output/before_standard_head_html_generation.php index 4a2e99ac9b4..1021830c028 100644 --- a/lib/classes/hook/output/before_standard_head_html_generation.php +++ b/lib/classes/hook/output/before_standard_head_html_generation.php @@ -27,13 +27,21 @@ namespace core\hook\output; #[\core\attribute\label('Allows plugins to add any elements to the page <head> html tag.')] #[\core\attribute\hook\replaces_callbacks('before_standard_html_head')] final class before_standard_head_html_generation { + /** + * Hook to allow subscribers to add HTML content to page head tag. + * + * @param renderer_base $renderer + * @param string $output Initial output + */ public function __construct( /** @var \renderer_base The core_renderer instance used for the generation */ public readonly \renderer_base $renderer, + /** @var string The collected output */ private string $output = '', ) { } + /** * Plugins implementing callback can add any HTML to the page. *