Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_adminpresets;
  18  
  19  use memory_xml_output;
  20  use moodle_exception;
  21  use stdClass;
  22  use xml_writer;
  23  
  24  defined('MOODLE_INTERNAL') || die();
  25  
  26  global $CFG;
  27  require_once($CFG->libdir . '/adminlib.php');
  28  
  29  /**
  30   * Admin tool presets manager class.
  31   *
  32   * @package          core_adminpresets
  33   * @copyright        2021 Pimenko <support@pimenko.com><pimenko.com>
  34   * @author           Jordan Kesraoui | Sylvain Revenu | Pimenko based on David MonllaĆ³ <david.monllao@urv.cat> code
  35   * @license          http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class manager {
  38  
  39      /** @var \admin_root The admin root tree with the settings. **/
  40      private $adminroot;
  41  
  42      /** @var array Setting classes mapping, to associated the local/setting class that should be used when there is
  43       * no specific class. */
  44      protected static $settingclassesmap = [
  45              'adminpresets_admin_setting_agedigitalconsentmap' => 'adminpresets_admin_setting_configtext',
  46              'adminpresets_admin_setting_configcolourpicker' => 'adminpresets_admin_setting_configtext',
  47              'adminpresets_admin_setting_configdirectory' => 'adminpresets_admin_setting_configtext',
  48              'adminpresets_admin_setting_configduration_with_advanced' => 'adminpresets_admin_setting_configtext_with_advanced',
  49              'adminpresets_admin_setting_configduration' => 'adminpresets_admin_setting_configtext',
  50              'adminpresets_admin_setting_configempty' => 'adminpresets_admin_setting_configtext',
  51              'adminpresets_admin_setting_configexecutable' => 'adminpresets_admin_setting_configtext',
  52              'adminpresets_admin_setting_configfile' => 'adminpresets_admin_setting_configtext',
  53              'adminpresets_admin_setting_confightmleditor' => 'adminpresets_admin_setting_configtext',
  54              'adminpresets_admin_setting_configmixedhostiplist' => 'adminpresets_admin_setting_configtext',
  55              'adminpresets_admin_setting_configmultiselect_modules' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  56              'adminpresets_admin_setting_configpasswordunmask' => 'adminpresets_admin_setting_configtext',
  57              'adminpresets_admin_setting_configportlist' => 'adminpresets_admin_setting_configtext',
  58              'adminpresets_admin_setting_configselect_with_lock' => 'adminpresets_admin_setting_configselect',
  59              'adminpresets_admin_setting_configtext_trim_lower' => 'adminpresets_admin_setting_configtext',
  60              'adminpresets_admin_setting_configtext_with_maxlength' => 'adminpresets_admin_setting_configtext',
  61              'adminpresets_admin_setting_configtextarea' => 'adminpresets_admin_setting_configtext',
  62              'adminpresets_admin_setting_configthemepreset' => 'adminpresets_admin_setting_configselect',
  63              'adminpresets_admin_setting_countrycodes' => 'adminpresets_admin_setting_configtext',
  64              'adminpresets_admin_setting_courselist_frontpage' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  65              'adminpresets_admin_setting_description' => 'adminpresets_admin_setting_configtext',
  66              'adminpresets_admin_setting_enablemobileservice' => 'adminpresets_admin_setting_configcheckbox',
  67              'adminpresets_admin_setting_filetypes' => 'adminpresets_admin_setting_configtext',
  68              'adminpresets_admin_setting_forcetimezone' => 'adminpresets_admin_setting_configselect',
  69              'adminpresets_admin_setting_grade_profilereport' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  70              'adminpresets_admin_setting_langlist' => 'adminpresets_admin_setting_configtext',
  71              'adminpresets_admin_setting_my_grades_report' => 'adminpresets_admin_setting_configselect',
  72              'adminpresets_admin_setting_pickroles' => 'adminpresets_admin_setting_configmulticheckbox',
  73              'adminpresets_admin_setting_question_behaviour' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  74              'adminpresets_admin_setting_regradingcheckbox' => 'adminpresets_admin_setting_configcheckbox',
  75              'adminpresets_admin_setting_scsscode' => 'adminpresets_admin_setting_configtext',
  76              'adminpresets_admin_setting_servertimezone' => 'adminpresets_admin_setting_configselect',
  77              'adminpresets_admin_setting_sitesetcheckbox' => 'adminpresets_admin_setting_configcheckbox',
  78              'adminpresets_admin_setting_sitesetselect' => 'adminpresets_admin_setting_configselect',
  79              'adminpresets_admin_setting_special_adminseesall' => 'adminpresets_admin_setting_configcheckbox',
  80              'adminpresets_admin_setting_special_backup_auto_destination' => 'adminpresets_admin_setting_configtext',
  81              'adminpresets_admin_setting_special_coursecontact' => 'adminpresets_admin_setting_configmulticheckbox',
  82              'adminpresets_admin_setting_special_coursemanager' => 'adminpresets_admin_setting_configmulticheckbox',
  83              'adminpresets_admin_setting_special_debug' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  84              'adminpresets_admin_setting_special_frontpagedesc' => 'adminpresets_admin_setting_sitesettext',
  85              'adminpresets_admin_setting_special_gradebookroles' => 'adminpresets_admin_setting_configmulticheckbox',
  86              'adminpresets_admin_setting_special_gradeexport' => 'adminpresets_admin_setting_configmulticheckbox',
  87              'adminpresets_admin_setting_special_gradelimiting' => 'adminpresets_admin_setting_configcheckbox',
  88              'adminpresets_admin_setting_special_grademinmaxtouse' => 'adminpresets_admin_setting_configselect',
  89              'adminpresets_admin_setting_special_gradepointdefault' => 'adminpresets_admin_setting_configtext',
  90              'adminpresets_admin_setting_special_gradepointmax' => 'adminpresets_admin_setting_configtext',
  91              'adminpresets_admin_setting_special_registerauth' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  92              'adminpresets_admin_setting_special_selectsetup' => 'adminpresets_admin_setting_configselect',
  93              'adminpresets_admin_settings_country_select' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  94              'adminpresets_admin_settings_coursecat_select' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  95              'adminpresets_admin_settings_h5plib_handler_select' => 'adminpresets_admin_setting_configselect',
  96              'adminpresets_admin_settings_num_course_sections' => 'adminpresets_admin_setting_configmultiselect_with_loader',
  97              'adminpresets_admin_settings_sitepolicy_handler_select' => 'adminpresets_admin_setting_configselect',
  98              'adminpresets_antivirus_clamav_pathtounixsocket_setting' => 'adminpresets_admin_setting_configtext',
  99              'adminpresets_antivirus_clamav_runningmethod_setting' => 'adminpresets_admin_setting_configselect',
 100              'adminpresets_antivirus_clamav_tcpsockethost_setting' => 'adminpresets_admin_setting_configtext',
 101              'adminpresets_auth_db_admin_setting_special_auth_configtext' => 'adminpresets_admin_setting_configtext',
 102              'adminpresets_auth_ldap_admin_setting_special_lowercase_configtext' => 'adminpresets_admin_setting_configtext',
 103              'adminpresets_auth_ldap_admin_setting_special_ntlm_configtext' => 'adminpresets_admin_setting_configtext',
 104              'adminpresets_auth_shibboleth_admin_setting_convert_data' => 'adminpresets_admin_setting_configtext',
 105              'adminpresets_auth_shibboleth_admin_setting_special_idp_configtextarea' => 'adminpresets_admin_setting_configtext',
 106              'adminpresets_auth_shibboleth_admin_setting_special_wayf_select' => 'adminpresets_admin_setting_configselect',
 107              'adminpresets_editor_atto_toolbar_setting' => 'adminpresets_admin_setting_configtext',
 108              'adminpresets_editor_tinymce_json_setting_textarea' => 'adminpresets_admin_setting_configtext',
 109              'adminpresets_enrol_database_admin_setting_category' => 'adminpresets_admin_setting_configselect',
 110              'adminpresets_enrol_flatfile_role_setting' => 'adminpresets_admin_setting_configtext',
 111              'adminpresets_enrol_ldap_admin_setting_category' => 'adminpresets_admin_setting_configselect',
 112              'adminpresets_format_singleactivity_admin_setting_activitytype' => 'adminpresets_admin_setting_configselect',
 113              'adminpresets_qtype_multichoice_admin_setting_answernumbering' => 'adminpresets_admin_setting_configselect',
 114      ];
 115  
 116      /** @var array Relation between database fields and XML files. **/
 117      protected static $dbxmlrelations = [
 118          'name' => 'NAME',
 119          'comments' => 'COMMENTS',
 120          'timecreated' => 'PRESET_DATE',
 121          'site' => 'SITE_URL',
 122          'author' => 'AUTHOR',
 123          'moodleversion' => 'MOODLE_VERSION',
 124          'moodlerelease' => 'MOODLE_RELEASE'
 125      ];
 126  
 127      /** @var int Non-core preset */
 128      public const NONCORE_PRESET = 0;
 129  
 130      /** @var int Starter preset */
 131      public const STARTER_PRESET = 1;
 132  
 133      /** @var int Full preset */
 134      public const FULL_PRESET = 2;
 135  
 136      /**
 137       * Gets the system settings
 138       *
 139       * Loads the DB $CFG->prefix.'config' values and the
 140       * $CFG->prefix.'config_plugins' values and redirects
 141       * the flow through $this->get_settings()
 142       *
 143       * @return array $settings Array format $array['plugin']['settingname'] = settings_types child class
 144       */
 145      public function get_site_settings(): array {
 146          global $DB;
 147  
 148          // Db configs (to avoid multiple queries).
 149          $dbconfig = $DB->get_records_select('config', '', [], '', 'name, value');
 150  
 151          // Adding site settings in course table.
 152          $frontpagevalues = $DB->get_record_select('course', 'id = 1',
 153                  [], 'fullname, shortname, summary');
 154          foreach ($frontpagevalues as $field => $value) {
 155              $dbconfig[$field] = new stdClass();
 156              $dbconfig[$field]->name = $field;
 157              $dbconfig[$field]->value = $value;
 158          }
 159          $sitedbsettings['none'] = $dbconfig;
 160  
 161          // Config plugins.
 162          $configplugins = $DB->get_records('config_plugins');
 163          foreach ($configplugins as $configplugin) {
 164              $sitedbsettings[$configplugin->plugin][$configplugin->name] = new stdClass();
 165              $sitedbsettings[$configplugin->plugin][$configplugin->name]->name = $configplugin->name;
 166              $sitedbsettings[$configplugin->plugin][$configplugin->name]->value = $configplugin->value;
 167          }
 168          // Get an array with the common format.
 169          return $this->get_settings($sitedbsettings, true, []);
 170      }
 171  
 172      /**
 173       * Constructs an array with all the system settings
 174       *
 175       * If a setting value can't be found on the DB it considers
 176       * the default value as the setting value
 177       *
 178       * Settings without plugin are marked as 'none' in the plugin field
 179       *
 180       * Returns an standarized settings array format.
 181       *
 182       * @param array $dbsettings Standarized array,
 183       * format $array['plugin']['name'] = obj('name'=>'settingname', 'value'=>'settingvalue')
 184       * @param boolean $sitedbvalues Indicates if $dbsettings comes from the site db or not
 185       * @param array $settings Array format $array['plugin']['settingname'] = settings_types child class
 186       * @param array|false $children Array of admin_category children or false
 187       * @return    array Array format $array['plugin']['settingname'] = settings_types child class
 188       */
 189      public function get_settings(array $dbsettings, bool $sitedbvalues = false, array $settings = [], $children = false): array {
 190          global $DB;
 191  
 192          // If there are no children, load admin tree and iterate through.
 193          if (!$children) {
 194              $this->adminroot = admin_get_root(false, true);
 195              $children = $this->adminroot->children;
 196          }
 197  
 198          // Iteates through children.
 199          foreach ($children as $key => $child) {
 200  
 201              // We must search category children.
 202              if (is_a($child, 'admin_category')) {
 203  
 204                  if ($child->children) {
 205                      $settings = $this->get_settings($dbsettings, $sitedbvalues, $settings, $child->children);
 206                  }
 207  
 208                  // Settings page.
 209              } else if (is_a($child, 'admin_settingpage')) {
 210  
 211                  if (property_exists($child, 'settings')) {
 212  
 213                      foreach ($child->settings as $values) {
 214                          $settingname = $values->name;
 215  
 216                          unset($settingvalue);
 217  
 218                          // Look for his config value.
 219                          if ($values->plugin == '') {
 220                              $values->plugin = 'none';
 221                          }
 222  
 223                          if (!empty($dbsettings[$values->plugin][$settingname])) {
 224                              $settingvalue = $dbsettings[$values->plugin][$settingname]->value;
 225                          }
 226  
 227                          // If no db value found default value.
 228                          if ($sitedbvalues && !isset($settingvalue)) {
 229                              // For settings with multiple values.
 230                              if (is_array($values->defaultsetting)) {
 231  
 232                                  if (isset($values->defaultsetting['value'])) {
 233                                      $settingvalue = $values->defaultsetting['value'];
 234                                      // Configtime case, does not have a 'value' default setting.
 235                                  } else {
 236                                      $settingvalue = 0;
 237                                  }
 238                              } else {
 239                                  $settingvalue = $values->defaultsetting;
 240                              }
 241                          }
 242  
 243                          // If there aren't any value loaded, skip that setting.
 244                          if (!isset($settingvalue)) {
 245                              continue;
 246                          }
 247                          // If there is no setting class defined continue.
 248                          if (!$setting = $this->get_setting($values, $settingvalue)) {
 249                              continue;
 250                          }
 251  
 252                          // Settings_types childs with.
 253                          // attributes provides an attributes array.
 254                          if ($attributes = $setting->get_attributes()) {
 255  
 256                              // Look for settings attributes if it is a presets.
 257                              if (!$sitedbvalues) {
 258                                  $itemid = $dbsettings[$values->plugin][$settingname]->itemid;
 259                                  $attrs = $DB->get_records('adminpresets_it_a',
 260                                          ['itemid' => $itemid], '', 'name, value');
 261                              }
 262                              foreach ($attributes as $defaultvarname => $varname) {
 263  
 264                                  unset($attributevalue);
 265  
 266                                  // Settings from site.
 267                                  if ($sitedbvalues) {
 268                                      if (!empty($dbsettings[$values->plugin][$varname])) {
 269                                          $attributevalue = $dbsettings[$values->plugin][$varname]->value;
 270                                      }
 271  
 272                                      // Settings from a preset.
 273                                  } else if (!$sitedbvalues && isset($attrs[$varname])) {
 274                                      $attributevalue = $attrs[$varname]->value;
 275                                  }
 276  
 277                                  // If no value found, default value,
 278                                  // But we may not have a default value for the attribute.
 279                                  if (!isset($attributevalue) && !empty($values->defaultsetting[$defaultvarname])) {
 280                                      $attributevalue = $values->defaultsetting[$defaultvarname];
 281                                  }
 282  
 283                                  // If there is no even a default for this setting will be empty.
 284                                  // So we do nothing in this case.
 285                                  if (isset($attributevalue)) {
 286                                      $setting->set_attribute_value($varname, $attributevalue);
 287                                  }
 288                              }
 289                          }
 290  
 291                          // Adding to general settings array.
 292                          $settings[$values->plugin][$settingname] = $setting;
 293                      }
 294                  }
 295              }
 296          }
 297  
 298          return $settings;
 299      }
 300  
 301      /**
 302       * Returns the class type object
 303       *
 304       * @param object $settingdata Setting data
 305       * @param mixed $currentvalue
 306       * @return mixed
 307       */
 308      public function get_setting($settingdata, $currentvalue) {
 309  
 310          $classname = null;
 311  
 312          // Getting the appropiate class to get the correct setting value.
 313          $settingtype = get_class($settingdata);
 314  
 315          // Check if it is a setting from a plugin.
 316          $plugindata = explode('_', $settingtype);
 317          $types = \core_component::get_plugin_types();
 318          if (array_key_exists($plugindata[0], $types)) {
 319              $plugins = \core_component::get_plugin_list($plugindata[0]);
 320              if (array_key_exists($plugindata[1], $plugins)) {
 321                  // Check if there is a specific class for this plugin admin setting.
 322                  $settingname = 'adminpresets_' . $settingtype;
 323                  $classname = "\\$plugindata[0]_$plugindata[1]\\adminpresets\\$settingname";
 324                  if (!class_exists($classname)) {
 325                      $classname = null;
 326                  }
 327              }
 328          } else {
 329              $settingname = 'adminpresets_' . $settingtype;
 330              $classname = '\\core_adminpresets\\local\\setting\\' . $settingname;
 331              if (!class_exists($classname)) {
 332                  // Check if there is some mapped class that should be used for this setting.
 333                  $classname = self::get_settings_class($settingname);
 334              }
 335          }
 336  
 337          if (is_null($classname)) {
 338              // Return the default setting class if there is no specific class for this setting.
 339              $classname = '\\core_adminpresets\\local\\setting\\adminpresets_setting';
 340          }
 341  
 342          return new $classname($settingdata, $currentvalue);
 343      }
 344  
 345      /**
 346       * Returns the settings class mapped to the defined $classname or null if it doesn't exist any associated class.
 347       *
 348       * @param string $classname The classname to get the mapped class.
 349       * @return string|null
 350       */
 351      public static function get_settings_class(string $classname): ?string {
 352          if (array_key_exists($classname, self::$settingclassesmap)) {
 353              return '\\core_adminpresets\\local\\setting\\' . self::$settingclassesmap[$classname];
 354          }
 355  
 356          return null;
 357      }
 358  
 359      /**
 360       * Gets the standarized settings array from DB records
 361       *
 362       * @param array $dbsettings Array of objects
 363       * @return   array Standarized array,
 364       * format $array['plugin']['name'] = obj('name'=>'settingname', 'value'=>'settingvalue')
 365       */
 366      public function get_settings_from_db(array $dbsettings): array {
 367          $settings = [];
 368  
 369          if (!$dbsettings) {
 370              return $settings;
 371          }
 372  
 373          foreach ($dbsettings as $dbsetting) {
 374              $settings[$dbsetting->plugin][$dbsetting->name] = new stdClass();
 375              $settings[$dbsetting->plugin][$dbsetting->name]->itemid = $dbsetting->id;
 376              $settings[$dbsetting->plugin][$dbsetting->name]->name = $dbsetting->name;
 377              $settings[$dbsetting->plugin][$dbsetting->name]->value = $dbsetting->value;
 378          }
 379  
 380          return $settings;
 381      }
 382  
 383  
 384      /**
 385       * Apply a given preset.
 386       *
 387       * @param int $presetid The preset identifier to apply.
 388       * @param bool $simulate Whether this is a simulation or not.
 389       * @return array List with an array with the applied settings and another with the skipped ones.
 390       */
 391      public function apply_preset(int $presetid, bool $simulate = false): array {
 392          global $DB;
 393  
 394          if (!$DB->get_record('adminpresets', ['id' => $presetid])) {
 395              throw new moodle_exception('errornopreset', 'core_adminpresets');
 396          }
 397  
 398          // Apply preset settings.
 399          [$settingsapplied, $settingsskipped, $appid] = $this->apply_settings($presetid, $simulate);
 400  
 401          // Set plugins visibility.
 402          [$pluginsapplied, $pluginsskipped] = $this->apply_plugins($presetid, $simulate, $appid);
 403  
 404          $applied = array_merge($settingsapplied, $pluginsapplied);
 405          $skipped = array_merge($settingsskipped, $pluginsskipped);
 406  
 407          if (!$simulate) {
 408              // Store it in a config setting as the last preset applied.
 409              set_config('lastpresetapplied', $presetid, 'adminpresets');
 410          }
 411  
 412          return [$applied, $skipped];
 413      }
 414  
 415      /**
 416       * Create a preset with the current settings and plugins information.
 417       *
 418       * @param \stdClass $data Preset info, such as name or description, to be used when creating the preset with the current
 419       *                 settings and plugins.
 420       * @return array List with an the presetid created (int), a boolean to define if any setting has been found and
 421       *               another boolean to specify if any plugin has been found.
 422       */
 423      public function export_preset(stdClass $data): array {
 424          global $DB;
 425  
 426          // Admin_preset record.
 427          $presetdata = [
 428              'name' => $data->name ?? '',
 429              'comments' => !empty($data->comments) ? $data->comments['text'] : '',
 430              'author' => $data->author ?? '',
 431          ];
 432          if (!$presetid = helper::create_preset($presetdata)) {
 433              throw new moodle_exception('errorinserting', 'core_adminpresets');
 434          }
 435  
 436          // Store settings.
 437          $settingsfound = false;
 438  
 439          // Site settings.
 440          $sitesettings = $this->get_site_settings();
 441  
 442          // Sensible settings.
 443          $sensiblesettings = explode(',', str_replace(' ', '', get_config('adminpresets', 'sensiblesettings')));
 444          $sensiblesettings = array_combine($sensiblesettings, $sensiblesettings);
 445          foreach ($sitesettings as $plugin => $pluginsettings) {
 446              foreach ($pluginsettings as $settingname => $sitesetting) {
 447                  // Avoid sensible data.
 448                  if (empty($data->includesensiblesettings) && !empty($sensiblesettings["$settingname@@$plugin"])) {
 449                      continue;
 450                  }
 451  
 452                  $setting = new stdClass();
 453                  $setting->adminpresetid = $presetid;
 454                  $setting->plugin = $plugin;
 455                  $setting->name = $settingname;
 456                  $setting->value = $sitesetting->get_value();
 457                  if (!$setting->id = $DB->insert_record('adminpresets_it', $setting)) {
 458                      throw new moodle_exception('errorinserting', 'core_adminpresets');
 459                  }
 460  
 461                  // Setting attributes must also be exported.
 462                  if ($attributes = $sitesetting->get_attributes_values()) {
 463                      foreach ($attributes as $attname => $attvalue) {
 464                          $attr = new stdClass();
 465                          $attr->itemid = $setting->id;
 466                          $attr->name = $attname;
 467                          $attr->value = $attvalue;
 468  
 469                          $DB->insert_record('adminpresets_it_a', $attr);
 470                      }
 471                  }
 472                  $settingsfound = true;
 473              }
 474          }
 475  
 476          // Store plugins visibility (enabled/disabled).
 477          $pluginsfound = false;
 478          $pluginmanager = \core_plugin_manager::instance();
 479          $types = $pluginmanager->get_plugin_types();
 480          foreach ($types as $plugintype => $notused) {
 481              $plugins = $pluginmanager->get_present_plugins($plugintype);
 482              $pluginclass = \core_plugin_manager::resolve_plugininfo_class($plugintype);
 483              if (!empty($plugins)) {
 484                  foreach ($plugins as $pluginname => $plugin) {
 485                      $entry = new stdClass();
 486                      $entry->adminpresetid = $presetid;
 487                      $entry->plugin = $plugintype;
 488                      $entry->name = $pluginname;
 489                      $entry->enabled = $pluginclass::get_enabled_plugin($pluginname);
 490  
 491                      $DB->insert_record('adminpresets_plug', $entry);
 492                      $pluginsfound = true;
 493                  }
 494              }
 495          }
 496  
 497          // If there are no settings nor plugins, the admin preset record should be removed.
 498          if (!$settingsfound && !$pluginsfound) {
 499              $DB->delete_records('adminpresets', ['id' => $presetid]);
 500              $presetid = null;
 501          }
 502  
 503          return [$presetid, $settingsfound, $pluginsfound];
 504      }
 505  
 506      /**
 507       * Create the XML content for a given preset.
 508       *
 509       * @param int $presetid The preset to download.
 510       * @return array List with the XML content (string) and a filename proposal based on the preset name (string).
 511       */
 512      public function download_preset(int $presetid): array {
 513          global $DB;
 514  
 515          if (!$preset = $DB->get_record('adminpresets', ['id' => $presetid])) {
 516              throw new moodle_exception('errornopreset', 'core_adminpresets');
 517          }
 518  
 519          // Start.
 520          $xmloutput = new memory_xml_output();
 521          $xmlwriter = new xml_writer($xmloutput);
 522          $xmlwriter->start();
 523  
 524          // Preset data.
 525          $xmlwriter->begin_tag('PRESET');
 526          foreach (static::$dbxmlrelations as $dbname => $xmlname) {
 527              $xmlwriter->full_tag($xmlname, $preset->$dbname);
 528          }
 529  
 530          // We ride through the settings array.
 531          $items = $DB->get_records('adminpresets_it', ['adminpresetid' => $preset->id]);
 532          $allsettings = $this->get_settings_from_db($items);
 533          if ($allsettings) {
 534              $xmlwriter->begin_tag('ADMIN_SETTINGS');
 535  
 536              foreach ($allsettings as $plugin => $settings) {
 537                  $tagname = strtoupper($plugin);
 538  
 539                  // To aviod xml slash problems.
 540                  if (strstr($tagname, '/') != false) {
 541                      $tagname = str_replace('/', '__', $tagname);
 542                  }
 543  
 544                  $xmlwriter->begin_tag($tagname);
 545  
 546                  // One tag for each plugin setting.
 547                  if (!empty($settings)) {
 548                      $xmlwriter->begin_tag('SETTINGS');
 549                      foreach ($settings as $setting) {
 550                          // Unset the tag attributes string.
 551                          $attributes = [];
 552  
 553                          // Getting setting attributes, if present.
 554                          $attrs = $DB->get_records('adminpresets_it_a', ['itemid' => $setting->itemid]);
 555                          if ($attrs) {
 556                              foreach ($attrs as $attr) {
 557                                  $attributes[$attr->name] = $attr->value;
 558                              }
 559                          }
 560  
 561                          $xmlwriter->full_tag(strtoupper($setting->name), $setting->value, $attributes);
 562                      }
 563  
 564                      $xmlwriter->end_tag('SETTINGS');
 565                  }
 566  
 567                  $xmlwriter->end_tag(strtoupper($tagname));
 568              }
 569  
 570              $xmlwriter->end_tag('ADMIN_SETTINGS');
 571          }
 572  
 573          // We ride through the plugins array.
 574          $data = $DB->get_records('adminpresets_plug', ['adminpresetid' => $preset->id]);
 575          if ($data) {
 576              $plugins = [];
 577              foreach ($data as $plugin) {
 578                  $plugins[$plugin->plugin][] = $plugin;
 579              }
 580  
 581              $xmlwriter->begin_tag('PLUGINS');
 582  
 583              foreach ($plugins as $plugintype => $plugintypes) {
 584                  $tagname = strtoupper($plugintype);
 585                  $xmlwriter->begin_tag($tagname);
 586  
 587                  foreach ($plugintypes as $plugin) {
 588                      $xmlwriter->full_tag(strtoupper($plugin->name), $plugin->enabled);
 589                  }
 590  
 591                  $xmlwriter->end_tag(strtoupper($tagname));
 592              }
 593  
 594              $xmlwriter->end_tag('PLUGINS');
 595          }
 596  
 597          // End.
 598          $xmlwriter->end_tag('PRESET');
 599          $xmlwriter->stop();
 600          $xmlstr = $xmloutput->get_allcontents();
 601  
 602          $filename = addcslashes($preset->name, '"') . '.xml';
 603  
 604          return [$xmlstr, $filename];
 605      }
 606  
 607      /**
 608       * Import a given XML preset.
 609       *
 610       * @param string $xmlcontent The XML context with the preset to be imported.
 611       * @param string|null $presetname The preset name that will overwrite the one given in the XML file.
 612       * @return array List with an the XML element (SimpleXMLElement|null), the imported preset (stdClass|null), a boolean
 613       *               to define if any setting has been found and another boolean to specify if any plugin has been found.
 614       */
 615      public function import_preset(string $xmlcontent, ?string $presetname = null): array {
 616          global $DB, $USER;
 617  
 618          $settingsfound = false;
 619          $pluginsfound = false;
 620  
 621          try {
 622              $xml = simplexml_load_string($xmlcontent);
 623          } catch (\Exception $exception) {
 624              $xml = false;
 625          }
 626          if (!$xml) {
 627              return [null, null, $settingsfound, $pluginsfound];
 628          }
 629  
 630          // Prepare the preset info.
 631          $preset = new stdClass();
 632          foreach (static::$dbxmlrelations as $dbname => $xmlname) {
 633              $preset->$dbname = (String) $xml->$xmlname;
 634          }
 635          $preset->userid = $USER->id;
 636          $preset->timeimported = time();
 637  
 638          // Overwrite preset name.
 639          if (!empty($presetname)) {
 640              $preset->name = $presetname;
 641          }
 642  
 643          // Create the preset.
 644          if (!$preset->id = $DB->insert_record('adminpresets', $preset)) {
 645              throw new moodle_exception('errorinserting', 'core_adminpresets');
 646          }
 647  
 648          // Process settings.
 649          $sitesettings = $this->get_site_settings();
 650          $xmladminsettings = $xml->ADMIN_SETTINGS[0];
 651          foreach ($xmladminsettings as $plugin => $settings) {
 652              $plugin = strtolower($plugin);
 653              if (strstr($plugin, '__') != false) {
 654                  $plugin = str_replace('__', '/', $plugin);
 655              }
 656  
 657              $pluginsettings = $settings->SETTINGS[0];
 658              if ($pluginsettings) {
 659                  foreach ($pluginsettings->children() as $name => $setting) {
 660                      $name = strtolower($name);
 661  
 662                      // Default to ''.
 663                      if ($setting->__toString() === false) {
 664                          $value = '';
 665                      } else {
 666                          $value = $setting->__toString();
 667                      }
 668  
 669                      if (empty($sitesettings[$plugin][$name])) {
 670                          debugging('Setting ' . $plugin . '/' . $name . ' not supported by this Moodle version', DEBUG_DEVELOPER);
 671                          continue;
 672                      }
 673  
 674                      // Cleaning the setting value.
 675                      if (!$presetsetting = $this->get_setting($sitesettings[$plugin][$name]->get_settingdata(), $value)) {
 676                          debugging('Setting ' . $plugin . '/' . $name . ' not implemented', DEBUG_DEVELOPER);
 677                          continue;
 678                      }
 679  
 680                      $settingsfound = true;
 681  
 682                      // New item.
 683                      $item = new stdClass();
 684                      $item->adminpresetid = $preset->id;
 685                      $item->plugin = $plugin;
 686                      $item->name = $name;
 687                      $item->value = $presetsetting->get_value();
 688  
 689                      // Insert preset item.
 690                      if (!$item->id = $DB->insert_record('adminpresets_it', $item)) {
 691                          throw new moodle_exception('errorinserting', 'core_adminpresets');
 692                      }
 693  
 694                      // Add setting attributes.
 695                      if ($setting->attributes() && ($itemattributes = $presetsetting->get_attributes())) {
 696                          foreach ($setting->attributes() as $attrname => $attrvalue) {
 697                              $itemattributenames = array_flip($itemattributes);
 698  
 699                              // Check the attribute existence.
 700                              if (!isset($itemattributenames[$attrname])) {
 701                                  debugging('The ' . $plugin . '/' . $name . ' attribute ' . $attrname .
 702                                          ' is not supported by this Moodle version', DEBUG_DEVELOPER);
 703                                  continue;
 704                              }
 705  
 706                              $attr = new stdClass();
 707                              $attr->itemid = $item->id;
 708                              $attr->name = $attrname;
 709                              $attr->value = $attrvalue->__toString();
 710                              $DB->insert_record('adminpresets_it_a', $attr);
 711                          }
 712                      }
 713                  }
 714              }
 715          }
 716  
 717          // Process plugins.
 718          if ($xml->PLUGINS) {
 719              $xmlplugins = $xml->PLUGINS[0];
 720              foreach ($xmlplugins as $plugin => $plugins) {
 721                  $pluginname = strtolower($plugin);
 722                  foreach ($plugins->children() as $name => $plugin) {
 723                      $pluginsfound = true;
 724  
 725                      // New plugin.
 726                      $entry = new stdClass();
 727                      $entry->adminpresetid = $preset->id;
 728                      $entry->plugin = $pluginname;
 729                      $entry->name = strtolower($name);
 730                      $entry->enabled = $plugin->__toString();
 731  
 732                      // Insert plugin.
 733                      if (!$entry->id = $DB->insert_record('adminpresets_plug', $entry)) {
 734                          throw new moodle_exception('errorinserting', 'core_adminpresets');
 735                      }
 736                  }
 737              }
 738          }
 739  
 740          // If there are no valid or selected settings we should delete the admin preset record.
 741          if (!$settingsfound && !$pluginsfound) {
 742              $DB->delete_records('adminpresets', ['id' => $preset->id]);
 743              $preset = null;
 744          }
 745  
 746          return [$xml, $preset, $settingsfound, $pluginsfound];
 747      }
 748  
 749      /**
 750       * Delete given preset.
 751       *
 752       * @param int $presetid Preset identifier to delete.
 753       * @return void
 754       */
 755      public function delete_preset(int $presetid): void {
 756          global $DB;
 757  
 758          // Check the preset exists.
 759          $preset = $DB->get_record('adminpresets', ['id' => $presetid]);
 760          if (!$preset) {
 761              throw new moodle_exception('errordeleting', 'core_adminpresets');
 762          }
 763  
 764          // Deleting the preset applications.
 765          if ($previouslyapplied = $DB->get_records('adminpresets_app', ['adminpresetid' => $presetid], 'id')) {
 766              $appids = array_keys($previouslyapplied);
 767              list($insql, $inparams) = $DB->get_in_or_equal($appids);
 768              $DB->delete_records_select('adminpresets_app_it', "adminpresetapplyid $insql", $inparams);
 769              $DB->delete_records_select('adminpresets_app_it_a', "adminpresetapplyid $insql", $inparams);
 770              $DB->delete_records_select('adminpresets_app_plug', "adminpresetapplyid $insql", $inparams);
 771  
 772              if (!$DB->delete_records('adminpresets_app', ['adminpresetid' => $presetid])) {
 773                  throw new moodle_exception('errordeleting', 'core_adminpresets');
 774              }
 775          }
 776  
 777          // Getting items ids and remove advanced items associated to them.
 778          $items = $DB->get_records('adminpresets_it', ['adminpresetid' => $presetid], 'id');
 779          if (!empty($items)) {
 780              $itemsid = array_keys($items);
 781              list($insql, $inparams) = $DB->get_in_or_equal($itemsid);
 782              $DB->delete_records_select('adminpresets_it_a', "itemid $insql", $inparams);
 783          }
 784  
 785          if (!$DB->delete_records('adminpresets_it', ['adminpresetid' => $presetid])) {
 786              throw new moodle_exception('errordeleting', 'core_adminpresets');
 787          }
 788  
 789          // Delete plugins.
 790          if (!$DB->delete_records('adminpresets_plug', ['adminpresetid' => $presetid])) {
 791              throw new moodle_exception('errordeleting', 'core_adminpresets');
 792          }
 793  
 794          // Delete preset.
 795          if (!$DB->delete_records('adminpresets', ['id' => $presetid])) {
 796              throw new moodle_exception('errordeleting', 'core_adminpresets');
 797          }
 798      }
 799  
 800      /**
 801       * Revert a given preset applied previously.
 802       * It backs settings and plugins to their original state before applying the presset and removes
 803       * the applied preset information from DB.
 804       *
 805       * @param int $presetappid The appplied preset identifier to be reverted.
 806       * @return array List with the presetapp removed (or null if there was some error), an array with the rollback settings/plugins
 807       *               changed and an array with the failures.
 808       */
 809      public function revert_preset(int $presetappid): array {
 810          global $DB;
 811  
 812          // To store rollback results.
 813          $presetapp = null;
 814          $rollback = [];
 815          $failures = [];
 816  
 817          // Actual settings.
 818          $sitesettings = $this->get_site_settings();
 819  
 820          if (!$DB->get_record('adminpresets_app', ['id' => $presetappid])) {
 821              throw new moodle_exception('wrongid', 'core_adminpresets');
 822          }
 823  
 824          // Items.
 825          $itemsql = "SELECT cl.id, cl.plugin, cl.name, cl.value, cl.oldvalue, ap.adminpresetapplyid
 826                        FROM {adminpresets_app_it} ap
 827                        JOIN {config_log} cl ON cl.id = ap.configlogid
 828                       WHERE ap.adminpresetapplyid = :presetid";
 829          $itemchanges = $DB->get_records_sql($itemsql, ['presetid' => $presetappid]);
 830          if ($itemchanges) {
 831              foreach ($itemchanges as $change) {
 832                  if ($change->plugin == '') {
 833                      $change->plugin = 'none';
 834                  }
 835  
 836                  // Admin setting.
 837                  if (!empty($sitesettings[$change->plugin][$change->name])) {
 838                      $actualsetting = $sitesettings[$change->plugin][$change->name];
 839                      $oldsetting = $this->get_setting($actualsetting->get_settingdata(), $change->oldvalue);
 840  
 841                      $visiblepluginname = $oldsetting->get_settingdata()->plugin;
 842                      if ($visiblepluginname == 'none') {
 843                          $visiblepluginname = 'core';
 844                      }
 845                      $contextdata = [
 846                          'plugin' => $visiblepluginname,
 847                          'visiblename' => $oldsetting->get_settingdata()->visiblename,
 848                          'oldvisiblevalue' => $actualsetting->get_visiblevalue(),
 849                          'visiblevalue' => $oldsetting->get_visiblevalue()
 850                      ];
 851  
 852                      // Check if the actual value is the same set by the preset.
 853                      if ($change->value == $actualsetting->get_value()) {
 854                          $oldsetting->save_value();
 855  
 856                          // Output table.
 857                          $rollback[] = $contextdata;
 858  
 859                          // Deleting the adminpreset applied item instance.
 860                          $deletewhere = [
 861                              'adminpresetapplyid' => $change->adminpresetapplyid,
 862                              'configlogid' => $change->id,
 863                          ];
 864                          $DB->delete_records('adminpresets_app_it', $deletewhere);
 865  
 866                      } else {
 867                          $failures[] = $contextdata;
 868                      }
 869                  }
 870              }
 871          }
 872  
 873          // Attributes.
 874          $attrsql = "SELECT cl.id, cl.plugin, cl.name, cl.value, cl.oldvalue, ap.itemname, ap.adminpresetapplyid
 875                        FROM {adminpresets_app_it_a} ap
 876                        JOIN {config_log} cl ON cl.id = ap.configlogid
 877                       WHERE ap.adminpresetapplyid = :presetid";
 878          $attrchanges = $DB->get_records_sql($attrsql, ['presetid' => $presetappid]);
 879          if ($attrchanges) {
 880              foreach ($attrchanges as $change) {
 881                  if ($change->plugin == '') {
 882                      $change->plugin = 'none';
 883                  }
 884  
 885                  // Admin setting of the attribute item.
 886                  if (!empty($sitesettings[$change->plugin][$change->itemname])) {
 887                      // Getting the attribute item.
 888                      $actualsetting = $sitesettings[$change->plugin][$change->itemname];
 889  
 890                      $oldsetting = $this->get_setting($actualsetting->get_settingdata(), $actualsetting->get_value());
 891                      $oldsetting->set_attribute_value($change->name, $change->oldvalue);
 892  
 893                      $varname = $change->plugin . '_' . $change->name;
 894  
 895                      // Check if the actual value is the same set by the preset.
 896                      $actualattributes = $actualsetting->get_attributes_values();
 897                      if ($change->value == $actualattributes[$change->name]) {
 898                          $oldsetting->save_attributes_values();
 899  
 900                          // Output table.
 901                          $visiblepluginname = $oldsetting->get_settingdata()->plugin;
 902                          if ($visiblepluginname == 'none') {
 903                              $visiblepluginname = 'core';
 904                          }
 905                          $rollback[] = [
 906                              'plugin' => $visiblepluginname,
 907                              'visiblename' => $oldsetting->get_settingdata()->visiblename,
 908                              'oldvisiblevalue' => $actualsetting->get_visiblevalue(),
 909                              'visiblevalue' => $oldsetting->get_visiblevalue()
 910                          ];
 911  
 912                          // Deleting the adminpreset applied item attribute instance.
 913                          $deletewhere = [
 914                              'adminpresetapplyid' => $change->adminpresetapplyid,
 915                              'configlogid' => $change->id,
 916                          ];
 917                          $DB->delete_records('adminpresets_app_it_a', $deletewhere);
 918  
 919                      } else {
 920                          $visiblepluginname = $oldsetting->get_settingdata()->plugin;
 921                          if ($visiblepluginname == 'none') {
 922                              $visiblepluginname = 'core';
 923                          }
 924                          $failures[] = [
 925                              'plugin' => $visiblepluginname,
 926                              'visiblename' => $oldsetting->get_settingdata()->visiblename,
 927                              'oldvisiblevalue' => $actualsetting->get_visiblevalue(),
 928                              'visiblevalue' => $oldsetting->get_visiblevalue()
 929                          ];
 930                      }
 931                  }
 932              }
 933          }
 934  
 935          // Plugins.
 936          $plugins = $DB->get_records('adminpresets_app_plug', ['adminpresetapplyid' => $presetappid]);
 937          if ($plugins) {
 938              $pluginmanager = \core_plugin_manager::instance();
 939              foreach ($plugins as $plugin) {
 940                  $pluginclass = \core_plugin_manager::resolve_plugininfo_class($plugin->plugin);
 941                  $pluginclass::enable_plugin($plugin->name, (int) $plugin->oldvalue);
 942  
 943                  // Get the plugininfo object for this plugin, to get its proper visible name.
 944                  $plugininfo = $pluginmanager->get_plugin_info($plugin->plugin . '_' . $plugin->name);
 945                  if ($plugininfo != null) {
 946                      $visiblename = $plugininfo->displayname;
 947                  } else {
 948                      $visiblename = $plugin->plugin . '_' . $plugin->name;
 949                  }
 950  
 951                  // Output table.
 952                  $rollback[] = [
 953                      'plugin' => $plugin->plugin,
 954                      'visiblename' => $visiblename,
 955                      'oldvisiblevalue' => $plugin->value,
 956                      'visiblevalue' => $plugin->oldvalue,
 957                  ];
 958              }
 959              $DB->delete_records('adminpresets_app_plug', ['adminpresetapplyid' => $presetappid]);
 960          }
 961  
 962          // Delete application if no items nor attributes nor plugins of the application remains.
 963          if (!$DB->get_records('adminpresets_app_it', ['adminpresetapplyid' => $presetappid]) &&
 964                  !$DB->get_records('adminpresets_app_it_a', ['adminpresetapplyid' => $presetappid]) &&
 965                  !$DB->get_records('adminpresets_app_plug', ['adminpresetapplyid' => $presetappid])) {
 966  
 967              $presetapp = $DB->get_record('adminpresets_app', ['id' => $presetappid]);
 968              $DB->delete_records('adminpresets_app', ['id' => $presetappid]);
 969          }
 970  
 971          return [$presetapp, $rollback, $failures];
 972      }
 973  
 974      /**
 975       * Apply settings from a preset.
 976       *
 977       * @param int $presetid The preset identifier to apply.
 978       * @param bool $simulate Whether this is a simulation or not.
 979       * @param int|null $adminpresetapplyid The identifier of the adminpresetapply or null if it hasn't been created previously.
 980       * @return array List with an array with the applied settings, another with the skipped ones and the adminpresetapplyid.
 981       */
 982      protected function apply_settings(int $presetid, bool $simulate = false, ?int $adminpresetapplyid = null): array {
 983          global $DB, $USER;
 984  
 985          $applied = [];
 986          $skipped = [];
 987          if (!$items = $DB->get_records('adminpresets_it', ['adminpresetid' => $presetid])) {
 988              return [$applied, $skipped, $adminpresetapplyid];
 989          }
 990  
 991          $presetdbsettings = $this->get_settings_from_db($items);
 992          // Standarized format: $array['plugin']['settingname'] = child class.
 993          $presetsettings = $this->get_settings($presetdbsettings, false, []);
 994  
 995          // Standarized format: $array['plugin']['settingname'] = child class.
 996          $siteavailablesettings = $this->get_site_settings();
 997  
 998          // Set settings values.
 999          foreach ($presetsettings as $plugin => $pluginsettings) {
1000              foreach ($pluginsettings as $settingname => $presetsetting) {
1001                  $updatesetting = false;
1002  
1003                  // Current value (which will become old value if the setting is legit to be applied).
1004                  $sitesetting = $siteavailablesettings[$plugin][$settingname];
1005  
1006                  // Wrong setting, set_value() method has previously cleaned the value.
1007                  if ($sitesetting->get_value() === false) {
1008                      debugging($presetsetting->get_settingdata()->plugin . '/' . $presetsetting->get_settingdata()->name .
1009                              ' setting has a wrong value!', DEBUG_DEVELOPER);
1010                      continue;
1011                  }
1012  
1013                  // If the new value is different the setting must be updated.
1014                  if ($presetsetting->get_value() != $sitesetting->get_value()) {
1015                      $updatesetting = true;
1016                  }
1017  
1018                  // If one of the setting attributes values is different, setting must also be updated.
1019                  if ($presetsetting->get_attributes_values()) {
1020  
1021                      $siteattributesvalues = $presetsetting->get_attributes_values();
1022                      foreach ($presetsetting->get_attributes_values() as $attributename => $attributevalue) {
1023  
1024                          if ($attributevalue !== $siteattributesvalues[$attributename]) {
1025                              $updatesetting = true;
1026                          }
1027                      }
1028                  }
1029  
1030                  $visiblepluginname = $presetsetting->get_settingdata()->plugin;
1031                  if ($visiblepluginname == 'none') {
1032                      $visiblepluginname = 'core';
1033                  }
1034                  $data = [
1035                      'plugin' => $visiblepluginname,
1036                      'visiblename' => $presetsetting->get_settingdata()->visiblename,
1037                      'visiblevalue' => $presetsetting->get_visiblevalue(),
1038                  ];
1039  
1040                  // Saving data.
1041                  if ($updatesetting) {
1042                      // The preset application it's only saved when differences (in their values) are found.
1043                      if (empty($applieditem)) {
1044                          // Save the preset application and store the preset applied id.
1045                          $presetapplied = new stdClass();
1046                          $presetapplied->adminpresetid = $presetid;
1047                          $presetapplied->userid = $USER->id;
1048                          $presetapplied->time = time();
1049                          if (!$simulate && !$adminpresetapplyid = $DB->insert_record('adminpresets_app', $presetapplied)) {
1050                              throw new moodle_exception('errorinserting', 'core_adminpresets');
1051                          }
1052                      }
1053  
1054                      // Implemented this way because the config_write method of admin_setting class does not return the
1055                      // config_log inserted id.
1056                      $applieditem = new stdClass();
1057                      $applieditem->adminpresetapplyid = $adminpresetapplyid;
1058                      if (!$simulate && $applieditem->configlogid = $presetsetting->save_value()) {
1059                          $DB->insert_record('adminpresets_app_it', $applieditem);
1060                      }
1061  
1062                      // For settings with multiple values.
1063                      if (!$simulate && $attributeslogids = $presetsetting->save_attributes_values()) {
1064                          foreach ($attributeslogids as $attributelogid) {
1065                              $applieditemattr = new stdClass();
1066                              $applieditemattr->adminpresetapplyid = $applieditem->adminpresetapplyid;
1067                              $applieditemattr->configlogid = $attributelogid;
1068                              $applieditemattr->itemname = $presetsetting->get_settingdata()->name;
1069                              $DB->insert_record('adminpresets_app_it_a', $applieditemattr);
1070                          }
1071                      }
1072  
1073                      // Added to changed values.
1074                      $data['oldvisiblevalue'] = $sitesetting->get_visiblevalue();
1075                      $applied[] = $data;
1076                  } else {
1077                      // Unnecessary changes (actual setting value).
1078                      $skipped[] = $data;
1079                  }
1080              }
1081          }
1082          return [$applied, $skipped, $adminpresetapplyid];
1083      }
1084  
1085      /**
1086       * Apply plugins from a preset.
1087       *
1088       * @param int $presetid The preset identifier to apply.
1089       * @param bool $simulate Whether this is a simulation or not.
1090       * @param int|null $adminpresetapplyid The identifier of the adminpresetapply or null if it hasn't been created previously.
1091       * @return array List with an array with the applied settings, another with the skipped ones and the adminpresetapplyid.
1092       */
1093      protected function apply_plugins(int $presetid, bool $simulate = false, ?int $adminpresetapplyid = null): array {
1094          global $DB, $USER;
1095  
1096          $applied = [];
1097          $skipped = [];
1098  
1099          $strenabled = get_string('enabled', 'core_adminpresets');
1100          $strdisabled = get_string('disabled', 'core_adminpresets');
1101  
1102          $plugins = $DB->get_records('adminpresets_plug', ['adminpresetid' => $presetid]);
1103          $pluginmanager = \core_plugin_manager::instance();
1104          foreach ($plugins as $plugin) {
1105              $pluginclass = \core_plugin_manager::resolve_plugininfo_class($plugin->plugin);
1106              $oldvalue = $pluginclass::get_enabled_plugin($plugin->name);
1107  
1108              // Get the plugininfo object for this plugin, to get its proper visible name.
1109              $plugininfo = $pluginmanager->get_plugin_info($plugin->plugin . '_' . $plugin->name);
1110              if ($plugininfo != null) {
1111                  $visiblename = $plugininfo->displayname;
1112              } else {
1113                  $visiblename = $plugin->plugin . '_' . $plugin->name;
1114              }
1115  
1116              if ($plugin->enabled > 0) {
1117                  $visiblevalue = $strenabled;
1118              } else if ($plugin->enabled == 0) {
1119                  $visiblevalue = $strdisabled;
1120              } else {
1121                  $visiblevalue = get_string('disabledwithvalue', 'core_adminpresets', $plugin->enabled);
1122              }
1123  
1124              $data = [
1125                  'plugin' => $plugin->plugin,
1126                  'visiblename' => $visiblename,
1127                  'visiblevalue' => $visiblevalue,
1128              ];
1129  
1130              if ($pluginclass == '\core\plugininfo\orphaned') {
1131                  $skipped[] = $data;
1132                  continue;
1133              }
1134  
1135              // Only change the plugin visibility if it's different to current value.
1136              if (($plugin->enabled != $oldvalue) && (($plugin->enabled > 0 && !$oldvalue) || ($plugin->enabled < 1 && $oldvalue))) {
1137                  try {
1138                      if (!$simulate) {
1139                          $pluginclass::enable_plugin($plugin->name, $plugin->enabled);
1140  
1141                          // The preset application it's only saved when values differences are found.
1142                          if (empty($adminpresetapplyid)) {
1143                              // Save the preset application and store the preset applied id.
1144                              $presetapplied = new stdClass();
1145                              $presetapplied->adminpresetid = $presetid;
1146                              $presetapplied->userid = $USER->id;
1147                              $presetapplied->time = time();
1148                              if (!$adminpresetapplyid = $DB->insert_record('adminpresets_app', $presetapplied)) {
1149                                  throw new moodle_exception('errorinserting', 'core_adminpresets');
1150                              }
1151                          }
1152  
1153                          // Add plugin to aplied plugins table (for being able to restore in the future if required).
1154                          $appliedplug = new stdClass();
1155                          $appliedplug->adminpresetapplyid = $adminpresetapplyid;
1156                          $appliedplug->plugin = $plugin->plugin;
1157                          $appliedplug->name = $plugin->name;
1158                          $appliedplug->value = $plugin->enabled;
1159                          $appliedplug->oldvalue = $oldvalue;
1160                          $DB->insert_record('adminpresets_app_plug', $appliedplug);
1161                      }
1162  
1163                      if ($oldvalue > 0) {
1164                          $oldvisiblevalue = $strenabled;
1165                      } else if ($oldvalue == 0) {
1166                          $oldvisiblevalue = $strdisabled;
1167                      } else {
1168                          $oldvisiblevalue = get_string('disabledwithvalue', 'core_adminpresets', $oldvalue);
1169                      }
1170                      $data['oldvisiblevalue'] = $oldvisiblevalue;
1171                      $applied[] = $data;
1172                  } catch (\exception $e) {
1173                      $skipped[] = $data;
1174                  }
1175              } else {
1176                  $skipped[] = $data;
1177              }
1178          }
1179  
1180          return [$applied, $skipped, $adminpresetapplyid];
1181      }
1182  
1183  }