Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

7933 lines (7009 sloc) 273.059 kb
<?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');
require_once($CFG->libdir.'/messagelib.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;
// This may take a long time.
@set_time_limit(0);
// 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));
}
}
} else if ($type === 'block') {
if ($block = $DB->get_record('block', array('name'=>$name))) {
// Inform block it's about to be deleted
if (file_exists("$CFG->dirroot/blocks/$block->name/block_$block->name.php")) {
$blockobject = block_instance($block->name);
if ($blockobject) {
$blockobject->before_delete(); //only if we can create instance, block might have been already removed
}
}
// First delete instances and related contexts
$instances = $DB->get_records('block_instances', array('blockname' => $block->name));
foreach($instances as $instance) {
blocks_delete_instance($instance);
}
// Delete block
$DB->delete_records('block', array('id'=>$block->id));
}
}
// 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));
// 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 message provider
message_provider_uninstall($component);
// delete message processor
if ($type === 'message') {
message_processor_uninstall($name);
}
// 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);
// 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->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 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 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;
}
}
/**
* 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;
/** @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;
}
/**
* 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(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;
/** @var array list of visible names of page parents */
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);
}
$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;
/** @var bool if set, indicates that a change to this setting requires rebuild course cache */
public $affectsmodinfo = 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;
}
/**
* Returns the ID string based on plugin and name
* @return string
*/
public function get_id() {
return 'id_s_'.$this->plugin.'_'.$this->name;
}
/**
* @param bool $affectsmodinfo If true, changes to this setting will
* cause the course cache to be rebuilt
*/
public function set_affects_modinfo($affectsmodinfo) {
$this->affectsmodinfo = $affectsmodinfo;
}
/**
* Returns the config if possible
*
* @return mixed returns config if successful else null
*/
public function config_read($name) {
global $CFG;
if (!empty($this->plugin)) {
$value = get_config($this->plugin, $name);
return $value === false ? NULL : $value;
} else {
if (isset($CFG->$name)) {
return $CFG->$name;
} else {
return NULL;
}
}
}
/**
* Used to set a config pair and log change
*
* @param string $name
* @param mixed $value Gets converted to string if not null
* @return bool Write setting to config table
*/
public function config_write($name, $value) {
global $DB, $USER, $CFG;
if ($this->nosave) {
return true;
}
// make sure it is a real change
$oldvalue = get_config($this->plugin, $name);
$oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
$value = is_null($value) ? null : (string)$value;
if ($oldvalue === $value) {
return true;
}
// store change
set_config($name, $value, $this->plugin);
// Some admin settings affect course modinfo
if ($this->affectsmodinfo) {
// Clear course cache for all courses
rebuild_course_cache(0, true);
}
// log change
$log = new stdClass();
$log->userid = during_initial_install() ? 0 :$USER->id; // 0 as user id during install
$log->timemodified = time();
$log->plugin = $this->plugin;
$log->name = $name;
$log->value = $value;
$log->oldvalue = $oldvalue;
$DB->insert_record('config_log', $log);
return true; // BC only
}
/**
* Returns current value of this setting
* @return mixed array or string depending on instance, NULL means not set yet
*/
public abstract function get_setting();
/**
* Returns default setting if exists
* @return mixed array or string depending on instance; NULL means no default, user must supply
*/
public function get_defaultsetting() {
$adminroot = admin_get_root(false, false);
if (!empty($adminroot->custom_defaults)) {
$plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
if (isset($adminroot->custom_defaults[$plugin])) {
if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
return $adminroot->custom_defaults[$plugin][$this->name];
}
}
}
return $this->defaultsetting;
}
/**
* Store new setting
*
* @param mixed $data string or array, must not be NULL
* @return string empty string if ok, string error message otherwise
*/
public abstract function write_setting($data);
/**
* Return part of form with setting
* This function should always be overwritten
*
* @param mixed $data array or string depending on setting
* @param string $query
* @return string
*/
public function output_html($data, $query='') {
// should be overridden
return;
}
/**
* Function called if setting updated - cleanup, cache reset, etc.
* @param string $functionname Sets the function name
* @return void
*/
public function set_updatedcallback($functionname) {
$this->updatedcallback = $functionname;
}
/**
* Is setting related to query text - used when searching
* @param string $query
* @return bool
*/
public function is_related($query) {
if (strpos(strtolower($this->name), $query) !== false) {
return true;
}
if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
return true;
}
if (strpos(textlib::strtolower($this->description), $query) !== false) {
return true;
}
$current = $this->get_setting();
if (!is_null($current)) {
if (is_string($current)) {
if (strpos(textlib::strtolower($current), $query) !== false) {
return true;
}
}
}
$default = $this->get_defaultsetting();
if (!is_null($default)) {
if (is_string($default)) {
if (strpos(textlib::strtolower($default), $query) !== false) {
return true;
}
}
}
return false;
}
}
/**
* No setting - just heading and text.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_heading extends admin_setting {
/**
* not a setting, just text
* @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
* @param string $heading heading
* @param string $information text in box
*/
public function __construct($name, $heading, $information) {
$this->nosave = true;
parent::__construct($name, $heading, $information, '');
}
/**
* Always returns true
* @return bool Always returns true
*/
public function get_setting() {
return true;
}
/**
* Always returns true
* @return bool Always returns true
*/
public function get_defaultsetting() {
return true;
}
/**
* Never write settings
* @return string Always returns an empty string
*/
public function write_setting($data) {
// do not write any setting
return '';
}
/**
* Returns an HTML string
* @return string Returns an HTML string
*/
public function output_html($data, $query='') {
global $OUTPUT;
$return = '';
if ($this->visiblename != '') {
$return .= $OUTPUT->heading($this->visiblename, 3, 'main');
}
if ($this->description != '') {
$return .= $OUTPUT->box(highlight($query, markdown_to_html($this->description)), 'generalbox formsettingheading');
}
return $return;
}
}
/**
* The most flexibly setting, user is typing text
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configtext extends admin_setting {
/** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
public $paramtype;
/** @var int default field size */
public $size;
/**
* Config text 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
* @param string $description long localised info
* @param string $defaultsetting
* @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
* @param int $size default field size
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
$this->paramtype = $paramtype;
if (!is_null($size)) {
$this->size = $size;
} else {
$this->size = ($paramtype === PARAM_INT) ? 5 : 30;
}
parent::__construct($name, $visiblename, $description, $defaultsetting);
}
/**
* Return the setting
*
* @return mixed returns config if successful else null
*/
public function get_setting() {
return $this->config_read($this->name);
}
public function write_setting($data) {
if ($this->paramtype === PARAM_INT and $data === '') {
// do not complain if '' used instead of 0
$data = 0;
}
// $data is a string
$validated = $this->validate($data);
if ($validated !== true) {
return $validated;
}
return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
}
/**
* Validate data before storage
* @param string data
* @return mixed true if ok string if error found
*/
public function validate($data) {
// allow paramtype to be a custom regex if it is the form of /pattern/
if (preg_match('#^/.*/$#', $this->paramtype)) {
if (preg_match($this->paramtype, $data)) {
return true;
} else {
return get_string('validateerror', 'admin');
}
} else if ($this->paramtype === PARAM_RAW) {
return true;
} else {
$cleaned = clean_param($data, $this->paramtype);
if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
return true;
} else {
return get_string('validateerror', 'admin');
}
}
}
/**
* Return an XHTML string for the setting
* @return string Returns an XHTML string
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
return format_admin_setting($this, $this->visiblename,
'<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
$this->description, true, '', $default, $query);
}
}
/**
* General text area without html editor.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configtextarea extends admin_setting_configtext {
private $rows;
private $cols;
/**
* @param string $name
* @param string $visiblename
* @param string $description
* @param mixed $defaultsetting string or array
* @param mixed $paramtype
* @param string $cols The number of columns to make the editor
* @param string $rows The number of rows to make the editor
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
$this->rows = $rows;
$this->cols = $cols;
parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
}
/**
* Returns an XHTML string for the editor
*
* @param string $data
* @param string $query
* @return string XHTML string for the editor
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
$defaultinfo = $default;
if (!is_null($default) and $default !== '') {
$defaultinfo = "\n".$default;
}
return format_admin_setting($this, $this->visiblename,
'<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
$this->description, true, '', $defaultinfo, $query);
}
}
/**
* General text area with html editor.
*/
class admin_setting_confightmleditor extends admin_setting_configtext {
private $rows;
private $cols;
/**
* @param string $name
* @param string $visiblename
* @param string $description
* @param mixed $defaultsetting string or array
* @param mixed $paramtype
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
$this->rows = $rows;
$this->cols = $cols;
parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
editors_head_setup();
}
/**
* Returns an XHTML string for the editor
*
* @param string $data
* @param string $query
* @return string XHTML string for the editor
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
$defaultinfo = $default;
if (!is_null($default) and $default !== '') {
$defaultinfo = "\n".$default;
}
$editor = editors_get_preferred_editor(FORMAT_HTML);
$editor->use_editor($this->get_id(), array('noclean'=>true));
return format_admin_setting($this, $this->visiblename,
'<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
$this->description, true, '', $defaultinfo, $query);
}
}
/**
* Password field, allows unmasking of password
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configpasswordunmask extends admin_setting_configtext {
/**
* 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
* @param string $description long localised info
* @param string $defaultsetting default password
*/
public function __construct($name, $visiblename, $description, $defaultsetting) {
parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
}
/**
* Returns XHTML for the field
* Writes Javascript into the HTML below right before the last div
*
* @todo Make javascript available through newer methods if possible
* @param string $data Value for the field
* @param string $query Passed as final argument for format_admin_setting
* @return string XHTML field
*/
public function output_html($data, $query='') {
$id = $this->get_id();
$unmask = get_string('unmaskpassword', 'form');
$unmaskjs = '<script type="text/javascript">
//<![CDATA[
var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
var unmaskchb = document.createElement("input");
unmaskchb.setAttribute("type", "checkbox");
unmaskchb.setAttribute("id", "'.$id.'unmask");
unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
unmaskdiv.appendChild(unmaskchb);
var unmasklbl = document.createElement("label");
unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
if (is_ie) {
unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
} else {
unmasklbl.setAttribute("for", "'.$id.'unmask");
}
unmaskdiv.appendChild(unmasklbl);
if (is_ie) {
// ugly hack to work around the famous onchange IE bug
unmaskchb.onclick = function() {this.blur();};
unmaskdiv.onclick = function() {this.blur();};
}
//]]>
</script>';
return format_admin_setting($this, $this->visiblename,
'<div class="form-password"><input type="password" size="'.$this->size.'" id="'.$id.'" name="'.$this->get_full_name().'" value="'.s($data).'" /><div class="unmask" id="'.$id.'unmaskdiv"></div>'.$unmaskjs.'</div>',
$this->description, true, '', NULL, $query);
}
}
/**
* Path to directory
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configfile extends admin_setting_configtext {
/**
* 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
* @param string $description long localised info
* @param string $defaultdirectory default directory location
*/
public function __construct($name, $visiblename, $description, $defaultdirectory) {
parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
}
/**
* Returns XHTML for the field
*
* Returns XHTML for the field and also checks whether the file
* specified in $data exists using file_exists()
*
* @param string $data File name and path to use in value attr
* @param string $query
* @return string XHTML field
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
if ($data) {
if (file_exists($data)) {
$executable = '<span class="pathok">&#x2714;</span>';
} else {
$executable = '<span class="patherror">&#x2718;</span>';
}
} else {
$executable = '';
}
return format_admin_setting($this, $this->visiblename,
'<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
$this->description, true, '', $default, $query);
}
}
/**
* Path to executable file
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configexecutable extends admin_setting_configfile {
/**
* Returns an XHTML field
*
* @param string $data This is the value for the field
* @param string $query
* @return string XHTML field
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
if ($data) {
if (file_exists($data) and is_executable($data)) {
$executable = '<span class="pathok">&#x2714;</span>';
} else {
$executable = '<span class="patherror">&#x2718;</span>';
}
} else {
$executable = '';
}
return format_admin_setting($this, $this->visiblename,
'<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
$this->description, true, '', $default, $query);
}
}
/**
* Path to directory
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configdirectory extends admin_setting_configfile {
/**
* Returns an XHTML field
*
* @param string $data This is the value for the field
* @param string $query
* @return string XHTML
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
if ($data) {
if (file_exists($data) and is_dir($data)) {
$executable = '<span class="pathok">&#x2714;</span>';
} else {
$executable = '<span class="patherror">&#x2718;</span>';
}
} else {
$executable = '';
}
return format_admin_setting($this, $this->visiblename,
'<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
$this->description, true, '', $default, $query);
}
}
/**
* Checkbox
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configcheckbox extends admin_setting {
/** @var string Value used when checked */
public $yes;
/** @var string Value used when not checked */
public $no;
/**
* 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
* @param string $description long localised info
* @param string $defaultsetting
* @param string $yes value used when checked
* @param string $no value used when not checked
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
parent::__construct($name, $visiblename, $description, $defaultsetting);
$this->yes = (string)$yes;
$this->no = (string)$no;
}
/**
* Retrieves the current setting using the objects name
*
* @return string
*/
public function get_setting() {
return $this->config_read($this->name);
}
/**
* Sets the value for the setting
*
* Sets the value for the setting to either the yes or no values
* of the object by comparing $data to yes
*
* @param mixed $data Gets converted to str for comparison against yes value
* @return string empty string or error
*/
public function write_setting($data) {
if ((string)$data === $this->yes) { // convert to strings before comparison
$data = $this->yes;
} else {
$data = $this->no;
}
return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
}
/**
* Returns an XHTML checkbox field
*
* @param string $data If $data matches yes then checkbox is checked
* @param string $query
* @return string XHTML field
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
if (!is_null($default)) {
if ((string)$default === $this->yes) {
$defaultinfo = get_string('checkboxyes', 'admin');
} else {
$defaultinfo = get_string('checkboxno', 'admin');
}
} else {
$defaultinfo = NULL;
}
if ((string)$data === $this->yes) { // convert to strings before comparison
$checked = 'checked="checked"';
} else {
$checked = '';
}
return format_admin_setting($this, $this->visiblename,
'<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
.'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
$this->description, true, '', $defaultinfo, $query);
}
}
/**
* Multiple checkboxes, each represents different value, stored in csv format
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configmulticheckbox extends admin_setting {
/** @var array Array of choices value=>label */
public $choices;
/**
* Constructor: uses parent::__construct
*
* @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
* @param string $description long localised info
* @param array $defaultsetting array of selected
* @param array $choices array of $value=>$label for each checkbox
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
$this->choices = $choices;
parent::__construct($name, $visiblename, $description, $defaultsetting);
}
/**
* This public function may be used in ancestors for lazy loading of choices
*
* @todo Check if this function is still required content commented out only returns true
* @return bool true if loaded, false if error
*/
public function load_choices() {
/*
if (is_array($this->choices)) {
return true;
}
.... load choices here
*/
return true;
}
/**
* Is setting related to query text - used when searching
*
* @param string $query
* @return bool true on related, false on not or failure
*/
public function is_related($query) {
if (!$this->load_choices() or empty($this->choices)) {
return false;
}
if (parent::is_related($query)) {
return true;
}
foreach ($this->choices as $desc) {
if (strpos(textlib::strtolower($desc), $query) !== false) {
return true;
}
}
return false;
}
/**
* Returns the current setting if it is set
*
* @return mixed null if null, else an array
*/
public function get_setting() {
$result = $this->config_read($this->name);
if (is_null($result)) {
return NULL;
}
if ($result === '') {
return array();
}
$enabled = explode(',', $result);
$setting = array();
foreach ($enabled as $option) {
$setting[$option] = 1;
}
return $setting;
}
/**
* Saves the setting(s) provided in $data
*
* @param array $data An array of data, if not array returns empty str
* @return mixed empty string on useless data or bool true=success, false=failed
*/
public function write_setting($data) {
if (!is_array($data)) {
return ''; // ignore it
}
if (!$this->load_choices() or empty($this->choices)) {
return '';
}
unset($data['xxxxx']);
$result = array();
foreach ($data as $key => $value) {
if ($value and array_key_exists($key, $this->choices)) {
$result[] = $key;
}
}
return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
}
/**
* Returns XHTML field(s) as required by choices
*
* Relies on data being an array should data ever be another valid vartype with
* acceptable value this may cause a warning/error
* if (!is_array($data)) would fix the problem
*
* @todo Add vartype handling to ensure $data is an array
*
* @param array $data An array of checked values
* @param string $query
* @return string XHTML field
*/
public function output_html($data, $query='') {
if (!$this->load_choices() or empty($this->choices)) {
return '';
}
$default = $this->get_defaultsetting();
if (is_null($default)) {
$default = array();
}
if (is_null($data)) {
$data = array();
}
$options = array();
$defaults = array();
foreach ($this->choices as $key=>$description) {
if (!empty($data[$key])) {
$checked = 'checked="checked"';
} else {
$checked = '';
}
if (!empty($default[$key])) {
$defaults[] = $description;
}
$options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
.'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
}
if (is_null($default)) {
$defaultinfo = NULL;
} else if (!empty($defaults)) {
$defaultinfo = implode(', ', $defaults);
} else {
$defaultinfo = get_string('none');
}
$return = '<div class="form-multicheckbox">';
$return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
if ($options) {
$return .= '<ul>';
foreach ($options as $option) {
$return .= '<li>'.$option.'</li>';
}
$return .= '</ul>';
}
$return .= '</div>';
return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
}
}
/**
* Multiple checkboxes 2, value stored as string 00101011
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
/**
* Returns the setting if set
*
* @return mixed null if not set, else an array of set settings
*/
public function get_setting() {
$result = $this->config_read($this->name);
if (is_null($result)) {
return NULL;
}
if (!$this->load_choices()) {
return NULL;
}
$result = str_pad($result, count($this->choices), '0');
$result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
$setting = array();
foreach ($this->choices as $key=>$unused) {
$value = array_shift($result);
if ($value) {
$setting[$key] = 1;
}
}
return $setting;
}
/**
* Save setting(s) provided in $data param
*
* @param array $data An array of settings to save
* @return mixed empty string for bad data or bool true=>success, false=>error
*/
public function write_setting($data) {
if (!is_array($data)) {
return ''; // ignore it
}
if (!$this->load_choices() or empty($this->choices)) {
return '';
}
$result = '';
foreach ($this->choices as $key=>$unused) {
if (!empty($data[$key])) {
$result .= '1';
} else {
$result .= '0';
}
}
return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
}
}
/**
* Select one value from list
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configselect extends admin_setting {
/** @var array Array of choices value=>label */
public $choices;
/**
* 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
* @param string $description long localised info
* @param string|int $defaultsetting
* @param array $choices array of $value=>$label for each selection
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
$this->choices = $choices;
parent::__construct($name, $visiblename, $description, $defaultsetting);
}
/**
* This function may be used in ancestors for lazy loading of choices
*
* Override this method if loading of choices is expensive, such
* as when it requires multiple db requests.
*
* @return bool true if loaded, false if error
*/
public function load_choices() {
/*
if (is_array($this->choices)) {
return true;
}
.... load choices here
*/
return true;
}
/**
* Check if this is $query is related to a choice
*
* @param string $query
* @return bool true if related, false if not
*/
public function is_related($query) {
if (parent::is_related($query)) {
return true;
}
if (!$this->load_choices()) {
return false;
}
foreach ($this->choices as $key=>$value) {
if (strpos(textlib::strtolower($key), $query) !== false) {
return true;
}
if (strpos(textlib::strtolower($value), $query) !== false) {
return true;
}
}
return false;
}
/**
* Return the setting
*
* @return mixed returns config if successful else null
*/
public function get_setting() {
return $this->config_read($this->name);
}
/**
* Save a setting
*
* @param string $data
* @return string empty of error string
*/
public function write_setting($data) {
if (!$this->load_choices() or empty($this->choices)) {
return '';
}
if (!array_key_exists($data, $this->choices)) {
return ''; // ignore it
}
return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
}
/**
* Returns XHTML select field
*
* Ensure the options are loaded, and generate the XHTML for the select
* element and any warning message. Separating this out from output_html
* makes it easier to subclass this class.
*
* @param string $data the option to show as selected.
* @param string $current the currently selected option in the database, null if none.
* @param string $default the default selected option.
* @return array the HTML for the select element, and a warning message.
*/
public function output_select_html($data, $current, $default, $extraname = '') {
if (!$this->load_choices() or empty($this->choices)) {
return array('', '');
}
$warning = '';
if (is_null($current)) {
// first run
} else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
// no warning
} else if (!array_key_exists($current, $this->choices)) {
$warning = get_string('warningcurrentsetting', 'admin', s($current));
if (!is_null($default) and $data == $current) {
$data = $default; // use default instead of first value when showing the form
}
}
$selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
foreach ($this->choices as $key => $value) {
// the string cast is needed because key may be integer - 0 is equal to most strings!
$selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
}
$selecthtml .= '</select>';
return array($selecthtml, $warning);
}
/**
* Returns XHTML select field and wrapping div(s)
*
* @see output_select_html()
*
* @param string $data the option to show as selected
* @param string $query
* @return string XHTML field and wrapping div
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
$current = $this->get_setting();
list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
if (!$selecthtml) {
return '';
}
if (!is_null($default) and array_key_exists($default, $this->choices)) {
$defaultinfo = $this->choices[$default];
} else {
$defaultinfo = NULL;
}
$return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
}
}
/**
* Select multiple items from list
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configmultiselect extends admin_setting_configselect {
/**
* 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
* @param string $description long localised info
* @param array $defaultsetting array of selected items
* @param array $choices array of $value=>$label for each list item
*/
public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
}
/**
* Returns the select setting(s)
*
* @return mixed null or array. Null if no settings else array of setting(s)
*/
public function get_setting() {
$result = $this->config_read($this->name);
if (is_null($result)) {
return NULL;
}
if ($result === '') {
return array();
}
return explode(',', $result);
}
/**
* Saves setting(s) provided through $data
*
* Potential bug in the works should anyone call with this function
* using a vartype that is not an array
*
* @param array $data
*/
public function write_setting($data) {
if (!is_array($data)) {
return ''; //ignore it
}
if (!$this->load_choices() or empty($this->choices)) {
return '';
}
unset($data['xxxxx']);
$save = array();
foreach ($data as $value) {
if (!array_key_exists($value, $this->choices)) {
continue; // ignore it
}
$save[] = $value;
}
return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
}
/**
* Is setting related to query text - used when searching
*
* @param string $query
* @return bool true if related, false if not
*/
public function is_related($query) {
if (!$this->load_choices() or empty($this->choices)) {
return false;
}
if (parent::is_related($query)) {
return true;
}
foreach ($this->choices as $desc) {
if (strpos(textlib::strtolower($desc), $query) !== false) {
return true;
}
}
return false;
}
/**
* Returns XHTML multi-select field
*
* @todo Add vartype handling to ensure $data is an array
* @param array $data Array of values to select by default
* @param string $query
* @return string XHTML multi-select field
*/
public function output_html($data, $query='') {
if (!$this->load_choices() or empty($this->choices)) {
return '';
}
$choices = $this->choices;
$default = $this->get_defaultsetting();
if (is_null($default)) {
$default = array();
}
if (is_null($data)) {
$data = array();
}
$defaults = array();
$size = min(10, count($this->choices));
$return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
$return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
foreach ($this->choices as $key => $description) {
if (in_array($key, $data)) {
$selected = 'selected="selected"';
} else {
$selected = '';
}
if (in_array($key, $default)) {
$defaults[] = $description;
}
$return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
}
if (is_null($default)) {
$defaultinfo = NULL;
} if (!empty($defaults)) {
$defaultinfo = implode(', ', $defaults);
} else {
$defaultinfo = get_string('none');
}
$return .= '</select></div>';
return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
}
}
/**
* Time selector
*
* This is a liiitle bit messy. we're using two selects, but we're returning
* them as an array named after $name (so we only use $name2 internally for the setting)
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configtime extends admin_setting {
/** @var string Used for setting second select (minutes) */
public $name2;
/**
* Constructor
* @param string $hoursname setting for hours
* @param string $minutesname setting for hours
* @param string $visiblename localised
* @param string $description long localised info
* @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
*/
public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
$this->name2 = $minutesname;
parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
}
/**
* Get the selected time
*
* @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
*/
public function get_setting() {
$result1 = $this->config_read($this->name);
$result2 = $this->config_read($this->name2);
if (is_null($result1) or is_null($result2)) {
return NULL;
}
return array('h' => $result1, 'm' => $result2);
}
/**
* Store the time (hours and minutes)
*
* @param array $data Must be form 'h'=>xx, 'm'=>xx
* @return bool true if success, false if not
*/
public function write_setting($data) {
if (!is_array($data)) {
return '';
}
$result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
return ($result ? '' : get_string('errorsetting', 'admin'));
}
/**
* Returns XHTML time select fields
*
* @param array $data Must be form 'h'=>xx, 'm'=>xx
* @param string $query
* @return string XHTML time select fields and wrapping div(s)
*/
public function output_html($data, $query='') {
$default = $this->get_defaultsetting();
if (is_array($default)) {
$defaultinfo = $default['h'].':'.$default['m'];
} else {
$defaultinfo = NULL;
}
$return = '<div class="form-time defaultsnext">'.
'<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
for ($i = 0; $i < 24; $i++) {
$return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
}
$return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
for ($i = 0; $i < 60; $i += 5) {
$return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
}
$return .= '</select></div>';
return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
}
}
/**
* Used to validate a textarea used for ip addresses
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_configiplist extends admin_setting_configtextarea {
/**
* Validate the contents of the textarea as IP addresses
*
* Used to validate a new line separated list of IP addresses collected from
* a textarea control
*
* @param string $data A list of IP Addresses separated by new lines
* @return mixed bool true for success or string:error on failure
*/
public function validate($data) {
if(!empty($data)) {
$ips = explode("\n", $data);
} else {
return true;
}
$result = true;
foreach($ips as $ip) {
$ip = trim($ip);
if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
$result = true;
} else {
$result = false;
break;
}
}
if($result) {
return true;
} else {
return get_string('validateerror', 'admin');
}
}
}
/**
* An admin setting for selecting one or more users who have a capability
* in the system context
*
* An admin setting for selecting one or more users, who have a particular capability
* in the system context. Warning, make sure the list will never be too long. There is
* no paging or searching of this list.
*
* To correctly get a list of users from this config setting, you need to call the
* get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_users_with_capability extends admin_setting_configmultiselect {
/** @var string The capabilities name */
protected $capability;
/** @var int include admin users too */
protected $includeadmins;
/**
* 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 array $defaultsetting array of usernames
* @param string $capability string capability name.
* @param bool $includeadmins include administrators
*/
function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
$this->capability = $capability;
$this->includeadmins = $includeadmins;
parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
}
/**
* Load all of the uses who have the capability into choice array
*
* @return bool Always returns true
*/
function load_choices() {
if (is_array($this->choices)) {
return true;
}
$users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM),
$this->capability, 'u.id,u.username,u.firstname,u.lastname', 'u.lastname,u.firstname');
$this->choices = array(
'$@NONE@$' => get_string('nobody'),
'$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
);
if ($this->includeadmins) {
$admins = get_admins();
foreach ($admins as $user) {
$this->choices[$user->id] = fullname($user);
}
}
if (is_array($users)) {
foreach ($users as $user) {
$this->choices[$user->id] = fullname($user);
}
}
return true;
}
/**
* Returns the default setting for class
*
* @return mixed Array, or string. Empty string if no default
*/
public function get_defaultsetting() {
$this->load_choices();
$defaultsetting = parent::get_defaultsetting();
if (empty($defaultsetting)) {
return array('$@NONE@$');
} else if (array_key_exists($defaultsetting, $this->choices)) {
return $defaultsetting;
} else {
return '';
}
}
/**
* Returns the current setting
*
* @return mixed array or string
*/
public function get_setting() {
$result = parent::get_setting();
if ($result === null) {
// this is necessary for settings upgrade
return null;
}
if (empty($result)) {
$result = array('$@NONE@$');
}
return $result;
}
/**
* Save the chosen setting provided as $data
*
* @param array $data
* @return mixed string or array
*/
public function write_setting($data) {
// If all is selected, remove any explicit options.
if (in_array('$@ALL@$', $data)) {
$data = array('$@ALL@$');
}
// None never needs to be written to the DB.
if (in_array('$@NONE@$', $data)) {
unset($data[array_search('$@NONE@$', $data)]);
}
return parent::write_setting($data);
}
}
/**
* Special checkbox for calendar - resets SESSION vars.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
/**
* Calls the parent::__construct with default values
*
* name => calendar_adminseesall
* visiblename => get_string('adminseesall', 'admin')
* description => get_string('helpadminseesall', 'admin')
* defaultsetting => 0
*/
public function __construct() {
parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
get_string('helpadminseesall', 'admin'), '0');
}
/**
* Stores the setting passed in $data
*
* @param mixed gets converted to string for comparison
* @return string empty string or error message
*/
public function write_setting($data) {
global $SESSION;
return parent::write_setting($data);
}
}
/**
* Special select for settings that are altered in setup.php and can not be altered on the fly
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_special_selectsetup extends admin_setting_configselect {
/**
* Reads the setting directly from the database
*
* @return mixed
*/
public function get_setting() {
// read directly from db!
return get_config(NULL, $this->name);
}
/**
* Save the setting passed in $data
*
* @param string $data The setting to save
* @return string empty or error message
*/
public function write_setting($data) {
global $CFG;
// do not change active CFG setting!
$current = $CFG->{$this->name};
$result = parent::write_setting($data);
$CFG->{$this->name} = $current;
return $result;
}
}
/**
* Special select for frontpage - stores data in course table
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_sitesetselect extends admin_setting_configselect {
/**
* Returns the site name for the selected site
*
* @see get_site()
* @return string The site name of the selected site
*/
public function get_setting() {
$site = get_site();
return $site->{$this->name};
}
/**
* Updates the database and save the setting
*
* @param string data
* @return string empty or error message
*/
public function write_setting($data) {
global $DB, $SITE;
if (!in_array($data, array_keys($this->choices))) {
return get_string('errorsetting', 'admin');
}
$record = new stdClass();
$record->id = SITEID;
$temp = $this->name;
$record->$temp = $data;
$record->timemodified = time();
// update $SITE
$SITE->{$this->name} = $data;
return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
}
}
/**
* Select for blog's bloglevel setting: if set to 0, will set blog_menu
* block to hidden.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_bloglevel extends admin_setting_configselect {
/**
* Updates the database and save the setting
*
* @param string data
* @return string empty or error message
*/
public function write_setting($data) {
global $DB, $CFG;
if ($data == 0) {
$blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
foreach ($blogblocks as $block) {
$DB->set_field('block', 'visible', 0, array('id' => $block->id));
}
} else {
// reenable all blocks only when switching from disabled blogs
if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
$blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
foreach ($blogblocks as $block) {
$DB->set_field('block', 'visible', 1, array('id' => $block->id));
}
}
}
return parent::write_setting($data);
}
}
/**
* Special select - lists on the frontpage - hacky
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_courselist_frontpage extends admin_setting {
/** @var array Array of choices value=>label */
public $choices;
/**
* Construct override, requires one param
*
* @param bool $loggedin Is the user logged in
*/
public function __construct($loggedin) {
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
$name = 'frontpage'.($loggedin ? 'loggedin' : '');
$visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
$description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
$defaults = array(FRONTPAGECOURSELIST);
parent::__construct($name, $visiblename, $description, $defaults);
}
/**
* Loads the choices available
*
* @return bool always returns true
*/
public function load_choices() {
global $DB;
if (is_array($this->choices)) {
return true;
}
$this->choices = array(FRONTPAGENEWS => get_string('frontpagenews'),
FRONTPAGECOURSELIST => get_string('frontpagecourselist'),
FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
'none' => get_string('none'));
if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
unset($this->choices[FRONTPAGECOURSELIST]);
}
return true;
}
/**
* Returns the selected settings
*
* @param mixed array or setting or null
*/
public function get_setting() {
$result = $this->config_read($this->name);
if (is_null($result)) {
return NULL;
}
if ($result === '') {
return array();
}
return explode(',', $result);
}
/**
* Save the selected options
*
* @param array $data
* @return mixed empty string (data is not an array) or bool true=success false=failure
*/
public function write_setting($data) {
if (!is_array($data)) {
return '';
}
$this->load_choices();
$save = array();
foreach($data as $datum) {
if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
continue;
}
$save[$datum] = $datum; // no duplicates
}
return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
}
/**
* Return XHTML select field and wrapping div
*
* @todo Add vartype handling to make sure $data is an array
* @param array $data Array of elements to select by default
* @return string XHTML select field and wrapping div
*/
public function output_html($data, $query='') {
$this->load_choices();
$currentsetting = array();
foreach ($data as $key) {
if ($key != 'none' and array_key_exists($key, $this->choices)) {
$currentsetting[] = $key; // already selected first
}
}
$return = '<div class="form-group">';
for ($i = 0; $i < count($this->choices) - 1; $i++) {
if (!array_key_exists($i, $currentsetting)) {
$currentsetting[$i] = 'none'; //none
}
$return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
foreach ($this->choices as $key => $value) {
$return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
}
$return .= '</select>';
if ($i !== count($this->choices) - 2) {
$return .= '<br />';
}
}
$return .= '</div>';
return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
}
}
/**
* Special checkbox for frontpage - stores data in course table
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
/**
* Returns the current sites name
*
* @return string
*/
public function get_setting() {
$site = get_site();
return $site->{$this->name};
}
/**
* Save the selected setting
*
* @param string $data The selected site
* @return string empty string or error message
*/
public function write_setting($data) {
global $DB, $SITE;
$record = new stdClass();
$record->id = SITEID;
$record->{$this->name} = ($data == '1' ? 1 : 0);
$re