/lib/adminlib.php
PHP | 11709 lines | 6516 code | 1364 blank | 3829 comment | 1002 complexity | 716b0648d4355c6bc628990edd7e7ab0 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * Functions and classes used during installation, upgrades and for admin settings.
- *
- * ADMIN SETTINGS TREE INTRODUCTION
- *
- * This file performs the following tasks:
- * -it defines the necessary objects and interfaces to build the Moodle
- * admin hierarchy
- * -it defines the admin_externalpage_setup()
- *
- * ADMIN_SETTING OBJECTS
- *
- * Moodle settings are represented by objects that inherit from the admin_setting
- * class. These objects encapsulate how to read a setting, how to write a new value
- * to a setting, and how to appropriately display the HTML to modify the setting.
- *
- * ADMIN_SETTINGPAGE OBJECTS
- *
- * The admin_setting objects are then grouped into admin_settingpages. The latter
- * appear in the Moodle admin tree block. All interaction with admin_settingpage
- * objects is handled by the admin/settings.php file.
- *
- * ADMIN_EXTERNALPAGE OBJECTS
- *
- * There are some settings in Moodle that are too complex to (efficiently) handle
- * with admin_settingpages. (Consider, for example, user management and displaying
- * lists of users.) In this case, we use the admin_externalpage object. This object
- * places a link to an external PHP file in the admin tree block.
- *
- * If you're using an admin_externalpage object for some settings, you can take
- * advantage of the admin_externalpage_* functions. For example, suppose you wanted
- * to add a foo.php file into admin. First off, you add the following line to
- * admin/settings/first.php (at the end of the file) or to some other file in
- * admin/settings:
- * <code>
- * $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
- * $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
- * </code>
- *
- * Next, in foo.php, your file structure would resemble the following:
- * <code>
- * require(__DIR__.'/../../config.php');
- * require_once($CFG->libdir.'/adminlib.php');
- * admin_externalpage_setup('foo');
- * // functionality like processing form submissions goes here
- * echo $OUTPUT->header();
- * // your HTML goes here
- * echo $OUTPUT->footer();
- * </code>
- *
- * The admin_externalpage_setup() function call ensures the user is logged in,
- * and makes sure that they have the proper role permission to access the page.
- * It also configures all $PAGE properties needed for navigation.
- *
- * ADMIN_CATEGORY OBJECTS
- *
- * Above and beyond all this, we have admin_category objects. These objects
- * appear as folders in the admin tree block. They contain admin_settingpage's,
- * admin_externalpage's, and other admin_category's.
- *
- * OTHER NOTES
- *
- * admin_settingpage's, admin_externalpage's, and admin_category's all inherit
- * from part_of_admin_tree (a pseudointerface). This interface insists that
- * a class has a check_access method for access permissions, a locate method
- * used to find a specific node in the admin tree and find parent path.
- *
- * admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
- * interface ensures that the class implements a recursive add function which
- * accepts a part_of_admin_tree object and searches for the proper place to
- * put it. parentable_part_of_admin_tree implies part_of_admin_tree.
- *
- * Please note that the $this->name field of any part_of_admin_tree must be
- * UNIQUE throughout the ENTIRE admin tree.
- *
- * The $this->name field of an admin_setting object (which is *not* part_of_
- * admin_tree) must be unique on the respective admin_settingpage where it is
- * used.
- *
- * Original author: Vincenzo K. Marcovecchio
- * Maintainer: Petr Skoda
- *
- * @package core
- * @subpackage admin
- * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- use core_admin\local\settings\linkable_settings_page;
- defined('MOODLE_INTERNAL') || die();
- /// Add libraries
- require_once($CFG->libdir.'/ddllib.php');
- require_once($CFG->libdir.'/xmlize.php');
- require_once($CFG->libdir.'/messagelib.php');
- // Add classes, traits, and interfaces which should be autoloaded.
- // The autoloader is configured late in setup.php, after ABORT_AFTER_CONFIG.
- // This is also required where the setup system is not included at all.
- require_once($CFG->dirroot.'/admin/classes/local/settings/linkable_settings_page.php');
- define('INSECURE_DATAROOT_WARNING', 1);
- define('INSECURE_DATAROOT_ERROR', 2);
- /**
- * Automatically clean-up all plugin data and remove the plugin DB tables
- *
- * NOTE: do not call directly, use new /admin/plugins.php?uninstall=component instead!
- *
- * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
- * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
- * @uses global $OUTPUT to produce notices and other messages
- * @return void
- */
- function uninstall_plugin($type, $name) {
- global $CFG, $DB, $OUTPUT;
- // This may take a long time.
- core_php_time_limit::raise();
- // Recursively uninstall all subplugins first.
- $subplugintypes = core_component::get_plugin_types_with_subplugins();
- if (isset($subplugintypes[$type])) {
- $base = core_component::get_plugin_directory($type, $name);
- $subpluginsfile = "{$base}/db/subplugins.json";
- if (file_exists($subpluginsfile)) {
- $subplugins = (array) json_decode(file_get_contents($subpluginsfile))->plugintypes;
- } else if (file_exists("{$base}/db/subplugins.php")) {
- debugging('Use of subplugins.php has been deprecated. ' .
- 'Please update your plugin to provide a subplugins.json file instead.',
- DEBUG_DEVELOPER);
- $subplugins = [];
- include("{$base}/db/subplugins.php");
- }
- if (!empty($subplugins)) {
- foreach (array_keys($subplugins) as $subplugintype) {
- $instances = core_component::get_plugin_list($subplugintype);
- foreach ($instances as $subpluginname => $notusedpluginpath) {
- uninstall_plugin($subplugintype, $subpluginname);
- }
- }
- }
- }
- $component = $type . '_' . $name; // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
- if ($type === 'mod') {
- $pluginname = $name; // eg. 'forum'
- if (get_string_manager()->string_exists('modulename', $component)) {
- $strpluginname = get_string('modulename', $component);
- } else {
- $strpluginname = $component;
- }
- } else {
- $pluginname = $component;
- if (get_string_manager()->string_exists('pluginname', $component)) {
- $strpluginname = get_string('pluginname', $component);
- } else {
- $strpluginname = $component;
- }
- }
- echo $OUTPUT->heading($pluginname);
- // Delete all tag areas, collections and instances associated with this plugin.
- core_tag_area::uninstall($component);
- // Custom plugin uninstall.
- $plugindirectory = core_component::get_plugin_directory($type, $name);
- $uninstalllib = $plugindirectory . '/db/uninstall.php';
- if (file_exists($uninstalllib)) {
- require_once($uninstalllib);
- $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall'; // eg. 'xmldb_workshop_uninstall()'
- if (function_exists($uninstallfunction)) {
- // Do not verify result, let plugin complain if necessary.
- $uninstallfunction();
- }
- }
- // Specific plugin type cleanup.
- $plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
- if ($plugininfo) {
- $plugininfo->uninstall_cleanup();
- core_plugin_manager::reset_caches();
- }
- $plugininfo = null;
- // perform clean-up task common for all the plugin/subplugin types
- //delete the web service functions and pre-built services
- require_once($CFG->dirroot.'/lib/externallib.php');
- external_delete_descriptions($component);
- // delete calendar events
- $DB->delete_records('event', array('modulename' => $pluginname));
- $DB->delete_records('event', ['component' => $component]);
- // Delete scheduled tasks.
- $DB->delete_records('task_adhoc', ['component' => $component]);
- $DB->delete_records('task_scheduled', array('component' => $component));
- // Delete Inbound Message datakeys.
- $DB->delete_records_select('messageinbound_datakeys',
- 'handler IN (SELECT id FROM {messageinbound_handlers} WHERE component = ?)', array($component));
- // Delete Inbound Message handlers.
- $DB->delete_records('messageinbound_handlers', array('component' => $component));
- // delete all the logs
- $DB->delete_records('log', array('module' => $pluginname));
- // delete log_display information
- $DB->delete_records('log_display', array('component' => $component));
- // delete the module configuration records
- unset_all_config_for_plugin($component);
- if ($type === 'mod') {
- unset_all_config_for_plugin($pluginname);
- }
- // delete message provider
- message_provider_uninstall($component);
- // delete the plugin tables
- $xmldbfilepath = $plugindirectory . '/db/install.xml';
- drop_plugin_tables($component, $xmldbfilepath, false);
- if ($type === 'mod' or $type === 'block') {
- // non-frankenstyle table prefixes
- drop_plugin_tables($name, $xmldbfilepath, false);
- }
- // delete the capabilities that were defined by this module
- capabilities_cleanup($component);
- // Delete all remaining files in the filepool owned by the component.
- $fs = get_file_storage();
- $fs->delete_component_files($component);
- // Finally purge all caches.
- purge_all_caches();
- // Invalidate the hash used for upgrade detections.
- set_config('allversionshash', '');
- echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
- }
- /**
- * Returns the version of installed component
- *
- * @param string $component component name
- * @param string $source either 'disk' or 'installed' - where to get the version information from
- * @return string|bool version number or false if the component is not found
- */
- function get_component_version($component, $source='installed') {
- global $CFG, $DB;
- list($type, $name) = core_component::normalize_component($component);
- // moodle core or a core subsystem
- if ($type === 'core') {
- if ($source === 'installed') {
- if (empty($CFG->version)) {
- return false;
- } else {
- return $CFG->version;
- }
- } else {
- if (!is_readable($CFG->dirroot.'/version.php')) {
- return false;
- } else {
- $version = null; //initialize variable for IDEs
- include($CFG->dirroot.'/version.php');
- return $version;
- }
- }
- }
- // activity module
- if ($type === 'mod') {
- if ($source === 'installed') {
- if ($CFG->version < 2013092001.02) {
- return $DB->get_field('modules', 'version', array('name'=>$name));
- } else {
- return get_config('mod_'.$name, 'version');
- }
- } else {
- $mods = core_component::get_plugin_list('mod');
- if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
- return false;
- } else {
- $plugin = new stdClass();
- $plugin->version = null;
- $module = $plugin;
- include($mods[$name].'/version.php');
- return $plugin->version;
- }
- }
- }
- // block
- if ($type === 'block') {
- if ($source === 'installed') {
- if ($CFG->version < 2013092001.02) {
- return $DB->get_field('block', 'version', array('name'=>$name));
- } else {
- return get_config('block_'.$name, 'version');
- }
- } else {
- $blocks = core_component::get_plugin_list('block');
- if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
- return false;
- } else {
- $plugin = new stdclass();
- include($blocks[$name].'/version.php');
- return $plugin->version;
- }
- }
- }
- // all other plugin types
- if ($source === 'installed') {
- return get_config($type.'_'.$name, 'version');
- } else {
- $plugins = core_component::get_plugin_list($type);
- if (empty($plugins[$name])) {
- return false;
- } else {
- $plugin = new stdclass();
- include($plugins[$name].'/version.php');
- return $plugin->version;
- }
- }
- }
- /**
- * Delete all plugin tables
- *
- * @param string $name Name of plugin, used as table prefix
- * @param string $file Path to install.xml file
- * @param bool $feedback defaults to true
- * @return bool Always returns true
- */
- function drop_plugin_tables($name, $file, $feedback=true) {
- global $CFG, $DB;
- // first try normal delete
- if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
- return true;
- }
- // then try to find all tables that start with name and are not in any xml file
- $used_tables = get_used_table_names();
- $tables = $DB->get_tables();
- /// Iterate over, fixing id fields as necessary
- foreach ($tables as $table) {
- if (in_array($table, $used_tables)) {
- continue;
- }
- if (strpos($table, $name) !== 0) {
- continue;
- }
- // found orphan table --> delete it
- if ($DB->get_manager()->table_exists($table)) {
- $xmldb_table = new xmldb_table($table);
- $DB->get_manager()->drop_table($xmldb_table);
- }
- }
- return true;
- }
- /**
- * Returns names of all known tables == tables that moodle knows about.
- *
- * @return array Array of lowercase table names
- */
- function get_used_table_names() {
- $table_names = array();
- $dbdirs = get_db_directories();
- foreach ($dbdirs as $dbdir) {
- $file = $dbdir.'/install.xml';
- $xmldb_file = new xmldb_file($file);
- if (!$xmldb_file->fileExists()) {
- continue;
- }
- $loaded = $xmldb_file->loadXMLStructure();
- $structure = $xmldb_file->getStructure();
- if ($loaded and $tables = $structure->getTables()) {
- foreach($tables as $table) {
- $table_names[] = strtolower($table->getName());
- }
- }
- }
- return $table_names;
- }
- /**
- * Returns list of all directories where we expect install.xml files
- * @return array Array of paths
- */
- function get_db_directories() {
- global $CFG;
- $dbdirs = array();
- /// First, the main one (lib/db)
- $dbdirs[] = $CFG->libdir.'/db';
- /// Then, all the ones defined by core_component::get_plugin_types()
- $plugintypes = core_component::get_plugin_types();
- foreach ($plugintypes as $plugintype => $pluginbasedir) {
- if ($plugins = core_component::get_plugin_list($plugintype)) {
- foreach ($plugins as $plugin => $plugindir) {
- $dbdirs[] = $plugindir.'/db';
- }
- }
- }
- return $dbdirs;
- }
- /**
- * Try to obtain or release the cron lock.
- * @param string $name name of lock
- * @param int $until timestamp when this lock considered stale, null means remove lock unconditionally
- * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
- * @return bool true if lock obtained
- */
- function set_cron_lock($name, $until, $ignorecurrent=false) {
- global $DB;
- if (empty($name)) {
- debugging("Tried to get a cron lock for a null fieldname");
- return false;
- }
- // remove lock by force == remove from config table
- if (is_null($until)) {
- set_config($name, null);
- return true;
- }
- if (!$ignorecurrent) {
- // read value from db - other processes might have changed it
- $value = $DB->get_field('config', 'value', array('name'=>$name));
- if ($value and $value > time()) {
- //lock active
- return false;
- }
- }
- set_config($name, $until);
- return true;
- }
- /**
- * Test if and critical warnings are present
- * @return bool
- */
- function admin_critical_warnings_present() {
- global $SESSION;
- if (!has_capability('moodle/site:config', context_system::instance())) {
- return 0;
- }
- if (!isset($SESSION->admin_critical_warning)) {
- $SESSION->admin_critical_warning = 0;
- if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
- $SESSION->admin_critical_warning = 1;
- }
- }
- return $SESSION->admin_critical_warning;
- }
- /**
- * Detects if float supports at least 10 decimal digits
- *
- * Detects if float supports at least 10 decimal digits
- * and also if float-->string conversion works as expected.
- *
- * @return bool true if problem found
- */
- function is_float_problem() {
- $num1 = 2009010200.01;
- $num2 = 2009010200.02;
- return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
- }
- /**
- * Try to verify that dataroot is not accessible from web.
- *
- * Try to verify that dataroot is not accessible from web.
- * It is not 100% correct but might help to reduce number of vulnerable sites.
- * Protection from httpd.conf and .htaccess is not detected properly.
- *
- * @uses INSECURE_DATAROOT_WARNING
- * @uses INSECURE_DATAROOT_ERROR
- * @param bool $fetchtest try to test public access by fetching file, default false
- * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
- */
- function is_dataroot_insecure($fetchtest=false) {
- global $CFG;
- $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
- $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
- $rp = strrev(trim($rp, '/'));
- $rp = explode('/', $rp);
- foreach($rp as $r) {
- if (strpos($siteroot, '/'.$r.'/') === 0) {
- $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
- } else {
- break; // probably alias root
- }
- }
- $siteroot = strrev($siteroot);
- $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
- if (strpos($dataroot, $siteroot) !== 0) {
- return false;
- }
- if (!$fetchtest) {
- return INSECURE_DATAROOT_WARNING;
- }
- // now try all methods to fetch a test file using http protocol
- $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
- preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
- $httpdocroot = $matches[1];
- $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
- make_upload_directory('diag');
- $testfile = $CFG->dataroot.'/diag/public.txt';
- if (!file_exists($testfile)) {
- file_put_contents($testfile, 'test file, do not delete');
- @chmod($testfile, $CFG->filepermissions);
- }
- $teststr = trim(file_get_contents($testfile));
- if (empty($teststr)) {
- // hmm, strange
- return INSECURE_DATAROOT_WARNING;
- }
- $testurl = $datarooturl.'/diag/public.txt';
- if (extension_loaded('curl') and
- !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
- !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
- ($ch = @curl_init($testurl)) !== false) {
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_HEADER, false);
- $data = curl_exec($ch);
- if (!curl_errno($ch)) {
- $data = trim($data);
- if ($data === $teststr) {
- curl_close($ch);
- return INSECURE_DATAROOT_ERROR;
- }
- }
- curl_close($ch);
- }
- if ($data = @file_get_contents($testurl)) {
- $data = trim($data);
- if ($data === $teststr) {
- return INSECURE_DATAROOT_ERROR;
- }
- }
- preg_match('|https?://([^/]+)|i', $testurl, $matches);
- $sitename = $matches[1];
- $error = 0;
- if ($fp = @fsockopen($sitename, 80, $error)) {
- preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
- $localurl = $matches[1];
- $out = "GET $localurl HTTP/1.1\r\n";
- $out .= "Host: $sitename\r\n";
- $out .= "Connection: Close\r\n\r\n";
- fwrite($fp, $out);
- $data = '';
- $incoming = false;
- while (!feof($fp)) {
- if ($incoming) {
- $data .= fgets($fp, 1024);
- } else if (@fgets($fp, 1024) === "\r\n") {
- $incoming = true;
- }
- }
- fclose($fp);
- $data = trim($data);
- if ($data === $teststr) {
- return INSECURE_DATAROOT_ERROR;
- }
- }
- return INSECURE_DATAROOT_WARNING;
- }
- /**
- * Enables CLI maintenance mode by creating new dataroot/climaintenance.html file.
- */
- function enable_cli_maintenance_mode() {
- global $CFG, $SITE;
- if (file_exists("$CFG->dataroot/climaintenance.html")) {
- unlink("$CFG->dataroot/climaintenance.html");
- }
- if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
- $data = $CFG->maintenance_message;
- $data = bootstrap_renderer::early_error_content($data, null, null, null);
- $data = bootstrap_renderer::plain_page(get_string('sitemaintenance', 'admin'), $data);
- } else if (file_exists("$CFG->dataroot/climaintenance.template.html")) {
- $data = file_get_contents("$CFG->dataroot/climaintenance.template.html");
- } else {
- $data = get_string('sitemaintenance', 'admin');
- $data = bootstrap_renderer::early_error_content($data, null, null, null);
- $data = bootstrap_renderer::plain_page(get_string('sitemaintenancetitle', 'admin',
- format_string($SITE->fullname, true, ['context' => context_system::instance()])), $data);
- }
- file_put_contents("$CFG->dataroot/climaintenance.html", $data);
- chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
- }
- /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
- /**
- * Interface for anything appearing in the admin tree
- *
- * The interface that is implemented by anything that appears in the admin tree
- * block. It forces inheriting classes to define a method for checking user permissions
- * and methods for finding something in the admin tree.
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- interface part_of_admin_tree {
- /**
- * Finds a named part_of_admin_tree.
- *
- * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
- * and not parentable_part_of_admin_tree, then this function should only check if
- * $this->name matches $name. If it does, it should return a reference to $this,
- * otherwise, it should return a reference to NULL.
- *
- * If a class inherits parentable_part_of_admin_tree, this method should be called
- * recursively on all child objects (assuming, of course, the parent object's name
- * doesn't match the search criterion).
- *
- * @param string $name The internal name of the part_of_admin_tree we're searching for.
- * @return mixed An object reference or a NULL reference.
- */
- public function locate($name);
- /**
- * Removes named part_of_admin_tree.
- *
- * @param string $name The internal name of the part_of_admin_tree we want to remove.
- * @return bool success.
- */
- public function prune($name);
- /**
- * Search using query
- * @param string $query
- * @return mixed array-object structure of found settings and pages
- */
- public function search($query);
- /**
- * Verifies current user's access to this part_of_admin_tree.
- *
- * Used to check if the current user has access to this part of the admin tree or
- * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
- * then this method is usually just a call to has_capability() in the site context.
- *
- * If a class inherits parentable_part_of_admin_tree, this method should return the
- * logical OR of the return of check_access() on all child objects.
- *
- * @return bool True if the user has access, false if she doesn't.
- */
- public function check_access();
- /**
- * Mostly useful for removing of some parts of the tree in admin tree block.
- *
- * @return True is hidden from normal list view
- */
- public function is_hidden();
- /**
- * Show we display Save button at the page bottom?
- * @return bool
- */
- public function show_save();
- }
- /**
- * Interface implemented by any part_of_admin_tree that has children.
- *
- * The interface implemented by any part_of_admin_tree that can be a parent
- * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
- * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
- * include an add method for adding other part_of_admin_tree objects as children.
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- interface parentable_part_of_admin_tree extends part_of_admin_tree {
- /**
- * Adds a part_of_admin_tree object to the admin tree.
- *
- * Used to add a part_of_admin_tree object to this object or a child of this
- * object. $something should only be added if $destinationname matches
- * $this->name. If it doesn't, add should be called on child objects that are
- * also parentable_part_of_admin_tree's.
- *
- * $something should be appended as the last child in the $destinationname. If the
- * $beforesibling is specified, $something should be prepended to it. If the given
- * sibling is not found, $something should be appended to the end of $destinationname
- * and a developer debugging message should be displayed.
- *
- * @param string $destinationname The internal name of the new parent for $something.
- * @param part_of_admin_tree $something The object to be added.
- * @return bool True on success, false on failure.
- */
- public function add($destinationname, $something, $beforesibling = null);
- }
- /**
- * The object used to represent folders (a.k.a. categories) in the admin tree block.
- *
- * Each admin_category object contains a number of part_of_admin_tree objects.
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class admin_category implements parentable_part_of_admin_tree, linkable_settings_page {
- /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
- protected $children;
- /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
- public $name;
- /** @var string The displayed name for this category. Usually obtained through get_string() */
- public $visiblename;
- /** @var bool Should this category be hidden in admin tree block? */
- public $hidden;
- /** @var mixed Either a string or an array or strings */
- public $path;
- /** @var mixed Either a string or an array or strings */
- public $visiblepath;
- /** @var array fast lookup category cache, all categories of one tree point to one cache */
- protected $category_cache;
- /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
- protected $sort = false;
- /** @var bool If set to true children will be sorted in ascending order. */
- protected $sortasc = true;
- /** @var bool If set to true sub categories and pages will be split and then sorted.. */
- protected $sortsplit = true;
- /** @var bool $sorted True if the children have been sorted and don't need resorting */
- protected $sorted = false;
- /**
- * Constructor for an empty admin category
- *
- * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
- * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
- * @param bool $hidden hide category in admin tree block, defaults to false
- */
- public function __construct($name, $visiblename, $hidden=false) {
- $this->children = array();
- $this->name = $name;
- $this->visiblename = $visiblename;
- $this->hidden = $hidden;
- }
- /**
- * Get the URL to view this settings page.
- *
- * @return moodle_url
- */
- public function get_settings_page_url(): moodle_url {
- return new moodle_url(
- '/admin/category.php',
- [
- 'category' => $this->name,
- ]
- );
- }
- /**
- * Returns a reference to the part_of_admin_tree object with internal name $name.
- *
- * @param string $name The internal name of the object we want.
- * @param bool $findpath initialize path and visiblepath arrays
- * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
- * defaults to false
- */
- public function locate($name, $findpath=false) {
- if (!isset($this->category_cache[$this->name])) {
- // somebody much have purged the cache
- $this->category_cache[$this->name] = $this;
- }
- if ($this->name == $name) {
- if ($findpath) {
- $this->visiblepath[] = $this->visiblename;
- $this->path[] = $this->name;
- }
- return $this;
- }
- // quick category lookup
- if (!$findpath and isset($this->category_cache[$name])) {
- return $this->category_cache[$name];
- }
- $return = NULL;
- foreach($this->children as $childid=>$unused) {
- if ($return = $this->children[$childid]->locate($name, $findpath)) {
- break;
- }
- }
- if (!is_null($return) and $findpath) {
- $return->visiblepath[] = $this->visiblename;
- $return->path[] = $this->name;
- }
- return $return;
- }
- /**
- * Search using query
- *
- * @param string query
- * @return mixed array-object structure of found settings and pages
- */
- public function search($query) {
- $result = array();
- foreach ($this->get_children() as $child) {
- $subsearch = $child->search($query);
- if (!is_array($subsearch)) {
- debugging('Incorrect search result from '.$child->name);
- continue;
- }
- $result = array_merge($result, $subsearch);
- }
- return $result;
- }
- /**
- * Removes part_of_admin_tree object with internal name $name.
- *
- * @param string $name The internal name of the object we want to remove.
- * @return bool success
- */
- public function prune($name) {
- if ($this->name == $name) {
- return false; //can not remove itself
- }
- foreach($this->children as $precedence => $child) {
- if ($child->name == $name) {
- // clear cache and delete self
- while($this->category_cache) {
- // delete the cache, but keep the original array address
- array_pop($this->category_cache);
- }
- unset($this->children[$precedence]);
- return true;
- } else if ($this->children[$precedence]->prune($name)) {
- return true;
- }
- }
- return false;
- }
- /**
- * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
- *
- * By default the new part of the tree is appended as the last child of the parent. You
- * can specify a sibling node that the new part should be prepended to. If the given
- * sibling is not found, the part is appended to the end (as it would be by default) and
- * a developer debugging message is displayed.
- *
- * @throws coding_exception if the $beforesibling is empty string or is not string at all.
- * @param string $destinationame The internal name of the immediate parent that we want for $something.
- * @param mixed $something A part_of_admin_tree or setting instance to be added.
- * @param string $beforesibling The name of the parent's child the $something should be prepended to.
- * @return bool True if successfully added, false if $something can not be added.
- */
- public function add($parentname, $something, $beforesibling = null) {
- global $CFG;
- $parent = $this->locate($parentname);
- if (is_null($parent)) {
- debugging('parent does not exist!');
- return false;
- }
- if ($something instanceof part_of_admin_tree) {
- if (!($parent instanceof parentable_part_of_admin_tree)) {
- debugging('error - parts of tree can be inserted only into parentable parts');
- return false;
- }
- if ($CFG->debugdeveloper && !is_null($this->locate($something->name))) {
- // The name of the node is already used, simply warn the developer that this should not happen.
- // It is intentional to check for the debug level before performing the check.
- debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
- }
- if (is_null($beforesibling)) {
- // Append $something as the parent's last child.
- $parent->children[] = $something;
- } else {
- if (!is_string($beforesibling) or trim($beforesibling) === '') {
- throw new coding_exception('Unexpected value of the beforesibling parameter');
- }
- // Try to find the position of the sibling.
- $siblingposition = null;
- foreach ($parent->children as $childposition => $child) {
- if ($child->name === $beforesibling) {
- $siblingposition = $childposition;
- break;
- }
- }
- if (is_null($siblingposition)) {
- debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
- $parent->children[] = $something;
- } else {
- $parent->children = array_merge(
- array_slice($parent->children, 0, $siblingposition),
- array($something),
- array_slice($parent->children, $siblingposition)
- );
- }
- }
- if ($something instanceof admin_category) {
- if (isset($this->category_cache[$something->name])) {
- debugging('Duplicate admin category name: '.$something->name);
- } else {
- $this->category_cache[$something->name] = $something;
- $something->category_cache =& $this->category_cache;
- foreach ($something->children as $child) {
- // just in case somebody already added subcategories
- if ($child instanceof admin_category) {
- if (isset($this->category_cache[$child->name])) {
- debugging('Duplicate admin category name: '.$child->name);
- } else {
- $this->category_cache[$child->name] = $child;
- $child->category_cache =& $this->category_cache;
- }
- }
- }
- }
- }
- return true;
- } else {
- debugging('error - can not add this element');
- return false;
- }
- }
- /**
- * Checks if the user has access to anything in this category.
- *
- * @return bool True if the user has access to at least one child in this category, false otherwise.
- */
- public function check_access() {
- foreach ($this->children as $child) {
- if ($child->check_access()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Is this category hidden in admin tree block?
- *
- * @return bool True if hidden
- */
- public function is_hidden() {
- return $this->hidden;
- }
- /**
- * Show we display Save button at the page bottom?
- * @return bool
- */
- public function show_save() {
- foreach ($this->children as $child) {
- if ($child->show_save()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Sets sorting on this category.
- *
- * Please note this function doesn't actually do the sorting.
- * It can be called anytime.
- * Sorting occurs when the user calls get_children.
- * Code using the children array directly won't see the sorted results.
- *
- * @param bool $sort If set to true children will be sorted, if false they won't be.
- * @param bool $asc If true sorting will be ascending, otherwise descending.
- * @param bool $split If true we sort pages and sub categories separately.
- */
- public function set_sorting($sort, $asc = true, $split = true) {
- $this->sort = (bool)$sort;
- $this->sortasc = (bool)$asc;
- $this->sortsplit = (bool)$split;
- }
- /**
- * Returns the children associated with this category.
- *
- * @return part_of_admin_tree[]
- */
- public function get_children() {
- // If we should sort and it hasn't already been sorted.
- if ($this->sort && !$this->sorted) {
- if ($this->sortsplit) {
- $categories = array();
- $pages = array();
- foreach ($this->children as $child) {
- if ($child instanceof admin_category) {
- $categories[] = $child;
- } else {
- $pages[] = $child;
- }
- }
- core_collator::asort_objects_by_property($categories, 'visiblename');
- core_collator::asort_objects_by_property($pages, 'visiblename');
- if (!$this->sortasc) {
- $categories = array_reverse($categories);
- $pages = array_reverse($pages);
- }
- $this->children = array_merge($pages, $categories);
- } else {
- core_collator::asort_objects_by_property($this->children, 'visiblename');
- if (!$this->sortasc) {
- $this->children = array_reverse($this->children);
- }
- }
- $this->sorted = true;
- }
- return $this->children;
- }
- /**
- * Magically gets a property from this object.
- *
- * @param $property
- * @return part_of_admin_tree[]
- * @throws coding_exception
- */
- public function __get($property) {
- if ($property === 'children') {
- return $this->get_children();
- }
- throw new coding_exception('Invalid property requested.');
- }
- /**
- * Magically sets a property against this object.
- *
- * @param string $property
- * @param mixed $value
- * @throws coding_exception
- */
- public function __set($property, $value) {
- if ($property === 'children') {
- $this->sorted = false;
- $this->children = $value;
- } else {
- throw new coding_exception('Invalid property requested.');
- }
- }
- /**
- * Checks if an inaccessible property is set.
- *
- * @param string $property
- * @return bool
- * @throws coding_exception
- */
- public function __isset($property) {
- if ($property === 'children') {
- return isset($this->children);
- }
- throw new coding_exception('Invalid property requested.');
- }
- }
- /**
- * Root of admin settings tree, does not have any parent.
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class admin_root extends admin_category {
- /** @var array List of errors */
- public $errors;
- /** @var string search query */
- public $search;
- /** @var bool full tree flag - true means all settings required, false only pages required */
- public $fulltree;
- /** @var bool flag indicating loaded tree */
- public $loaded;
- /** @var mixed site custom defaults overriding defaults in settings files*/
- public $custom_defaults;
- /**
- * @param bool $fulltree true means all settings required,
- * false only pages required
- */
- public function __construct($fulltree) {
- global $CFG;
- parent::__construct('root', get_string('administration'), false);
- $this->errors = array();
- $this->search = '';
- $this->fulltree = $fulltree;
- $this->loaded = false;
- $this->category_cache = array();
- // load custom defaults if found
- $this->custom_defaults = null;
- $defaultsfile = "$CFG->dirroot/local/defaults.php";
- if (is_readable($defaultsfile)) {
- $defaults = array();
- include($defaultsfile);
- if (is_array($defaults) and count($defaults)) {
- $this->custom_defaults = $defaults;
- }
- }
- }
- /**
- * Empties children array, and sets loaded to false
- *
- * @param bool $requirefulltree
- */
- public function purge_children($requirefulltree) {
- $this->children = array();
- $this->fulltree = ($requirefulltree || $this->fulltree);
- $this->loaded = false;
- //break circular dependencies - this helps PHP 5.2
- while($this->category_cache) {
- array_pop($this->category_cache);
- }
- $this->category_cache = array();
- }
- }
- /**
- * Links external PHP pages into the admin tree.
- *
- * See detailed usage example at the top of this document (adminlib.php)
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class admin_externalpage implements part_of_admin_tree, linkable_settings_page {
- /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
- public $name;
- /** @var string The displayed name for this external page. Usually obtained through get_string(). */
- public $visiblename;
- /** @var string The external URL that we should link to when someone requests this external page. */
- public $url;
- /** @var array The role capability/permission a user must have to access this external page. */
- public $req_capability;
- /** @var object The context in which capability/permission should be checked, default is site context. */
- public $context;
- /** @var bool hidden in admin tree block. */
- public $hidden;
- /** @var mixed either string or array of string */
- public $path;
- /** @var array list of visible names of page parents */
- public $visiblepath;
- /**
- * Constructor for adding an external page into the admin tree.
- *
- * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
- * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
- * @param string $url The external URL that we should link to when someone requests this external page.
- * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
- * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
- * @param stdClass $context The context the page relates to. Not sure what happens
- * if you specify something other than system or front page. Defaults to system.
- */
- public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
- $this->name = $name;
- $this->visiblename = $visiblename;
- $this->url = $url;
- if (is_array($req_capability)) {
- $this->req_capability = $req_capability;
- } else {
- $this->req_capability = array($req_capability);
- }
- $this->hidden = $hidden;
- $this->context = $context;
- }
- /**
- * Get the URL to view this settings page.
- *
- * @return moodle_url
- */
- public function get_settings_page_url(): moodle_url {
- return new moodle_url($this->url);
- }
- /**
- * Returns a reference to the part_of_admin_tree object with internal name $name.
- *
- * @param string $name The internal name of the object we want.
- * @param bool $findpath defaults to false
- * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
- */
- public function locate($name, $findpath=false) {
- if ($this->name == $name) {
- if ($findpath) {
- $this->visiblepath = array($this->visiblename);
- $this->path = array($this->name);
- }
- return $this;
- } else {
- $return = NULL;
- return $return;
- }
- }
- /**
- * This function always returns false, required function by interface
- *
- * @param string $name
- * @return false
- */
- public function prune($name) {
- return false;
- }
- /**
- * Search using query
- *
- * @param string $query
- * @return mixed array-object structure of found settings and pages
- */
- public function search($query) {
- $found = false;
- if (strpos(strtolower($this->name), $query) !== false) {
- $found = true;
- } else if (strpos(core_text::strtolower($this->visiblename), $query) !== false) {
- $found = true;
- }
- if ($found) {
- $result = new stdClass();
- $result->page = $this;
- $result->settings = array();
- return array($this->name => $result);
- } else {
- return array();
- }
- }
- /**
- * Determines if the current user has access to this external page based on $this->req_capability.
- *
- * @return bool True if user has access, false otherwise.
- */
- public function check_access() {
- global $CFG;
- $context = empty($this->context) ? context_system::instance() : $this->context;
- foreach($this->req_capability as $cap) {
- if (has_capability($cap, $context)) {
- return true;
- }
- }
- return false;
- }
- /**
- * Is this external page hidden in admin tree block?
- *
- * @return bool True if hidden
- */
- public function is_hidden() {
- return $this->hidden;
- }
- /**
- * Show we display Save button at the page bottom?
- * @return bool
- */
- public function show_save() {
- return false;
- }
- }
- /**
- * Used to store details of the dependency between two settings elements.
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @copyright 2017 Davo Smith, Synergy Learning
- */
- class admin_settingdependency {
- /** @var string the name of the setting to be shown/hidden */
- public $settingname;
- /** @var string the setting this is dependent on */
- public $dependenton;
- /** @var string the condition to show/hide the element */
- public $condition;
- /** @var string the value to compare against */
- public $value;
- /** @var string[] list of valid conditions */
- private static $validconditions = ['checked', 'notchecked', 'noitemselected', 'eq', 'neq', 'in'];
- /**
- * admin_settingdependency constructor.
- * @param string $settingname
- * @param string $dependenton
- * @param string $condition
- * @param string $value
- * @throws \coding_exception
- */
- public function __construct($settingname, $dependenton, $condition, $value) {
- $this->settingname = $this->parse_name($settingname);
- $this->dependenton = $this->parse_name($dependenton);
- $this->condition = $condition;
- $this->value = $value;
- if (!in_array($this->condition, self::$validconditions)) {
- throw new coding_exception("Invalid condition '$condition'");
- }
- }
- /**
- * Convert the setting name into the form field name.
- * @param string $name
- * @return string
- */
- private function parse_name($name) {
- $bits = explode('/', $name);
- $name = array_pop($bits);
- $plugin = '';
- if ($bits) {
- $plugin = array_pop($bits);
- if ($plugin === 'moodle') {
- $plugin = '';
- }
- }
- return 's_'.$plugin.'_'.$name;
- }
- /**
- * Gather toget…
Large files files are truncated, but you can click here to view the full file