From dfeedc5dabfcca995a059a1105ca3cd2acf8c243 Mon Sep 17 00:00:00 2001 From: Laurent David Date: Wed, 15 Mar 2023 21:36:45 +0100 Subject: [PATCH 1/2] MDL-76710 mod_bigbluebuttonbn: Subplugin implementation * Setup the base for extension type of plugins * Add basic unit test for extension plugin (callback and classes) --- mod/bigbluebuttonbn/adminmanageplugins.php | 43 +++ mod/bigbluebuttonbn/classes/extension.php | 103 ++++++ .../local/extension/mod_instance_helper.php | 52 +++ .../plugins/admin_page_manage_extensions.php | 81 +++++ .../local/plugins/admin_plugin_manager.php | 327 ++++++++++++++++++ .../classes/plugininfo/bbbext.php | 149 ++++++++ mod/bigbluebuttonbn/classes/settings.php | 17 +- .../test/subplugins_test_helper_trait.php | 79 +++++ mod/bigbluebuttonbn/db/subplugins.json | 5 + mod/bigbluebuttonbn/extension/.gitignore | 1 + mod/bigbluebuttonbn/extension/README.md | 9 + .../lang/en/bigbluebuttonbn.php | 4 + mod/bigbluebuttonbn/lib.php | 9 + mod/bigbluebuttonbn/settings.php | 2 +- .../bigbluebuttonbn/mod_instance_helper.php | 67 ++++ .../fixtures/extension/simple/db/install.xml | 19 + .../simple/lang/en/bbbext_simple.php | 27 ++ .../fixtures/extension/simple/settings.php | 34 ++ .../fixtures/extension/simple/version.php | 29 ++ .../tests/local/extension_test.php | 170 +++++++++ mod/bigbluebuttonbn/version.php | 2 +- 21 files changed, 1226 insertions(+), 3 deletions(-) create mode 100644 mod/bigbluebuttonbn/adminmanageplugins.php create mode 100644 mod/bigbluebuttonbn/classes/extension.php create mode 100644 mod/bigbluebuttonbn/classes/local/extension/mod_instance_helper.php create mode 100644 mod/bigbluebuttonbn/classes/local/plugins/admin_page_manage_extensions.php create mode 100644 mod/bigbluebuttonbn/classes/local/plugins/admin_plugin_manager.php create mode 100644 mod/bigbluebuttonbn/classes/plugininfo/bbbext.php create mode 100644 mod/bigbluebuttonbn/classes/test/subplugins_test_helper_trait.php create mode 100644 mod/bigbluebuttonbn/db/subplugins.json create mode 100644 mod/bigbluebuttonbn/extension/.gitignore create mode 100644 mod/bigbluebuttonbn/extension/README.md create mode 100644 mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/mod_instance_helper.php create mode 100644 mod/bigbluebuttonbn/tests/fixtures/extension/simple/db/install.xml create mode 100644 mod/bigbluebuttonbn/tests/fixtures/extension/simple/lang/en/bbbext_simple.php create mode 100644 mod/bigbluebuttonbn/tests/fixtures/extension/simple/settings.php create mode 100644 mod/bigbluebuttonbn/tests/fixtures/extension/simple/version.php create mode 100644 mod/bigbluebuttonbn/tests/local/extension_test.php diff --git a/mod/bigbluebuttonbn/adminmanageplugins.php b/mod/bigbluebuttonbn/adminmanageplugins.php new file mode 100644 index 00000000000..e92c19135f0 --- /dev/null +++ b/mod/bigbluebuttonbn/adminmanageplugins.php @@ -0,0 +1,43 @@ +. + +/** + * Allows the admin to manage extension plugins + * + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ + +use mod_bigbluebuttonbn\local\plugins\admin_plugin_manager; + +require_once(__DIR__ . '/../../config.php'); +global $PAGE; +require_login(); +$action = optional_param('action', null, PARAM_PLUGIN); +$plugin = optional_param('plugin', null, PARAM_PLUGIN); + +if (!empty($plugin)) { + require_sesskey(); +} + +// Create the class for this controller. +$pluginmanager = new admin_plugin_manager(); + +$PAGE->set_context(context_system::instance()); + +// Execute the controller. +$pluginmanager->execute($action, $plugin); diff --git a/mod/bigbluebuttonbn/classes/extension.php b/mod/bigbluebuttonbn/classes/extension.php new file mode 100644 index 00000000000..5f5fa72f8dc --- /dev/null +++ b/mod/bigbluebuttonbn/classes/extension.php @@ -0,0 +1,103 @@ +. +namespace mod_bigbluebuttonbn; + +use mod_bigbluebuttonbn\local\extension\mod_instance_helper; +use stdClass; +use core_plugin_manager; + +/** + * Generic subplugin management helper + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class extension { + /** + * Plugin name for extension + */ + const BBB_EXTENSION_PLUGIN_NAME = 'bbbext'; + + /** + * Add instance processing + * + * @param stdClass $data data to persist + * @return void + */ + public static function add_instance(stdClass $data): void { + $formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class); + foreach ($formmanagersclasses as $fmclass) { + $fmclass->add_instance($data); + } + } + + /** + * Update instance processing + * + * @param stdClass $data data to persist + * @return void + */ + public static function update_instance(stdClass $data): void { + $formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class); + foreach ($formmanagersclasses as $fmclass) { + $fmclass->update_instance($data); + } + } + + /** + * Delete instance processing + * + * @param int $id instance id + * @return void + */ + public static function delete_instance(int $id): void { + $formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class); + foreach ($formmanagersclasses as $fmclass) { + $fmclass->delete_instance($id); + } + } + + /** + * Get new instance of classes that are named on the base of this classname and implementing this class + * + * @param string $classname + * @return array + */ + protected static function get_instances_implementing(string $classname): array { + // Get the class basename without Reflection API. + $classnamecomponents = explode("\\", $classname); + $classbasename = end($classnamecomponents); + $allsubs = core_plugin_manager::instance()->get_plugins_of_type(self::BBB_EXTENSION_PLUGIN_NAME); + $extensionclasses = []; + foreach ($allsubs as $sub) { + if (!$sub->is_enabled()) { + continue; + } + $targetclassname = "\\bbbext_{$sub->name}\\bigbluebuttonbn\\$classbasename"; + if (!class_exists($targetclassname)) { + continue; + } + if (!is_subclass_of($targetclassname, $classname)) { + debugging("The class $targetclassname should extend $classname in the subplugin {$sub->name}. Ignoring."); + continue; + } + $extensionclasses[] = new $targetclassname(); + } + return $extensionclasses; + } +} diff --git a/mod/bigbluebuttonbn/classes/local/extension/mod_instance_helper.php b/mod/bigbluebuttonbn/classes/local/extension/mod_instance_helper.php new file mode 100644 index 00000000000..fcc14b28eae --- /dev/null +++ b/mod/bigbluebuttonbn/classes/local/extension/mod_instance_helper.php @@ -0,0 +1,52 @@ +. +namespace mod_bigbluebuttonbn\local\extension; +use stdClass; + +/** + * Class defining a way to deal with instance save/update/delete in extension + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class mod_instance_helper { + /** + * Runs any processes that must run before a bigbluebuttonbn insert/update. + * + * @param stdClass $bigbluebuttonbn BigBlueButtonBN form data + */ + public function add_instance(stdClass $bigbluebuttonbn) { + // Nothing for now. + } + /** + * Runs any processes that must be run after a bigbluebuttonbn insert/update. + * + * @param stdClass $bigbluebuttonbn BigBlueButtonBN form data + */ + public function update_instance(stdClass $bigbluebuttonbn): void { + // Nothing for now. + } + + /** + * Runs any processes that must be run after a bigbluebuttonbn delete. + * + * @param int $cmid + */ + public function delete_instance(int $cmid): void { + } +} diff --git a/mod/bigbluebuttonbn/classes/local/plugins/admin_page_manage_extensions.php b/mod/bigbluebuttonbn/classes/local/plugins/admin_page_manage_extensions.php new file mode 100644 index 00000000000..a305ea8dfeb --- /dev/null +++ b/mod/bigbluebuttonbn/classes/local/plugins/admin_page_manage_extensions.php @@ -0,0 +1,81 @@ +. + +namespace mod_bigbluebuttonbn\local\plugins; + +defined('MOODLE_INTERNAL') || die(); +global $CFG; +require_once($CFG->libdir . '/adminlib.php'); + +use admin_externalpage; +use core_component; +use core_text; +use mod_bigbluebuttonbn\extension; +use moodle_url; + +/** + * Admin external page that displays a list of the installed extension plugins. + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class admin_page_manage_extensions extends admin_externalpage { + /** + * Global URL for page. + */ + const ADMIN_PAGE_URL = '/mod/bigbluebuttonbn/adminmanageplugins.php'; + + /** + * The constructor - calls parent constructor + * + */ + public function __construct() { + $url = new moodle_url(self::ADMIN_PAGE_URL); + $managepagename = 'manage' . extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins'; + parent::__construct( + $managepagename, + get_string($managepagename, 'mod_bigbluebuttonbn'), + $url + ); + } + + /** + * Search plugins for the specified string + * + * @param string $query The string to search for + * @return array + */ + public function search($query): array { + if ($result = parent::search($query)) { + return $result; + } + foreach (core_component::get_plugin_list(extension::BBB_EXTENSION_PLUGIN_NAME ) as $name => $notused) { + $pluginname = core_text::strtolower( + get_string('pluginname', extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $name) + ); + if (str_contains($pluginname, $query) !== false) { + $result = (object)[ + 'page' => $this, + 'settings' => [], + ]; + return [$this->name => $result]; + } + } + return []; + } +} diff --git a/mod/bigbluebuttonbn/classes/local/plugins/admin_plugin_manager.php b/mod/bigbluebuttonbn/classes/local/plugins/admin_plugin_manager.php new file mode 100644 index 00000000000..ad752446e68 --- /dev/null +++ b/mod/bigbluebuttonbn/classes/local/plugins/admin_plugin_manager.php @@ -0,0 +1,327 @@ +. +namespace mod_bigbluebuttonbn\local\plugins; + +use cache_helper; +use context_system; +use core_component; +use core_plugin_manager; +use flexible_table; +use html_writer; +use mod_bigbluebuttonbn\extension; +use moodle_url; +use pix_icon; + +/** + * Class that handles the display and configuration of the list of extension plugins. + * + * This is directly taken from the mod_assign code. We might need to have a global API there for this. + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class admin_plugin_manager { + /** @var object the url of the manage submission plugin page */ + private $pageurl; + + /** + * Constructor for this assignment plugin manager + * + */ + public function __construct() { + $this->pageurl = new moodle_url(admin_page_manage_extensions::ADMIN_PAGE_URL); + } + + /** + * This is the entry point for this controller class. + * + * @param string|null $action - The action to perform + * @param string|null $plugin - Optional name of a plugin type to perform the action on + * @return void + */ + public function execute(?string $action = null, ?string $plugin = null): void { + if (empty($action) || empty($plugin)) { + $action = 'view'; + } + $this->check_permissions(); + + $actionname = "plugins_$action"; + if (method_exists($this, $actionname)) { + $nextaction = $this->$actionname($plugin); + if ($nextaction) { + $this->execute($nextaction, $plugin); + } + } + } + + /** + * Check this user has permission to edit the list of installed plugins + * + * @return void + */ + private function check_permissions(): void { + require_login(); + $systemcontext = context_system::instance(); + require_capability('moodle/site:config', $systemcontext); + } + + /** + * Write the HTML for the submission plugins table. + * + * @return void + */ + private function plugins_view(): void { + global $OUTPUT, $CFG; + require_once($CFG->libdir . '/tablelib.php'); + $this->print_header(); + $table = new flexible_table(extension::BBB_EXTENSION_PLUGIN_NAME . 'pluginsadminttable'); + $table->define_baseurl($this->pageurl); + $table->define_columns([ + 'pluginname', + 'version', + 'hideshow', + 'order', + 'settings', + 'uninstall' + ]); + $table->define_headers([ + get_string('subplugintype_bbbext', 'mod_bigbluebuttonbn'), + get_string('version'), get_string('hide') . '/' . get_string('show'), + get_string('order'), + get_string('settings'), + get_string('uninstallplugin', 'core_admin') + ]); + $table->set_attribute('id', extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins'); + $table->set_attribute('class', 'admintable generaltable'); + $table->setup(); + + $plugins = $this->get_sorted_plugins_list(); + $instances = core_plugin_manager::instance()->get_plugins_of_type(extension::BBB_EXTENSION_PLUGIN_NAME); + + foreach ($plugins as $idx => $plugin) { + $componentname = extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin; + $typebasedir = ""; + if (in_array($plugin, array_keys($instances))) { + $typebasedir = ($instances[$plugin])->typerootdir; + } + $row = []; + $class = ''; + $pluginversion = get_config($componentname, 'version'); + $row[] = get_string('pluginname', $componentname); + $row[] = $pluginversion; + $visible = !get_config($componentname, 'disabled'); + + if ($visible) { + $row[] = $this->format_icon_link('hide', $plugin, 't/hide', get_string('disable')); + } else { + $row[] = $this->format_icon_link('show', $plugin, 't/show', get_string('enable')); + $class = 'dimmed_text'; + } + + $movelinks = ''; + if (!$idx == 0) { + $movelinks .= $this->format_icon_link('moveup', $plugin, 't/up', get_string('up')) . ' '; + } else { + $movelinks .= $OUTPUT->spacer(['width' => 16]); + } + if ($idx != count($plugins) - 1) { + $movelinks .= $this->format_icon_link('movedown', $plugin, 't/down', get_string('down')) . ' '; + } + $row[] = $movelinks; + + $exists = file_exists($typebasedir . '/' . $plugin . '/settings.php'); + // We do not display settings for plugin who have not yet been installed (so have no version yet). + if (!empty($pluginversion) && $exists) { + $row[] = html_writer::link( + new moodle_url('/admin/settings.php', ['section' => $componentname]), + get_string('settings') + ); + } else { + $row[] = ' '; + } + $url = core_plugin_manager::instance()->get_uninstall_url( + $componentname, + 'manage' + ); + if ($url) { + $row[] = html_writer::link($url, get_string('uninstallplugin', 'core_admin')); + } else { + $row[] = ' '; + } + + $table->add_data($row, $class); + } + + $table->finish_output(); + $this->print_footer(); + } + + /** + * Write the page header + * + * @return void + */ + private function print_header(): void { + global $OUTPUT; + $pageidentifier = 'manage' . extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins'; + admin_externalpage_setup($pageidentifier); + echo $OUTPUT->header(); + echo $OUTPUT->heading(get_string($pageidentifier, 'mod_bigbluebuttonbn')); + } + + /** + * Return a list of plugins sorted by the order defined in the admin interface + * + * @return array The list of plugins + */ + public function get_sorted_plugins_list(): array { + $names = core_component::get_plugin_list(extension::BBB_EXTENSION_PLUGIN_NAME); + + $result = []; + + foreach ($names as $name => $path) { + $idx = get_config(extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $name, 'sortorder'); + if (!$idx) { + $idx = 0; + } + while (array_key_exists($idx, $result)) { + $idx += 1; + } + $result[$idx] = $name; + } + ksort($result); + + return $result; + } + + /** + * Util function for writing an action icon link + * + * @param string $action URL parameter to include in the link + * @param string $plugin URL parameter to include in the link + * @param string $icon The key to the icon to use (e.g. 't/up') + * @param string $alt The string description of the link used as the title and alt text + * @return string The icon/link + */ + private function format_icon_link(string $action, string $plugin, string $icon, string $alt): string { + global $OUTPUT; + return $OUTPUT->action_icon( + new moodle_url( + $this->pageurl, + ['action' => $action, 'plugin' => $plugin, 'sesskey' => sesskey()] + ), + new pix_icon($icon, $alt, 'moodle', ['title' => $alt]), + null, + ['title' => $alt] + ); + } + + /** + * Write the page footer + * + * @return void + */ + private function print_footer(): void { + global $OUTPUT; + echo $OUTPUT->footer(); + } + + /** + * Hide this plugin. + * + * @param string $plugin - The plugin to hide + * @return string The next page to display + */ + private function plugins_hide(string $plugin): string { + $class = \core_plugin_manager::resolve_plugininfo_class(extension::BBB_EXTENSION_PLUGIN_NAME); + $class::enable_plugin($plugin, false); + cache_helper::purge_by_event('mod_bigbluebuttonbn/pluginenabledisabled'); + return 'view'; + } + + /** + * Show this plugin. + * + * @param string $plugin - The plugin to show + * @return string The next page to display + */ + private function plugins_show(string $plugin): string { + $class = \core_plugin_manager::resolve_plugininfo_class(extension::BBB_EXTENSION_PLUGIN_NAME); + $class::enable_plugin($plugin, true); + cache_helper::purge_by_event('mod_bigbluebuttonbn/pluginenabledisabled'); + return 'view'; + } + + /** + * Move this plugin up + * + * We need this function so we can call directly (without the dir parameter) + * @param string $plugintomove - The plugin to move + * @return string The next page to display + */ + private function plugins_moveup(string $plugintomove): string { + return $this->move_plugin($plugintomove, 'up'); + } + + /** + * Move this plugin down + * + * We need this function so we can call directly (without the dir parameter) + * @param string $plugintomove - The plugin to move + * @return string The next page to display + */ + private function plugins_movedown(string $plugintomove): string { + return $this->move_plugin($plugintomove, 'down'); + } + + /** + * Change the order of this plugin. + * + * @param string $plugintomove - The plugin to move + * @param string $dir - up or down + * @return string The next page to display + */ + private function move_plugin(string $plugintomove, string $dir): string { + $plugins = $this->get_sorted_plugins_list(); + $plugins = array_values($plugins); + $currentindex = array_search($plugintomove, $plugins); + if ($currentindex === false) { + return 'view'; + } + // Make the switch. + if ($dir === 'up') { + if ($currentindex > 0) { + $tempplugin = $plugins[$currentindex - 1]; + $plugins[$currentindex - 1] = $plugins[$currentindex]; + $plugins[$currentindex] = $tempplugin; + } + } else if ($dir === 'down') { + if ($currentindex < (count($plugins) - 1)) { + $tempplugin = $plugins[$currentindex + 1]; + $plugins[$currentindex + 1] = $plugins[$currentindex]; + $plugins[$currentindex] = $tempplugin; + } + } + + // Save the new normal order. + foreach ($plugins as $key => $plugin) { + set_config('sortorder', $key, extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin); + } + return 'view'; + } +} diff --git a/mod/bigbluebuttonbn/classes/plugininfo/bbbext.php b/mod/bigbluebuttonbn/classes/plugininfo/bbbext.php new file mode 100644 index 00000000000..4bfd30642f2 --- /dev/null +++ b/mod/bigbluebuttonbn/classes/plugininfo/bbbext.php @@ -0,0 +1,149 @@ +. +namespace mod_bigbluebuttonbn\plugininfo; + +use core\plugininfo\base; +use mod_bigbluebuttonbn\extension; + +/** + * Subplugin extension info class. + * + * @package mod_bigbluebuttonbn + * @copyright 2022 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class bbbext extends base { + /** + * Check if BigBlueButton plugin is enabled + * @return bool + */ + private static function is_bbb_enabled(): bool { + $enabledplugins = \core\plugininfo\mod::get_enabled_plugins(); + return isset($enabledplugins['bigbluebuttonbn']); + } + + /** + * Get a list of enabled plugins + * + * @return array + * @throws \dml_exception + */ + public static function get_enabled_plugins(): array { + // If the mod_bigbluebuttonbn is not enabled, then all subplugin are disabled. + if (!self::is_bbb_enabled()) { + return []; + } + // Get all available plugins. + $plugins = \core_plugin_manager::instance()->get_installed_plugins(extension::BBB_EXTENSION_PLUGIN_NAME); + if (!$plugins) { + return []; + } + + // Check they are enabled using get_config (which is cached and hopefully fast). + $enabled = []; + foreach ($plugins as $plugin => $version) { + $disabled = get_config(extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin, 'disabled'); + if (empty($disabled)) { + $enabled[$plugin] = $plugin; + } + } + + return $enabled; + } + + /** + * Enable the plugin + * + * @param string $pluginname + * @param int $enabled + * @return bool + * @throws \dml_exception + */ + public static function enable_plugin(string $pluginname, int $enabled): bool { + $haschanged = false; + // If the mod_bigbluebuttonbn is not enabled, then all subplugin are disabled. + if (!self::is_bbb_enabled()) { + return false; + } + $plugin = extension::BBB_EXTENSION_PLUGIN_NAME. '_' . $pluginname; + $oldvalue = get_config($plugin, 'disabled'); + $disabled = !$enabled; + // Only set value if there is no config setting or if the value is different from the previous one. + if ($oldvalue == false && $disabled) { + set_config('disabled', $disabled, $plugin); + $haschanged = true; + } else if ($oldvalue != false && !$disabled) { + unset_config('disabled', $plugin); + $haschanged = true; + } + + if ($haschanged) { + add_to_config_log('disabled', $oldvalue, $disabled, $plugin); + \core_plugin_manager::reset_caches(); + } + + return $haschanged; + } + + /** + * Loads plugin settings to the settings tree + * + * This function usually includes settings.php file in plugins folder. + * Alternatively it can create a link to some settings page (instance of admin_externalpage) + * + * @param \part_of_admin_tree $adminroot + * @param string $parentnodename + * @param bool $hassiteconfig whether the current user has moodle/site:config capability + */ + public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + // If the mod_bigbluebuttonbn is not enabled, then all subplugin are disabled. + if (!self::is_bbb_enabled()) { + return; + } + $ADMIN = $adminroot; + $plugininfo = $this; + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config', + $this->is_enabled() === false); + + if ($adminroot->fulltree) { + $shortsubtype = substr($this->type, strlen(extension::BBB_EXTENSION_PLUGIN_NAME)); + include($this->full_path('settings.php')); + } + + $adminroot->add($this->type . 'plugins', $settings); + } + + /** + * Get settings section name + * + * @return string + */ + public function get_settings_section_name() { + return $this->type . '_' . $this->name; + } +} diff --git a/mod/bigbluebuttonbn/classes/settings.php b/mod/bigbluebuttonbn/classes/settings.php index 95cae03cff5..79f9a86ed0d 100644 --- a/mod/bigbluebuttonbn/classes/settings.php +++ b/mod/bigbluebuttonbn/classes/settings.php @@ -28,9 +28,11 @@ use admin_setting_configtextarea; use admin_setting_heading; use admin_settingpage; use cache_helper; +use core_plugin_manager; use lang_string; use mod_bigbluebuttonbn\local\config; use mod_bigbluebuttonbn\local\helpers\roles; +use mod_bigbluebuttonbn\local\plugins\admin_page_manage_extensions; use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy; /** @@ -46,6 +48,9 @@ class settings { /** @var admin_category shared value */ private $admin; + /** @var bool whether the current user has moodle/site:config capability */ + private $hassiteconfig; + /** @var bool Module is enabled */ private $moduleenabled; @@ -64,11 +69,13 @@ class settings { * @param admin_category $admin * @param \core\plugininfo\mod $module * @param string $categoryname for the plugin setting (main setting page) + * @param bool $hassiteconfig whether the current user has moodle/site:config capability */ - public function __construct(admin_category $admin, \core\plugininfo\mod $module, string $categoryname) { + public function __construct(admin_category $admin, \core\plugininfo\mod $module, string $categoryname, bool $hassiteconfig) { $this->moduleenabled = $module->is_enabled() === true; $this->admin = $admin; $this->section = $categoryname; + $this->hassiteconfig = $hassiteconfig; $modbigbluebuttobnfolder = new admin_category( $this->parent, @@ -108,6 +115,14 @@ class settings { $this->add_extended_settings(); // Renders settings for experimental features. $this->add_experimental_settings(); + + // Add all subplugin settings if any. + $this->admin->add($this->parent, new admin_category('bbbextplugins', + new lang_string('subplugintype_bbbext', 'mod_bigbluebuttonbn'), !$this->moduleenabled)); + $this->admin->add($this->parent, new admin_page_manage_extensions()); + foreach (core_plugin_manager::instance()->get_plugins_of_type(extension::BBB_EXTENSION_PLUGIN_NAME) as $plugin) { + $plugin->load_settings($this->admin, extension::BBB_EXTENSION_PLUGIN_NAME, $this->hassiteconfig); + } } /** diff --git a/mod/bigbluebuttonbn/classes/test/subplugins_test_helper_trait.php b/mod/bigbluebuttonbn/classes/test/subplugins_test_helper_trait.php new file mode 100644 index 00000000000..1499487afbc --- /dev/null +++ b/mod/bigbluebuttonbn/classes/test/subplugins_test_helper_trait.php @@ -0,0 +1,79 @@ +. + +/** + * Subplugin test helper trait + * + * @package mod_bigbluebuttonbn + * @copyright 2023 - present, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ + +namespace mod_bigbluebuttonbn\test; + +use core_component; +use mod_bigbluebuttonbn\extension; +use ReflectionClass; + +trait subplugins_test_helper_trait { + /** + * Setup a fake extension plugin + * + * This is intended to behave in most case like a real subplugina and will + * allow most functionalities to be tested. + * + * @param string $pluginname plugin name + * @return void + */ + protected function setup_fake_plugin(string $pluginname): void { + global $CFG; + require_once("$CFG->libdir/upgradelib.php"); + $bbbextpath = "{$CFG->dirroot}/mod/bigbluebuttonbn/tests/fixtures/extension"; + // This is similar to accesslib_test::setup_fake_plugin. + $mockedcomponent = new ReflectionClass(core_component::class); + + $mockedplugins = $mockedcomponent->getProperty('plugins'); + $mockedplugins->setAccessible(true); + $plugins = $mockedplugins->getValue(); + $plugins[extension::BBB_EXTENSION_PLUGIN_NAME] = [$pluginname => $bbbextpath . "/$pluginname"]; + $mockedplugins->setValue($plugins); + + $mockedplugintypes = $mockedcomponent->getProperty('plugintypes'); + $mockedplugintypes->setAccessible(true); + $pluginstypes = $mockedplugintypes->getValue(); + $pluginstypes[extension::BBB_EXTENSION_PLUGIN_NAME] = $bbbextpath; + $mockedplugintypes->setValue($pluginstypes); + + $fillclassmap = $mockedcomponent->getMethod('fill_classmap_cache'); + $fillclassmap->setAccessible(true); + $fillclassmap->invoke(null); + + $fillfilemap = $mockedcomponent->getMethod('fill_filemap_cache'); + $fillfilemap->setAccessible(true); + $fillfilemap->invoke(null); + + // Make sure the plugin is installed. + ob_start(); + upgrade_noncore(false); + upgrade_finished(); + ob_end_clean(); + \core_plugin_manager::reset_caches(); + $this->resetDebugging(); // We might have debugging messages here that we need to get rid of. + // End of the component loader mock. + } + +} diff --git a/mod/bigbluebuttonbn/db/subplugins.json b/mod/bigbluebuttonbn/db/subplugins.json new file mode 100644 index 00000000000..5eab4ee78d3 --- /dev/null +++ b/mod/bigbluebuttonbn/db/subplugins.json @@ -0,0 +1,5 @@ +{ + "plugintypes": { + "bbbext": "mod\/bigbluebuttonbn\/extension" + } +} \ No newline at end of file diff --git a/mod/bigbluebuttonbn/extension/.gitignore b/mod/bigbluebuttonbn/extension/.gitignore new file mode 100644 index 00000000000..0a00d70141f --- /dev/null +++ b/mod/bigbluebuttonbn/extension/.gitignore @@ -0,0 +1 @@ +*/ \ No newline at end of file diff --git a/mod/bigbluebuttonbn/extension/README.md b/mod/bigbluebuttonbn/extension/README.md new file mode 100644 index 00000000000..bbbbbd3eb82 --- /dev/null +++ b/mod/bigbluebuttonbn/extension/README.md @@ -0,0 +1,9 @@ +## BigBlueButtonBN extension subplugins + +The plugins are made to extend existing BigBlueButtonBN behaviour. For now, we have one extension point using as base classes that should be implemented. +* mod_instance_helper : inherit this class so all methods will be called when we either add/delete/or update a module instance. +The extension classes should be placed in your plugin with exactly the same name but in a different namespace for example +* **\\bbbext_\\bigbluebuttonbn\\mod_instance_helper** to extend hooks from the mod_instance_helper class. + + +Some examples are provided in the tests/fixtures/simple folder. diff --git a/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php b/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php index 689ffd080b6..90fcef3b667 100644 --- a/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php +++ b/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php @@ -364,6 +364,7 @@ $string['index_heading_users'] = 'Users'; $string['index_heading_viewer'] = 'Viewers'; $string['index_heading'] = 'BigBlueButton rooms'; $string['instanceprofilewithoutrecordings'] = 'This instance profile cannot display recordings'; +$string['managebbbextplugins'] = 'Manage BigBlueButton extension plugins'; $string['mod_form_block_general'] = 'General'; $string['mod_form_block_guestaccess'] = 'Guest access'; $string['mod_form_block_room'] = 'Room settings'; @@ -660,3 +661,6 @@ $string['userlimitreached'] = 'The number of users allowed in a session has been $string['waitformoderator'] = 'Waiting for a moderator to join.'; $string['recordingurlnotfound'] = 'The recording URL is invalid.'; + +$string['subplugintype_bbbext'] = 'BigBlueButton activity extension'; +$string['subplugintype_bbbext_plural'] = 'BigBlueButton activity extensions'; diff --git a/mod/bigbluebuttonbn/lib.php b/mod/bigbluebuttonbn/lib.php index c51159e8c4a..936b0ebd4be 100644 --- a/mod/bigbluebuttonbn/lib.php +++ b/mod/bigbluebuttonbn/lib.php @@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die; use core_calendar\action_factory; use core_calendar\local\event\entities\action_interface; use mod_bigbluebuttonbn\completion\custom_completion; +use mod_bigbluebuttonbn\extension; use mod_bigbluebuttonbn\instance; use mod_bigbluebuttonbn\local\bigbluebutton; use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception; @@ -107,6 +108,9 @@ function bigbluebuttonbn_add_instance($bigbluebuttonbn) { logger::log_instance_created($bigbluebuttonbn); // Complete the process. mod_helper::process_post_save($bigbluebuttonbn); + + // Call any active subplugin so to signal a new creation. + extension::add_instance($bigbluebuttonbn); return $bigbluebuttonbn->id; } @@ -142,6 +146,8 @@ function bigbluebuttonbn_update_instance($bigbluebuttonbn) { // Complete the process. mod_helper::process_post_save($bigbluebuttonbn); + // Call any active subplugin so to signal update. + extension::update_instance($bigbluebuttonbn); return true; } @@ -194,6 +200,9 @@ function bigbluebuttonbn_delete_instance($id) { $result = true; + // Call any active subplugin so to signal deletion. + extension::delete_instance($id); + // Delete the instance. if (!$DB->delete_records('bigbluebuttonbn', ['id' => $id])) { $result = false; diff --git a/mod/bigbluebuttonbn/settings.php b/mod/bigbluebuttonbn/settings.php index 889361b1ea9..847d960c9fc 100644 --- a/mod/bigbluebuttonbn/settings.php +++ b/mod/bigbluebuttonbn/settings.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die; -$bbbsettings = new mod_bigbluebuttonbn\settings($ADMIN, $module, $section); +$bbbsettings = new mod_bigbluebuttonbn\settings($ADMIN, $module, $section, $hassiteconfig); $bbbsettings->add_all_settings(); $settings = null; diff --git a/mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/mod_instance_helper.php b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/mod_instance_helper.php new file mode 100644 index 00000000000..ee893bcc6b2 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/mod_instance_helper.php @@ -0,0 +1,67 @@ +. +namespace bbbext_simple\bigbluebuttonbn; + +use stdClass; + +/** + * Class defining a way to deal with instance save/update/delete in extension + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class mod_instance_helper extends \mod_bigbluebuttonbn\local\extension\mod_instance_helper { + /** + * Runs any processes that must run before a bigbluebuttonbn insert/update. + * + * @param stdClass $bigbluebuttonbn BigBlueButtonBN form data + **/ + public function add_instance(stdClass $bigbluebuttonbn) { + global $DB; + $DB->insert_record('bbbext_simple', (object) [ + 'bigbluebuttonbnid' => $bigbluebuttonbn->id, + 'newfield' => 2 + ]); + } + + /** + * Runs any processes that must be run after a bigbluebuttonbn insert/update. + * + * @param stdClass $bigbluebuttonbn BigBlueButtonBN form data + **/ + public function update_instance(stdClass $bigbluebuttonbn): void { + global $DB; + $record = $DB->get_record('bbbext_simple', [ + 'bigbluebuttonbnid' => $bigbluebuttonbn->id, + ]); + $record->newfield = $bigbluebuttonbn->newfield; + $DB->update_record('bbbext_simple', $record); + } + + /** + * Runs any processes that must be run after a bigbluebuttonbn delete. + * + * @param int $id + */ + public function delete_instance(int $id): void { + global $DB; + $DB->delete_records('bbbext_simple', [ + 'bigbluebuttonbnid' => $id, + ]); + } +} diff --git a/mod/bigbluebuttonbn/tests/fixtures/extension/simple/db/install.xml b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/db/install.xml new file mode 100644 index 00000000000..27be9232c30 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/db/install.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/mod/bigbluebuttonbn/tests/fixtures/extension/simple/lang/en/bbbext_simple.php b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/lang/en/bbbext_simple.php new file mode 100644 index 00000000000..76d377cc8fe --- /dev/null +++ b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/lang/en/bbbext_simple.php @@ -0,0 +1,27 @@ +. +/** + * Language File. + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ + +defined('MOODLE_INTERNAL') || die(); +$string['config_extension'] = 'Sample config extension setting'; +$string['pluginname'] = 'Simple BigBlueButtonPlugin'; diff --git a/mod/bigbluebuttonbn/tests/fixtures/extension/simple/settings.php b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/settings.php new file mode 100644 index 00000000000..5dd868b7fa9 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/settings.php @@ -0,0 +1,34 @@ +. + +/** + * This file defines the admin settings for this plugin + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent [at] call-learning [dt] fr) + */ + +defined('MOODLE_INTERNAL') || die; + +$settings->add(new admin_setting_configtext( + 'bbbext_simple/config_extension', + new lang_string('config_extension', 'bbbext_simple'), + new lang_string('config_extension', 'bbbext_simple'), + 1024, + PARAM_TEXT) +); diff --git a/mod/bigbluebuttonbn/tests/fixtures/extension/simple/version.php b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/version.php new file mode 100644 index 00000000000..8befbba8fc9 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/version.php @@ -0,0 +1,29 @@ +. + +/** + * This file contains the version information for the sample bigbluebuttonbn subplugin + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent [at] call-learning [dt] fr) + */ +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2023020800; +$plugin->requires = 2023020300; +$plugin->component = 'bbbext_simple'; diff --git a/mod/bigbluebuttonbn/tests/local/extension_test.php b/mod/bigbluebuttonbn/tests/local/extension_test.php new file mode 100644 index 00000000000..5c4d16290a0 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/local/extension_test.php @@ -0,0 +1,170 @@ +. +namespace mod_bigbluebuttonbn\local; + +use mod_bigbluebuttonbn\extension; +use mod_bigbluebuttonbn\local\extension\mod_instance_helper; +use mod_bigbluebuttonbn\test\subplugins_test_helper_trait; +use mod_bigbluebuttonbn\test\testcase_helper_trait; + +/** + * Extension helper class test + * + * @package mod_bigbluebuttonbn + * @copyright 2023 - present, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + * @coversDefaultClass \mod_bigbluebuttonbn\extension + */ +class extension_test extends \advanced_testcase { + use subplugins_test_helper_trait; + use testcase_helper_trait; + + /** + * Setup our fake plugin + * + * @return void + */ + public function setUp(): void { + $this->resetAfterTest(true); + $this->setup_fake_plugin('simple'); + } + + /** + * Test for the type_text provider. + * + * @param bool $bbbenabled + * @param string $apiclass + * @param array $extensionclasses + * + * @dataProvider classes_implementing_class + * @covers \mod_bigbluebuttonbn\extension::get_instances_implementing + */ + public function test_get_class_implementing(bool $bbbenabled, string $apiclass, array $extensionclasses): void { + $this->enable_plugins($bbbenabled); + // Make the method public so we can test it. + $reflectionextension = new \ReflectionClass(extension::class); + $getclassimplementing = $reflectionextension->getMethod('get_instances_implementing'); + $getclassimplementing->setAccessible(true); + $allfoundinstances = $getclassimplementing->invoke(null, $apiclass); + $foundclasses = array_map( + function($instance) { + return get_class($instance); + }, + $allfoundinstances + ); + $this->assertEquals($extensionclasses, $foundclasses); + } + + /** + * Test the add module callback + * + * @return void + * @covers \mod_bigbluebuttonbn\local\extension\mod_instance_helpe + */ + public function test_mod_instance_helper_add() { + global $DB; + // Enable plugin. + $this->enable_plugins(true); + + $course = $this->getDataGenerator()->create_course(); + $record = $this->getDataGenerator()->create_module( + 'bigbluebuttonbn', + ['course' => $course->id, 'newfield' => 2] + ); + $this->assertEquals(2, $DB->get_field('bbbext_simple', 'newfield', ['bigbluebuttonbnid' => $record->id])); + } + + /** + * Test the update module callback + * + * @return void + * @covers \mod_bigbluebuttonbn\local\extension\mod_instance_helpe + */ + public function test_mod_instance_helper_update() { + global $DB; + $this->setAdminUser(); + // Enable plugin. + $this->enable_plugins(true); + + $course = $this->getDataGenerator()->create_course(); + $record = $this->getDataGenerator()->create_module('bigbluebuttonbn', ['course' => $course->id, 'newfield' => 2]); + $cm = get_fast_modinfo($course)->instances['bigbluebuttonbn'][$record->id]; + [$cm, $context, $moduleinfo, $data] = get_moduleinfo_data($cm, $course); + $data->newfield = 3; + bigbluebuttonbn_update_instance($data); + $this->assertEquals(3, $DB->get_field('bbbext_simple', 'newfield', ['bigbluebuttonbnid' => $record->id])); + } + + /** + * Test delete module callback + * + * @return void + * @covers \mod_bigbluebuttonbn\local\extension\mod_instance_helpe + */ + public function test_mod_instance_helper_delete() { + global $DB; + $this->initialise_mock_server(); + // Enable plugin. + $this->enable_plugins(true); + + $course = $this->getDataGenerator()->create_course(); + $record = $this->getDataGenerator()->create_module('bigbluebuttonbn', ['course' => $course->id, 'newfield' => 2]); + $cm = get_fast_modinfo($course)->instances['bigbluebuttonbn'][$record->id]; + course_delete_module($cm->id, false); + $this->assertFalse($DB->get_field('bbbext_simple', 'newfield', ['bigbluebuttonbnid' => $record->id])); + } + + /** + * Data provider for testing get_class_implementing + * + * @return array[] + */ + public function classes_implementing_class(): array { + return [ + 'mod_instance_helper with plugin disabled' => [ + 'bbbenabled' => false, + 'apiclass' => mod_instance_helper::class, + 'result' => [] + ], + 'mod_instance_helper with plugin enabled' => [ + 'bbbenabled' => true, + 'apiclass' => mod_instance_helper::class, + 'result' => [ + 'bbbext_simple\\bigbluebuttonbn\\mod_instance_helper' + ] + ] + ]; + } + + /** + * Enable plugins + * + * @param bool $bbbenabled + * @return void + */ + private function enable_plugins(bool $bbbenabled) { + // First make sure that either BBB is enabled or not. + set_config('bigbluebuttonbn_default_dpa_accepted', $bbbenabled); + \core\plugininfo\mod::enable_plugin('bigbluebuttonbn', $bbbenabled ? 1 : 0); + $plugin = extension::BBB_EXTENSION_PLUGIN_NAME . '_simple'; + if ($bbbenabled) { + unset_config('disabled', $plugin); + } else { + set_config('disabled', 'disabled', $plugin); + } + } +} diff --git a/mod/bigbluebuttonbn/version.php b/mod/bigbluebuttonbn/version.php index 392615c30d8..a970f87fb90 100644 --- a/mod/bigbluebuttonbn/version.php +++ b/mod/bigbluebuttonbn/version.php @@ -27,6 +27,6 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2023042400; +$plugin->version = 2023042800; $plugin->requires = 2023041800; $plugin->component = 'mod_bigbluebuttonbn'; From 664bc729fe8ab46e2a60046c4066f43048496854 Mon Sep 17 00:00:00 2001 From: Laurent David Date: Wed, 15 Mar 2023 21:44:33 +0100 Subject: [PATCH 2/2] MDL-76710 mod_bigbluebuttonbn: Action URL addons subplugin * Add action URL extension point --- mod/bigbluebuttonbn/classes/extension.php | 27 ++++++++++++ .../local/extension/action_url_addons.php | 43 +++++++++++++++++++ .../classes/local/proxy/proxy_base.php | 6 ++- mod/bigbluebuttonbn/extension/README.md | 5 ++- .../bigbluebuttonbn/action_url_addons.php | 43 +++++++++++++++++++ .../tests/local/extension_test.php | 20 +++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 mod/bigbluebuttonbn/classes/local/extension/action_url_addons.php create mode 100644 mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/action_url_addons.php diff --git a/mod/bigbluebuttonbn/classes/extension.php b/mod/bigbluebuttonbn/classes/extension.php index 5f5fa72f8dc..5964a6111c5 100644 --- a/mod/bigbluebuttonbn/classes/extension.php +++ b/mod/bigbluebuttonbn/classes/extension.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . namespace mod_bigbluebuttonbn; +use mod_bigbluebuttonbn\local\extension\action_url_addons; use mod_bigbluebuttonbn\local\extension\mod_instance_helper; use stdClass; use core_plugin_manager; @@ -33,6 +34,32 @@ class extension { */ const BBB_EXTENSION_PLUGIN_NAME = 'bbbext'; + /** + * Invoke a subplugin hook that will return additional parameters + * + * @param string $action + * @param array $data + * @param array $metadata + * @return array associative array with the additional data and metadata (indexed by 'data' and + * 'metadata' keys). + */ + public static function action_url_addons(string $action = '', array $data = [], array $metadata = []): array { + $allmutationclass = self::get_instances_implementing(action_url_addons::class); + $additionaldata = []; + $additionalmetadata = []; + foreach ($allmutationclass as $mutationclass) { + // Here we intentionally just pass data and metadata and not the result as we + // do not want subplugin to assume that another subplugin is doing a modification. + ['data' => $newdata, 'metadata' => $newmetadata] = $mutationclass->execute($action, $data, $metadata); + $additionaldata = array_merge($additionaldata, $newdata ?? []); + $additionalmetadata = array_merge($additionalmetadata, $newmetadata ?? []); + } + return [ + 'data' => $additionaldata, + 'metadata' => $additionalmetadata + ]; + } + /** * Add instance processing * diff --git a/mod/bigbluebuttonbn/classes/local/extension/action_url_addons.php b/mod/bigbluebuttonbn/classes/local/extension/action_url_addons.php new file mode 100644 index 00000000000..1f6f4f35be3 --- /dev/null +++ b/mod/bigbluebuttonbn/classes/local/extension/action_url_addons.php @@ -0,0 +1,43 @@ +. +namespace mod_bigbluebuttonbn\local\extension; + +/** + * A single action class to mutate the action URL. + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class action_url_addons { + /** + * Mutate the action URL. + * + * By design: + * 1. we should only add parameters + * 2. we cannot count on the order the subplugins are called + * + * @param string $action + * @param array $data + * @param array $metadata + * @return array associative array with the additional data and metadata (indexed by 'data' and + * 'metadata' keys). + */ + public function execute(string $action = '', array $data = [], array $metadata = []): array { + return ['data' => [], 'metadata' => []]; + } +} diff --git a/mod/bigbluebuttonbn/classes/local/proxy/proxy_base.php b/mod/bigbluebuttonbn/classes/local/proxy/proxy_base.php index 8ed29249878..2fd0d1b7c6e 100644 --- a/mod/bigbluebuttonbn/classes/local/proxy/proxy_base.php +++ b/mod/bigbluebuttonbn/classes/local/proxy/proxy_base.php @@ -16,6 +16,7 @@ namespace mod_bigbluebuttonbn\local\proxy; +use mod_bigbluebuttonbn\extension; use mod_bigbluebuttonbn\local\config; use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception; use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception; @@ -55,10 +56,13 @@ abstract class proxy_base { */ protected static function action_url(string $action = '', array $data = [], array $metadata = []): string { $baseurl = self::sanitized_url() . $action . '?'; + ['data' => $additionaldata, 'metadata' => $additionalmetadata] = extension::action_url_addons($action, $data, $metadata); + $data = array_merge($data, $additionaldata ?? []); + $metadata = array_merge($metadata, $additionalmetadata ?? []); + $metadata = array_combine(array_map(function($k) { return 'meta_' . $k; }, array_keys($metadata)), $metadata); - $params = http_build_query($data + $metadata, '', '&'); $checksum = self::get_checksum($action, $params); return $baseurl . $params . '&checksum=' . $checksum; diff --git a/mod/bigbluebuttonbn/extension/README.md b/mod/bigbluebuttonbn/extension/README.md index bbbbbd3eb82..ba09fab3d3f 100644 --- a/mod/bigbluebuttonbn/extension/README.md +++ b/mod/bigbluebuttonbn/extension/README.md @@ -1,8 +1,9 @@ ## BigBlueButtonBN extension subplugins -The plugins are made to extend existing BigBlueButtonBN behaviour. For now, we have one extension point using as base classes that should be implemented. +The plugins are made to extend existing BigBlueButtonBN behaviour. For now, we have two extensions points using as base classes that should be implemented. +* action_url_addons: inherit this class and redefine the execute method so add new parameter when we send an action url to the BigBlueButton server. * mod_instance_helper : inherit this class so all methods will be called when we either add/delete/or update a module instance. -The extension classes should be placed in your plugin with exactly the same name but in a different namespace for example + The extension classes should be placed in your plugin with exactly the same name but in a different namespace for example * **\\bbbext_\\bigbluebuttonbn\\mod_instance_helper** to extend hooks from the mod_instance_helper class. diff --git a/mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/action_url_addons.php b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/action_url_addons.php new file mode 100644 index 00000000000..9919e8e9ef4 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/fixtures/extension/simple/classes/bigbluebuttonbn/action_url_addons.php @@ -0,0 +1,43 @@ +. +namespace bbbext_simple\bigbluebuttonbn; + +/** + * A single action class to mutate the action URL. + * + * @package mod_bigbluebuttonbn + * @copyright 2023 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent@call-learning.fr) + */ +class action_url_addons extends \mod_bigbluebuttonbn\local\extension\action_url_addons { + /** + * Sample mutate the action URL. + * + * + * @param string $action + * @param array $data + * @param array $metadata + * @return array associative array with the additional data and metadata (indexed by 'data' and + * 'metadata' keys) + */ + public function execute(string $action = '', array $data = [], array $metadata = []): array { + return [ + 'data' => $action == 'create' ? [] : ['a', 'b'], + 'metadata' => in_array('Test', $metadata) ? ['c', 'd'] : [] + ]; + } +} diff --git a/mod/bigbluebuttonbn/tests/local/extension_test.php b/mod/bigbluebuttonbn/tests/local/extension_test.php index 5c4d16290a0..1211c2e3b4f 100644 --- a/mod/bigbluebuttonbn/tests/local/extension_test.php +++ b/mod/bigbluebuttonbn/tests/local/extension_test.php @@ -128,6 +128,26 @@ class extension_test extends \advanced_testcase { $this->assertFalse($DB->get_field('bbbext_simple', 'newfield', ['bigbluebuttonbnid' => $record->id])); } + /** + * Test the action_url_addons with plugin enabled + * + * @return void + * @covers \mod_bigbluebuttonbn\extension::action_url_addons + */ + public function test_action_url_addons() { + // Enable plugin. + $this->enable_plugins(true); + // Set a random var here. + $var1 = []; + $var2 = ['Test']; + ['data' => $additionalvar1, 'metadata' => $additionalvar2] = extension::action_url_addons('create', [], ['Test']); + $this->assertEmpty($additionalvar1); + $this->assertCount(2, $additionalvar2); + ['data' => $additionalvar1, 'metadata' => $additionalvar2] = extension::action_url_addons('delete'); + $this->assertNotEmpty($additionalvar1); + $this->assertEmpty($additionalvar2); + } + /** * Data provider for testing get_class_implementing *