Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?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_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 \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 appropriate class to get the correct setting value.$settingtype = get_class($settingdata);// Check if it is a setting from a plugin.$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 (cannot delete the pre-installed core "Starter" and "Full" presets).$preset = $DB->get_record('adminpresets', ['id' => $presetid, 'iscore' => self::NONCORE_PRESET]);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.adminpresetapplyidFROM {adminpresets_app_it} apJOIN {config_log} cl ON cl.id = ap.configlogidWHERE 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.adminpresetapplyidFROM {adminpresets_app_it_a} apJOIN {config_log} cl ON cl.id = ap.configlogidWHERE 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];}}