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