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