/lib/adminlib.php
PHP | 7357 lines | 4377 code | 731 blank | 2249 comment | 611 complexity | eb6f6701b00e817ffbf591cf5f9ffcf9 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, BSD-3-Clause
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(dirname(dirname(dirname(__FILE__))).'/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
- */
- defined('MOODLE_INTERNAL') || die();
- /// Add libraries
- require_once($CFG->libdir.'/ddllib.php');
- require_once($CFG->libdir.'/xmlize.php');
- define('INSECURE_DATAROOT_WARNING', 1);
- define('INSECURE_DATAROOT_ERROR', 2);
- /**
- * Automatically clean-up all plugin data and remove the plugin DB tables
- *
- * @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;
- // recursively uninstall all module subplugins first
- if ($type === 'mod') {
- if (file_exists("$CFG->dirroot/mod/$name/db/subplugins.php")) {
- $subplugins = array();
- include("$CFG->dirroot/mod/$name/db/subplugins.php");
- foreach ($subplugins as $subplugintype=>$dir) {
- $instances = 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);
- $plugindirectory = 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)) {
- if (!$uninstallfunction()) {
- echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $pluginname);
- }
- }
- }
- if ($type === 'mod') {
- // perform cleanup tasks specific for activity modules
- if (!$module = $DB->get_record('modules', array('name' => $name))) {
- print_error('moduledoesnotexist', 'error');
- }
- // delete all the relevant instances from all course sections
- if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) {
- foreach ($coursemods as $coursemod) {
- if (!delete_mod_from_section($coursemod->id, $coursemod->section)) {
- echo $OUTPUT->notification("Could not delete the $strpluginname with id = $coursemod->id from section $coursemod->section");
- }
- }
- }
- // clear course.modinfo for courses that used this module
- $sql = "UPDATE {course}
- SET modinfo=''
- WHERE id IN (SELECT DISTINCT course
- FROM {course_modules}
- WHERE module=?)";
- $DB->execute($sql, array($module->id));
- // delete all the course module records
- $DB->delete_records('course_modules', array('module' => $module->id));
- // delete module contexts
- if ($coursemods) {
- foreach ($coursemods as $coursemod) {
- if (!delete_context(CONTEXT_MODULE, $coursemod->id)) {
- echo $OUTPUT->notification("Could not delete the context for $strpluginname with id = $coursemod->id");
- }
- }
- }
- // delete the module entry itself
- $DB->delete_records('modules', array('name' => $module->name));
- // cleanup the gradebook
- require_once($CFG->libdir.'/gradelib.php');
- grade_uninstalled_module($module->name);
- // Perform any custom uninstall tasks
- if (file_exists($CFG->dirroot . '/mod/' . $module->name . '/lib.php')) {
- require_once($CFG->dirroot . '/mod/' . $module->name . '/lib.php');
- $uninstallfunction = $module->name . '_uninstall';
- if (function_exists($uninstallfunction)) {
- debugging("{$uninstallfunction}() has been deprecated. Use the plugin's db/uninstall.php instead", DEBUG_DEVELOPER);
- if (!$uninstallfunction()) {
- echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $module->name.'!');
- }
- }
- }
- } else if ($type === 'enrol') {
- // NOTE: this is a bit brute force way - it will not trigger events and hooks properly
- // nuke all role assignments
- role_unassign_all(array('component'=>$component));
- // purge participants
- $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($name));
- // purge enrol instances
- $DB->delete_records('enrol', array('enrol'=>$name));
- // tweak enrol settings
- if (!empty($CFG->enrol_plugins_enabled)) {
- $enabledenrols = explode(',', $CFG->enrol_plugins_enabled);
- $enabledenrols = array_unique($enabledenrols);
- $enabledenrols = array_flip($enabledenrols);
- unset($enabledenrols[$name]);
- $enabledenrols = array_flip($enabledenrols);
- if (is_array($enabledenrols)) {
- set_config('enrol_plugins_enabled', implode(',', $enabledenrols));
- }
- }
- }
- // perform clean-up task common for all the plugin/subplugin types
- // delete calendar events
- $DB->delete_records('event', array('modulename' => $pluginname));
- // 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($pluginname);
- // delete the plugin tables
- $xmldbfilepath = $plugindirectory . '/db/install.xml';
- drop_plugin_tables($pluginname, $xmldbfilepath, false);
- // delete the capabilities that were defined by this module
- capabilities_cleanup($component);
- // remove event handlers and dequeue pending events
- events_uninstall($component);
- 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) = 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') {
- return $DB->get_field('modules', 'version', array('name'=>$name));
- } else {
- $mods = get_plugin_list('mod');
- if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
- return false;
- } else {
- $module = new stdclass();
- include($mods[$name].'/version.php');
- return $module->version;
- }
- }
- }
- // block
- if ($type === 'block') {
- if ($source === 'installed') {
- return $DB->get_field('block', 'version', array('name'=>$name));
- } else {
- $blocks = 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 = 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->name);
- }
- }
- }
- 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 get_plugin_types()
- $plugintypes = get_plugin_types();
- foreach ($plugintypes as $plugintype => $pluginbasedir) {
- if ($plugins = 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', get_context_instance(CONTEXT_SYSTEM))) {
- 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');
- }
- $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;
- }
- /// 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.
- *
- * @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);
- }
- /**
- * 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 {
- /** @var mixed An array of part_of_admin_tree objects that are this object's children */
- public $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;
- /**
- * 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;
- }
- /**
- * 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 (is_array($this->category_cache) and !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 is_array($this->category_cache) 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->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
- if (is_array($this->category_cache)) {
- 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.
- *
- * @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.
- * @return bool True if successfully added, false if $something can not be added.
- */
- public function add($parentname, $something) {
- $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;
- }
- $parent->children[] = $something;
- if (is_array($this->category_cache) and ($something instanceof admin_category)) {
- if (isset($this->category_cache[$something->name])) {
- debugging('Duplicate admin catefory 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 catefory 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;
- }
- }
- /**
- * 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 {
- /** @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 string 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;
- 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;
- }
- /**
- * 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) {
- $textlib = textlib_get_instance();
- $found = false;
- if (strpos(strtolower($this->name), $query) !== false) {
- $found = true;
- } else if (strpos($textlib->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) ? get_context_instance(CONTEXT_SYSTEM) : $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 group a number of admin_setting objects into a page and add them to the admin tree.
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class admin_settingpage implements part_of_admin_tree {
- /** @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 mixed An array of admin_setting objects that are part of this setting page. */
- public $settings;
- /** @var string 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 string of paths or array of strings of paths */
- public $path;
- public $visiblepath;
- /**
- * see admin_settingpage for details of this function
- *
- * @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 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, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
- $this->settings = new stdClass();
- $this->name = $name;
- $this->visiblename = $visiblename;
- if (is_array($req_capability)) {
- $this->req_capability = $req_capability;
- } else {
- $this->req_capability = array($req_capability);
- }
- $this->hidden = $hidden;
- $this->context = $context;
- }
- /**
- * see admin_category
- *
- * @param string $name
- * @param bool $findpath
- * @return mixed Object (this) if name == this->name, else returns 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;
- }
- }
- /**
- * Search string in settings page.
- *
- * @param string $query
- * @return array
- */
- public function search($query) {
- $found = array();
- foreach ($this->settings as $setting) {
- if ($setting->is_related($query)) {
- $found[] = $setting;
- }
- }
- if ($found) {
- $result = new stdClass();
- $result->page = $this;
- $result->settings = $found;
- return array($this->name => $result);
- }
- $textlib = textlib_get_instance();
- $found = false;
- if (strpos(strtolower($this->name), $query) !== false) {
- $found = true;
- } else if (strpos($textlib->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();
- }
- }
- /**
- * This function always returns false, required by interface
- *
- * @param string $name
- * @return bool Always false
- */
- public function prune($name) {
- return false;
- }
- /**
- * adds an admin_setting to this admin_settingpage
- *
- * 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
- * n.b. each admin_setting in an admin_settingpage must have a unique internal name
- *
- * @param object $setting is the admin_setting object you want to add
- * @return bool true if successful, false if not
- */
- public function add($setting) {
- if (!($setting instanceof admin_setting)) {
- debugging('error - not a setting instance');
- return false;
- }
- $this->settings->{$setting->name} = $setting;
- return true;
- }
- /**
- * see admin_externalpage
- *
- * @return bool Returns true for yes false for no
- */
- public function check_access() {
- global $CFG;
- $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
- foreach($this->req_capability as $cap) {
- if (has_capability($cap, $context)) {
- return true;
- }
- }
- return false;
- }
- /**
- * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
- * @return string Returns an XHTML string
- */
- public function output_html() {
- $adminroot = admin_get_root();
- $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
- foreach($this->settings as $setting) {
- $fullname = $setting->get_full_name();
- if (array_key_exists($fullname, $adminroot->errors)) {
- $data = $adminroot->errors[$fullname]->data;
- } else {
- $data = $setting->get_setting();
- // do not use defaults if settings not available - upgrade settings handles the defaults!
- }
- $return .= $setting->output_html($data);
- }
- $return .= '</fieldset>';
- return $return;
- }
- /**
- * Is this settings 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() {
- foreach($this->settings as $setting) {
- if (empty($setting->nosave)) {
- return true;
- }
- }
- return false;
- }
- }
- /**
- * Admin settings class. Only exists on setting pages.
- * Read & write happens at this level; no authentication.
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- abstract class admin_setting {
- /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
- public $name;
- /** @var string localised name */
- public $visiblename;
- /** @var string localised long description in Markdown format */
- public $description;
- /** @var mixed Can be string or array of string */
- public $defaultsetting;
- /** @var string */
- public $updatedcallback;
- /** @var mixed can be String or Null. Null means main config table */
- public $plugin; // null means main config table
- /** @var bool true indicates this setting does not actually save anything, just information */
- public $nosave = false;
- /**
- * Constructor
- * @param string $name unique ascii name, either 'mysetting' for settings that in config,
- * or 'myplugin/mysetting' for ones in config_plugins.
- * @param string $visiblename localised name
- * @param string $description localised long description
- * @param mixed $defaultsetting string or array depending on implementation
- */
- public function __construct($name, $visiblename, $description, $defaultsetting) {
- $this->parse_setting_name($name);
- $this->visiblename = $visiblename;
- $this->description = $description;
- $this->defaultsetting = $defaultsetting;
- }
- /**
- * Set up $this->name and potentially $this->plugin
- *
- * Set up $this->name and possibly $this->plugin based on whether $name looks
- * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
- * on the names, that is, output a developer debug warning if the name
- * contains anything other than [a-zA-Z0-9_]+.
- *
- * @param string $name the setting name passed in to the constructor.
- */
- private function parse_setting_name($name) {
- $bits = explode('/', $name);
- if (count($bits) > 2) {
- throw new moodle_exception('invalidadminsettingname', '', '', $name);
- }
- $this->name = array_pop($bits);
- if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
- throw new moodle_exception('invalidadminsettingname', '', '', $name);
- }
- if (!empty($bits)) {
- $this->plugin = array_pop($bits);
- if ($this->plugin === 'moodle') {
- $this->plugin = null;
- } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
- throw new moodle_exception('invalidadminsettingname', '', '', $name);
- }
- }
- }
- /**
- * Returns the fullname prefixed by the plugin
- * @return string
- */
- public function get_full_name() {
- return 's_'.$this->plugin.'_'.$this->name;
- }
- /**…
Large files files are truncated, but you can click here to view the full file