diff --git a/classes/settings/admin_setting_configtextwithvalidation.php b/classes/settings/admin_setting_configtextwithvalidation.php index 81d855d..5428b4c 100644 --- a/classes/settings/admin_setting_configtextwithvalidation.php +++ b/classes/settings/admin_setting_configtextwithvalidation.php @@ -27,6 +27,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class admin_setting_configtextwithvalidation extends \admin_setting_configtext { + + /** @var string the old value of the this setting. */ + private $oldvalue; + + /** @var string the newly saved value of the this setting. */ + private $newlysavedvalue; + /** * Validate data before storage * @param mixed $data @@ -50,12 +57,27 @@ public function validate($data) { * @return bool|\lang_string|mixed|string empty string if successful, otherwise error message */ public function write_setting($data) { + // Recording the old settings, before writing new settings. + $this->oldvalue = $this->get_setting(); + $failed = parent::write_setting($data); if (!$failed) { + + // Record newly saved settings that pass the validation, to work with later on. + $this->newlysavedvalue = $this->get_setting(); + + // Catching what is saved in terms of instance ids, to avoid propagating deleted ones! + $newocinstanceids = $this->get_new_instance_ids(); + // Propagate changes to plugins. $adminroot = admin_get_root(); $toolsettings = $adminroot->locate('tool_opencast'); foreach ($toolsettings->get_children() as $child) { + // Making sure that setting is current. + if (!$this->is_setting_current($child->name, $newocinstanceids)) { + continue; + } + if (substr($child->name, 0, 28) == 'tool_opencast_configuration_') { $toolchildsettings = property_exists($child, 'settings') ? $child->settings : []; foreach ($toolchildsettings as $name => $setting) { @@ -72,6 +94,10 @@ public function write_setting($data) { if (core_plugin_manager::instance()->get_plugin_info('block_opencast')) { $blocksettings = $adminroot->locate('block_opencast'); foreach ($blocksettings->get_children() as $category) { + // Making sure that category is current. + if (!$this->is_setting_current($category->name, $newocinstanceids)) { + continue; + } if ($category instanceof admin_category) { foreach ($category->get_children() as $child) { $blockchildsettings = property_exists($child, 'settings') ? $child->settings : []; @@ -94,6 +120,10 @@ public function write_setting($data) { $modsettings = $adminroot->locate('modsettingopencast'); $modchildsettings = property_exists($modsettings, 'settings') ? $modsettings->settings : []; foreach ($modchildsettings as $name => $setting) { + // Making sure that setting is current. + if (!$this->is_setting_current($name, $newocinstanceids)) { + continue; + } $data = $setting->get_setting(); if (is_null($data)) { $data = $setting->get_defaultsetting(); @@ -101,8 +131,151 @@ public function write_setting($data) { } } } + + // Perform setting deletions for those instances that do not exist! + // Since the main purpose of this function is already fulfilled, therefore we don't need to catch any errors during + // deletion, in order to prevent further confusion. + $this->perform_ocinstance_deletion(); + return ''; } return $failed; } + + /** + * Extracts the IDs of deleted Opencast instances. + * + * This method compares the old and new values of Opencast instance IDs and identifies + * which IDs have been deleted. It returns an array of deleted Opencast instance IDs. + * + * @return array An array of deleted Opencast instance IDs. + */ + private function extract_deleted_ocinstances(): array { + $deletedocinstanceids = []; + if (!empty($this->oldvalue)) { + try { + $oldocinstanceids = array_column(json_decode($this->oldvalue), 'id'); + + $newocinstanceids = $this->get_new_instance_ids(); + + // If there are ids in the old settings, but not in the new, then it mean those ids are being deleted. + $deletedocinstanceids = array_diff($oldocinstanceids, $newocinstanceids); + } catch (\moodle_exception $e) { + // Something went wrong during all the decoding and differing arrays, then return empty array, + // to avoid unwanted deletions. + $deletedocinstanceids = []; + } + } + return $deletedocinstanceids; + } + + /** + * Checks if the given setting name corresponds to a current instance ID. + * + * This method extracts the instance ID from the setting name using a regular expression. + * It then checks if the extracted instance ID is present in the provided list of current instance IDs. + * + * @param string $settingname The name of the setting to check. + * @param array $currentinstanceids An array of current instance IDs to validate against. + * @return bool Returns true if the setting name corresponds to a current instance ID, false otherwise. + */ + private function is_setting_current(string $settingname, array $currentinstanceids): bool { + if (preg_match('#_(\d+)$#', $settingname, $matches)) { + $settinginstanceid = intval($matches[1]); + if ($settinginstanceid && !in_array($settinginstanceid, $currentinstanceids)) { + return false; + } + } + return true; + } + + /** + * Retrieves the new instance IDs from the newly saved value. + * + * This method attempts to decode the JSON-encoded newly saved value and extract the 'id' fields + * from it. If the newly saved value is empty or an error occurs during decoding, an empty array + * is returned. + * + * @return array An array of new instance IDs. + */ + private function get_new_instance_ids(): array { + $newocinstanceids = []; + try { + if (!empty($this->newlysavedvalue)) { + $newocinstanceids = array_column(json_decode($this->newlysavedvalue), 'id'); + } + } catch (\Throwable $th) { + $newocinstanceids = []; + } + return $newocinstanceids; + } + + /** + * Deletes the Opencast instance configurations from the database. + * + * This method performs the deletion of Opencast instance configurations when instances are removed. + * It ensures that the deletion process is only executed when there are actual deletions to prevent + * unnecessary processing. + * + * @return void + */ + private function perform_ocinstance_deletion(): void { + global $DB; + + // If there are no old values, we don't need to perform the deletion, as there are no instances to delete. + if (empty($this->oldvalue)) { + return; + } + + // We check and extract deleted instances to ensure that the following laborious procedure + // is only executed when deletions occur. This prevents unnecessary processing for other operations. + + $deletedocinstanceids = $this->extract_deleted_ocinstances(); + + // Nothing to delete, we return the process. + if (empty($deletedocinstanceids)) { + return; + } + + // Setting plugins to loop through and find configs to delete. + $plugins = [ + 'tool_opencast', + 'block_opencast', + 'mod_opencast', + 'filter_opencast', + ]; + + // Preparing the (where clause) query for select. + $select = $DB->sql_equal('plugin', ':plugin') . " AND " . $DB->sql_like('name', ':confignamelike'); + + // First level of looping is to go through the deleted instances. + foreach ($deletedocinstanceids as $id) { + + // In here we need to set the params value and determine the config name based on the ocinstance id. + $params = [ + 'confignamelike' => '%_' . $id, + ]; + + // Second level of looping is to go through the plugins. + foreach ($plugins as $plugin) { + + // In here we can determine the plugin name. + $params['plugin'] = $plugin; + + // Now that the information to get the plugin configuration related to deleted instance is ready, + // we perform the select records query. + $selectedpluginconfigs = $DB->get_records_select('config_plugins', $select, $params, '', 'name'); + + // If we found something, we proceed to the deletion by using "unset_config". + if (!empty($selectedpluginconfigs)) { + foreach ($selectedpluginconfigs as $configrecord) { + if (!empty($configrecord->name)) { + // Very important to use "unset_config", because of taking care of cached configuration as well. + unset_config($configrecord->name, $plugin); + } + } + } + } + } + } }