mirror of
https://github.com/moodle/moodle.git
synced 2025-04-14 04:52:36 +02:00
MDL-78332 core: fix hook discovery and use is_subclass_of()
And order hooks in admin UI - core first.
This commit is contained in:
parent
0dcb5c4281
commit
8a9e5aeb7a
@ -92,7 +92,7 @@ class hook_list_table extends flexible_table {
|
||||
public function out(): void {
|
||||
// All hook consumers referenced from the db/hooks.php files.
|
||||
$hookmanager = \core\hook\manager::get_instance();
|
||||
$allhooks = $hookmanager->get_all_callbacks();
|
||||
$allhooks = (array)$hookmanager->get_all_callbacks();
|
||||
|
||||
// Add any unused hooks.
|
||||
foreach (array_keys($this->emitters) as $classname) {
|
||||
@ -102,6 +102,17 @@ class hook_list_table extends flexible_table {
|
||||
$allhooks[$classname] = [];
|
||||
}
|
||||
|
||||
// Order rows by hook name, putting core first.
|
||||
\core_collator::ksort($allhooks);
|
||||
$corehooks = [];
|
||||
foreach ($allhooks as $classname => $consumers) {
|
||||
if (str_starts_with($classname, 'core\\')) {
|
||||
$corehooks[$classname] = $consumers;
|
||||
unset($allhooks[$classname]);
|
||||
}
|
||||
}
|
||||
$allhooks = array_merge($corehooks, $allhooks);
|
||||
|
||||
foreach ($allhooks as $classname => $consumers) {
|
||||
$this->add_data_keyed(
|
||||
$this->format_row((object) [
|
||||
|
@ -17,7 +17,7 @@
|
||||
namespace core\hook;
|
||||
|
||||
/**
|
||||
* Interface for hook callbacks that were deprecated by the hook.
|
||||
* Interface for describing of lib.php callbacks that were deprecated by the hook.
|
||||
*
|
||||
* @package core
|
||||
* @author Petr Skoda
|
||||
|
@ -26,7 +26,7 @@ namespace core\hook;
|
||||
*/
|
||||
interface described_hook {
|
||||
/**
|
||||
* Mandatory hook purpose description in Markdown format
|
||||
* Hook purpose description in Markdown format
|
||||
* used on Hooks overview page.
|
||||
*
|
||||
* It should include description of callback priority setting
|
||||
|
@ -17,7 +17,11 @@
|
||||
namespace core\hook;
|
||||
|
||||
/**
|
||||
* This interface describes a component which can discover hooks in its own namespace.
|
||||
* This interface describes a class which can discover all hook
|
||||
* classes of a plugin.
|
||||
*
|
||||
* To add new discovery agent in your plugin you need to add your_plugin\hooks
|
||||
* class that implements this interface.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Andrew Lyons <andrew@nicols.co.uk>
|
||||
@ -25,7 +29,7 @@ namespace core\hook;
|
||||
*/
|
||||
interface discovery_agent {
|
||||
/**
|
||||
* Discover hooks belonging to the component.
|
||||
* Returns a list of hooks for component.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
@ -94,8 +94,6 @@ final class manager implements
|
||||
*
|
||||
* NOTE: this is the "Listener Provider" described in PSR-14,
|
||||
* instead of instance parameter it uses real PHP class names.
|
||||
* Moodle hooks should be final and parents of hook class are not
|
||||
* considered when resolving callbacks.
|
||||
*
|
||||
* @param string $hookclassname PHP class name of hook
|
||||
* @return array list of callback definitions
|
||||
@ -420,10 +418,7 @@ final class manager implements
|
||||
if (!class_exists($hookclassname)) {
|
||||
continue;
|
||||
}
|
||||
// It's 2023 and PHP still doesn't provide a simple way to detect if a class implements an interface without
|
||||
// that class being instantiated.
|
||||
$rc = new \ReflectionClass($hookclassname);
|
||||
if (!$rc->implementsInterface(\core\hook\deprecated_callback_replacement::class)) {
|
||||
if (!is_subclass_of($hookclassname, \core\hook\deprecated_callback_replacement::class)) {
|
||||
continue;
|
||||
}
|
||||
$deprecations = $hookclassname::get_deprecated_plugin_callbacks();
|
||||
@ -558,18 +553,19 @@ final class manager implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of hooks discovered through standardised Moodle methods.
|
||||
* Returns list of hooks discovered through hook namespaces or discovery agents.
|
||||
*
|
||||
* Note that the exact discovery logic may change in the future,
|
||||
* for now this looks for hooks mentioned in callback registrations
|
||||
* and non-abstract classes in \component_name\hook namespaces that
|
||||
* implement described_hook interface.
|
||||
* The hooks overview page includes also all other classes that are
|
||||
* referenced in callback registrations in db/hooks.php files, those
|
||||
* are not included here.
|
||||
*
|
||||
* @return array hook class names
|
||||
*/
|
||||
public static function discover_known_hooks(): array {
|
||||
// All classes in hook namespace of core and plugins, unless plugin has a discovery agent.
|
||||
$hooks = \core\hooks::discover_hooks();
|
||||
|
||||
// Look for hooks classes in all plugins that implement discovery agent interface.
|
||||
foreach (\core_component::get_component_names() as $component) {
|
||||
$classname = "{$component}\\hooks";
|
||||
|
||||
@ -577,8 +573,7 @@ final class manager implements
|
||||
continue;
|
||||
}
|
||||
|
||||
$rc = new \ReflectionClass($classname);
|
||||
if (!$rc->implementsInterface(\core\hook\hook_discover_agent::class)) {
|
||||
if (!is_subclass_of($classname, discovery_agent::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -17,22 +17,45 @@
|
||||
namespace core;
|
||||
|
||||
/**
|
||||
* Hook discovery agent for core.
|
||||
* Standard hook discovery agent for Moodle which lists
|
||||
* all non-abstract classes in hooks namespace of core and all plugins
|
||||
* unless there is a hook discovery agent in a plugin.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class hooks implements \core\hook\discovery_agent {
|
||||
final class hooks implements \core\hook\discovery_agent {
|
||||
/**
|
||||
* Returns all Moodle hooks in standard hook namespace.
|
||||
*
|
||||
* @return array list of hook classes
|
||||
*/
|
||||
public static function discover_hooks(): array {
|
||||
// Describe any hard-coded hooks which can't be easily discovered by namespace.
|
||||
// Look for hooks in hook namespace in core and all components.
|
||||
$hooks = [];
|
||||
|
||||
$hooks = array_merge($hooks, self::discover_hooks_in_namespace('core', 'hook'));
|
||||
|
||||
foreach (\core_component::get_component_names() as $component) {
|
||||
$agent = "$component\\hooks";
|
||||
if (class_exists($agent) && is_subclass_of($agent, hook\discovery_agent::class)) {
|
||||
// Let the plugin supply the list of hooks instead.
|
||||
continue;
|
||||
}
|
||||
$hooks = array_merge($hooks, self::discover_hooks_in_namespace($component, 'hook'));
|
||||
}
|
||||
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up all non-abstract classes in "$component\$namespace" namespace.
|
||||
*
|
||||
* @param string $component
|
||||
* @param string $namespace
|
||||
* @return array list of hook classes
|
||||
*/
|
||||
public static function discover_hooks_in_namespace(string $component, string $namespace): array {
|
||||
$classes = \core_component::get_component_classes_in_namespace($component, $namespace);
|
||||
|
||||
@ -44,8 +67,8 @@ class hooks implements \core\hook\discovery_agent {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_a($classname, \core\hook\manager::class, true)) {
|
||||
// Skip the manager.
|
||||
if ($classname === \core\hook\manager::class) {
|
||||
// Skip the manager in core.
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -55,11 +78,9 @@ class hooks implements \core\hook\discovery_agent {
|
||||
'tags' => [],
|
||||
];
|
||||
|
||||
if ($rc->implementsInterface(\core\hook\described_hook::class)) {
|
||||
if (is_subclass_of($classname, \core\hook\described_hook::class)) {
|
||||
$hooks[$classname]['description'] = $classname::get_hook_description();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $hooks;
|
||||
|
Loading…
x
Reference in New Issue
Block a user