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: