Differences Between: [Versions 400 and 402] [Versions 400 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body