MDL-17847 moev backup_config to config_plugin table
[moodle.git] / lib / adminlib.php
CommitLineData
a4e10845 1<?php
88a7228a 2
3/**
4 * adminlib.php - Contains functions that only administrators will ever need to use
5 *
a4e10845 6 * @author Martin Dougiamas and many others
88a7228a 7 * @version $Id$
8 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
9 * @package moodlecore
10 */
11
598b38f7 12/// Add libraries
13require_once($CFG->libdir.'/ddllib.php');
b1f93b15 14require_once($CFG->libdir.'/xmlize.php');
598b38f7 15require_once($CFG->libdir.'/messagelib.php'); // Messagelib functions
b1f93b15 16
bba0beae 17define('INSECURE_DATAROOT_WARNING', 1);
18define('INSECURE_DATAROOT_ERROR', 2);
775f811a 19
20/**
21 * Upgrade savepoint, marks end of each upgrade block.
22 * It stores new main version, resets upgrade timeout
23 * and abort upgrade if user cancels page loading.
24 *
25 * Please do not make large upgrade blocks with lots of operations,
26 * for example when adding tables keep only one table operation per block.
27 *
28 * @param bool $result false if upgrade step failed, true if completed
29 * @param string or float $version main version
30 * @return void
31 */
8ddcdd86 32function upgrade_main_savepoint($result, $version) {
33 global $CFG;
34
35 if ($result) {
36 if ($CFG->version >= $version) {
37 // something really wrong is going on in main upgrade script
775f811a 38 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$CFG->version, 'newversion'=>$version));
8ddcdd86 39 }
40 set_config('version', $version);
41 } else {
42 notify ("Upgrade savepoint: Error during main upgrade to version $version");
43 }
775f811a 44
45 // reset upgrade timeout to default
46 upgrade_set_timeout();
47
48 // this is a safe place to stop upgrades if user aborts page loading
49 if (connection_aborted()) {
50 die;
51 }
8ddcdd86 52}
53
775f811a 54/**
55 * Module upgrade savepoint, marks end of module upgrade blocks
56 * It stores module version, resets upgrade timeout
57 * and abort upgrade if usercancels page loading.
58 *
59 * @param bool $result false if upgrade step failed, true if completed
60 * @param string or float $version main version
61 * @return void
62 */
04264aed 63function upgrade_mod_savepoint($result, $version, $modname) {
64 global $DB;
65
66 if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
67 print_error('modulenotexist', 'debug', '', $modname);
68 }
69
70 if ($result) {
71 if ($module->version >= $version) {
72 // something really wrong is going on in upgrade script
775f811a 73 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$module->version, 'newversion'=>$version));
04264aed 74 }
91b59e05 75 $module->version = $version;
04264aed 76 $DB->update_record('modules', $module);
77 } else {
78 notify ("Upgrade savepoint: Error during mod upgrade to version $version");
79 }
775f811a 80
81 // reset upgrade timeout to default
82 upgrade_set_timeout();
83
84 // this is a safe place to stop upgrades if user aborts page loading
85 if (connection_aborted()) {
86 die;
87 }
88}
89
90function upgrade_blocks_savepoint($result, $version, $blockname) {
91 global $DB;
92
93 if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
94 print_error('blocknotexist', 'debug', '', $blockname);
95 }
96
97 if ($result) {
98 if ($block->version >= $version) {
99 // something really wrong is going on in upgrade script
100 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$block->version, 'newversion'=>$version));
101 }
91b59e05 102 $block->version = $version;
775f811a 103 $DB->update_record('block', $block);
104 } else {
105 notify ("Upgrade savepoint: Error during mod upgrade to version $version");
106 }
107
108 // reset upgrade timeout to default
109 upgrade_set_timeout();
110
111 // this is a safe place to stop upgrades if user aborts page loading
112 if (connection_aborted()) {
113 die;
114 }
8ddcdd86 115}
116
117function upgrade_plugin_savepoint($result, $version, $type, $dir) {
d849880e 118 if ($result) {
119 $fullname = $type . '_' . $dir;
120 $installedversion = get_config($fullname, 'version');
121 if ($installedversion >= $version) {
122 // Something really wrong is going on in the upgrade script
123 $a = new stdClass;
124 $a->oldversion = $installedversion;
125 $a->newversion = $version;
126 print_error('cannotdowngrade', 'debug', '', $a);
127 }
128 set_config('version', $version, $fullname);
129 } else {
130 notify ("Upgrade savepoint: Error during mod upgrade to version $version");
131 }
132
133 // Reset upgrade timeout to default
134 upgrade_set_timeout();
135
136 // This is a safe place to stop upgrades if user aborts page loading
137 if (connection_aborted()) {
138 die;
139 }
8ddcdd86 140}
141
b1f93b15 142/**
143 * Delete all plugin tables
144 * @name string name of plugin, used as table prefix
145 * @file string path to install.xml file
146 * @feedback boolean
147 */
148function drop_plugin_tables($name, $file, $feedback=true) {
149 global $CFG, $DB;
150
151 // first try normal delete
8b4ca8f7 152 if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
b1f93b15 153 return true;
154 }
155
156 // then try to find all tables that start with name and are not in any xml file
157 $used_tables = get_used_table_names();
158
159 $tables = $DB->get_tables();
160
161 /// Iterate over, fixing id fields as necessary
162 foreach ($tables as $table) {
163 if (in_array($table, $used_tables)) {
164 continue;
165 }
166
8b4ca8f7 167 if (strpos($table, $name) !== 0) {
168 continue;
169 }
170
b1f93b15 171 // found orphan table --> delete it
172 if ($DB->get_manager()->table_exists($table)) {
173 $xmldb_table = new xmldb_table($table);
eee5d9bb 174 $DB->get_manager()->drop_table($xmldb_table);
b1f93b15 175 }
176 }
177
178 return true;
179}
180
181/**
182 * Returns names of all known tables == tables that moodle knowns about.
183 * @return array of lowercase table names
184 */
185function get_used_table_names() {
186 $table_names = array();
187 $dbdirs = get_db_directories();
188
189 foreach ($dbdirs as $dbdir) {
190 $file = $dbdir.'/install.xml';
191
192 $xmldb_file = new xmldb_file($file);
193
194 if (!$xmldb_file->fileExists()) {
195 continue;
196 }
197
198 $loaded = $xmldb_file->loadXMLStructure();
199 $structure =& $xmldb_file->getStructure();
200
201 if ($loaded and $tables = $structure->getTables()) {
202 foreach($tables as $table) {
203 $table_names[] = strtolower($table->name);
204 }
205 }
206 }
207
208 return $table_names;
209}
210
704f4cdb 211/**
212 * Lists all plugin types
213 * @return array of strings - name=>location
214 */
215function get_plugin_types() {
216 global $CFG;
217
218 return array('mod' => 'mod',
219 'qtype' => 'question/type',
220 'blocks' => 'blocks',
221 'auth' => 'auth',
222 'enrol' => 'enrol',
223 'format' => 'course/format',
224 'gradeexport' => 'grade/export',
225 'gradeimport' => 'grade/import',
226 'gradereport' => 'grade/report',
227 'message' => 'message/output',
228 'coursereport' => 'course/report',
229 'report' => $CFG->admin.'/report',
230 'quizreport' => 'mod/quiz/report',
231 'portfolio' => 'portfolio/type',
232 'trackerexport' => 'tracker/export',
233 'trackerimport' => 'tracker/import',
234 'trackerreport' => 'tracker/report',
235 );
236}
237
b1f93b15 238/**
239 * Returns list of all directories where we expect install.xml files
240 * @return array of paths
241 */
242function get_db_directories() {
243 global $CFG;
244
245 $dbdirs = array();
246
247/// First, the main one (lib/db)
248 $dbdirs[] = $CFG->libdir.'/db';
249
250/// Now, activity modules (mod/xxx/db)
251 if ($plugins = get_list_of_plugins('mod')) {
252 foreach ($plugins as $plugin) {
253 $dbdirs[] = $CFG->dirroot.'/mod/'.$plugin.'/db';
254 }
255 }
256
257/// Now, assignment submodules (mod/assignment/type/xxx/db)
258 if ($plugins = get_list_of_plugins('mod/assignment/type')) {
259 foreach ($plugins as $plugin) {
260 $dbdirs[] = $CFG->dirroot.'/mod/assignment/type/'.$plugin.'/db';
261 }
262 }
263
264/// Now, question types (question/type/xxx/db)
265 if ($plugins = get_list_of_plugins('question/type')) {
266 foreach ($plugins as $plugin) {
267 $dbdirs[] = $CFG->dirroot.'/question/type/'.$plugin.'/db';
268 }
269 }
270
b1f93b15 271/// Now, blocks (blocks/xxx/db)
272 if ($plugins = get_list_of_plugins('blocks', 'db')) {
273 foreach ($plugins as $plugin) {
274 $dbdirs[] = $CFG->dirroot.'/blocks/'.$plugin.'/db';
275 }
276 }
277
278/// Now, course formats (course/format/xxx/db)
279 if ($plugins = get_list_of_plugins('course/format', 'db')) {
280 foreach ($plugins as $plugin) {
281 $dbdirs[] = $CFG->dirroot.'/course/format/'.$plugin.'/db';
282 }
283 }
284
285/// Now, enrolment plugins (enrol/xxx/db)
286 if ($plugins = get_list_of_plugins('enrol', 'db')) {
287 foreach ($plugins as $plugin) {
288 $dbdirs[] = $CFG->dirroot.'/enrol/'.$plugin.'/db';
289 }
290 }
291
292/// Now admin report plugins (admin/report/xxx/db)
293 if ($plugins = get_list_of_plugins($CFG->admin.'/report', 'db')) {
294 foreach ($plugins as $plugin) {
295 $dbdirs[] = $CFG->dirroot.'/'.$CFG->admin.'/report/'.$plugin.'/db';
296 }
297 }
c5d2d0dd 298
7cdd8b22 299/// Now quiz report plugins (mod/quiz/report/xxx/db)
300 if ($plugins = get_list_of_plugins('mod/quiz/report', 'db')) {
301 foreach ($plugins as $plugin) {
302 $dbdirs[] = $CFG->dirroot.'/mod/quiz/report/'.$plugin.'/db';
303 }
b91b274b 304 }
305
306 if ($plugins = get_list_of_plugins('portfolio/type', 'db')) {
307 foreach ($plugins as $plugin) {
308 $dbdirs[] = $CFG->dirroot . '/portfolio/type/' . $plugin . '/db';
309 }
7cdd8b22 310 }
b1f93b15 311
312/// Local database changes, if the local folder exists.
313 if (file_exists($CFG->dirroot . '/local')) {
314 $dbdirs[] = $CFG->dirroot.'/local/db';
315 }
316
317 return $dbdirs;
318}
319
704f4cdb 320function print_upgrade_header() {
321 if (defined('HEADER_PRINTED')) {
322 return;
323 }
324
325 $strpluginsetup = get_string('pluginsetup');
326
327 print_header($strpluginsetup, $strpluginsetup,
328 build_navigation(array(array('name' => $strpluginsetup, 'link' => null, 'type' => 'misc'))), '',
329 upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
330}
331
88a7228a 332/**
ead29342 333 * Upgrade plugins
88a7228a 334 *
88a7228a 335 * @uses $CFG
ead29342 336 * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
337 * @param string $dir The directory where the plugins are located (e.g. 'question/questiontypes')
338 * @param string $return The url to prompt the user to continue to
eef868d1 339 */
704f4cdb 340function upgrade_plugins($type, $dir) {
f33e1ed4 341 global $CFG, $interactive, $DB;
173cc1c3 342
704f4cdb 343/// special cases
344 if ($type === 'mod') {
345 return upgrade_activity_modules();
346 } else if ($type === 'blocks') {
347 return upgrade_blocks_plugins();
348 }
15999a93 349
bb7053f3 350 $plugs = get_list_of_plugins($dir);
583fad99 351 $updated_plugins = false;
352 $strpluginsetup = get_string('pluginsetup');
353
ead29342 354 foreach ($plugs as $plug) {
173cc1c3 355
ead29342 356 $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug;
173cc1c3 357
ead29342 358 unset($plugin);
173cc1c3 359
bbbf2d40 360 if (is_readable($fullplug .'/version.php')) {
ead29342 361 include_once($fullplug .'/version.php'); // defines $plugin with version etc
173cc1c3 362 } else {
363 continue; // Nothing to do.
364 }
365
e79a09a2 366 $newupgrade = false;
db8bd7a6 367 if (is_readable($fullplug . '/db/upgrade.php')) {
7c006e34 368 include_once($fullplug . '/db/upgrade.php'); // defines new upgrading function
e79a09a2 369 $newupgrade = true;
370 }
371
ead29342 372 if (!isset($plugin)) {
173cc1c3 373 continue;
374 }
375
ead29342 376 if (!empty($plugin->requires)) {
377 if ($plugin->requires > $CFG->version) {
acdd790f 378 $info = new object();
ead29342 379 $info->pluginname = $plug;
380 $info->pluginversion = $plugin->version;
173cc1c3 381 $info->currentmoodle = $CFG->version;
ead29342 382 $info->requiremoodle = $plugin->requires;
704f4cdb 383 print_upgrade_header();
583fad99 384 upgrade_log_start();
ead29342 385 notify(get_string('pluginrequirementsnotmet', 'error', $info));
583fad99 386 $updated_plugins = true;
173cc1c3 387 continue;
388 }
389 }
390
ead29342 391 $plugin->name = $plug; // The name MUST match the directory
d849880e 392 $plugin->fullname = $type.'_'.$plug; // The name MUST match the directory
173cc1c3 393
d849880e 394 $installedversion = get_config($plugin->fullname, 'version');
173cc1c3 395
d849880e 396 if ($installedversion === false) {
397 set_config('version', 0, $plugin->fullname);
173cc1c3 398 }
eef868d1 399
d849880e 400 if ($installedversion == $plugin->version) {
173cc1c3 401 // do nothing
d849880e 402 } else if ($installedversion < $plugin->version) {
704f4cdb 403 print_upgrade_header();
e79a09a2 404 $updated_plugins = true;
583fad99 405 upgrade_log_start();
05b42119 406 print_heading($dir.'/'. $plugin->name .' plugin needs upgrading');
2f13f94c 407 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
f33e1ed4 408 $DB->set_debug(true);
2f13f94c 409 }
e79a09a2 410 @set_time_limit(0); // To allow slow databases to complete the long SQL
411
d849880e 412 if ($installedversion == 0) { // It's a new install of this plugin
e79a09a2 413 /// Both old .sql files and new install.xml are supported
414 /// but we priorize install.xml (XMLDB) if present
db8bd7a6 415 if (file_exists($fullplug . '/db/install.xml')) {
eee5d9bb 416 $DB->get_manager()->install_from_xmldb_file($fullplug . '/db/install.xml'); //New method
d87a9d73 417 }
eee5d9bb 418 $status = true;
2f13f94c 419 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
f33e1ed4 420 $DB->set_debug(false);
2f13f94c 421 }
eef868d1 422 /// Continue with the instalation, roles and other stuff
e79a09a2 423 if ($status) {
3f59a54e 424 /// OK so far, now update the plugins record
d849880e 425 set_config('version', $plugin->version, $plugin->fullname);
3f59a54e 426
427 /// Install capabilities
ae628043 428 if (!update_capabilities($type.'/'.$plug)) {
2f137aa1 429 print_error('cannotsetupcapforplugin', '', '', $plugin->name);
e79a09a2 430 }
3f59a54e 431 /// Install events
0856223c 432 events_update_definition($type.'/'.$plug);
3f59a54e 433
120b3758 434 /// Install message providers
435 message_update_providers($type.'/'.$plug);
436
3f59a54e 437 /// Run local install function if there is one
d849880e 438 if (is_readable($fullplug . '/lib.php')) {
439 include_once($fullplug . '/lib.php');
3f59a54e 440 $installfunction = $plugin->name.'_install';
441 if (function_exists($installfunction)) {
442 if (! $installfunction() ) {
443 notify('Encountered a problem running install function for '.$module->name.'!');
444 }
445 }
446 }
9ba38673 447
e79a09a2 448 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
449 } else {
450 notify('Installing '. $plugin->name .' FAILED!');
451 }
d87a9d73 452 } else { // Upgrade existing install
d849880e 453 /// Run the upgrade function for the plugin.
454 $newupgrade_function = 'xmldb_' .$plugin->fullname .'_upgrade';
e79a09a2 455 $newupgrade_status = true;
47c53a54 456 if ($newupgrade && function_exists($newupgrade_function)) {
2f13f94c 457 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
f33e1ed4 458 $DB->set_debug(true);
2f13f94c 459 }
d849880e 460 $newupgrade_status = $newupgrade_function($installedversion);
e79a09a2 461 } else if ($newupgrade) {
462 notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
463 $fullplug . '/db/upgrade.php');
464 }
2f13f94c 465 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
f33e1ed4 466 $DB->set_debug(false);
2f13f94c 467 }
e79a09a2 468 /// Now analyze upgrade results
47c53a54 469 if ($newupgrade_status) { // No upgrading failed
120b3758 470 /// OK so far, now update the plugins record
d849880e 471 set_config('version', $plugin->version, $plugin->fullname);
ae628043 472 if (!update_capabilities($type.'/'.$plug)) {
2f137aa1 473 print_error('cannotupdateplugincap', '', '', $plugin->name);
d87a9d73 474 }
120b3758 475 /// Update events
0856223c 476 events_update_definition($type.'/'.$plug);
120b3758 477
478 /// Update message providers
479 message_update_providers($type.'/'.$plug);
480
e79a09a2 481 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
482 } else {
d849880e 483 notify('Upgrading '. $plugin->name .' from '. $installedversion .' to '. $plugin->version .' FAILED!');
173cc1c3 484 }
485 }
2f13f94c 486 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
487 echo '<hr />';
488 }
173cc1c3 489 } else {
d849880e 490 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$installedversion, 'newversion'=>$plugin->version));
173cc1c3 491 }
492 }
493
704f4cdb 494 return $updated_plugins;
173cc1c3 495}
496
88a7228a 497/**
498 * Find and check all modules and load them up or upgrade them if necessary
499 *
88a7228a 500 * @uses $CFG
501 * @param string $return The url to prompt the user to continue to
502 * @todo Finish documenting this function
eef868d1 503 */
704f4cdb 504function upgrade_activity_modules() {
173cc1c3 505
c7d306e1 506 global $CFG, $interactive, $DB, $unittest;
173cc1c3 507
88a7228a 508 if (!$mods = get_list_of_plugins('mod') ) {
2f137aa1 509 print_error('nomodules', 'debug');
173cc1c3 510 }
511
583fad99 512 $updated_modules = false;
513 $strmodulesetup = get_string('modulesetup');
514
173cc1c3 515 foreach ($mods as $mod) {
516
88a7228a 517 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it
173cc1c3 518 continue;
519 }
520
88a7228a 521 $fullmod = $CFG->dirroot .'/mod/'. $mod;
173cc1c3 522
523 unset($module);
524
88a7228a 525 if ( is_readable($fullmod .'/version.php')) {
526 include_once($fullmod .'/version.php'); // defines $module with version etc
173cc1c3 527 } else {
88a7228a 528 notify('Module '. $mod .': '. $fullmod .'/version.php was not readable');
173cc1c3 529 continue;
530 }
531
d6eb06b6 532 $newupgrade = false;
db8bd7a6 533 if ( is_readable($fullmod . '/db/upgrade.php')) {
7c006e34 534 include_once($fullmod . '/db/upgrade.php'); // defines new upgrading function
d6eb06b6 535 $newupgrade = true;
173cc1c3 536 }
537
538 if (!isset($module)) {
539 continue;
540 }
541
542 if (!empty($module->requires)) {
543 if ($module->requires > $CFG->version) {
acdd790f 544 $info = new object();
173cc1c3 545 $info->modulename = $mod;
546 $info->moduleversion = $module->version;
547 $info->currentmoodle = $CFG->version;
548 $info->requiremoodle = $module->requires;
704f4cdb 549 print_upgrade_header();
583fad99 550 upgrade_log_start();
173cc1c3 551 notify(get_string('modulerequirementsnotmet', 'error', $info));
583fad99 552 $updated_modules = true;
173cc1c3 553 continue;
554 }
555 }
556
557 $module->name = $mod; // The name MUST match the directory
eef868d1 558
11e4a24b 559 include_once($fullmod.'/lib.php'); // defines upgrading and/or installing functions
560
f33e1ed4 561 if ($currmodule = $DB->get_record('modules', array('name'=>$module->name))) {
173cc1c3 562 if ($currmodule->version == $module->version) {
563 // do nothing
564 } else if ($currmodule->version < $module->version) {
d6eb06b6 565 /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
47c53a54 566 if (!$newupgrade) {
567 notify('Upgrade file ' . $mod . ': ' . $fullmod . '/db/upgrade.php is not readable');
d6eb06b6 568 continue;
569 }
704f4cdb 570 print_upgrade_header();
583fad99 571 upgrade_log_start();
2f13f94c 572
88a7228a 573 print_heading($module->name .' module needs upgrading');
d6eb06b6 574
575 /// Run de old and new upgrade functions for the module
d6eb06b6 576 $newupgrade_function = 'xmldb_' . $module->name . '_upgrade';
577
d6eb06b6 578 /// Then, the new function if exists and the old one was ok
579 $newupgrade_status = true;
47c53a54 580 if ($newupgrade && function_exists($newupgrade_function)) {
2f13f94c 581 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
f33e1ed4 582 $DB->set_debug(true);
2f13f94c 583 }
d6eb06b6 584 $newupgrade_status = $newupgrade_function($currmodule->version, $module);
47c53a54 585 } else if ($newupgrade) {
d6eb06b6 586 notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
587 $mod . ': ' . $fullmod . '/db/upgrade.php');
d6eb06b6 588 }
2f13f94c 589 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
f33e1ed4 590 $DB->set_debug(false);
2f13f94c 591 }
d6eb06b6 592 /// Now analyze upgrade results
47c53a54 593 if ($newupgrade_status) { // No upgrading failed
d6eb06b6 594 // OK so far, now update the modules record
595 $module->id = $currmodule->id;
f33e1ed4 596 if (!$DB->update_record('modules', $module)) {
2f137aa1 597 print_error('cannotupdatemod', '', '', $module->name);
173cc1c3 598 }
d6eb06b6 599 remove_dir($CFG->dataroot . '/cache', true); // flush cache
600 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
2f13f94c 601 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE) {
775f811a 602 echo '<hr />';
2f13f94c 603 }
d6eb06b6 604 } else {
d6eb06b6 605 notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!');
173cc1c3 606 }
bbbf2d40 607
d6eb06b6 608 /// Update the capabilities table?
bbbf2d40 609 if (!update_capabilities('mod/'.$module->name)) {
2f137aa1 610 print_error('cannotupdatemodcap', '', '', $module->name);
bbbf2d40 611 }
120b3758 612
613 /// Update events
0856223c 614 events_update_definition('mod/'.$module->name);
bbbf2d40 615
120b3758 616 /// Update message providers
617 message_update_providers('mod/'.$module->name);
618
173cc1c3 619 $updated_modules = true;
eef868d1 620
173cc1c3 621 } else {
775f811a 622 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$currmodule->version, 'newversion'=>$module->version));
173cc1c3 623 }
eef868d1 624
173cc1c3 625 } else { // module not installed yet, so install it
704f4cdb 626 print_upgrade_header();
583fad99 627 upgrade_log_start();
173cc1c3 628 print_heading($module->name);
629 $updated_modules = true;
2f13f94c 630 // To avoid unnecessary output from the SQL queries in the CLI version
631 if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
f33e1ed4 632 $DB->set_debug(true);
2f13f94c 633 }
173cc1c3 634 @set_time_limit(0); // To allow slow databases to complete the long SQL
d6eb06b6 635
636 /// Both old .sql files and new install.xml are supported
637 /// but we priorize install.xml (XMLDB) if present
db8bd7a6 638 if (file_exists($fullmod . '/db/install.xml')) {
eee5d9bb 639 $DB->get_manager()->install_from_xmldb_file($fullmod . '/db/install.xml'); //New method
640 $status = true;
d6eb06b6 641 }
2f13f94c 642 if (!defined('CLI_UPGRADE') || !CLI_UPGRADE ) {
f33e1ed4 643 $DB->set_debug(false);
2f13f94c 644 }
11e4a24b 645
646 /// Continue with the installation, roles and other stuff
d6eb06b6 647 if ($status) {
f33e1ed4 648 if ($module->id = $DB->insert_record('modules', $module)) {
11e4a24b 649
9ba38673 650 /// Capabilities
bbbf2d40 651 if (!update_capabilities('mod/'.$module->name)) {
2f137aa1 652 print_error('cannotsetupcapformod', '', '', $module->name);
bbbf2d40 653 }
9ba38673 654
11e4a24b 655 /// Events
0856223c 656 events_update_definition('mod/'.$module->name);
11e4a24b 657
120b3758 658 /// Message providers
659 message_update_providers('mod/'.$module->name);
660
11e4a24b 661 /// Run local install function if there is one
662 $installfunction = $module->name.'_install';
663 if (function_exists($installfunction)) {
664 if (! $installfunction() ) {
665 notify('Encountered a problem running install function for '.$module->name.'!');
666 }
667 }
668
a8f68426 669 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
2f13f94c 670 if (!defined('CLI_UPGRADE')|| !CLI_UPGRADE ) {
775f811a 671 echo '<hr />';
2f13f94c 672 }
173cc1c3 673 } else {
e6edb40e 674 print_error('cannotaddmodule', '', '', $module->name);
173cc1c3 675 }
eef868d1 676 } else {
e6edb40e 677 print_error('cannotsetuptable', 'debug', '', $module->name);
173cc1c3 678 }
679 }
e5bd4e58 680
681 /// Check submodules of this module if necessary
682
e5bd4e58 683 $submoduleupgrade = $module->name.'_upgrade_submodules';
684 if (function_exists($submoduleupgrade)) {
685 $submoduleupgrade();
686 }
687
e5bd4e58 688 /// Run any defaults or final code that is necessary for this module
689
a5c0990e 690 if ( is_readable($fullmod .'/defaults.php')) {
691 // Insert default values for any important configuration variables
9e6e7502 692 unset($defaults);
220a90c5 693 include($fullmod .'/defaults.php'); // include here means execute, not library include
f9a2e515 694 if (!empty($defaults)) {
220a5c87 695 if (!empty($defaults['_use_config_plugins'])) {
696 unset($defaults['_use_config_plugins']);
697 $localcfg = get_config($module->name);
698 foreach ($defaults as $name => $value) {
699 if (!isset($localcfg->$name)) {
700 set_config($name, $value, $module->name);
701 }
702 }
703 } else {
704 foreach ($defaults as $name => $value) {
705 if (!isset($CFG->$name)) {
706 set_config($name, $value);
707 }
f9a2e515 708 }
a5c0990e 709 }
710 }
711 }
173cc1c3 712 }
713
704f4cdb 714 return $updated_modules;
173cc1c3 715}
716
eef868d1 717/**
61460dd6 718 * Try to obtain or release the cron lock.
80be7ee3 719 *
61460dd6 720 * @param string $name name of lock
721 * @param int $until timestamp when this lock considered stale, null means remove lock unconditionaly
722 * @param bool $ignorecurrent ignore current lock state, usually entend previous lock
723 * @return bool true if lock obtained
f3221af9 724 */
61460dd6 725function set_cron_lock($name, $until, $ignorecurrent=false) {
f33e1ed4 726 global $DB;
f3221af9 727 if (empty($name)) {
61460dd6 728 debugging("Tried to get a cron lock for a null fieldname");
f3221af9 729 return false;
730 }
731
61460dd6 732 // remove lock by force == remove from config table
733 if (is_null($until)) {
734 set_config($name, null);
f3221af9 735 return true;
736 }
737
61460dd6 738 if (!$ignorecurrent) {
739 // read value from db - other processes might have changed it
f33e1ed4 740 $value = $DB->get_field('config', 'value', array('name'=>$name));
61460dd6 741
742 if ($value and $value > time()) {
743 //lock active
744 return false;
f3221af9 745 }
746 }
61460dd6 747
748 set_config($name, $until);
f3221af9 749 return true;
750}
a597f8a8 751
fb06b255 752function print_progress($done, $total, $updatetime=5, $sleeptime=1, $donetext='') {
67784b2f 753 static $thisbarid;
a597f8a8 754 static $starttime;
755 static $lasttime;
756
26ea4888 757 if ($total < 2) { // No need to show anything
758 return;
759 }
760
67784b2f 761 // Are we done?
762 if ($done >= $total) {
763 $done = $total;
764 if (!empty($thisbarid)) {
765 $donetext .= ' ('.$done.'/'.$total.') '.get_string('success');
766 print_progress_redraw($thisbarid, $done, $total, 500, $donetext);
767 $thisbarid = $starttime = $lasttime = NULL;
768 }
769 return;
770 }
771
a597f8a8 772 if (empty($starttime)) {
773 $starttime = $lasttime = time();
774 $lasttime = $starttime - $updatetime;
67784b2f 775 $thisbarid = uniqid();
a597f8a8 776 echo '<table width="500" cellpadding="0" cellspacing="0" align="center"><tr><td width="500">';
67784b2f 777 echo '<div id="bar'.$thisbarid.'" style="border-style:solid;border-width:1px;width:500px;height:50px;">';
778 echo '<div id="slider'.$thisbarid.'" style="border-style:solid;border-width:1px;height:48px;width:10px;background-color:green;"></div>';
a597f8a8 779 echo '</div>';
67784b2f 780 echo '<div id="text'.$thisbarid.'" align="center" style="width:500px;"></div>';
a597f8a8 781 echo '</td></tr></table>';
782 echo '</div>';
783 }
784
a597f8a8 785 $now = time();
786
787 if ($done && (($now - $lasttime) >= $updatetime)) {
788 $elapsedtime = $now - $starttime;
789 $projectedtime = (int)(((float)$total / (float)$done) * $elapsedtime) - $elapsedtime;
76317c73 790 $percentage = round((float)$done / (float)$total, 2);
a597f8a8 791 $width = (int)(500 * $percentage);
792
fb06b255 793 if ($projectedtime > 10) {
794 $projectedtext = ' Ending: '.format_time($projectedtime);
795 } else {
796 $projectedtext = '';
797 }
798
67784b2f 799 $donetext .= ' ('.$done.'/'.$total.') '.$projectedtext;
800 print_progress_redraw($thisbarid, $done, $total, $width, $donetext);
a597f8a8 801
802 $lasttime = $now;
a597f8a8 803 }
804}
583fad99 805
67784b2f 806// Don't call this function directly, it's called from print_progress.
807function print_progress_redraw($thisbarid, $done, $total, $width, $donetext='') {
808 if (empty($thisbarid)) {
809 return;
810 }
811 echo '<script>';
294ce987 812 echo 'document.getElementById("text'.$thisbarid.'").innerHTML = "'.addslashes_js($donetext).'";'."\n";
67784b2f 813 echo 'document.getElementById("slider'.$thisbarid.'").style.width = \''.$width.'px\';'."\n";
814 echo '</script>';
815}
816
371a32e3 817function upgrade_get_javascript() {
ac110efc 818 global $CFG;
371a32e3 819
ac110efc 820 return '<script type="text/javascript" src="'.$CFG->wwwroot.'/lib/scroll_to_errors.js"></script>';
371a32e3 821}
ad6226fb 822
2f13f94c 823function create_admin_user($user_input=NULL) {
db5af934 824 global $CFG, $DB;
ad6226fb 825
db5af934 826 $user = new object();
827 $user->auth = 'manual';
828 $user->firstname = get_string('admin');
829 $user->lastname = get_string('user');
830 $user->username = 'admin';
831 $user->password = hash_internal_user_password('admin');
832 $user->email = 'root@localhost';
833 $user->confirmed = 1;
834 $user->mnethostid = $CFG->mnet_localhost_id;
835 $user->lang = $CFG->lang;
836 $user->maildisplay = 1;
837 $user->timemodified = time();
ad6226fb 838
db5af934 839 if ($user_input) { // do we want to override any defaults?
840 foreach ($user_input as $key=>$value) {
841 $user->$key = $value;
842 }
843 }
844 $user->id = $DB->insert_record('user', $user);
775f811a 845
db5af934 846 if (!$user = $DB->get_record('user', array('id'=>$user->id))) { // Double check.
847 print_error('invaliduserid');
848 }
ad6226fb 849
db5af934 850 // Assign the default admin roles to the new user.
851 if (!$adminroles = get_roles_with_capability('moodle/legacy:admin', CAP_ALLOW)) {
852 print_error('noadminrole', 'message');
853 }
ad6226fb 854
db5af934 855 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
856 foreach ($adminroles as $adminrole) {
857 role_assign($adminrole->id, $user->id, 0, $systemcontext->id);
858 }
ad6226fb 859
db5af934 860 //set default message preferences
861 if (!message_set_default_message_preferences($user)){
862 print_error('cannotsavemessageprefs', 'message');
ad6226fb 863 }
db5af934 864
865 $user = get_complete_user_data('username', 'admin');
866
867 // indicate that this site is fully configured
868 set_config('rolesactive', 1);
869
870 return $user;
ad6226fb 871}
872
583fad99 873////////////////////////////////////////////////
874/// upgrade logging functions
875////////////////////////////////////////////////
876
583fad99 877/**
775f811a 878 * Marks start of upgrade, blocks any other access to site.
879 * The upgrade is finished at the end of script or after timeout.
583fad99 880 */
3e46c66a 881function upgrade_log_start() {
c7d306e1 882 global $CFG, $DB;
7cdd8b22 883
775f811a 884 static $started = false;
885
886 if ($started) {
887 upgrade_set_timeout(120);
888
889 } else {
890 ignore_user_abort(true);
891 register_shutdown_function('upgrade_finished_handler');
3e46c66a 892 set_config('upgraderunning', time()+300);
775f811a 893 $started = true;
583fad99 894 }
895}
896
775f811a 897/**
3e46c66a 898 * Internal function - executed if upgrade interruped.
775f811a 899 */
900function upgrade_finished_handler() {
901 upgrade_log_finish();
583fad99 902}
903
904/**
3e46c66a 905 * Indicates upgrade is finished.
583fad99 906 *
907 * This function may be called repeatedly.
908 */
909function upgrade_log_finish() {
3e46c66a 910 unset_config('upgraderunning');
911 ignore_user_abort(false);
583fad99 912}
913
bba0beae 914/**
915 * Test if and critical warnings are present
916 * @return bool
917 */
918function admin_critical_warnings_present() {
919 global $SESSION;
920
921 if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
922 return 0;
923 }
924
925 if (!isset($SESSION->admin_critical_warning)) {
926 $SESSION->admin_critical_warning = 0;
927 if (ini_get_bool('register_globals')) {
928 $SESSION->admin_critical_warning = 1;
929 } else if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
930 $SESSION->admin_critical_warning = 1;
931 }
932 }
933
934 return $SESSION->admin_critical_warning;
935}
936
57e35f32 937/**
938 * Try to verify that dataroot is not accessible from web.
939 * It is not 100% correct but might help to reduce number of vulnerable sites.
940 *
941 * Protection from httpd.conf and .htaccess is not detected properly.
bba0beae 942 * @param bool $fetchtest try to test public access by fetching file
943 * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING migth be problematic
57e35f32 944 */
bba0beae 945function is_dataroot_insecure($fetchtest=false) {
57e35f32 946 global $CFG;
947
948 $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
949
950 $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
951 $rp = strrev(trim($rp, '/'));
952 $rp = explode('/', $rp);
953 foreach($rp as $r) {
954 if (strpos($siteroot, '/'.$r.'/') === 0) {
955 $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
956 } else {
957 break; // probably alias root
958 }
959 }
960
961 $siteroot = strrev($siteroot);
962 $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
963
bba0beae 964 if (strpos($dataroot, $siteroot) !== 0) {
965 return false;
966 }
967
968 if (!$fetchtest) {
969 return INSECURE_DATAROOT_WARNING;
970 }
971
972 // now try all methods to fetch a test file using http protocol
973
974 $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
975 preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
976 $httpdocroot = $matches[1];
977 $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
978 if (make_upload_directory('diag', false) === false) {
979 return INSECURE_DATAROOT_WARNING;
980 }
981 $testfile = $CFG->dataroot.'/diag/public.txt';
982 if (!file_exists($testfile)) {
983 file_put_contents($testfile, 'test file, do not delete');
984 }
985 $teststr = trim(file_get_contents($testfile));
986 if (empty($teststr)) {
987 // hmm, strange
988 return INSECURE_DATAROOT_WARNING;
989 }
990
991 $testurl = $datarooturl.'/diag/public.txt';
041a4b0f 992 if (extension_loaded('curl') and
993 !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
994 !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
995 ($ch = @curl_init($testurl)) !== false) {
bba0beae 996 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
997 curl_setopt($ch, CURLOPT_HEADER, false);
998 $data = curl_exec($ch);
999 if (!curl_errno($ch)) {
1000 $data = trim($data);
1001 if ($data === $teststr) {
1002 curl_close($ch);
1003 return INSECURE_DATAROOT_ERROR;
1004 }
1005 }
1006 curl_close($ch);
1007 }
1008
1009 if ($data = @file_get_contents($testurl)) {
1010 $data = trim($data);
1011 if ($data === $teststr) {
1012 return INSECURE_DATAROOT_ERROR;
1013 }
1014 }
1015
1016 preg_match('|https?://([^/]+)|i', $testurl, $matches);
1017 $sitename = $matches[1];
1018 $error = 0;
1019 if ($fp = @fsockopen($sitename, 80, $error)) {
1020 preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
1021 $localurl = $matches[1];
1022 $out = "GET $localurl HTTP/1.1\r\n";
1023 $out .= "Host: $sitename\r\n";
1024 $out .= "Connection: Close\r\n\r\n";
1025 fwrite($fp, $out);
1026 $data = '';
1027 $incoming = false;
1028 while (!feof($fp)) {
1029 if ($incoming) {
1030 $data .= fgets($fp, 1024);
1031 } else if (@fgets($fp, 1024) === "\r\n") {
1032 $incoming = true;
1033 }
1034 }
1035 fclose($fp);
1036 $data = trim($data);
1037 if ($data === $teststr) {
1038 return INSECURE_DATAROOT_ERROR;
1039 }
57e35f32 1040 }
bba0beae 1041
1042 return INSECURE_DATAROOT_WARNING;
57e35f32 1043}
6e4dc10f 1044
1045/// =============================================================================================================
1046/// administration tree classes and functions
1047
1048
1049// n.b. documentation is still in progress for this code
1050
1051/// INTRODUCTION
1052
1053/// This file performs the following tasks:
1054/// -it defines the necessary objects and interfaces to build the Moodle
1055/// admin hierarchy
eef868d1 1056/// -it defines the admin_externalpage_setup(), admin_externalpage_print_header(),
6e4dc10f 1057/// and admin_externalpage_print_footer() functions used on admin pages
1058
1059/// ADMIN_SETTING OBJECTS
1060
eef868d1 1061/// Moodle settings are represented by objects that inherit from the admin_setting
6e4dc10f 1062/// class. These objects encapsulate how to read a setting, how to write a new value
1063/// to a setting, and how to appropriately display the HTML to modify the setting.
1064
1065/// ADMIN_SETTINGPAGE OBJECTS
1066
1067/// The admin_setting objects are then grouped into admin_settingpages. The latter
1068/// appear in the Moodle admin tree block. All interaction with admin_settingpage
1069/// objects is handled by the admin/settings.php file.
1070
1071/// ADMIN_EXTERNALPAGE OBJECTS
1072
1073/// There are some settings in Moodle that are too complex to (efficiently) handle
1074/// with admin_settingpages. (Consider, for example, user management and displaying
1075/// lists of users.) In this case, we use the admin_externalpage object. This object
1076/// places a link to an external PHP file in the admin tree block.
1077
1078/// If you're using an admin_externalpage object for some settings, you can take
1079/// advantage of the admin_externalpage_* functions. For example, suppose you wanted
1080/// to add a foo.php file into admin. First off, you add the following line to
1081/// admin/settings/first.php (at the end of the file) or to some other file in
1082/// admin/settings:
1083
eef868d1 1084/// $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
6e4dc10f 1085/// $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
1086
1087/// Next, in foo.php, your file structure would resemble the following:
1088
1089/// require_once('.../config.php');
1090/// require_once($CFG->libdir.'/adminlib.php');
1ae083e4 1091/// admin_externalpage_setup('foo');
6e4dc10f 1092/// // functionality like processing form submissions goes here
1ae083e4 1093/// admin_externalpage_print_header();
6e4dc10f 1094/// // your HTML goes here
1ae083e4 1095/// admin_externalpage_print_footer();
6e4dc10f 1096
1097/// The admin_externalpage_setup() function call ensures the user is logged in,
1098/// and makes sure that they have the proper role permission to access the page.
1099
1100/// The admin_externalpage_print_header() function prints the header (it figures
1101/// out what category and subcategories the page is classified under) and ensures
1102/// that you're using the admin pagelib (which provides the admin tree block and
1103/// the admin bookmarks block).
1104
1105/// The admin_externalpage_print_footer() function properly closes the tables
1106/// opened up by the admin_externalpage_print_header() function and prints the
1107/// standard Moodle footer.
1108
1109/// ADMIN_CATEGORY OBJECTS
1110
1111/// Above and beyond all this, we have admin_category objects. These objects
1112/// appear as folders in the admin tree block. They contain admin_settingpage's,
1113/// admin_externalpage's, and other admin_category's.
1114
1115/// OTHER NOTES
1116
1117/// admin_settingpage's, admin_externalpage's, and admin_category's all inherit
1118/// from part_of_admin_tree (a pseudointerface). This interface insists that
1119/// a class has a check_access method for access permissions, a locate method
220a90c5 1120/// used to find a specific node in the admin tree and find parent path.
6e4dc10f 1121
1122/// admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
1123/// interface ensures that the class implements a recursive add function which
1124/// accepts a part_of_admin_tree object and searches for the proper place to
1125/// put it. parentable_part_of_admin_tree implies part_of_admin_tree.
1126
1127/// Please note that the $this->name field of any part_of_admin_tree must be
1128/// UNIQUE throughout the ENTIRE admin tree.
1129
1130/// The $this->name field of an admin_setting object (which is *not* part_of_
1131/// admin_tree) must be unique on the respective admin_settingpage where it is
1132/// used.
1133
1134
6e4dc10f 1135/// CLASS DEFINITIONS /////////////////////////////////////////////////////////
1136
1137/**
1138 * Pseudointerface for anything appearing in the admin tree
1139 *
1140 * The pseudointerface that is implemented by anything that appears in the admin tree
1141 * block. It forces inheriting classes to define a method for checking user permissions
1142 * and methods for finding something in the admin tree.
1143 *
1144 * @author Vincenzo K. Marcovecchio
1145 * @package admin
1146 */
1147class part_of_admin_tree {
1148
1149 /**
1150 * Finds a named part_of_admin_tree.
1151 *
1152 * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
1153 * and not parentable_part_of_admin_tree, then this function should only check if
1154 * $this->name matches $name. If it does, it should return a reference to $this,
1155 * otherwise, it should return a reference to NULL.
1156 *
1157 * If a class inherits parentable_part_of_admin_tree, this method should be called
1158 * recursively on all child objects (assuming, of course, the parent object's name
1159 * doesn't match the search criterion).
1160 *
1161 * @param string $name The internal name of the part_of_admin_tree we're searching for.
1162 * @return mixed An object reference or a NULL reference.
1163 */
eef868d1 1164 function &locate($name) {
1165 trigger_error('Admin class does not implement method <strong>locate()</strong>', E_USER_WARNING);
1166 return;
6e4dc10f 1167 }
4672d955 1168
1169 /**
1170 * Removes named part_of_admin_tree.
1171 *
1172 * @param string $name The internal name of the part_of_admin_tree we want to remove.
a8a66c96 1173 * @return bool success.
4672d955 1174 */
1175 function prune($name) {
eef868d1 1176 trigger_error('Admin class does not implement method <strong>prune()</strong>', E_USER_WARNING);
4672d955 1177 return;
eef868d1 1178 }
4672d955 1179
220a90c5 1180 /**
1181 * Search using query
1182 * @param strin query
1183 * @return mixed array-object structure of found settings and pages
1184 */
1185 function search($query) {
1186 trigger_error('Admin class does not implement method <strong>search()</strong>', E_USER_WARNING);
1187 return;
1188 }
1189
6e4dc10f 1190 /**
1191 * Verifies current user's access to this part_of_admin_tree.
1192 *
1193 * Used to check if the current user has access to this part of the admin tree or
1194 * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
1195 * then this method is usually just a call to has_capability() in the site context.
1196 *
1197 * If a class inherits parentable_part_of_admin_tree, this method should return the
1198 * logical OR of the return of check_access() on all child objects.
1199 *
1200 * @return bool True if the user has access, false if she doesn't.
1201 */
eef868d1 1202 function check_access() {
1203 trigger_error('Admin class does not implement method <strong>check_access()</strong>', E_USER_WARNING);
1204 return;
6e4dc10f 1205 }
eef868d1 1206
a8a66c96 1207 /**
1208 * Mostly usefull for removing of some parts of the tree in admin tree block.
1209 *
1210 * @return True is hidden from normal list view
1211 */
1212 function is_hidden() {
1213 trigger_error('Admin class does not implement method <strong>is_hidden()</strong>', E_USER_WARNING);
1214 return;
1215 }
6e4dc10f 1216}
1217
1218/**
1219 * Pseudointerface implemented by any part_of_admin_tree that has children.
1220 *
1221 * The pseudointerface implemented by any part_of_admin_tree that can be a parent
1222 * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
eef868d1 1223 * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
6e4dc10f 1224 * include an add method for adding other part_of_admin_tree objects as children.
1225 *
1226 * @author Vincenzo K. Marcovecchio
1227 * @package admin
1228 */
1229class parentable_part_of_admin_tree extends part_of_admin_tree {
eef868d1 1230
6e4dc10f 1231 /**
1232 * Adds a part_of_admin_tree object to the admin tree.
1233 *
1234 * Used to add a part_of_admin_tree object to this object or a child of this
1235 * object. $something should only be added if $destinationname matches
1236 * $this->name. If it doesn't, add should be called on child objects that are
1237 * also parentable_part_of_admin_tree's.
1238 *
1239 * @param string $destinationname The internal name of the new parent for $something.
1240 * @param part_of_admin_tree &$something The object to be added.
1241 * @return bool True on success, false on failure.
1242 */
220a90c5 1243 function add($destinationname, $something) {
eef868d1 1244 trigger_error('Admin class does not implement method <strong>add()</strong>', E_USER_WARNING);
1245 return;
6e4dc10f 1246 }
eef868d1 1247
6e4dc10f 1248}
1249
1250/**
1251 * The object used to represent folders (a.k.a. categories) in the admin tree block.
eef868d1 1252 *
6e4dc10f 1253 * Each admin_category object contains a number of part_of_admin_tree objects.
1254 *
1255 * @author Vincenzo K. Marcovecchio
1256 * @package admin
1257 */
1258class admin_category extends parentable_part_of_admin_tree {
1259
1260 /**
1261 * @var mixed An array of part_of_admin_tree objects that are this object's children
1262 */
1263 var $children;
eef868d1 1264
6e4dc10f 1265 /**
1266 * @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1267 */
1268 var $name;
eef868d1 1269
6e4dc10f 1270 /**
1271 * @var string The displayed name for this category. Usually obtained through get_string()
1272 */
1273 var $visiblename;
eef868d1 1274
a8a66c96 1275 /**
1276 * @var bool Should this category be hidden in admin tree block?
1277 */
1278 var $hidden;
1279
220a90c5 1280 /**
1281 * paths
1282 */
1283 var $path;
1284 var $visiblepath;
6e4dc10f 1285
1286 /**
1287 * Constructor for an empty admin category
1288 *
1289 * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
1290 * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
a8a66c96 1291 * @param bool $hidden hide category in admin tree block
6e4dc10f 1292 */
220a90c5 1293 function admin_category($name, $visiblename, $hidden=false) {
1294 $this->children = array();
1295 $this->name = $name;
6e4dc10f 1296 $this->visiblename = $visiblename;
220a90c5 1297 $this->hidden = $hidden;
6e4dc10f 1298 }
eef868d1 1299
6e4dc10f 1300 /**
220a90c5 1301 * Returns a reference to the part_of_admin_tree object with internal name $name.
6e4dc10f 1302 *
220a90c5 1303 * @param string $name The internal name of the object we want.
1304 * @param bool $findpath initialize path and visiblepath arrays
1305 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
6e4dc10f 1306 */
220a90c5 1307 function &locate($name, $findpath=false) {
6e4dc10f 1308 if ($this->name == $name) {
220a90c5 1309 if ($findpath) {
1310 $this->visiblepath[] = $this->visiblename;
1311 $this->path[] = $this->name;
1312 }
1313 return $this;
6e4dc10f 1314 }
eef868d1 1315
220a90c5 1316 $return = NULL;
1317 foreach($this->children as $childid=>$unused) {
1318 if ($return =& $this->children[$childid]->locate($name, $findpath)) {
1319 break;
6e4dc10f 1320 }
1321 }
eef868d1 1322
220a90c5 1323 if (!is_null($return) and $findpath) {
1324 $return->visiblepath[] = $this->visiblename;
1325 $return->path[] = $this->name;
1326 }
eef868d1 1327
220a90c5 1328 return $return;
6e4dc10f 1329 }
1330
1331 /**
220a90c5 1332 * Search using query
1333 * @param strin query
1334 * @return mixed array-object structure of found settings and pages
6e4dc10f 1335 */
220a90c5 1336 function search($query) {
1337 $result = array();
1338 foreach ($this->children as $child) {
3cea9c55 1339 $subsearch = $child->search($query);
1340 if (!is_array($subsearch)) {
1341 debugging('Incorrect search result from '.$child->name);
1342 continue;
1343 }
1344 $result = array_merge($result, $subsearch);
6e4dc10f 1345 }
220a90c5 1346 return $result;
6e4dc10f 1347 }
1348
4672d955 1349 /**
1350 * Removes part_of_admin_tree object with internal name $name.
1351 *
1352 * @param string $name The internal name of the object we want to remove.
a8a66c96 1353 * @return bool success
4672d955 1354 */
1355 function prune($name) {
1356
1357 if ($this->name == $name) {
1358 return false; //can not remove itself
1359 }
1360
1361 foreach($this->children as $precedence => $child) {
1362 if ($child->name == $name) {
1363 // found it!
eef868d1 1364 unset($this->children[$precedence]);
4672d955 1365 return true;
1366 }
1367 if ($this->children[$precedence]->prune($name)) {
1368 return true;
1369 }
1370 }
1371 return false;
1372 }
1373
6e4dc10f 1374 /**
1375 * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
1376 *
220a90c5 1377 * @param string $destinationame The internal name of the immediate parent that we want for $something.
1378 * @param mixed $something A part_of_admin_tree or setting instanceto be added.
1379 * @return bool True if successfully added, false if $something can not be added.
6e4dc10f 1380 */
220a90c5 1381 function add($parentname, $something) {
1382 $parent =& $this->locate($parentname);
1383 if (is_null($parent)) {
1384 debugging('parent does not exist!');
6e4dc10f 1385 return false;
1386 }
1387
220a90c5 1388 if (is_a($something, 'part_of_admin_tree')) {
1389 if (!is_a($parent, 'parentable_part_of_admin_tree')) {
1390 debugging('error - parts of tree can be inserted only into parentable parts');
1391 return false;
6e4dc10f 1392 }
220a90c5 1393 $parent->children[] = $something;
6e4dc10f 1394 return true;
eef868d1 1395
220a90c5 1396 } else {
1397 debugging('error - can not add this element');
1398 return false;
6e4dc10f 1399 }
eef868d1 1400
6e4dc10f 1401 }
eef868d1 1402
6e4dc10f 1403 /**
1404 * Checks if the user has access to anything in this category.
1405 *
1406 * @return bool True if the user has access to atleast one child in this category, false otherwise.
1407 */
1408 function check_access() {
6e4dc10f 1409 foreach ($this->children as $child) {
220a90c5 1410 if ($child->check_access()) {
1411 return true;
1412 }
6e4dc10f 1413 }
220a90c5 1414 return false;
6e4dc10f 1415 }
eef868d1 1416
a8a66c96 1417 /**
1418 * Is this category hidden in admin tree block?
1419 *
1420 * @return bool True if hidden
1421 */
1422 function is_hidden() {
1423 return $this->hidden;
1424 }
6e4dc10f 1425}
1426
220a90c5 1427class admin_root extends admin_category {
1428 /**
1429 * list of errors
1430 */
1431 var $errors;
1432
1433 /**
1434 * search query
1435 */
1436 var $search;
1437
1438 /**
1439 * full tree flag - true means all settings required, false onlypages required
1440 */
1441 var $fulltree;
1442
1443
1444 function admin_root() {
1445 parent::admin_category('root', get_string('administration'), false);
1446 $this->errors = array();
1447 $this->search = '';
1448 $this->fulltree = true;
1449 }
1450}
1451
6e4dc10f 1452/**
1453 * Links external PHP pages into the admin tree.
1454 *
1455 * See detailed usage example at the top of this document (adminlib.php)
1456 *
1457 * @author Vincenzo K. Marcovecchio
1458 * @package admin
1459 */
1460class admin_externalpage extends part_of_admin_tree {
1461
eef868d1 1462 /**
6e4dc10f 1463 * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1464 */
1465 var $name;
eef868d1 1466
6e4dc10f 1467 /**
1468 * @var string The displayed name for this external page. Usually obtained through get_string().
1469 */
1470 var $visiblename;
eef868d1 1471
6e4dc10f 1472 /**
1473 * @var string The external URL that we should link to when someone requests this external page.
1474 */
1475 var $url;
eef868d1 1476
6e4dc10f 1477 /**
1478 * @var string The role capability/permission a user must have to access this external page.
1479 */
2ce38b70 1480 var $req_capability;
eef868d1 1481
84c8ede0 1482 /**
1483 * @var object The context in which capability/permission should be checked, default is site context.
1484 */
1485 var $context;
1486
a8a66c96 1487 /**
1488 * @var bool hidden in admin tree block.
1489 */
1490 var $hidden;
1491
220a90c5 1492 /**
1493 * visible path
1494 */
1495 var $path;
1496 var $visiblepath;
1497
6e4dc10f 1498 /**
1499 * Constructor for adding an external page into the admin tree.
1500 *
1501 * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
1502 * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
1503 * @param string $url The external URL that we should link to when someone requests this external page.
38d2d43b 1504 * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
92f00846 1505 * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
1506 * @param context $context The context the page relates to. Not sure what happens
1507 * if you specify something other than system or front page. Defaults to system.
6e4dc10f 1508 */
220a90c5 1509 function admin_externalpage($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1510 $this->name = $name;
6e4dc10f 1511 $this->visiblename = $visiblename;
220a90c5 1512 $this->url = $url;
38d2d43b 1513 if (is_array($req_capability)) {
1514 $this->req_capability = $req_capability;
1515 } else {
1516 $this->req_capability = array($req_capability);
1517 }
92f00846 1518 $this->hidden = $hidden;
84c8ede0 1519 $this->context = $context;
6e4dc10f 1520 }
eef868d1 1521
6e4dc10f 1522 /**
1523 * Returns a reference to the part_of_admin_tree object with internal name $name.
1524 *
1525 * @param string $name The internal name of the object we want.
1526 * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
1527 */
220a90c5 1528 function &locate($name, $findpath=false) {
1529 if ($this->name == $name) {
1530 if ($findpath) {
1531 $this->visiblepath = array($this->visiblename);
1532 $this->path = array($this->name);
1533 }
1534 return $this;
1535 } else {
1536 $return = NULL;
1537 return $return;
1538 }
6e4dc10f 1539 }
4672d955 1540
1541 function prune($name) {
1542 return false;
1543 }
1544
220a90c5 1545 /**
1546 * Search using query
1547 * @param strin query
1548 * @return mixed array-object structure of found settings and pages
1549 */
1550 function search($query) {
1551 $textlib = textlib_get_instance();
1552
1553 $found = false;
1554 if (strpos(strtolower($this->name), $query) !== false) {
1555 $found = true;
1556 } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1557 $found = true;
1558 }
1559 if ($found) {
1560 $result = new object();
1561 $result->page = $this;
1562 $result->settings = array();
1563 return array($this->name => $result);
1564 } else {
1565 return array();
1566 }
1567 }
1568
6e4dc10f 1569 /**
2ce38b70 1570 * Determines if the current user has access to this external page based on $this->req_capability.
6e4dc10f 1571 * @return bool True if user has access, false otherwise.
1572 */
1573 function check_access() {
1caea91e 1574 global $CFG;
1575 if (empty($CFG->rolesactive)) {
6e4dc10f 1576 return true; // no access check before site is fully set up
1577 }
84c8ede0 1578 $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
38d2d43b 1579 foreach($this->req_capability as $cap) {
1a5b427e 1580 if (is_valid_capability($cap) and has_capability($cap, $context)) {
38d2d43b 1581 return true;
1582 }
1583 }
1584 return false;
6e4dc10f 1585 }
1586
a8a66c96 1587 /**
1588 * Is this external page hidden in admin tree block?
1589 *
1590 * @return bool True if hidden
1591 */
1592 function is_hidden() {
1593 return $this->hidden;
1594 }
1595
6e4dc10f 1596}
1597
1598/**
1599 * Used to group a number of admin_setting objects into a page and add them to the admin tree.
1600 *
1601 * @author Vincenzo K. Marcovecchio
1602 * @package admin
1603 */
1604class admin_settingpage extends part_of_admin_tree {
1605
eef868d1 1606 /**
6e4dc10f 1607 * @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects
1608 */
1609 var $name;
eef868d1 1610
6e4dc10f 1611 /**
1612 * @var string The displayed name for this external page. Usually obtained through get_string().
1613 */
1614 var $visiblename;
1615 /**
1616 * @var mixed An array of admin_setting objects that are part of this setting page.
1617 */
1618 var $settings;
eef868d1 1619
6e4dc10f 1620 /**
1621 * @var string The role capability/permission a user must have to access this external page.
1622 */
2ce38b70 1623 var $req_capability;
eef868d1 1624
84c8ede0 1625 /**
1626 * @var object The context in which capability/permission should be checked, default is site context.
1627 */
1628 var $context;
1629
a8a66c96 1630 /**
1631 * @var bool hidden in admin tree block.
1632 */
1633 var $hidden;
1634
220a90c5 1635 /**
1636 * paths
1637 */
1638 var $path;
1639 var $visiblepath;
1640
1641 // see admin_externalpage
1642 function admin_settingpage($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
1643 $this->settings = new object();
1644 $this->name = $name;
1645 $this->visiblename = $visiblename;
1646 if (is_array($req_capability)) {
1647 $this->req_capability = $req_capability;
6e4dc10f 1648 } else {
220a90c5 1649 $this->req_capability = array($req_capability);
6e4dc10f 1650 }
220a90c5 1651 $this->hidden = $hidden;
1652 $this->context = $context;
6e4dc10f 1653 }
eef868d1 1654
6e4dc10f 1655 // see admin_category
220a90c5 1656 function &locate($name, $findpath=false) {
1657 if ($this->name == $name) {
1658 if ($findpath) {
1659 $this->visiblepath = array($this->visiblename);
1660 $this->path = array($this->name);
1661 }
1662 return $this;
1663 } else {
1664 $return = NULL;
1665 return $return;
1666 }
6e4dc10f 1667 }
4672d955 1668
220a90c5 1669 function search($query) {
1670 $found = array();
4672d955 1671
220a90c5 1672 foreach ($this->settings as $setting) {
1673 if ($setting->is_related($query)) {
1674 $found[] = $setting;
1675 }
1676 }
1677
1678 if ($found) {
1679 $result = new object();
1680 $result->page = $this;
1681 $result->settings = $found;
1682 return array($this->name => $result);
1683 }
1684
1685 $textlib = textlib_get_instance();
1686
1687 $found = false;
1688 if (strpos(strtolower($this->name), $query) !== false) {
1689 $found = true;
1690 } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1691 $found = true;
1692 }
1693 if ($found) {
1694 $result = new object();
1695 $result->page = $this;
1696 $result->settings = array();
1697 return array($this->name => $result);
38d2d43b 1698 } else {
220a90c5 1699 return array();
38d2d43b 1700 }
6e4dc10f 1701 }
eef868d1 1702
220a90c5 1703 function prune($name) {
6e4dc10f 1704 return false;
1705 }
eef868d1 1706
220a90c5 1707 /**
1708 * 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
1709 * n.b. each admin_setting in an admin_settingpage must have a unique internal name
1710 * @param object $setting is the admin_setting object you want to add
1711 * @return true if successful, false if not
1712 */
1713 function add($setting) {
1714 if (!is_a($setting, 'admin_setting')) {
1715 debugging('error - not a setting instance');
1716 return false;
1717 }
1718
1719 $this->settings->{$setting->name} = $setting;
1720 return true;
1721 }
1722
6e4dc10f 1723 // see admin_externalpage
1724 function check_access() {
1caea91e 1725 global $CFG;
1726 if (empty($CFG->rolesactive)) {
6e4dc10f 1727 return true; // no access check before site is fully set up
1728 }
84c8ede0 1729 $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
38d2d43b 1730 foreach($this->req_capability as $cap) {
1a5b427e 1731 if (is_valid_capability($cap) and has_capability($cap, $context)) {
38d2d43b 1732 return true;
1733 }
1734 }
1735 return false;
6e4dc10f 1736 }
eef868d1 1737
220a90c5 1738 /**
1739 * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
1740 * returns a string of the html
1741 */
6e4dc10f 1742 function output_html() {
220a90c5 1743 $adminroot =& admin_get_root();
1744 $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
6e4dc10f 1745 foreach($this->settings as $setting) {
220a90c5 1746 $fullname = $setting->get_full_name();
1747 if (array_key_exists($fullname, $adminroot->errors)) {
1748 $data = $adminroot->errors[$fullname]->data;
6e4dc10f 1749 } else {
220a90c5 1750 $data = $setting->get_setting();
775f811a 1751 // do not use defaults if settings not available - upgrdesettings handles the defaults!
6e4dc10f 1752 }
220a90c5 1753 $return .= $setting->output_html($data);
6e4dc10f 1754 }
220a90c5 1755 $return .= '</fieldset>';
6e4dc10f 1756 return $return;
1757 }
1758
a8a66c96 1759 /**
1760 * Is this settigns page hidden in admin tree block?
1761 *
1762 * @return bool True if hidden
1763 */
1764 function is_hidden() {
1765 return $this->hidden;
1766 }
1767
6e4dc10f 1768}
1769
1770
220a90c5 1771/**
1772 * Admin settings class. Only exists on setting pages.
1773 * Read & write happens at this level; no authentication.
1774 */
6e4dc10f 1775class admin_setting {
1776
1777 var $name;
1778 var $visiblename;
1779 var $description;
1780 var $defaultsetting;
220a90c5 1781 var $updatedcallback;
1782 var $plugin; // null means main config table
6e4dc10f 1783
220a90c5 1784 /**
1785 * Constructor
1a41e806 1786 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
1787 * @param string $visiblename localised name
1788 * @param string $description localised long description
220a90c5 1789 * @param mixed $defaultsetting string or array depending on implementation
1790 */
6e4dc10f 1791 function admin_setting($name, $visiblename, $description, $defaultsetting) {
7fb0303d 1792 $this->parse_setting_name($name);
220a90c5 1793 $this->visiblename = $visiblename;
1794 $this->description = $description;
6e4dc10f 1795 $this->defaultsetting = $defaultsetting;
1796 }
eef868d1 1797
7fb0303d 1798 /**
1799 * Set up $this->name and possibly $this->plugin based on whether $name looks
1800 * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
1801 * on the names, that is, output a developer debug warning if the name
1802 * contains anything other than [a-zA-Z0-9_]+.
1803 *
1804 * @param string $name the setting name passed in to the constructor.
1805 */
1806 private function parse_setting_name($name) {
1807 $bits = explode('/', $name);
1808 if (count($bits) > 2) {
1809 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1810 }
1811 $this->name = array_pop($bits);
1812 if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
1813 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1814 }
1815 if (!empty($bits)) {
1816 $this->plugin = array_pop($bits);
1817 if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
1818 throw new moodle_exception('invalidadminsettingname', '', '', $name);
1819 }
1820 }
1821 }
1822
220a90c5 1823 function get_full_name() {
1824 return 's_'.$this->plugin.'_'.$this->name;
1825 }
1826
1827 function get_id() {
1828 return 'id_s_'.$this->plugin.'_'.$this->name;
1829 }
1830
1831 function config_read($name) {
1832 global $CFG;
eb6a973c 1833 if (!empty($this->plugin)) {
220a90c5 1834 $value = get_config($this->plugin, $name);
1835 return $value === false ? NULL : $value;
1836
1837 } else {
1838 if (isset($CFG->$name)) {
1839 return $CFG->$name;
1840 } else {
1841 return NULL;
1842 }
1843 }
1844 }
1845
1846 function config_write($name, $value) {
eb6a973c 1847 return (boolean)set_config($name, $value, $this->plugin);
220a90c5 1848 }
1849
1850 /**
1851 * Returns current value of this setting
1852 * @return mixed array or string depending on instance, NULL means not set yet
1853 */
6e4dc10f 1854 function get_setting() {
220a90c5 1855 // has to be overridden
1856 return NULL;
6e4dc10f 1857 }
eef868d1 1858
220a90c5 1859 /**
1860 * Returns default setting if exists
1861 * @return mixed array or string depending on instance; NULL means no default, user must supply
1862 */
8e5da17a 1863 function get_defaultsetting() {
1864 return $this->defaultsetting;
1865 }
1866
220a90c5 1867 /**
1868 * Store new setting
1869 * @param mixed string or array, must not be NULL
1870 * @return '' if ok, string error message otherwise
1871 */
6e4dc10f 1872 function write_setting($data) {
220a90c5 1873 // should be overridden
1874 return '';
6e4dc10f 1875 }
eef868d1 1876
220a90c5 1877 /**
1878 * Return part of form with setting
1879 * @param mixed data array or string depending on setting
1880 * @return string
1881 */
587c7040 1882 function output_html($data, $query='') {
220a90c5 1883 // should be overridden
1884 return;
1885 }
1886
1887 /**
1888 * function called if setting updated - cleanup, cache reset, etc.
1889 */
1890 function set_updatedcallback($functionname) {
1891 $this->updatedcallback = $functionname;
1892 }
1893
1894 /**
1895 * Is setting related to query text - used when searching
1896 * @param string $query
1897 * @return bool
1898 */
1899 function is_related($query) {
1900 if (strpos(strtolower($this->name), $query) !== false) {
1901 return true;
1902 }
1903 $textlib = textlib_get_instance();
1904 if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
1905 return true;
1906 }
1907 if (strpos($textlib->strtolower($this->description), $query) !== false) {
1908 return true;
1909 }
587c7040 1910 $current = $this->get_setting();
1911 if (!is_null($current)) {
1912 if (is_string($current)) {
1913 if (strpos($textlib->strtolower($current), $query) !== false) {
1914 return true;
1915 }
1916 }
1917 }
1918 $default = $this->get_defaultsetting();
1919 if (!is_null($default)) {
1920 if (is_string($default)) {
1921 if (strpos($textlib->strtolower($default), $query) !== false) {
1922 return true;
1923 }
1924 }
1925 }
220a90c5 1926 return false;
6e4dc10f 1927 }
220a90c5 1928}
eef868d1 1929
220a90c5 1930/**
1931 * No setting - just heading and text.
1932 */
1933class admin_setting_heading extends admin_setting {
1934 /**
1935 * not a setting, just text
1a41e806 1936 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 1937 * @param string $heading heading
1938 * @param string $information text in box
1939 */
1940 function admin_setting_heading($name, $heading, $information) {
1941 parent::admin_setting($name, $heading, $information, '');
1942 }
1943
1944 function get_setting() {
1945 return true;
1946 }
1947
1948 function get_defaultsetting() {
1949 return true;
1950 }
1951
1952 function write_setting($data) {
1953 // do not write any setting
1954 return '';
1955 }
1956
587c7040 1957 function output_html($data, $query='') {
220a90c5 1958 $return = '';
1959 if ($this->visiblename != '') {
587c7040 1960 $return .= print_heading('<a name="'.$this->name.'">'.highlightfast($query, $this->visiblename).'</a>', '', 3, 'main', true);
220a90c5 1961 }
1962 if ($this->description != '') {
587c7040 1963 $return .= print_box(highlight($query, $this->description), 'generalbox formsettingheading', '', true);
220a90c5 1964 }
1965 return $return;
1966 }
1967}
6e4dc10f 1968
220a90c5 1969/**
1970 * The most flexibly setting, user is typing text
1971 */
6e4dc10f 1972class admin_setting_configtext extends admin_setting {
1973
1974 var $paramtype;
f7633b0f 1975 var $size;
6e4dc10f 1976
220a90c5 1977 /**
1978 * config text contructor
1a41e806 1979 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 1980 * @param string $visiblename localised
1981 * @param string $description long localised info
1982 * @param string $defaultsetting
1983 * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex
f7633b0f 1984 * @param int $size default field size
220a90c5 1985 */
f7633b0f 1986 function admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) {
6e4dc10f 1987 $this->paramtype = $paramtype;
f7633b0f 1988 if (!is_null($size)) {
1989 $this->size = $size;
1990 } else {
1991 $this->size = ($paramtype == PARAM_INT) ? 5 : 30;
1992 }
6e4dc10f 1993 parent::admin_setting($name, $visiblename, $description, $defaultsetting);
1994 }
1995
1996 function get_setting() {
220a90c5 1997 return $this->config_read($this->name);
6e4dc10f 1998 }
eef868d1 1999
6e4dc10f 2000 function write_setting($data) {
8cad6cca 2001 if ($this->paramtype === PARAM_INT and $data === '') {
2002 // do not complain if '' used instead of 0
2003 $data = 0;
2004 }
220a90c5 2005 // $data is a string
c5d2d0dd 2006 $validated = $this->validate($data);
e33fbf87 2007 if ($validated !== true) {
2008 return $validated;
c235598d 2009 }
220a90c5 2010 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2011 }
2012
e33fbf87 2013 /**
2014 * Validate data before storage
2015 * @param string data
2016 * @return mixed true if ok string if error found
2017 */
c235598d 2018 function validate($data) {
9e24fbd1 2019 if (is_string($this->paramtype)) {
e33fbf87 2020 if (preg_match($this->paramtype, $data)) {
2021 return true;
2022 } else {
2023 return get_string('validateerror', 'admin');
2024 }
2025
9e24fbd1 2026 } else if ($this->paramtype === PARAM_RAW) {
2027 return true;
e33fbf87 2028
9e24fbd1 2029 } else {
294ce987 2030 $cleaned = clean_param($data, $this->paramtype);
e33fbf87 2031 if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
2032 return true;
2033 } else {
2034 return get_string('validateerror', 'admin');
2035 }
9e24fbd1 2036 }
c235598d 2037 }
2038
587c7040 2039 function output_html($data, $query='') {
220a90c5 2040 $default = $this->get_defaultsetting();
2041
220a90c5 2042 return format_admin_setting($this, $this->visiblename,
f7633b0f 2043 '<div class="form-text defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" /></div>',
587c7040 2044 $this->description, true, '', $default, $query);
6e4dc10f 2045 }
6e4dc10f 2046}
2047
220a90c5 2048/**
2049 * General text area without html editor.
2050 */
2051class admin_setting_configtextarea extends admin_setting_configtext {
2052 var $rows;
2053 var $cols;
eba8cd63 2054
28764710 2055 function admin_setting_configtextarea($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $cols='60', $rows='8') {
220a90c5 2056 $this->rows = $rows;
2057 $this->cols = $cols;
eba8cd63 2058 parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, $paramtype);
2059 }
2060
587c7040 2061 function output_html($data, $query='') {
220a90c5 2062 $default = $this->get_defaultsetting();
2063
587c7040 2064 $defaultinfo = $default;
2065 if (!is_null($default) and $default !== '') {
2066 $defaultinfo = "\n".$default;
c5d2d0dd 2067 }
220a90c5 2068
2069 return format_admin_setting($this, $this->visiblename,
8c37106d 2070 '<div class="form-textarea form-textarea-advanced" ><textarea rows="'. $this->rows .'" cols="'. $this->cols .'" id="'. $this->get_id() .'" name="'. $this->get_full_name() .'">'. s($data) .'</textarea></div>',
587c7040 2071 $this->description, true, '', $defaultinfo, $query);
220a90c5 2072 }
2073}
2074
2075/**
2076 * Password field, allows unmasking of password
2077 */
2078class admin_setting_configpasswordunmask extends admin_setting_configtext {
2079 /**
2080 * Constructor
1a41e806 2081 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2082 * @param string $visiblename localised
2083 * @param string $description long localised info
2084 * @param string $defaultsetting default password
2085 */
2086 function admin_setting_configpasswordunmask($name, $visiblename, $description, $defaultsetting) {
f7633b0f 2087 parent::admin_setting_configtext($name, $visiblename, $description, $defaultsetting, PARAM_RAW, 30);
220a90c5 2088 }
2089
587c7040 2090 function output_html($data, $query='') {
220a90c5 2091 $id = $this->get_id();
2092 $unmask = get_string('unmaskpassword', 'form');
2093 $unmaskjs = '<script type="text/javascript">
eba8cd63 2094//<![CDATA[
633239f6 2095var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
2096
2097document.getElementById("'.$id.'").setAttribute("autocomplete", "off");
2098
2099var unmaskdiv = document.getElementById("'.$id.'unmaskdiv");
2100
2101var unmaskchb = document.createElement("input");
2102unmaskchb.setAttribute("type", "checkbox");
2103unmaskchb.setAttribute("id", "'.$id.'unmask");
2104unmaskchb.onchange = function() {unmaskPassword("'.$id.'");};
2105unmaskdiv.appendChild(unmaskchb);
2106
2107var unmasklbl = document.createElement("label");
2108unmasklbl.innerHTML = "'.addslashes_js($unmask).'";
2109if (is_ie) {
2110 unmasklbl.setAttribute("htmlFor", "'.$id.'unmask");
2111} else {
2112 unmasklbl.setAttribute("for", "'.$id.'unmask");
2113}
2114unmaskdiv.appendChild(unmasklbl);
2115
2116if (is_ie) {
2117 // ugly hack to work around the famous onchange IE bug
2118 unmaskchb.onclick = function() {this.blur();};
2119 unmaskdiv.onclick = function() {this.blur();};
2120}
eba8cd63 2121//]]>
2122</script>';
220a90c5 2123 return format_admin_setting($this, $this->visiblename,
633239f6 2124 '<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>',
587c7040 2125 $this->description, true, '', NULL, $query);
220a90c5 2126 }
2127}
2128
2129/**
e9c0fa35 2130 * Path to directory
220a90c5 2131 */
e9c0fa35 2132class admin_setting_configfile extends admin_setting_configtext {
220a90c5 2133 /**
2134 * Constructor
1a41e806 2135 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2136 * @param string $visiblename localised
2137 * @param string $description long localised info
e9c0fa35 2138 * @param string $defaultdirectory default directory location
220a90c5 2139 */
e9c0fa35 2140 function admin_setting_configfile($name, $visiblename, $description, $defaultdirectory) {
f7633b0f 2141 parent::admin_setting_configtext($name, $visiblename, $description, $defaultdirectory, PARAM_RAW, 50);
220a90c5 2142 }
2143
587c7040 2144 function output_html($data, $query='') {
220a90c5 2145 $default = $this->get_defaultsetting();
2146
220a90c5 2147 if ($data) {
e9c0fa35 2148 if (file_exists($data)) {
220a90c5 2149 $executable = '<span class="pathok">&#x2714;</span>';
2150 } else {
2151 $executable = '<span class="patherror">&#x2718;</span>';
2152 }
2153 } else {
2154 $executable = '';
2155 }
2156
2157 return format_admin_setting($this, $this->visiblename,
f7633b0f 2158 '<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>',
587c7040 2159 $this->description, true, '', $default, $query);
eba8cd63 2160 }
220a90c5 2161}
2162
2163/**
e9c0fa35 2164 * Path to executable file
220a90c5 2165 */
e9c0fa35 2166class admin_setting_configexecutable extends admin_setting_configfile {
2167
2168 function output_html($data, $query='') {
2169 $default = $this->get_defaultsetting();
2170
2171 if ($data) {
2172 if (file_exists($data) and is_executable($data)) {
2173 $executable = '<span class="pathok">&#x2714;</span>';
2174 } else {
2175 $executable = '<span class="patherror">&#x2718;</span>';
2176 }
2177 } else {
2178 $executable = '';
2179 }
2180
2181 return format_admin_setting($this, $this->visiblename,
f7633b0f 2182 '<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>',
e9c0fa35 2183 $this->description, true, '', $default, $query);
220a90c5 2184 }
e9c0fa35 2185}
220a90c5 2186
e9c0fa35 2187/**
2188 * Path to directory
2189 */
2190class admin_setting_configdirectory extends admin_setting_configfile {
587c7040 2191 function output_html($data, $query='') {
220a90c5 2192 $default = $this->get_defaultsetting();
2193
220a90c5 2194 if ($data) {
2195 if (file_exists($data) and is_dir($data)) {
2196 $executable = '<span class="pathok">&#x2714;</span>';
2197 } else {
2198 $executable = '<span class="patherror">&#x2718;</span>';
2199 }
2200 } else {
2201 $executable = '';
2202 }
9ba38673 2203
220a90c5 2204 return format_admin_setting($this, $this->visiblename,
f7633b0f 2205 '<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>',
587c7040 2206 $this->description, true, '', $default, $query);
220a90c5 2207 }
eba8cd63 2208}
2209
220a90c5 2210/**
2211 * Checkbox
2212 */
6e4dc10f 2213class admin_setting_configcheckbox extends admin_setting {
220a90c5 2214 var $yes;
2215 var $no;
6e4dc10f 2216
220a90c5 2217 /**
2218 * Constructor
1a41e806 2219 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2220 * @param string $visiblename localised
2221 * @param string $description long localised info
2222 * @param string $defaultsetting
2223 * @param string $yes value used when checked
2224 * @param string $no value used when not checked
2225 */
2226 function admin_setting_configcheckbox($name, $visiblename, $description, $defaultsetting, $yes='1', $no='0') {
6e4dc10f 2227 parent::admin_setting($name, $visiblename, $description, $defaultsetting);
220a90c5 2228 $this->yes = (string)$yes;
2229 $this->no = (string)$no;
6e4dc10f 2230 }
2231
2232 function get_setting() {
220a90c5 2233 return $this->config_read($this->name);
6e4dc10f 2234 }
eef868d1 2235
6e4dc10f 2236 function write_setting($data) {
220a90c5 2237 if ((string)$data === $this->yes) { // convert to strings before comparison
2238 $data = $this->yes;
6e4dc10f 2239 } else {
220a90c5 2240 $data = $this->no;
6e4dc10f 2241 }
220a90c5 2242 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2243 }
2244
587c7040 2245 function output_html($data, $query='') {
220a90c5 2246 $default = $this->get_defaultsetting();
2247
2248 if (!is_null($default)) {
2249 if ((string)$default === $this->yes) {
587c7040 2250 $defaultinfo = get_string('checkboxyes', 'admin');
220a90c5 2251 } else {
587c7040 2252 $defaultinfo = get_string('checkboxno', 'admin');
220a90c5 2253 }
c8218a42 2254 } else {
587c7040 2255 $defaultinfo = NULL;
c8218a42 2256 }
220a90c5 2257
2258 if ((string)$data === $this->yes) { // convert to strings before comparison
2259 $checked = 'checked="checked"';
2260 } else {
2261 $checked = '';
2262 }
2263
2264 return format_admin_setting($this, $this->visiblename,
587c7040 2265 '<div class="form-checkbox defaultsnext" ><input type="hidden" name="'.$this->get_full_name().'" value="'.s($this->no).'" /> '
2266 .'<input type="checkbox" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($this->yes).'" '.$checked.' /></div>',
2267 $this->description, true, '', $defaultinfo, $query);
6e4dc10f 2268 }
6e4dc10f 2269}
2270
220a90c5 2271/**
2272 * Multiple checkboxes, each represents different value, stored in csv format
2273 */
2274class admin_setting_configmulticheckbox extends admin_setting {
6e4dc10f 2275 var $choices;
eef868d1 2276
220a90c5 2277 /**
2278 * Constructor
1a41e806 2279 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2280 * @param string $visiblename localised
2281 * @param string $description long localised info
2282 * @param array $defaultsetting array of selected
2283 * @param array $choices array of $value=>$label for each checkbox
2284 */
2285 function admin_setting_configmulticheckbox($name, $visiblename, $description, $defaultsetting, $choices) {
6e4dc10f 2286 $this->choices = $choices;
2287 parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2288 }
2289
0a784551 2290 /**
2291 * This function may be used in ancestors for lazy loading of choices
220a90c5 2292 * @return true if loaded, false if error
0a784551 2293 */
2294 function load_choices() {
2295 /*
220a90c5 2296 if (is_array($this->choices)) {
2297 return true;
0a784551 2298 }
2299 .... load choices here
2300 */
220a90c5 2301 return true;
2302 }
2303
2304 /**
2305 * Is setting related to query text - used when searching
2306 * @param string $query
2307 * @return bool
2308 */
2309 function is_related($query) {
2310 if (!$this->load_choices() or empty($this->choices)) {
2311 return false;
2312 }
2313 if (parent::is_related($query)) {
2314 return true;
2315 }
2316
2317 $textlib = textlib_get_instance();
2318 foreach ($this->choices as $desc) {
2319 if (strpos($textlib->strtolower($desc), $query) !== false) {
2320 return true;
2321 }
2322 }
2323 return false;
0a784551 2324 }
2325
6e4dc10f 2326 function get_setting() {
220a90c5 2327 $result = $this->config_read($this->name);
10f19c49 2328
220a90c5 2329 if (is_null($result)) {
2330 return NULL;
2331 }
2332 if ($result === '') {
2333 return array();
2334 }
10f19c49 2335 $enabled = explode(',', $result);
2336 $setting = array();
2337 foreach ($enabled as $option) {
2338 $setting[$option] = 1;
2339 }
2340 return $setting;
6e4dc10f 2341 }
eef868d1 2342
6e4dc10f 2343 function write_setting($data) {
220a90c5 2344 if (!is_array($data)) {
2345 return ''; // ignore it
2346 }
2347 if (!$this->load_choices() or empty($this->choices)) {
2348 return '';
2349 }
2350 unset($data['xxxxx']);
2351 $result = array();
2352 foreach ($data as $key => $value) {
2353 if ($value and array_key_exists($key, $this->choices)) {
2354 $result[] = $key;
2355 }
2356 }
2357 return $this->config_write($this->name, implode(',', $result)) ? '' : get_string('errorsetting', 'admin');
6e4dc10f 2358 }
eef868d1 2359
587c7040 2360 function output_html($data, $query='') {
220a90c5 2361 if (!$this->load_choices() or empty($this->choices)) {
2362 return '';
2363 }
2364 $default = $this->get_defaultsetting();
2365 if (is_null($default)) {
2366 $default = array();
2367 }
2368 if (is_null($data)) {
775f811a 2369 $data = array();
220a90c5 2370 }
220a90c5 2371 $options = array();
2372 $defaults = array();
10f19c49 2373 foreach ($this->choices as $key=>$description) {
2374 if (!empty($data[$key])) {
220a90c5 2375 $checked = 'checked="checked"';
2376 } else {
2377 $checked = '';
2378 }
10f19c49 2379 if (!empty($default[$key])) {
220a90c5 2380 $defaults[] = $description;
2381 }
2382
2383 $options[] = '<input type="checkbox" id="'.$this->get_id().'_'.$key.'" name="'.$this->get_full_name().'['.$key.']" value="1" '.$checked.' />'
587c7040 2384 .'<label for="'.$this->get_id().'_'.$key.'">'.highlightfast($query, $description).'</label>';
220a90c5 2385 }
2386
587c7040 2387 if (is_null($default)) {
2388 $defaultinfo = NULL;
2389 } else if (!empty($defaults)) {
2390 $defaultinfo = implode(', ', $defaults);
c8218a42 2391 } else {
587c7040 2392 $defaultinfo = get_string('none');
c8218a42 2393 }
220a90c5 2394
2395 $return = '<div class="form-multicheckbox">';
2396 $return .= '<input type="hidden" name="'.$this->get_full_name().'[xxxxx]" value="1" />'; // something must be submitted even if nothing selected
2397 if ($options) {
2398 $return .= '<ul>';
2399 foreach ($options as $option) {
2400 $return .= '<li>'.$option.'</li>';
2401 }
2402 $return .= '</ul>';
6e4dc10f 2403 }
587c7040 2404 $return .= '</div>';
6153cf58 2405
587c7040 2406 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
c5d2d0dd 2407
6e4dc10f 2408 }
6e4dc10f 2409}
2410
220a90c5 2411/**
2412 * Multiple checkboxes 2, value stored as string 00101011
2413 */
2414class admin_setting_configmulticheckbox2 extends admin_setting_configmulticheckbox {
2415 function get_setting() {
2416 $result = $this->config_read($this->name);
2417 if (is_null($result)) {
2418 return NULL;
2419 }
2420 if (!$this->load_choices()) {
2421 return NULL;
2422 }
2423 $result = str_pad($result, count($this->choices), '0');
2424 $result = preg_split('//', $result, -1, PREG_SPLIT_NO_EMPTY);
2425 $setting = array();
2426 foreach ($this->choices as $key=>$unused) {
2427 $value = array_shift($result);
2428 if ($value) {
10f19c49 2429 $setting[$key] = 1;
220a90c5 2430 }
2431 }
2432 return $setting;
2433 }
6e4dc10f 2434
220a90c5 2435 function write_setting($data) {
2436 if (!is_array($data)) {
2437 return ''; // ignore it
2438 }
2439 if (!$this->load_choices() or empty($this->choices)) {
2440 return '';
2441 }
2442 $result = '';
2443 foreach ($this->choices as $key=>$unused) {
2444 if (!empty($data[$key])) {
2445 $result .= '1';
2446 } else {
2447 $result .= '0';
2448 }
2449 }
2450 return $this->config_write($this->name, $result) ? '' : get_string('errorsetting', 'admin');
2451 }
2452}
2453
2454/**
2455 * Select one value from list
2456 */
2457class admin_setting_configselect extends admin_setting {
6e4dc10f 2458 var $choices;
6e4dc10f 2459
220a90c5 2460 /**
2461 * Constructor
1a41e806 2462 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2463 * @param string $visiblename localised
2464 * @param string $description long localised info
2465 * @param string $defaultsetting
2466 * @param array $choices array of $value=>$label for each selection
2467 */
2468 function admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices) {
2469 $this->choices = $choices;
2470 parent::admin_setting($name, $visiblename, $description, $defaultsetting);
2471 }
2472
2473 /**
2474 * This function may be used in ancestors for lazy loading of choices
2475 * @return true if loaded, false if error
2476 */
2477 function load_choices() {
2478 /*
2479 if (is_array($this->choices)) {
2480 return true;
6e4dc10f 2481 }
220a90c5 2482 .... load choices here
2483 */
2484 return true;
6e4dc10f 2485 }
2486
407d8134 2487 function is_related($query) {
2488 if (parent::is_related($query)) {
2489 return true;
2490 }
2491 if (!$this->load_choices()) {
2492 return false;
2493 }
2494 $textlib = textlib_get_instance();
2495 foreach ($this->choices as $key=>$value) {
2496 if (strpos($textlib->strtolower($key), $query) !== false) {
2497 return true;
2498 }
2499 if (strpos($textlib->strtolower($value), $query) !== false) {
2500 return true;
2501 }
c5d2d0dd 2502 }
407d8134 2503 return false;
2504 }
2505
6e4dc10f 2506 function get_setting() {
220a90c5 2507 return $this->config_read($this->name);
6e4dc10f 2508 }
eef868d1 2509
6e4dc10f 2510 function write_setting($data) {
220a90c5 2511 if (!$this->load_choices() or empty($this->choices)) {
2512 return '';
2513 }
2514 if (!array_key_exists($data, $this->choices)) {
2515 return ''; // ignore it
2516 }
eef868d1 2517
220a90c5 2518 return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2519 }
eef868d1 2520
e2249afe 2521 /**
2522 * Ensure the options are loaded, and generate the HTML for the select
2523 * element and any warning message. Separating this out from output_html
2524 * makes it easier to subclass this class.
2525 *
2526 * @param string $data the option to show as selected.
2527 * @param string $current the currently selected option in the database, null if none.
2528 * @param string $default the default selected option.
2529 * @return array the HTML for the select element, and a warning message.
2530 */
2531 function output_select_html($data, $current, $default, $extraname = '') {
220a90c5 2532 if (!$this->load_choices() or empty($this->choices)) {
e2249afe 2533 return array('', '');
6e4dc10f 2534 }
220a90c5 2535
9c305ba1 2536 $warning = '';
2537 if (is_null($current)) {
e2249afe 2538 // first run
9c305ba1 2539 } else if (empty($current) and (array_key_exists('', $this->choices) or array_key_exists(0, $this->choices))) {
2540 // no warning
2541 } else if (!array_key_exists($current, $this->choices)) {
2542 $warning = get_string('warningcurrentsetting', 'admin', s($current));
e2249afe 2543 if (!is_null($default) and $data == $current) {
9c305ba1 2544 $data = $default; // use default instead of first value when showing the form
2545 }
2546 }
2547
e2249afe 2548 $selecthtml = '<select id="'.$this->get_id().'" name="'.$this->get_full_name().$extraname.'">';
6e4dc10f 2549 foreach ($this->choices as $key => $value) {
220a90c5 2550 // the string cast is needed because key may be integer - 0 is equal to most strings!
e2249afe 2551 $selecthtml .= '<option value="'.$key.'"'.((string)$key==$data ? ' selected="selected"' : '').'>'.$value.'</option>';
eef868d1 2552 }
e2249afe 2553 $selecthtml .= '</select>';
2554 return array($selecthtml, $warning);
2555 }
2556
2557 function output_html($data, $query='') {
2558 $default = $this->get_defaultsetting();
2559 $current = $this->get_setting();
2560
2561 list($selecthtml, $warning) = $this->output_select_html($data, $current, $default);
2562 if (!$selecthtml) {
2563 return '';
2564 }
2565
2566 if (!is_null($default) and array_key_exists($default, $this->choices)) {
2567 $defaultinfo = $this->choices[$default];
2568 } else {
2569 $defaultinfo = NULL;
2570 }
2571
2572 $return = '<div class="form-select defaultsnext">' . $selecthtml . '</div>';
220a90c5 2573
587c7040 2574 return format_admin_setting($this, $this->visiblename, $return, $this->description, true, $warning, $defaultinfo, $query);
6e4dc10f 2575 }
6e4dc10f 2576}
2577
220a90c5 2578/**
2579 * Select multiple items from list
2580 */
6e4dc10f 2581class admin_setting_configmultiselect extends admin_setting_configselect {
220a90c5 2582 /**
2583 * Constructor
1a41e806 2584 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
220a90c5 2585 * @param string $visiblename localised
2586 * @param string $description long localised info
2587 * @param array $defaultsetting array of selected items
2588 * @param array $choices array of $value=>$label for each list item
2589 */
6e4dc10f 2590 function admin_setting_configmultiselect($name, $visiblename, $description, $defaultsetting, $choices) {
2591 parent::admin_setting_configselect($name, $visiblename, $description, $defaultsetting, $choices);
2592 }
2593
2594 function get_setting() {
220a90c5 2595 $result = $this->config_read($this->name);
2596 if (is_null($result)) {
d7933a55 2597 return NULL;
2598 }
220a90c5 2599 if ($result === '') {
2600 return array();
2601 }
2602 return explode(',', $result);
6e4dc10f 2603 }
eef868d1 2604
6e4dc10f 2605 function write_setting($data) {
220a90c5 2606 if (!is_array($data)) {
2607 return ''; //ignore it
2608 }
2609 if (!$this->load_choices() or empty($this->choices)) {
2610 return '';
2611 }
2612
a7ad48fd 2613 unset($data['xxxxx']);
2614
220a90c5 2615 $save = array();
2616 foreach ($data as $value) {
2617 if (!array_key_exists($value, $this->choices)) {
2618 continue; // ignore it
2619 }
2620 $save[] = $value;
2621 }
2622
2623 return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
2624 }
2625
2626 /**
2627 * Is setting related to query text - used when searching
2628 * @param string $query
2629 * @return bool
2630 */
2631 function is_related($query) {
2632 if (!$this->load_choices() or empty($this->choices)) {
2633 return false;
2634 }
2635 if (parent::is_related($query)) {
2636 return true;
2637 }
2638
2639 $textlib = textlib_get_instance();
2640 foreach ($this->choices as $desc) {
2641 if (strpos($textlib->strtolower($desc), $query) !== false) {
2642 return true;
2643 }
2644 }
2645 return false;
2646 }
2647
587c7040 2648 function output_html($data, $query='') {
220a90c5 2649 if (!$this->load_choices() or empty($this->choices)) {
2650 return '';
2651 }
2652 $choices = $this->choices;
2653 $default = $this->get_defaultsetting();
2654 if (is_null($default)) {
2655 $default = array();
2656 }
2657 if (is_null($data)) {
d7933a55 2658 $data = array();
2659 }
220a90c5 2660
2661 $defaults = array();
4413941f 2662 $size = min(10, count($this->choices));
a7ad48fd 2663 $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 2664 $return .= '<select id="'.$this->get_id().'" name="'.$this->get_full_name().'[]" size="'.$size.'" multiple="multiple">';
220a90c5 2665 foreach ($this->choices as $key => $description) {
2666 if (in_array($key, $data)) {
2667 $selected = 'selected="selected"';
2668 } else {
2669 $selected = '';
2670 }
2671 if (in_array($key, $default)) {
2672 $defaults[] = $description;
6e4dc10f 2673 }
220a90c5 2674
2675 $return .= '<option value="'.s($key).'" '.$selected.'>'.$description.'</option>';
2676 }
2677
587c7040 2678 if (is_null($default)) {
2679 $defaultinfo = NULL;
2680 } if (!empty($defaults)) {
2681 $defaultinfo = implode(', ', $defaults);
220a90c5 2682 } else {
587c7040 2683 $defaultinfo = get_string('none');
6e4dc10f 2684 }
eef868d1 2685
587c7040 2686 $return .= '</select></div>';
2687 return format_admin_setting($this, $this->visiblename, $return, $this->description, true, '', $defaultinfo, $query);
6e4dc10f 2688 }
220a90c5 2689}
eef868d1 2690
220a90c5 2691/**
2692 * Time selector
2693 * this is a liiitle bit messy. we're using two selects, but we're returning
2694 * them as an array named after $name (so we only use $name2 internally for the setting)
2695 */
2696class admin_setting_configtime extends admin_setting {
2697 var $name2;
2698
2699 /**
2700 * Constructor
2701 * @param string $hoursname setting for hours
2702 * @param string $minutesname setting for hours
2703 * @param string $visiblename localised
2704 * @param string $description long localised info
2705 * @param array $defaultsetting array representing default time 'h'=>hours, 'm'=>minutes
2706 */
2707 function admin_setting_configtime($hoursname, $minutesname, $visiblename, $description, $defaultsetting) {
2708 $this->name2 = $minutesname;
2709 parent::admin_setting($hoursname, $visiblename, $description, $defaultsetting);
2710 }
2711
2712 function get_setting() {
2713 $result1 = $this->config_read($this->name);
2714 $result2 = $this->config_read($this->name2);
2715 if (is_null($result1) or is_null($result2)) {
2716 return NULL;
2717 }
2718
2719 return array('h' => $result1, 'm' => $result2);
2720 }
2721
2722 function write_setting($data) {
2723 if (!is_array($data)) {
2724 return '';
2725 }
2726
2727 $result = $this->config_write($this->name, (int)$data['h']) && $this->config_write($this->name2, (int)$data['m']);
2728 return ($result ? '' : get_string('errorsetting', 'admin'));
2729 }
2730
587c7040 2731 function output_html($data, $query='') {
220a90c5 2732 $default = $this->get_defaultsetting();
2733
2734 if (is_array($default)) {
587c7040 2735 $defaultinfo = $default['h'].':'.$default['m'];
cc73de71 2736 } else {
587c7040 2737 $defaultinfo = NULL;
6e4dc10f 2738 }
220a90c5 2739
587c7040 2740 $return = '<div class="form-time defaultsnext">'.
220a90c5 2741 '<select id="'.$this->get_id().'h" name="'.$this->get_full_name().'[h]">';
2742 for ($i = 0; $i < 24; $i++) {
2743 $return .= '<option value="'.$i.'"'.($i == $data['h'] ? ' selected="selected"' : '').'>'.$i.'</option>';
6e4dc10f 2744 }
220a90c5 2745 $return .= '</select>:<select id="'.$this->get_id().'m" name="'.$this->get_full_name().'[m]">';
2746 for ($i = 0; $i < 60; $i += 5) {
2747 $return .= '<option value="'.$i.'"'.($i == $data['m'] ? ' selected="selected"' : '').'>'.$i.'</option>';
2748 }
587c7040 2749 $return .= '</select></div>';
2750 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', $defaultinfo, $query);
6e4dc10f 2751 }
2752
2753}
2754
4e639121 2755class admin_setting_configiplist extends admin_setting_configtextarea {
2756 function validate($data) {
2757 if(!empty($data)) {
c5d2d0dd 2758 $ips = explode("\n", $data);
4e639121 2759 } else {
2760 return true;
2761 }
2762 $result = true;
2763 foreach($ips as $ip) {
2764 $ip = trim($ip);
2765 if(preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}$#', $ip, $match) ||
2766 preg_match('#^(\d{1,3})(\.\d{1,3}){0,3}(\/\d{1,2})$#', $ip, $match) ||
2767 preg_match('#^(\d{1,3})(\.\d{1,3}){3}(-\d{1,3})$#', $ip, $match)) {
2768 $result = true;
2769 } else {
2770 $result = false;
2771 break;
2772 }
2773 }
2774 if($result){
2775 return true;
2776 } else {
2777 return get_string('validateerror', 'admin');
2778 }
2779 }
2780}
2781
4413941f 2782/**
2783 * An admin setting for selecting one or more users, who have a particular capability
2784 * in the system context. Warning, make sure the list will never be too long. There is
2785 * no paging or searching of this list.
2786 *
2787 * To correctly get a list of users from this config setting, you need to call the
2788 * get_users_from_config($CFG->mysetting, $capability); function in moodlelib.php.
2789 */
2790class admin_setting_users_with_capability extends admin_setting_configmultiselect {
2791 protected $capability;
2792
2793 /**
2794 * Constructor.
2795 *
2796 * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins.
2797 * @param string $visiblename localised name
2798 * @param string $description localised long description
2799 * @param array $defaultsetting array of usernames
2800 * @param string $capability string capability name.
2801 */
2802 function __construct($name, $visiblename, $description, $defaultsetting, $capability) {
2803 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM),
2804 $capability, 'username,firstname,lastname', 'lastname,firstname');
2805 $choices = array(
2806 '$@NONE@$' => get_string('nobody'),
2807 '$@ALL@$' => get_string('everyonewhocan', 'admin', get_capability_string($capability)),
2808 );
2809 foreach ($users as $user) {
2810 $choices[$user->username] = fullname($user);
2811 }
2812 parent::admin_setting_configmultiselect($name, $visiblename, $description, $defaultsetting, $choices);
2813 }
2814
2815 function get_defaultsetting() {
2816 $this->load_choices();
2817 if (empty($this->defaultsetting)) {
2818 return array('$@NONE@$');
2819 } else if (array_key_exists($this->defaultsetting, $this->choices)) {
2820 return $this->defaultsetting;
2821 } else {
2822 return '';
2823 }
2824 }
2825
2826 function get_setting() {
2827 $result = parent::get_setting();
2828 if (empty($result)) {
2829 $result = array('$@NONE@$');
2830 }
2831 return $result;
2832 }
2833
2834 function write_setting($data) {
2835 // If all is selected, remove any explicit options.
2836 if (in_array('$@ALL@$', $data)) {
2837 $data = array('$@ALL@$');
2838 }
2839 // None never needs to be writted to the DB.
2840 if (in_array('$@NONE@$', $data)) {
2841 unset($data[array_search('$@NONE@$', $data)]);
2842 }
2843 return parent::write_setting($data);
2844 }
2845}
2846
220a90c5 2847/**
2848 * Special checkbox for calendar - resets SESSION vars.
2849 */
6e4dc10f 2850class admin_setting_special_adminseesall extends admin_setting_configcheckbox {
6e4dc10f 2851 function admin_setting_special_adminseesall() {
220a90c5 2852 parent::admin_setting_configcheckbox('calendar_adminseesall', get_string('adminseesall', 'admin'),
2853 get_string('helpadminseesall', 'admin'), '0');
6e4dc10f 2854 }
2855
2856 function write_setting($data) {
2857 global $SESSION;
2858 unset($SESSION->cal_courses_shown);
220a90c5 2859 return parent::write_setting($data);
6e4dc10f 2860 }
2861}
2862
392e7363 2863/**
2864 * Special select for settings that are altered in setup.php and can not be altered on the fly
2865 */
2866class admin_setting_special_selectsetup extends admin_setting_configselect {
2867 function get_setting() {
2868 // read directly from db!
2869 return get_config(NULL, $this->name);
2870 }
2871
2872 function write_setting($data) {
2873 global $CFG;
2874 // do not change active CFG setting!
2875 $current = $CFG->{$this->name};
2876 $result = parent::write_setting($data);
2877 $CFG->{$this->name} = $current;
2878 return $result;
2879 }
2880}
2881
220a90c5 2882/**
2883 * Special select for frontpage - stores data in course table
2884 */
6e4dc10f 2885class admin_setting_sitesetselect extends admin_setting_configselect {
6e4dc10f 2886 function get_setting() {
2887 $site = get_site();
220a90c5 2888 return $site->{$this->name};
6e4dc10f 2889 }
eef868d1 2890
6e4dc10f 2891 function write_setting($data) {
f33e1ed4 2892 global $DB;
6e4dc10f 2893 if (!in_array($data, array_keys($this->choices))) {
220a90c5 2894 return get_string('errorsetting', 'admin');
6e4dc10f 2895 }
2896 $record = new stdClass();
220a90c5 2897 $record->id = SITEID;
2898 $temp = $this->name;
2899 $record->$temp = $data;
6e4dc10f 2900 $record->timemodified = time();
f33e1ed4 2901 return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2902 }
6e4dc10f 2903}
2904
220a90c5 2905/**
2906 * Special select - lists on the frontpage - hacky
2907 */
2908class admin_setting_courselist_frontpage extends admin_setting {
2909 var $choices;
6e4dc10f 2910
e0f6e995 2911 function admin_setting_courselist_frontpage($loggedin) {
6e4dc10f 2912 global $CFG;
220a90c5 2913 require_once($CFG->dirroot.'/course/lib.php');
2914 $name = 'frontpage'.($loggedin ? 'loggedin' : '');
2915 $visiblename = get_string('frontpage'.($loggedin ? 'loggedin' : ''),'admin');
2916 $description = get_string('configfrontpage'.($loggedin ? 'loggedin' : ''),'admin');
2917 $defaults = array(FRONTPAGECOURSELIST);
2918 parent::admin_setting($name, $visiblename, $description, $defaults);
6e4dc10f 2919 }
eef868d1 2920
220a90c5 2921 function load_choices() {
c7da4357 2922 global $DB;
220a90c5 2923 if (is_array($this->choices)) {
2924 return true;
2925 }
2926 $this->choices = array(FRONTPAGENEWS => get_string('frontpagenews'),
2927 FRONTPAGECOURSELIST => get_string('frontpagecourselist'),
2928 FRONTPAGECATEGORYNAMES => get_string('frontpagecategorynames'),
2929 FRONTPAGECATEGORYCOMBO => get_string('frontpagecategorycombo'),
2930 'none' => get_string('none'));
c7da4357 2931 if ($this->name == 'frontpage' and $DB->count_records('course') > FRONTPAGECOURSELIMIT) {
220a90c5 2932 unset($this->choices[FRONTPAGECOURSELIST]);
2933 }
2934 return true;
2935 }
6e4dc10f 2936 function get_setting() {
220a90c5 2937 $result = $this->config_read($this->name);
2938 if (is_null($result)) {
2939 return NULL;
2940 }
2941 if ($result === '') {
2942 return array();
2943 }
2944 return explode(',', $result);
6e4dc10f 2945 }
eef868d1 2946
6e4dc10f 2947 function write_setting($data) {
220a90c5 2948 if (!is_array($data)) {
2949 return '';
6e4dc10f 2950 }
220a90c5 2951 $this->load_choices();
2952 $save = array();
6e4dc10f 2953 foreach($data as $datum) {
220a90c5 2954 if ($datum == 'none' or !array_key_exists($datum, $this->choices)) {
2955 continue;
6e4dc10f 2956 }
220a90c5 2957 $save[$datum] = $datum; // no duplicates
6e4dc10f 2958 }
220a90c5 2959 return ($this->config_write($this->name, implode(',', $save)) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 2960 }
eef868d1 2961
587c7040 2962 function output_html($data, $query='') {
220a90c5 2963 $this->load_choices();
2964 $currentsetting = array();
2965 foreach ($data as $key) {
2966 if ($key != 'none' and array_key_exists($key, $this->choices)) {
2967 $currentsetting[] = $key; // already selected first
6e4dc10f 2968 }
2969 }
220a90c5 2970
0a7e84c3 2971 $return = '<div class="form-group">';
6e4dc10f 2972 for ($i = 0; $i < count($this->choices) - 1; $i++) {
220a90c5 2973 if (!array_key_exists($i, $currentsetting)) {
2974 $currentsetting[$i] = 'none'; //none
2975 }
2976 $return .='<select class="form-select" id="'.$this->get_id().$i.'" name="'.$this->get_full_name().'[]">';
6e4dc10f 2977 foreach ($this->choices as $key => $value) {
220a90c5 2978 $return .= '<option value="'.$key.'"'.("$key" == $currentsetting[$i] ? ' selected="selected"' : '').'>'.$value.'</option>';
6e4dc10f 2979 }
2980 $return .= '</select>';
2981 if ($i !== count($this->choices) - 2) {
975211bb 2982 $return .= '<br />';
6e4dc10f 2983 }
2984 }
0a7e84c3 2985 $return .= '</div>';
2986
587c7040 2987 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
6e4dc10f 2988 }
2989}
2990
220a90c5 2991/**
2992 * Special checkbox for frontpage - stores data in course table
2993 */
6e4dc10f 2994class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox {
6e4dc10f 2995 function get_setting() {
2996 $site = get_site();
8e5da17a 2997 return $site->{$this->name};
6e4dc10f 2998 }
eef868d1 2999
6e4dc10f 3000 function write_setting($data) {
f33e1ed4 3001 global $DB;
220a90c5 3002 $record = new object();
3003 $record->id = SITEID;
3004 $record->{$this->name} = ($data == '1' ? 1 : 0);
3005 $record->timemodified = time();
f33e1ed4 3006 return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 3007 }
6e4dc10f 3008}
3009
220a90c5 3010/**
3011 * Special text for frontpage - stores data in course table.
3012 * Empty string means not set here. Manual setting is required.
3013 */
6e4dc10f 3014class admin_setting_sitesettext extends admin_setting_configtext {
6e4dc10f 3015 function get_setting() {
3016 $site = get_site();
8e5da17a 3017 return $site->{$this->name} != '' ? $site->{$this->name} : NULL;
6e4dc10f 3018 }
90cfbd0a 3019
b89639f9 3020 function validate($data) {
294ce987 3021 $cleaned = clean_param($data, PARAM_MULTILANG);
e33fbf87 3022 if ($cleaned === '') {
3023 return get_string('required');
3024 }
3025 if ("$data" == "$cleaned") { // implicit conversion to string is needed to do exact comparison
3026 return true;
3027 } else {
3028 return get_string('validateerror', 'admin');
b89639f9 3029 }
b89639f9 3030 }
3031
6e4dc10f 3032 function write_setting($data) {
f33e1ed4 3033 global $DB;
b89639f9 3034 $data = trim($data);
c5d2d0dd 3035 $validated = $this->validate($data);
e33fbf87 3036 if ($validated !== true) {
3037 return $validated;
90cfbd0a 3038 }
eef868d1 3039
220a90c5 3040 $record = new object();
3041 $record->id = SITEID;
f33e1ed4 3042 $record->{$this->name} = $data;
220a90c5 3043 $record->timemodified = time();
f33e1ed4 3044 return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error'));
6e4dc10f 3045 }
6e4dc10f 3046}
3047
220a90c5 3048/**
3049 * Special text editor for site description.
3050 */
6e4dc10f 3051class admin_setting_special_frontpagedesc extends admin_setting {
6e4dc10f 3052 function admin_setting_special_frontpagedesc() {
220a90c5 3053 parent::admin_setting('summary', get_string('frontpagedescription'), get_string('frontpagedescriptionhelp'), NULL);
6e4dc10f 3054 }
eef868d1 3055
6e4dc10f 3056 function get_setting() {
6e4dc10f 3057 $site = get_site();
c626c2f4 3058 return $site->{$this->name};
3059 }
eef868d1 3060
6e4dc10f 3061 function write_setting($data) {
f33e1ed4 3062 global $DB;
c626c2f4 3063 $record = new object();
220a90c5 3064 $record->id = SITEID;
f33e1ed4 3065 $record->{$this->name} = $data;
c626c2f4 3066 $record->timemodified = time();
f33e1ed4 3067 return($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 3068 }
3069
587c7040 3070 function output_html($data, $query='') {
cb6c02c4 3071 global $CFG;
6e4dc10f 3072
220a90c5 3073 $CFG->adminusehtmleditor = can_use_html_editor();
8c37106d 3074 $return = '<div class="form-htmlarea">'.print_textarea($CFG->adminusehtmleditor, 15, 60, 0, 0, $this->get_full_name(), $data, 0, true, 'summary') .'</div>';
220a90c5 3075
587c7040 3076 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
220a90c5 3077 }
3078}
6e4dc10f 3079
3080class admin_setting_special_editorfontlist extends admin_setting {
3081
3082 var $items;
3083
3084 function admin_setting_special_editorfontlist() {
3085 global $CFG;
3086 $name = 'editorfontlist';
3087 $visiblename = get_string('editorfontlist', 'admin');
3088 $description = get_string('configeditorfontlist', 'admin');
6e4dc10f 3089 $defaults = array('k0' => 'Trebuchet',
3090 'v0' => 'Trebuchet MS,Verdana,Arial,Helvetica,sans-serif',
3091 'k1' => 'Arial',
3092 'v1' => 'arial,helvetica,sans-serif',
3093 'k2' => 'Courier New',
3094 'v2' => 'courier new,courier,monospace',
3095 'k3' => 'Georgia',
3096 'v3' => 'georgia,times new roman,times,serif',
3097 'k4' => 'Tahoma',
3098 'v4' => 'tahoma,arial,helvetica,sans-serif',
3099 'k5' => 'Times New Roman',
3100 'v5' => 'times new roman,times,serif',
3101 'k6' => 'Verdana',
3102 'v6' => 'verdana,arial,helvetica,sans-serif',
3103 'k7' => 'Impact',
3104 'v7' => 'impact',
3105 'k8' => 'Wingdings',
3106 'v8' => 'wingdings');
3107 parent::admin_setting($name, $visiblename, $description, $defaults);
3108 }
eef868d1 3109
6e4dc10f 3110 function get_setting() {
cc73de71 3111 global $CFG;
220a90c5 3112 $result = $this->config_read($this->name);
3113 if (is_null($result)) {
cc73de71 3114 return NULL;
3115 }
220a90c5 3116 $i = 0;
3117 $currentsetting = array();
3118 $items = explode(';', $result);
3119 foreach ($items as $item) {
3120 $item = explode(':', $item);
3121 $currentsetting['k'.$i] = $item[0];
3122 $currentsetting['v'.$i] = $item[1];
3123 $i++;
3124 }
3125 return $currentsetting;
6e4dc10f 3126 }
eef868d1 3127
6e4dc10f 3128 function write_setting($data) {
eef868d1 3129
6e4dc10f 3130 // there miiight be an easier way to do this :)
3131 // if this is changed, make sure the $defaults array above is modified so that this
3132 // function processes it correctly
eef868d1 3133
6e4dc10f 3134 $keys = array();
3135 $values = array();
eef868d1 3136
6e4dc10f 3137 foreach ($data as $key => $value) {
3138 if (substr($key,0,1) == 'k') {
3139 $keys[substr($key,1)] = $value;
3140 } elseif (substr($key,0,1) == 'v') {
3141 $values[substr($key,1)] = $value;
3142 }
3143 }
eef868d1 3144
220a90c5 3145 $result = array();
6e4dc10f 3146 for ($i = 0; $i < count($keys); $i++) {
3147 if (($keys[$i] !== '') && ($values[$i] !== '')) {
220a90c5 3148 $result[] = clean_param($keys[$i],PARAM_NOTAGS).':'.clean_param($values[$i], PARAM_NOTAGS);
6e4dc10f 3149 }
3150 }
eef868d1 3151
220a90c5 3152 return ($this->config_write($this->name, implode(';', $result)) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 3153 }
eef868d1 3154
587c7040 3155 function output_html($data, $query='') {
220a90c5 3156 $fullname = $this->get_full_name();
1beed35f 3157 $return = '<div class="form-group">';
220a90c5 3158 for ($i = 0; $i < count($data) / 2; $i++) {
3159 $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
6e4dc10f 3160 $return .= '&nbsp;&nbsp;';
220a90c5 3161 $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
6e4dc10f 3162 }
220a90c5 3163 $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
6e4dc10f 3164 $return .= '&nbsp;&nbsp;';
220a90c5 3165 $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3166 $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
6e4dc10f 3167 $return .= '&nbsp;&nbsp;';
220a90c5 3168 $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
1beed35f 3169 $return .= '</div>';
6153cf58 3170
587c7040 3171 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
6e4dc10f 3172 }
eef868d1 3173
6e4dc10f 3174}
3175
93c61c18 3176class admin_setting_emoticons extends admin_setting {
3177
3178 var $items;
3179
3180 function admin_setting_emoticons() {
3181 global $CFG;
3182 $name = 'emoticons';
3183 $visiblename = get_string('emoticons', 'admin');
3184 $description = get_string('configemoticons', 'admin');
3185 $defaults = array('k0' => ':-)',
3186 'v0' => 'smiley',
3187 'k1' => ':)',
3188 'v1' => 'smiley',
3189 'k2' => ':-D',
3190 'v2' => 'biggrin',
3191 'k3' => ';-)',
3192 'v3' => 'wink',
3193 'k4' => ':-/',
3194 'v4' => 'mixed',
3195 'k5' => 'V-.',
3196 'v5' => 'thoughtful',
3197 'k6' => ':-P',
3198 'v6' => 'tongueout',
3199 'k7' => 'B-)',
3200 'v7' => 'cool',
3201 'k8' => '^-)',
3202 'v8' => 'approve',
3203 'k9' => '8-)',
3204 'v9' => 'wideeyes',
3205 'k10' => ':o)',
3206 'v10' => 'clown',
3207 'k11' => ':-(',
3208 'v11' => 'sad',
3209 'k12' => ':(',
3210 'v12' => 'sad',
3211 'k13' => '8-.',
3212 'v13' => 'shy',
3213 'k14' => ':-I',
3214 'v14' => 'blush',
3215 'k15' => ':-X',
3216 'v15' => 'kiss',
3217 'k16' => '8-o',
3218 'v16' => 'surprise',
3219 'k17' => 'P-|',
3220 'v17' => 'blackeye',
3221 'k18' => '8-[',
3222 'v18' => 'angry',
3223 'k19' => 'xx-P',
3224 'v19' => 'dead',
3225 'k20' => '|-.',
3226 'v20' => 'sleepy',
3227 'k21' => '}-]',
3228 'v21' => 'evil',
3229 'k22' => '(h)',
3230 'v22' => 'heart',
3231 'k23' => '(heart)',
3232 'v23' => 'heart',
3233 'k24' => '(y)',
3234 'v24' => 'yes',
3235 'k25' => '(n)',
3236 'v25' => 'no',
3237 'k26' => '(martin)',
3238 'v26' => 'martin',
3239 'k27' => '( )',
3240 'v27' => 'egg');
3241 parent::admin_setting($name, $visiblename, $description, $defaults);
3242 }
3243
3244 function get_setting() {
3245 global $CFG;
220a90c5 3246 $result = $this->config_read($this->name);
3247 if (is_null($result)) {
93c61c18 3248 return NULL;
3249 }
220a90c5 3250 $i = 0;
3251 $currentsetting = array();
3252 $items = explode('{;}', $result);
3253 foreach ($items as $item) {
3254 $item = explode('{:}', $item);
3255 $currentsetting['k'.$i] = $item[0];
3256 $currentsetting['v'.$i] = $item[1];
3257 $i++;
3258 }
3259 return $currentsetting;
93c61c18 3260 }
3261
3262 function write_setting($data) {
3263
3264 // there miiight be an easier way to do this :)
3265 // if this is changed, make sure the $defaults array above is modified so that this
3266 // function processes it correctly
3267
3268 $keys = array();
3269 $values = array();
3270
3271 foreach ($data as $key => $value) {
3272 if (substr($key,0,1) == 'k') {
3273 $keys[substr($key,1)] = $value;
3274 } elseif (substr($key,0,1) == 'v') {
3275 $values[substr($key,1)] = $value;
3276 }
3277 }
3278
220a90c5 3279 $result = array();
93c61c18 3280 for ($i = 0; $i < count($keys); $i++) {
3281 if (($keys[$i] !== '') && ($values[$i] !== '')) {
220a90c5 3282 $result[] = clean_param($keys[$i],PARAM_NOTAGS).'{:}'.clean_param($values[$i], PARAM_NOTAGS);
93c61c18 3283 }
3284 }
3285
220a90c5 3286 return ($this->config_write($this->name, implode('{;}', $result)) ? '' : get_string('errorsetting', 'admin').$this->visiblename.'<br />');
93c61c18 3287 }
3288
587c7040 3289 function output_html($data, $query='') {
220a90c5 3290 $fullname = $this->get_full_name();
93c61c18 3291 $return = '<div class="form-group">';
220a90c5 3292 for ($i = 0; $i < count($data) / 2; $i++) {
3293 $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="'.$data['k'.$i].'" />';
93c61c18 3294 $return .= '&nbsp;&nbsp;';
220a90c5 3295 $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="'.$data['v'.$i].'" /><br />';
93c61c18 3296 }
220a90c5 3297 $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.$i.']" value="" />';
93c61c18 3298 $return .= '&nbsp;&nbsp;';
220a90c5 3299 $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.$i.']" value="" /><br />';
3300 $return .= '<input type="text" class="form-text" name="'.$fullname.'[k'.($i + 1).']" value="" />';
93c61c18 3301 $return .= '&nbsp;&nbsp;';
220a90c5 3302 $return .= '<input type="text" class="form-text" name="'.$fullname.'[v'.($i + 1).']" value="" />';
93c61c18 3303 $return .= '</div>';
3304
587c7040 3305 return format_admin_setting($this, $this->visiblename, $return, $this->description, false, '', NULL, $query);
93c61c18 3306 }
3307
3308}
3309
6e4dc10f 3310class admin_setting_special_editorhidebuttons extends admin_setting {
6e4dc10f 3311 var $items;
3312
3313 function admin_setting_special_editorhidebuttons() {
220a90c5 3314 parent::admin_setting('editorhidebuttons', get_string('editorhidebuttons', 'admin'),
3315 get_string('confeditorhidebuttons', 'admin'), array());
6e4dc10f 3316 // weird array... buttonname => buttonimage (assume proper path appended). if you leave buttomimage blank, text will be printed instead
3317 $this->items = array('fontname' => '',
3318 'fontsize' => '',
3319 'formatblock' => '',
3320 'bold' => 'ed_format_bold.gif',
3321 'italic' => 'ed_format_italic.gif',
3322 'underline' => 'ed_format_underline.gif',
3323 'strikethrough' => 'ed_format_strike.gif',
3324 'subscript' => 'ed_format_sub.gif',
3325 'superscript' => 'ed_format_sup.gif',
3326 'copy' => 'ed_copy.gif',
3327 'cut' => 'ed_cut.gif',
3328 'paste' => 'ed_paste.gif',
3329 'clean' => 'ed_wordclean.gif',
3330 'undo' => 'ed_undo.gif',
3331 'redo' => 'ed_redo.gif',
3332 'justifyleft' => 'ed_align_left.gif',
3333 'justifycenter' => 'ed_align_center.gif',
3334 'justifyright' => 'ed_align_right.gif',
3335 'justifyfull' => 'ed_align_justify.gif',
3336 'lefttoright' => 'ed_left_to_right.gif',
3337 'righttoleft' => 'ed_right_to_left.gif',
3338 'insertorderedlist' => 'ed_list_num.gif',
3339 'insertunorderedlist' => 'ed_list_bullet.gif',
3340 'outdent' => 'ed_indent_less.gif',
3341 'indent' => 'ed_indent_more.gif',
3342 'forecolor' => 'ed_color_fg.gif',
3343 'hilitecolor' => 'ed_color_bg.gif',
3344 'inserthorizontalrule' => 'ed_hr.gif',
3345 'createanchor' => 'ed_anchor.gif',
3346 'createlink' => 'ed_link.gif',
3347 'unlink' => 'ed_unlink.gif',
3348 'insertimage' => 'ed_image.gif',
3349 'inserttable' => 'insert_table.gif',
3350 'insertsmile' => 'em.icon.smile.gif',
3351 'insertchar' => 'icon_ins_char.gif',
3352 'spellcheck' => 'spell-check.gif',
3353 'htmlmode' => 'ed_html.gif',
3354 'popupeditor' => 'fullscreen_maximize.gif',
3355 'search_replace' => 'ed_replace.gif');
3356 }
3357
3358 function get_setting() {
220a90c5 3359 $result = $this->config_read($this->name);
3360 if (is_null($result)) {
3361 return NULL;
3362 }
3363 if ($result === '') {
3364 return array();
3365 }
5ac776ac 3366 return explode(' ', $result);
6e4dc10f 3367 }
3368
3369 function write_setting($data) {
220a90c5 3370 if (!is_array($data)) {
3371 return ''; // ignore it
3372 }
3373 unset($data['xxxxx']);
6e4dc10f 3374 $result = array();
220a90c5 3375
6e4dc10f 3376 foreach ($data as $key => $value) {
5ac776ac 3377 if (!isset($this->items[$key])) {
220a90c5 3378 return get_string('errorsetting', 'admin');
6e4dc10f 3379 }
3380 if ($value == '1') {
3381 $result[] = $key;
3382 }
3383 }
220a90c5 3384 return ($this->config_write($this->name, implode(' ', $result)) ? '' : get_string('errorsetting', 'admin'));
6e4dc10f 3385 }
3386
587c7040 3387 function output_html($data, $query='') {
eef868d1 3388
6e4dc10f 3389 global $CFG;
eef868d1 3390
6e4dc10f 3391 // checkboxes with input name="$this->name[$key]" value="1"
3392 // we do 15 fields per column