See Release Notes
Long Term Support Release
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace core_adminpresets; use memory_xml_output; use moodle_exception; use stdClass; use xml_writer; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->libdir . '/adminlib.php'); /** * Admin tool presets manager class. * * @package core_adminpresets * @copyright 2021 Pimenko <support@pimenko.com><pimenko.com> * @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David MonllaĆ³ <david.monllao@urv.cat> code * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class manager { /** @var \admin_root The admin root tree with the settings. **/ private $adminroot; /** @var array Setting classes mapping, to associated the local/setting class that should be used when there is * no specific class. */ protected static $settingclassesmap = [ 'adminpresets_admin_setting_agedigitalconsentmap' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configcolourpicker' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configdirectory' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configduration_with_advanced' => 'adminpresets_admin_setting_configtext_with_advanced', 'adminpresets_admin_setting_configduration' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configempty' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configexecutable' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configfile' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_confightmleditor' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configmixedhostiplist' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configmultiselect_modules' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_setting_configpasswordunmask' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configportlist' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configselect_with_lock' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_setting_configtext_trim_lower' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configtext_with_maxlength' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configtextarea' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_configthemepreset' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_setting_countrycodes' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_courselist_frontpage' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_setting_description' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_enablemobileservice' => 'adminpresets_admin_setting_configcheckbox', 'adminpresets_admin_setting_filetypes' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_forcetimezone' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_setting_grade_profilereport' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_setting_langlist' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_my_grades_report' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_setting_pickroles' => 'adminpresets_admin_setting_configmulticheckbox', 'adminpresets_admin_setting_question_behaviour' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_setting_regradingcheckbox' => 'adminpresets_admin_setting_configcheckbox', 'adminpresets_admin_setting_scsscode' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_servertimezone' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_setting_sitesetcheckbox' => 'adminpresets_admin_setting_configcheckbox', 'adminpresets_admin_setting_sitesetselect' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_setting_special_adminseesall' => 'adminpresets_admin_setting_configcheckbox', 'adminpresets_admin_setting_special_backup_auto_destination' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_special_coursecontact' => 'adminpresets_admin_setting_configmulticheckbox', 'adminpresets_admin_setting_special_coursemanager' => 'adminpresets_admin_setting_configmulticheckbox', 'adminpresets_admin_setting_special_debug' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_setting_special_frontpagedesc' => 'adminpresets_admin_setting_sitesettext', 'adminpresets_admin_setting_special_gradebookroles' => 'adminpresets_admin_setting_configmulticheckbox', 'adminpresets_admin_setting_special_gradeexport' => 'adminpresets_admin_setting_configmulticheckbox', 'adminpresets_admin_setting_special_gradelimiting' => 'adminpresets_admin_setting_configcheckbox', 'adminpresets_admin_setting_special_grademinmaxtouse' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_setting_special_gradepointdefault' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_special_gradepointmax' => 'adminpresets_admin_setting_configtext', 'adminpresets_admin_setting_special_registerauth' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_setting_special_selectsetup' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_settings_country_select' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_settings_coursecat_select' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_settings_h5plib_handler_select' => 'adminpresets_admin_setting_configselect', 'adminpresets_admin_settings_num_course_sections' => 'adminpresets_admin_setting_configmultiselect_with_loader', 'adminpresets_admin_settings_sitepolicy_handler_select' => 'adminpresets_admin_setting_configselect', 'adminpresets_antivirus_clamav_pathtounixsocket_setting' => 'adminpresets_admin_setting_configtext', 'adminpresets_antivirus_clamav_runningmethod_setting' => 'adminpresets_admin_setting_configselect', 'adminpresets_antivirus_clamav_tcpsockethost_setting' => 'adminpresets_admin_setting_configtext', 'adminpresets_auth_db_admin_setting_special_auth_configtext' => 'adminpresets_admin_setting_configtext', 'adminpresets_auth_ldap_admin_setting_special_lowercase_configtext' => 'adminpresets_admin_setting_configtext', 'adminpresets_auth_ldap_admin_setting_special_ntlm_configtext' => 'adminpresets_admin_setting_configtext', 'adminpresets_auth_shibboleth_admin_setting_convert_data' => 'adminpresets_admin_setting_configtext', 'adminpresets_auth_shibboleth_admin_setting_special_idp_configtextarea' => 'adminpresets_admin_setting_configtext', 'adminpresets_auth_shibboleth_admin_setting_special_wayf_select' => 'adminpresets_admin_setting_configselect', 'adminpresets_editor_atto_toolbar_setting' => 'adminpresets_admin_setting_configtext',< 'adminpresets_editor_tinymce_json_setting_textarea' => 'adminpresets_admin_setting_configtext','adminpresets_enrol_database_admin_setting_category' => 'adminpresets_admin_setting_configselect', 'adminpresets_enrol_flatfile_role_setting' => 'adminpresets_admin_setting_configtext', 'adminpresets_enrol_ldap_admin_setting_category' => 'adminpresets_admin_setting_configselect', 'adminpresets_format_singleactivity_admin_setting_activitytype' => 'adminpresets_admin_setting_configselect', 'adminpresets_qtype_multichoice_admin_setting_answernumbering' => 'adminpresets_admin_setting_configselect', ]; /** @var array Relation between database fields and XML files. **/ protected static $dbxmlrelations = [ 'name' => 'NAME', 'comments' => 'COMMENTS', 'timecreated' => 'PRESET_DATE', 'site' => 'SITE_URL', 'author' => 'AUTHOR', 'moodleversion' => 'MOODLE_VERSION', 'moodlerelease' => 'MOODLE_RELEASE' ]; /** @var int Non-core preset */ public const NONCORE_PRESET = 0; /** @var int Starter preset */ public const STARTER_PRESET = 1; /** @var int Full preset */ public const FULL_PRESET = 2; /** * Gets the system settings * * Loads the DB $CFG->prefix.'config' values and the * $CFG->prefix.'config_plugins' values and redirects * the flow through $this->get_settings() * * @return array $settings Array format $array['plugin']['settingname'] = settings_types child class */ public function get_site_settings(): array { global $DB; // Db configs (to avoid multiple queries). $dbconfig = $DB->get_records_select('config', '', [], '', 'name, value'); // Adding site settings in course table. $frontpagevalues = $DB->get_record_select('course', 'id = 1', [], 'fullname, shortname, summary'); foreach ($frontpagevalues as $field => $value) { $dbconfig[$field] = new stdClass(); $dbconfig[$field]->name = $field; $dbconfig[$field]->value = $value; } $sitedbsettings['none'] = $dbconfig; // Config plugins. $configplugins = $DB->get_records('config_plugins'); foreach ($configplugins as $configplugin) { $sitedbsettings[$configplugin->plugin][$configplugin->name] = new stdClass(); $sitedbsettings[$configplugin->plugin][$configplugin->name]->name = $configplugin->name; $sitedbsettings[$configplugin->plugin][$configplugin->name]->value = $configplugin->value; } // Get an array with the common format. return $this->get_settings($sitedbsettings, true, []); } /** * Constructs an array with all the system settings * * If a setting value can't be found on the DB it considers * the default value as the setting value * * Settings without plugin are marked as 'none' in the plugin field * * Returns an standarized settings array format. * * @param array $dbsettings Standarized array, * format $array['plugin']['name'] = obj('name'=>'settingname', 'value'=>'settingvalue') * @param boolean $sitedbvalues Indicates if $dbsettings comes from the site db or not * @param array $settings Array format $array['plugin']['settingname'] = settings_types child class * @param array|false $children Array of admin_category children or false< * @return array Array format $array['plugin']['settingname'] = settings_types child class> * @return \core_adminpresets\local\setting\adminpresets_setting[][] Array format > * $array['plugin']['settingname'] = adminpresets_setting child class*/ public function get_settings(array $dbsettings, bool $sitedbvalues = false, array $settings = [], $children = false): array { global $DB; // If there are no children, load admin tree and iterate through. if (!$children) { $this->adminroot = admin_get_root(false, true); $children = $this->adminroot->children; } // Iteates through children. foreach ($children as $key => $child) { // We must search category children. if (is_a($child, 'admin_category')) { if ($child->children) { $settings = $this->get_settings($dbsettings, $sitedbvalues, $settings, $child->children); } // Settings page. } else if (is_a($child, 'admin_settingpage')) { if (property_exists($child, 'settings')) { foreach ($child->settings as $values) { $settingname = $values->name; unset($settingvalue); // Look for his config value. if ($values->plugin == '') { $values->plugin = 'none'; } if (!empty($dbsettings[$values->plugin][$settingname])) { $settingvalue = $dbsettings[$values->plugin][$settingname]->value; } // If no db value found default value. if ($sitedbvalues && !isset($settingvalue)) { // For settings with multiple values. if (is_array($values->defaultsetting)) { if (isset($values->defaultsetting['value'])) { $settingvalue = $values->defaultsetting['value']; // Configtime case, does not have a 'value' default setting. } else { $settingvalue = 0; } } else { $settingvalue = $values->defaultsetting; } } // If there aren't any value loaded, skip that setting. if (!isset($settingvalue)) { continue; } // If there is no setting class defined continue. if (!$setting = $this->get_setting($values, $settingvalue)) { continue; } // Settings_types childs with. // attributes provides an attributes array. if ($attributes = $setting->get_attributes()) { // Look for settings attributes if it is a presets. if (!$sitedbvalues) { $itemid = $dbsettings[$values->plugin][$settingname]->itemid; $attrs = $DB->get_records('adminpresets_it_a', ['itemid' => $itemid], '', 'name, value'); } foreach ($attributes as $defaultvarname => $varname) { unset($attributevalue); // Settings from site. if ($sitedbvalues) { if (!empty($dbsettings[$values->plugin][$varname])) { $attributevalue = $dbsettings[$values->plugin][$varname]->value; } // Settings from a preset. } else if (!$sitedbvalues && isset($attrs[$varname])) { $attributevalue = $attrs[$varname]->value; } // If no value found, default value, // But we may not have a default value for the attribute. if (!isset($attributevalue) && !empty($values->defaultsetting[$defaultvarname])) { $attributevalue = $values->defaultsetting[$defaultvarname]; } // If there is no even a default for this setting will be empty. // So we do nothing in this case. if (isset($attributevalue)) { $setting->set_attribute_value($varname, $attributevalue); } } } // Adding to general settings array. $settings[$values->plugin][$settingname] = $setting; } } } } return $settings; } /** * Returns the class type object * * @param object $settingdata Setting data * @param mixed $currentvalue * @return mixed */ public function get_setting($settingdata, $currentvalue) { $classname = null;< // Getting the appropiate class to get the correct setting value.> // Getting the appropriate class to get the correct setting value.$settingtype = get_class($settingdata);<// Check if it is a setting from a plugin.< $plugindata = explode('_', $settingtype);> $namespacedata = explode('\\', $settingtype); > if (count($namespacedata) > 1) { > $plugindata = explode('_', $namespacedata[0]); > $settingtype = end($namespacedata); > } else { > $plugindata = explode('_', $settingtype, 2); > } >$types = \core_component::get_plugin_types(); if (array_key_exists($plugindata[0], $types)) { $plugins = \core_component::get_plugin_list($plugindata[0]); if (array_key_exists($plugindata[1], $plugins)) { // Check if there is a specific class for this plugin admin setting. $settingname = 'adminpresets_' . $settingtype; $classname = "\\$plugindata[0]_$plugindata[1]\\adminpresets\\$settingname"; if (!class_exists($classname)) { $classname = null; } } } else { $settingname = 'adminpresets_' . $settingtype; $classname = '\\core_adminpresets\\local\\setting\\' . $settingname; if (!class_exists($classname)) { // Check if there is some mapped class that should be used for this setting. $classname = self::get_settings_class($settingname); } } if (is_null($classname)) { // Return the default setting class if there is no specific class for this setting. $classname = '\\core_adminpresets\\local\\setting\\adminpresets_setting'; } return new $classname($settingdata, $currentvalue); } /** * Returns the settings class mapped to the defined $classname or null if it doesn't exist any associated class. * * @param string $classname The classname to get the mapped class. * @return string|null */ public static function get_settings_class(string $classname): ?string { if (array_key_exists($classname, self::$settingclassesmap)) { return '\\core_adminpresets\\local\\setting\\' . self::$settingclassesmap[$classname]; } return null; } /** * Gets the standarized settings array from DB records * * @param array $dbsettings Array of objects * @return array Standarized array, * format $array['plugin']['name'] = obj('name'=>'settingname', 'value'=>'settingvalue') */ public function get_settings_from_db(array $dbsettings): array { $settings = []; if (!$dbsettings) { return $settings; } foreach ($dbsettings as $dbsetting) { $settings[$dbsetting->plugin][$dbsetting->name] = new stdClass(); $settings[$dbsetting->plugin][$dbsetting->name]->itemid = $dbsetting->id; $settings[$dbsetting->plugin][$dbsetting->name]->name = $dbsetting->name; $settings[$dbsetting->plugin][$dbsetting->name]->value = $dbsetting->value; } return $settings; } /** * Apply a given preset. * * @param int $presetid The preset identifier to apply. * @param bool $simulate Whether this is a simulation or not. * @return array List with an array with the applied settings and another with the skipped ones. */ public function apply_preset(int $presetid, bool $simulate = false): array { global $DB; if (!$DB->get_record('adminpresets', ['id' => $presetid])) { throw new moodle_exception('errornopreset', 'core_adminpresets'); } // Apply preset settings. [$settingsapplied, $settingsskipped, $appid] = $this->apply_settings($presetid, $simulate); // Set plugins visibility. [$pluginsapplied, $pluginsskipped] = $this->apply_plugins($presetid, $simulate, $appid); $applied = array_merge($settingsapplied, $pluginsapplied); $skipped = array_merge($settingsskipped, $pluginsskipped); if (!$simulate) { // Store it in a config setting as the last preset applied. set_config('lastpresetapplied', $presetid, 'adminpresets'); } return [$applied, $skipped]; } /** * Create a preset with the current settings and plugins information. * * @param \stdClass $data Preset info, such as name or description, to be used when creating the preset with the current * settings and plugins. * @return array List with an the presetid created (int), a boolean to define if any setting has been found and * another boolean to specify if any plugin has been found. */ public function export_preset(stdClass $data): array { global $DB; // Admin_preset record. $presetdata = [ 'name' => $data->name ?? '', 'comments' => !empty($data->comments) ? $data->comments['text'] : '', 'author' => $data->author ?? '', ]; if (!$presetid = helper::create_preset($presetdata)) { throw new moodle_exception('errorinserting', 'core_adminpresets'); } // Store settings. $settingsfound = false; // Site settings. $sitesettings = $this->get_site_settings(); // Sensible settings. $sensiblesettings = explode(',', str_replace(' ', '', get_config('adminpresets', 'sensiblesettings'))); $sensiblesettings = array_combine($sensiblesettings, $sensiblesettings); foreach ($sitesettings as $plugin => $pluginsettings) { foreach ($pluginsettings as $settingname => $sitesetting) { // Avoid sensible data. if (empty($data->includesensiblesettings) && !empty($sensiblesettings["$settingname@@$plugin"])) { continue; } $setting = new stdClass(); $setting->adminpresetid = $presetid; $setting->plugin = $plugin; $setting->name = $settingname; $setting->value = $sitesetting->get_value(); if (!$setting->id = $DB->insert_record('adminpresets_it', $setting)) { throw new moodle_exception('errorinserting', 'core_adminpresets'); } // Setting attributes must also be exported. if ($attributes = $sitesetting->get_attributes_values()) { foreach ($attributes as $attname => $attvalue) { $attr = new stdClass(); $attr->itemid = $setting->id; $attr->name = $attname; $attr->value = $attvalue; $DB->insert_record('adminpresets_it_a', $attr); } } $settingsfound = true; } } // Store plugins visibility (enabled/disabled). $pluginsfound = false; $pluginmanager = \core_plugin_manager::instance(); $types = $pluginmanager->get_plugin_types(); foreach ($types as $plugintype => $notused) { $plugins = $pluginmanager->get_present_plugins($plugintype); $pluginclass = \core_plugin_manager::resolve_plugininfo_class($plugintype); if (!empty($plugins)) { foreach ($plugins as $pluginname => $plugin) { $entry = new stdClass(); $entry->adminpresetid = $presetid; $entry->plugin = $plugintype; $entry->name = $pluginname; $entry->enabled = $pluginclass::get_enabled_plugin($pluginname); $DB->insert_record('adminpresets_plug', $entry); $pluginsfound = true; } } } // If there are no settings nor plugins, the admin preset record should be removed. if (!$settingsfound && !$pluginsfound) { $DB->delete_records('adminpresets', ['id' => $presetid]); $presetid = null; } return [$presetid, $settingsfound, $pluginsfound]; } /** * Create the XML content for a given preset. * * @param int $presetid The preset to download. * @return array List with the XML content (string) and a filename proposal based on the preset name (string). */ public function download_preset(int $presetid): array { global $DB; if (!$preset = $DB->get_record('adminpresets', ['id' => $presetid])) { throw new moodle_exception('errornopreset', 'core_adminpresets'); } // Start. $xmloutput = new memory_xml_output(); $xmlwriter = new xml_writer($xmloutput); $xmlwriter->start(); // Preset data. $xmlwriter->begin_tag('PRESET'); foreach (static::$dbxmlrelations as $dbname => $xmlname) { $xmlwriter->full_tag($xmlname, $preset->$dbname); } // We ride through the settings array. $items = $DB->get_records('adminpresets_it', ['adminpresetid' => $preset->id]); $allsettings = $this->get_settings_from_db($items); if ($allsettings) { $xmlwriter->begin_tag('ADMIN_SETTINGS'); foreach ($allsettings as $plugin => $settings) { $tagname = strtoupper($plugin); // To aviod xml slash problems. if (strstr($tagname, '/') != false) { $tagname = str_replace('/', '__', $tagname); } $xmlwriter->begin_tag($tagname); // One tag for each plugin setting. if (!empty($settings)) { $xmlwriter->begin_tag('SETTINGS'); foreach ($settings as $setting) { // Unset the tag attributes string. $attributes = []; // Getting setting attributes, if present. $attrs = $DB->get_records('adminpresets_it_a', ['itemid' => $setting->itemid]); if ($attrs) { foreach ($attrs as $attr) { $attributes[$attr->name] = $attr->value; } } $xmlwriter->full_tag(strtoupper($setting->name), $setting->value, $attributes); } $xmlwriter->end_tag('SETTINGS'); } $xmlwriter->end_tag(strtoupper($tagname)); } $xmlwriter->end_tag('ADMIN_SETTINGS'); } // We ride through the plugins array. $data = $DB->get_records('adminpresets_plug', ['adminpresetid' => $preset->id]); if ($data) { $plugins = []; foreach ($data as $plugin) { $plugins[$plugin->plugin][] = $plugin; } $xmlwriter->begin_tag('PLUGINS'); foreach ($plugins as $plugintype => $plugintypes) { $tagname = strtoupper($plugintype); $xmlwriter->begin_tag($tagname); foreach ($plugintypes as $plugin) { $xmlwriter->full_tag(strtoupper($plugin->name), $plugin->enabled); } $xmlwriter->end_tag(strtoupper($tagname)); } $xmlwriter->end_tag('PLUGINS'); } // End. $xmlwriter->end_tag('PRESET'); $xmlwriter->stop(); $xmlstr = $xmloutput->get_allcontents(); $filename = addcslashes($preset->name, '"') . '.xml'; return [$xmlstr, $filename]; } /** * Import a given XML preset. * * @param string $xmlcontent The XML context with the preset to be imported. * @param string|null $presetname The preset name that will overwrite the one given in the XML file. * @return array List with an the XML element (SimpleXMLElement|null), the imported preset (stdClass|null), a boolean * to define if any setting has been found and another boolean to specify if any plugin has been found. */ public function import_preset(string $xmlcontent, ?string $presetname = null): array { global $DB, $USER; $settingsfound = false; $pluginsfound = false; try { $xml = simplexml_load_string($xmlcontent); } catch (\Exception $exception) { $xml = false; } if (!$xml) { return [null, null, $settingsfound, $pluginsfound]; } // Prepare the preset info. $preset = new stdClass(); foreach (static::$dbxmlrelations as $dbname => $xmlname) { $preset->$dbname = (String) $xml->$xmlname; } $preset->userid = $USER->id; $preset->timeimported = time(); // Overwrite preset name. if (!empty($presetname)) { $preset->name = $presetname; } // Create the preset. if (!$preset->id = $DB->insert_record('adminpresets', $preset)) { throw new moodle_exception('errorinserting', 'core_adminpresets'); } // Process settings. $sitesettings = $this->get_site_settings(); $xmladminsettings = $xml->ADMIN_SETTINGS[0]; foreach ($xmladminsettings as $plugin => $settings) { $plugin = strtolower($plugin); if (strstr($plugin, '__') != false) { $plugin = str_replace('__', '/', $plugin); } $pluginsettings = $settings->SETTINGS[0]; if ($pluginsettings) { foreach ($pluginsettings->children() as $name => $setting) { $name = strtolower($name); // Default to ''. if ($setting->__toString() === false) { $value = ''; } else { $value = $setting->__toString(); } if (empty($sitesettings[$plugin][$name])) { debugging('Setting ' . $plugin . '/' . $name . ' not supported by this Moodle version', DEBUG_DEVELOPER); continue; } // Cleaning the setting value. if (!$presetsetting = $this->get_setting($sitesettings[$plugin][$name]->get_settingdata(), $value)) { debugging('Setting ' . $plugin . '/' . $name . ' not implemented', DEBUG_DEVELOPER); continue; } $settingsfound = true; // New item. $item = new stdClass(); $item->adminpresetid = $preset->id; $item->plugin = $plugin; $item->name = $name; $item->value = $presetsetting->get_value(); // Insert preset item. if (!$item->id = $DB->insert_record('adminpresets_it', $item)) { throw new moodle_exception('errorinserting', 'core_adminpresets'); } // Add setting attributes. if ($setting->attributes() && ($itemattributes = $presetsetting->get_attributes())) { foreach ($setting->attributes() as $attrname => $attrvalue) { $itemattributenames = array_flip($itemattributes); // Check the attribute existence. if (!isset($itemattributenames[$attrname])) { debugging('The ' . $plugin . '/' . $name . ' attribute ' . $attrname . ' is not supported by this Moodle version', DEBUG_DEVELOPER); continue; } $attr = new stdClass(); $attr->itemid = $item->id; $attr->name = $attrname; $attr->value = $attrvalue->__toString(); $DB->insert_record('adminpresets_it_a', $attr); } } } } } // Process plugins. if ($xml->PLUGINS) { $xmlplugins = $xml->PLUGINS[0]; foreach ($xmlplugins as $plugin => $plugins) { $pluginname = strtolower($plugin); foreach ($plugins->children() as $name => $plugin) { $pluginsfound = true; // New plugin. $entry = new stdClass(); $entry->adminpresetid = $preset->id; $entry->plugin = $pluginname; $entry->name = strtolower($name); $entry->enabled = $plugin->__toString(); // Insert plugin. if (!$entry->id = $DB->insert_record('adminpresets_plug', $entry)) { throw new moodle_exception('errorinserting', 'core_adminpresets'); } } } } // If there are no valid or selected settings we should delete the admin preset record. if (!$settingsfound && !$pluginsfound) { $DB->delete_records('adminpresets', ['id' => $preset->id]); $preset = null; } return [$xml, $preset, $settingsfound, $pluginsfound]; } /** * Delete given preset. * * @param int $presetid Preset identifier to delete. * @return void */ public function delete_preset(int $presetid): void { global $DB; // Check the preset exists. $preset = $DB->get_record('adminpresets', ['id' => $presetid]); if (!$preset) { throw new moodle_exception('errordeleting', 'core_adminpresets'); } // Deleting the preset applications. if ($previouslyapplied = $DB->get_records('adminpresets_app', ['adminpresetid' => $presetid], 'id')) { $appids = array_keys($previouslyapplied); list($insql, $inparams) = $DB->get_in_or_equal($appids); $DB->delete_records_select('adminpresets_app_it', "adminpresetapplyid $insql", $inparams); $DB->delete_records_select('adminpresets_app_it_a', "adminpresetapplyid $insql", $inparams); $DB->delete_records_select('adminpresets_app_plug', "adminpresetapplyid $insql", $inparams); if (!$DB->delete_records('adminpresets_app', ['adminpresetid' => $presetid])) { throw new moodle_exception('errordeleting', 'core_adminpresets'); } } // Getting items ids and remove advanced items associated to them. $items = $DB->get_records('adminpresets_it', ['adminpresetid' => $presetid], 'id'); if (!empty($items)) { $itemsid = array_keys($items); list($insql, $inparams) = $DB->get_in_or_equal($itemsid); $DB->delete_records_select('adminpresets_it_a', "itemid $insql", $inparams); } if (!$DB->delete_records('adminpresets_it', ['adminpresetid' => $presetid])) { throw new moodle_exception('errordeleting', 'core_adminpresets'); } // Delete plugins. if (!$DB->delete_records('adminpresets_plug', ['adminpresetid' => $presetid])) { throw new moodle_exception('errordeleting', 'core_adminpresets'); } // Delete preset. if (!$DB->delete_records('adminpresets', ['id' => $presetid])) { throw new moodle_exception('errordeleting', 'core_adminpresets'); } } /** * Revert a given preset applied previously. * It backs settings and plugins to their original state before applying the presset and removes * the applied preset information from DB. * * @param int $presetappid The appplied preset identifier to be reverted. * @return array List with the presetapp removed (or null if there was some error), an array with the rollback settings/plugins * changed and an array with the failures. */ public function revert_preset(int $presetappid): array { global $DB; // To store rollback results. $presetapp = null; $rollback = []; $failures = []; // Actual settings. $sitesettings = $this->get_site_settings(); if (!$DB->get_record('adminpresets_app', ['id' => $presetappid])) { throw new moodle_exception('wrongid', 'core_adminpresets'); } // Items. $itemsql = "SELECT cl.id, cl.plugin, cl.name, cl.value, cl.oldvalue, ap.adminpresetapplyid FROM {adminpresets_app_it} ap JOIN {config_log} cl ON cl.id = ap.configlogid WHERE ap.adminpresetapplyid = :presetid"; $itemchanges = $DB->get_records_sql($itemsql, ['presetid' => $presetappid]); if ($itemchanges) { foreach ($itemchanges as $change) { if ($change->plugin == '') { $change->plugin = 'none'; } // Admin setting. if (!empty($sitesettings[$change->plugin][$change->name])) { $actualsetting = $sitesettings[$change->plugin][$change->name]; $oldsetting = $this->get_setting($actualsetting->get_settingdata(), $change->oldvalue); $visiblepluginname = $oldsetting->get_settingdata()->plugin; if ($visiblepluginname == 'none') { $visiblepluginname = 'core'; } $contextdata = [ 'plugin' => $visiblepluginname, 'visiblename' => $oldsetting->get_settingdata()->visiblename, 'oldvisiblevalue' => $actualsetting->get_visiblevalue(), 'visiblevalue' => $oldsetting->get_visiblevalue() ]; // Check if the actual value is the same set by the preset. if ($change->value == $actualsetting->get_value()) { $oldsetting->save_value(); // Output table. $rollback[] = $contextdata; // Deleting the adminpreset applied item instance. $deletewhere = [ 'adminpresetapplyid' => $change->adminpresetapplyid, 'configlogid' => $change->id, ]; $DB->delete_records('adminpresets_app_it', $deletewhere); } else { $failures[] = $contextdata; } } } } // Attributes. $attrsql = "SELECT cl.id, cl.plugin, cl.name, cl.value, cl.oldvalue, ap.itemname, ap.adminpresetapplyid FROM {adminpresets_app_it_a} ap JOIN {config_log} cl ON cl.id = ap.configlogid WHERE ap.adminpresetapplyid = :presetid"; $attrchanges = $DB->get_records_sql($attrsql, ['presetid' => $presetappid]); if ($attrchanges) { foreach ($attrchanges as $change) { if ($change->plugin == '') { $change->plugin = 'none'; } // Admin setting of the attribute item. if (!empty($sitesettings[$change->plugin][$change->itemname])) { // Getting the attribute item. $actualsetting = $sitesettings[$change->plugin][$change->itemname]; $oldsetting = $this->get_setting($actualsetting->get_settingdata(), $actualsetting->get_value()); $oldsetting->set_attribute_value($change->name, $change->oldvalue); $varname = $change->plugin . '_' . $change->name; // Check if the actual value is the same set by the preset. $actualattributes = $actualsetting->get_attributes_values(); if ($change->value == $actualattributes[$change->name]) { $oldsetting->save_attributes_values(); // Output table. $visiblepluginname = $oldsetting->get_settingdata()->plugin; if ($visiblepluginname == 'none') { $visiblepluginname = 'core'; } $rollback[] = [ 'plugin' => $visiblepluginname, 'visiblename' => $oldsetting->get_settingdata()->visiblename, 'oldvisiblevalue' => $actualsetting->get_visiblevalue(), 'visiblevalue' => $oldsetting->get_visiblevalue() ]; // Deleting the adminpreset applied item attribute instance. $deletewhere = [ 'adminpresetapplyid' => $change->adminpresetapplyid, 'configlogid' => $change->id, ]; $DB->delete_records('adminpresets_app_it_a', $deletewhere); } else { $visiblepluginname = $oldsetting->get_settingdata()->plugin; if ($visiblepluginname == 'none') { $visiblepluginname = 'core'; } $failures[] = [ 'plugin' => $visiblepluginname, 'visiblename' => $oldsetting->get_settingdata()->visiblename, 'oldvisiblevalue' => $actualsetting->get_visiblevalue(), 'visiblevalue' => $oldsetting->get_visiblevalue() ]; } } } } // Plugins. $plugins = $DB->get_records('adminpresets_app_plug', ['adminpresetapplyid' => $presetappid]); if ($plugins) { $pluginmanager = \core_plugin_manager::instance(); foreach ($plugins as $plugin) { $pluginclass = \core_plugin_manager::resolve_plugininfo_class($plugin->plugin); $pluginclass::enable_plugin($plugin->name, (int) $plugin->oldvalue); // Get the plugininfo object for this plugin, to get its proper visible name. $plugininfo = $pluginmanager->get_plugin_info($plugin->plugin . '_' . $plugin->name); if ($plugininfo != null) { $visiblename = $plugininfo->displayname; } else { $visiblename = $plugin->plugin . '_' . $plugin->name; } // Output table. $rollback[] = [ 'plugin' => $plugin->plugin, 'visiblename' => $visiblename, 'oldvisiblevalue' => $plugin->value, 'visiblevalue' => $plugin->oldvalue, ]; } $DB->delete_records('adminpresets_app_plug', ['adminpresetapplyid' => $presetappid]); } // Delete application if no items nor attributes nor plugins of the application remains. if (!$DB->get_records('adminpresets_app_it', ['adminpresetapplyid' => $presetappid]) && !$DB->get_records('adminpresets_app_it_a', ['adminpresetapplyid' => $presetappid]) && !$DB->get_records('adminpresets_app_plug', ['adminpresetapplyid' => $presetappid])) { $presetapp = $DB->get_record('adminpresets_app', ['id' => $presetappid]); $DB->delete_records('adminpresets_app', ['id' => $presetappid]); } return [$presetapp, $rollback, $failures]; } /** * Apply settings from a preset. * * @param int $presetid The preset identifier to apply. * @param bool $simulate Whether this is a simulation or not. * @param int|null $adminpresetapplyid The identifier of the adminpresetapply or null if it hasn't been created previously. * @return array List with an array with the applied settings, another with the skipped ones and the adminpresetapplyid. */ protected function apply_settings(int $presetid, bool $simulate = false, ?int $adminpresetapplyid = null): array { global $DB, $USER; $applied = []; $skipped = []; if (!$items = $DB->get_records('adminpresets_it', ['adminpresetid' => $presetid])) { return [$applied, $skipped, $adminpresetapplyid]; } $presetdbsettings = $this->get_settings_from_db($items); // Standarized format: $array['plugin']['settingname'] = child class. $presetsettings = $this->get_settings($presetdbsettings, false, []); // Standarized format: $array['plugin']['settingname'] = child class. $siteavailablesettings = $this->get_site_settings(); // Set settings values. foreach ($presetsettings as $plugin => $pluginsettings) { foreach ($pluginsettings as $settingname => $presetsetting) { $updatesetting = false; // Current value (which will become old value if the setting is legit to be applied). $sitesetting = $siteavailablesettings[$plugin][$settingname]; // Wrong setting, set_value() method has previously cleaned the value. if ($sitesetting->get_value() === false) { debugging($presetsetting->get_settingdata()->plugin . '/' . $presetsetting->get_settingdata()->name . ' setting has a wrong value!', DEBUG_DEVELOPER); continue; } // If the new value is different the setting must be updated. if ($presetsetting->get_value() != $sitesetting->get_value()) { $updatesetting = true; } // If one of the setting attributes values is different, setting must also be updated. if ($presetsetting->get_attributes_values()) { $siteattributesvalues = $presetsetting->get_attributes_values(); foreach ($presetsetting->get_attributes_values() as $attributename => $attributevalue) { if ($attributevalue !== $siteattributesvalues[$attributename]) { $updatesetting = true; } } } $visiblepluginname = $presetsetting->get_settingdata()->plugin; if ($visiblepluginname == 'none') { $visiblepluginname = 'core'; } $data = [ 'plugin' => $visiblepluginname, 'visiblename' => $presetsetting->get_settingdata()->visiblename, 'visiblevalue' => $presetsetting->get_visiblevalue(), ]; // Saving data. if ($updatesetting) { // The preset application it's only saved when differences (in their values) are found. if (empty($applieditem)) { // Save the preset application and store the preset applied id. $presetapplied = new stdClass(); $presetapplied->adminpresetid = $presetid; $presetapplied->userid = $USER->id; $presetapplied->time = time(); if (!$simulate && !$adminpresetapplyid = $DB->insert_record('adminpresets_app', $presetapplied)) { throw new moodle_exception('errorinserting', 'core_adminpresets'); } } // Implemented this way because the config_write method of admin_setting class does not return the // config_log inserted id. $applieditem = new stdClass(); $applieditem->adminpresetapplyid = $adminpresetapplyid; if (!$simulate && $applieditem->configlogid = $presetsetting->save_value()) { $DB->insert_record('adminpresets_app_it', $applieditem); } // For settings with multiple values. if (!$simulate && $attributeslogids = $presetsetting->save_attributes_values()) { foreach ($attributeslogids as $attributelogid) { $applieditemattr = new stdClass(); $applieditemattr->adminpresetapplyid = $applieditem->adminpresetapplyid; $applieditemattr->configlogid = $attributelogid; $applieditemattr->itemname = $presetsetting->get_settingdata()->name; $DB->insert_record('adminpresets_app_it_a', $applieditemattr); } } // Added to changed values. $data['oldvisiblevalue'] = $sitesetting->get_visiblevalue(); $applied[] = $data; } else { // Unnecessary changes (actual setting value). $skipped[] = $data; } } } return [$applied, $skipped, $adminpresetapplyid]; } /** * Apply plugins from a preset. * * @param int $presetid The preset identifier to apply. * @param bool $simulate Whether this is a simulation or not. * @param int|null $adminpresetapplyid The identifier of the adminpresetapply or null if it hasn't been created previously. * @return array List with an array with the applied settings, another with the skipped ones and the adminpresetapplyid. */ protected function apply_plugins(int $presetid, bool $simulate = false, ?int $adminpresetapplyid = null): array { global $DB, $USER; $applied = []; $skipped = []; $strenabled = get_string('enabled', 'core_adminpresets'); $strdisabled = get_string('disabled', 'core_adminpresets'); $plugins = $DB->get_records('adminpresets_plug', ['adminpresetid' => $presetid]); $pluginmanager = \core_plugin_manager::instance(); foreach ($plugins as $plugin) { $pluginclass = \core_plugin_manager::resolve_plugininfo_class($plugin->plugin); $oldvalue = $pluginclass::get_enabled_plugin($plugin->name); // Get the plugininfo object for this plugin, to get its proper visible name. $plugininfo = $pluginmanager->get_plugin_info($plugin->plugin . '_' . $plugin->name); if ($plugininfo != null) { $visiblename = $plugininfo->displayname; } else { $visiblename = $plugin->plugin . '_' . $plugin->name; } if ($plugin->enabled > 0) { $visiblevalue = $strenabled; } else if ($plugin->enabled == 0) { $visiblevalue = $strdisabled; } else { $visiblevalue = get_string('disabledwithvalue', 'core_adminpresets', $plugin->enabled); } $data = [ 'plugin' => $plugin->plugin, 'visiblename' => $visiblename, 'visiblevalue' => $visiblevalue, ]; if ($pluginclass == '\core\plugininfo\orphaned') { $skipped[] = $data; continue; } // Only change the plugin visibility if it's different to current value. if (($plugin->enabled != $oldvalue) && (($plugin->enabled > 0 && !$oldvalue) || ($plugin->enabled < 1 && $oldvalue))) { try { if (!$simulate) { $pluginclass::enable_plugin($plugin->name, $plugin->enabled); // The preset application it's only saved when values differences are found. if (empty($adminpresetapplyid)) { // Save the preset application and store the preset applied id. $presetapplied = new stdClass(); $presetapplied->adminpresetid = $presetid; $presetapplied->userid = $USER->id; $presetapplied->time = time(); if (!$adminpresetapplyid = $DB->insert_record('adminpresets_app', $presetapplied)) { throw new moodle_exception('errorinserting', 'core_adminpresets'); } } // Add plugin to aplied plugins table (for being able to restore in the future if required). $appliedplug = new stdClass(); $appliedplug->adminpresetapplyid = $adminpresetapplyid; $appliedplug->plugin = $plugin->plugin; $appliedplug->name = $plugin->name; $appliedplug->value = $plugin->enabled; $appliedplug->oldvalue = $oldvalue; $DB->insert_record('adminpresets_app_plug', $appliedplug); } if ($oldvalue > 0) { $oldvisiblevalue = $strenabled; } else if ($oldvalue == 0) { $oldvisiblevalue = $strdisabled; } else { $oldvisiblevalue = get_string('disabledwithvalue', 'core_adminpresets', $oldvalue); } $data['oldvisiblevalue'] = $oldvisiblevalue; $applied[] = $data; } catch (\exception $e) { $skipped[] = $data; } } else { $skipped[] = $data; } } return [$applied, $skipped, $adminpresetapplyid]; } }