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