1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core_admin\table; 18 19 use core_plugin_manager; 20 use flexible_table; 21 use html_writer; 22 use stdClass; 23 24 defined('MOODLE_INTERNAL') || die(); 25 require_once("{$CFG->libdir}/tablelib.php"); 26 27 /** 28 * Plugin Management table. 29 * 30 * @package core_admin 31 * @copyright 2023 Andrew Lyons <andrew@nicols.co.uk> 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class hook_list_table extends flexible_table { 35 36 /** @var \core\plugininfo\base[] The plugin list */ 37 protected array $plugins = []; 38 39 /** @var int The number of enabled plugins of this type */ 40 protected int $enabledplugincount = 0; 41 42 /** @var core_plugin_manager */ 43 protected core_plugin_manager $pluginmanager; 44 45 /** @var string The plugininfo class for this plugintype */ 46 protected string $plugininfoclass; 47 48 /** @var stdClass[] The list of emitted hooks with metadata */ 49 protected array $emitters; 50 51 public function __construct() { 52 global $CFG; 53 54 $this->define_baseurl('/admin/hooks.php'); 55 parent::__construct('core_admin-hook_list_table'); 56 57 // Add emitted hooks. 58 $this->emitters = \core\hook\manager::discover_known_hooks(); 59 60 $this->setup_column_configuration(); 61 $this->setup(); 62 } 63 64 /** 65 * Set up the column configuration for this table. 66 */ 67 protected function setup_column_configuration(): void { 68 $columnlist = [ 69 'details' => get_string('hookname', 'core_admin'), 70 'callbacks' => get_string('hookcallbacks', 'core_admin'), 71 'deprecates' => get_string('hookdeprecates', 'core_admin'), 72 ]; 73 $this->define_columns(array_keys($columnlist)); 74 $this->define_headers(array_values($columnlist)); 75 76 $columnswithhelp = [ 77 'callbacks' => new \help_icon('hookcallbacks', 'admin'), 78 ]; 79 $columnhelp = array_map(function (string $column) use ($columnswithhelp): ?\renderable { 80 if (array_key_exists($column, $columnswithhelp)) { 81 return $columnswithhelp[$column]; 82 } 83 84 return null; 85 }, array_keys($columnlist)); 86 $this->define_help_for_headers($columnhelp); 87 } 88 89 /** 90 * Print the table. 91 */ 92 public function out(): void { 93 // All hook consumers referenced from the db/hooks.php files. 94 $hookmanager = \core\hook\manager::get_instance(); 95 $allhooks = (array)$hookmanager->get_all_callbacks(); 96 97 // Add any unused hooks. 98 foreach (array_keys($this->emitters) as $classname) { 99 if (isset($allhooks[$classname])) { 100 continue; 101 } 102 $allhooks[$classname] = []; 103 } 104 105 // Order rows by hook name, putting core first. 106 \core_collator::ksort($allhooks); 107 $corehooks = []; 108 foreach ($allhooks as $classname => $consumers) { 109 if (str_starts_with($classname, 'core\\')) { 110 $corehooks[$classname] = $consumers; 111 unset($allhooks[$classname]); 112 } 113 } 114 $allhooks = array_merge($corehooks, $allhooks); 115 116 foreach ($allhooks as $classname => $consumers) { 117 $this->add_data_keyed( 118 $this->format_row((object) [ 119 'classname' => $classname, 120 'callbacks' => $consumers, 121 ]), 122 $this->get_row_class($classname), 123 ); 124 } 125 126 $this->finish_output(false); 127 } 128 129 protected function col_details(stdClass $row): string { 130 return $row->classname . 131 $this->get_description($row) . 132 html_writer::div($this->get_tags_for_row($row)); 133 } 134 135 /** 136 * Show the name column content. 137 * 138 * @param stdClass $row 139 * @return string 140 */ 141 protected function get_description(stdClass $row): string { 142 if (!array_key_exists($row->classname, $this->emitters)) { 143 return ''; 144 } 145 146 return html_writer::tag( 147 'small', 148 clean_text(markdown_to_html($this->emitters[$row->classname]['description']), FORMAT_HTML), 149 ); 150 } 151 152 protected function col_deprecates(stdClass $row): string { 153 if (!class_exists($row->classname)) { 154 return ''; 155 } 156 157 $rc = new \ReflectionClass($row->classname); 158 if (!$rc->implementsInterface(\core\hook\deprecated_callback_replacement::class)) { 159 return ''; 160 } 161 $deprecates = call_user_func([$row->classname, 'get_deprecated_plugin_callbacks']); 162 if (count($deprecates) === 0) { 163 return ''; 164 } 165 $content = html_writer::start_tag('ul'); 166 167 foreach ($deprecates as $deprecatedmethod) { 168 $content .= html_writer::tag('li', $deprecatedmethod); 169 } 170 $content .= html_writer::end_tag('ul'); 171 return $content; 172 } 173 174 protected function col_callbacks(stdClass $row): string { 175 global $CFG; 176 177 $hookclass = $row->classname; 178 $cbinfo = []; 179 foreach ($row->callbacks as $definition) { 180 $iscallable = is_callable($definition['callback'], false, $callbackname); 181 $isoverridden = isset($CFG->hooks_callback_overrides[$hookclass][$definition['callback']]); 182 $info = "{$callbackname} ({$definition['priority']})"; 183 if (!$iscallable) { 184 $info .= ' '; 185 $info .= $this->get_tag( 186 get_string('error'), 187 'danger', 188 get_string('hookcallbacknotcallable', 'core_admin', $callbackname), 189 ); 190 } 191 if ($isoverridden) { 192 // The lang string meaning should be close enough here. 193 $info .= $this->get_tag( 194 get_string('hookconfigoverride', 'core_admin'), 195 'warning', 196 get_string('hookconfigoverride_help', 'core_admin'), 197 ); 198 } 199 200 $cbinfo[] = $info; 201 } 202 203 if ($cbinfo) { 204 $output = html_writer::start_tag('ol'); 205 foreach ($cbinfo as $callback) { 206 $class = ''; 207 if ($definition['disabled']) { 208 $class = 'dimmed_text'; 209 } 210 $output .= html_writer::tag('li', $callback, ['class' => $class]); 211 } 212 $output .= html_writer::end_tag('ol'); 213 return $output; 214 } else { 215 return ''; 216 } 217 } 218 219 /** 220 * Get the HTML to display the badge with tooltip. 221 * 222 * @param string $tag The main text to display 223 * @param null|string $type The pill type 224 * @param null|string $tooltip The content of the tooltip 225 * @return string 226 */ 227 protected function get_tag( 228 string $tag, 229 ?string $type = null, 230 ?string $tooltip = null, 231 ): string { 232 $attributes = []; 233 234 if ($type === null) { 235 $type = 'info'; 236 } 237 238 if ($tooltip) { 239 $attributes['data-toggle'] = 'tooltip'; 240 $attributes['title'] = $tooltip; 241 } 242 return html_writer::span($tag, "badge badge-{$type}", $attributes); 243 } 244 245 /** 246 * Get the code to display a set of tags for this table row. 247 * 248 * @param stdClass $row 249 * @return string 250 */ 251 protected function get_tags_for_row(stdClass $row): string { 252 if (!array_key_exists($row->classname, $this->emitters)) { 253 // This hook has been defined in the db/hooks.php file 254 // but does not refer to a hook in this version of Moodle. 255 return $this->get_tag( 256 get_string('hookunknown', 'core_admin'), 257 'warning', 258 get_string('hookunknown_desc', 'core_admin'), 259 ); 260 } 261 262 if (!class_exists($row->classname)) { 263 // This hook has been defined in a hook discovery agent, but the class it refers to could not be found. 264 return $this->get_tag( 265 get_string('hookclassmissing', 'core_admin'), 266 'warning', 267 get_string('hookclassmissing_desc', 'core_admin'), 268 ); 269 } 270 271 $tags = $this->emitters[$row->classname]['tags'] ?? []; 272 $taglist = array_map(function($tag): string { 273 if (is_array($tag)) { 274 return $this->get_tag(...$tag); 275 } 276 return $this->get_tag($tag, 'badge badge-info'); 277 }, $tags); 278 279 return implode("\n", $taglist); 280 } 281 282 protected function get_row_class(string $classname): string { 283 return ''; 284 } 285 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body