Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 38 and 311] [Versions 39 and 311]
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 /** 18 * Functions and classes used during installation, upgrades and for admin settings. 19 * 20 * ADMIN SETTINGS TREE INTRODUCTION 21 * 22 * This file performs the following tasks: 23 * -it defines the necessary objects and interfaces to build the Moodle 24 * admin hierarchy 25 * -it defines the admin_externalpage_setup() 26 * 27 * ADMIN_SETTING OBJECTS 28 * 29 * Moodle settings are represented by objects that inherit from the admin_setting 30 * class. These objects encapsulate how to read a setting, how to write a new value 31 * to a setting, and how to appropriately display the HTML to modify the setting. 32 * 33 * ADMIN_SETTINGPAGE OBJECTS 34 * 35 * The admin_setting objects are then grouped into admin_settingpages. The latter 36 * appear in the Moodle admin tree block. All interaction with admin_settingpage 37 * objects is handled by the admin/settings.php file. 38 * 39 * ADMIN_EXTERNALPAGE OBJECTS 40 * 41 * There are some settings in Moodle that are too complex to (efficiently) handle 42 * with admin_settingpages. (Consider, for example, user management and displaying 43 * lists of users.) In this case, we use the admin_externalpage object. This object 44 * places a link to an external PHP file in the admin tree block. 45 * 46 * If you're using an admin_externalpage object for some settings, you can take 47 * advantage of the admin_externalpage_* functions. For example, suppose you wanted 48 * to add a foo.php file into admin. First off, you add the following line to 49 * admin/settings/first.php (at the end of the file) or to some other file in 50 * admin/settings: 51 * <code> 52 * $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'), 53 * $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission')); 54 * </code> 55 * 56 * Next, in foo.php, your file structure would resemble the following: 57 * <code> 58 * require(__DIR__.'/../../config.php'); 59 * require_once($CFG->libdir.'/adminlib.php'); 60 * admin_externalpage_setup('foo'); 61 * // functionality like processing form submissions goes here 62 * echo $OUTPUT->header(); 63 * // your HTML goes here 64 * echo $OUTPUT->footer(); 65 * </code> 66 * 67 * The admin_externalpage_setup() function call ensures the user is logged in, 68 * and makes sure that they have the proper role permission to access the page. 69 * It also configures all $PAGE properties needed for navigation. 70 * 71 * ADMIN_CATEGORY OBJECTS 72 * 73 * Above and beyond all this, we have admin_category objects. These objects 74 * appear as folders in the admin tree block. They contain admin_settingpage's, 75 * admin_externalpage's, and other admin_category's. 76 * 77 * OTHER NOTES 78 * 79 * admin_settingpage's, admin_externalpage's, and admin_category's all inherit 80 * from part_of_admin_tree (a pseudointerface). This interface insists that 81 * a class has a check_access method for access permissions, a locate method 82 * used to find a specific node in the admin tree and find parent path. 83 * 84 * admin_category's inherit from parentable_part_of_admin_tree. This pseudo- 85 * interface ensures that the class implements a recursive add function which 86 * accepts a part_of_admin_tree object and searches for the proper place to 87 * put it. parentable_part_of_admin_tree implies part_of_admin_tree. 88 * 89 * Please note that the $this->name field of any part_of_admin_tree must be 90 * UNIQUE throughout the ENTIRE admin tree. 91 * 92 * The $this->name field of an admin_setting object (which is *not* part_of_ 93 * admin_tree) must be unique on the respective admin_settingpage where it is 94 * used. 95 * 96 * Original author: Vincenzo K. Marcovecchio 97 * Maintainer: Petr Skoda 98 * 99 * @package core 100 * @subpackage admin 101 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com 102 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 103 */ 104 105 defined('MOODLE_INTERNAL') || die(); 106 107 /// Add libraries 108 require_once($CFG->libdir.'/ddllib.php'); 109 require_once($CFG->libdir.'/xmlize.php'); 110 require_once($CFG->libdir.'/messagelib.php'); 111 112 define('INSECURE_DATAROOT_WARNING', 1); 113 define('INSECURE_DATAROOT_ERROR', 2); 114 115 /** 116 * Automatically clean-up all plugin data and remove the plugin DB tables 117 * 118 * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead! 119 * 120 * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc. 121 * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc. 122 * @uses global $OUTPUT to produce notices and other messages 123 * @return void 124 */ 125 function uninstall_plugin($type, $name) { 126 global $CFG, $DB, $OUTPUT; 127 128 // This may take a long time. 129 core_php_time_limit::raise(); 130 131 // Recursively uninstall all subplugins first. 132 $subplugintypes = core_component::get_plugin_types_with_subplugins(); 133 if (isset($subplugintypes[$type])) { 134 $base = core_component::get_plugin_directory($type, $name); 135 136 $subpluginsfile = "{$base}/db/subplugins.json"; 137 if (file_exists($subpluginsfile)) { 138 $subplugins = (array) json_decode(file_get_contents($subpluginsfile))->plugintypes; 139 } else if (file_exists("{$base}/db/subplugins.php")) { 140 debugging('Use of subplugins.php has been deprecated. ' . 141 'Please update your plugin to provide a subplugins.json file instead.', 142 DEBUG_DEVELOPER); 143 $subplugins = []; 144 include("{$base}/db/subplugins.php"); 145 } 146 147 if (!empty($subplugins)) { 148 foreach (array_keys($subplugins) as $subplugintype) { 149 $instances = core_component::get_plugin_list($subplugintype); 150 foreach ($instances as $subpluginname => $notusedpluginpath) { 151 uninstall_plugin($subplugintype, $subpluginname); 152 } 153 } 154 } 155 } 156 157 $component = $type . '_' . $name; // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum' 158 159 if ($type === 'mod') { 160 $pluginname = $name; // eg. 'forum' 161 if (get_string_manager()->string_exists('modulename', $component)) { 162 $strpluginname = get_string('modulename', $component); 163 } else { 164 $strpluginname = $component; 165 } 166 167 } else { 168 $pluginname = $component; 169 if (get_string_manager()->string_exists('pluginname', $component)) { 170 $strpluginname = get_string('pluginname', $component); 171 } else { 172 $strpluginname = $component; 173 } 174 } 175 176 echo $OUTPUT->heading($pluginname); 177 178 // Delete all tag areas, collections and instances associated with this plugin. 179 core_tag_area::uninstall($component); 180 181 // Custom plugin uninstall. 182 $plugindirectory = core_component::get_plugin_directory($type, $name); 183 $uninstalllib = $plugindirectory . '/db/uninstall.php'; 184 if (file_exists($uninstalllib)) { 185 require_once($uninstalllib); 186 $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall'; // eg. 'xmldb_workshop_uninstall()' 187 if (function_exists($uninstallfunction)) { 188 // Do not verify result, let plugin complain if necessary. 189 $uninstallfunction(); 190 } 191 } 192 193 // Specific plugin type cleanup. 194 $plugininfo = core_plugin_manager::instance()->get_plugin_info($component); 195 if ($plugininfo) { 196 $plugininfo->uninstall_cleanup(); 197 core_plugin_manager::reset_caches(); 198 } 199 $plugininfo = null; 200 201 // perform clean-up task common for all the plugin/subplugin types 202 203 //delete the web service functions and pre-built services 204 require_once($CFG->dirroot.'/lib/externallib.php'); 205 external_delete_descriptions($component); 206 207 // delete calendar events 208 $DB->delete_records('event', array('modulename' => $pluginname)); 209 $DB->delete_records('event', ['component' => $component]); 210 211 // Delete scheduled tasks. 212 $DB->delete_records('task_adhoc', ['component' => $component]); 213 $DB->delete_records('task_scheduled', array('component' => $component)); 214 215 // Delete Inbound Message datakeys. 216 $DB->delete_records_select('messageinbound_datakeys', 217 'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($component)); 218 219 // Delete Inbound Message handlers. 220 $DB->delete_records('messageinbound_handlers', array('component' => $component)); 221 222 // delete all the logs 223 $DB->delete_records('log', array('module' => $pluginname)); 224 225 // delete log_display information 226 $DB->delete_records('log_display', array('component' => $component)); 227 228 // delete the module configuration records 229 unset_all_config_for_plugin($component); 230 if ($type === 'mod') { 231 unset_all_config_for_plugin($pluginname); 232 } 233 234 // delete message provider 235 message_provider_uninstall($component); 236 237 // delete the plugin tables 238 $xmldbfilepath = $plugindirectory . '/db/install.xml'; 239 drop_plugin_tables($component, $xmldbfilepath, false); 240 if ($type === 'mod' or $type === 'block') { 241 // non-frankenstyle table prefixes 242 drop_plugin_tables($name, $xmldbfilepath, false); 243 } 244 245 // delete the capabilities that were defined by this module 246 capabilities_cleanup($component); 247 248 // Delete all remaining files in the filepool owned by the component. 249 $fs = get_file_storage(); 250 $fs->delete_component_files($component); 251 252 // Finally purge all caches. 253 purge_all_caches(); 254 255 // Invalidate the hash used for upgrade detections. 256 set_config('allversionshash', ''); 257 258 echo $OUTPUT->notification(get_string('success'), 'notifysuccess'); 259 } 260 261 /** 262 * Returns the version of installed component 263 * 264 * @param string $component component name 265 * @param string $source either 'disk' or 'installed' - where to get the version information from 266 * @return string|bool version number or false if the component is not found 267 */ 268 function get_component_version($component, $source='installed') { 269 global $CFG, $DB; 270 271 list($type, $name) = core_component::normalize_component($component); 272 273 // moodle core or a core subsystem 274 if ($type === 'core') { 275 if ($source === 'installed') { 276 if (empty($CFG->version)) { 277 return false; 278 } else { 279 return $CFG->version; 280 } 281 } else { 282 if (!is_readable($CFG->dirroot.'/version.php')) { 283 return false; 284 } else { 285 $version = null; //initialize variable for IDEs 286 include($CFG->dirroot.'/version.php'); 287 return $version; 288 } 289 } 290 } 291 292 // activity module 293 if ($type === 'mod') { 294 if ($source === 'installed') { 295 if ($CFG->version < 2013092001.02) { 296 return $DB->get_field('modules', 'version', array('name'=>$name)); 297 } else { 298 return get_config('mod_'.$name, 'version'); 299 } 300 301 } else { 302 $mods = core_component::get_plugin_list('mod'); 303 if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) { 304 return false; 305 } else { 306 $plugin = new stdClass(); 307 $plugin->version = null; 308 $module = $plugin; 309 include($mods[$name].'/version.php'); 310 return $plugin->version; 311 } 312 } 313 } 314 315 // block 316 if ($type === 'block') { 317 if ($source === 'installed') { 318 if ($CFG->version < 2013092001.02) { 319 return $DB->get_field('block', 'version', array('name'=>$name)); 320 } else { 321 return get_config('block_'.$name, 'version'); 322 } 323 } else { 324 $blocks = core_component::get_plugin_list('block'); 325 if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) { 326 return false; 327 } else { 328 $plugin = new stdclass(); 329 include($blocks[$name].'/version.php'); 330 return $plugin->version; 331 } 332 } 333 } 334 335 // all other plugin types 336 if ($source === 'installed') { 337 return get_config($type.'_'.$name, 'version'); 338 } else { 339 $plugins = core_component::get_plugin_list($type); 340 if (empty($plugins[$name])) { 341 return false; 342 } else { 343 $plugin = new stdclass(); 344 include($plugins[$name].'/version.php'); 345 return $plugin->version; 346 } 347 } 348 } 349 350 /** 351 * Delete all plugin tables 352 * 353 * @param string $name Name of plugin, used as table prefix 354 * @param string $file Path to install.xml file 355 * @param bool $feedback defaults to true 356 * @return bool Always returns true 357 */ 358 function drop_plugin_tables($name, $file, $feedback=true) { 359 global $CFG, $DB; 360 361 // first try normal delete 362 if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) { 363 return true; 364 } 365 366 // then try to find all tables that start with name and are not in any xml file 367 $used_tables = get_used_table_names(); 368 369 $tables = $DB->get_tables(); 370 371 /// Iterate over, fixing id fields as necessary 372 foreach ($tables as $table) { 373 if (in_array($table, $used_tables)) { 374 continue; 375 } 376 377 if (strpos($table, $name) !== 0) { 378 continue; 379 } 380 381 // found orphan table --> delete it 382 if ($DB->get_manager()->table_exists($table)) { 383 $xmldb_table = new xmldb_table($table); 384 $DB->get_manager()->drop_table($xmldb_table); 385 } 386 } 387 388 return true; 389 } 390 391 /** 392 * Returns names of all known tables == tables that moodle knows about. 393 * 394 * @return array Array of lowercase table names 395 */ 396 function get_used_table_names() { 397 $table_names = array(); 398 $dbdirs = get_db_directories(); 399 400 foreach ($dbdirs as $dbdir) { 401 $file = $dbdir.'/install.xml'; 402 403 $xmldb_file = new xmldb_file($file); 404 405 if (!$xmldb_file->fileExists()) { 406 continue; 407 } 408 409 $loaded = $xmldb_file->loadXMLStructure(); 410 $structure = $xmldb_file->getStructure(); 411 412 if ($loaded and $tables = $structure->getTables()) { 413 foreach($tables as $table) { 414 $table_names[] = strtolower($table->getName()); 415 } 416 } 417 } 418 419 return $table_names; 420 } 421 422 /** 423 * Returns list of all directories where we expect install.xml files 424 * @return array Array of paths 425 */ 426 function get_db_directories() { 427 global $CFG; 428 429 $dbdirs = array(); 430 431 /// First, the main one (lib/db) 432 $dbdirs[] = $CFG->libdir.'/db'; 433 434 /// Then, all the ones defined by core_component::get_plugin_types() 435 $plugintypes = core_component::get_plugin_types(); 436 foreach ($plugintypes as $plugintype => $pluginbasedir) { 437 if ($plugins = core_component::get_plugin_list($plugintype)) { 438 foreach ($plugins as $plugin => $plugindir) { 439 $dbdirs[] = $plugindir.'/db'; 440 } 441 } 442 } 443 444 return $dbdirs; 445 } 446 447 /** 448 * Try to obtain or release the cron lock. 449 * @param string $name name of lock 450 * @param int $until timestamp when this lock considered stale, null means remove lock unconditionally 451 * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false 452 * @return bool true if lock obtained 453 */ 454 function set_cron_lock($name, $until, $ignorecurrent=false) { 455 global $DB; 456 if (empty($name)) { 457 debugging("Tried to get a cron lock for a null fieldname"); 458 return false; 459 } 460 461 // remove lock by force == remove from config table 462 if (is_null($until)) { 463 set_config($name, null); 464 return true; 465 } 466 467 if (!$ignorecurrent) { 468 // read value from db - other processes might have changed it 469 $value = $DB->get_field('config', 'value', array('name'=>$name)); 470 471 if ($value and $value > time()) { 472 //lock active 473 return false; 474 } 475 } 476 477 set_config($name, $until); 478 return true; 479 } 480 481 /** 482 * Test if and critical warnings are present 483 * @return bool 484 */ 485 function admin_critical_warnings_present() { 486 global $SESSION; 487 488 if (!has_capability('moodle/site:config', context_system::instance())) { 489 return 0; 490 } 491 492 if (!isset($SESSION->admin_critical_warning)) { 493 $SESSION->admin_critical_warning = 0; 494 if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) { 495 $SESSION->admin_critical_warning = 1; 496 } 497 } 498 499 return $SESSION->admin_critical_warning; 500 } 501 502 /** 503 * Detects if float supports at least 10 decimal digits 504 * 505 * Detects if float supports at least 10 decimal digits 506 * and also if float-->string conversion works as expected. 507 * 508 * @return bool true if problem found 509 */ 510 function is_float_problem() { 511 $num1 = 2009010200.01; 512 $num2 = 2009010200.02; 513 514 return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1); 515 } 516 517 /** 518 * Try to verify that dataroot is not accessible from web. 519 * 520 * Try to verify that dataroot is not accessible from web. 521 * It is not 100% correct but might help to reduce number of vulnerable sites. 522 * Protection from httpd.conf and .htaccess is not detected properly. 523 * 524 * @uses INSECURE_DATAROOT_WARNING 525 * @uses INSECURE_DATAROOT_ERROR 526 * @param bool $fetchtest try to test public access by fetching file, default false 527 * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic 528 */ 529 function is_dataroot_insecure($fetchtest=false) { 530 global $CFG; 531 532 $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround 533 534 $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1); 535 $rp = strrev(trim($rp, '/')); 536 $rp = explode('/', $rp); 537 foreach($rp as $r) { 538 if (strpos($siteroot, '/'.$r.'/') === 0) { 539 $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory 540 } else { 541 break; // probably alias root 542 } 543 } 544 545 $siteroot = strrev($siteroot); 546 $dataroot = str_replace('\\', '/', $CFG->dataroot.'/'); 547 548 if (strpos($dataroot, $siteroot) !== 0) { 549 return false; 550 } 551 552 if (!$fetchtest) { 553 return INSECURE_DATAROOT_WARNING; 554 } 555 556 // now try all methods to fetch a test file using http protocol 557 558 $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); 559 preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches); 560 $httpdocroot = $matches[1]; 561 $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot)); 562 make_upload_directory('diag'); 563 $testfile = $CFG->dataroot.'/diag/public.txt'; 564 if (!file_exists($testfile)) { 565 file_put_contents($testfile, 'test file, do not delete'); 566 @chmod($testfile, $CFG->filepermissions); 567 } 568 $teststr = trim(file_get_contents($testfile)); 569 if (empty($teststr)) { 570 // hmm, strange 571 return INSECURE_DATAROOT_WARNING; 572 } 573 574 $testurl = $datarooturl.'/diag/public.txt'; 575 if (extension_loaded('curl') and 576 !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and 577 !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and 578 ($ch = @curl_init($testurl)) !== false) { 579 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 580 curl_setopt($ch, CURLOPT_HEADER, false); 581 $data = curl_exec($ch); 582 if (!curl_errno($ch)) { 583 $data = trim($data); 584 if ($data === $teststr) { 585 curl_close($ch); 586 return INSECURE_DATAROOT_ERROR; 587 } 588 } 589 curl_close($ch); 590 } 591 592 if ($data = @file_get_contents($testurl)) { 593 $data = trim($data); 594 if ($data === $teststr) { 595 return INSECURE_DATAROOT_ERROR; 596 } 597 } 598 599 preg_match('|https?://([^/]+)|i', $testurl, $matches); 600 $sitename = $matches[1]; 601 $error = 0; 602 if ($fp = @fsockopen($sitename, 80, $error)) { 603 preg_match('|https?://[^/]+(.*)|i', $testurl, $matches); 604 $localurl = $matches[1]; 605 $out = "GET $localurl HTTP/1.1\r\n"; 606 $out .= "Host: $sitename\r\n"; 607 $out .= "Connection: Close\r\n\r\n"; 608 fwrite($fp, $out); 609 $data = ''; 610 $incoming = false; 611 while (!feof($fp)) { 612 if ($incoming) { 613 $data .= fgets($fp, 1024); 614 } else if (@fgets($fp, 1024) === "\r\n") { 615 $incoming = true; 616 } 617 } 618 fclose($fp); 619 $data = trim($data); 620 if ($data === $teststr) { 621 return INSECURE_DATAROOT_ERROR; 622 } 623 } 624 625 return INSECURE_DATAROOT_WARNING; 626 } 627 628 /** 629 * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file. 630 */ 631 function enable_cli_maintenance_mode() { 632 global $CFG, $SITE; 633 634 if (file_exists("$CFG->dataroot/climaintenance.html")) { 635 unlink("$CFG->dataroot/climaintenance.html"); 636 } 637 638 if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) { 639 $data = $CFG->maintenance_message; 640 $data = bootstrap_renderer::early_error_content($data, null, null, null); 641 $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data); 642 643 } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) { 644 $data = file_get_contents("$CFG->dataroot/climaintenance.template.html"); 645 646 } else { 647 $data = get_string('sitemaintenance', 'admin'); 648 $data = bootstrap_renderer::early_error_content($data, null, null, null); 649 $data = bootstrap_renderer::plain_page(get_string('sitemaintenancetitle', 'admin', 650 format_string($SITE->fullname, true, ['context' => context_system::instance()])), $data); 651 } 652 653 file_put_contents("$CFG->dataroot/climaintenance.html", $data); 654 chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions); 655 } 656 657 /// CLASS DEFINITIONS ///////////////////////////////////////////////////////// 658 659 660 /** 661 * Interface for anything appearing in the admin tree 662 * 663 * The interface that is implemented by anything that appears in the admin tree 664 * block. It forces inheriting classes to define a method for checking user permissions 665 * and methods for finding something in the admin tree. 666 * 667 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 668 */ 669 interface part_of_admin_tree { 670 671 /** 672 * Finds a named part_of_admin_tree. 673 * 674 * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree 675 * and not parentable_part_of_admin_tree, then this function should only check if 676 * $this->name matches $name. If it does, it should return a reference to $this, 677 * otherwise, it should return a reference to NULL. 678 * 679 * If a class inherits parentable_part_of_admin_tree, this method should be called 680 * recursively on all child objects (assuming, of course, the parent object's name 681 * doesn't match the search criterion). 682 * 683 * @param string $name The internal name of the part_of_admin_tree we're searching for. 684 * @return mixed An object reference or a NULL reference. 685 */ 686 public function locate($name); 687 688 /** 689 * Removes named part_of_admin_tree. 690 * 691 * @param string $name The internal name of the part_of_admin_tree we want to remove. 692 * @return bool success. 693 */ 694 public function prune($name); 695 696 /** 697 * Search using query 698 * @param string $query 699 * @return mixed array-object structure of found settings and pages 700 */ 701 public function search($query); 702 703 /** 704 * Verifies current user's access to this part_of_admin_tree. 705 * 706 * Used to check if the current user has access to this part of the admin tree or 707 * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree, 708 * then this method is usually just a call to has_capability() in the site context. 709 * 710 * If a class inherits parentable_part_of_admin_tree, this method should return the 711 * logical OR of the return of check_access() on all child objects. 712 * 713 * @return bool True if the user has access, false if she doesn't. 714 */ 715 public function check_access(); 716 717 /** 718 * Mostly useful for removing of some parts of the tree in admin tree block. 719 * 720 * @return True is hidden from normal list view 721 */ 722 public function is_hidden(); 723 724 /** 725 * Show we display Save button at the page bottom? 726 * @return bool 727 */ 728 public function show_save(); 729 } 730 731 732 /** 733 * Interface implemented by any part_of_admin_tree that has children. 734 * 735 * The interface implemented by any part_of_admin_tree that can be a parent 736 * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart 737 * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods 738 * include an add method for adding other part_of_admin_tree objects as children. 739 * 740 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 741 */ 742 interface parentable_part_of_admin_tree extends part_of_admin_tree { 743 744 /** 745 * Adds a part_of_admin_tree object to the admin tree. 746 * 747 * Used to add a part_of_admin_tree object to this object or a child of this 748 * object. $something should only be added if $destinationname matches 749 * $this->name. If it doesn't, add should be called on child objects that are 750 * also parentable_part_of_admin_tree's. 751 * 752 * $something should be appended as the last child in the $destinationname. If the 753 * $beforesibling is specified, $something should be prepended to it. If the given 754 * sibling is not found, $something should be appended to the end of $destinationname 755 * and a developer debugging message should be displayed. 756 * 757 * @param string $destinationname The internal name of the new parent for $something. 758 * @param part_of_admin_tree $something The object to be added. 759 * @return bool True on success, false on failure. 760 */ 761 public function add($destinationname, $something, $beforesibling = null); 762 763 } 764 765 766 /** 767 * The object used to represent folders (a.k.a. categories) in the admin tree block. 768 * 769 * Each admin_category object contains a number of part_of_admin_tree objects. 770 * 771 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 772 */ 773 class admin_category implements parentable_part_of_admin_tree { 774 775 /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */ 776 protected $children; 777 /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */ 778 public $name; 779 /** @var string The displayed name for this category. Usually obtained through get_string() */ 780 public $visiblename; 781 /** @var bool Should this category be hidden in admin tree block? */ 782 public $hidden; 783 /** @var mixed Either a string or an array or strings */ 784 public $path; 785 /** @var mixed Either a string or an array or strings */ 786 public $visiblepath; 787 788 /** @var array fast lookup category cache, all categories of one tree point to one cache */ 789 protected $category_cache; 790 791 /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */ 792 protected $sort = false; 793 /** @var bool If set to true children will be sorted in ascending order. */ 794 protected $sortasc = true; 795 /** @var bool If set to true sub categories and pages will be split and then sorted.. */ 796 protected $sortsplit = true; 797 /** @var bool $sorted True if the children have been sorted and don't need resorting */ 798 protected $sorted = false; 799 800 /** 801 * Constructor for an empty admin category 802 * 803 * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects 804 * @param string $visiblename The displayed named for this category. Usually obtained through get_string() 805 * @param bool $hidden hide category in admin tree block, defaults to false 806 */ 807 public function __construct($name, $visiblename, $hidden=false) { 808 $this->children = array(); 809 $this->name = $name; 810 $this->visiblename = $visiblename; 811 $this->hidden = $hidden; 812 } 813 814 /** 815 * Get the URL to view this page. 816 * 817 * @return moodle_url 818 */ 819 public function get_settings_page_url(): moodle_url { 820 return new moodle_url( 821 '/admin/category.php', 822 [ 823 'category' => $this->name, 824 ] 825 ); 826 } 827 828 /** 829 * Returns a reference to the part_of_admin_tree object with internal name $name. 830 * 831 * @param string $name The internal name of the object we want. 832 * @param bool $findpath initialize path and visiblepath arrays 833 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL. 834 * defaults to false 835 */ 836 public function locate($name, $findpath=false) { 837 if (!isset($this->category_cache[$this->name])) { 838 // somebody much have purged the cache 839 $this->category_cache[$this->name] = $this; 840 } 841 842 if ($this->name == $name) { 843 if ($findpath) { 844 $this->visiblepath[] = $this->visiblename; 845 $this->path[] = $this->name; 846 } 847 return $this; 848 } 849 850 // quick category lookup 851 if (!$findpath and isset($this->category_cache[$name])) { 852 return $this->category_cache[$name]; 853 } 854 855 $return = NULL; 856 foreach($this->children as $childid=>$unused) { 857 if ($return = $this->children[$childid]->locate($name, $findpath)) { 858 break; 859 } 860 } 861 862 if (!is_null($return) and $findpath) { 863 $return->visiblepath[] = $this->visiblename; 864 $return->path[] = $this->name; 865 } 866 867 return $return; 868 } 869 870 /** 871 * Search using query 872 * 873 * @param string query 874 * @return mixed array-object structure of found settings and pages 875 */ 876 public function search($query) { 877 $result = array(); 878 foreach ($this->get_children() as $child) { 879 $subsearch = $child->search($query); 880 if (!is_array($subsearch)) { 881 debugging('Incorrect search result from '.$child->name); 882 continue; 883 } 884 $result = array_merge($result, $subsearch); 885 } 886 return $result; 887 } 888 889 /** 890 * Removes part_of_admin_tree object with internal name $name. 891 * 892 * @param string $name The internal name of the object we want to remove. 893 * @return bool success 894 */ 895 public function prune($name) { 896 897 if ($this->name == $name) { 898 return false; //can not remove itself 899 } 900 901 foreach($this->children as $precedence => $child) { 902 if ($child->name == $name) { 903 // clear cache and delete self 904 while($this->category_cache) { 905 // delete the cache, but keep the original array address 906 array_pop($this->category_cache); 907 } 908 unset($this->children[$precedence]); 909 return true; 910 } else if ($this->children[$precedence]->prune($name)) { 911 return true; 912 } 913 } 914 return false; 915 } 916 917 /** 918 * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object. 919 * 920 * By default the new part of the tree is appended as the last child of the parent. You 921 * can specify a sibling node that the new part should be prepended to. If the given 922 * sibling is not found, the part is appended to the end (as it would be by default) and 923 * a developer debugging message is displayed. 924 * 925 * @throws coding_exception if the $beforesibling is empty string or is not string at all. 926 * @param string $destinationame The internal name of the immediate parent that we want for $something. 927 * @param mixed $something A part_of_admin_tree or setting instance to be added. 928 * @param string $beforesibling The name of the parent's child the $something should be prepended to. 929 * @return bool True if successfully added, false if $something can not be added. 930 */ 931 public function add($parentname, $something, $beforesibling = null) { 932 global $CFG; 933 934 $parent = $this->locate($parentname); 935 if (is_null($parent)) { 936 debugging('parent does not exist!'); 937 return false; 938 } 939 940 if ($something instanceof part_of_admin_tree) { 941 if (!($parent instanceof parentable_part_of_admin_tree)) { 942 debugging('error - parts of tree can be inserted only into parentable parts'); 943 return false; 944 } 945 if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) { 946 // The name of the node is already used, simply warn the developer that this should not happen. 947 // It is intentional to check for the debug level before performing the check. 948 debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER); 949 } 950 if (is_null($beforesibling)) { 951 // Append $something as the parent's last child. 952 $parent->children[] = $something; 953 } else { 954 if (!is_string($beforesibling) or trim($beforesibling) === '') { 955 throw new coding_exception('Unexpected value of the beforesibling parameter'); 956 } 957 // Try to find the position of the sibling. 958 $siblingposition = null; 959 foreach ($parent->children as $childposition => $child) { 960 if ($child->name === $beforesibling) { 961 $siblingposition = $childposition; 962 break; 963 } 964 } 965 if (is_null($siblingposition)) { 966 debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER); 967 $parent->children[] = $something; 968 } else { 969 $parent->children = array_merge( 970 array_slice($parent->children, 0, $siblingposition), 971 array($something), 972 array_slice($parent->children, $siblingposition) 973 ); 974 } 975 } 976 if ($something instanceof admin_category) { 977 if (isset($this->category_cache[$something->name])) { 978 debugging('Duplicate admin category name: '.$something->name); 979 } else { 980 $this->category_cache[$something->name] = $something; 981 $something->category_cache =& $this->category_cache; 982 foreach ($something->children as $child) { 983 // just in case somebody already added subcategories 984 if ($child instanceof admin_category) { 985 if (isset($this->category_cache[$child->name])) { 986 debugging('Duplicate admin category name: '.$child->name); 987 } else { 988 $this->category_cache[$child->name] = $child; 989 $child->category_cache =& $this->category_cache; 990 } 991 } 992 } 993 } 994 } 995 return true; 996 997 } else { 998 debugging('error - can not add this element'); 999 return false; 1000 } 1001 1002 } 1003 1004 /** 1005 * Checks if the user has access to anything in this category. 1006 * 1007 * @return bool True if the user has access to at least one child in this category, false otherwise. 1008 */ 1009 public function check_access() { 1010 foreach ($this->children as $child) { 1011 if ($child->check_access()) { 1012 return true; 1013 } 1014 } 1015 return false; 1016 } 1017 1018 /** 1019 * Is this category hidden in admin tree block? 1020 * 1021 * @return bool True if hidden 1022 */ 1023 public function is_hidden() { 1024 return $this->hidden; 1025 } 1026 1027 /** 1028 * Show we display Save button at the page bottom? 1029 * @return bool 1030 */ 1031 public function show_save() { 1032 foreach ($this->children as $child) { 1033 if ($child->show_save()) { 1034 return true; 1035 } 1036 } 1037 return false; 1038 } 1039 1040 /** 1041 * Sets sorting on this category. 1042 * 1043 * Please note this function doesn't actually do the sorting. 1044 * It can be called anytime. 1045 * Sorting occurs when the user calls get_children. 1046 * Code using the children array directly won't see the sorted results. 1047 * 1048 * @param bool $sort If set to true children will be sorted, if false they won't be. 1049 * @param bool $asc If true sorting will be ascending, otherwise descending. 1050 * @param bool $split If true we sort pages and sub categories separately. 1051 */ 1052 public function set_sorting($sort, $asc = true, $split = true) { 1053 $this->sort = (bool)$sort; 1054 $this->sortasc = (bool)$asc; 1055 $this->sortsplit = (bool)$split; 1056 } 1057 1058 /** 1059 * Returns the children associated with this category. 1060 * 1061 * @return part_of_admin_tree[] 1062 */ 1063 public function get_children() { 1064 // If we should sort and it hasn't already been sorted. 1065 if ($this->sort && !$this->sorted) { 1066 if ($this->sortsplit) { 1067 $categories = array(); 1068 $pages = array(); 1069 foreach ($this->children as $child) { 1070 if ($child instanceof admin_category) { 1071 $categories[] = $child; 1072 } else { 1073 $pages[] = $child; 1074 } 1075 } 1076 core_collator::asort_objects_by_property($categories, 'visiblename'); 1077 core_collator::asort_objects_by_property($pages, 'visiblename'); 1078 if (!$this->sortasc) { 1079 $categories = array_reverse($categories); 1080 $pages = array_reverse($pages); 1081 } 1082 $this->children = array_merge($pages, $categories); 1083 } else { 1084 core_collator::asort_objects_by_property($this->children, 'visiblename'); 1085 if (!$this->sortasc) { 1086 $this->children = array_reverse($this->children); 1087 } 1088 } 1089 $this->sorted = true; 1090 } 1091 return $this->children; 1092 } 1093 1094 /** 1095 * Magically gets a property from this object. 1096 * 1097 * @param $property 1098 * @return part_of_admin_tree[] 1099 * @throws coding_exception 1100 */ 1101 public function __get($property) { 1102 if ($property === 'children') { 1103 return $this->get_children(); 1104 } 1105 throw new coding_exception('Invalid property requested.'); 1106 } 1107 1108 /** 1109 * Magically sets a property against this object. 1110 * 1111 * @param string $property 1112 * @param mixed $value 1113 * @throws coding_exception 1114 */ 1115 public function __set($property, $value) { 1116 if ($property === 'children') { 1117 $this->sorted = false; 1118 $this->children = $value; 1119 } else { 1120 throw new coding_exception('Invalid property requested.'); 1121 } 1122 } 1123 1124 /** 1125 * Checks if an inaccessible property is set. 1126 * 1127 * @param string $property 1128 * @return bool 1129 * @throws coding_exception 1130 */ 1131 public function __isset($property) { 1132 if ($property === 'children') { 1133 return isset($this->children); 1134 } 1135 throw new coding_exception('Invalid property requested.'); 1136 } 1137 } 1138 1139 1140 /** 1141 * Root of admin settings tree, does not have any parent. 1142 * 1143 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1144 */ 1145 class admin_root extends admin_category { 1146 /** @var array List of errors */ 1147 public $errors; 1148 /** @var string search query */ 1149 public $search; 1150 /** @var bool full tree flag - true means all settings required, false only pages required */ 1151 public $fulltree; 1152 /** @var bool flag indicating loaded tree */ 1153 public $loaded; 1154 /** @var mixed site custom defaults overriding defaults in settings files*/ 1155 public $custom_defaults; 1156 1157 /** 1158 * @param bool $fulltree true means all settings required, 1159 * false only pages required 1160 */ 1161 public function __construct($fulltree) { 1162 global $CFG; 1163 1164 parent::__construct('root', get_string('administration'), false); 1165 $this->errors = array(); 1166 $this->search = ''; 1167 $this->fulltree = $fulltree; 1168 $this->loaded = false; 1169 1170 $this->category_cache = array(); 1171 1172 // load custom defaults if found 1173 $this->custom_defaults = null; 1174 $defaultsfile = "$CFG->dirroot/local/defaults.php"; 1175 if (is_readable($defaultsfile)) { 1176 $defaults = array(); 1177 include($defaultsfile); 1178 if (is_array($defaults) and count($defaults)) { 1179 $this->custom_defaults = $defaults; 1180 } 1181 } 1182 } 1183 1184 /** 1185 * Empties children array, and sets loaded to false 1186 * 1187 * @param bool $requirefulltree 1188 */ 1189 public function purge_children($requirefulltree) { 1190 $this->children = array(); 1191 $this->fulltree = ($requirefulltree || $this->fulltree); 1192 $this->loaded = false; 1193 //break circular dependencies - this helps PHP 5.2 1194 while($this->category_cache) { 1195 array_pop($this->category_cache); 1196 } 1197 $this->category_cache = array(); 1198 } 1199 } 1200 1201 1202 /** 1203 * Links external PHP pages into the admin tree. 1204 * 1205 * See detailed usage example at the top of this document (adminlib.php) 1206 * 1207 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1208 */ 1209 class admin_externalpage implements part_of_admin_tree { 1210 1211 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */ 1212 public $name; 1213 1214 /** @var string The displayed name for this external page. Usually obtained through get_string(). */ 1215 public $visiblename; 1216 1217 /** @var string The external URL that we should link to when someone requests this external page. */ 1218 public $url; 1219 1220 /** @var array The role capability/permission a user must have to access this external page. */ 1221 public $req_capability; 1222 1223 /** @var object The context in which capability/permission should be checked, default is site context. */ 1224 public $context; 1225 1226 /** @var bool hidden in admin tree block. */ 1227 public $hidden; 1228 1229 /** @var mixed either string or array of string */ 1230 public $path; 1231 1232 /** @var array list of visible names of page parents */ 1233 public $visiblepath; 1234 1235 /** 1236 * Constructor for adding an external page into the admin tree. 1237 * 1238 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects. 1239 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string(). 1240 * @param string $url The external URL that we should link to when someone requests this external page. 1241 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'. 1242 * @param boolean $hidden Is this external page hidden in admin tree block? Default false. 1243 * @param stdClass $context The context the page relates to. Not sure what happens 1244 * if you specify something other than system or front page. Defaults to system. 1245 */ 1246 public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) { 1247 $this->name = $name; 1248 $this->visiblename = $visiblename; 1249 $this->url = $url; 1250 if (is_array($req_capability)) { 1251 $this->req_capability = $req_capability; 1252 } else { 1253 $this->req_capability = array($req_capability); 1254 } 1255 $this->hidden = $hidden; 1256 $this->context = $context; 1257 } 1258 1259 /** 1260 * Returns a reference to the part_of_admin_tree object with internal name $name. 1261 * 1262 * @param string $name The internal name of the object we want. 1263 * @param bool $findpath defaults to false 1264 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL. 1265 */ 1266 public function locate($name, $findpath=false) { 1267 if ($this->name == $name) { 1268 if ($findpath) { 1269 $this->visiblepath = array($this->visiblename); 1270 $this->path = array($this->name); 1271 } 1272 return $this; 1273 } else { 1274 $return = NULL; 1275 return $return; 1276 } 1277 } 1278 1279 /** 1280 * This function always returns false, required function by interface 1281 * 1282 * @param string $name 1283 * @return false 1284 */ 1285 public function prune($name) { 1286 return false; 1287 } 1288 1289 /** 1290 * Search using query 1291 * 1292 * @param string $query 1293 * @return mixed array-object structure of found settings and pages 1294 */ 1295 public function search($query) { 1296 $found = false; 1297 if (strpos(strtolower($this->name), $query) !== false) { 1298 $found = true; 1299 } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) { 1300 $found = true; 1301 } 1302 if ($found) { 1303 $result = new stdClass(); 1304 $result->page = $this; 1305 $result->settings = array(); 1306 return array($this->name => $result); 1307 } else { 1308 return array(); 1309 } 1310 } 1311 1312 /** 1313 * Determines if the current user has access to this external page based on $this->req_capability. 1314 * 1315 * @return bool True if user has access, false otherwise. 1316 */ 1317 public function check_access() { 1318 global $CFG; 1319 $context = empty($this->context) ? context_system::instance() : $this->context; 1320 foreach($this->req_capability as $cap) { 1321 if (has_capability($cap, $context)) { 1322 return true; 1323 } 1324 } 1325 return false; 1326 } 1327 1328 /** 1329 * Is this external page hidden in admin tree block? 1330 * 1331 * @return bool True if hidden 1332 */ 1333 public function is_hidden() { 1334 return $this->hidden; 1335 } 1336 1337 /** 1338 * Show we display Save button at the page bottom? 1339 * @return bool 1340 */ 1341 public function show_save() { 1342 return false; 1343 } 1344 } 1345 1346 /** 1347 * Used to store details of the dependency between two settings elements. 1348 * 1349 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1350 * @copyright 2017 Davo Smith, Synergy Learning 1351 */ 1352 class admin_settingdependency { 1353 /** @var string the name of the setting to be shown/hidden */ 1354 public $settingname; 1355 /** @var string the setting this is dependent on */ 1356 public $dependenton; 1357 /** @var string the condition to show/hide the element */ 1358 public $condition; 1359 /** @var string the value to compare against */ 1360 public $value; 1361 1362 /** @var string[] list of valid conditions */ 1363 private static $validconditions = ['checked', 'notchecked', 'noitemselected', 'eq', 'neq', 'in']; 1364 1365 /** 1366 * admin_settingdependency constructor. 1367 * @param string $settingname 1368 * @param string $dependenton 1369 * @param string $condition 1370 * @param string $value 1371 * @throws \coding_exception 1372 */ 1373 public function __construct($settingname, $dependenton, $condition, $value) { 1374 $this->settingname = $this->parse_name($settingname); 1375 $this->dependenton = $this->parse_name($dependenton); 1376 $this->condition = $condition; 1377 $this->value = $value; 1378 1379 if (!in_array($this->condition, self::$validconditions)) { 1380 throw new coding_exception("Invalid condition '$condition'"); 1381 } 1382 } 1383 1384 /** 1385 * Convert the setting name into the form field name. 1386 * @param string $name 1387 * @return string 1388 */ 1389 private function parse_name($name) { 1390 $bits = explode('/', $name); 1391 $name = array_pop($bits); 1392 $plugin = ''; 1393 if ($bits) { 1394 $plugin = array_pop($bits); 1395 if ($plugin === 'moodle') { 1396 $plugin = ''; 1397 } 1398 } 1399 return 's_'.$plugin.'_'.$name; 1400 } 1401 1402 /** 1403 * Gather together all the dependencies in a format suitable for initialising javascript 1404 * @param admin_settingdependency[] $dependencies 1405 * @return array 1406 */ 1407 public static function prepare_for_javascript($dependencies) { 1408 $result = []; 1409 foreach ($dependencies as $d) { 1410 if (!isset($result[$d->dependenton])) { 1411 $result[$d->dependenton] = []; 1412 } 1413 if (!isset($result[$d->dependenton][$d->condition])) { 1414 $result[$d->dependenton][$d->condition] = []; 1415 } 1416 if (!isset($result[$d->dependenton][$d->condition][$d->value])) { 1417 $result[$d->dependenton][$d->condition][$d->value] = []; 1418 } 1419 $result[$d->dependenton][$d->condition][$d->value][] = $d->settingname; 1420 } 1421 return $result; 1422 } 1423 } 1424 1425 /** 1426 * Used to group a number of admin_setting objects into a page and add them to the admin tree. 1427 * 1428 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1429 */ 1430 class admin_settingpage implements part_of_admin_tree { 1431 1432 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */ 1433 public $name; 1434 1435 /** @var string The displayed name for this external page. Usually obtained through get_string(). */ 1436 public $visiblename; 1437 1438 /** @var mixed An array of admin_setting objects that are part of this setting page. */ 1439 public $settings; 1440 1441 /** @var admin_settingdependency[] list of settings to hide when certain conditions are met */ 1442 protected $dependencies = []; 1443 1444 /** @var array The role capability/permission a user must have to access this external page. */ 1445 public $req_capability; 1446 1447 /** @var object The context in which capability/permission should be checked, default is site context. */ 1448 public $context; 1449 1450 /** @var bool hidden in admin tree block. */ 1451 public $hidden; 1452 1453 /** @var mixed string of paths or array of strings of paths */ 1454 public $path; 1455 1456 /** @var array list of visible names of page parents */ 1457 public $visiblepath; 1458 1459 /** 1460 * see admin_settingpage for details of this function 1461 * 1462 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects. 1463 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string(). 1464 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'. 1465 * @param boolean $hidden Is this external page hidden in admin tree block? Default false. 1466 * @param stdClass $context The context the page relates to. Not sure what happens 1467 * if you specify something other than system or front page. Defaults to system. 1468 */ 1469 public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) { 1470 $this->settings = new stdClass(); 1471 $this->name = $name; 1472 $this->visiblename = $visiblename; 1473 if (is_array($req_capability)) { 1474 $this->req_capability = $req_capability; 1475 } else { 1476 $this->req_capability = array($req_capability); 1477 } 1478 $this->hidden = $hidden; 1479 $this->context = $context; 1480 } 1481 1482 /** 1483 * see admin_category 1484 * 1485 * @param string $name 1486 * @param bool $findpath 1487 * @return mixed Object (this) if name == this->name, else returns null 1488 */ 1489 public function locate($name, $findpath=false) { 1490 if ($this->name == $name) { 1491 if ($findpath) { 1492 $this->visiblepath = array($this->visiblename); 1493 $this->path = array($this->name); 1494 } 1495 return $this; 1496 } else { 1497 $return = NULL; 1498 return $return; 1499 } 1500 } 1501 1502 /** 1503 * Search string in settings page. 1504 * 1505 * @param string $query 1506 * @return array 1507 */ 1508 public function search($query) { 1509 $found = array(); 1510 1511 foreach ($this->settings as $setting) { 1512 if ($setting->is_related($query)) { 1513 $found[] = $setting; 1514 } 1515 } 1516 1517 if ($found) { 1518 $result = new stdClass(); 1519 $result->page = $this; 1520 $result->settings = $found; 1521 return array($this->name => $result); 1522 } 1523 1524 $found = false; 1525 if (strpos(strtolower($this->name), $query) !== false) { 1526 $found = true; 1527 } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) { 1528 $found = true; 1529 } 1530 if ($found) { 1531 $result = new stdClass(); 1532 $result->page = $this; 1533 $result->settings = array(); 1534 return array($this->name => $result); 1535 } else { 1536 return array(); 1537 } 1538 } 1539 1540 /** 1541 * This function always returns false, required by interface 1542 * 1543 * @param string $name 1544 * @return bool Always false 1545 */ 1546 public function prune($name) { 1547 return false; 1548 } 1549 1550 /** 1551 * adds an admin_setting to this admin_settingpage 1552 * 1553 * not the same as add for admin_category. adds an admin_setting to this admin_settingpage. settings appear (on the settingpage) in the order in which they're added 1554 * n.b. each admin_setting in an admin_settingpage must have a unique internal name 1555 * 1556 * @param object $setting is the admin_setting object you want to add 1557 * @return bool true if successful, false if not 1558 */ 1559 public function add($setting) { 1560 if (!($setting instanceof admin_setting)) { 1561 debugging('error - not a setting instance'); 1562 return false; 1563 } 1564 1565 $name = $setting->name; 1566 if ($setting->plugin) { 1567 $name = $setting->plugin . $name; 1568 } 1569 $this->settings->{$name} = $setting; 1570 return true; 1571 } 1572 1573 /** 1574 * Hide the named setting if the specified condition is matched. 1575 * 1576 * @param string $settingname 1577 * @param string $dependenton 1578 * @param string $condition 1579 * @param string $value 1580 */ 1581 public function hide_if($settingname, $dependenton, $condition = 'notchecked', $value = '1') { 1582 $this->dependencies[] = new admin_settingdependency($settingname, $dependenton, $condition, $value); 1583 1584 // Reformat the dependency name to the plugin | name format used in the display. 1585 $dependenton = str_replace('/', ' | ', $dependenton); 1586 1587 // Let the setting know, so it can be displayed underneath. 1588 $findname = str_replace('/', '', $settingname); 1589 foreach ($this->settings as $name => $setting) { 1590 if ($name === $findname) { 1591 $setting->add_dependent_on($dependenton); 1592 } 1593 } 1594 } 1595 1596 /** 1597 * see admin_externalpage 1598 * 1599 * @return bool Returns true for yes false for no 1600 */ 1601 public function check_access() { 1602 global $CFG; 1603 $context = empty($this->context) ? context_system::instance() : $this->context; 1604 foreach($this->req_capability as $cap) { 1605 if (has_capability($cap, $context)) { 1606 return true; 1607 } 1608 } 1609 return false; 1610 } 1611 1612 /** 1613 * outputs this page as html in a table (suitable for inclusion in an admin pagetype) 1614 * @return string Returns an XHTML string 1615 */ 1616 public function output_html() { 1617 $adminroot = admin_get_root(); 1618 $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n"; 1619 foreach($this->settings as $setting) { 1620 $fullname = $setting->get_full_name(); 1621 if (array_key_exists($fullname, $adminroot->errors)) { 1622 $data = $adminroot->errors[$fullname]->data; 1623 } else { 1624 $data = $setting->get_setting(); 1625 // do not use defaults if settings not available - upgrade settings handles the defaults! 1626 } 1627 $return .= $setting->output_html($data); 1628 } 1629 $return .= '</fieldset>'; 1630 return $return; 1631 } 1632 1633 /** 1634 * Is this settings page hidden in admin tree block? 1635 * 1636 * @return bool True if hidden 1637 */ 1638 public function is_hidden() { 1639 return $this->hidden; 1640 } 1641 1642 /** 1643 * Show we display Save button at the page bottom? 1644 * @return bool 1645 */ 1646 public function show_save() { 1647 foreach($this->settings as $setting) { 1648 if (empty($setting->nosave)) { 1649 return true; 1650 } 1651 } 1652 return false; 1653 } 1654 1655 /** 1656 * Should any of the settings on this page be shown / hidden based on conditions? 1657 * @return bool 1658 */ 1659 public function has_dependencies() { 1660 return (bool)$this->dependencies; 1661 } 1662 1663 /** 1664 * Format the setting show/hide conditions ready to initialise the page javascript 1665 * @return array 1666 */ 1667 public function get_dependencies_for_javascript() { 1668 if (!$this->has_dependencies()) { 1669 return []; 1670 } 1671 return admin_settingdependency::prepare_for_javascript($this->dependencies); 1672 } 1673 } 1674 1675 1676 /** 1677 * Admin settings class. Only exists on setting pages. 1678 * Read & write happens at this level; no authentication. 1679 * 1680 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1681 */ 1682 abstract class admin_setting { 1683 /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */ 1684 public $name; 1685 /** @var string localised name */ 1686 public $visiblename; 1687 /** @var string localised long description in Markdown format */ 1688 public $description; 1689 /** @var mixed Can be string or array of string */ 1690 public $defaultsetting; 1691 /** @var string */ 1692 public $updatedcallback; 1693 /** @var mixed can be String or Null. Null means main config table */ 1694 public $plugin; // null means main config table 1695 /** @var bool true indicates this setting does not actually save anything, just information */ 1696 public $nosave = false; 1697 /** @var bool if set, indicates that a change to this setting requires rebuild course cache */ 1698 public $affectsmodinfo = false; 1699 /** @var array of admin_setting_flag - These are extra checkboxes attached to a setting. */ 1700 private $flags = array(); 1701 /** @var bool Whether this field must be forced LTR. */ 1702 private $forceltr = null; 1703 /** @var array list of other settings that may cause this setting to be hidden */ 1704 private $dependenton = []; 1705 /** @var bool Whether this setting uses a custom form control */ 1706 protected $customcontrol = false; 1707 1708 /** 1709 * Constructor 1710 * @param string $name unique ascii name, either 'mysetting' for settings that in config, 1711 * or 'myplugin/mysetting' for ones in config_plugins. 1712 * @param string $visiblename localised name 1713 * @param string $description localised long description 1714 * @param mixed $defaultsetting string or array depending on implementation 1715 */ 1716 public function __construct($name, $visiblename, $description, $defaultsetting) { 1717 $this->parse_setting_name($name); 1718 $this->visiblename = $visiblename; 1719 $this->description = $description; 1720 $this->defaultsetting = $defaultsetting; 1721 } 1722 1723 /** 1724 * Generic function to add a flag to this admin setting. 1725 * 1726 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED 1727 * @param bool $default - The default for the flag 1728 * @param string $shortname - The shortname for this flag. Used as a suffix for the setting name. 1729 * @param string $displayname - The display name for this flag. Used as a label next to the checkbox. 1730 */ 1731 protected function set_flag_options($enabled, $default, $shortname, $displayname) { 1732 if (empty($this->flags[$shortname])) { 1733 $this->flags[$shortname] = new admin_setting_flag($enabled, $default, $shortname, $displayname); 1734 } else { 1735 $this->flags[$shortname]->set_options($enabled, $default); 1736 } 1737 } 1738 1739 /** 1740 * Set the enabled options flag on this admin setting. 1741 * 1742 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED 1743 * @param bool $default - The default for the flag 1744 */ 1745 public function set_enabled_flag_options($enabled, $default) { 1746 $this->set_flag_options($enabled, $default, 'enabled', new lang_string('enabled', 'core_admin')); 1747 } 1748 1749 /** 1750 * Set the advanced options flag on this admin setting. 1751 * 1752 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED 1753 * @param bool $default - The default for the flag 1754 */ 1755 public function set_advanced_flag_options($enabled, $default) { 1756 $this->set_flag_options($enabled, $default, 'adv', new lang_string('advanced')); 1757 } 1758 1759 1760 /** 1761 * Set the locked options flag on this admin setting. 1762 * 1763 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED 1764 * @param bool $default - The default for the flag 1765 */ 1766 public function set_locked_flag_options($enabled, $default) { 1767 $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin')); 1768 } 1769 1770 /** 1771 * Set the required options flag on this admin setting. 1772 * 1773 * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED. 1774 * @param bool $default - The default for the flag. 1775 */ 1776 public function set_required_flag_options($enabled, $default) { 1777 $this->set_flag_options($enabled, $default, 'required', new lang_string('required', 'core_admin')); 1778 } 1779 1780 /** 1781 * Is this option forced in config.php? 1782 * 1783 * @return bool 1784 */ 1785 public function is_readonly(): bool { 1786 global $CFG; 1787 1788 if (empty($this->plugin)) { 1789 if (array_key_exists($this->name, $CFG->config_php_settings)) { 1790 return true; 1791 } 1792 } else { 1793 if (array_key_exists($this->plugin, $CFG->forced_plugin_settings) 1794 and array_key_exists($this->name, $CFG->forced_plugin_settings[$this->plugin])) { 1795 return true; 1796 } 1797 } 1798 return false; 1799 } 1800 1801 /** 1802 * Get the currently saved value for a setting flag 1803 * 1804 * @param admin_setting_flag $flag - One of the admin_setting_flag for this admin_setting. 1805 * @return bool 1806 */ 1807 public function get_setting_flag_value(admin_setting_flag $flag) { 1808 $value = $this->config_read($this->name . '_' . $flag->get_shortname()); 1809 if (!isset($value)) { 1810 $value = $flag->get_default(); 1811 } 1812 1813 return !empty($value); 1814 } 1815 1816 /** 1817 * Get the list of defaults for the flags on this setting. 1818 * 1819 * @param array of strings describing the defaults for this setting. This is appended to by this function. 1820 */ 1821 public function get_setting_flag_defaults(& $defaults) { 1822 foreach ($this->flags as $flag) { 1823 if ($flag->is_enabled() && $flag->get_default()) { 1824 $defaults[] = $flag->get_displayname(); 1825 } 1826 } 1827 } 1828 1829 /** 1830 * Output the input fields for the advanced and locked flags on this setting. 1831 * 1832 * @param bool $adv - The current value of the advanced flag. 1833 * @param bool $locked - The current value of the locked flag. 1834 * @return string $output - The html for the flags. 1835 */ 1836 public function output_setting_flags() { 1837 $output = ''; 1838 1839 foreach ($this->flags as $flag) { 1840 if ($flag->is_enabled()) { 1841 $output .= $flag->output_setting_flag($this); 1842 } 1843 } 1844 1845 if (!empty($output)) { 1846 return html_writer::tag('span', $output, array('class' => 'adminsettingsflags')); 1847 } 1848 return $output; 1849 } 1850 1851 /** 1852 * Write the values of the flags for this admin setting. 1853 * 1854 * @param array $data - The data submitted from the form or null to set the default value for new installs. 1855 * @return bool - true if successful. 1856 */ 1857 public function write_setting_flags($data) { 1858 $result = true; 1859 foreach ($this->flags as $flag) { 1860 $result = $result && $flag->write_setting_flag($this, $data); 1861 } 1862 return $result; 1863 } 1864 1865 /** 1866 * Set up $this->name and potentially $this->plugin 1867 * 1868 * Set up $this->name and possibly $this->plugin based on whether $name looks 1869 * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking 1870 * on the names, that is, output a developer debug warning if the name 1871 * contains anything other than [a-zA-Z0-9_]+. 1872 * 1873 * @param string $name the setting name passed in to the constructor. 1874 */ 1875 private function parse_setting_name($name) { 1876 $bits = explode('/', $name); 1877 if (count($bits) > 2) { 1878 throw new moodle_exception('invalidadminsettingname', '', '', $name); 1879 } 1880 $this->name = array_pop($bits); 1881 if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) { 1882 throw new moodle_exception('invalidadminsettingname', '', '', $name); 1883 } 1884 if (!empty($bits)) { 1885 $this->plugin = array_pop($bits); 1886 if ($this->plugin === 'moodle') { 1887 $this->plugin = null; 1888 } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) { 1889 throw new moodle_exception('invalidadminsettingname', '', '', $name); 1890 } 1891 } 1892 } 1893 1894 /** 1895 * Returns the fullname prefixed by the plugin 1896 * @return string 1897 */ 1898 public function get_full_name() { 1899 return 's_'.$this->plugin.'_'.$this->name; 1900 } 1901 1902 /** 1903 * Returns the ID string based on plugin and name 1904 * @return string 1905 */ 1906 public function get_id() { 1907 return 'id_s_'.$this->plugin.'_'.$this->name; 1908 } 1909 1910 /** 1911 * @param bool $affectsmodinfo If true, changes to this setting will 1912 * cause the course cache to be rebuilt 1913 */ 1914 public function set_affects_modinfo($affectsmodinfo) { 1915 $this->affectsmodinfo = $affectsmodinfo; 1916 } 1917 1918 /** 1919 * Returns the config if possible 1920 * 1921 * @return mixed returns config if successful else null 1922 */ 1923 public function config_read($name) { 1924 global $CFG; 1925 if (!empty($this->plugin)) { 1926 $value = get_config($this->plugin, $name); 1927 return $value === false ? NULL : $value; 1928 1929 } else { 1930 if (isset($CFG->$name)) { 1931 return $CFG->$name; 1932 } else { 1933 return NULL; 1934 } 1935 } 1936 } 1937 1938 /** 1939 * Used to set a config pair and log change 1940 * 1941 * @param string $name 1942 * @param mixed $value Gets converted to string if not null 1943 * @return bool Write setting to config table 1944 */ 1945 public function config_write($name, $value) { 1946 global $DB, $USER, $CFG; 1947 1948 if ($this->nosave) { 1949 return true; 1950 } 1951 1952 // make sure it is a real change 1953 $oldvalue = get_config($this->plugin, $name); 1954 $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise 1955 $value = is_null($value) ? null : (string)$value; 1956 1957 if ($oldvalue === $value) { 1958 return true; 1959 } 1960 1961 // store change 1962 set_config($name, $value, $this->plugin); 1963 1964 // Some admin settings affect course modinfo 1965 if ($this->affectsmodinfo) { 1966 // Clear course cache for all courses 1967 rebuild_course_cache(0, true); 1968 } 1969 1970 $this->add_to_config_log($name, $oldvalue, $value); 1971 1972 return true; // BC only 1973 } 1974 1975 /** 1976 * Log config changes if necessary. 1977 * @param string $name 1978 * @param string $oldvalue 1979 * @param string $value 1980 */ 1981 protected function add_to_config_log($name, $oldvalue, $value) { 1982 add_to_config_log($name, $oldvalue, $value, $this->plugin); 1983 } 1984 1985 /** 1986 * Returns current value of this setting 1987 * @return mixed array or string depending on instance, NULL means not set yet 1988 */ 1989 public abstract function get_setting(); 1990 1991 /** 1992 * Returns default setting if exists 1993 * @return mixed array or string depending on instance; NULL means no default, user must supply 1994 */ 1995 public function get_defaultsetting() { 1996 $adminroot = admin_get_root(false, false); 1997 if (!empty($adminroot->custom_defaults)) { 1998 $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin; 1999 if (isset($adminroot->custom_defaults[$plugin])) { 2000 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-) 2001 return $adminroot->custom_defaults[$plugin][$this->name]; 2002 } 2003 } 2004 } 2005 return $this->defaultsetting; 2006 } 2007 2008 /** 2009 * Store new setting 2010 * 2011 * @param mixed $data string or array, must not be NULL 2012 * @return string empty string if ok, string error message otherwise 2013 */ 2014 public abstract function write_setting($data); 2015 2016 /** 2017 * Return part of form with setting 2018 * This function should always be overwritten 2019 * 2020 * @param mixed $data array or string depending on setting 2021 * @param string $query 2022 * @return string 2023 */ 2024 public function output_html($data, $query='') { 2025 // should be overridden 2026 return; 2027 } 2028 2029 /** 2030 * Function called if setting updated - cleanup, cache reset, etc. 2031 * @param string $functionname Sets the function name 2032 * @return void 2033 */ 2034 public function set_updatedcallback($functionname) { 2035 $this->updatedcallback = $functionname; 2036 } 2037 2038 /** 2039 * Execute postupdatecallback if necessary. 2040 * @param mixed $original original value before write_setting() 2041 * @return bool true if changed, false if not. 2042 */ 2043 public function post_write_settings($original) { 2044 // Comparison must work for arrays too. 2045 if (serialize($original) === serialize($this->get_setting())) { 2046 return false; 2047 } 2048 2049 $callbackfunction = $this->updatedcallback; 2050 if (!empty($callbackfunction) and is_callable($callbackfunction)) { 2051 $callbackfunction($this->get_full_name()); 2052 } 2053 return true; 2054 } 2055 2056 /** 2057 * Is setting related to query text - used when searching 2058 * @param string $query 2059 * @return bool 2060 */ 2061 public function is_related($query) { 2062 if (strpos(strtolower($this->name), $query) !== false) { 2063 return true; 2064 } 2065 if (strpos(core_text::strtolower($this->visiblename), $query) !== false) { 2066 return true; 2067 } 2068 if (strpos(core_text::strtolower($this->description), $query) !== false) { 2069 return true; 2070 } 2071 $current = $this->get_setting(); 2072 if (!is_null($current)) { 2073 if (is_string($current)) { 2074 if (strpos(core_text::strtolower($current), $query) !== false) { 2075 return true; 2076 } 2077 } 2078 } 2079 $default = $this->get_defaultsetting(); 2080 if (!is_null($default)) { 2081 if (is_string($default)) { 2082 if (strpos(core_text::strtolower($default), $query) !== false) { 2083 return true; 2084 } 2085 } 2086 } 2087 return false; 2088 } 2089 2090 /** 2091 * Get whether this should be displayed in LTR mode. 2092 * 2093 * @return bool|null 2094 */ 2095 public function get_force_ltr() { 2096 return $this->forceltr; 2097 } 2098 2099 /** 2100 * Set whether to force LTR or not. 2101 * 2102 * @param bool $value True when forced, false when not force, null when unknown. 2103 */ 2104 public function set_force_ltr($value) { 2105 $this->forceltr = $value; 2106 } 2107 2108 /** 2109 * Add a setting to the list of those that could cause this one to be hidden 2110 * @param string $dependenton 2111 */ 2112 public function add_dependent_on($dependenton) { 2113 $this->dependenton[] = $dependenton; 2114 } 2115 2116 /** 2117 * Get a list of the settings that could cause this one to be hidden. 2118 * @return array 2119 */ 2120 public function get_dependent_on() { 2121 return $this->dependenton; 2122 } 2123 2124 /** 2125 * Whether this setting uses a custom form control. 2126 * This function is especially useful to decide if we should render a label element for this setting or not. 2127 * 2128 * @return bool 2129 */ 2130 public function has_custom_form_control(): bool { 2131 return $this->customcontrol; 2132 } 2133 } 2134 2135 /** 2136 * An additional option that can be applied to an admin setting. 2137 * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'. 2138 * 2139 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2140 */ 2141 class admin_setting_flag { 2142 /** @var bool Flag to indicate if this option can be toggled for this setting */ 2143 private $enabled = false; 2144 /** @var bool Flag to indicate if this option defaults to true or false */ 2145 private $default = false; 2146 /** @var string Short string used to create setting name - e.g. 'adv' */ 2147 private $shortname = ''; 2148 /** @var string String used as the label for this flag */ 2149 private $displayname = ''; 2150 /** @const Checkbox for this flag is displayed in admin page */ 2151 const ENABLED = true; 2152 /** @const Checkbox for this flag is not displayed in admin page */ 2153 const DISABLED = false; 2154 2155 /** 2156 * Constructor 2157 * 2158 * @param bool $enabled Can this option can be toggled. 2159 * Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED. 2160 * @param bool $default The default checked state for this setting option. 2161 * @param string $shortname The shortname of this flag. Currently supported flags are 'locked' and 'adv' 2162 * @param string $displayname The displayname of this flag. Used as a label for the flag. 2163 */ 2164 public function __construct($enabled, $default, $shortname, $displayname) { 2165 $this->shortname = $shortname; 2166 $this->displayname = $displayname; 2167 $this->set_options($enabled, $default); 2168 } 2169 2170 /** 2171 * Update the values of this setting options class 2172 * 2173 * @param bool $enabled Can this option can be toggled. 2174 * Should be one of admin_setting_flag::ENABLED or admin_setting_flag::DISABLED. 2175 * @param bool $default The default checked state for this setting option. 2176 */ 2177 public function set_options($enabled, $default) { 2178 $this->enabled = $enabled; 2179 $this->default = $default; 2180 } 2181 2182 /** 2183 * Should this option appear in the interface and be toggleable? 2184 * 2185 * @return bool Is it enabled? 2186 */ 2187 public function is_enabled() { 2188 return $this->enabled; 2189 } 2190 2191 /** 2192 * Should this option be checked by default? 2193 * 2194 * @return bool Is it on by default? 2195 */ 2196 public function get_default() { 2197 return $this->default; 2198 } 2199 2200 /** 2201 * Return the short name for this flag. e.g. 'adv' or 'locked' 2202 * 2203 * @return string 2204 */ 2205 public function get_shortname() { 2206 return $this->shortname; 2207 } 2208 2209 /** 2210 * Return the display name for this flag. e.g. 'Advanced' or 'Locked' 2211 * 2212 * @return string 2213 */ 2214 public function get_displayname() { 2215 return $this->displayname; 2216 } 2217 2218 /** 2219 * Save the submitted data for this flag - or set it to the default if $data is null. 2220 * 2221 * @param admin_setting $setting - The admin setting for this flag 2222 * @param array $data - The data submitted from the form or null to set the default value for new installs. 2223 * @return bool 2224 */ 2225 public function write_setting_flag(admin_setting $setting, $data) { 2226 $result = true; 2227 if ($this->is_enabled()) { 2228 if (!isset($data)) { 2229 $value = $this->get_default(); 2230 } else { 2231 $value = !empty($data[$setting->get_full_name() . '_' . $this->get_shortname()]); 2232 } 2233 $result = $setting->config_write($setting->name . '_' . $this->get_shortname(), $value); 2234 } 2235 2236 return $result; 2237 2238 } 2239 2240 /** 2241 * Output the checkbox for this setting flag. Should only be called if the flag is enabled. 2242 * 2243 * @param admin_setting $setting - The admin setting for this flag 2244 * @return string - The html for the checkbox. 2245 */ 2246 public function output_setting_flag(admin_setting $setting) { 2247 global $OUTPUT; 2248 2249 $value = $setting->get_setting_flag_value($this); 2250 2251 $context = new stdClass(); 2252 $context->id = $setting->get_id() . '_' . $this->get_shortname(); 2253 $context->name = $setting->get_full_name() . '_' . $this->get_shortname(); 2254 $context->value = 1; 2255 $context->checked = $value ? true : false; 2256 $context->label = $this->get_displayname(); 2257 2258 return $OUTPUT->render_from_template('core_admin/setting_flag', $context); 2259 } 2260 } 2261 2262 2263 /** 2264 * No setting - just heading and text. 2265 * 2266 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2267 */ 2268 class admin_setting_heading extends admin_setting { 2269 2270 /** 2271 * not a setting, just text 2272 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 2273 * @param string $heading heading 2274 * @param string $information text in box 2275 */ 2276 public function __construct($name, $heading, $information) { 2277 $this->nosave = true; 2278 parent::__construct($name, $heading, $information, ''); 2279 } 2280 2281 /** 2282 * Always returns true 2283 * @return bool Always returns true 2284 */ 2285 public function get_setting() { 2286 return true; 2287 } 2288 2289 /** 2290 * Always returns true 2291 * @return bool Always returns true 2292 */ 2293 public function get_defaultsetting() { 2294 return true; 2295 } 2296 2297 /** 2298 * Never write settings 2299 * @return string Always returns an empty string 2300 */ 2301 public function write_setting($data) { 2302 // do not write any setting 2303 return ''; 2304 } 2305 2306 /** 2307 * Returns an HTML string 2308 * @return string Returns an HTML string 2309 */ 2310 public function output_html($data, $query='') { 2311 global $OUTPUT; 2312 $context = new stdClass(); 2313 $context->title = $this->visiblename; 2314 $context->description = $this->description; 2315 $context->descriptionformatted = highlight($query, markdown_to_html($this->description)); 2316 return $OUTPUT->render_from_template('core_admin/setting_heading', $context); 2317 } 2318 } 2319 2320 /** 2321 * No setting - just name and description in same row. 2322 * 2323 * @copyright 2018 onwards Amaia Anabitarte 2324 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2325 */ 2326 class admin_setting_description extends admin_setting { 2327 2328 /** 2329 * Not a setting, just text 2330 * 2331 * @param string $name 2332 * @param string $visiblename 2333 * @param string $description 2334 */ 2335 public function __construct($name, $visiblename, $description) { 2336 $this->nosave = true; 2337 parent::__construct($name, $visiblename, $description, ''); 2338 } 2339 2340 /** 2341 * Always returns true 2342 * 2343 * @return bool Always returns true 2344 */ 2345 public function get_setting() { 2346 return true; 2347 } 2348 2349 /** 2350 * Always returns true 2351 * 2352 * @return bool Always returns true 2353 */ 2354 public function get_defaultsetting() { 2355 return true; 2356 } 2357 2358 /** 2359 * Never write settings 2360 * 2361 * @param mixed $data Gets converted to str for comparison against yes value 2362 * @return string Always returns an empty string 2363 */ 2364 public function write_setting($data) { 2365 // Do not write any setting. 2366 return ''; 2367 } 2368 2369 /** 2370 * Returns an HTML string 2371 * 2372 * @param string $data 2373 * @param string $query 2374 * @return string Returns an HTML string 2375 */ 2376 public function output_html($data, $query='') { 2377 global $OUTPUT; 2378 2379 $context = new stdClass(); 2380 $context->title = $this->visiblename; 2381 $context->description = $this->description; 2382 2383 return $OUTPUT->render_from_template('core_admin/setting_description', $context); 2384 } 2385 } 2386 2387 2388 2389 /** 2390 * The most flexible setting, the user enters text. 2391 * 2392 * This type of field should be used for config settings which are using 2393 * English words and are not localised (passwords, database name, list of values, ...). 2394 * 2395 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2396 */ 2397 class admin_setting_configtext extends admin_setting { 2398 2399 /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */ 2400 public $paramtype; 2401 /** @var int default field size */ 2402 public $size; 2403 2404 /** 2405 * Config text constructor 2406 * 2407 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 2408 * @param string $visiblename localised 2409 * @param string $description long localised info 2410 * @param string $defaultsetting 2411 * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex 2412 * @param int $size default field size 2413 */ 2414 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) { 2415 $this->paramtype = $paramtype; 2416 if (!is_null($size)) { 2417 $this->size = $size; 2418 } else { 2419 $this->size = ($paramtype === PARAM_INT) ? 5 : 30; 2420 } 2421 parent::__construct($name, $visiblename, $description, $defaultsetting); 2422 } 2423 2424 /** 2425 * Get whether this should be displayed in LTR mode. 2426 * 2427 * Try to guess from the PARAM type unless specifically set. 2428 */ 2429 public function get_force_ltr() { 2430 $forceltr = parent::get_force_ltr(); 2431 if ($forceltr === null) { 2432 return !is_rtl_compatible($this->paramtype); 2433 } 2434 return $forceltr; 2435 } 2436 2437 /** 2438 * Return the setting 2439 * 2440 * @return mixed returns config if successful else null 2441 */ 2442 public function get_setting() { 2443 return $this->config_read($this->name); 2444 } 2445 2446 public function write_setting($data) { 2447 if ($this->paramtype === PARAM_INT and $data === '') { 2448 // do not complain if '' used instead of 0 2449 $data = 0; 2450 } 2451 // $data is a string 2452 $validated = $this->validate($data); 2453 if ($validated !== true) { 2454 return $validated; 2455 } 2456 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); 2457 } 2458 2459 /** 2460 * Validate data before storage 2461 * @param string data 2462 * @return mixed true if ok string if error found 2463 */ 2464 public function validate($data) { 2465 // allow paramtype to be a custom regex if it is the form of /pattern/ 2466 if (preg_match('#^/.*/$#', $this->paramtype)) { 2467 if (preg_match($this->paramtype, $data)) { 2468 return true; 2469 } else { 2470 return get_string('validateerror', 'admin'); 2471 } 2472 2473 } else if ($this->paramtype === PARAM_RAW) { 2474 return true; 2475 2476 } else { 2477 $cleaned = clean_param($data, $this->paramtype); 2478 if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison 2479 return true; 2480 } else { 2481 return get_string('validateerror', 'admin'); 2482 } 2483 } 2484 } 2485 2486 /** 2487 * Return an XHTML string for the setting 2488 * @return string Returns an XHTML string 2489 */ 2490 public function output_html($data, $query='') { 2491 global $OUTPUT; 2492 2493 $default = $this->get_defaultsetting(); 2494 $context = (object) [ 2495 'size' => $this->size, 2496 'id' => $this->get_id(), 2497 'name' => $this->get_full_name(), 2498 'value' => $data, 2499 'forceltr' => $this->get_force_ltr(), 2500 'readonly' => $this->is_readonly(), 2501 ]; 2502 $element = $OUTPUT->render_from_template('core_admin/setting_configtext', $context); 2503 2504 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query); 2505 } 2506 } 2507 2508 /** 2509 * Text input with a maximum length constraint. 2510 * 2511 * @copyright 2015 onwards Ankit Agarwal 2512 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2513 */ 2514 class admin_setting_configtext_with_maxlength extends admin_setting_configtext { 2515 2516 /** @var int maximum number of chars allowed. */ 2517 protected $maxlength; 2518 2519 /** 2520 * Config text constructor 2521 * 2522 * @param string $name unique ascii name, either 'mysetting' for settings that in config, 2523 * or 'myplugin/mysetting' for ones in config_plugins. 2524 * @param string $visiblename localised 2525 * @param string $description long localised info 2526 * @param string $defaultsetting 2527 * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex 2528 * @param int $size default field size 2529 * @param mixed $maxlength int maxlength allowed, 0 for infinite. 2530 */ 2531 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, 2532 $size=null, $maxlength = 0) { 2533 $this->maxlength = $maxlength; 2534 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $size); 2535 } 2536 2537 /** 2538 * Validate data before storage 2539 * 2540 * @param string $data data 2541 * @return mixed true if ok string if error found 2542 */ 2543 public function validate($data) { 2544 $parentvalidation = parent::validate($data); 2545 if ($parentvalidation === true) { 2546 if ($this->maxlength > 0) { 2547 // Max length check. 2548 $length = core_text::strlen($data); 2549 if ($length > $this->maxlength) { 2550 return get_string('maximumchars', 'moodle', $this->maxlength); 2551 } 2552 return true; 2553 } else { 2554 return true; // No max length check needed. 2555 } 2556 } else { 2557 return $parentvalidation; 2558 } 2559 } 2560 } 2561 2562 /** 2563 * General text area without html editor. 2564 * 2565 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2566 */ 2567 class admin_setting_configtextarea extends admin_setting_configtext { 2568 private $rows; 2569 private $cols; 2570 2571 /** 2572 * @param string $name 2573 * @param string $visiblename 2574 * @param string $description 2575 * @param mixed $defaultsetting string or array 2576 * @param mixed $paramtype 2577 * @param string $cols The number of columns to make the editor 2578 * @param string $rows The number of rows to make the editor 2579 */ 2580 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') { 2581 $this->rows = $rows; 2582 $this->cols = $cols; 2583 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype); 2584 } 2585 2586 /** 2587 * Returns an XHTML string for the editor 2588 * 2589 * @param string $data 2590 * @param string $query 2591 * @return string XHTML string for the editor 2592 */ 2593 public function output_html($data, $query='') { 2594 global $OUTPUT; 2595 2596 $default = $this->get_defaultsetting(); 2597 $defaultinfo = $default; 2598 if (!is_null($default) and $default !== '') { 2599 $defaultinfo = "\n".$default; 2600 } 2601 2602 $context = (object) [ 2603 'cols' => $this->cols, 2604 'rows' => $this->rows, 2605 'id' => $this->get_id(), 2606 'name' => $this->get_full_name(), 2607 'value' => $data, 2608 'forceltr' => $this->get_force_ltr(), 2609 'readonly' => $this->is_readonly(), 2610 ]; 2611 $element = $OUTPUT->render_from_template('core_admin/setting_configtextarea', $context); 2612 2613 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query); 2614 } 2615 } 2616 2617 /** 2618 * General text area with html editor. 2619 */ 2620 class admin_setting_confightmleditor extends admin_setting_configtextarea { 2621 2622 /** 2623 * @param string $name 2624 * @param string $visiblename 2625 * @param string $description 2626 * @param mixed $defaultsetting string or array 2627 * @param mixed $paramtype 2628 */ 2629 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') { 2630 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype, $cols, $rows); 2631 $this->set_force_ltr(false); 2632 editors_head_setup(); 2633 } 2634 2635 /** 2636 * Returns an XHTML string for the editor 2637 * 2638 * @param string $data 2639 * @param string $query 2640 * @return string XHTML string for the editor 2641 */ 2642 public function output_html($data, $query='') { 2643 $editor = editors_get_preferred_editor(FORMAT_HTML); 2644 $editor->set_text($data); 2645 $editor->use_editor($this->get_id(), array('noclean'=>true)); 2646 return parent::output_html($data, $query); 2647 } 2648 2649 /** 2650 * Checks if data has empty html. 2651 * 2652 * @param string $data 2653 * @return string Empty when no errors. 2654 */ 2655 public function write_setting($data) { 2656 if (trim(html_to_text($data)) === '') { 2657 $data = ''; 2658 } 2659 return parent::write_setting($data); 2660 } 2661 } 2662 2663 2664 /** 2665 * Password field, allows unmasking of password 2666 * 2667 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2668 */ 2669 class admin_setting_configpasswordunmask extends admin_setting_configtext { 2670 2671 /** 2672 * Constructor 2673 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 2674 * @param string $visiblename localised 2675 * @param string $description long localised info 2676 * @param string $defaultsetting default password 2677 */ 2678 public function __construct($name, $visiblename, $description, $defaultsetting) { 2679 parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30); 2680 } 2681 2682 /** 2683 * Log config changes if necessary. 2684 * @param string $name 2685 * @param string $oldvalue 2686 * @param string $value 2687 */ 2688 protected function add_to_config_log($name, $oldvalue, $value) { 2689 if ($value !== '') { 2690 $value = '********'; 2691 } 2692 if ($oldvalue !== '' and $oldvalue !== null) { 2693 $oldvalue = '********'; 2694 } 2695 parent::add_to_config_log($name, $oldvalue, $value); 2696 } 2697 2698 /** 2699 * Returns HTML for the field. 2700 * 2701 * @param string $data Value for the field 2702 * @param string $query Passed as final argument for format_admin_setting 2703 * @return string Rendered HTML 2704 */ 2705 public function output_html($data, $query='') { 2706 global $OUTPUT; 2707 2708 $context = (object) [ 2709 'id' => $this->get_id(), 2710 'name' => $this->get_full_name(), 2711 'size' => $this->size, 2712 'value' => $this->is_readonly() ? null : $data, 2713 'forceltr' => $this->get_force_ltr(), 2714 'readonly' => $this->is_readonly(), 2715 ]; 2716 $element = $OUTPUT->render_from_template('core_admin/setting_configpasswordunmask', $context); 2717 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', null, $query); 2718 } 2719 } 2720 2721 /** 2722 * Password field, allows unmasking of password, with an advanced checkbox that controls an additional $name.'_adv' setting. 2723 * 2724 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2725 * @copyright 2018 Paul Holden (pholden@greenhead.ac.uk) 2726 */ 2727 class admin_setting_configpasswordunmask_with_advanced extends admin_setting_configpasswordunmask { 2728 2729 /** 2730 * Constructor 2731 * 2732 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 2733 * @param string $visiblename localised 2734 * @param string $description long localised info 2735 * @param array $defaultsetting ('value'=>string, 'adv'=>bool) 2736 */ 2737 public function __construct($name, $visiblename, $description, $defaultsetting) { 2738 parent::__construct($name, $visiblename, $description, $defaultsetting['value']); 2739 $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv'])); 2740 } 2741 } 2742 2743 /** 2744 * Admin setting class for encrypted values using secure encryption. 2745 * 2746 * @copyright 2019 The Open University 2747 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2748 */ 2749 class admin_setting_encryptedpassword extends admin_setting { 2750 2751 /** 2752 * Constructor. Same as parent except that the default value is always an empty string. 2753 * 2754 * @param string $name Internal name used in config table 2755 * @param string $visiblename Name shown on form 2756 * @param string $description Description that appears below field 2757 */ 2758 public function __construct(string $name, string $visiblename, string $description) { 2759 parent::__construct($name, $visiblename, $description, ''); 2760 } 2761 2762 public function get_setting() { 2763 return $this->config_read($this->name); 2764 } 2765 2766 public function write_setting($data) { 2767 $data = trim($data); 2768 if ($data === '') { 2769 // Value can really be set to nothing. 2770 $savedata = ''; 2771 } else { 2772 // Encrypt value before saving it. 2773 $savedata = \core\encryption::encrypt($data); 2774 } 2775 return ($this->config_write($this->name, $savedata) ? '' : get_string('errorsetting', 'admin')); 2776 } 2777 2778 public function output_html($data, $query='') { 2779 global $OUTPUT; 2780 2781 $default = $this->get_defaultsetting(); 2782 $context = (object) [ 2783 'id' => $this->get_id(), 2784 'name' => $this->get_full_name(), 2785 'set' => $data !== '', 2786 'novalue' => $this->get_setting() === null 2787 ]; 2788 $element = $OUTPUT->render_from_template('core_admin/setting_encryptedpassword', $context); 2789 2790 return format_admin_setting($this, $this->visiblename, $element, $this->description, 2791 true, '', $default, $query); 2792 } 2793 } 2794 2795 /** 2796 * Empty setting used to allow flags (advanced) on settings that can have no sensible default. 2797 * Note: Only advanced makes sense right now - locked does not. 2798 * 2799 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2800 */ 2801 class admin_setting_configempty extends admin_setting_configtext { 2802 2803 /** 2804 * @param string $name 2805 * @param string $visiblename 2806 * @param string $description 2807 */ 2808 public function __construct($name, $visiblename, $description) { 2809 parent::__construct($name, $visiblename, $description, '', PARAM_RAW); 2810 } 2811 2812 /** 2813 * Returns an XHTML string for the hidden field 2814 * 2815 * @param string $data 2816 * @param string $query 2817 * @return string XHTML string for the editor 2818 */ 2819 public function output_html($data, $query='') { 2820 global $OUTPUT; 2821 2822 $context = (object) [ 2823 'id' => $this->get_id(), 2824 'name' => $this->get_full_name() 2825 ]; 2826 $element = $OUTPUT->render_from_template('core_admin/setting_configempty', $context); 2827 2828 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', get_string('none'), $query); 2829 } 2830 } 2831 2832 2833 /** 2834 * Path to directory 2835 * 2836 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2837 */ 2838 class admin_setting_configfile extends admin_setting_configtext { 2839 /** 2840 * Constructor 2841 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 2842 * @param string $visiblename localised 2843 * @param string $description long localised info 2844 * @param string $defaultdirectory default directory location 2845 */ 2846 public function __construct($name, $visiblename, $description, $defaultdirectory) { 2847 parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50); 2848 } 2849 2850 /** 2851 * Returns XHTML for the field 2852 * 2853 * Returns XHTML for the field and also checks whether the file 2854 * specified in $data exists using file_exists() 2855 * 2856 * @param string $data File name and path to use in value attr 2857 * @param string $query 2858 * @return string XHTML field 2859 */ 2860 public function output_html($data, $query='') { 2861 global $CFG, $OUTPUT; 2862 2863 $default = $this->get_defaultsetting(); 2864 $context = (object) [ 2865 'id' => $this->get_id(), 2866 'name' => $this->get_full_name(), 2867 'size' => $this->size, 2868 'value' => $data, 2869 'showvalidity' => !empty($data), 2870 'valid' => $data && file_exists($data), 2871 'readonly' => !empty($CFG->preventexecpath) || $this->is_readonly(), 2872 'forceltr' => $this->get_force_ltr(), 2873 ]; 2874 2875 if ($context->readonly) { 2876 $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>'; 2877 } 2878 2879 $element = $OUTPUT->render_from_template('core_admin/setting_configfile', $context); 2880 2881 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query); 2882 } 2883 2884 /** 2885 * Checks if execpatch has been disabled in config.php 2886 */ 2887 public function write_setting($data) { 2888 global $CFG; 2889 if (!empty($CFG->preventexecpath)) { 2890 if ($this->get_setting() === null) { 2891 // Use default during installation. 2892 $data = $this->get_defaultsetting(); 2893 if ($data === null) { 2894 $data = ''; 2895 } 2896 } else { 2897 return ''; 2898 } 2899 } 2900 return parent::write_setting($data); 2901 } 2902 2903 } 2904 2905 2906 /** 2907 * Path to executable file 2908 * 2909 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2910 */ 2911 class admin_setting_configexecutable extends admin_setting_configfile { 2912 2913 /** 2914 * Returns an XHTML field 2915 * 2916 * @param string $data This is the value for the field 2917 * @param string $query 2918 * @return string XHTML field 2919 */ 2920 public function output_html($data, $query='') { 2921 global $CFG, $OUTPUT; 2922 $default = $this->get_defaultsetting(); 2923 require_once("$CFG->libdir/filelib.php"); 2924 2925 $context = (object) [ 2926 'id' => $this->get_id(), 2927 'name' => $this->get_full_name(), 2928 'size' => $this->size, 2929 'value' => $data, 2930 'showvalidity' => !empty($data), 2931 'valid' => $data && file_exists($data) && !is_dir($data) && file_is_executable($data), 2932 'readonly' => !empty($CFG->preventexecpath), 2933 'forceltr' => $this->get_force_ltr() 2934 ]; 2935 2936 if (!empty($CFG->preventexecpath)) { 2937 $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>'; 2938 } 2939 2940 $element = $OUTPUT->render_from_template('core_admin/setting_configexecutable', $context); 2941 2942 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query); 2943 } 2944 } 2945 2946 2947 /** 2948 * Path to directory 2949 * 2950 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2951 */ 2952 class admin_setting_configdirectory extends admin_setting_configfile { 2953 2954 /** 2955 * Returns an XHTML field 2956 * 2957 * @param string $data This is the value for the field 2958 * @param string $query 2959 * @return string XHTML 2960 */ 2961 public function output_html($data, $query='') { 2962 global $CFG, $OUTPUT; 2963 $default = $this->get_defaultsetting(); 2964 2965 $context = (object) [ 2966 'id' => $this->get_id(), 2967 'name' => $this->get_full_name(), 2968 'size' => $this->size, 2969 'value' => $data, 2970 'showvalidity' => !empty($data), 2971 'valid' => $data && file_exists($data) && is_dir($data), 2972 'readonly' => !empty($CFG->preventexecpath), 2973 'forceltr' => $this->get_force_ltr() 2974 ]; 2975 2976 if (!empty($CFG->preventexecpath)) { 2977 $this->visiblename .= '<div class="alert alert-info">'.get_string('execpathnotallowed', 'admin').'</div>'; 2978 } 2979 2980 $element = $OUTPUT->render_from_template('core_admin/setting_configdirectory', $context); 2981 2982 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query); 2983 } 2984 } 2985 2986 2987 /** 2988 * Checkbox 2989 * 2990 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2991 */ 2992 class admin_setting_configcheckbox extends admin_setting { 2993 /** @var string Value used when checked */ 2994 public $yes; 2995 /** @var string Value used when not checked */ 2996 public $no; 2997 2998 /** 2999 * Constructor 3000 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 3001 * @param string $visiblename localised 3002 * @param string $description long localised info 3003 * @param string $defaultsetting 3004 * @param string $yes value used when checked 3005 * @param string $no value used when not checked 3006 */ 3007 public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') { 3008 parent::__construct($name, $visiblename, $description, $defaultsetting); 3009 $this->yes = (string)$yes; 3010 $this->no = (string)$no; 3011 } 3012 3013 /** 3014 * Retrieves the current setting using the objects name 3015 * 3016 * @return string 3017 */ 3018 public function get_setting() { 3019 return $this->config_read($this->name); 3020 } 3021 3022 /** 3023 * Sets the value for the setting 3024 * 3025 * Sets the value for the setting to either the yes or no values 3026 * of the object by comparing $data to yes 3027 * 3028 * @param mixed $data Gets converted to str for comparison against yes value 3029 * @return string empty string or error 3030 */ 3031 public function write_setting($data) { 3032 if ((string)$data === $this->yes) { // convert to strings before comparison 3033 $data = $this->yes; 3034 } else { 3035 $data = $this->no; 3036 } 3037 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); 3038 } 3039 3040 /** 3041 * Returns an XHTML checkbox field 3042 * 3043 * @param string $data If $data matches yes then checkbox is checked 3044 * @param string $query 3045 * @return string XHTML field 3046 */ 3047 public function output_html($data, $query='') { 3048 global $OUTPUT; 3049 3050 $context = (object) [ 3051 'id' => $this->get_id(), 3052 'name' => $this->get_full_name(), 3053 'no' => $this->no, 3054 'value' => $this->yes, 3055 'checked' => (string) $data === $this->yes, 3056 'readonly' => $this->is_readonly(), 3057 ]; 3058 3059 $default = $this->get_defaultsetting(); 3060 if (!is_null($default)) { 3061 if ((string)$default === $this->yes) { 3062 $defaultinfo = get_string('checkboxyes', 'admin'); 3063 } else { 3064 $defaultinfo = get_string('checkboxno', 'admin'); 3065 } 3066 } else { 3067 $defaultinfo = NULL; 3068 } 3069 3070 $element = $OUTPUT->render_from_template('core_admin/setting_configcheckbox', $context); 3071 3072 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query); 3073 } 3074 } 3075 3076 3077 /** 3078 * Multiple checkboxes, each represents different value, stored in csv format 3079 * 3080 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3081 */ 3082 class admin_setting_configmulticheckbox extends admin_setting { 3083 /** @var array Array of choices value=>label */ 3084 public $choices; 3085 /** @var callable|null Loader function for choices */ 3086 protected $choiceloader = null; 3087 3088 /** 3089 * Constructor: uses parent::__construct 3090 * 3091 * The $choices parameter may be either an array of $value => $label format, 3092 * e.g. [1 => get_string('yes')], or a callback function which takes no parameters and 3093 * returns an array in that format. 3094 * 3095 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 3096 * @param string $visiblename localised 3097 * @param string $description long localised info 3098 * @param array $defaultsetting array of selected 3099 * @param array|callable $choices array of $value => $label for each checkbox, or a callback 3100 */ 3101 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) { 3102 if (is_array($choices)) { 3103 $this->choices = $choices; 3104 } 3105 if (is_callable($choices)) { 3106 $this->choiceloader = $choices; 3107 } 3108 parent::__construct($name, $visiblename, $description, $defaultsetting); 3109 } 3110 3111 /** 3112 * This function may be used in ancestors for lazy loading of choices 3113 * 3114 * Override this method if loading of choices is expensive, such 3115 * as when it requires multiple db requests. 3116 * 3117 * @return bool true if loaded, false if error 3118 */ 3119 public function load_choices() { 3120 if ($this->choiceloader) { 3121 if (!is_array($this->choices)) { 3122 $this->choices = call_user_func($this->choiceloader); 3123 } 3124 } 3125 return true; 3126 } 3127 3128 /** 3129 * Is setting related to query text - used when searching 3130 * 3131 * @param string $query 3132 * @return bool true on related, false on not or failure 3133 */ 3134 public function is_related($query) { 3135 if (!$this->load_choices() or empty($this->choices)) { 3136 return false; 3137 } 3138 if (parent::is_related($query)) { 3139 return true; 3140 } 3141 3142 foreach ($this->choices as $desc) { 3143 if (strpos(core_text::strtolower($desc), $query) !== false) { 3144 return true; 3145 } 3146 } 3147 return false; 3148 } 3149 3150 /** 3151 * Returns the current setting if it is set 3152 * 3153 * @return mixed null if null, else an array 3154 */ 3155 public function get_setting() { 3156 $result = $this->config_read($this->name); 3157 3158 if (is_null($result)) { 3159 return NULL; 3160 } 3161 if ($result === '') { 3162 return array(); 3163 } 3164 $enabled = explode(',', $result); 3165 $setting = array(); 3166 foreach ($enabled as $option) { 3167 $setting[$option] = 1; 3168 } 3169 return $setting; 3170 } 3171 3172 /** 3173 * Saves the setting(s) provided in $data 3174 * 3175 * @param array $data An array of data, if not array returns empty str 3176 * @return mixed empty string on useless data or bool true=success, false=failed 3177 */ 3178 public function write_setting($data) { 3179 if (!is_array($data)) { 3180 return ''; // ignore it 3181 } 3182 if (!$this->load_choices() or empty($this->choices)) { 3183 return ''; 3184 } 3185 unset($data['xxxxx']); 3186 $result = array(); 3187 foreach ($data as $key => $value) { 3188 if ($value and array_key_exists($key, $this->choices)) { 3189 $result[] = $key; 3190 } 3191 } 3192 return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin'); 3193 } 3194 3195 /** 3196 * Returns XHTML field(s) as required by choices 3197 * 3198 * Relies on data being an array should data ever be another valid vartype with 3199 * acceptable value this may cause a warning/error 3200 * if (!is_array($data)) would fix the problem 3201 * 3202 * @todo Add vartype handling to ensure $data is an array 3203 * 3204 * @param array $data An array of checked values 3205 * @param string $query 3206 * @return string XHTML field 3207 */ 3208 public function output_html($data, $query='') { 3209 global $OUTPUT; 3210 3211 if (!$this->load_choices() or empty($this->choices)) { 3212 return ''; 3213 } 3214 3215 $default = $this->get_defaultsetting(); 3216 if (is_null($default)) { 3217 $default = array(); 3218 } 3219 if (is_null($data)) { 3220 $data = array(); 3221 } 3222 3223 $context = (object) [ 3224 'id' => $this->get_id(), 3225 'name' => $this->get_full_name(), 3226 ]; 3227 3228 $options = array(); 3229 $defaults = array(); 3230 foreach ($this->choices as $key => $description) { 3231 if (!empty($default[$key])) { 3232 $defaults[] = $description; 3233 } 3234 3235 $options[] = [ 3236 'key' => $key, 3237 'checked' => !empty($data[$key]), 3238 'label' => highlightfast($query, $description) 3239 ]; 3240 } 3241 3242 if (is_null($default)) { 3243 $defaultinfo = null; 3244 } else if (!empty($defaults)) { 3245 $defaultinfo = implode(', ', $defaults); 3246 } else { 3247 $defaultinfo = get_string('none'); 3248 } 3249 3250 $context->options = $options; 3251 $context->hasoptions = !empty($options); 3252 3253 $element = $OUTPUT->render_from_template('core_admin/setting_configmulticheckbox', $context); 3254 3255 return format_admin_setting($this, $this->visiblename, $element, $this->description, false, '', $defaultinfo, $query); 3256 3257 } 3258 } 3259 3260 3261 /** 3262 * Multiple checkboxes 2, value stored as string 00101011 3263 * 3264 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3265 */ 3266 class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox { 3267 3268 /** 3269 * Returns the setting if set 3270 * 3271 * @return mixed null if not set, else an array of set settings 3272 */ 3273 public function get_setting() { 3274 $result = $this->config_read($this->name); 3275 if (is_null($result)) { 3276 return NULL; 3277 } 3278 if (!$this->load_choices()) { 3279 return NULL; 3280 } 3281 $result = str_pad($result, count($this->choices), '0'); 3282 $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY); 3283 $setting = array(); 3284 foreach ($this->choices as $key=>$unused) { 3285 $value = array_shift($result); 3286 if ($value) { 3287 $setting[$key] = 1; 3288 } 3289 } 3290 return $setting; 3291 } 3292 3293 /** 3294 * Save setting(s) provided in $data param 3295 * 3296 * @param array $data An array of settings to save 3297 * @return mixed empty string for bad data or bool true=>success, false=>error 3298 */ 3299 public function write_setting($data) { 3300 if (!is_array($data)) { 3301 return ''; // ignore it 3302 } 3303 if (!$this->load_choices() or empty($this->choices)) { 3304 return ''; 3305 } 3306 $result = ''; 3307 foreach ($this->choices as $key=>$unused) { 3308 if (!empty($data[$key])) { 3309 $result .= '1'; 3310 } else { 3311 $result .= '0'; 3312 } 3313 } 3314 return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin'); 3315 } 3316 } 3317 3318 3319 /** 3320 * Select one value from list 3321 * 3322 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3323 */ 3324 class admin_setting_configselect extends admin_setting { 3325 /** @var array Array of choices value=>label */ 3326 public $choices; 3327 /** @var array Array of choices grouped using optgroups */ 3328 public $optgroups; 3329 /** @var callable|null Loader function for choices */ 3330 protected $choiceloader = null; 3331 /** @var callable|null Validation function */ 3332 protected $validatefunction = null; 3333 3334 /** 3335 * Constructor. 3336 * 3337 * If you want to lazy-load the choices, pass a callback function that returns a choice 3338 * array for the $choices parameter. 3339 * 3340 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 3341 * @param string $visiblename localised 3342 * @param string $description long localised info 3343 * @param string|int $defaultsetting 3344 * @param array|callable|null $choices array of $value=>$label for each selection, or callback 3345 */ 3346 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) { 3347 // Look for optgroup and single options. 3348 if (is_array($choices)) { 3349 $this->choices = []; 3350 foreach ($choices as $key => $val) { 3351 if (is_array($val)) { 3352 $this->optgroups[$key] = $val; 3353 $this->choices = array_merge($this->choices, $val); 3354 } else { 3355 $this->choices[$key] = $val; 3356 } 3357 } 3358 } 3359 if (is_callable($choices)) { 3360 $this->choiceloader = $choices; 3361 } 3362 3363 parent::__construct($name, $visiblename, $description, $defaultsetting); 3364 } 3365 3366 /** 3367 * Sets a validate function. 3368 * 3369 * The callback will be passed one parameter, the new setting value, and should return either 3370 * an empty string '' if the value is OK, or an error message if not. 3371 * 3372 * @param callable|null $validatefunction Validate function or null to clear 3373 * @since Moodle 3.10 3374 */ 3375 public function set_validate_function(?callable $validatefunction = null) { 3376 $this->validatefunction = $validatefunction; 3377 } 3378 3379 /** 3380 * This function may be used in ancestors for lazy loading of choices 3381 * 3382 * Override this method if loading of choices is expensive, such 3383 * as when it requires multiple db requests. 3384 * 3385 * @return bool true if loaded, false if error 3386 */ 3387 public function load_choices() { 3388 if ($this->choiceloader) { 3389 if (!is_array($this->choices)) { 3390 $this->choices = call_user_func($this->choiceloader); 3391 } 3392 return true; 3393 } 3394 return true; 3395 } 3396 3397 /** 3398 * Check if this is $query is related to a choice 3399 * 3400 * @param string $query 3401 * @return bool true if related, false if not 3402 */ 3403 public function is_related($query) { 3404 if (parent::is_related($query)) { 3405 return true; 3406 } 3407 if (!$this->load_choices()) { 3408 return false; 3409 } 3410 foreach ($this->choices as $key=>$value) { 3411 if (strpos(core_text::strtolower($key), $query) !== false) { 3412 return true; 3413 } 3414 if (strpos(core_text::strtolower($value), $query) !== false) { 3415 return true; 3416 } 3417 } 3418 return false; 3419 } 3420 3421 /** 3422 * Return the setting 3423 * 3424 * @return mixed returns config if successful else null 3425 */ 3426 public function get_setting() { 3427 return $this->config_read($this->name); 3428 } 3429 3430 /** 3431 * Save a setting 3432 * 3433 * @param string $data 3434 * @return string empty of error string 3435 */ 3436 public function write_setting($data) { 3437 if (!$this->load_choices() or empty($this->choices)) { 3438 return ''; 3439 } 3440 if (!array_key_exists($data, $this->choices)) { 3441 return ''; // ignore it 3442 } 3443 3444 // Validate the new setting. 3445 $error = $this->validate_setting($data); 3446 if ($error) { 3447 return $error; 3448 } 3449 3450 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); 3451 } 3452 3453 /** 3454 * Validate the setting. This uses the callback function if provided; subclasses could override 3455 * to carry out validation directly in the class. 3456 * 3457 * @param string $data New value being set 3458 * @return string Empty string if valid, or error message text 3459 * @since Moodle 3.10 3460 */ 3461 protected function validate_setting(string $data): string { 3462 // If validation function is specified, call it now. 3463 if ($this->validatefunction) { 3464 return call_user_func($this->validatefunction, $data); 3465 } else { 3466 return ''; 3467 } 3468 } 3469 3470 /** 3471 * Returns XHTML select field 3472 * 3473 * Ensure the options are loaded, and generate the XHTML for the select 3474 * element and any warning message. Separating this out from output_html 3475 * makes it easier to subclass this class. 3476 * 3477 * @param string $data the option to show as selected. 3478 * @param string $current the currently selected option in the database, null if none. 3479 * @param string $default the default selected option. 3480 * @return array the HTML for the select element, and a warning message. 3481 * @deprecated since Moodle 3.2 3482 */ 3483 public function output_select_html($data, $current, $default, $extraname = '') { 3484 debugging('The method admin_setting_configselect::output_select_html is depreacted, do not use any more.', DEBUG_DEVELOPER); 3485 } 3486 3487 /** 3488 * Returns XHTML select field and wrapping div(s) 3489 * 3490 * @see output_select_html() 3491 * 3492 * @param string $data the option to show as selected 3493 * @param string $query 3494 * @return string XHTML field and wrapping div 3495 */ 3496 public function output_html($data, $query='') { 3497 global $OUTPUT; 3498 3499 $default = $this->get_defaultsetting(); 3500 $current = $this->get_setting(); 3501 3502 if (!$this->load_choices() || empty($this->choices)) { 3503 return ''; 3504 } 3505 3506 $context = (object) [ 3507 'id' => $this->get_id(), 3508 'name' => $this->get_full_name(), 3509 ]; 3510 3511 if (!is_null($default) && array_key_exists($default, $this->choices)) { 3512 $defaultinfo = $this->choices[$default]; 3513 } else { 3514 $defaultinfo = NULL; 3515 } 3516 3517 // Warnings. 3518 $warning = ''; 3519 if ($current === null) { 3520 // First run. 3521 } else if (empty($current) && (array_key_exists('', $this->choices) || array_key_exists(0, $this->choices))) { 3522 // No warning. 3523 } else if (!array_key_exists($current, $this->choices)) { 3524 $warning = get_string('warningcurrentsetting', 'admin', $current); 3525 if (!is_null($default) && $data == $current) { 3526 $data = $default; // Use default instead of first value when showing the form. 3527 } 3528 } 3529 3530 $options = []; 3531 $template = 'core_admin/setting_configselect'; 3532 3533 if (!empty($this->optgroups)) { 3534 $optgroups = []; 3535 foreach ($this->optgroups as $label => $choices) { 3536 $optgroup = array('label' => $label, 'options' => []); 3537 foreach ($choices as $value => $name) { 3538 $optgroup['options'][] = [ 3539 'value' => $value, 3540 'name' => $name, 3541 'selected' => (string) $value == $data 3542 ]; 3543 unset($this->choices[$value]); 3544 } 3545 $optgroups[] = $optgroup; 3546 } 3547 $context->options = $options; 3548 $context->optgroups = $optgroups; 3549 $template = 'core_admin/setting_configselect_optgroup'; 3550 } 3551 3552 foreach ($this->choices as $value => $name) { 3553 $options[] = [ 3554 'value' => $value, 3555 'name' => $name, 3556 'selected' => (string) $value == $data 3557 ]; 3558 } 3559 $context->options = $options; 3560 $context->readonly = $this->is_readonly(); 3561 3562 $element = $OUTPUT->render_from_template($template, $context); 3563 3564 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, $warning, $defaultinfo, $query); 3565 } 3566 } 3567 3568 /** 3569 * Select multiple items from list 3570 * 3571 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3572 */ 3573 class admin_setting_configmultiselect extends admin_setting_configselect { 3574 /** 3575 * Constructor 3576 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 3577 * @param string $visiblename localised 3578 * @param string $description long localised info 3579 * @param array $defaultsetting array of selected items 3580 * @param array $choices array of $value=>$label for each list item 3581 */ 3582 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) { 3583 parent::__construct($name, $visiblename, $description, $defaultsetting, $choices); 3584 } 3585 3586 /** 3587 * Returns the select setting(s) 3588 * 3589 * @return mixed null or array. Null if no settings else array of setting(s) 3590 */ 3591 public function get_setting() { 3592 $result = $this->config_read($this->name); 3593 if (is_null($result)) { 3594 return NULL; 3595 } 3596 if ($result === '') { 3597 return array(); 3598 } 3599 return explode(',', $result); 3600 } 3601 3602 /** 3603 * Saves setting(s) provided through $data 3604 * 3605 * Potential bug in the works should anyone call with this function 3606 * using a vartype that is not an array 3607 * 3608 * @param array $data 3609 */ 3610 public function write_setting($data) { 3611 if (!is_array($data)) { 3612 return ''; //ignore it 3613 } 3614 if (!$this->load_choices() or empty($this->choices)) { 3615 return ''; 3616 } 3617 3618 unset($data['xxxxx']); 3619 3620 $save = array(); 3621 foreach ($data as $value) { 3622 if (!array_key_exists($value, $this->choices)) { 3623 continue; // ignore it 3624 } 3625 $save[] = $value; 3626 } 3627 3628 return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin')); 3629 } 3630 3631 /** 3632 * Is setting related to query text - used when searching 3633 * 3634 * @param string $query 3635 * @return bool true if related, false if not 3636 */ 3637 public function is_related($query) { 3638 if (!$this->load_choices() or empty($this->choices)) { 3639 return false; 3640 } 3641 if (parent::is_related($query)) { 3642 return true; 3643 } 3644 3645 foreach ($this->choices as $desc) { 3646 if (strpos(core_text::strtolower($desc), $query) !== false) { 3647 return true; 3648 } 3649 } 3650 return false; 3651 } 3652 3653 /** 3654 * Returns XHTML multi-select field 3655 * 3656 * @todo Add vartype handling to ensure $data is an array 3657 * @param array $data Array of values to select by default 3658 * @param string $query 3659 * @return string XHTML multi-select field 3660 */ 3661 public function output_html($data, $query='') { 3662 global $OUTPUT; 3663 3664 if (!$this->load_choices() or empty($this->choices)) { 3665 return ''; 3666 } 3667 3668 $default = $this->get_defaultsetting(); 3669 if (is_null($default)) { 3670 $default = array(); 3671 } 3672 if (is_null($data)) { 3673 $data = array(); 3674 } 3675 3676 $context = (object) [ 3677 'id' => $this->get_id(), 3678 'name' => $this->get_full_name(), 3679 'size' => min(10, count($this->choices)) 3680 ]; 3681 3682 $defaults = []; 3683 $options = []; 3684 $template = 'core_admin/setting_configmultiselect'; 3685 3686 if (!empty($this->optgroups)) { 3687 $optgroups = []; 3688 foreach ($this->optgroups as $label => $choices) { 3689 $optgroup = array('label' => $label, 'options' => []); 3690 foreach ($choices as $value => $name) { 3691 if (in_array($value, $default)) { 3692 $defaults[] = $name; 3693 } 3694 $optgroup['options'][] = [ 3695 'value' => $value, 3696 'name' => $name, 3697 'selected' => in_array($value, $data) 3698 ]; 3699 unset($this->choices[$value]); 3700 } 3701 $optgroups[] = $optgroup; 3702 } 3703 $context->optgroups = $optgroups; 3704 $template = 'core_admin/setting_configmultiselect_optgroup'; 3705 } 3706 3707 foreach ($this->choices as $value => $name) { 3708 if (in_array($value, $default)) { 3709 $defaults[] = $name; 3710 } 3711 $options[] = [ 3712 'value' => $value, 3713 'name' => $name, 3714 'selected' => in_array($value, $data) 3715 ]; 3716 } 3717 $context->options = $options; 3718 $context->readonly = $this->is_readonly(); 3719 3720 if (is_null($default)) { 3721 $defaultinfo = NULL; 3722 } if (!empty($defaults)) { 3723 $defaultinfo = implode(', ', $defaults); 3724 } else { 3725 $defaultinfo = get_string('none'); 3726 } 3727 3728 $element = $OUTPUT->render_from_template($template, $context); 3729 3730 return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $defaultinfo, $query); 3731 } 3732 } 3733 3734 /** 3735 * Time selector 3736 * 3737 * This is a liiitle bit messy. we're using two selects, but we're returning 3738 * them as an array named after $name (so we only use $name2 internally for the setting) 3739 * 3740 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3741 */ 3742 class admin_setting_configtime extends admin_setting { 3743 /** @var string Used for setting second select (minutes) */ 3744 public $name2; 3745 3746 /** 3747 * Constructor 3748 * @param string $hoursname setting for hours 3749 * @param string $minutesname setting for hours 3750 * @param string $visiblename localised 3751 * @param string $description long localised info 3752 * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes 3753 */ 3754 public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) { 3755 $this->name2 = $minutesname; 3756 parent::__construct($hoursname, $visiblename, $description, $defaultsetting); 3757 } 3758 3759 /** 3760 * Get the selected time 3761 * 3762 * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set 3763 */ 3764 public function get_setting() { 3765 $result1 = $this->config_read($this->name); 3766 $result2 = $this->config_read($this->name2); 3767 if (is_null($result1) or is_null($result2)) { 3768 return NULL; 3769 } 3770 3771 return array('h' => $result1, 'm' => $result2); 3772 } 3773 3774 /** 3775 * Store the time (hours and minutes) 3776 * 3777 * @param array $data Must be form 'h'=>xx, 'm'=>xx 3778 * @return bool true if success, false if not 3779 */ 3780 public function write_setting($data) { 3781 if (!is_array($data)) { 3782 return ''; 3783 } 3784 3785 $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']); 3786 return ($result ? '' : get_string('errorsetting', 'admin')); 3787 } 3788 3789 /** 3790 * Returns XHTML time select fields 3791 * 3792 * @param array $data Must be form 'h'=>xx, 'm'=>xx 3793 * @param string $query 3794 * @return string XHTML time select fields and wrapping div(s) 3795 */ 3796 public function output_html($data, $query='') { 3797 global $OUTPUT; 3798 3799 $default = $this->get_defaultsetting(); 3800 if (is_array($default)) { 3801 $defaultinfo = $default['h'].':'.$default['m']; 3802 } else { 3803 $defaultinfo = NULL; 3804 } 3805 3806 $context = (object) [ 3807 'id' => $this->get_id(), 3808 'name' => $this->get_full_name(), 3809 'readonly' => $this->is_readonly(), 3810 'hours' => array_map(function($i) use ($data) { 3811 return [ 3812 'value' => $i, 3813 'name' => $i, 3814 'selected' => $i == $data['h'] 3815 ]; 3816 }, range(0, 23)), 3817 'minutes' => array_map(function($i) use ($data) { 3818 return [ 3819 'value' => $i, 3820 'name' => $i, 3821 'selected' => $i == $data['m'] 3822 ]; 3823 }, range(0, 59, 5)) 3824 ]; 3825 3826 $element = $OUTPUT->render_from_template('core_admin/setting_configtime', $context); 3827 3828 return format_admin_setting($this, $this->visiblename, $element, $this->description, 3829 $this->get_id() . 'h', '', $defaultinfo, $query); 3830 } 3831 3832 } 3833 3834 3835 /** 3836 * Seconds duration setting. 3837 * 3838 * @copyright 2012 Petr Skoda (http://skodak.org) 3839 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3840 */ 3841 class admin_setting_configduration extends admin_setting { 3842 3843 /** @var int default duration unit */ 3844 protected $defaultunit; 3845 /** @var callable|null Validation function */ 3846 protected $validatefunction = null; 3847 3848 /** 3849 * Constructor 3850 * @param string $name unique ascii name, either 'mysetting' for settings that in config, 3851 * or 'myplugin/mysetting' for ones in config_plugins. 3852 * @param string $visiblename localised name 3853 * @param string $description localised long description 3854 * @param mixed $defaultsetting string or array depending on implementation 3855 * @param int $defaultunit - day, week, etc. (in seconds) 3856 */ 3857 public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) { 3858 if (is_number($defaultsetting)) { 3859 $defaultsetting = self::parse_seconds($defaultsetting); 3860 } 3861 $units = self::get_units(); 3862 if (isset($units[$defaultunit])) { 3863 $this->defaultunit = $defaultunit; 3864 } else { 3865 $this->defaultunit = 86400; 3866 } 3867 parent::__construct($name, $visiblename, $description, $defaultsetting); 3868 } 3869 3870 /** 3871 * Sets a validate function. 3872 * 3873 * The callback will be passed one parameter, the new setting value, and should return either 3874 * an empty string '' if the value is OK, or an error message if not. 3875 * 3876 * @param callable|null $validatefunction Validate function or null to clear 3877 * @since Moodle 3.10 3878 */ 3879 public function set_validate_function(?callable $validatefunction = null) { 3880 $this->validatefunction = $validatefunction; 3881 } 3882 3883 /** 3884 * Validate the setting. This uses the callback function if provided; subclasses could override 3885 * to carry out validation directly in the class. 3886 * 3887 * @param int $data New value being set 3888 * @return string Empty string if valid, or error message text 3889 * @since Moodle 3.10 3890 */ 3891 protected function validate_setting(int $data): string { 3892 // If validation function is specified, call it now. 3893 if ($this->validatefunction) { 3894 return call_user_func($this->validatefunction, $data); 3895 } else { 3896 if ($data < 0) { 3897 return get_string('errorsetting', 'admin'); 3898 } 3899 return ''; 3900 } 3901 } 3902 3903 /** 3904 * Returns selectable units. 3905 * @static 3906 * @return array 3907 */ 3908 protected static function get_units() { 3909 return array( 3910 604800 => get_string('weeks'), 3911 86400 => get_string('days'), 3912 3600 => get_string('hours'), 3913 60 => get_string('minutes'), 3914 1 => get_string('seconds'), 3915 ); 3916 } 3917 3918 /** 3919 * Converts seconds to some more user friendly string. 3920 * @static 3921 * @param int $seconds 3922 * @return string 3923 */ 3924 protected static function get_duration_text($seconds) { 3925 if (empty($seconds)) { 3926 return get_string('none'); 3927 } 3928 $data = self::parse_seconds($seconds); 3929 switch ($data['u']) { 3930 case (60*60*24*7): 3931 return get_string('numweeks', '', $data['v']); 3932 case (60*60*24): 3933 return get_string('numdays', '', $data['v']); 3934 case (60*60): 3935 return get_string('numhours', '', $data['v']); 3936 case (60): 3937 return get_string('numminutes', '', $data['v']); 3938 default: 3939 return get_string('numseconds', '', $data['v']*$data['u']); 3940 } 3941 } 3942 3943 /** 3944 * Finds suitable units for given duration. 3945 * @static 3946 * @param int $seconds 3947 * @return array 3948 */ 3949 protected static function parse_seconds($seconds) { 3950 foreach (self::get_units() as $unit => $unused) { 3951 if ($seconds % $unit === 0) { 3952 return array('v'=>(int)($seconds/$unit), 'u'=>$unit); 3953 } 3954 } 3955 return array('v'=>(int)$seconds, 'u'=>1); 3956 } 3957 3958 /** 3959 * Get the selected duration as array. 3960 * 3961 * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set 3962 */ 3963 public function get_setting() { 3964 $seconds = $this->config_read($this->name); 3965 if (is_null($seconds)) { 3966 return null; 3967 } 3968 3969 return self::parse_seconds($seconds); 3970 } 3971 3972 /** 3973 * Store the duration as seconds. 3974 * 3975 * @param array $data Must be form 'h'=>xx, 'm'=>xx 3976 * @return bool true if success, false if not 3977 */ 3978 public function write_setting($data) { 3979 if (!is_array($data)) { 3980 return ''; 3981 } 3982 3983 $unit = (int)$data['u']; 3984 $value = (int)$data['v']; 3985 $seconds = $value * $unit; 3986 3987 // Validate the new setting. 3988 $error = $this->validate_setting($seconds); 3989 if ($error) { 3990 return $error; 3991 } 3992 3993 $result = $this->config_write($this->name, $seconds); 3994 return ($result ? '' : get_string('errorsetting', 'admin')); 3995 } 3996 3997 /** 3998 * Returns duration text+select fields. 3999 * 4000 * @param array $data Must be form 'v'=>xx, 'u'=>xx 4001 * @param string $query 4002 * @return string duration text+select fields and wrapping div(s) 4003 */ 4004 public function output_html($data, $query='') { 4005 global $OUTPUT; 4006 4007 $default = $this->get_defaultsetting(); 4008 if (is_number($default)) { 4009 $defaultinfo = self::get_duration_text($default); 4010 } else if (is_array($default)) { 4011 $defaultinfo = self::get_duration_text($default['v']*$default['u']); 4012 } else { 4013 $defaultinfo = null; 4014 } 4015 4016 $inputid = $this->get_id() . 'v'; 4017 $units = self::get_units(); 4018 $defaultunit = $this->defaultunit; 4019 4020 $context = (object) [ 4021 'id' => $this->get_id(), 4022 'name' => $this->get_full_name(), 4023 'value' => $data['v'], 4024 'readonly' => $this->is_readonly(), 4025 'options' => array_map(function($unit) use ($units, $data, $defaultunit) { 4026 return [ 4027 'value' => $unit, 4028 'name' => $units[$unit], 4029 'selected' => ($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u'] 4030 ]; 4031 }, array_keys($units)) 4032 ]; 4033 4034 $element = $OUTPUT->render_from_template('core_admin/setting_configduration', $context); 4035 4036 return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query); 4037 } 4038 } 4039 4040 4041 /** 4042 * Seconds duration setting with an advanced checkbox, that controls a additional 4043 * $name.'_adv' setting. 4044 * 4045 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4046 * @copyright 2014 The Open University 4047 */ 4048 class admin_setting_configduration_with_advanced extends admin_setting_configduration { 4049 /** 4050 * Constructor 4051 * @param string $name unique ascii name, either 'mysetting' for settings that in config, 4052 * or 'myplugin/mysetting' for ones in config_plugins. 4053 * @param string $visiblename localised name 4054 * @param string $description localised long description 4055 * @param array $defaultsetting array of int value, and bool whether it is 4056 * is advanced by default. 4057 * @param int $defaultunit - day, week, etc. (in seconds) 4058 */ 4059 public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) { 4060 parent::__construct($name, $visiblename, $description, $defaultsetting['value'], $defaultunit); 4061 $this->set_advanced_flag_options(admin_setting_flag::ENABLED, !empty($defaultsetting['adv'])); 4062 } 4063 } 4064 4065 4066 /** 4067 * Used to validate a textarea used for ip addresses 4068 * 4069 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4070 * @copyright 2011 Petr Skoda (http://skodak.org) 4071 */ 4072 class admin_setting_configiplist extends admin_setting_configtextarea { 4073 4074 /** 4075 * Validate the contents of the textarea as IP addresses 4076 * 4077 * Used to validate a new line separated list of IP addresses collected from 4078 * a textarea control 4079 * 4080 * @param string $data A list of IP Addresses separated by new lines 4081 * @return mixed bool true for success or string:error on failure 4082 */ 4083 public function validate($data) { 4084 if(!empty($data)) { 4085 $lines = explode("\n", $data); 4086 } else { 4087 return true; 4088 } 4089 $result = true; 4090 $badips = array(); 4091 foreach ($lines as $line) { 4092 $tokens = explode('#', $line); 4093 $ip = trim($tokens[0]); 4094 if (empty($ip)) { 4095 continue; 4096 } 4097 if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) || 4098 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) || 4099 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) { 4100 } else { 4101 $result = false; 4102 $badips[] = $ip; 4103 } 4104 } 4105 if($result) { 4106 return true; 4107 } else { 4108 return get_string('validateiperror', 'admin', join(', ', $badips)); 4109 } 4110 } 4111 } 4112 4113 /** 4114 * Used to validate a textarea used for domain names, wildcard domain names and IP addresses/ranges (both IPv4 and IPv6 format). 4115 * 4116 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4117 * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com) 4118 */ 4119 class admin_setting_configmixedhostiplist extends admin_setting_configtextarea { 4120 4121 /** 4122 * Validate the contents of the textarea as either IP addresses, domain name or wildcard domain name (RFC 4592). 4123 * Used to validate a new line separated list of entries collected from a textarea control. 4124 * 4125 * This setting provides support for internationalised domain names (IDNs), however, such UTF-8 names will be converted to 4126 * their ascii-compatible encoding (punycode) on save, and converted back to their UTF-8 representation when fetched 4127 * via the get_setting() method, which has been overriden. 4128 * 4129 * @param string $data A list of FQDNs, DNS wildcard format domains, and IP addresses, separated by new lines. 4130 * @return mixed bool true for success or string:error on failure 4131 */ 4132 public function validate($data) { 4133 if (empty($data)) { 4134 return true; 4135 } 4136 $entries = explode("\n", $data); 4137 $badentries = []; 4138 4139 foreach ($entries as $key => $entry) { 4140 $entry = trim($entry); 4141 if (empty($entry)) { 4142 return get_string('validateemptylineerror', 'admin'); 4143 } 4144 4145 // Validate each string entry against the supported formats. 4146 if (\core\ip_utils::is_ip_address($entry) || \core\ip_utils::is_ipv6_range($entry) 4147 || \core\ip_utils::is_ipv4_range($entry) || \core\ip_utils::is_domain_name($entry) 4148 || \core\ip_utils::is_domain_matching_pattern($entry)) { 4149 continue; 4150 } 4151 4152 // Otherwise, the entry is invalid. 4153 $badentries[] = $entry; 4154 } 4155 4156 if ($badentries) { 4157 return get_string('validateerrorlist', 'admin', join(', ', $badentries)); 4158 } 4159 return true; 4160 } 4161 4162 /** 4163 * Convert any lines containing international domain names (IDNs) to their ascii-compatible encoding (ACE). 4164 * 4165 * @param string $data the setting data, as sent from the web form. 4166 * @return string $data the setting data, with all IDNs converted (using punycode) to their ascii encoded version. 4167 */ 4168 protected function ace_encode($data) { 4169 if (empty($data)) { 4170 return $data; 4171 } 4172 $entries = explode("\n", $data); 4173 foreach ($entries as $key => $entry) { 4174 $entry = trim($entry); 4175 // This regex matches any string that has non-ascii character. 4176 if (preg_match('/[^\x00-\x7f]/', $entry)) { 4177 // If we can convert the unicode string to an idn, do so. 4178 // Otherwise, leave the original unicode string alone and let the validation function handle it (it will fail). 4179 $val = idn_to_ascii($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); 4180 $entries[$key] = $val ? $val : $entry; 4181 } 4182 } 4183 return implode("\n", $entries); 4184 } 4185 4186 /** 4187 * Decode any ascii-encoded domain names back to their utf-8 representation for display. 4188 * 4189 * @param string $data the setting data, as found in the database. 4190 * @return string $data the setting data, with all ascii-encoded IDNs decoded back to their utf-8 representation. 4191 */ 4192 protected function ace_decode($data) { 4193 $entries = explode("\n", $data); 4194 foreach ($entries as $key => $entry) { 4195 $entry = trim($entry); 4196 if (strpos($entry, 'xn--') !== false) { 4197 $entries[$key] = idn_to_utf8($entry, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); 4198 } 4199 } 4200 return implode("\n", $entries); 4201 } 4202 4203 /** 4204 * Override, providing utf8-decoding for ascii-encoded IDN strings. 4205 * 4206 * @return mixed returns punycode-converted setting string if successful, else null. 4207 */ 4208 public function get_setting() { 4209 // Here, we need to decode any ascii-encoded IDNs back to their native, utf-8 representation. 4210 $data = $this->config_read($this->name); 4211 if (function_exists('idn_to_utf8') && !is_null($data)) { 4212 $data = $this->ace_decode($data); 4213 } 4214 return $data; 4215 } 4216 4217 /** 4218 * Override, providing ascii-encoding for utf8 (native) IDN strings. 4219 * 4220 * @param string $data 4221 * @return string 4222 */ 4223 public function write_setting($data) { 4224 if ($this->paramtype === PARAM_INT and $data === '') { 4225 // Do not complain if '' used instead of 0. 4226 $data = 0; 4227 } 4228 4229 // Try to convert any non-ascii domains to ACE prior to validation - we can't modify anything in validate! 4230 if (function_exists('idn_to_ascii')) { 4231 $data = $this->ace_encode($data); 4232 } 4233 4234 $validated = $this->validate($data); 4235 if ($validated !== true) { 4236 return $validated; 4237 } 4238 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); 4239 } 4240 } 4241 4242 /** 4243 * Used to validate a textarea used for port numbers. 4244 * 4245 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4246 * @copyright 2016 Jake Dallimore (jrhdallimore@gmail.com) 4247 */ 4248 class admin_setting_configportlist extends admin_setting_configtextarea { 4249 4250 /** 4251 * Validate the contents of the textarea as port numbers. 4252 * Used to validate a new line separated list of ports collected from a textarea control. 4253 * 4254 * @param string $data A list of ports separated by new lines 4255 * @return mixed bool true for success or string:error on failure 4256 */ 4257 public function validate($data) { 4258 if (empty($data)) { 4259 return true; 4260 } 4261 $ports = explode("\n", $data); 4262 $badentries = []; 4263 foreach ($ports as $port) { 4264 $port = trim($port); 4265 if (empty($port)) { 4266 return get_string('validateemptylineerror', 'admin'); 4267 } 4268 4269 // Is the string a valid integer number? 4270 if (strval(intval($port)) !== $port || intval($port) <= 0) { 4271 $badentries[] = $port; 4272 } 4273 } 4274 if ($badentries) { 4275 return get_string('validateerrorlist', 'admin', $badentries); 4276 } 4277 return true; 4278 } 4279 } 4280 4281 4282 /** 4283 * An admin setting for selecting one or more users who have a capability 4284 * in the system context 4285 * 4286 * An admin setting for selecting one or more users, who have a particular capability 4287 * in the system context. Warning, make sure the list will never be too long. There is 4288 * no paging or searching of this list. 4289 * 4290 * To correctly get a list of users from this config setting, you need to call the 4291 * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php. 4292 * 4293 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4294 */ 4295 class admin_setting_users_with_capability extends admin_setting_configmultiselect { 4296 /** @var string The capabilities name */ 4297 protected $capability; 4298 /** @var int include admin users too */ 4299 protected $includeadmins; 4300 4301 /** 4302 * Constructor. 4303 * 4304 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. 4305 * @param string $visiblename localised name 4306 * @param string $description localised long description 4307 * @param array $defaultsetting array of usernames 4308 * @param string $capability string capability name. 4309 * @param bool $includeadmins include administrators 4310 */ 4311 function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) { 4312 $this->capability = $capability; 4313 $this->includeadmins = $includeadmins; 4314 parent::__construct($name, $visiblename, $description, $defaultsetting, NULL); 4315 } 4316 4317 /** 4318 * Load all of the uses who have the capability into choice array 4319 * 4320 * @return bool Always returns true 4321 */ 4322 function load_choices() { 4323 if (is_array($this->choices)) { 4324 return true; 4325 } 4326 list($sort, $sortparams) = users_order_by_sql('u'); 4327 if (!empty($sortparams)) { 4328 throw new coding_exception('users_order_by_sql returned some query parameters. ' . 4329 'This is unexpected, and a problem because there is no way to pass these ' . 4330 'parameters to get_users_by_capability. See MDL-34657.'); 4331 } 4332 $userfieldsapi = \core_user\fields::for_name(); 4333 $userfields = 'u.id, u.username, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects; 4334 $users = get_users_by_capability(context_system::instance(), $this->capability, $userfields, $sort); 4335 $this->choices = array( 4336 '$@NONE@$' => get_string('nobody'), 4337 '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)), 4338 ); 4339 if ($this->includeadmins) { 4340 $admins = get_admins(); 4341 foreach ($admins as $user) { 4342 $this->choices[$user->id] = fullname($user); 4343 } 4344 } 4345 if (is_array($users)) { 4346 foreach ($users as $user) { 4347 $this->choices[$user->id] = fullname($user); 4348 } 4349 } 4350 return true; 4351 } 4352 4353 /** 4354 * Returns the default setting for class 4355 * 4356 * @return mixed Array, or string. Empty string if no default 4357 */ 4358 public function get_defaultsetting() { 4359 $this->load_choices(); 4360 $defaultsetting = parent::get_defaultsetting(); 4361 if (empty($defaultsetting)) { 4362 return array('$@NONE@$'); 4363 } else if (array_key_exists($defaultsetting, $this->choices)) { 4364 return $defaultsetting; 4365 } else { 4366 return ''; 4367 } 4368 } 4369 4370 /** 4371 * Returns the current setting 4372 * 4373 * @return mixed array or string 4374 */ 4375 public function get_setting() { 4376 $result = parent::get_setting(); 4377 if ($result === null) { 4378 // this is necessary for settings upgrade 4379 return null; 4380 } 4381 if (empty($result)) { 4382 $result = array('$@NONE@$'); 4383 } 4384 return $result; 4385 } 4386 4387 /** 4388 * Save the chosen setting provided as $data 4389 * 4390 * @param array $data 4391 * @return mixed string or array 4392 */ 4393 public function write_setting($data) { 4394 // If all is selected, remove any explicit options. 4395 if (in_array('$@ALL@$', $data)) { 4396 $data = array('$@ALL@$'); 4397 } 4398 // None never needs to be written to the DB. 4399 if (in_array('$@NONE@$', $data)) { 4400 unset($data[array_search('$@NONE@$', $data)]); 4401 } 4402 return parent::write_setting($data); 4403 } 4404 } 4405 4406 4407 /** 4408 * Special checkbox for calendar - resets SESSION vars. 4409 * 4410 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4411 */ 4412 class admin_setting_special_adminseesall extends admin_setting_configcheckbox { 4413 /** 4414 * Calls the parent::__construct with default values 4415 * 4416 * name => calendar_adminseesall 4417 * visiblename => get_string('adminseesall', 'admin') 4418 * description => get_string('helpadminseesall', 'admin') 4419 * defaultsetting => 0 4420 */ 4421 public function __construct() { 4422 parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'), 4423 get_string('helpadminseesall', 'admin'), '0'); 4424 } 4425 4426 /** 4427 * Stores the setting passed in $data 4428 * 4429 * @param mixed gets converted to string for comparison 4430 * @return string empty string or error message 4431 */ 4432 public function write_setting($data) { 4433 global $SESSION; 4434 return parent::write_setting($data); 4435 } 4436 } 4437 4438 /** 4439 * Special select for settings that are altered in setup.php and can not be altered on the fly 4440 * 4441 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4442 */ 4443 class admin_setting_special_selectsetup extends admin_setting_configselect { 4444 /** 4445 * Reads the setting directly from the database 4446 * 4447 * @return mixed 4448 */ 4449 public function get_setting() { 4450 // read directly from db! 4451 return get_config(NULL, $this->name); 4452 } 4453 4454 /** 4455 * Save the setting passed in $data 4456 * 4457 * @param string $data The setting to save 4458 * @return string empty or error message 4459 */ 4460 public function write_setting($data) { 4461 global $CFG; 4462 // do not change active CFG setting! 4463 $current = $CFG->{$this->name}; 4464 $result = parent::write_setting($data); 4465 $CFG->{$this->name} = $current; 4466 return $result; 4467 } 4468 } 4469 4470 4471 /** 4472 * Special select for frontpage - stores data in course table 4473 * 4474 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4475 */ 4476 class admin_setting_sitesetselect extends admin_setting_configselect { 4477 /** 4478 * Returns the site name for the selected site 4479 * 4480 * @see get_site() 4481 * @return string The site name of the selected site 4482 */ 4483 public function get_setting() { 4484 $site = course_get_format(get_site())->get_course(); 4485 return $site->{$this->name}; 4486 } 4487 4488 /** 4489 * Updates the database and save the setting 4490 * 4491 * @param string data 4492 * @return string empty or error message 4493 */ 4494 public function write_setting($data) { 4495 global $DB, $SITE, $COURSE; 4496 if (!in_array($data, array_keys($this->choices))) { 4497 return get_string('errorsetting', 'admin'); 4498 } 4499 $record = new stdClass(); 4500 $record->id = SITEID; 4501 $temp = $this->name; 4502 $record->$temp = $data; 4503 $record->timemodified = time(); 4504 4505 course_get_format($SITE)->update_course_format_options($record); 4506 $DB->update_record('course', $record); 4507 4508 // Reset caches. 4509 $SITE = $DB->get_record('course', array('id'=>$SITE->id), '*', MUST_EXIST); 4510 if ($SITE->id == $COURSE->id) { 4511 $COURSE = $SITE; 4512 } 4513 format_base::reset_course_cache($SITE->id); 4514 4515 return ''; 4516 4517 } 4518 } 4519 4520 4521 /** 4522 * Select for blog's bloglevel setting: if set to 0, will set blog_menu 4523 * block to hidden. 4524 * 4525 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4526 */ 4527 class admin_setting_bloglevel extends admin_setting_configselect { 4528 /** 4529 * Updates the database and save the setting 4530 * 4531 * @param string data 4532 * @return string empty or error message 4533 */ 4534 public function write_setting($data) { 4535 global $DB, $CFG; 4536 if ($data == 0) { 4537 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1"); 4538 foreach ($blogblocks as $block) { 4539 $DB->set_field('block', 'visible', 0, array('id' => $block->id)); 4540 } 4541 } else { 4542 // reenable all blocks only when switching from disabled blogs 4543 if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) { 4544 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0"); 4545 foreach ($blogblocks as $block) { 4546 $DB->set_field('block', 'visible', 1, array('id' => $block->id)); 4547 } 4548 } 4549 } 4550 return parent::write_setting($data); 4551 } 4552 } 4553 4554 4555 /** 4556 * Special select - lists on the frontpage - hacky 4557 * 4558 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4559 */ 4560 class admin_setting_courselist_frontpage extends admin_setting { 4561 /** @var array Array of choices value=>label */ 4562 public $choices; 4563 4564 /** 4565 * Construct override, requires one param 4566 * 4567 * @param bool $loggedin Is the user logged in 4568 */ 4569 public function __construct($loggedin) { 4570 global $CFG; 4571 require_once($CFG->dirroot.'/course/lib.php'); 4572 $name = 'frontpage'.($loggedin ? 'loggedin' : ''); 4573 $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin'); 4574 $description =