MDL-36795 - lib / administration: maxsections now limits the default setting for...
[moodle.git] / lib / adminlib.php
CommitLineData
db26acd4 1<?php
0c079f19 2// This file is part of Moodle - http://moodle.org/
3//
db26acd4 4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
0c079f19 13//
db26acd4 14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
88a7228a 16
17/**
0c079f19 18 * Functions and classes used during installation, upgrades and for admin settings.
db26acd4 19 *
0c079f19 20 * ADMIN SETTINGS TREE INTRODUCTION
db26acd4 21 *
22 * This file performs the following tasks:
23 * -it defines the necessary objects and interfaces to build the Moodle
24 * admin hierarchy
61ef8f9f 25 * -it defines the admin_externalpage_setup()
db26acd4 26 *
27 * ADMIN_SETTING OBJECTS
28 *
29 * Moodle settings are represented by objects that inherit from the admin_setting
30 * class. These objects encapsulate how to read a setting, how to write a new value
31 * to a setting, and how to appropriately display the HTML to modify the setting.
32 *
33 * ADMIN_SETTINGPAGE OBJECTS
34 *
35 * The admin_setting objects are then grouped into admin_settingpages. The latter
36 * appear in the Moodle admin tree block. All interaction with admin_settingpage
37 * objects is handled by the admin/settings.php file.
38 *
39 * ADMIN_EXTERNALPAGE OBJECTS
40 *
41 * There are some settings in Moodle that are too complex to (efficiently) handle
42 * with admin_settingpages. (Consider, for example, user management and displaying
43 * lists of users.) In this case, we use the admin_externalpage object. This object
44 * places a link to an external PHP file in the admin tree block.
45 *
46 * If you're using an admin_externalpage object for some settings, you can take
47 * advantage of the admin_externalpage_* functions. For example, suppose you wanted
48 * to add a foo.php file into admin. First off, you add the following line to
49 * admin/settings/first.php (at the end of the file) or to some other file in
50 * admin/settings:
51 * <code>
52 * $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
53 * $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
54 * </code>
55 *
56 * Next, in foo.php, your file structure would resemble the following:
57 * <code>
4d553cb2 58 * require(dirname(dirname(dirname(__FILE__))).'/config.php');
db26acd4 59 * require_once($CFG->libdir.'/adminlib.php');
60 * admin_externalpage_setup('foo');
61 * // functionality like processing form submissions goes here
4d553cb2 62 * echo $OUTPUT->header();
db26acd4 63 * // your HTML goes here
4d553cb2 64 * echo $OUTPUT->footer();
db26acd4 65 * </code>
66 *
67 * The admin_externalpage_setup() function call ensures the user is logged in,
68 * and makes sure that they have the proper role permission to access the page.
01a2ce80 69 * It also configures all $PAGE properties needed for navigation.
db26acd4 70 *
71 * ADMIN_CATEGORY OBJECTS
72 *
73 * Above and beyond all this, we have admin_category objects. These objects
74 * appear as folders in the admin tree block. They contain admin_settingpage's,
75 * admin_externalpage's, and other admin_category's.
76 *
77 * OTHER NOTES
78 *
79 * admin_settingpage's, admin_externalpage's, and admin_category's all inherit
80 * from part_of_admin_tree (a pseudointerface). This interface insists that
81 * a class has a check_access method for access permissions, a locate method
82 * used to find a specific node in the admin tree and find parent path.
83 *
84 * admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
85 * interface ensures that the class implements a recursive add function which
86 * accepts a part_of_admin_tree object and searches for the proper place to
87 * put it. parentable_part_of_admin_tree implies part_of_admin_tree.
88 *
89 * Please note that the $this->name field of any part_of_admin_tree must be
90 * UNIQUE throughout the ENTIRE admin tree.
91 *
92 * The $this->name field of an admin_setting object (which is *not* part_of_
93 * admin_tree) must be unique on the respective admin_settingpage where it is
94 * used.
95 *
0c079f19 96 * Original author: Vincenzo K. Marcovecchio
97 * Maintainer: Petr Skoda
db26acd4 98 *
78bfb562
PS
99 * @package core
100 * @subpackage admin
101 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
102 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
88a7228a 103 */
104
78bfb562
PS
105defined('MOODLE_INTERNAL') || die();
106
598b38f7 107/// Add libraries
108require_once($CFG->libdir.'/ddllib.php');
b1f93b15 109require_once($CFG->libdir.'/xmlize.php');
3bcbd80e 110require_once($CFG->libdir.'/messagelib.php');
b1f93b15 111
bba0beae 112define('INSECURE_DATAROOT_WARNING', 1);
113define('INSECURE_DATAROOT_ERROR', 2);
0c079f19 114
ed996ede 115/**
116 * Automatically clean-up all plugin data and remove the plugin DB tables
117 *
118 * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
119 * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
120 * @uses global $OUTPUT to produce notices and other messages
121 * @return void
122 */
123function uninstall_plugin($type, $name) {
124 global $CFG, $DB, $OUTPUT;
125
846e4e17
PS
126 // recursively uninstall all module subplugins first
127 if ($type === 'mod') {
976dc837 128 if (file_exists("$CFG->dirroot/mod/$name/db/subplugins.php")) {
846e4e17 129 $subplugins = array();
976dc837 130 include("$CFG->dirroot/mod/$name/db/subplugins.php");
846e4e17
PS
131 foreach ($subplugins as $subplugintype=>$dir) {
132 $instances = get_plugin_list($subplugintype);
133 foreach ($instances as $subpluginname => $notusedpluginpath) {
134 uninstall_plugin($subplugintype, $subpluginname);
135 }
ed996ede 136 }
137 }
846e4e17 138
ed996ede 139 }
140
2138244c 141 $component = $type . '_' . $name; // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
142
143 if ($type === 'mod') {
144 $pluginname = $name; // eg. 'forum'
70d35fe6
PS
145 if (get_string_manager()->string_exists('modulename', $component)) {
146 $strpluginname = get_string('modulename', $component);
147 } else {
148 $strpluginname = $component;
149 }
df997f84 150
2138244c 151 } else {
9baf6825 152 $pluginname = $component;
70d35fe6
PS
153 if (get_string_manager()->string_exists('pluginname', $component)) {
154 $strpluginname = get_string('pluginname', $component);
155 } else {
156 $strpluginname = $component;
157 }
2138244c 158 }
df997f84 159
ed996ede 160 echo $OUTPUT->heading($pluginname);
161
162 $plugindirectory = get_plugin_directory($type, $name);
163 $uninstalllib = $plugindirectory . '/db/uninstall.php';
164 if (file_exists($uninstalllib)) {
165 require_once($uninstalllib);
2138244c 166 $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall'; // eg. 'xmldb_workshop_uninstall()'
ed996ede 167 if (function_exists($uninstallfunction)) {
2138244c 168 if (!$uninstallfunction()) {
ed996ede 169 echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $pluginname);
170 }
171 }
172 }
173
df997f84 174 if ($type === 'mod') {
3e7069e7 175 // perform cleanup tasks specific for activity modules
ed996ede 176
ed996ede 177 if (!$module = $DB->get_record('modules', array('name' => $name))) {
178 print_error('moduledoesnotexist', 'error');
179 }
180
181 // delete all the relevant instances from all course sections
182 if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) {
183 foreach ($coursemods as $coursemod) {
184 if (!delete_mod_from_section($coursemod->id, $coursemod->section)) {
185 echo $OUTPUT->notification("Could not delete the $strpluginname with id = $coursemod->id from section $coursemod->section");
186 }
187 }
188 }
189
190 // clear course.modinfo for courses that used this module
191 $sql = "UPDATE {course}
192 SET modinfo=''
193 WHERE id IN (SELECT DISTINCT course
194 FROM {course_modules}
195 WHERE module=?)";
196 $DB->execute($sql, array($module->id));
197
198 // delete all the course module records
2138244c 199 $DB->delete_records('course_modules', array('module' => $module->id));
ed996ede 200
201 // delete module contexts
202 if ($coursemods) {
203 foreach ($coursemods as $coursemod) {
204 if (!delete_context(CONTEXT_MODULE, $coursemod->id)) {
205 echo $OUTPUT->notification("Could not delete the context for $strpluginname with id = $coursemod->id");
206 }
207 }
208 }
209
210 // delete the module entry itself
2138244c 211 $DB->delete_records('modules', array('name' => $module->name));
ed996ede 212
213 // cleanup the gradebook
214 require_once($CFG->libdir.'/gradelib.php');
215 grade_uninstalled_module($module->name);
216
217 // Perform any custom uninstall tasks
218 if (file_exists($CFG->dirroot . '/mod/' . $module->name . '/lib.php')) {
219 require_once($CFG->dirroot . '/mod/' . $module->name . '/lib.php');
220 $uninstallfunction = $module->name . '_uninstall';
221 if (function_exists($uninstallfunction)) {
222 debugging("{$uninstallfunction}() has been deprecated. Use the plugin's db/uninstall.php instead", DEBUG_DEVELOPER);
2138244c 223 if (!$uninstallfunction()) {
ed996ede 224 echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $module->name.'!');
225 }
226 }
227 }
df997f84
PS
228
229 } else if ($type === 'enrol') {
230 // NOTE: this is a bit brute force way - it will not trigger events and hooks properly
231 // nuke all role assignments
232 role_unassign_all(array('component'=>$component));
233 // purge participants
234 $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($name));
235 // purge enrol instances
236 $DB->delete_records('enrol', array('enrol'=>$name));
237 // tweak enrol settings
238 if (!empty($CFG->enrol_plugins_enabled)) {
239 $enabledenrols = explode(',', $CFG->enrol_plugins_enabled);
240 $enabledenrols = array_unique($enabledenrols);
241 $enabledenrols = array_flip($enabledenrols);
242 unset($enabledenrols[$name]);
243 $enabledenrols = array_flip($enabledenrols);
244 if (is_array($enabledenrols)) {
245 set_config('enrol_plugins_enabled', implode(',', $enabledenrols));
246 }
247 }
54483279
PS
248
249 } else if ($type === 'block') {
250 if ($block = $DB->get_record('block', array('name'=>$name))) {
251 // Inform block it's about to be deleted
252 if (file_exists("$CFG->dirroot/blocks/$block->name/block_$block->name.php")) {
253 $blockobject = block_instance($block->name);
254 if ($blockobject) {
255 $blockobject->before_delete(); //only if we can create instance, block might have been already removed
256 }
257 }
258
259 // First delete instances and related contexts
260 $instances = $DB->get_records('block_instances', array('blockname' => $block->name));
261 foreach($instances as $instance) {
262 blocks_delete_instance($instance);
263 }
264
265 // Delete block
266 $DB->delete_records('block', array('id'=>$block->id));
267 }
ed996ede 268 }
269
70d35fe6 270 // perform clean-up task common for all the plugin/subplugin types
ed996ede 271
bc81eadb
JM
272 //delete the web service functions and pre-built services
273 require_once($CFG->dirroot.'/lib/externallib.php');
274 external_delete_descriptions($component);
275
ed996ede 276 // delete calendar events
2138244c 277 $DB->delete_records('event', array('modulename' => $pluginname));
ed996ede 278
279 // delete all the logs
2138244c 280 $DB->delete_records('log', array('module' => $pluginname));
ed996ede 281
282 // delete log_display information
c6d75bff 283 $DB->delete_records('log_display', array('component' => $component));
ed996ede 284
285 // delete the module configuration records
2138244c 286 unset_all_config_for_plugin($pluginname);
ed996ede 287
3bcbd80e 288 // delete message provider
8e265315
RK
289 message_provider_uninstall($component);
290
291 // delete message processor
292 if ($type === 'message') {
293 message_processor_uninstall($name);
294 }
3bcbd80e 295
ed996ede 296 // delete the plugin tables
297 $xmldbfilepath = $plugindirectory . '/db/install.xml';
54483279
PS
298 drop_plugin_tables($component, $xmldbfilepath, false);
299 if ($type === 'mod' or $type === 'block') {
300 // non-frankenstyle table prefixes
301 drop_plugin_tables($name, $xmldbfilepath, false);
302 }
ed996ede 303
ed996ede 304 // delete the capabilities that were defined by this module
305 capabilities_cleanup($component);
306
875e5f07 307 // remove event handlers and dequeue pending events
ed996ede 308 events_uninstall($component);
309
310 echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
311}
312
e243c8c4
DM
313/**
314 * Returns the version of installed component
315 *
316 * @param string $component component name
317 * @param string $source either 'disk' or 'installed' - where to get the version information from
318 * @return string|bool version number or false if the component is not found
319 */
320function get_component_version($component, $source='installed') {
321 global $CFG, $DB;
322
323 list($type, $name) = normalize_component($component);
324
325 // moodle core or a core subsystem
326 if ($type === 'core') {
327 if ($source === 'installed') {
328 if (empty($CFG->version)) {
329 return false;
330 } else {
331 return $CFG->version;
332 }
333 } else {
334 if (!is_readable($CFG->dirroot.'/version.php')) {
335 return false;
336 } else {
9a8abf13 337 $version = null; //initialize variable for IDEs
e243c8c4
DM
338 include($CFG->dirroot.'/version.php');
339 return $version;
340 }
341 }
342 }
343
344 // activity module
345 if ($type === 'mod') {
346 if ($source === 'installed') {
347 return $DB->get_field('modules', 'version', array('name'=>$name));
348 } else {
349 $mods = get_plugin_list('mod');
d361c804 350 if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
e243c8c4
DM
351 return false;
352 } else {
353 $module = new stdclass();
d361c804 354 include($mods[$name].'/version.php');
e243c8c4
DM
355 return $module->version;
356 }
357 }
358 }
359
360 // block
361 if ($type === 'block') {
362 if ($source === 'installed') {
363 return $DB->get_field('block', 'version', array('name'=>$name));
364 } else {
365 $blocks = get_plugin_list('block');
366 if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
367 return false;
368 } else {
369 $plugin = new stdclass();
370 include($blocks[$name].'/version.php');
371 return $plugin->version;
372 }
373 }
374 }
375
376 // all other plugin types
377 if ($source === 'installed') {
378 return get_config($type.'_'.$name, 'version');
379 } else {
380 $plugins = get_plugin_list($type);
381 if (empty($plugins[$name])) {
382 return false;
383 } else {
384 $plugin = new stdclass();
385 include($plugins[$name].'/version.php');
386 return $plugin->version;
387 }
388 }
389}
390
b1f93b15 391/**
392 * Delete all plugin tables
db26acd4 393 *
db26acd4 394 * @param string $name Name of plugin, used as table prefix
395 * @param string $file Path to install.xml file
396 * @param bool $feedback defaults to true
0c079f19 397 * @return bool Always returns true
b1f93b15 398 */
399function drop_plugin_tables($name, $file, $feedback=true) {
400 global $CFG, $DB;
401
402 // first try normal delete
8b4ca8f7 403 if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
b1f93b15 404 return true;
405 }
406
407 // then try to find all tables that start with name and are not in any xml file
408 $used_tables = get_used_table_names();
409
410 $tables = $DB->get_tables();
411
412 /// Iterate over, fixing id fields as necessary
413 foreach ($tables as $table) {
414 if (in_array($table, $used_tables)) {
415 continue;
416 }
417
8b4ca8f7 418 if (strpos($table, $name) !== 0) {
419 continue;
420 }
421
b1f93b15 422 // found orphan table --> delete it
423 if ($DB->get_manager()->table_exists($table)) {
424 $xmldb_table = new xmldb_table($table);
eee5d9bb 425 $DB->get_manager()->drop_table($xmldb_table);
b1f93b15 426 }
427 }
428
429 return true;
430}
431
432/**
875e5f07 433 * Returns names of all known tables == tables that moodle knows about.
db26acd4 434 *
435 * @return array Array of lowercase table names
b1f93b15 436 */
437function get_used_table_names() {
438 $table_names = array();
439 $dbdirs = get_db_directories();
440
441 foreach ($dbdirs as $dbdir) {
442 $file = $dbdir.'/install.xml';
443
444 $xmldb_file = new xmldb_file($file);
445
446 if (!$xmldb_file->fileExists()) {
447 continue;
448 }
449
450 $loaded = $xmldb_file->loadXMLStructure();
73fa96d5 451 $structure = $xmldb_file->getStructure();
b1f93b15 452
453 if ($loaded and $tables = $structure->getTables()) {
454 foreach($tables as $table) {
43fb824b 455 $table_names[] = strtolower($table->getName());
b1f93b15 456 }
457 }
458 }
459
460 return $table_names;
461}
462
463/**
464 * Returns list of all directories where we expect install.xml files
db26acd4 465 * @return array Array of paths
b1f93b15 466 */
467function get_db_directories() {
468 global $CFG;
469
470 $dbdirs = array();
471
9baf6825 472 /// First, the main one (lib/db)
b1f93b15 473 $dbdirs[] = $CFG->libdir.'/db';
474
9baf6825 475 /// Then, all the ones defined by get_plugin_types()
17da2e6f 476 $plugintypes = get_plugin_types();
477 foreach ($plugintypes as $plugintype => $pluginbasedir) {
478 if ($plugins = get_plugin_list($plugintype)) {
479 foreach ($plugins as $plugin => $plugindir) {
480 $dbdirs[] = $plugindir.'/db';
76b6c644 481 }
b91b274b 482 }
7cdd8b22 483 }
b1f93b15 484
b1f93b15 485 return $dbdirs;
486}
487
eef868d1 488/**
61460dd6 489 * Try to obtain or release the cron lock.
61460dd6 490 * @param string $name name of lock
875e5f07
PS
491 * @param int $until timestamp when this lock considered stale, null means remove lock unconditionally
492 * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
61460dd6 493 * @return bool true if lock obtained
f3221af9 494 */
61460dd6 495function set_cron_lock($name, $until, $ignorecurrent=false) {
f33e1ed4 496 global $DB;
f3221af9 497 if (empty($name)) {
61460dd6 498 debugging("Tried to get a cron lock for a null fieldname");
f3221af9 499 return false;
500 }
501
61460dd6 502 // remove lock by force == remove from config table
503 if (is_null($until)) {
504 set_config($name, null);
f3221af9 505 return true;
506 }
507
61460dd6 508 if (!$ignorecurrent) {
3e7069e7 509 // read value from db - other processes might have changed it
f33e1ed4 510 $value = $DB->get_field('config', 'value', array('name'=>$name));
61460dd6 511
512 if ($value and $value > time()) {
3e7069e7 513 //lock active
61460dd6 514 return false;
f3221af9 515 }
516 }
61460dd6 517
518 set_config($name, $until);
f3221af9 519 return true;
520}
a597f8a8 521
bba0beae 522/**
523 * Test if and critical warnings are present
524 * @return bool
525 */
526function admin_critical_warnings_present() {
527 global $SESSION;
528
529 if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
530 return 0;
531 }
532
533 if (!isset($SESSION->admin_critical_warning)) {
534 $SESSION->admin_critical_warning = 0;
fbf2c91e 535 if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
bba0beae 536 $SESSION->admin_critical_warning = 1;
537 }
538 }
539
540 return $SESSION->admin_critical_warning;
541}
542
61f9c4b4 543/**
db26acd4 544 * Detects if float supports at least 10 decimal digits
545 *
875e5f07 546 * Detects if float supports at least 10 decimal digits
61f9c4b4 547 * and also if float-->string conversion works as expected.
db26acd4 548 *
61f9c4b4 549 * @return bool true if problem found
550 */
551function is_float_problem() {
552 $num1 = 2009010200.01;
553 $num2 = 2009010200.02;
554
555 return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
556}
557
57e35f32 558/**
559 * Try to verify that dataroot is not accessible from web.
57e35f32 560 *
db26acd4 561 * Try to verify that dataroot is not accessible from web.
562 * It is not 100% correct but might help to reduce number of vulnerable sites.
57e35f32 563 * Protection from httpd.conf and .htaccess is not detected properly.
0c079f19 564 *
db26acd4 565 * @uses INSECURE_DATAROOT_WARNING
566 * @uses INSECURE_DATAROOT_ERROR
567 * @param bool $fetchtest try to test public access by fetching file, default false
875e5f07 568 * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
57e35f32 569 */
bba0beae 570function is_dataroot_insecure($fetchtest=false) {
57e35f32 571 global $CFG;
572
573 $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
574
575 $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
576 $rp = strrev(trim($rp, '/'));
577 $rp = explode('/', $rp);
578 foreach($rp as $r) {
579 if (strpos($siteroot, '/'.$r.'/') === 0) {
580 $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
581 } else {
582 break; // probably alias root
583 }
584 }
585
586 $siteroot = strrev($siteroot);
587 $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
588
bba0beae 589 if (strpos($dataroot, $siteroot) !== 0) {
590 return false;
591 }
592
593 if (!$fetchtest) {
594 return INSECURE_DATAROOT_WARNING;
595 }
596
597 // now try all methods to fetch a test file using http protocol
598
599 $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
600 preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
601 $httpdocroot = $matches[1];
602 $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
71904f4d 603 make_upload_directory('diag');
bba0beae 604 $testfile = $CFG->dataroot.'/diag/public.txt';
605 if (!file_exists($testfile)) {
606 file_put_contents($testfile, 'test file, do not delete');
607 }
608 $teststr = trim(file_get_contents($testfile));
609 if (empty($teststr)) {
9baf6825 610 // hmm, strange
bba0beae 611 return INSECURE_DATAROOT_WARNING;
612 }
613
614 $testurl = $datarooturl.'/diag/public.txt';
041a4b0f 615 if (extension_loaded('curl') and
616 !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
617 !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
618 ($ch = @curl_init($testurl)) !== false) {
bba0beae 619 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
620 curl_setopt($ch, CURLOPT_HEADER, false);
621 $data = curl_exec($ch);
622 if (!curl_errno($ch)) {
623 $data = trim($data);
624 if ($data === $teststr) {
625 curl_close($ch);
626 return INSECURE_DATAROOT_ERROR;
627 }
628 }
629 curl_close($ch);
630 }
631
632 if ($data = @file_get_contents($testurl)) {
633 $data = trim($data);
634 if ($data === $teststr) {
635 return INSECURE_DATAROOT_ERROR;
636 }
637 }
638
639 preg_match('|https?://([^/]+)|i', $testurl, $matches);
640 $sitename = $matches[1];
641 $error = 0;
642 if ($fp = @fsockopen($sitename, 80, $error)) {
643 preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
644 $localurl = $matches[1];
645 $out = "GET $localurl HTTP/1.1\r\n";
646 $out .= "Host: $sitename\r\n";
647 $out .= "Connection: Close\r\n\r\n";
648 fwrite($fp, $out);
649 $data = '';
650 $incoming = false;
651 while (!feof($fp)) {
652 if ($incoming) {
653 $data .= fgets($fp, 1024);
654 } else if (@fgets($fp, 1024) === "\r\n") {
9baf6825 655 $incoming = true;
656 }
bba0beae 657 }
658 fclose($fp);
659 $data = trim($data);
660 if ($data === $teststr) {
661 return INSECURE_DATAROOT_ERROR;
662 }
57e35f32 663 }
bba0beae 664
665 return INSECURE_DATAROOT_WARNING;
57e35f32 666}
6e4dc10f 667
6e4dc10f 668/// CLASS DEFINITIONS /////////////////////////////////////////////////////////
669
3e7069e7 670
6e4dc10f 671/**
875e5f07 672 * Interface for anything appearing in the admin tree
6e4dc10f 673 *
875e5f07 674 * The interface that is implemented by anything that appears in the admin tree
6e4dc10f 675 * block. It forces inheriting classes to define a method for checking user permissions
676 * and methods for finding something in the admin tree.
677 *
db26acd4 678 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6e4dc10f 679 */
73fa96d5 680interface part_of_admin_tree {
6e4dc10f 681
9baf6825 682/**
683 * Finds a named part_of_admin_tree.
684 *
685 * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
686 * and not parentable_part_of_admin_tree, then this function should only check if
687 * $this->name matches $name. If it does, it should return a reference to $this,
688 * otherwise, it should return a reference to NULL.
689 *
690 * If a class inherits parentable_part_of_admin_tree, this method should be called
691 * recursively on all child objects (assuming, of course, the parent object's name
692 * doesn't match the search criterion).
693 *
694 * @param string $name The internal name of the part_of_admin_tree we're searching for.
695 * @return mixed An object reference or a NULL reference.
696 */
73fa96d5 697 public function locate($name);
4672d955 698
699 /**
700 * Removes named part_of_admin_tree.
701 *
702 * @param string $name The internal name of the part_of_admin_tree we want to remove.
a8a66c96 703 * @return bool success.
4672d955 704 */
73fa96d5 705 public function prune($name);
4672d955 706
220a90c5 707 /**
708 * Search using query
db26acd4 709 * @param string $query
220a90c5 710 * @return mixed array-object structure of found settings and pages
711 */
73fa96d5 712 public function search($query);
220a90c5 713
6e4dc10f 714 /**
715 * Verifies current user's access to this part_of_admin_tree.
716 *
717 * Used to check if the current user has access to this part of the admin tree or
718 * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
719 * then this method is usually just a call to has_capability() in the site context.
720 *
721 * If a class inherits parentable_part_of_admin_tree, this method should return the
722 * logical OR of the return of check_access() on all child objects.
723 *
724 * @return bool True if the user has access, false if she doesn't.
725 */
73fa96d5 726 public function check_access();
eef868d1 727
a8a66c96 728 /**
875e5f07 729 * Mostly useful for removing of some parts of the tree in admin tree block.
a8a66c96 730 *
731 * @return True is hidden from normal list view
732 */
73fa96d5 733 public function is_hidden();
427649bf
PS
734
735 /**
736 * Show we display Save button at the page bottom?
737 * @return bool
738 */
739 public function show_save();
6e4dc10f 740}
741
3e7069e7 742
6e4dc10f 743/**
875e5f07 744 * Interface implemented by any part_of_admin_tree that has children.
6e4dc10f 745 *
875e5f07 746 * The interface implemented by any part_of_admin_tree that can be a parent
6e4dc10f 747 * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
eef868d1 748 * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
6e4dc10f 749 * include an add method for adding other part_of_admin_tree objects as children.
750 *
db26acd4 751 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6e4dc10f 752 */
73fa96d5 753interface parentable_part_of_admin_tree extends part_of_admin_tree {
eef868d1 754
9baf6825 755/**
756 * Adds a part_of_admin_tree object to the admin tree.
757 *
758 * Used to add a part_of_admin_tree object to this object or a child of this
759 * object. $something should only be added if $destinationname matches
760 * $this->name. If it doesn't, add should be called on child objects that are
761 * also parentable_part_of_admin_tree's.
762 *
763 * @param string $destinationname The internal name of the new parent for $something.
764 * @param part_of_admin_tree $something The object to be added.
765 * @return bool True on success, false on failure.
766 */
73fa96d5 767 public function add($destinationname, $something);
eef868d1 768
6e4dc10f 769}
770
3e7069e7 771
6e4dc10f 772/**
773 * The object used to represent folders (a.k.a. categories) in the admin tree block.
eef868d1 774 *
6e4dc10f 775 * Each admin_category object contains a number of part_of_admin_tree objects.
776 *
db26acd4 777 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6e4dc10f 778 */
73fa96d5 779class admin_category implements parentable_part_of_admin_tree {
6e4dc10f 780
cde44edc 781 /** @var mixed An array of part_of_admin_tree objects that are this object's children */
73fa96d5 782 public $children;
0c079f19 783 /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
73fa96d5 784 public $name;
0c079f19 785 /** @var string The displayed name for this category. Usually obtained through get_string() */
73fa96d5 786 public $visiblename;
0c079f19 787 /** @var bool Should this category be hidden in admin tree block? */
73fa96d5 788 public $hidden;
0c079f19 789 /** @var mixed Either a string or an array or strings */
73fa96d5 790 public $path;
0c079f19 791 /** @var mixed Either a string or an array or strings */
73fa96d5 792 public $visiblepath;
6e4dc10f 793
cde44edc
PS
794 /** @var array fast lookup category cache, all categories of one tree point to one cache */
795 protected $category_cache;
796
6e4dc10f 797 /**
798 * Constructor for an empty admin category
799 *
800 * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
801 * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
db26acd4 802 * @param bool $hidden hide category in admin tree block, defaults to false
6e4dc10f 803 */
73fa96d5 804 public function __construct($name, $visiblename, $hidden=false) {
220a90c5 805 $this->children = array();
806 $this->name = $name;
6e4dc10f 807 $this->visiblename = $visiblename;
220a90c5 808 $this->hidden = $hidden;
6e4dc10f 809 }
eef868d1 810
6e4dc10f 811 /**
220a90c5 812 * Returns a reference to the part_of_admin_tree object with internal name $name.
6e4dc10f 813 *
220a90c5 814 * @param string $name The internal name of the object we want.
815 * @param bool $findpath initialize path and visiblepath arrays
0c079f19 816 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
db26acd4 817 * defaults to false
6e4dc10f 818 */
73fa96d5 819 public function locate($name, $findpath=false) {
cde44edc
PS
820 if (is_array($this->category_cache) and !isset($this->category_cache[$this->name])) {
821 // somebody much have purged the cache
822 $this->category_cache[$this->name] = $this;
823 }
824
6e4dc10f 825 if ($this->name == $name) {
220a90c5 826 if ($findpath) {
827 $this->visiblepath[] = $this->visiblename;
828 $this->path[] = $this->name;
829 }
830 return $this;
6e4dc10f 831 }
eef868d1 832
cde44edc
PS
833 // quick category lookup
834 if (!$findpath and is_array($this->category_cache) and isset($this->category_cache[$name])) {
835 return $this->category_cache[$name];
836 }
837
220a90c5 838 $return = NULL;
839 foreach($this->children as $childid=>$unused) {
73fa96d5 840 if ($return = $this->children[$childid]->locate($name, $findpath)) {
220a90c5 841 break;
6e4dc10f 842 }
843 }
eef868d1 844
220a90c5 845 if (!is_null($return) and $findpath) {
846 $return->visiblepath[] = $this->visiblename;
847 $return->path[] = $this->name;
848 }
eef868d1 849
220a90c5 850 return $return;
6e4dc10f 851 }
852
853 /**
220a90c5 854 * Search using query
db26acd4 855 *
856 * @param string query
220a90c5 857 * @return mixed array-object structure of found settings and pages
6e4dc10f 858 */
73fa96d5 859 public function search($query) {
220a90c5 860 $result = array();
861 foreach ($this->children as $child) {
3cea9c55 862 $subsearch = $child->search($query);
863 if (!is_array($subsearch)) {
864 debugging('Incorrect search result from '.$child->name);
865 continue;
866 }
867 $result = array_merge($result, $subsearch);
6e4dc10f 868 }
220a90c5 869 return $result;
6e4dc10f 870 }
871
4672d955 872 /**
873 * Removes part_of_admin_tree object with internal name $name.
874 *
875 * @param string $name The internal name of the object we want to remove.
a8a66c96 876 * @return bool success
4672d955 877 */
73fa96d5 878 public function prune($name) {
4672d955 879
880 if ($this->name == $name) {
881 return false; //can not remove itself
882 }
883
884 foreach($this->children as $precedence => $child) {
885 if ($child->name == $name) {
cde44edc
PS
886 // clear cache and delete self
887 if (is_array($this->category_cache)) {
888 while($this->category_cache) {
889 // delete the cache, but keep the original array address
890 array_pop($this->category_cache);
891 }
892 }
eef868d1 893 unset($this->children[$precedence]);
4672d955 894 return true;
cde44edc 895 } else if ($this->children[$precedence]->prune($name)) {
4672d955 896 return true;
897 }
898 }
899 return false;
900 }
901
6e4dc10f 902 /**
903 * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
904 *
220a90c5 905 * @param string $destinationame The internal name of the immediate parent that we want for $something.
875e5f07 906 * @param mixed $something A part_of_admin_tree or setting instance to be added.
220a90c5 907 * @return bool True if successfully added, false if $something can not be added.
6e4dc10f 908 */
73fa96d5 909 public function add($parentname, $something) {
910 $parent = $this->locate($parentname);
220a90c5 911 if (is_null($parent)) {
912 debugging('parent does not exist!');
6e4dc10f 913 return false;
914 }
915
73fa96d5 916 if ($something instanceof part_of_admin_tree) {
917 if (!($parent instanceof parentable_part_of_admin_tree)) {
220a90c5 918 debugging('error - parts of tree can be inserted only into parentable parts');
919 return false;
6e4dc10f 920 }
220a90c5 921 $parent->children[] = $something;
cde44edc
PS
922 if (is_array($this->category_cache) and ($something instanceof admin_category)) {
923 if (isset($this->category_cache[$something->name])) {
3e7069e7 924 debugging('Duplicate admin category name: '.$something->name);
cde44edc
PS
925 } else {
926 $this->category_cache[$something->name] = $something;
927 $something->category_cache =& $this->category_cache;
928 foreach ($something->children as $child) {
929 // just in case somebody already added subcategories
930 if ($child instanceof admin_category) {
931 if (isset($this->category_cache[$child->name])) {
3e7069e7 932 debugging('Duplicate admin category name: '.$child->name);
cde44edc
PS
933 } else {
934 $this->category_cache[$child->name] = $child;
935 $child->category_cache =& $this->category_cache;
936 }
937 }
938 }
939 }
940 }
6e4dc10f 941 return true;
eef868d1 942
220a90c5 943 } else {
944 debugging('error - can not add this element');
945 return false;
6e4dc10f 946 }
eef868d1 947
6e4dc10f 948 }
eef868d1 949
6e4dc10f 950 /**
951 * Checks if the user has access to anything in this category.
952 *
875e5f07 953 * @return bool True if the user has access to at least one child in this category, false otherwise.
6e4dc10f 954 */
73fa96d5 955 public function check_access() {
6e4dc10f 956 foreach ($this->children as $child) {
220a90c5 957 if ($child->check_access()) {
958 return true;
959 }
6e4dc10f 960 }
220a90c5 961 return false;
6e4dc10f 962 }
eef868d1 963
a8a66c96 964 /**
965 * Is this category hidden in admin tree block?
966 *
967 * @return bool True if hidden
968 */
73fa96d5 969 public function is_hidden() {
a8a66c96 970 return $this->hidden;
971 }
427649bf
PS
972
973 /**
974 * Show we display Save button at the page bottom?
975 * @return bool
976 */
977 public function show_save() {
978 foreach ($this->children as $child) {
979 if ($child->show_save()) {
980 return true;
981 }
982 }
983 return false;
984 }
6e4dc10f 985}
986
3e7069e7 987
db26acd4 988/**
0c079f19 989 * Root of admin settings tree, does not have any parent.
db26acd4 990 *
991 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
db26acd4 992 */
220a90c5 993class admin_root extends admin_category {
9baf6825 994/** @var array List of errors */
73fa96d5 995 public $errors;
0c079f19 996 /** @var string search query */
73fa96d5 997 public $search;
875e5f07 998 /** @var bool full tree flag - true means all settings required, false only pages required */
73fa96d5 999 public $fulltree;
0c079f19 1000 /** @var bool flag indicating loaded tree */
73fa96d5 1001 public $loaded;
875e5f07 1002 /** @var mixed site custom defaults overriding defaults in settings files*/
cd3acbf2 1003 public $custom_defaults;
1004
db26acd4 1005 /**
0c079f19 1006 * @param bool $fulltree true means all settings required,
db26acd4 1007 * false only pages required
1008 */
73fa96d5 1009 public function __construct($fulltree) {
cd3acbf2 1010 global $CFG;
1011
73fa96d5 1012 parent::__construct('root', get_string('administration'), false);
220a90c5 1013 $this->errors = array();
1014 $this->search = '';
73fa96d5 1015 $this->fulltree = $fulltree;
1016 $this->loaded = false;
cd3acbf2 1017
cde44edc
PS
1018 $this->category_cache = array();
1019
cd3acbf2 1020 // load custom defaults if found
1021 $this->custom_defaults = null;
1022 $defaultsfile = "$CFG->dirroot/local/defaults.php";
1023 if (is_readable($defaultsfile)) {
1024 $defaults = array();
1025 include($defaultsfile);
1026 if (is_array($defaults) and count($defaults)) {
1027 $this->custom_defaults = $defaults;
1028 }
1029 }
73fa96d5 1030 }
1031
db26acd4 1032 /**
1033 * Empties children array, and sets loaded to false
1034 *
1035 * @param bool $requirefulltree
1036 */
73fa96d5 1037 public function purge_children($requirefulltree) {
1038 $this->children = array();
1039 $this->fulltree = ($requirefulltree || $this->fulltree);
1040 $this->loaded = false;
cde44edc
PS
1041 //break circular dependencies - this helps PHP 5.2
1042 while($this->category_cache) {
1043 array_pop($this->category_cache);
1044 }
1045 $this->category_cache = array();
220a90c5 1046 }
1047}
1048
3e7069e7 1049
6e4dc10f 1050/**
1051 * Links external PHP pages into the admin tree.
1052 *
1053 * See detailed usage example at the top of this document (adminlib.php)
1054 *
db26acd4 1055 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6e4dc10f 1056 */
73fa96d5 1057class admin_externalpage implements part_of_admin_tree {
6e4dc10f 1058
3e7069e7 1059 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
73fa96d5 1060 public $name;
eef868d1 1061
0c079f19 1062 /** @var string The displayed name for this external page. Usually obtained through get_string(). */
73fa96d5 1063 public $visiblename;
eef868d1 1064
0c079f19 1065 /** @var string The external URL that we should link to when someone requests this external page. */
73fa96d5 1066 public $url;
eef868d1 1067
0c079f19 1068 /** @var string The role capability/permission a user must have to access this external page. */
73fa96d5 1069 public $req_capability;
eef868d1 1070
0c079f19 1071 /** @var object The context in which capability/permission should be checked, default is site context. */
73fa96d5 1072 public $context;
84c8ede0 1073
0c079f19 1074 /** @var bool hidden in admin tree block. */
73fa96d5 1075 public $hidden;
a8a66c96 1076
0c079f19 1077 /** @var mixed either string or array of string */
73fa96d5 1078 public $path;
3e7069e7
PS
1079
1080 /** @var array list of visible names of page parents */
73fa96d5 1081 public $visiblepath;
220a90c5 1082
6e4dc10f 1083 /**
1084 * Constructor for adding an external page into the admin tree.
1085 *
1086 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1087 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1088 * @param string $url The external URL that we should link to when someone requests this external page.
38d2d43b 1089 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
92f00846 1090 * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
44d8a940 1091 * @param stdClass $context The context the page relates to. Not sure what happens
92f00846 1092 * if you specify something other than system or front page. Defaults to system.
6e4dc10f 1093 */
73fa96d5 1094 public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
220a90c5 1095 $this->name = $name;
6e4dc10f 1096 $this->visiblename = $visiblename;
220a90c5 1097 $this->url = $url;
38d2d43b 1098 if (is_array($req_capability)) {
1099 $this->req_capability = $req_capability;
1100 } else {
1101 $this->req_capability = array($req_capability);
1102 }
92f00846 1103 $this->hidden = $hidden;
84c8ede0 1104 $this->context = $context;
6e4dc10f 1105 }
eef868d1 1106
6e4dc10f 1107 /**
1108 * Returns a reference to the part_of_admin_tree object with internal name $name.
1109 *
1110 * @param string $name The internal name of the object we want.
db26acd4 1111 * @param bool $findpath defaults to false
6e4dc10f 1112 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1113 */
73fa96d5 1114 public function locate($name, $findpath=false) {
220a90c5 1115 if ($this->name == $name) {
1116 if ($findpath) {
1117 $this->visiblepath = array($this->visiblename);
1118 $this->path = array($this->name);
1119 }
1120 return $this;
1121 } else {
1122 $return = NULL;
1123 return $return;
1124 }
6e4dc10f 1125 }
4672d955 1126
db26acd4 1127 /**
1128 * This function always returns false, required function by interface
1129 *
1130 * @param string $name
1131 * @return false
1132 */
73fa96d5 1133 public function prune($name) {
4672d955 1134 return false;
1135 }
1136
220a90c5 1137 /**
1138 * Search using query
db26acd4 1139 *
1140 * @param string $query
220a90c5 1141 * @return mixed array-object structure of found settings and pages
1142 */
73fa96d5 1143 public function search($query) {
220a90c5 1144 $found = false;
1145 if (strpos(strtolower($this->name), $query) !== false) {
1146 $found = true;
f8311def 1147 } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
9baf6825 1148 $found = true;
1149 }
220a90c5 1150 if ($found) {
365a5941 1151 $result = new stdClass();
220a90c5 1152 $result->page = $this;
1153 $result->settings = array();
1154 return array($this->name => $result);
1155 } else {
1156 return array();
1157 }
1158 }
1159
6e4dc10f 1160 /**
2ce38b70 1161 * Determines if the current user has access to this external page based on $this->req_capability.
db26acd4 1162 *
6e4dc10f 1163 * @return bool True if user has access, false otherwise.
1164 */
73fa96d5 1165 public function check_access() {
1caea91e 1166 global $CFG;
84c8ede0 1167 $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
38d2d43b 1168 foreach($this->req_capability as $cap) {
4f0c2d00 1169 if (has_capability($cap, $context)) {
38d2d43b 1170 return true;
1171 }
1172 }
1173 return false;
6e4dc10f 1174 }
1175
a8a66c96 1176 /**
1177 * Is this external page hidden in admin tree block?
1178 *
1179 * @return bool True if hidden
1180 */
73fa96d5 1181 public function is_hidden() {
a8a66c96 1182 return $this->hidden;
1183 }
1184
427649bf
PS
1185 /**
1186 * Show we display Save button at the page bottom?
1187 * @return bool
1188 */
1189 public function show_save() {
1190 return false;
1191 }
6e4dc10f 1192}
1193
3e7069e7 1194
6e4dc10f 1195/**
1196 * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1197 *
db26acd4 1198 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6e4dc10f 1199 */
73fa96d5 1200class admin_settingpage implements part_of_admin_tree {
6e4dc10f 1201
3e7069e7 1202 /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
73fa96d5 1203 public $name;
eef868d1 1204
0c079f19 1205 /** @var string The displayed name for this external page. Usually obtained through get_string(). */
73fa96d5 1206 public $visiblename;
0c079f19 1207
1208 /** @var mixed An array of admin_setting objects that are part of this setting page. */
73fa96d5 1209 public $settings;
eef868d1 1210
0c079f19 1211 /** @var string The role capability/permission a user must have to access this external page. */
73fa96d5 1212 public $req_capability;
eef868d1 1213
0c079f19 1214 /** @var object The context in which capability/permission should be checked, default is site context. */
73fa96d5 1215 public $context;
84c8ede0 1216
0c079f19 1217 /** @var bool hidden in admin tree block. */
73fa96d5 1218 public $hidden;
a8a66c96 1219
0c079f19 1220 /** @var mixed string of paths or array of strings of paths */
73fa96d5 1221 public $path;
3e7069e7
PS
1222
1223 /** @var array list of visible names of page parents */
73fa96d5 1224 public $visiblepath;
220a90c5 1225
db26acd4 1226 /**
1227 * see admin_settingpage for details of this function
0c079f19 1228 *
db26acd4 1229 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1230 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1231 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
1232 * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
44d8a940 1233 * @param stdClass $context The context the page relates to. Not sure what happens
db26acd4 1234 * if you specify something other than system or front page. Defaults to system.
1235 */
73fa96d5 1236 public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
365a5941 1237 $this->settings = new stdClass();
220a90c5 1238 $this->name = $name;
1239 $this->visiblename = $visiblename;
1240 if (is_array($req_capability)) {
1241 $this->req_capability = $req_capability;
6e4dc10f 1242 } else {
220a90c5 1243 $this->req_capability = array($req_capability);
6e4dc10f 1244 }
220a90c5 1245 $this->hidden = $hidden;
1246 $this->context = $context;
6e4dc10f 1247 }
eef868d1 1248
0c079f19 1249 /**
db26acd4 1250 * see admin_category
1251 *
1252 * @param string $name
1253 * @param bool $findpath
1254 * @return mixed Object (this) if name == this->name, else returns null
1255 */
73fa96d5 1256 public function locate($name, $findpath=false) {
220a90c5 1257 if ($this->name == $name) {
1258 if ($findpath) {
1259 $this->visiblepath = array($this->visiblename);
1260 $this->path = array($this->name);
1261 }
1262 return $this;
1263 } else {
1264 $return = NULL;
1265 return $return;
1266 }
6e4dc10f 1267 }
4672d955 1268
0c079f19 1269 /**
1270 * Search string in settings page.
1271 *
db26acd4 1272 * @param string $query
1273 * @return array
1274 */
73fa96d5 1275 public function search($query) {
220a90c5 1276 $found = array();
4672d955 1277
220a90c5 1278 foreach ($this->settings as $setting) {
1279 if ($setting->is_related($query)) {
1280 $found[] = $setting;
1281 }
1282 }
1283
1284 if ($found) {
365a5941 1285 $result = new stdClass();
220a90c5 1286 $result->page = $this;
1287 $result->settings = $found;
1288 return array($this->name => $result);
1289 }
1290
220a90c5 1291 $found = false;
1292 if (strpos(strtolower($this->name), $query) !== false) {
1293 $found = true;
f8311def 1294 } else if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
9baf6825 1295 $found = true;
1296 }
220a90c5 1297 if ($found) {
365a5941 1298 $result = new stdClass();
220a90c5 1299 $result->page = $this;
1300 $result->settings = array();
1301 return array($this->name => $result);
38d2d43b 1302 } else {
220a90c5 1303 return array();
38d2d43b 1304 }
6e4dc10f 1305 }
eef868d1 1306
db26acd4 1307 /**
1308 * This function always returns false, required by interface
1309 *
1310 * @param string $name
1311 * @return bool Always false
1312 */
73fa96d5 1313 public function prune($name) {
6e4dc10f 1314 return false;
1315 }
eef868d1 1316
220a90c5 1317 /**
db26acd4 1318 * adds an admin_setting to this admin_settingpage
1319 *
220a90c5 1320 * 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
1321 * n.b. each admin_setting in an admin_settingpage must have a unique internal name
db26acd4 1322 *
220a90c5 1323 * @param object $setting is the admin_setting object you want to add
db26acd4 1324 * @return bool true if successful, false if not
220a90c5 1325 */
73fa96d5 1326 public function add($setting) {
1327 if (!($setting instanceof admin_setting)) {
220a90c5 1328 debugging('error - not a setting instance');
1329 return false;
1330 }
1331
1332 $this->settings->{$setting->name} = $setting;
1333 return true;
1334 }
1335
db26acd4 1336 /**
1337 * see admin_externalpage
1338 *
1339 * @return bool Returns true for yes false for no
1340 */
73fa96d5 1341 public function check_access() {
1caea91e 1342 global $CFG;
84c8ede0 1343 $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
38d2d43b 1344 foreach($this->req_capability as $cap) {
4f0c2d00 1345 if (has_capability($cap, $context)) {
38d2d43b 1346 return true;
1347 }
1348 }
1349 return false;
6e4dc10f 1350 }
eef868d1 1351
220a90c5 1352 /**
1353 * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
db26acd4 1354 * @return string Returns an XHTML string
220a90c5 1355 */
73fa96d5 1356 public function output_html() {
1357 $adminroot = admin_get_root();
220a90c5 1358 $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
6e4dc10f 1359 foreach($this->settings as $setting) {
220a90c5 1360 $fullname = $setting->get_full_name();
1361 if (array_key_exists($fullname, $adminroot->errors)) {
1362 $data = $adminroot->errors[$fullname]->data;
6e4dc10f 1363 } else {
220a90c5 1364 $data = $setting->get_setting();
79698344 1365 // do not use defaults if settings not available - upgrade settings handles the defaults!
6e4dc10f 1366 }
220a90c5 1367 $return .= $setting->output_html($data);
6e4dc10f 1368 }
220a90c5 1369 $return .= '</fieldset>';
6e4dc10f 1370 return $return;
1371 }
1372
a8a66c96 1373 /**
875e5f07 1374 * Is this settings page hidden in admin tree block?
a8a66c96 1375 *
1376 * @return bool True if hidden
1377 */
73fa96d5 1378 public function is_hidden() {
a8a66c96 1379 return $this->hidden;
1380 }
1381
427649bf
PS
1382 /**
1383 * Show we display Save button at the page bottom?
1384 * @return bool
1385 */
1386 public function show_save() {
1387 foreach($this->settings as $setting) {
1388 if (empty($setting->nosave)) {
1389 return true;
1390 }
1391 }
1392 return false;
1393 }
6e4dc10f 1394}
1395
1396
220a90c5 1397/**
1398 * Admin settings class. Only exists on setting pages.
1399 * Read & write happens at this level; no authentication.
db26acd4 1400 *
1401 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 1402 */
301bf0b2 1403abstract class admin_setting {
3e7069e7 1404 /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
73fa96d5 1405 public $name;
0c079f19 1406 /** @var string localised name */
73fa96d5 1407 public $visiblename;
3fa37159 1408 /** @var string localised long description in Markdown format */
73fa96d5 1409 public $description;
0c079f19 1410 /** @var mixed Can be string or array of string */
73fa96d5 1411 public $defaultsetting;
0c079f19 1412 /** @var string */
73fa96d5 1413 public $updatedcallback;
0c079f19 1414 /** @var mixed can be String or Null. Null means main config table */
73fa96d5 1415 public $plugin; // null means main config table
427649bf
PS
1416 /** @var bool true indicates this setting does not actually save anything, just information */
1417 public $nosave = false;
adaeccb6 1418 /** @var bool if set, indicates that a change to this setting requires rebuild course cache */
1419 public $affectsmodinfo = false;
6e4dc10f 1420
220a90c5 1421 /**
1422 * Constructor
0c079f19 1423 * @param string $name unique ascii name, either 'mysetting' for settings that in config,
db26acd4 1424 * or 'myplugin/mysetting' for ones in config_plugins.
1a41e806 1425 * @param string $visiblename localised name
1426 * @param string $description localised long description
220a90c5 1427 * @param mixed $defaultsetting string or array depending on implementation
1428 */
73fa96d5 1429 public function __construct($name, $visiblename, $description, $defaultsetting) {
7fb0303d 1430 $this->parse_setting_name($name);
220a90c5 1431 $this->visiblename = $visiblename;
8dbe233a 1432 $this->description = $description;
6e4dc10f 1433 $this->defaultsetting = $defaultsetting;
1434 }
eef868d1 1435
7fb0303d 1436 /**
db26acd4 1437 * Set up $this->name and potentially $this->plugin
1438 *
7fb0303d 1439 * Set up $this->name and possibly $this->plugin based on whether $name looks
1440 * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1441 * on the names, that is, output a developer debug warning if the name
1442 * contains anything other than [a-zA-Z0-9_]+.
1443 *
1444 * @param string $name the setting name passed in to the constructor.
1445 */
1446 private function parse_setting_name($name) {
1447 $bits = explode('/', $name);
1448 if (count($bits) > 2) {
1449 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1450 }
1451 $this->name = array_pop($bits);
1452 if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1453 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1454 }
1455 if (!empty($bits)) {
1456 $this->plugin = array_pop($bits);
cd3acbf2 1457 if ($this->plugin === 'moodle') {
1458 $this->plugin = null;
1459 } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
9baf6825 1460 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1461 }
7fb0303d 1462 }
1463 }
1464
db26acd4 1465 /**
1466 * Returns the fullname prefixed by the plugin
1467 * @return string
1468 */
73fa96d5 1469 public function get_full_name() {
220a90c5 1470 return 's_'.$this->plugin.'_'.$this->name;
1471 }
1472
db26acd4 1473 /**
1474 * Returns the ID string based on plugin and name
1475 * @return string
1476 */
73fa96d5 1477 public function get_id() {
220a90c5 1478 return 'id_s_'.$this->plugin.'_'.$this->name;
1479 }
1480
adaeccb6 1481 /**
1482 * @param bool $affectsmodinfo If true, changes to this setting will
1483 * cause the course cache to be rebuilt
1484 */
1485 public function set_affects_modinfo($affectsmodinfo) {
1486 $this->affectsmodinfo = $affectsmodinfo;
1487 }
1488
db26acd4 1489 /**
1490 * Returns the config if possible
1491 *
3e7069e7 1492 * @return mixed returns config if successful else null
db26acd4 1493 */
73fa96d5 1494 public function config_read($name) {
220a90c5 1495 global $CFG;
eb6a973c 1496 if (!empty($this->plugin)) {
220a90c5 1497 $value = get_config($this->plugin, $name);
1498 return $value === false ? NULL : $value;
1499
1500 } else {
1501 if (isset($CFG->$name)) {
1502 return $CFG->$name;
1503 } else {
1504 return NULL;
1505 }
1506 }
1507 }
1508
301bf0b2 1509 /**
db26acd4 1510 * Used to set a config pair and log change
301bf0b2 1511 *
db26acd4 1512 * @param string $name
1513 * @param mixed $value Gets converted to string if not null
875e5f07 1514 * @return bool Write setting to config table
301bf0b2 1515 */
73fa96d5 1516 public function config_write($name, $value) {
301bf0b2 1517 global $DB, $USER, $CFG;
1518
427649bf
PS
1519 if ($this->nosave) {
1520 return true;
1521 }
1522
301bf0b2 1523 // make sure it is a real change
1524 $oldvalue = get_config($this->plugin, $name);
1525 $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
1526 $value = is_null($value) ? null : (string)$value;
1527
1528 if ($oldvalue === $value) {
1529 return true;
1530 }
1531
1532 // store change
1533 set_config($name, $value, $this->plugin);
1534
adaeccb6 1535 // Some admin settings affect course modinfo
1536 if ($this->affectsmodinfo) {
1537 // Clear course cache for all courses
1538 rebuild_course_cache(0, true);
1539 }
1540
301bf0b2 1541 // log change
365a5941 1542 $log = new stdClass();
31a99877 1543 $log->userid = during_initial_install() ? 0 :$USER->id; // 0 as user id during install
301bf0b2 1544 $log->timemodified = time();
1545 $log->plugin = $this->plugin;
1546 $log->name = $name;
1547 $log->value = $value;
1548 $log->oldvalue = $oldvalue;
1549 $DB->insert_record('config_log', $log);
1550
1551 return true; // BC only
220a90c5 1552 }
1553
1554 /**
1555 * Returns current value of this setting
1556 * @return mixed array or string depending on instance, NULL means not set yet
1557 */
301bf0b2 1558 public abstract function get_setting();
eef868d1 1559
220a90c5 1560 /**
1561 * Returns default setting if exists
1562 * @return mixed array or string depending on instance; NULL means no default, user must supply
1563 */
73fa96d5 1564 public function get_defaultsetting() {
cd3acbf2 1565 $adminroot = admin_get_root(false, false);
1566 if (!empty($adminroot->custom_defaults)) {
1567 $plugin = is_null($this->plugin) ? 'moodle' : $this->plugin;
1568 if (isset($adminroot->custom_defaults[$plugin])) {
875e5f07 1569 if (array_key_exists($this->name, $adminroot->custom_defaults[$plugin])) { // null is valid value here ;-)
cd3acbf2 1570 return $adminroot->custom_defaults[$plugin][$this->name];
1571 }
1572 }
1573 }
8e5da17a 1574 return $this->defaultsetting;
1575 }
1576
220a90c5 1577 /**
1578 * Store new setting
db26acd4 1579 *
1580 * @param mixed $data string or array, must not be NULL
1581 * @return string empty string if ok, string error message otherwise
220a90c5 1582 */
301bf0b2 1583 public abstract function write_setting($data);
eef868d1 1584
220a90c5 1585 /**
1586 * Return part of form with setting
db26acd4 1587 * This function should always be overwritten
1588 *
1589 * @param mixed $data array or string depending on setting
1590 * @param string $query
220a90c5 1591 * @return string
1592 */
73fa96d5 1593 public function output_html($data, $query='') {
9baf6825 1594 // should be overridden
220a90c5 1595 return;
1596 }
1597
1598 /**
db26acd4 1599 * Function called if setting updated - cleanup, cache reset, etc.
1600 * @param string $functionname Sets the function name
3e7069e7 1601 * @return void
220a90c5 1602 */
73fa96d5 1603 public function set_updatedcallback($functionname) {
220a90c5 1604 $this->updatedcallback = $functionname;
1605 }
1606
1607 /**
1608 * Is setting related to query text - used when searching
1609 * @param string $query
1610 * @return bool
1611 */
73fa96d5 1612 public function is_related($query) {
220a90c5 1613 if (strpos(strtolower($this->name), $query) !== false) {
1614 return true;
1615 }
f8311def 1616 if (strpos(textlib::strtolower($this->visiblename), $query) !== false) {
220a90c5 1617 return true;
1618 }
f8311def 1619 if (strpos(textlib::strtolower($this->description), $query) !== false) {
220a90c5 1620 return true;
1621 }
587c7040 1622 $current = $this->get_setting();
1623 if (!is_null($current)) {
1624 if (is_string($current)) {
f8311def 1625 if (strpos(textlib::strtolower($current), $query) !== false) {
587c7040 1626 return true;
1627 }
1628 }
1629 }
1630 $default = $this->get_defaultsetting();
1631 if (!is_null($default)) {
1632 if (is_string($default)) {
f8311def 1633 if (strpos(textlib::strtolower($default), $query) !== false) {
587c7040 1634 return true;
1635 }
1636 }
1637 }
220a90c5 1638 return false;
6e4dc10f 1639 }
220a90c5 1640}
eef868d1 1641
3e7069e7 1642
220a90c5 1643/**
1644 * No setting - just heading and text.
db26acd4 1645 *
1646 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 1647 */
1648class admin_setting_heading extends admin_setting {
3e7069e7
PS
1649
1650 /**
1651 * not a setting, just text
1652 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1653 * @param string $heading heading
1654 * @param string $information text in box
1655 */
73fa96d5 1656 public function __construct($name, $heading, $information) {
427649bf 1657 $this->nosave = true;
73fa96d5 1658 parent::__construct($name, $heading, $information, '');
220a90c5 1659 }
1660
db26acd4 1661 /**
1662 * Always returns true
1663 * @return bool Always returns true
1664 */
73fa96d5 1665 public function get_setting() {
220a90c5 1666 return true;
1667 }
1668
db26acd4 1669 /**
1670 * Always returns true
1671 * @return bool Always returns true
1672 */
73fa96d5 1673 public function get_defaultsetting() {
220a90c5 1674 return true;
1675 }
1676
db26acd4 1677 /**
1678 * Never write settings
1679 * @return string Always returns an empty string
1680 */
73fa96d5 1681 public function write_setting($data) {
9baf6825 1682 // do not write any setting
220a90c5 1683 return '';
1684 }
0c079f19 1685
db26acd4 1686 /**
1687 * Returns an HTML string
1688 * @return string Returns an HTML string
1689 */
73fa96d5 1690 public function output_html($data, $query='') {
3c159385 1691 global $OUTPUT;
220a90c5 1692 $return = '';
1693 if ($this->visiblename != '') {
206dd861 1694 $return .= $OUTPUT->heading($this->visiblename, 3, 'main');
220a90c5 1695 }
1696 if ($this->description != '') {
8dbe233a 1697 $return .= $OUTPUT->box(highlight($query, markdown_to_html($this->description)), 'generalbox formsettingheading');
220a90c5 1698 }
1699 return $return;
1700 }
1701}
6e4dc10f 1702
3e7069e7 1703
220a90c5 1704/**
1705 * The most flexibly setting, user is typing text
db26acd4 1706 *
1707 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 1708 */
6e4dc10f 1709class admin_setting_configtext extends admin_setting {
1710
3e7069e7 1711 /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */
73fa96d5 1712 public $paramtype;
0c079f19 1713 /** @var int default field size */
73fa96d5 1714 public $size;
6e4dc10f 1715
220a90c5 1716 /**
875e5f07 1717 * Config text constructor
db26acd4 1718 *
1a41e806 1719 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 1720 * @param string $visiblename localised
1721 * @param string $description long localised info
1722 * @param string $defaultsetting
1723 * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
f7633b0f 1724 * @param int $size default field size
220a90c5 1725 */
73fa96d5 1726 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
6e4dc10f 1727 $this->paramtype = $paramtype;
f7633b0f 1728 if (!is_null($size)) {
1729 $this->size = $size;
1730 } else {
40ea93a4 1731 $this->size = ($paramtype === PARAM_INT) ? 5 : 30;
f7633b0f 1732 }
73fa96d5 1733 parent::__construct($name, $visiblename, $description, $defaultsetting);
6e4dc10f 1734 }
1735
db26acd4 1736 /**
1737 * Return the setting
1738 *
875e5f07 1739 * @return mixed returns config if successful else null
db26acd4 1740 */
73fa96d5 1741 public function get_setting() {
220a90c5 1742 return $this->config_read($this->name);
6e4dc10f 1743 }
eef868d1 1744
73fa96d5 1745 public function write_setting($data) {
8cad6cca 1746 if ($this->paramtype === PARAM_INT and $data === '') {
9baf6825 1747 // do not complain if '' used instead of 0
8cad6cca 1748 $data = 0;
1749 }
220a90c5 1750 // $data is a string
c5d2d0dd 1751 $validated = $this->validate($data);
e33fbf87 1752 if ($validated !== true) {
1753 return $validated;
c235598d 1754 }
220a90c5 1755 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 1756 }
1757
e33fbf87 1758 /**
1759 * Validate data before storage
1760 * @param string data
1761 * @return mixed true if ok string if error found
1762 */
73fa96d5 1763 public function validate($data) {
58aaa8e9 1764 // allow paramtype to be a custom regex if it is the form of /pattern/
1765 if (preg_match('#^/.*/$#', $this->paramtype)) {
e33fbf87 1766 if (preg_match($this->paramtype, $data)) {
1767 return true;
1768 } else {
1769 return get_string('validateerror', 'admin');
1770 }
1771
9e24fbd1 1772 } else if ($this->paramtype === PARAM_RAW) {
4ea56b3f 1773 return true;
9baf6825 1774
4ea56b3f 1775 } else {
1776 $cleaned = clean_param($data, $this->paramtype);
40ea93a4 1777 if ("$data" === "$cleaned") { // implicit conversion to string is needed to do exact comparison
4ea56b3f 1778 return true;
e33fbf87 1779 } else {
4ea56b3f 1780 return get_string('validateerror', 'admin');
e33fbf87 1781 }
4ea56b3f 1782 }
c235598d 1783 }
1784
db26acd4 1785 /**
1786 * Return an XHTML string for the setting
1787 * @return string Returns an XHTML string
1788 */
73fa96d5 1789 public function output_html($data, $query='') {
220a90c5 1790 $default = $this->get_defaultsetting();
1791
220a90c5 1792 return format_admin_setting($this, $this->visiblename,
9baf6825 1793 '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
1794 $this->description, true, '', $default, $query);
6e4dc10f 1795 }
6e4dc10f 1796}
1797
3e7069e7 1798
220a90c5 1799/**
1800 * General text area without html editor.
db26acd4 1801 *
1802 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 1803 */
1804class admin_setting_configtextarea extends admin_setting_configtext {
4fe2250a 1805 private $rows;
1806 private $cols;
eba8cd63 1807
db26acd4 1808 /**
1809 * @param string $name
1810 * @param string $visiblename
1811 * @param string $description
1812 * @param mixed $defaultsetting string or array
1813 * @param mixed $paramtype
1814 * @param string $cols The number of columns to make the editor
1815 * @param string $rows The number of rows to make the editor
1816 */
73fa96d5 1817 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
220a90c5 1818 $this->rows = $rows;
1819 $this->cols = $cols;
73fa96d5 1820 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
eba8cd63 1821 }
3e7069e7 1822
db26acd4 1823 /**
1824 * Returns an XHTML string for the editor
1825 *
1826 * @param string $data
1827 * @param string $query
1828 * @return string XHTML string for the editor
1829 */
73fa96d5 1830 public function output_html($data, $query='') {
220a90c5 1831 $default = $this->get_defaultsetting();
1832
587c7040 1833 $defaultinfo = $default;
1834 if (!is_null($default) and $default !== '') {
1835 $defaultinfo = "\n".$default;
c5d2d0dd 1836 }
220a90c5 1837
1838 return format_admin_setting($this, $this->visiblename,
9baf6825 1839 '<div class="form-textarea" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1840 $this->description, true, '', $defaultinfo, $query);
4fe2250a 1841 }
1842}
1843
3e7069e7 1844
4fe2250a 1845/**
1846 * General text area with html editor.
1847 */
1848class admin_setting_confightmleditor extends admin_setting_configtext {
1849 private $rows;
1850 private $cols;
0c079f19 1851
4fe2250a 1852 /**
1853 * @param string $name
1854 * @param string $visiblename
1855 * @param string $description
1856 * @param mixed $defaultsetting string or array
1857 * @param mixed $paramtype
1858 */
1859 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
1860 $this->rows = $rows;
1861 $this->cols = $cols;
1862 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
ff5fe311 1863 editors_head_setup();
4fe2250a 1864 }
3e7069e7 1865
4fe2250a 1866 /**
1867 * Returns an XHTML string for the editor
1868 *
1869 * @param string $data
1870 * @param string $query
1871 * @return string XHTML string for the editor
1872 */
1873 public function output_html($data, $query='') {
1874 $default = $this->get_defaultsetting();
1875
1876 $defaultinfo = $default;
1877 if (!is_null($default) and $default !== '') {
1878 $defaultinfo = "\n".$default;
1879 }
1880
20e5da7d 1881 $editor = editors_get_preferred_editor(FORMAT_HTML);
69429650 1882 $editor->use_editor($this->get_id(), array('noclean'=>true));
4fe2250a 1883
1884 return format_admin_setting($this, $this->visiblename,
9baf6825 1885 '<div class="form-textarea"><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
1886 $this->description, true, '', $defaultinfo, $query);
220a90c5 1887 }
1888}
1889
3e7069e7 1890
220a90c5 1891/**
1892 * Password field, allows unmasking of password
db26acd4 1893 *
1894 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 1895 */
1896class admin_setting_configpasswordunmask extends admin_setting_configtext {
3e7069e7
PS
1897 /**
1898 * Constructor
1899 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1900 * @param string $visiblename localised
1901 * @param string $description long localised info
1902 * @param string $defaultsetting default password
1903 */
73fa96d5 1904 public function __construct($name, $visiblename, $description, $defaultsetting) {
1905 parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
220a90c5 1906 }
0c079f19 1907
db26acd4 1908 /**
1909 * Returns XHTML for the field
1910 * Writes Javascript into the HTML below right before the last div
1911 *
1912 * @todo Make javascript available through newer methods if possible
1913 * @param string $data Value for the field
1914 * @param string $query Passed as final argument for format_admin_setting
1915 * @return string XHTML field
1916 */
73fa96d5 1917 public function output_html($data, $query='') {
220a90c5 1918 $id = $this->get_id();
1919 $unmask = get_string('unmaskpassword', 'form');
1920 $unmaskjs = '<script type="text/javascript">
eba8cd63 1921//<![CDATA[
633239f6 1922var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
1923
1924document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
1925
1926var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
1927
1928var unmaskchb = document.createElement("input");
1929unmaskchb.setAttribute("type", "checkbox");
1930unmaskchb.setAttribute("id", "'.$id.'unmask");
1931unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
1932unmaskdiv.appendChild(unmaskchb);
1933
1934var unmasklbl = document.createElement("label");
1935unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
1936if (is_ie) {
1937 unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
1938} else {
1939 unmasklbl.setAttribute("for", "'.$id.'unmask");
1940}
1941unmaskdiv.appendChild(unmasklbl);
1942
1943if (is_ie) {
1944 // ugly hack to work around the famous onchange IE bug
1945 unmaskchb.onclick = function() {this.blur();};
1946 unmaskdiv.onclick = function() {this.blur();};
1947}
eba8cd63 1948//]]>
1949</script>';
220a90c5 1950 return format_admin_setting($this, $this->visiblename,
9baf6825 1951 '<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>',
1952 $this->description, true, '', NULL, $query);
220a90c5 1953 }
1954}
1955
3e7069e7 1956
220a90c5 1957/**
e9c0fa35 1958 * Path to directory
db26acd4 1959 *
1960 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 1961 */
e9c0fa35 1962class admin_setting_configfile extends admin_setting_configtext {
3e7069e7
PS
1963 /**
1964 * Constructor
1965 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1966 * @param string $visiblename localised
1967 * @param string $description long localised info
1968 * @param string $defaultdirectory default directory location
1969 */
73fa96d5 1970 public function __construct($name, $visiblename, $description, $defaultdirectory) {
1971 parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
220a90c5 1972 }
1973
db26acd4 1974 /**
1975 * Returns XHTML for the field
0c079f19 1976 *
db26acd4 1977 * Returns XHTML for the field and also checks whether the file
1978 * specified in $data exists using file_exists()
1979 *
1980 * @param string $data File name and path to use in value attr
1981 * @param string $query
1982 * @return string XHTML field
1983 */
73fa96d5 1984 public function output_html($data, $query='') {
220a90c5 1985 $default = $this->get_defaultsetting();
1986
220a90c5 1987 if ($data) {
e9c0fa35 1988 if (file_exists($data)) {
220a90c5 1989 $executable = '<span class="pathok">&#x2714;</span>';
1990 } else {
1991 $executable = '<span class="patherror">&#x2718;</span>';
1992 }
1993 } else {
1994 $executable = '';
1995 }
1996
1997 return format_admin_setting($this, $this->visiblename,
9baf6825 1998 '<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>',
1999 $this->description, true, '', $default, $query);
eba8cd63 2000 }
220a90c5 2001}
2002
3e7069e7 2003
220a90c5 2004/**
e9c0fa35 2005 * Path to executable file
db26acd4 2006 *
2007 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2008 */
e9c0fa35 2009class admin_setting_configexecutable extends admin_setting_configfile {
2010
3e7069e7
PS
2011 /**
2012 * Returns an XHTML field
2013 *
2014 * @param string $data This is the value for the field
2015 * @param string $query
2016 * @return string XHTML field
2017 */
73fa96d5 2018 public function output_html($data, $query='') {
e9c0fa35 2019 $default = $this->get_defaultsetting();
2020
2021 if ($data) {
2022 if (file_exists($data) and is_executable($data)) {
2023 $executable = '<span class="pathok">&#x2714;</span>';
2024 } else {
2025 $executable = '<span class="patherror">&#x2718;</span>';
2026 }
2027 } else {
2028 $executable = '';
2029 }
2030
2031 return format_admin_setting($this, $this->visiblename,
9baf6825 2032 '<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>',
2033 $this->description, true, '', $default, $query);
220a90c5 2034 }
e9c0fa35 2035}
220a90c5 2036
3e7069e7 2037
e9c0fa35 2038/**
2039 * Path to directory
db26acd4 2040 *
2041 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
e9c0fa35 2042 */
2043class admin_setting_configdirectory extends admin_setting_configfile {
db26acd4 2044
3e7069e7
PS
2045 /**
2046 * Returns an XHTML field
2047 *
2048 * @param string $data This is the value for the field
2049 * @param string $query
2050 * @return string XHTML
2051 */
73fa96d5 2052 public function output_html($data, $query='') {
220a90c5 2053 $default = $this->get_defaultsetting();
2054
220a90c5 2055 if ($data) {
2056 if (file_exists($data) and is_dir($data)) {
2057 $executable = '<span class="pathok">&#x2714;</span>';
2058 } else {
2059 $executable = '<span class="patherror">&#x2718;</span>';
2060 }
2061 } else {
2062 $executable = '';
2063 }
9ba38673 2064
220a90c5 2065 return format_admin_setting($this, $this->visiblename,
9baf6825 2066 '<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>',
2067 $this->description, true, '', $default, $query);
220a90c5 2068 }
eba8cd63 2069}
2070
3e7069e7 2071
220a90c5 2072/**
2073 * Checkbox
db26acd4 2074 *
2075 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2076 */
6e4dc10f 2077class admin_setting_configcheckbox extends admin_setting {
3e7069e7 2078 /** @var string Value used when checked */
73fa96d5 2079 public $yes;
0c079f19 2080 /** @var string Value used when not checked */
73fa96d5 2081 public $no;
6e4dc10f 2082
220a90c5 2083 /**
2084 * Constructor
1a41e806 2085 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2086 * @param string $visiblename localised
2087 * @param string $description long localised info
2088 * @param string $defaultsetting
2089 * @param string $yes value used when checked
2090 * @param string $no value used when not checked
2091 */
73fa96d5 2092 public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2093 parent::__construct($name, $visiblename, $description, $defaultsetting);
220a90c5 2094 $this->yes = (string)$yes;
2095 $this->no = (string)$no;
6e4dc10f 2096 }
2097
db26acd4 2098 /**
2099 * Retrieves the current setting using the objects name
2100 *
2101 * @return string
2102 */
73fa96d5 2103 public function get_setting() {
220a90c5 2104 return $this->config_read($this->name);
6e4dc10f 2105 }
eef868d1 2106
db26acd4 2107 /**
2108 * Sets the value for the setting
2109 *
2110 * Sets the value for the setting to either the yes or no values
2111 * of the object by comparing $data to yes
2112 *
2113 * @param mixed $data Gets converted to str for comparison against yes value
2114 * @return string empty string or error
2115 */
73fa96d5 2116 public function write_setting($data) {
220a90c5 2117 if ((string)$data === $this->yes) { // convert to strings before comparison
2118 $data = $this->yes;
6e4dc10f 2119 } else {
220a90c5 2120 $data = $this->no;
6e4dc10f 2121 }
220a90c5 2122 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2123 }
2124
db26acd4 2125 /**
2126 * Returns an XHTML checkbox field
2127 *
2128 * @param string $data If $data matches yes then checkbox is checked
2129 * @param string $query
2130 * @return string XHTML field
2131 */
73fa96d5 2132 public function output_html($data, $query='') {
220a90c5 2133 $default = $this->get_defaultsetting();
2134
2135 if (!is_null($default)) {
2136 if ((string)$default === $this->yes) {
587c7040 2137 $defaultinfo = get_string('checkboxyes', 'admin');
220a90c5 2138 } else {
587c7040 2139 $defaultinfo = get_string('checkboxno', 'admin');
220a90c5 2140 }
c8218a42 2141 } else {
587c7040 2142 $defaultinfo = NULL;
c8218a42 2143 }
220a90c5 2144
2145 if ((string)$data === $this->yes) { // convert to strings before comparison
2146 $checked = 'checked="checked"';
2147 } else {
2148 $checked = '';
2149 }
2150
2151 return format_admin_setting($this, $this->visiblename,
9baf6825 2152 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2153 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2154 $this->description, true, '', $defaultinfo, $query);
6e4dc10f 2155 }
6e4dc10f 2156}
2157
3e7069e7 2158
220a90c5 2159/**
2160 * Multiple checkboxes, each represents different value, stored in csv format
db26acd4 2161 *
2162 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2163 */
2164class admin_setting_configmulticheckbox extends admin_setting {
3e7069e7 2165 /** @var array Array of choices value=>label */
73fa96d5 2166 public $choices;
eef868d1 2167
220a90c5 2168 /**
db26acd4 2169 * Constructor: uses parent::__construct
2170 *
1a41e806 2171 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2172 * @param string $visiblename localised
2173 * @param string $description long localised info
2174 * @param array $defaultsetting array of selected
2175 * @param array $choices array of $value=>$label for each checkbox
2176 */
73fa96d5 2177 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
6e4dc10f 2178 $this->choices = $choices;
73fa96d5 2179 parent::__construct($name, $visiblename, $description, $defaultsetting);
6e4dc10f 2180 }
2181
0a784551 2182 /**
73fa96d5 2183 * This public function may be used in ancestors for lazy loading of choices
db26acd4 2184 *
2185 * @todo Check if this function is still required content commented out only returns true
2186 * @return bool true if loaded, false if error
0a784551 2187 */
73fa96d5 2188 public function load_choices() {
0a784551 2189 /*
220a90c5 2190 if (is_array($this->choices)) {
2191 return true;
0a784551 2192 }
2193 .... load choices here
2194 */
220a90c5 2195 return true;
2196 }
2197
2198 /**
2199 * Is setting related to query text - used when searching
db26acd4 2200 *
220a90c5 2201 * @param string $query
db26acd4 2202 * @return bool true on related, false on not or failure
220a90c5 2203 */
73fa96d5 2204 public function is_related($query) {
220a90c5 2205 if (!$this->load_choices() or empty($this->choices)) {
2206 return false;
2207 }
2208 if (parent::is_related($query)) {
2209 return true;
2210 }
2211
220a90c5 2212 foreach ($this->choices as $desc) {
f8311def 2213 if (strpos(textlib::strtolower($desc), $query) !== false) {
220a90c5 2214 return true;
2215 }
2216 }
2217 return false;
0a784551 2218 }
2219
db26acd4 2220 /**
2221 * Returns the current setting if it is set
2222 *
2223 * @return mixed null if null, else an array
2224 */
73fa96d5 2225 public function get_setting() {
220a90c5 2226 $result = $this->config_read($this->name);
10f19c49 2227
220a90c5 2228 if (is_null($result)) {
2229 return NULL;
2230 }
2231 if ($result === '') {
2232 return array();
2233 }
10f19c49 2234 $enabled = explode(',', $result);
2235 $setting = array();
2236 foreach ($enabled as $option) {
2237 $setting[$option] = 1;
2238 }
2239 return $setting;
6e4dc10f 2240 }
eef868d1 2241
db26acd4 2242 /**
2243 * Saves the setting(s) provided in $data
2244 *
2245 * @param array $data An array of data, if not array returns empty str
2246 * @return mixed empty string on useless data or bool true=success, false=failed
2247 */
73fa96d5 2248 public function write_setting($data) {
220a90c5 2249 if (!is_array($data)) {
2250 return ''; // ignore it
2251 }
2252 if (!$this->load_choices() or empty($this->choices)) {
2253 return '';
2254 }
2255 unset($data['xxxxx']);
2256 $result = array();
2257 foreach ($data as $key => $value) {
2258 if ($value and array_key_exists($key, $this->choices)) {
2259 $result[] = $key;
2260 }
2261 }
2262 return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
6e4dc10f 2263 }
0c079f19 2264
db26acd4 2265 /**
2266 * Returns XHTML field(s) as required by choices
2267 *
2268 * Relies on data being an array should data ever be another valid vartype with
2269 * acceptable value this may cause a warning/error
2270 * if (!is_array($data)) would fix the problem
2271 *
2272 * @todo Add vartype handling to ensure $data is an array
2273 *
2274 * @param array $data An array of checked values
2275 * @param string $query
2276 * @return string XHTML field
2277 */
73fa96d5 2278 public function output_html($data, $query='') {
220a90c5 2279 if (!$this->load_choices() or empty($this->choices)) {
2280 return '';
2281 }
2282 $default = $this->get_defaultsetting();
2283 if (is_null($default)) {
2284 $default = array();
2285 }
2286 if (is_null($data)) {
775f811a 2287 $data = array();
220a90c5 2288 }
220a90c5 2289 $options = array();
2290 $defaults = array();
10f19c49 2291 foreach ($this->choices as $key=>$description) {
2292 if (!empty($data[$key])) {
220a90c5 2293 $checked = 'checked="checked"';
2294 } else {
2295 $checked = '';
2296 }
10f19c49 2297 if (!empty($default[$key])) {
220a90c5 2298 $defaults[] = $description;
2299 }
2300
2301 $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
9baf6825 2302 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
220a90c5 2303 }
2304
587c7040 2305 if (is_null($default)) {
2306 $defaultinfo = NULL;
2307 } else if (!empty($defaults)) {
9baf6825 2308 $defaultinfo = implode(', ', $defaults);
2309 } else {
2310 $defaultinfo = get_string('none');
2311 }
220a90c5 2312
2313 $return = '<div class="form-multicheckbox">';
2314 $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2315 if ($options) {
2316 $return .= '<ul>';
2317 foreach ($options as $option) {
2318 $return .= '<li>'.$option.'</li>';
2319 }
2320 $return .= '</ul>';
6e4dc10f 2321 }
587c7040 2322 $return .= '</div>';
6153cf58 2323
587c7040 2324 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
c5d2d0dd 2325
6e4dc10f 2326 }
6e4dc10f 2327}
2328
3e7069e7 2329
220a90c5 2330/**
2331 * Multiple checkboxes 2, value stored as string 00101011
db26acd4 2332 *
2333 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2334 */
2335class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
db26acd4 2336
3e7069e7
PS
2337 /**
2338 * Returns the setting if set
2339 *
2340 * @return mixed null if not set, else an array of set settings
2341 */
73fa96d5 2342 public function get_setting() {
220a90c5 2343 $result = $this->config_read($this->name);
2344 if (is_null($result)) {
2345 return NULL;
2346 }
2347 if (!$this->load_choices()) {
2348 return NULL;
2349 }
2350 $result = str_pad($result, count($this->choices), '0');
2351 $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2352 $setting = array();
2353 foreach ($this->choices as $key=>$unused) {
2354 $value = array_shift($result);
2355 if ($value) {
10f19c49 2356 $setting[$key] = 1;
220a90c5 2357 }
2358 }
2359 return $setting;
2360 }
6e4dc10f 2361
db26acd4 2362 /**
2363 * Save setting(s) provided in $data param
2364 *
2365 * @param array $data An array of settings to save
2366 * @return mixed empty string for bad data or bool true=>success, false=>error
2367 */
73fa96d5 2368 public function write_setting($data) {
220a90c5 2369 if (!is_array($data)) {
2370 return ''; // ignore it
2371 }
2372 if (!$this->load_choices() or empty($this->choices)) {
2373 return '';
2374 }
2375 $result = '';
2376 foreach ($this->choices as $key=>$unused) {
2377 if (!empty($data[$key])) {
2378 $result .= '1';
2379 } else {
2380 $result .= '0';
2381 }
2382 }
2383 return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2384 }
2385}
2386
3e7069e7 2387
220a90c5 2388/**
2389 * Select one value from list
db26acd4 2390 *
2391 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2392 */
2393class admin_setting_configselect extends admin_setting {
3e7069e7 2394 /** @var array Array of choices value=>label */
73fa96d5 2395 public $choices;
6e4dc10f 2396
220a90c5 2397 /**
2398 * Constructor
1a41e806 2399 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2400 * @param string $visiblename localised
2401 * @param string $description long localised info
eab8ed9f 2402 * @param string|int $defaultsetting
220a90c5 2403 * @param array $choices array of $value=>$label for each selection
2404 */
73fa96d5 2405 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
220a90c5 2406 $this->choices = $choices;
73fa96d5 2407 parent::__construct($name, $visiblename, $description, $defaultsetting);
220a90c5 2408 }
2409
2410 /**
2411 * This function may be used in ancestors for lazy loading of choices
db26acd4 2412 *
0c079f19 2413 * Override this method if loading of choices is expensive, such
2414 * as when it requires multiple db requests.
2415 *
db26acd4 2416 * @return bool true if loaded, false if error
220a90c5 2417 */
73fa96d5 2418 public function load_choices() {
220a90c5 2419 /*
2420 if (is_array($this->choices)) {
2421 return true;
6e4dc10f 2422 }
220a90c5 2423 .... load choices here
2424 */
2425 return true;
6e4dc10f 2426 }
2427
db26acd4 2428 /**
2429 * Check if this is $query is related to a choice
2430 *
2431 * @param string $query
2432 * @return bool true if related, false if not
2433 */
73fa96d5 2434 public function is_related($query) {
407d8134 2435 if (parent::is_related($query)) {
2436 return true;
2437 }
2438 if (!$this->load_choices()) {
2439 return false;
2440 }
407d8134 2441 foreach ($this->choices as $key=>$value) {
f8311def 2442 if (strpos(textlib::strtolower($key), $query) !== false) {
407d8134 2443 return true;
2444 }
f8311def 2445 if (strpos(textlib::strtolower($value), $query) !== false) {
407d8134 2446 return true;
2447 }
c5d2d0dd 2448 }
407d8134 2449 return false;
2450 }
2451
db26acd4 2452 /**
2453 * Return the setting
0c079f19 2454 *
875e5f07 2455 * @return mixed returns config if successful else null
db26acd4 2456 */
73fa96d5 2457 public function get_setting() {
220a90c5 2458 return $this->config_read($this->name);
6e4dc10f 2459 }
eef868d1 2460
db26acd4 2461 /**
2462 * Save a setting
2463 *
2464 * @param string $data
2465 * @return string empty of error string
2466 */
73fa96d5 2467 public function write_setting($data) {
220a90c5 2468 if (!$this->load_choices() or empty($this->choices)) {
2469 return '';
2470 }
2471 if (!array_key_exists($data, $this->choices)) {
2472 return ''; // ignore it
2473 }
eef868d1 2474
220a90c5 2475 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2476 }
eef868d1 2477
e2249afe 2478 /**
db26acd4 2479 * Returns XHTML select field
2480 *
2481 * Ensure the options are loaded, and generate the XHTML for the select
e2249afe 2482 * element and any warning message. Separating this out from output_html
2483 * makes it easier to subclass this class.
2484 *
2485 * @param string $data the option to show as selected.
2486 * @param string $current the currently selected option in the database, null if none.
2487 * @param string $default the default selected option.
2488 * @return array the HTML for the select element, and a warning message.
2489 */
73fa96d5 2490 public function output_select_html($data, $current, $default, $extraname = '') {
220a90c5 2491 if (!$this->load_choices() or empty($this->choices)) {
e2249afe 2492 return array('', '');
6e4dc10f 2493 }
220a90c5 2494
9c305ba1 2495 $warning = '';
2496 if (is_null($current)) {
9baf6825 2497 // first run
9c305ba1 2498 } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2499 // no warning
9baf6825 2500 } else if (!array_key_exists($current, $this->choices)) {
2501 $warning = get_string('warningcurrentsetting', 'admin', s($current));
2502 if (!is_null($default) and $data == $current) {
2503 $data = $default; // use default instead of first value when showing the form
2504 }
2505 }
9c305ba1 2506
e2249afe 2507 $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
6e4dc10f 2508 foreach ($this->choices as $key => $value) {
9baf6825 2509 // the string cast is needed because key may be integer - 0 is equal to most strings!
e2249afe 2510 $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
eef868d1 2511 }
e2249afe 2512 $selecthtml .= '</select>';
2513 return array($selecthtml, $warning);
2514 }
2515
db26acd4 2516 /**
2517 * Returns XHTML select field and wrapping div(s)
2518 *
2519 * @see output_select_html()
0c079f19 2520 *
db26acd4 2521 * @param string $data the option to show as selected
2522 * @param string $query
2523 * @return string XHTML field and wrapping div
2524 */
73fa96d5 2525 public function output_html($data, $query='') {
e2249afe 2526 $default = $this->get_defaultsetting();
2527 $current = $this->get_setting();
2528
2529 list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2530 if (!$selecthtml) {
2531 return '';
2532 }
2533
2534 if (!is_null($default) and array_key_exists($default, $this->choices)) {
2535 $defaultinfo = $this->choices[$default];
2536 } else {
2537 $defaultinfo = NULL;
2538 }
2539
2540 $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
220a90c5 2541
587c7040 2542 return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
6e4dc10f 2543 }
6e4dc10f 2544}
2545
3e7069e7 2546
220a90c5 2547/**
2548 * Select multiple items from list
db26acd4 2549 *
2550 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2551 */
6e4dc10f 2552class admin_setting_configmultiselect extends admin_setting_configselect {
3e7069e7
PS
2553 /**
2554 * Constructor
2555 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2556 * @param string $visiblename localised
2557 * @param string $description long localised info
2558 * @param array $defaultsetting array of selected items
2559 * @param array $choices array of $value=>$label for each list item
2560 */
73fa96d5 2561 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
2562 parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
6e4dc10f 2563 }
2564
db26acd4 2565 /**
2566 * Returns the select setting(s)
2567 *
2568 * @return mixed null or array. Null if no settings else array of setting(s)
2569 */
73fa96d5 2570 public function get_setting() {
220a90c5 2571 $result = $this->config_read($this->name);
2572 if (is_null($result)) {
d7933a55 2573 return NULL;
2574 }
220a90c5 2575 if ($result === '') {
2576 return array();
2577 }
2578 return explode(',', $result);
6e4dc10f 2579 }
eef868d1 2580
db26acd4 2581 /**
2582 * Saves setting(s) provided through $data
2583 *
2584 * Potential bug in the works should anyone call with this function
2585 * using a vartype that is not an array
2586 *
db26acd4 2587 * @param array $data
2588 */
73fa96d5 2589 public function write_setting($data) {
220a90c5 2590 if (!is_array($data)) {
2591 return ''; //ignore it
2592 }
2593 if (!$this->load_choices() or empty($this->choices)) {
2594 return '';
2595 }
2596
a7ad48fd 2597 unset($data['xxxxx']);
2598
220a90c5 2599 $save = array();
2600 foreach ($data as $value) {
2601 if (!array_key_exists($value, $this->choices)) {
2602 continue; // ignore it
2603 }
2604 $save[] = $value;
2605 }
2606
2607 return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2608 }
2609
2610 /**
2611 * Is setting related to query text - used when searching
db26acd4 2612 *
220a90c5 2613 * @param string $query
db26acd4 2614 * @return bool true if related, false if not
220a90c5 2615 */
73fa96d5 2616 public function is_related($query) {
220a90c5 2617 if (!$this->load_choices() or empty($this->choices)) {
2618 return false;
2619 }
2620 if (parent::is_related($query)) {
2621 return true;
2622 }
2623
220a90c5 2624 foreach ($this->choices as $desc) {
f8311def 2625 if (strpos(textlib::strtolower($desc), $query) !== false) {
220a90c5 2626 return true;
2627 }
2628 }
2629 return false;
2630 }
2631
db26acd4 2632 /**
2633 * Returns XHTML multi-select field
2634 *
2635 * @todo Add vartype handling to ensure $data is an array
2636 * @param array $data Array of values to select by default
2637 * @param string $query
2638 * @return string XHTML multi-select field
2639 */
73fa96d5 2640 public function output_html($data, $query='') {
220a90c5 2641 if (!$this->load_choices() or empty($this->choices)) {
2642 return '';
2643 }
2644 $choices = $this->choices;
2645 $default = $this->get_defaultsetting();
2646 if (is_null($default)) {
2647 $default = array();
2648 }
2649 if (is_null($data)) {
d7933a55 2650 $data = array();
2651 }
220a90c5 2652
2653 $defaults = array();
4413941f 2654 $size = min(10, count($this->choices));
a7ad48fd 2655 $return = '<div class="form-select"><input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
4413941f 2656 $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
220a90c5 2657 foreach ($this->choices as $key => $description) {
2658 if (in_array($key, $data)) {
2659 $selected = 'selected="selected"';
2660 } else {
2661 $selected = '';
2662 }
2663 if (in_array($key, $default)) {
2664 $defaults[] = $description;
6e4dc10f 2665 }
220a90c5 2666
2667 $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2668 }
2669
587c7040 2670 if (is_null($default)) {
2671 $defaultinfo = NULL;
2672 } if (!empty($defaults)) {
2673 $defaultinfo = implode(', ', $defaults);
220a90c5 2674 } else {
587c7040 2675 $defaultinfo = get_string('none');
6e4dc10f 2676 }
eef868d1 2677
587c7040 2678 $return .= '</select></div>';
2679 return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
6e4dc10f 2680 }
220a90c5 2681}
eef868d1 2682
220a90c5 2683/**
2684 * Time selector
db26acd4 2685 *
2686 * This is a liiitle bit messy. we're using two selects, but we're returning
220a90c5 2687 * them as an array named after $name (so we only use $name2 internally for the setting)
db26acd4 2688 *
2689 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2690 */
2691class admin_setting_configtime extends admin_setting {
3e7069e7 2692 /** @var string Used for setting second select (minutes) */
73fa96d5 2693 public $name2;
220a90c5 2694
2695 /**
2696 * Constructor
2697 * @param string $hoursname setting for hours
2698 * @param string $minutesname setting for hours
2699 * @param string $visiblename localised
2700 * @param string $description long localised info
2701 * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2702 */
73fa96d5 2703 public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
220a90c5 2704 $this->name2 = $minutesname;
73fa96d5 2705 parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
220a90c5 2706 }
2707
db26acd4 2708 /**
2709 * Get the selected time
0c079f19 2710 *
db26acd4 2711 * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
2712 */
73fa96d5 2713 public function get_setting() {
220a90c5 2714 $result1 = $this->config_read($this->name);
2715 $result2 = $this->config_read($this->name2);
2716 if (is_null($result1) or is_null($result2)) {
2717 return NULL;
2718 }
2719
2720 return array('h' => $result1, 'm' => $result2);
2721 }
2722
db26acd4 2723 /**
2724 * Store the time (hours and minutes)
0c079f19 2725 *
db26acd4 2726 * @param array $data Must be form 'h'=>xx, 'm'=>xx
2727 * @return bool true if success, false if not
2728 */
73fa96d5 2729 public function write_setting($data) {
220a90c5 2730 if (!is_array($data)) {
2731 return '';
2732 }
2733
2734 $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2735 return ($result ? '' : get_string('errorsetting', 'admin'));
2736 }
2737
db26acd4 2738 /**
2739 * Returns XHTML time select fields
2740 *
2741 * @param array $data Must be form 'h'=>xx, 'm'=>xx
2742 * @param string $query
2743 * @return string XHTML time select fields and wrapping div(s)
2744 */
73fa96d5 2745 public function output_html($data, $query='') {
220a90c5 2746 $default = $this->get_defaultsetting();
2747
2748 if (is_array($default)) {
587c7040 2749 $defaultinfo = $default['h'].':'.$default['m'];
cc73de71 2750 } else {
587c7040 2751 $defaultinfo = NULL;
6e4dc10f 2752 }
220a90c5 2753
587c7040 2754 $return = '<div class="form-time defaultsnext">'.
9baf6825 2755 '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
220a90c5 2756 for ($i = 0; $i < 24; $i++) {
2757 $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
6e4dc10f 2758 }
220a90c5 2759 $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2760 for ($i = 0; $i < 60; $i += 5) {
2761 $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2762 }
587c7040 2763 $return .= '</select></div>';
2764 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
6e4dc10f 2765 }
2766
2767}
2768
3e7069e7 2769
db26acd4 2770/**
2771 * Used to validate a textarea used for ip addresses
2772 *
2773 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
db26acd4 2774 */
4e639121 2775class admin_setting_configiplist extends admin_setting_configtextarea {
db26acd4 2776
3e7069e7
PS
2777 /**
2778 * Validate the contents of the textarea as IP addresses
2779 *
2780 * Used to validate a new line separated list of IP addresses collected from
2781 * a textarea control
2782 *
2783 * @param string $data A list of IP Addresses separated by new lines
2784 * @return mixed bool true for success or string:error on failure
2785 */
73fa96d5 2786 public function validate($data) {
4e639121 2787 if(!empty($data)) {
c5d2d0dd 2788 $ips = explode("\n", $data);
4e639121 2789 } else {
2790 return true;
2791 }
2792 $result = true;
2793 foreach($ips as $ip) {
2794 $ip = trim($ip);
3e7069e7 2795 if (preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
9baf6825 2796 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2797 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
4e639121 2798 $result = true;
2799 } else {
2800 $result = false;
2801 break;
2802 }
2803 }
9baf6825 2804 if($result) {
4e639121 2805 return true;
2806 } else {
2807 return get_string('validateerror', 'admin');
2808 }
2809 }
2810}
2811
3e7069e7 2812
4413941f 2813/**
db26acd4 2814 * An admin setting for selecting one or more users who have a capability
2815 * in the system context
2816 *
4413941f 2817 * An admin setting for selecting one or more users, who have a particular capability
2818 * in the system context. Warning, make sure the list will never be too long. There is
2819 * no paging or searching of this list.
2820 *
2821 * To correctly get a list of users from this config setting, you need to call the
2822 * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
db26acd4 2823 *
2824 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4413941f 2825 */
2826class admin_setting_users_with_capability extends admin_setting_configmultiselect {
adf176d7 2827 /** @var string The capabilities name */
4413941f 2828 protected $capability;
adf176d7
PS
2829 /** @var int include admin users too */
2830 protected $includeadmins;
4413941f 2831
2832 /**
2833 * Constructor.
2834 *
2835 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2836 * @param string $visiblename localised name
2837 * @param string $description localised long description
2838 * @param array $defaultsetting array of usernames
2839 * @param string $capability string capability name.
875e5f07 2840 * @param bool $includeadmins include administrators
4413941f 2841 */
adf176d7
PS
2842 function __construct($name, $visiblename, $description, $defaultsetting, $capability, $includeadmins = true) {
2843 $this->capability = $capability;
2844 $this->includeadmins = $includeadmins;
fb073f60 2845 parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
2846 }
2847
db26acd4 2848 /**
2849 * Load all of the uses who have the capability into choice array
2850 *
2851 * @return bool Always returns true
2852 */
fb073f60 2853 function load_choices() {
2854 if (is_array($this->choices)) {
2855 return true;
2856 }
4413941f 2857 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM),
9baf6825 2858 $this->capability, 'u.id,u.username,u.firstname,u.lastname', 'u.lastname,u.firstname');
fb073f60 2859 $this->choices = array(
4413941f 2860 '$@NONE@$' => get_string('nobody'),
fb073f60 2861 '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($this->capability)),
4413941f 2862 );
adf176d7
PS
2863 if ($this->includeadmins) {
2864 $admins = get_admins();
2865 foreach ($admins as $user) {
2866 $this->choices[$user->id] = fullname($user);
2867 }
2868 }
9cc0de51
DM
2869 if (is_array($users)) {
2870 foreach ($users as $user) {
2871 $this->choices[$user->id] = fullname($user);
2872 }
4413941f 2873 }
fb073f60 2874 return true;
4413941f 2875 }
2876
db26acd4 2877 /**
2878 * Returns the default setting for class
2879 *
2880 * @return mixed Array, or string. Empty string if no default
2881 */
73fa96d5 2882 public function get_defaultsetting() {
4413941f 2883 $this->load_choices();
cd3acbf2 2884 $defaultsetting = parent::get_defaultsetting();
2885 if (empty($defaultsetting)) {
4413941f 2886 return array('$@NONE@$');
cd3acbf2 2887 } else if (array_key_exists($defaultsetting, $this->choices)) {
9baf6825 2888 return $defaultsetting;
2889 } else {
2890 return '';
2891 }
4413941f 2892 }
2893
db26acd4 2894 /**
2895 * Returns the current setting
2896 *
2897 * @return mixed array or string
2898 */
73fa96d5 2899 public function get_setting() {
4413941f 2900 $result = parent::get_setting();
adf176d7
PS
2901 if ($result === null) {
2902 // this is necessary for settings upgrade
2903 return null;
2904 }
4413941f 2905 if (empty($result)) {
2906 $result = array('$@NONE@$');
2907 }
2908 return $result;
2909 }
2910
db26acd4 2911 /**
2912 * Save the chosen setting provided as $data
2913 *
2914 * @param array $data
2915 * @return mixed string or array
2916 */
73fa96d5 2917 public function write_setting($data) {
9baf6825 2918 // If all is selected, remove any explicit options.
4413941f 2919 if (in_array('$@ALL@$', $data)) {
2920 $data = array('$@ALL@$');
2921 }
875e5f07 2922 // None never needs to be written to the DB.
4413941f 2923 if (in_array('$@NONE@$', $data)) {
2924 unset($data[array_search('$@NONE@$', $data)]);
2925 }
2926 return parent::write_setting($data);
2927 }
2928}
2929
3e7069e7 2930
220a90c5 2931/**
2932 * Special checkbox for calendar - resets SESSION vars.
db26acd4 2933 *
2934 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2935 */
6e4dc10f 2936class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
3e7069e7
PS
2937 /**
2938 * Calls the parent::__construct with default values
2939 *
2940 * name => calendar_adminseesall
2941 * visiblename => get_string('adminseesall', 'admin')
2942 * description => get_string('helpadminseesall', 'admin')
2943 * defaultsetting => 0
2944 */
73fa96d5 2945 public function __construct() {
2946 parent::__construct('calendar_adminseesall', get_string('adminseesall', 'admin'),
9baf6825 2947 get_string('helpadminseesall', 'admin'), '0');
6e4dc10f 2948 }
2949
db26acd4 2950 /**
2951 * Stores the setting passed in $data
2952 *
db26acd4 2953 * @param mixed gets converted to string for comparison
2954 * @return string empty string or error message
2955 */
73fa96d5 2956 public function write_setting($data) {
6e4dc10f 2957 global $SESSION;
220a90c5 2958 return parent::write_setting($data);
6e4dc10f 2959 }
2960}
2961
392e7363 2962/**
2963 * Special select for settings that are altered in setup.php and can not be altered on the fly
db26acd4 2964 *
2965 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
392e7363 2966 */
2967class admin_setting_special_selectsetup extends admin_setting_configselect {
3e7069e7
PS
2968 /**
2969 * Reads the setting directly from the database
2970 *
2971 * @return mixed
2972 */
73fa96d5 2973 public function get_setting() {
9baf6825 2974 // read directly from db!
392e7363 2975 return get_config(NULL, $this->name);
2976 }
2977
db26acd4 2978 /**
2979 * Save the setting passed in $data
2980 *
db26acd4 2981 * @param string $data The setting to save
2982 * @return string empty or error message
2983 */
73fa96d5 2984 public function write_setting($data) {
392e7363 2985 global $CFG;
2986 // do not change active CFG setting!
2987 $current = $CFG->{$this->name};
2988 $result = parent::write_setting($data);
2989 $CFG->{$this->name} = $current;
2990 return $result;
2991 }
2992}
2993
3e7069e7 2994
220a90c5 2995/**
2996 * Special select for frontpage - stores data in course table
db26acd4 2997 *
2998 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2999 */
6e4dc10f 3000class admin_setting_sitesetselect extends admin_setting_configselect {
3e7069e7
PS
3001 /**
3002 * Returns the site name for the selected site
3003 *
3004 * @see get_site()
3005 * @return string The site name of the selected site
3006 */
73fa96d5 3007 public function get_setting() {
b2bf016e 3008 $site = get_site();
3009 return $site->{$this->name};
6e4dc10f 3010 }
3e7069e7 3011
db26acd4 3012 /**
3013 * Updates the database and save the setting
3014 *
db26acd4 3015 * @param string data
3016 * @return string empty or error message
3017 */
73fa96d5 3018 public function write_setting($data) {
b2bf016e 3019 global $DB, $SITE;
6e4dc10f 3020 if (!in_array($data, array_keys($this->choices))) {
220a90c5 3021 return get_string('errorsetting', 'admin');
6e4dc10f 3022 }
3023 $record = new stdClass();
220a90c5 3024 $record->id = SITEID;
3025 $temp = $this->name;
3026 $record->$temp = $data;
6e4dc10f 3027 $record->timemodified = time();
b2bf016e 3028 // update $SITE
3029 $SITE->{$this->name} = $data;
f33e1ed4 3030 return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 3031 }
6e4dc10f 3032}
3033
3e7069e7 3034
ca497f4f 3035/**
3036 * Select for blog's bloglevel setting: if set to 0, will set blog_menu
3037 * block to hidden.
3038 *
3039 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3040 */
3041class admin_setting_bloglevel extends admin_setting_configselect {
3042 /**
3043 * Updates the database and save the setting
3044 *
3045 * @param string data
3046 * @return string empty or error message
3047 */
3048 public function write_setting($data) {
d9e0b80f 3049 global $DB, $CFG;
00b9c582 3050 if ($data == 0) {
d9e0b80f
PS
3051 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 1");
3052 foreach ($blogblocks as $block) {
3053 $DB->set_field('block', 'visible', 0, array('id' => $block->id));
3054 }
ca497f4f 3055 } else {
d9e0b80f
PS
3056 // reenable all blocks only when switching from disabled blogs
3057 if (isset($CFG->bloglevel) and $CFG->bloglevel == 0) {
3058 $blogblocks = $DB->get_records_select('block', "name LIKE 'blog_%' AND visible = 0");
3059 foreach ($blogblocks as $block) {
3060 $DB->set_field('block', 'visible', 1, array('id' => $block->id));
3061 }
3062 }
ca497f4f 3063 }
3064 return parent::write_setting($data);
3065 }
3066}
3067
3e7069e7 3068
220a90c5 3069/**
3070 * Special select - lists on the frontpage - hacky
db26acd4 3071 *
3072 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 3073 */
3074class admin_setting_courselist_frontpage extends admin_setting {
3e7069e7 3075 /** @var array Array of choices value=>label */
73fa96d5 3076 public $choices;
6e4dc10f 3077
db26acd4 3078 /**
3079 * Construct override, requires one param
3080 *
db26acd4 3081 * @param bool $loggedin Is the user logged in
3082 */
73fa96d5 3083 public function __construct($loggedin) {
6e4dc10f 3084 global $CFG;
220a90c5 3085 require_once($CFG->dirroot.'/course/lib.php');
3086 $name = 'frontpage'.($loggedin ? 'loggedin' : '');
3087 $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
3088 $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
3089 $defaults = array(FRONTPAGECOURSELIST);
73fa96d5 3090 parent::__construct($name, $visiblename, $description, $defaults);
6e4dc10f 3091 }
eef868d1 3092
db26acd4 3093 /**
3094 * Loads the choices available
3095 *
db26acd4 3096 * @return bool always returns true
3097 */
73fa96d5 3098 public function load_choices() {
c7da4357 3099 global $DB;
220a90c5 3100 if (is_array($this->choices)) {
3101 return true;
3102 }
3103 $this->choices = array(FRONTPAGENEWS => get_string('frontpagenews'),
9baf6825 3104 FRONTPAGECOURSELIST => get_string('frontpagecourselist'),
3105 FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
3106 FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
3107 'none' => get_string('none'));
c7da4357 3108 if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
220a90c5 3109 unset($this->choices[FRONTPAGECOURSELIST]);
3110 }
3111 return true;
3112 }
3e7069e7 3113
db26acd4 3114 /**
3115 * Returns the selected settings
3116 *
3117 * @param mixed array or setting or null
3118 */
73fa96d5 3119 public function get_setting() {
220a90c5 3120 $result = $this->config_read($this->name);
3121 if (is_null($result)) {
3122 return NULL;
3123 }
3124 if ($result === '') {
3125 return array();
3126 }
3127 return explode(',', $result);
6e4dc10f 3128 }
eef868d1 3129
db26acd4 3130 /**
3131 * Save the selected options
3132 *
3133 * @param array $data
3134 * @return mixed empty string (data is not an array) or bool true=success false=failure
3135 */
73fa96d5 3136 public function write_setting($data) {
220a90c5 3137 if (!is_array($data)) {
3138 return '';
6e4dc10f 3139 }
220a90c5 3140 $this->load_choices();
3141 $save = array();
6e4dc10f 3142 foreach($data as $datum) {
220a90c5 3143 if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
3144 continue;
6e4dc10f 3145 }
220a90c5 3146 $save[$datum] = $datum; // no duplicates
6e4dc10f 3147 }
220a90c5 3148 return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 3149 }
eef868d1 3150
db26acd4 3151 /**
3152 * Return XHTML select field and wrapping div
3153 *
3154 * @todo Add vartype handling to make sure $data is an array
3155 * @param array $data Array of elements to select by default
3156 * @return string XHTML select field and wrapping div
3157 */
73fa96d5 3158 public function output_html($data, $query='') {
220a90c5 3159 $this->load_choices();
3160 $currentsetting = array();
3161 foreach ($data as $key) {
3162 if ($key != 'none' and array_key_exists($key, $this->choices)) {
3163 $currentsetting[] = $key; // already selected first
6e4dc10f 3164 }
3165 }
220a90c5 3166
0a7e84c3 3167 $return = '<div class="form-group">';
6e4dc10f 3168 for ($i = 0; $i < count($this->choices) - 1; $i++) {
220a90c5 3169 if (!array_key_exists($i, $currentsetting)) {
3170 $currentsetting[$i] = 'none'; //none
3171 }
3172 $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
6e4dc10f 3173 foreach ($this->choices as $key => $value) {
220a90c5 3174 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
6e4dc10f 3175 }
3176 $return .= '</select>';
3177 if ($i !== count($this->choices) - 2) {
975211bb 3178 $return .= '<br />';
6e4dc10f 3179 }
3180 }
0a7e84c3 3181 $return .= '</div>';
3182
587c7040 3183 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
6e4dc10f 3184 }
3185}
3186
3e7069e7 3187
220a90c5 3188/**
3189 * Special checkbox for frontpage - stores data in course table
db26acd4 3190 *
3191 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 3192 */
6e4dc10f 3193class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
3e7069e7
PS
3194 /**
3195 * Returns the current sites name
3196 *
3197 * @return string
3198 */
73fa96d5 3199 public function get_setting() {
b2bf016e 3200 $site = get_site();
3201 return $site->{$this->name};
6e4dc10f 3202 }
eef868d1 3203
db26acd4 3204 /**
3205 * Save the selected setting
3206 *
db26acd4 3207 * @param string $data The selected site
3208 * @return string empty string or error message
3209 */
73fa96d5 3210 public function write_setting($data) {
b2bf016e 3211 global $DB, $SITE;
365a5941 3212 $record = new stdClass();
220a90c5 3213 $record->id = SITEID;
3214 $record->{$this->name} = ($data == '1' ? 1 : 0);
3215 $record->timemodified = time();
b2bf016e 3216 // update $SITE
3217 $SITE->{$this->name} = $data;
f33e1ed4 3218 return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 3219 }
6e4dc10f 3220}
3221
220a90c5 3222/**
3223 * Special text for frontpage - stores data in course table.
3224 * Empty string means not set here. Manual setting is required.
db26acd4 3225 *
3226 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 3227 */
6e4dc10f 3228class admin_setting_sitesettext extends admin_setting_configtext {
3e7069e7
PS
3229 /**
3230 * Return the current setting
3231 *
3232 * @return mixed string or null
3233 */
73fa96d5 3234 public function get_setting() {
b2bf016e 3235 $site = get_site();
a7747679 3236 return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
6e4dc10f 3237 }
90cfbd0a 3238
db26acd4 3239 /**
3240 * Validate the selected data
3241 *
3242 * @param string $data The selected value to validate
3243 * @return mixed true or message string
3244 */
73fa96d5 3245 public function validate($data) {
294ce987 3246 $cleaned = clean_param($data, PARAM_MULTILANG);
e33fbf87 3247 if ($cleaned === '') {
3248 return get_string('required');
3249 }
3250 if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3251 return true;
3252 } else {
3253 return get_string('validateerror', 'admin');
b89639f9 3254 }
b89639f9 3255 }
3256
db26acd4 3257 /**
3258 * Save the selected setting
3259 *
db26acd4 3260 * @param string $data The selected value
875e5f07 3261 * @return string empty or error message
db26acd4 3262 */
73fa96d5 3263 public function write_setting($data) {
b2bf016e 3264 global $DB, $SITE;
b89639f9 3265 $data = trim($data);
c5d2d0dd 3266 $validated = $this->validate($data);
e33fbf87 3267 if ($validated !== true) {
3268 return $validated;