weekly on-sync release 3.0dev
[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
3e7069e7 2185
220a90c5 2186/**
2187 * General text area without html editor.
db26acd4 2188 *
2189 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2190 */
2191class admin_setting_configtextarea extends admin_setting_configtext {
4fe2250a 2192 private $rows;
2193 private $cols;
eba8cd63 2194
db26acd4 2195 /**
2196 * @param string $name
2197 * @param string $visiblename
2198 * @param string $description
2199 * @param mixed $defaultsetting string or array
2200 * @param mixed $paramtype
2201 * @param string $cols The number of columns to make the editor
2202 * @param string $rows The number of rows to make the editor
2203 */
73fa96d5 2204 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
220a90c5 2205 $this->rows = $rows;
2206 $this->cols = $cols;
73fa96d5 2207 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
eba8cd63 2208 }
3e7069e7 2209
db26acd4 2210 /**
2211 * Returns an XHTML string for the editor
2212 *
2213 * @param string $data
2214 * @param string $query
2215 * @return string XHTML string for the editor
2216 */
73fa96d5 2217 public function output_html($data, $query='') {
220a90c5 2218 $default = $this->get_defaultsetting();
2219
587c7040 2220 $defaultinfo = $default;
2221 if (!is_null($default) and $default !== '') {
2222 $defaultinfo = "\n".$default;
c5d2d0dd 2223 }
220a90c5 2224
2225 return format_admin_setting($this, $this->visiblename,
0ac97084 2226 '<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 2227 $this->description, true, '', $defaultinfo, $query);
4fe2250a 2228 }
2229}
2230
3e7069e7 2231
4fe2250a 2232/**
2233 * General text area with html editor.
2234 */
2235class admin_setting_confightmleditor extends admin_setting_configtext {
2236 private $rows;
2237 private $cols;
0c079f19 2238
4fe2250a 2239 /**
2240 * @param string $name
2241 * @param string $visiblename
2242 * @param string $description
2243 * @param mixed $defaultsetting string or array
2244 * @param mixed $paramtype
2245 */
2246 public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
2247 $this->rows = $rows;
2248 $this->cols = $cols;
2249 parent::__construct($name, $visiblename, $description, $defaultsetting, $paramtype);
ff5fe311 2250 editors_head_setup();
4fe2250a 2251 }
3e7069e7 2252
4fe2250a 2253 /**
2254 * Returns an XHTML string for the editor
2255 *
2256 * @param string $data
2257 * @param string $query
2258 * @return string XHTML string for the editor
2259 */
2260 public function output_html($data, $query='') {
2261 $default = $this->get_defaultsetting();
2262
2263 $defaultinfo = $default;
2264 if (!is_null($default) and $default !== '') {
2265 $defaultinfo = "\n".$default;
2266 }
2267
20e5da7d 2268 $editor = editors_get_preferred_editor(FORMAT_HTML);
69429650 2269 $editor->use_editor($this->get_id(), array('noclean'=>true));
4fe2250a 2270
2271 return format_admin_setting($this, $this->visiblename,
0ac97084 2272 '<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 2273 $this->description, true, '', $defaultinfo, $query);
220a90c5 2274 }
2275}
2276
3e7069e7 2277
220a90c5 2278/**
2279 * Password field, allows unmasking of password
db26acd4 2280 *
2281 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2282 */
2283class admin_setting_configpasswordunmask extends admin_setting_configtext {
3e7069e7
PS
2284 /**
2285 * Constructor
2286 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2287 * @param string $visiblename localised
2288 * @param string $description long localised info
2289 * @param string $defaultsetting default password
2290 */
73fa96d5 2291 public function __construct($name, $visiblename, $description, $defaultsetting) {
2292 parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
220a90c5 2293 }
0c079f19 2294
914499a3
PS
2295 /**
2296 * Log config changes if necessary.
2297 * @param string $name
2298 * @param string $oldvalue
2299 * @param string $value
2300 */
2301 protected function add_to_config_log($name, $oldvalue, $value) {
2302 if ($value !== '') {
2303 $value = '********';
2304 }
2305 if ($oldvalue !== '' and $oldvalue !== null) {
2306 $oldvalue = '********';
2307 }
2308 parent::add_to_config_log($name, $oldvalue, $value);
2309 }
2310
db26acd4 2311 /**
2312 * Returns XHTML for the field
2313 * Writes Javascript into the HTML below right before the last div
2314 *
2315 * @todo Make javascript available through newer methods if possible
2316 * @param string $data Value for the field
2317 * @param string $query Passed as final argument for format_admin_setting
2318 * @return string XHTML field
2319 */
73fa96d5 2320 public function output_html($data, $query='') {
220a90c5 2321 $id = $this->get_id();
2322 $unmask = get_string('unmaskpassword', 'form');
2323 $unmaskjs = '<script type="text/javascript">
eba8cd63 2324//<![CDATA[
633239f6 2325var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
2326
2327document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
2328
2329var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
2330
2331var unmaskchb = document.createElement("input");
2332unmaskchb.setAttribute("type", "checkbox");
2333unmaskchb.setAttribute("id", "'.$id.'unmask");
2334unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
2335unmaskdiv.appendChild(unmaskchb);
2336
2337var unmasklbl = document.createElement("label");
2338unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
2339if (is_ie) {
2340 unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
2341} else {
2342 unmasklbl.setAttribute("for", "'.$id.'unmask");
2343}
2344unmaskdiv.appendChild(unmasklbl);
2345
2346if (is_ie) {
2347 // ugly hack to work around the famous onchange IE bug
2348 unmaskchb.onclick = function() {this.blur();};
2349 unmaskdiv.onclick = function() {this.blur();};
2350}
eba8cd63 2351//]]>
2352</script>';
220a90c5 2353 return format_admin_setting($this, $this->visiblename,
9baf6825 2354 '<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>',
2355 $this->description, true, '', NULL, $query);
220a90c5 2356 }
2357}
2358
c7cd8d9c
DW
2359/**
2360 * Empty setting used to allow flags (advanced) on settings that can have no sensible default.
2361 * Note: Only advanced makes sense right now - locked does not.
2362 *
2363 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2364 */
2365class admin_setting_configempty extends admin_setting_configtext {
2366
2367 /**
2368 * @param string $name
2369 * @param string $visiblename
2370 * @param string $description
2371 */
2372 public function __construct($name, $visiblename, $description) {
2373 parent::__construct($name, $visiblename, $description, '', PARAM_RAW);
2374 }
2375
2376 /**
2377 * Returns an XHTML string for the hidden field
2378 *
2379 * @param string $data
2380 * @param string $query
2381 * @return string XHTML string for the editor
2382 */
2383 public function output_html($data, $query='') {
2384 return format_admin_setting($this,
2385 $this->visiblename,
2386 '<div class="form-empty" >' .
2387 '<input type="hidden"' .
2388 ' id="'. $this->get_id() .'"' .
2389 ' name="'. $this->get_full_name() .'"' .
2390 ' value=""/></div>',
2391 $this->description,
2392 true,
2393 '',
2394 get_string('none'),
2395 $query);
2396 }
2397}
2398
3e7069e7 2399
220a90c5 2400/**
e9c0fa35 2401 * Path to directory
db26acd4 2402 *
2403 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2404 */
e9c0fa35 2405class admin_setting_configfile extends admin_setting_configtext {
3e7069e7
PS
2406 /**
2407 * Constructor
2408 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2409 * @param string $visiblename localised
2410 * @param string $description long localised info
2411 * @param string $defaultdirectory default directory location
2412 */
73fa96d5 2413 public function __construct($name, $visiblename, $description, $defaultdirectory) {
2414 parent::__construct($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
220a90c5 2415 }
2416
db26acd4 2417 /**
2418 * Returns XHTML for the field
0c079f19 2419 *
db26acd4 2420 * Returns XHTML for the field and also checks whether the file
2421 * specified in $data exists using file_exists()
2422 *
2423 * @param string $data File name and path to use in value attr
2424 * @param string $query
2425 * @return string XHTML field
2426 */
73fa96d5 2427 public function output_html($data, $query='') {
5aaac8b2 2428 global $CFG;
220a90c5 2429 $default = $this->get_defaultsetting();
2430
220a90c5 2431 if ($data) {
e9c0fa35 2432 if (file_exists($data)) {
220a90c5 2433 $executable = '<span class="pathok">&#x2714;</span>';
2434 } else {
2435 $executable = '<span class="patherror">&#x2718;</span>';
2436 }
2437 } else {
2438 $executable = '';
2439 }
5aaac8b2
PS
2440 $readonly = '';
2441 if (!empty($CFG->preventexecpath)) {
2442 $this->visiblename .= '<div class="form-overridden">'.get_string('execpathnotallowed', 'admin').'</div>';
2443 $readonly = 'readonly="readonly"';
2444 }
220a90c5 2445
2446 return format_admin_setting($this, $this->visiblename,
5aaac8b2 2447 '<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 2448 $this->description, true, '', $default, $query);
eba8cd63 2449 }
41186f4f 2450
9a2b5e0b 2451 /**
41186f4f 2452 * Checks if execpatch has been disabled in config.php
9a2b5e0b
HD
2453 */
2454 public function write_setting($data) {
2455 global $CFG;
2456 if (!empty($CFG->preventexecpath)) {
41186f4f
PS
2457 if ($this->get_setting() === null) {
2458 // Use default during installation.
2459 $data = $this->get_defaultsetting();
2460 if ($data === null) {
2461 $data = '';
2462 }
2463 } else {
2464 return '';
2465 }
9a2b5e0b
HD
2466 }
2467 return parent::write_setting($data);
2468 }
220a90c5 2469}
2470
3e7069e7 2471
220a90c5 2472/**
e9c0fa35 2473 * Path to executable file
db26acd4 2474 *
2475 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2476 */
e9c0fa35 2477class admin_setting_configexecutable extends admin_setting_configfile {
2478
3e7069e7
PS
2479 /**
2480 * Returns an XHTML field
2481 *
2482 * @param string $data This is the value for the field
2483 * @param string $query
2484 * @return string XHTML field
2485 */
73fa96d5 2486 public function output_html($data, $query='') {
9a2b5e0b 2487 global $CFG;
e9c0fa35 2488 $default = $this->get_defaultsetting();
2489
2490 if ($data) {
9cd7bb37 2491 if (file_exists($data) and !is_dir($data) and is_executable($data)) {
e9c0fa35 2492 $executable = '<span class="pathok">&#x2714;</span>';
2493 } else {
2494 $executable = '<span class="patherror">&#x2718;</span>';
2495 }
2496 } else {
2497 $executable = '';
2498 }
5aaac8b2 2499 $readonly = '';
9a2b5e0b
HD
2500 if (!empty($CFG->preventexecpath)) {
2501 $this->visiblename .= '<div class="form-overridden">'.get_string('execpathnotallowed', 'admin').'</div>';
5aaac8b2 2502 $readonly = 'readonly="readonly"';
9a2b5e0b 2503 }
e9c0fa35 2504
2505 return format_admin_setting($this, $this->visiblename,
5aaac8b2 2506 '<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 2507 $this->description, true, '', $default, $query);
220a90c5 2508 }
e9c0fa35 2509}
220a90c5 2510
3e7069e7 2511
e9c0fa35 2512/**
2513 * Path to directory
db26acd4 2514 *
2515 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
e9c0fa35 2516 */
2517class admin_setting_configdirectory extends admin_setting_configfile {
db26acd4 2518
3e7069e7
PS
2519 /**
2520 * Returns an XHTML field
2521 *
2522 * @param string $data This is the value for the field
2523 * @param string $query
2524 * @return string XHTML
2525 */
73fa96d5 2526 public function output_html($data, $query='') {
5aaac8b2 2527 global $CFG;
220a90c5 2528 $default = $this->get_defaultsetting();
2529
220a90c5 2530 if ($data) {
2531 if (file_exists($data) and is_dir($data)) {
2532 $executable = '<span class="pathok">&#x2714;</span>';
2533 } else {
2534 $executable = '<span class="patherror">&#x2718;</span>';
2535 }
2536 } else {
2537 $executable = '';
2538 }
5aaac8b2
PS
2539 $readonly = '';
2540 if (!empty($CFG->preventexecpath)) {
2541 $this->visiblename .= '<div class="form-overridden">'.get_string('execpathnotallowed', 'admin').'</div>';
2542 $readonly = 'readonly="readonly"';
2543 }
9ba38673 2544
220a90c5 2545 return format_admin_setting($this, $this->visiblename,
5aaac8b2 2546 '<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 2547 $this->description, true, '', $default, $query);
220a90c5 2548 }
eba8cd63 2549}
2550
3e7069e7 2551
220a90c5 2552/**
2553 * Checkbox
db26acd4 2554 *
2555 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2556 */
6e4dc10f 2557class admin_setting_configcheckbox extends admin_setting {
3e7069e7 2558 /** @var string Value used when checked */
73fa96d5 2559 public $yes;
0c079f19 2560 /** @var string Value used when not checked */
73fa96d5 2561 public $no;
6e4dc10f 2562
220a90c5 2563 /**
2564 * Constructor
1a41e806 2565 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2566 * @param string $visiblename localised
2567 * @param string $description long localised info
2568 * @param string $defaultsetting
2569 * @param string $yes value used when checked
2570 * @param string $no value used when not checked
2571 */
73fa96d5 2572 public function __construct($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
2573 parent::__construct($name, $visiblename, $description, $defaultsetting);
220a90c5 2574 $this->yes = (string)$yes;
2575 $this->no = (string)$no;
6e4dc10f 2576 }
2577
db26acd4 2578 /**
2579 * Retrieves the current setting using the objects name
2580 *
2581 * @return string
2582 */
73fa96d5 2583 public function get_setting() {
220a90c5 2584 return $this->config_read($this->name);
6e4dc10f 2585 }
eef868d1 2586
db26acd4 2587 /**
2588 * Sets the value for the setting
2589 *
2590 * Sets the value for the setting to either the yes or no values
2591 * of the object by comparing $data to yes
2592 *
2593 * @param mixed $data Gets converted to str for comparison against yes value
2594 * @return string empty string or error
2595 */
73fa96d5 2596 public function write_setting($data) {
220a90c5 2597 if ((string)$data === $this->yes) { // convert to strings before comparison
2598 $data = $this->yes;
6e4dc10f 2599 } else {
220a90c5 2600 $data = $this->no;
6e4dc10f 2601 }
220a90c5 2602 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2603 }
2604
db26acd4 2605 /**
2606 * Returns an XHTML checkbox field
2607 *
2608 * @param string $data If $data matches yes then checkbox is checked
2609 * @param string $query
2610 * @return string XHTML field
2611 */
73fa96d5 2612 public function output_html($data, $query='') {
220a90c5 2613 $default = $this->get_defaultsetting();
2614
2615 if (!is_null($default)) {
2616 if ((string)$default === $this->yes) {
587c7040 2617 $defaultinfo = get_string('checkboxyes', 'admin');
220a90c5 2618 } else {
587c7040 2619 $defaultinfo = get_string('checkboxno', 'admin');
220a90c5 2620 }
c8218a42 2621 } else {
587c7040 2622 $defaultinfo = NULL;
c8218a42 2623 }
220a90c5 2624
2625 if ((string)$data === $this->yes) { // convert to strings before comparison
2626 $checked = 'checked="checked"';
2627 } else {
2628 $checked = '';
2629 }
2630
2631 return format_admin_setting($this, $this->visiblename,
9baf6825 2632 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2633 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2634 $this->description, true, '', $defaultinfo, $query);
6e4dc10f 2635 }
6e4dc10f 2636}
2637
3e7069e7 2638
220a90c5 2639/**
2640 * Multiple checkboxes, each represents different value, stored in csv format
db26acd4 2641 *
2642 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2643 */
2644class admin_setting_configmulticheckbox extends admin_setting {
3e7069e7 2645 /** @var array Array of choices value=>label */
73fa96d5 2646 public $choices;
eef868d1 2647
220a90c5 2648 /**
db26acd4 2649 * Constructor: uses parent::__construct
2650 *
1a41e806 2651 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2652 * @param string $visiblename localised
2653 * @param string $description long localised info
2654 * @param array $defaultsetting array of selected
2655 * @param array $choices array of $value=>$label for each checkbox
2656 */
73fa96d5 2657 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
6e4dc10f 2658 $this->choices = $choices;
73fa96d5 2659 parent::__construct($name, $visiblename, $description, $defaultsetting);
6e4dc10f 2660 }
2661
0a784551 2662 /**
73fa96d5 2663 * This public function may be used in ancestors for lazy loading of choices
db26acd4 2664 *
2665 * @todo Check if this function is still required content commented out only returns true
2666 * @return bool true if loaded, false if error
0a784551 2667 */
73fa96d5 2668 public function load_choices() {
0a784551 2669 /*
220a90c5 2670 if (is_array($this->choices)) {
2671 return true;
0a784551 2672 }
2673 .... load choices here
2674 */
220a90c5 2675 return true;
2676 }
2677
2678 /**
2679 * Is setting related to query text - used when searching
db26acd4 2680 *
220a90c5 2681 * @param string $query
db26acd4 2682 * @return bool true on related, false on not or failure
220a90c5 2683 */
73fa96d5 2684 public function is_related($query) {
220a90c5 2685 if (!$this->load_choices() or empty($this->choices)) {
2686 return false;
2687 }
2688 if (parent::is_related($query)) {
2689 return true;
2690 }
2691
220a90c5 2692 foreach ($this->choices as $desc) {
2f1e464a 2693 if (strpos(core_text::strtolower($desc), $query) !== false) {
220a90c5 2694 return true;
2695 }
2696 }
2697 return false;
0a784551 2698 }
2699
db26acd4 2700 /**
2701 * Returns the current setting if it is set
2702 *
2703 * @return mixed null if null, else an array
2704 */
73fa96d5 2705 public function get_setting() {
220a90c5 2706 $result = $this->config_read($this->name);
10f19c49 2707
220a90c5 2708 if (is_null($result)) {
2709 return NULL;
2710 }
2711 if ($result === '') {
2712 return array();
2713 }
10f19c49 2714 $enabled = explode(',', $result);
2715 $setting = array();
2716 foreach ($enabled as $option) {
2717 $setting[$option] = 1;
2718 }
2719 return $setting;
6e4dc10f 2720 }
eef868d1 2721
db26acd4 2722 /**
2723 * Saves the setting(s) provided in $data
2724 *
2725 * @param array $data An array of data, if not array returns empty str
2726 * @return mixed empty string on useless data or bool true=success, false=failed
2727 */
73fa96d5 2728 public function write_setting($data) {
220a90c5 2729 if (!is_array($data)) {
2730 return ''; // ignore it
2731 }
2732 if (!$this->load_choices() or empty($this->choices)) {
2733 return '';
2734 }
2735 unset($data['xxxxx']);
2736 $result = array();
2737 foreach ($data as $key => $value) {
2738 if ($value and array_key_exists($key, $this->choices)) {
2739 $result[] = $key;
2740 }
2741 }
2742 return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
6e4dc10f 2743 }
0c079f19 2744
db26acd4 2745 /**
2746 * Returns XHTML field(s) as required by choices
2747 *
2748 * Relies on data being an array should data ever be another valid vartype with
2749 * acceptable value this may cause a warning/error
2750 * if (!is_array($data)) would fix the problem
2751 *
2752 * @todo Add vartype handling to ensure $data is an array
2753 *
2754 * @param array $data An array of checked values
2755 * @param string $query
2756 * @return string XHTML field
2757 */
73fa96d5 2758 public function output_html($data, $query='') {
220a90c5 2759 if (!$this->load_choices() or empty($this->choices)) {
2760 return '';
2761 }
2762 $default = $this->get_defaultsetting();
2763 if (is_null($default)) {
2764 $default = array();
2765 }
2766 if (is_null($data)) {
775f811a 2767 $data = array();
220a90c5 2768 }
220a90c5 2769 $options = array();
2770 $defaults = array();
10f19c49 2771 foreach ($this->choices as $key=>$description) {
2772 if (!empty($data[$key])) {
220a90c5 2773 $checked = 'checked="checked"';
2774 } else {
2775 $checked = '';
2776 }
10f19c49 2777 if (!empty($default[$key])) {
220a90c5 2778 $defaults[] = $description;
2779 }
2780
2781 $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
9baf6825 2782 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
220a90c5 2783 }
2784
587c7040 2785 if (is_null($default)) {
2786 $defaultinfo = NULL;
2787 } else if (!empty($defaults)) {
9baf6825 2788 $defaultinfo = implode(', ', $defaults);
2789 } else {
2790 $defaultinfo = get_string('none');
2791 }
220a90c5 2792
2793 $return = '<div class="form-multicheckbox">';
2794 $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2795 if ($options) {
2796 $return .= '<ul>';
2797 foreach ($options as $option) {
2798 $return .= '<li>'.$option.'</li>';
2799 }
2800 $return .= '</ul>';
6e4dc10f 2801 }
587c7040 2802 $return .= '</div>';
6153cf58 2803
587c7040 2804 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
c5d2d0dd 2805
6e4dc10f 2806 }
6e4dc10f 2807}
2808
3e7069e7 2809
220a90c5 2810/**
2811 * Multiple checkboxes 2, value stored as string 00101011
db26acd4 2812 *
2813 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2814 */
2815class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
db26acd4 2816
3e7069e7
PS
2817 /**
2818 * Returns the setting if set
2819 *
2820 * @return mixed null if not set, else an array of set settings
2821 */
73fa96d5 2822 public function get_setting() {
220a90c5 2823 $result = $this->config_read($this->name);
2824 if (is_null($result)) {
2825 return NULL;
2826 }
2827 if (!$this->load_choices()) {
2828 return NULL;
2829 }
2830 $result = str_pad($result, count($this->choices), '0');
2831 $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2832 $setting = array();
2833 foreach ($this->choices as $key=>$unused) {
2834 $value = array_shift($result);
2835 if ($value) {
10f19c49 2836 $setting[$key] = 1;
220a90c5 2837 }
2838 }
2839 return $setting;
2840 }
6e4dc10f 2841
db26acd4 2842 /**
2843 * Save setting(s) provided in $data param
2844 *
2845 * @param array $data An array of settings to save
2846 * @return mixed empty string for bad data or bool true=>success, false=>error
2847 */
73fa96d5 2848 public function write_setting($data) {
220a90c5 2849 if (!is_array($data)) {
2850 return ''; // ignore it
2851 }
2852 if (!$this->load_choices() or empty($this->choices)) {
2853 return '';
2854 }
2855 $result = '';
2856 foreach ($this->choices as $key=>$unused) {
2857 if (!empty($data[$key])) {
2858 $result .= '1';
2859 } else {
2860 $result .= '0';
2861 }
2862 }
2863 return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2864 }
2865}
2866
3e7069e7 2867
220a90c5 2868/**
2869 * Select one value from list
db26acd4 2870 *
2871 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 2872 */
2873class admin_setting_configselect extends admin_setting {
3e7069e7 2874 /** @var array Array of choices value=>label */
73fa96d5 2875 public $choices;
6e4dc10f 2876
220a90c5 2877 /**
2878 * Constructor
1a41e806 2879 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2880 * @param string $visiblename localised
2881 * @param string $description long localised info
eab8ed9f 2882 * @param string|int $defaultsetting
220a90c5 2883 * @param array $choices array of $value=>$label for each selection
2884 */
73fa96d5 2885 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
220a90c5 2886 $this->choices = $choices;
73fa96d5 2887 parent::__construct($name, $visiblename, $description, $defaultsetting);
220a90c5 2888 }
2889
2890 /**
2891 * This function may be used in ancestors for lazy loading of choices
db26acd4 2892 *
0c079f19 2893 * Override this method if loading of choices is expensive, such
2894 * as when it requires multiple db requests.
2895 *
db26acd4 2896 * @return bool true if loaded, false if error
220a90c5 2897 */
73fa96d5 2898 public function load_choices() {
220a90c5 2899 /*
2900 if (is_array($this->choices)) {
2901 return true;
6e4dc10f 2902 }
220a90c5 2903 .... load choices here
2904 */
2905 return true;
6e4dc10f 2906 }
2907
db26acd4 2908 /**
2909 * Check if this is $query is related to a choice
2910 *
2911 * @param string $query
2912 * @return bool true if related, false if not
2913 */
73fa96d5 2914 public function is_related($query) {
407d8134 2915 if (parent::is_related($query)) {
2916 return true;
2917 }
2918 if (!$this->load_choices()) {
2919 return false;
2920 }
407d8134 2921 foreach ($this->choices as $key=>$value) {
2f1e464a 2922 if (strpos(core_text::strtolower($key), $query) !== false) {
407d8134 2923 return true;
2924 }
2f1e464a 2925 if (strpos(core_text::strtolower($value), $query) !== false) {
407d8134 2926 return true;
2927 }
c5d2d0dd 2928 }
407d8134 2929 return false;
2930 }
2931
db26acd4 2932 /**
2933 * Return the setting
0c079f19 2934 *
875e5f07 2935 * @return mixed returns config if successful else null
db26acd4 2936 */
73fa96d5 2937 public function get_setting() {
220a90c5 2938 return $this->config_read($this->name);
6e4dc10f 2939 }
eef868d1 2940
db26acd4 2941 /**
2942 * Save a setting
2943 *
2944 * @param string $data
2945 * @return string empty of error string
2946 */
73fa96d5 2947 public function write_setting($data) {
220a90c5 2948 if (!$this->load_choices() or empty($this->choices)) {
2949 return '';
2950 }
2951 if (!array_key_exists($data, $this->choices)) {
2952 return ''; // ignore it
2953 }
eef868d1 2954
220a90c5 2955 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2956 }
eef868d1 2957
e2249afe 2958 /**
db26acd4 2959 * Returns XHTML select field
2960 *
2961 * Ensure the options are loaded, and generate the XHTML for the select
e2249afe 2962 * element and any warning message. Separating this out from output_html
2963 * makes it easier to subclass this class.
2964 *
2965 * @param string $data the option to show as selected.
2966 * @param string $current the currently selected option in the database, null if none.
2967 * @param string $default the default selected option.
2968 * @return array the HTML for the select element, and a warning message.
2969 */
73fa96d5 2970 public function output_select_html($data, $current, $default, $extraname = '') {
220a90c5 2971 if (!$this->load_choices() or empty($this->choices)) {
e2249afe 2972 return array('', '');
6e4dc10f 2973 }
220a90c5 2974
9c305ba1 2975 $warning = '';
2976 if (is_null($current)) {
9baf6825 2977 // first run
9c305ba1 2978 } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2979 // no warning
9baf6825 2980 } else if (!array_key_exists($current, $this->choices)) {
2981 $warning = get_string('warningcurrentsetting', 'admin', s($current));
2982 if (!is_null($default) and $data == $current) {
2983 $data = $default; // use default instead of first value when showing the form
2984 }
2985 }
9c305ba1 2986
e2249afe 2987 $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
6e4dc10f 2988 foreach ($this->choices as $key => $value) {
9baf6825 2989 // the string cast is needed because key may be integer - 0 is equal to most strings!
e2249afe 2990 $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
eef868d1 2991 }
e2249afe 2992 $selecthtml .= '</select>';
2993 return array($selecthtml, $warning);
2994 }
2995
db26acd4 2996 /**
2997 * Returns XHTML select field and wrapping div(s)
2998 *
2999 * @see output_select_html()
0c079f19 3000 *
db26acd4 3001 * @param string $data the option to show as selected
3002 * @param string $query
3003 * @return string XHTML field and wrapping div
3004 */
73fa96d5 3005 public function output_html($data, $query='') {
e2249afe 3006 $default = $this->get_defaultsetting();
3007 $current = $this->get_setting();
3008
3009 list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
3010 if (!$selecthtml) {
3011 return '';
3012 }
3013
3014 if (!is_null($default) and array_key_exists($default, $this->choices)) {
3015 $defaultinfo = $this->choices[$default];
3016 } else {
3017 $defaultinfo = NULL;
3018 }
3019
3020 $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
220a90c5 3021
587c7040 3022 return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
6e4dc10f 3023 }
6e4dc10f 3024}
3025
3e7069e7 3026
220a90c5 3027/**
3028 * Select multiple items from list
db26acd4 3029 *
3030 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 3031 */
6e4dc10f 3032class admin_setting_configmultiselect extends admin_setting_configselect {
3e7069e7
PS
3033 /**
3034 * Constructor
3035 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
3036 * @param string $visiblename localised
3037 * @param string $description long localised info
3038 * @param array $defaultsetting array of selected items
3039 * @param array $choices array of $value=>$label for each list item
3040 */
73fa96d5 3041 public function __construct($name, $visiblename, $description, $defaultsetting, $choices) {
3042 parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
6e4dc10f 3043 }
3044
db26acd4 3045 /**
3046 * Returns the select setting(s)
3047 *
3048 * @return mixed null or array. Null if no settings else array of setting(s)
3049 */
73fa96d5 3050 public function get_setting() {
220a90c5 3051 $result = $this->config_read($this->name);
3052 if (is_null($result)) {
d7933a55 3053 return NULL;
3054 }
220a90c5 3055 if ($result === '') {
3056 return array();
3057 }
3058 return explode(',', $result);
6e4dc10f 3059 }
eef868d1 3060
db26acd4 3061 /**
3062 * Saves setting(s) provided through $data
3063 *
3064 * Potential bug in the works should anyone call with this function
3065 * using a vartype that is not an array
3066 *
db26acd4 3067 * @param array $data
3068 */
73fa96d5 3069 public function write_setting($data) {
220a90c5 3070 if (!is_array($data)) {
3071 return ''; //ignore it
3072 }
3073 if (!$this->load_choices() or empty($this->choices)) {
3074 return '';
3075 }
3076
a7ad48fd 3077 unset($data['xxxxx']);
3078
220a90c5 3079 $save = array();
3080 foreach ($data as $value) {
3081 if (!array_key_exists($value, $this->choices)) {
3082 continue; // ignore it
3083 }
3084 $save[] = $value;
3085 }
3086
3087 return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
3088 }
3089
3090 /**
3091 * Is setting related to query text - used when searching
db26acd4 3092 *
220a90c5 3093 * @param string $query
db26acd4 3094 * @return bool true if related, false if not
220a90c5 3095 */
73fa96d5 3096 public function is_related($query) {
220a90c5 3097 if (!$this->load_choices() or empty($this->choices)) {
3098 return false;
3099 }
3100 if (parent::is_related($query)) {
3101 return true;
3102 }
3103
220a90c5 3104 foreach ($this->choices as $desc) {
2f1e464a 3105 if (strpos(core_text::strtolower($desc), $query) !== false) {
220a90c5 3106 return true;
3107 }
3108 }
3109 return false;
3110 }
3111
db26acd4 3112 /**
3113 * Returns XHTML multi-select field
3114 *
3115 * @todo Add vartype handling to ensure $data is an array
3116 * @param array $data Array of values to select by default
3117 * @param string $query
3118 * @return string XHTML multi-select field
3119 */
73fa96d5 3120 public function output_html($data, $query='') {
220a90c5 3121 if (!$this->load_choices() or empty($this->choices)) {
3122 return '';
3123 }
3124 $choices = $this->choices;
3125 $default = $this->get_defaultsetting();
3126 if (is_null($default)) {
3127 $default = array();
3128 }
3129 if (is_null($data)) {
d7933a55 3130 $data = array();
3131 }
220a90c5 3132
3133 $defaults = array();
4413941f 3134 $size = min(10, count($this->choices));
a7ad48fd 3135 $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 3136 $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
220a90c5 3137 foreach ($this->choices as $key => $description) {
3138 if (in_array($key, $data)) {
3139 $selected = 'selected="selected"';
3140 } else {
3141 $selected = '';
3142 }
3143 if (in_array($key, $default)) {
3144 $defaults[] = $description;
6e4dc10f 3145 }
220a90c5 3146
3147 $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
3148 }
3149
587c7040 3150 if (is_null($default)) {
3151 $defaultinfo = NULL;
3152 } if (!empty($defaults)) {
3153 $defaultinfo = implode(', ', $defaults);
220a90c5 3154 } else {
587c7040 3155 $defaultinfo = get_string('none');
6e4dc10f 3156 }
eef868d1 3157
587c7040 3158 $return .= '</select></div>';
3159 return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
6e4dc10f 3160 }
220a90c5 3161}
eef868d1 3162
220a90c5 3163/**
3164 * Time selector
db26acd4 3165 *
3166 * This is a liiitle bit messy. we're using two selects, but we're returning
220a90c5 3167 * them as an array named after $name (so we only use $name2 internally for the setting)
db26acd4 3168 *
3169 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
220a90c5 3170 */
3171class admin_setting_configtime extends admin_setting {
3e7069e7 3172 /** @var string Used for setting second select (minutes) */
73fa96d5 3173 public $name2;
220a90c5 3174
3175 /**
3176 * Constructor
3177 * @param string $hoursname setting for hours
3178 * @param string $minutesname setting for hours
3179 * @param string $visiblename localised
3180 * @param string $description long localised info
3181 * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
3182 */
73fa96d5 3183 public function __construct($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
220a90c5 3184 $this->name2 = $minutesname;
73fa96d5 3185 parent::__construct($hoursname, $visiblename, $description, $defaultsetting);
220a90c5 3186 }
3187
db26acd4 3188 /**
3189 * Get the selected time
0c079f19 3190 *
db26acd4 3191 * @return mixed An array containing 'h'=>xx, 'm'=>xx, or null if not set
3192 */
73fa96d5 3193 public function get_setting() {
220a90c5 3194 $result1 = $this->config_read($this->name);
3195 $result2 = $this->config_read($this->name2);
3196 if (is_null($result1) or is_null($result2)) {
3197 return NULL;
3198 }
3199
3200 return array('h' => $result1, 'm' => $result2);
3201 }
3202
db26acd4 3203 /**
3204 * Store the time (hours and minutes)
0c079f19 3205 *
db26acd4 3206 * @param array $data Must be form 'h'=>xx, 'm'=>xx
3207 * @return bool true if success, false if not
3208 */
73fa96d5 3209 public function write_setting($data) {
220a90c5 3210 if (!is_array($data)) {
3211 return '';
3212 }
3213
3214 $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
3215 return ($result ? '' : get_string('errorsetting', 'admin'));
3216 }
3217
db26acd4 3218 /**
3219 * Returns XHTML time select fields
3220 *
3221 * @param array $data Must be form 'h'=>xx, 'm'=>xx
3222 * @param string $query
3223 * @return string XHTML time select fields and wrapping div(s)
3224 */
73fa96d5 3225 public function output_html($data, $query='') {
220a90c5 3226 $default = $this->get_defaultsetting();
3227
3228 if (is_array($default)) {
587c7040 3229 $defaultinfo = $default['h'].':'.$default['m'];
cc73de71 3230 } else {
587c7040 3231 $defaultinfo = NULL;
6e4dc10f 3232 }
220a90c5 3233
c6bcbad0
AN
3234 $return = '<div class="form-time defaultsnext">';
3235 $return .= '<label class="accesshide" for="' . $this->get_id() . 'h">' . get_string('hours') . '</label>';
3236 $return .= '<select id="' . $this->get_id() . 'h" name="' . $this->get_full_name() . '[h]">';
220a90c5 3237 for ($i = 0; $i < 24; $i++) {
c6bcbad0 3238 $return .= '<option value="' . $i . '"' . ($i == $data['h'] ? ' selected="selected"' : '') . '>' . $i . '</option>';
6e4dc10f 3239 }
c6bcbad0
AN
3240 $return .= '</select>:';
3241 $return .= '<label class="accesshide" for="' . $this->get_id() . 'm">' . get_string('minutes') . '</label>';
3242 $return .= '<select id="' . $this->get_id() . 'm" name="' . $this->get_full_name() . '[m]">';
220a90c5 3243 for ($i = 0; $i < 60; $i += 5) {
c6bcbad0 3244 $return .= '<option value="' . $i . '"' . ($i == $data['m'] ? ' selected="selected"' : '') . '>' . $i . '</option>';
220a90c5 3245 }
c6bcbad0
AN
3246 $return .= '</select>';
3247 $return .= '</div>';
e323bf51
BB
3248 return format_admin_setting($this, $this->visiblename, $return, $this->description,
3249 $this->get_id() . 'h', '', $defaultinfo, $query);
6e4dc10f 3250 }
3251
3252}
3253
3e7069e7 3254
38257347
PS
3255/**
3256 * Seconds duration setting.
3257 *
3258 * @copyright 2012 Petr Skoda (http://skodak.org)
3259 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3260 */
3261class admin_setting_configduration extends admin_setting {
3262
3263 /** @var int default duration unit */
3264 protected $defaultunit;
3265
3266 /**
3267 * Constructor
3268 * @param string $name unique ascii name, either 'mysetting' for settings that in config,
3269 * or 'myplugin/mysetting' for ones in config_plugins.
3270 * @param string $visiblename localised name
3271 * @param string $description localised long description
3272 * @param mixed $defaultsetting string or array depending on implementation
3273 * @param int $defaultunit - day, week, etc. (in seconds)
3274 */
3275 public function __construct($name, $visiblename, $description, $defaultsetting, $defaultunit = 86400) {
3276 if (is_number($defaultsetting)) {
3277 $defaultsetting = self::parse_seconds($defaultsetting);
3278 }
3279 $units = self::get_units();
3280 if (isset($units[$defaultunit])) {
3281 $this->defaultunit = $defaultunit;
3282 } else {
3283 $this->defaultunit = 86400;
3284 }
3285 parent::__construct($name, $visiblename, $description, $defaultsetting);
3286 }
3287
3288 /**
3289 * Returns selectable units.
3290 * @static
3291 * @return array
3292 */
3293 protected static function get_units() {
3294 return array(
3295 604800 => get_string('weeks'),
3296 86400 => get_string('days'),
3297 3600 => get_string('hours'),
3298 60 => get_string('minutes'),
3299 1 => get_string('seconds'),
3300 );
3301 }
3302
3303 /**
3304 * Converts seconds to some more user friendly string.
3305 * @static
3306 * @param int $seconds
3307 * @return string
3308 */
3309 protected static function get_duration_text($seconds) {
3310 if (empty($seconds)) {
3311 return get_string('none');
3312 }
3313 $data = self::parse_seconds($seconds);
3314 switch ($data['u']) {
444af7c6
EL
3315 case (60*60*24*7):
3316 return get_string('numweeks', '', $data['v']);
3317 case (60*60*24):
3318 return get_string('numdays', '', $data['v']);
3319 case (60*60):
3320 return get_string('numhours', '', $data['v']);
3321 case (60):
3322 return get_string('numminutes', '', $data['v']);
3323 default:
3324 return get_string('numseconds', '', $data['v']*$data['u']);
38257347
PS
3325 }
3326 }
3327
3328 /**
3329 * Finds suitable units for given duration.
3330 * @static
3331 * @param int $seconds
3332 * @return array
3333 */
3334 protected static function parse_seconds($seconds) {
444af7c6 3335 foreach (self::get_units() as $unit => $unused) {
38257347
PS
3336 if ($seconds % $unit === 0) {
3337 return array('v'=>(int)($seconds/$unit), 'u'=>$unit);
3338 }
3339 }
3340 return array('v'=>(int)$seconds, 'u'=>1);
3341 }
3342
3343 /**
3344 * Get the selected duration as array.
3345 *
3346 * @return mixed An array containing 'v'=>xx, 'u'=>xx, or null if not set
3347 */
3348 public function get_setting() {
3349