PageRenderTime 61ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/adminlib.php

https://github.com/mijiacang/moodle
PHP | 7313 lines | 4343 code | 727 blank | 2243 comment | 601 complexity | 6bca0fd599f1aad2ec9645aff8e87da0 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, BSD-3-Clause, AGPL-3.0, MPL-2.0-no-copyleft-exception, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Functions and classes used during installation, upgrades and for admin settings.
  18. *
  19. * ADMIN SETTINGS TREE INTRODUCTION
  20. *
  21. * This file performs the following tasks:
  22. * -it defines the necessary objects and interfaces to build the Moodle
  23. * admin hierarchy
  24. * -it defines the admin_externalpage_setup()
  25. *
  26. * ADMIN_SETTING OBJECTS
  27. *
  28. * Moodle settings are represented by objects that inherit from the admin_setting
  29. * class. These objects encapsulate how to read a setting, how to write a new value
  30. * to a setting, and how to appropriately display the HTML to modify the setting.
  31. *
  32. * ADMIN_SETTINGPAGE OBJECTS
  33. *
  34. * The admin_setting objects are then grouped into admin_settingpages. The latter
  35. * appear in the Moodle admin tree block. All interaction with admin_settingpage
  36. * objects is handled by the admin/settings.php file.
  37. *
  38. * ADMIN_EXTERNALPAGE OBJECTS
  39. *
  40. * There are some settings in Moodle that are too complex to (efficiently) handle
  41. * with admin_settingpages. (Consider, for example, user management and displaying
  42. * lists of users.) In this case, we use the admin_externalpage object. This object
  43. * places a link to an external PHP file in the admin tree block.
  44. *
  45. * If you're using an admin_externalpage object for some settings, you can take
  46. * advantage of the admin_externalpage_* functions. For example, suppose you wanted
  47. * to add a foo.php file into admin. First off, you add the following line to
  48. * admin/settings/first.php (at the end of the file) or to some other file in
  49. * admin/settings:
  50. * <code>
  51. * $ADMIN->add('userinterface', new admin_externalpage('foo', get_string('foo'),
  52. * $CFG->wwwdir . '/' . '$CFG->admin . '/foo.php', 'some_role_permission'));
  53. * </code>
  54. *
  55. * Next, in foo.php, your file structure would resemble the following:
  56. * <code>
  57. * require(dirname(dirname(dirname(__FILE__))).'/config.php');
  58. * require_once($CFG->libdir.'/adminlib.php');
  59. * admin_externalpage_setup('foo');
  60. * // functionality like processing form submissions goes here
  61. * echo $OUTPUT->header();
  62. * // your HTML goes here
  63. * echo $OUTPUT->footer();
  64. * </code>
  65. *
  66. * The admin_externalpage_setup() function call ensures the user is logged in,
  67. * and makes sure that they have the proper role permission to access the page.
  68. * It also configures all $PAGE properties needed for navigation.
  69. *
  70. * ADMIN_CATEGORY OBJECTS
  71. *
  72. * Above and beyond all this, we have admin_category objects. These objects
  73. * appear as folders in the admin tree block. They contain admin_settingpage's,
  74. * admin_externalpage's, and other admin_category's.
  75. *
  76. * OTHER NOTES
  77. *
  78. * admin_settingpage's, admin_externalpage's, and admin_category's all inherit
  79. * from part_of_admin_tree (a pseudointerface). This interface insists that
  80. * a class has a check_access method for access permissions, a locate method
  81. * used to find a specific node in the admin tree and find parent path.
  82. *
  83. * admin_category's inherit from parentable_part_of_admin_tree. This pseudo-
  84. * interface ensures that the class implements a recursive add function which
  85. * accepts a part_of_admin_tree object and searches for the proper place to
  86. * put it. parentable_part_of_admin_tree implies part_of_admin_tree.
  87. *
  88. * Please note that the $this->name field of any part_of_admin_tree must be
  89. * UNIQUE throughout the ENTIRE admin tree.
  90. *
  91. * The $this->name field of an admin_setting object (which is *not* part_of_
  92. * admin_tree) must be unique on the respective admin_settingpage where it is
  93. * used.
  94. *
  95. * Original author: Vincenzo K. Marcovecchio
  96. * Maintainer: Petr Skoda
  97. *
  98. * @package core
  99. * @subpackage admin
  100. * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
  101. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  102. */
  103. defined('MOODLE_INTERNAL') || die();
  104. /// Add libraries
  105. require_once($CFG->libdir.'/ddllib.php');
  106. require_once($CFG->libdir.'/xmlize.php');
  107. define('INSECURE_DATAROOT_WARNING', 1);
  108. define('INSECURE_DATAROOT_ERROR', 2);
  109. /**
  110. * Automatically clean-up all plugin data and remove the plugin DB tables
  111. *
  112. * @param string $type The plugin type, eg. 'mod', 'qtype', 'workshopgrading' etc.
  113. * @param string $name The plugin name, eg. 'forum', 'multichoice', 'accumulative' etc.
  114. * @uses global $OUTPUT to produce notices and other messages
  115. * @return void
  116. */
  117. function uninstall_plugin($type, $name) {
  118. global $CFG, $DB, $OUTPUT;
  119. // recursively uninstall all module subplugins first
  120. if ($type === 'mod') {
  121. if (file_exists("$CFG->dirroot/mod/$name/db/subplugins.php")) {
  122. $subplugins = array();
  123. include("$CFG->dirroot/mod/$name/db/subplugins.php");
  124. foreach ($subplugins as $subplugintype=>$dir) {
  125. $instances = get_plugin_list($subplugintype);
  126. foreach ($instances as $subpluginname => $notusedpluginpath) {
  127. uninstall_plugin($subplugintype, $subpluginname);
  128. }
  129. }
  130. }
  131. }
  132. $component = $type . '_' . $name; // eg. 'qtype_multichoice' or 'workshopgrading_accumulative' or 'mod_forum'
  133. if ($type === 'mod') {
  134. $pluginname = $name; // eg. 'forum'
  135. if (get_string_manager()->string_exists('modulename', $component)) {
  136. $strpluginname = get_string('modulename', $component);
  137. } else {
  138. $strpluginname = $component;
  139. }
  140. } else {
  141. $pluginname = $component;
  142. if (get_string_manager()->string_exists('pluginname', $component)) {
  143. $strpluginname = get_string('pluginname', $component);
  144. } else {
  145. $strpluginname = $component;
  146. }
  147. }
  148. echo $OUTPUT->heading($pluginname);
  149. $plugindirectory = get_plugin_directory($type, $name);
  150. $uninstalllib = $plugindirectory . '/db/uninstall.php';
  151. if (file_exists($uninstalllib)) {
  152. require_once($uninstalllib);
  153. $uninstallfunction = 'xmldb_' . $pluginname . '_uninstall'; // eg. 'xmldb_workshop_uninstall()'
  154. if (function_exists($uninstallfunction)) {
  155. if (!$uninstallfunction()) {
  156. echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $pluginname);
  157. }
  158. }
  159. }
  160. if ($type === 'mod') {
  161. // perform cleanup tasks specific for activity modules
  162. if (!$module = $DB->get_record('modules', array('name' => $name))) {
  163. print_error('moduledoesnotexist', 'error');
  164. }
  165. // delete all the relevant instances from all course sections
  166. if ($coursemods = $DB->get_records('course_modules', array('module' => $module->id))) {
  167. foreach ($coursemods as $coursemod) {
  168. if (!delete_mod_from_section($coursemod->id, $coursemod->section)) {
  169. echo $OUTPUT->notification("Could not delete the $strpluginname with id = $coursemod->id from section $coursemod->section");
  170. }
  171. }
  172. }
  173. // clear course.modinfo for courses that used this module
  174. $sql = "UPDATE {course}
  175. SET modinfo=''
  176. WHERE id IN (SELECT DISTINCT course
  177. FROM {course_modules}
  178. WHERE module=?)";
  179. $DB->execute($sql, array($module->id));
  180. // delete all the course module records
  181. $DB->delete_records('course_modules', array('module' => $module->id));
  182. // delete module contexts
  183. if ($coursemods) {
  184. foreach ($coursemods as $coursemod) {
  185. if (!delete_context(CONTEXT_MODULE, $coursemod->id)) {
  186. echo $OUTPUT->notification("Could not delete the context for $strpluginname with id = $coursemod->id");
  187. }
  188. }
  189. }
  190. // delete the module entry itself
  191. $DB->delete_records('modules', array('name' => $module->name));
  192. // cleanup the gradebook
  193. require_once($CFG->libdir.'/gradelib.php');
  194. grade_uninstalled_module($module->name);
  195. // Perform any custom uninstall tasks
  196. if (file_exists($CFG->dirroot . '/mod/' . $module->name . '/lib.php')) {
  197. require_once($CFG->dirroot . '/mod/' . $module->name . '/lib.php');
  198. $uninstallfunction = $module->name . '_uninstall';
  199. if (function_exists($uninstallfunction)) {
  200. debugging("{$uninstallfunction}() has been deprecated. Use the plugin's db/uninstall.php instead", DEBUG_DEVELOPER);
  201. if (!$uninstallfunction()) {
  202. echo $OUTPUT->notification('Encountered a problem running uninstall function for '. $module->name.'!');
  203. }
  204. }
  205. }
  206. } else if ($type === 'enrol') {
  207. // NOTE: this is a bit brute force way - it will not trigger events and hooks properly
  208. // nuke all role assignments
  209. role_unassign_all(array('component'=>$component));
  210. // purge participants
  211. $DB->delete_records_select('user_enrolments', "enrolid IN (SELECT id FROM {enrol} WHERE enrol = ?)", array($name));
  212. // purge enrol instances
  213. $DB->delete_records('enrol', array('enrol'=>$name));
  214. // tweak enrol settings
  215. if (!empty($CFG->enrol_plugins_enabled)) {
  216. $enabledenrols = explode(',', $CFG->enrol_plugins_enabled);
  217. $enabledenrols = array_unique($enabledenrols);
  218. $enabledenrols = array_flip($enabledenrols);
  219. unset($enabledenrols[$name]);
  220. $enabledenrols = array_flip($enabledenrols);
  221. if (is_array($enabledenrols)) {
  222. set_config('enrol_plugins_enabled', implode(',', $enabledenrols));
  223. }
  224. }
  225. }
  226. // perform clean-up task common for all the plugin/subplugin types
  227. // delete calendar events
  228. $DB->delete_records('event', array('modulename' => $pluginname));
  229. // delete all the logs
  230. $DB->delete_records('log', array('module' => $pluginname));
  231. // delete log_display information
  232. $DB->delete_records('log_display', array('component' => $component));
  233. // delete the module configuration records
  234. unset_all_config_for_plugin($pluginname);
  235. // delete the plugin tables
  236. $xmldbfilepath = $plugindirectory . '/db/install.xml';
  237. drop_plugin_tables($pluginname, $xmldbfilepath, false);
  238. // delete the capabilities that were defined by this module
  239. capabilities_cleanup($component);
  240. // remove event handlers and dequeue pending events
  241. events_uninstall($component);
  242. echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
  243. }
  244. /**
  245. * Returns the version of installed component
  246. *
  247. * @param string $component component name
  248. * @param string $source either 'disk' or 'installed' - where to get the version information from
  249. * @return string|bool version number or false if the component is not found
  250. */
  251. function get_component_version($component, $source='installed') {
  252. global $CFG, $DB;
  253. list($type, $name) = normalize_component($component);
  254. // moodle core or a core subsystem
  255. if ($type === 'core') {
  256. if ($source === 'installed') {
  257. if (empty($CFG->version)) {
  258. return false;
  259. } else {
  260. return $CFG->version;
  261. }
  262. } else {
  263. if (!is_readable($CFG->dirroot.'/version.php')) {
  264. return false;
  265. } else {
  266. $version = null; //initialize variable for IDEs
  267. include($CFG->dirroot.'/version.php');
  268. return $version;
  269. }
  270. }
  271. }
  272. // activity module
  273. if ($type === 'mod') {
  274. if ($source === 'installed') {
  275. return $DB->get_field('modules', 'version', array('name'=>$name));
  276. } else {
  277. $mods = get_plugin_list('mod');
  278. if (empty($mods[$name]) or !is_readable($mods[$name].'/version.php')) {
  279. return false;
  280. } else {
  281. $module = new stdclass();
  282. include($mods[$name].'/version.php');
  283. return $module->version;
  284. }
  285. }
  286. }
  287. // block
  288. if ($type === 'block') {
  289. if ($source === 'installed') {
  290. return $DB->get_field('block', 'version', array('name'=>$name));
  291. } else {
  292. $blocks = get_plugin_list('block');
  293. if (empty($blocks[$name]) or !is_readable($blocks[$name].'/version.php')) {
  294. return false;
  295. } else {
  296. $plugin = new stdclass();
  297. include($blocks[$name].'/version.php');
  298. return $plugin->version;
  299. }
  300. }
  301. }
  302. // all other plugin types
  303. if ($source === 'installed') {
  304. return get_config($type.'_'.$name, 'version');
  305. } else {
  306. $plugins = get_plugin_list($type);
  307. if (empty($plugins[$name])) {
  308. return false;
  309. } else {
  310. $plugin = new stdclass();
  311. include($plugins[$name].'/version.php');
  312. return $plugin->version;
  313. }
  314. }
  315. }
  316. /**
  317. * Delete all plugin tables
  318. *
  319. * @param string $name Name of plugin, used as table prefix
  320. * @param string $file Path to install.xml file
  321. * @param bool $feedback defaults to true
  322. * @return bool Always returns true
  323. */
  324. function drop_plugin_tables($name, $file, $feedback=true) {
  325. global $CFG, $DB;
  326. // first try normal delete
  327. if (file_exists($file) and $DB->get_manager()->delete_tables_from_xmldb_file($file)) {
  328. return true;
  329. }
  330. // then try to find all tables that start with name and are not in any xml file
  331. $used_tables = get_used_table_names();
  332. $tables = $DB->get_tables();
  333. /// Iterate over, fixing id fields as necessary
  334. foreach ($tables as $table) {
  335. if (in_array($table, $used_tables)) {
  336. continue;
  337. }
  338. if (strpos($table, $name) !== 0) {
  339. continue;
  340. }
  341. // found orphan table --> delete it
  342. if ($DB->get_manager()->table_exists($table)) {
  343. $xmldb_table = new xmldb_table($table);
  344. $DB->get_manager()->drop_table($xmldb_table);
  345. }
  346. }
  347. return true;
  348. }
  349. /**
  350. * Returns names of all known tables == tables that moodle knows about.
  351. *
  352. * @return array Array of lowercase table names
  353. */
  354. function get_used_table_names() {
  355. $table_names = array();
  356. $dbdirs = get_db_directories();
  357. foreach ($dbdirs as $dbdir) {
  358. $file = $dbdir.'/install.xml';
  359. $xmldb_file = new xmldb_file($file);
  360. if (!$xmldb_file->fileExists()) {
  361. continue;
  362. }
  363. $loaded = $xmldb_file->loadXMLStructure();
  364. $structure = $xmldb_file->getStructure();
  365. if ($loaded and $tables = $structure->getTables()) {
  366. foreach($tables as $table) {
  367. $table_names[] = strtolower($table->name);
  368. }
  369. }
  370. }
  371. return $table_names;
  372. }
  373. /**
  374. * Returns list of all directories where we expect install.xml files
  375. * @return array Array of paths
  376. */
  377. function get_db_directories() {
  378. global $CFG;
  379. $dbdirs = array();
  380. /// First, the main one (lib/db)
  381. $dbdirs[] = $CFG->libdir.'/db';
  382. /// Then, all the ones defined by get_plugin_types()
  383. $plugintypes = get_plugin_types();
  384. foreach ($plugintypes as $plugintype => $pluginbasedir) {
  385. if ($plugins = get_plugin_list($plugintype)) {
  386. foreach ($plugins as $plugin => $plugindir) {
  387. $dbdirs[] = $plugindir.'/db';
  388. }
  389. }
  390. }
  391. return $dbdirs;
  392. }
  393. /**
  394. * Try to obtain or release the cron lock.
  395. * @param string $name name of lock
  396. * @param int $until timestamp when this lock considered stale, null means remove lock unconditionally
  397. * @param bool $ignorecurrent ignore current lock state, usually extend previous lock, defaults to false
  398. * @return bool true if lock obtained
  399. */
  400. function set_cron_lock($name, $until, $ignorecurrent=false) {
  401. global $DB;
  402. if (empty($name)) {
  403. debugging("Tried to get a cron lock for a null fieldname");
  404. return false;
  405. }
  406. // remove lock by force == remove from config table
  407. if (is_null($until)) {
  408. set_config($name, null);
  409. return true;
  410. }
  411. if (!$ignorecurrent) {
  412. // read value from db - other processes might have changed it
  413. $value = $DB->get_field('config', 'value', array('name'=>$name));
  414. if ($value and $value > time()) {
  415. //lock active
  416. return false;
  417. }
  418. }
  419. set_config($name, $until);
  420. return true;
  421. }
  422. /**
  423. * Test if and critical warnings are present
  424. * @return bool
  425. */
  426. function admin_critical_warnings_present() {
  427. global $SESSION;
  428. if (!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
  429. return 0;
  430. }
  431. if (!isset($SESSION->admin_critical_warning)) {
  432. $SESSION->admin_critical_warning = 0;
  433. if (is_dataroot_insecure(true) === INSECURE_DATAROOT_ERROR) {
  434. $SESSION->admin_critical_warning = 1;
  435. }
  436. }
  437. return $SESSION->admin_critical_warning;
  438. }
  439. /**
  440. * Detects if float supports at least 10 decimal digits
  441. *
  442. * Detects if float supports at least 10 decimal digits
  443. * and also if float-->string conversion works as expected.
  444. *
  445. * @return bool true if problem found
  446. */
  447. function is_float_problem() {
  448. $num1 = 2009010200.01;
  449. $num2 = 2009010200.02;
  450. return ((string)$num1 === (string)$num2 or $num1 === $num2 or $num2 <= (string)$num1);
  451. }
  452. /**
  453. * Try to verify that dataroot is not accessible from web.
  454. *
  455. * Try to verify that dataroot is not accessible from web.
  456. * It is not 100% correct but might help to reduce number of vulnerable sites.
  457. * Protection from httpd.conf and .htaccess is not detected properly.
  458. *
  459. * @uses INSECURE_DATAROOT_WARNING
  460. * @uses INSECURE_DATAROOT_ERROR
  461. * @param bool $fetchtest try to test public access by fetching file, default false
  462. * @return mixed empty means secure, INSECURE_DATAROOT_ERROR found a critical problem, INSECURE_DATAROOT_WARNING might be problematic
  463. */
  464. function is_dataroot_insecure($fetchtest=false) {
  465. global $CFG;
  466. $siteroot = str_replace('\\', '/', strrev($CFG->dirroot.'/')); // win32 backslash workaround
  467. $rp = preg_replace('|https?://[^/]+|i', '', $CFG->wwwroot, 1);
  468. $rp = strrev(trim($rp, '/'));
  469. $rp = explode('/', $rp);
  470. foreach($rp as $r) {
  471. if (strpos($siteroot, '/'.$r.'/') === 0) {
  472. $siteroot = substr($siteroot, strlen($r)+1); // moodle web in subdirectory
  473. } else {
  474. break; // probably alias root
  475. }
  476. }
  477. $siteroot = strrev($siteroot);
  478. $dataroot = str_replace('\\', '/', $CFG->dataroot.'/');
  479. if (strpos($dataroot, $siteroot) !== 0) {
  480. return false;
  481. }
  482. if (!$fetchtest) {
  483. return INSECURE_DATAROOT_WARNING;
  484. }
  485. // now try all methods to fetch a test file using http protocol
  486. $httpdocroot = str_replace('\\', '/', strrev($CFG->dirroot.'/'));
  487. preg_match('|(https?://[^/]+)|i', $CFG->wwwroot, $matches);
  488. $httpdocroot = $matches[1];
  489. $datarooturl = $httpdocroot.'/'. substr($dataroot, strlen($siteroot));
  490. make_upload_directory('diag');
  491. $testfile = $CFG->dataroot.'/diag/public.txt';
  492. if (!file_exists($testfile)) {
  493. file_put_contents($testfile, 'test file, do not delete');
  494. }
  495. $teststr = trim(file_get_contents($testfile));
  496. if (empty($teststr)) {
  497. // hmm, strange
  498. return INSECURE_DATAROOT_WARNING;
  499. }
  500. $testurl = $datarooturl.'/diag/public.txt';
  501. if (extension_loaded('curl') and
  502. !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
  503. !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
  504. ($ch = @curl_init($testurl)) !== false) {
  505. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  506. curl_setopt($ch, CURLOPT_HEADER, false);
  507. $data = curl_exec($ch);
  508. if (!curl_errno($ch)) {
  509. $data = trim($data);
  510. if ($data === $teststr) {
  511. curl_close($ch);
  512. return INSECURE_DATAROOT_ERROR;
  513. }
  514. }
  515. curl_close($ch);
  516. }
  517. if ($data = @file_get_contents($testurl)) {
  518. $data = trim($data);
  519. if ($data === $teststr) {
  520. return INSECURE_DATAROOT_ERROR;
  521. }
  522. }
  523. preg_match('|https?://([^/]+)|i', $testurl, $matches);
  524. $sitename = $matches[1];
  525. $error = 0;
  526. if ($fp = @fsockopen($sitename, 80, $error)) {
  527. preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
  528. $localurl = $matches[1];
  529. $out = "GET $localurl HTTP/1.1\r\n";
  530. $out .= "Host: $sitename\r\n";
  531. $out .= "Connection: Close\r\n\r\n";
  532. fwrite($fp, $out);
  533. $data = '';
  534. $incoming = false;
  535. while (!feof($fp)) {
  536. if ($incoming) {
  537. $data .= fgets($fp, 1024);
  538. } else if (@fgets($fp, 1024) === "\r\n") {
  539. $incoming = true;
  540. }
  541. }
  542. fclose($fp);
  543. $data = trim($data);
  544. if ($data === $teststr) {
  545. return INSECURE_DATAROOT_ERROR;
  546. }
  547. }
  548. return INSECURE_DATAROOT_WARNING;
  549. }
  550. /// CLASS DEFINITIONS /////////////////////////////////////////////////////////
  551. /**
  552. * Interface for anything appearing in the admin tree
  553. *
  554. * The interface that is implemented by anything that appears in the admin tree
  555. * block. It forces inheriting classes to define a method for checking user permissions
  556. * and methods for finding something in the admin tree.
  557. *
  558. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  559. */
  560. interface part_of_admin_tree {
  561. /**
  562. * Finds a named part_of_admin_tree.
  563. *
  564. * Used to find a part_of_admin_tree. If a class only inherits part_of_admin_tree
  565. * and not parentable_part_of_admin_tree, then this function should only check if
  566. * $this->name matches $name. If it does, it should return a reference to $this,
  567. * otherwise, it should return a reference to NULL.
  568. *
  569. * If a class inherits parentable_part_of_admin_tree, this method should be called
  570. * recursively on all child objects (assuming, of course, the parent object's name
  571. * doesn't match the search criterion).
  572. *
  573. * @param string $name The internal name of the part_of_admin_tree we're searching for.
  574. * @return mixed An object reference or a NULL reference.
  575. */
  576. public function locate($name);
  577. /**
  578. * Removes named part_of_admin_tree.
  579. *
  580. * @param string $name The internal name of the part_of_admin_tree we want to remove.
  581. * @return bool success.
  582. */
  583. public function prune($name);
  584. /**
  585. * Search using query
  586. * @param string $query
  587. * @return mixed array-object structure of found settings and pages
  588. */
  589. public function search($query);
  590. /**
  591. * Verifies current user's access to this part_of_admin_tree.
  592. *
  593. * Used to check if the current user has access to this part of the admin tree or
  594. * not. If a class only inherits part_of_admin_tree and not parentable_part_of_admin_tree,
  595. * then this method is usually just a call to has_capability() in the site context.
  596. *
  597. * If a class inherits parentable_part_of_admin_tree, this method should return the
  598. * logical OR of the return of check_access() on all child objects.
  599. *
  600. * @return bool True if the user has access, false if she doesn't.
  601. */
  602. public function check_access();
  603. /**
  604. * Mostly useful for removing of some parts of the tree in admin tree block.
  605. *
  606. * @return True is hidden from normal list view
  607. */
  608. public function is_hidden();
  609. /**
  610. * Show we display Save button at the page bottom?
  611. * @return bool
  612. */
  613. public function show_save();
  614. }
  615. /**
  616. * Interface implemented by any part_of_admin_tree that has children.
  617. *
  618. * The interface implemented by any part_of_admin_tree that can be a parent
  619. * to other part_of_admin_tree's. (For now, this only includes admin_category.) Apart
  620. * from ensuring part_of_admin_tree compliancy, it also ensures inheriting methods
  621. * include an add method for adding other part_of_admin_tree objects as children.
  622. *
  623. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  624. */
  625. interface parentable_part_of_admin_tree extends part_of_admin_tree {
  626. /**
  627. * Adds a part_of_admin_tree object to the admin tree.
  628. *
  629. * Used to add a part_of_admin_tree object to this object or a child of this
  630. * object. $something should only be added if $destinationname matches
  631. * $this->name. If it doesn't, add should be called on child objects that are
  632. * also parentable_part_of_admin_tree's.
  633. *
  634. * @param string $destinationname The internal name of the new parent for $something.
  635. * @param part_of_admin_tree $something The object to be added.
  636. * @return bool True on success, false on failure.
  637. */
  638. public function add($destinationname, $something);
  639. }
  640. /**
  641. * The object used to represent folders (a.k.a. categories) in the admin tree block.
  642. *
  643. * Each admin_category object contains a number of part_of_admin_tree objects.
  644. *
  645. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  646. */
  647. class admin_category implements parentable_part_of_admin_tree {
  648. /** @var mixed An array of part_of_admin_tree objects that are this object's children */
  649. public $children;
  650. /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
  651. public $name;
  652. /** @var string The displayed name for this category. Usually obtained through get_string() */
  653. public $visiblename;
  654. /** @var bool Should this category be hidden in admin tree block? */
  655. public $hidden;
  656. /** @var mixed Either a string or an array or strings */
  657. public $path;
  658. /** @var mixed Either a string or an array or strings */
  659. public $visiblepath;
  660. /**
  661. * Constructor for an empty admin category
  662. *
  663. * @param string $name The internal name for this category. Must be unique amongst ALL part_of_admin_tree objects
  664. * @param string $visiblename The displayed named for this category. Usually obtained through get_string()
  665. * @param bool $hidden hide category in admin tree block, defaults to false
  666. */
  667. public function __construct($name, $visiblename, $hidden=false) {
  668. $this->children = array();
  669. $this->name = $name;
  670. $this->visiblename = $visiblename;
  671. $this->hidden = $hidden;
  672. }
  673. /**
  674. * Returns a reference to the part_of_admin_tree object with internal name $name.
  675. *
  676. * @param string $name The internal name of the object we want.
  677. * @param bool $findpath initialize path and visiblepath arrays
  678. * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
  679. * defaults to false
  680. */
  681. public function locate($name, $findpath=false) {
  682. if ($this->name == $name) {
  683. if ($findpath) {
  684. $this->visiblepath[] = $this->visiblename;
  685. $this->path[] = $this->name;
  686. }
  687. return $this;
  688. }
  689. $return = NULL;
  690. foreach($this->children as $childid=>$unused) {
  691. if ($return = $this->children[$childid]->locate($name, $findpath)) {
  692. break;
  693. }
  694. }
  695. if (!is_null($return) and $findpath) {
  696. $return->visiblepath[] = $this->visiblename;
  697. $return->path[] = $this->name;
  698. }
  699. return $return;
  700. }
  701. /**
  702. * Search using query
  703. *
  704. * @param string query
  705. * @return mixed array-object structure of found settings and pages
  706. */
  707. public function search($query) {
  708. $result = array();
  709. foreach ($this->children as $child) {
  710. $subsearch = $child->search($query);
  711. if (!is_array($subsearch)) {
  712. debugging('Incorrect search result from '.$child->name);
  713. continue;
  714. }
  715. $result = array_merge($result, $subsearch);
  716. }
  717. return $result;
  718. }
  719. /**
  720. * Removes part_of_admin_tree object with internal name $name.
  721. *
  722. * @param string $name The internal name of the object we want to remove.
  723. * @return bool success
  724. */
  725. public function prune($name) {
  726. if ($this->name == $name) {
  727. return false; //can not remove itself
  728. }
  729. foreach($this->children as $precedence => $child) {
  730. if ($child->name == $name) {
  731. // found it!
  732. unset($this->children[$precedence]);
  733. return true;
  734. }
  735. if ($this->children[$precedence]->prune($name)) {
  736. return true;
  737. }
  738. }
  739. return false;
  740. }
  741. /**
  742. * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
  743. *
  744. * @param string $destinationame The internal name of the immediate parent that we want for $something.
  745. * @param mixed $something A part_of_admin_tree or setting instance to be added.
  746. * @return bool True if successfully added, false if $something can not be added.
  747. */
  748. public function add($parentname, $something) {
  749. $parent = $this->locate($parentname);
  750. if (is_null($parent)) {
  751. debugging('parent does not exist!');
  752. return false;
  753. }
  754. if ($something instanceof part_of_admin_tree) {
  755. if (!($parent instanceof parentable_part_of_admin_tree)) {
  756. debugging('error - parts of tree can be inserted only into parentable parts');
  757. return false;
  758. }
  759. $parent->children[] = $something;
  760. return true;
  761. } else {
  762. debugging('error - can not add this element');
  763. return false;
  764. }
  765. }
  766. /**
  767. * Checks if the user has access to anything in this category.
  768. *
  769. * @return bool True if the user has access to at least one child in this category, false otherwise.
  770. */
  771. public function check_access() {
  772. foreach ($this->children as $child) {
  773. if ($child->check_access()) {
  774. return true;
  775. }
  776. }
  777. return false;
  778. }
  779. /**
  780. * Is this category hidden in admin tree block?
  781. *
  782. * @return bool True if hidden
  783. */
  784. public function is_hidden() {
  785. return $this->hidden;
  786. }
  787. /**
  788. * Show we display Save button at the page bottom?
  789. * @return bool
  790. */
  791. public function show_save() {
  792. foreach ($this->children as $child) {
  793. if ($child->show_save()) {
  794. return true;
  795. }
  796. }
  797. return false;
  798. }
  799. }
  800. /**
  801. * Root of admin settings tree, does not have any parent.
  802. *
  803. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  804. */
  805. class admin_root extends admin_category {
  806. /** @var array List of errors */
  807. public $errors;
  808. /** @var string search query */
  809. public $search;
  810. /** @var bool full tree flag - true means all settings required, false only pages required */
  811. public $fulltree;
  812. /** @var bool flag indicating loaded tree */
  813. public $loaded;
  814. /** @var mixed site custom defaults overriding defaults in settings files*/
  815. public $custom_defaults;
  816. /**
  817. * @param bool $fulltree true means all settings required,
  818. * false only pages required
  819. */
  820. public function __construct($fulltree) {
  821. global $CFG;
  822. parent::__construct('root', get_string('administration'), false);
  823. $this->errors = array();
  824. $this->search = '';
  825. $this->fulltree = $fulltree;
  826. $this->loaded = false;
  827. // load custom defaults if found
  828. $this->custom_defaults = null;
  829. $defaultsfile = "$CFG->dirroot/local/defaults.php";
  830. if (is_readable($defaultsfile)) {
  831. $defaults = array();
  832. include($defaultsfile);
  833. if (is_array($defaults) and count($defaults)) {
  834. $this->custom_defaults = $defaults;
  835. }
  836. }
  837. }
  838. /**
  839. * Empties children array, and sets loaded to false
  840. *
  841. * @param bool $requirefulltree
  842. */
  843. public function purge_children($requirefulltree) {
  844. $this->children = array();
  845. $this->fulltree = ($requirefulltree || $this->fulltree);
  846. $this->loaded = false;
  847. }
  848. }
  849. /**
  850. * Links external PHP pages into the admin tree.
  851. *
  852. * See detailed usage example at the top of this document (adminlib.php)
  853. *
  854. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  855. */
  856. class admin_externalpage implements part_of_admin_tree {
  857. /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
  858. public $name;
  859. /** @var string The displayed name for this external page. Usually obtained through get_string(). */
  860. public $visiblename;
  861. /** @var string The external URL that we should link to when someone requests this external page. */
  862. public $url;
  863. /** @var string The role capability/permission a user must have to access this external page. */
  864. public $req_capability;
  865. /** @var object The context in which capability/permission should be checked, default is site context. */
  866. public $context;
  867. /** @var bool hidden in admin tree block. */
  868. public $hidden;
  869. /** @var mixed either string or array of string */
  870. public $path;
  871. public $visiblepath;
  872. /**
  873. * Constructor for adding an external page into the admin tree.
  874. *
  875. * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
  876. * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
  877. * @param string $url The external URL that we should link to when someone requests this external page.
  878. * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
  879. * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
  880. * @param stdClass $context The context the page relates to. Not sure what happens
  881. * if you specify something other than system or front page. Defaults to system.
  882. */
  883. public function __construct($name, $visiblename, $url, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
  884. $this->name = $name;
  885. $this->visiblename = $visiblename;
  886. $this->url = $url;
  887. if (is_array($req_capability)) {
  888. $this->req_capability = $req_capability;
  889. } else {
  890. $this->req_capability = array($req_capability);
  891. }
  892. $this->hidden = $hidden;
  893. $this->context = $context;
  894. }
  895. /**
  896. * Returns a reference to the part_of_admin_tree object with internal name $name.
  897. *
  898. * @param string $name The internal name of the object we want.
  899. * @param bool $findpath defaults to false
  900. * @return mixed A reference to the object with internal name $name if found, otherwise a reference to NULL.
  901. */
  902. public function locate($name, $findpath=false) {
  903. if ($this->name == $name) {
  904. if ($findpath) {
  905. $this->visiblepath = array($this->visiblename);
  906. $this->path = array($this->name);
  907. }
  908. return $this;
  909. } else {
  910. $return = NULL;
  911. return $return;
  912. }
  913. }
  914. /**
  915. * This function always returns false, required function by interface
  916. *
  917. * @param string $name
  918. * @return false
  919. */
  920. public function prune($name) {
  921. return false;
  922. }
  923. /**
  924. * Search using query
  925. *
  926. * @param string $query
  927. * @return mixed array-object structure of found settings and pages
  928. */
  929. public function search($query) {
  930. $textlib = textlib_get_instance();
  931. $found = false;
  932. if (strpos(strtolower($this->name), $query) !== false) {
  933. $found = true;
  934. } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
  935. $found = true;
  936. }
  937. if ($found) {
  938. $result = new stdClass();
  939. $result->page = $this;
  940. $result->settings = array();
  941. return array($this->name => $result);
  942. } else {
  943. return array();
  944. }
  945. }
  946. /**
  947. * Determines if the current user has access to this external page based on $this->req_capability.
  948. *
  949. * @return bool True if user has access, false otherwise.
  950. */
  951. public function check_access() {
  952. global $CFG;
  953. $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
  954. foreach($this->req_capability as $cap) {
  955. if (has_capability($cap, $context)) {
  956. return true;
  957. }
  958. }
  959. return false;
  960. }
  961. /**
  962. * Is this external page hidden in admin tree block?
  963. *
  964. * @return bool True if hidden
  965. */
  966. public function is_hidden() {
  967. return $this->hidden;
  968. }
  969. /**
  970. * Show we display Save button at the page bottom?
  971. * @return bool
  972. */
  973. public function show_save() {
  974. return false;
  975. }
  976. }
  977. /**
  978. * Used to group a number of admin_setting objects into a page and add them to the admin tree.
  979. *
  980. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  981. */
  982. class admin_settingpage implements part_of_admin_tree {
  983. /** @var string An internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects */
  984. public $name;
  985. /** @var string The displayed name for this external page. Usually obtained through get_string(). */
  986. public $visiblename;
  987. /** @var mixed An array of admin_setting objects that are part of this setting page. */
  988. public $settings;
  989. /** @var string The role capability/permission a user must have to access this external page. */
  990. public $req_capability;
  991. /** @var object The context in which capability/permission should be checked, default is site context. */
  992. public $context;
  993. /** @var bool hidden in admin tree block. */
  994. public $hidden;
  995. /** @var mixed string of paths or array of strings of paths */
  996. public $path;
  997. public $visiblepath;
  998. /**
  999. * see admin_settingpage for details of this function
  1000. *
  1001. * @param string $name The internal name for this external page. Must be unique amongst ALL part_of_admin_tree objects.
  1002. * @param string $visiblename The displayed name for this external page. Usually obtained through get_string().
  1003. * @param mixed $req_capability The role capability/permission a user must have to access this external page. Defaults to 'moodle/site:config'.
  1004. * @param boolean $hidden Is this external page hidden in admin tree block? Default false.
  1005. * @param stdClass $context The context the page relates to. Not sure what happens
  1006. * if you specify something other than system or front page. Defaults to system.
  1007. */
  1008. public function __construct($name, $visiblename, $req_capability='moodle/site:config', $hidden=false, $context=NULL) {
  1009. $this->settings = new stdClass();
  1010. $this->name = $name;
  1011. $this->visiblename = $visiblename;
  1012. if (is_array($req_capability)) {
  1013. $this->req_capability = $req_capability;
  1014. } else {
  1015. $this->req_capability = array($req_capability);
  1016. }
  1017. $this->hidden = $hidden;
  1018. $this->context = $context;
  1019. }
  1020. /**
  1021. * see admin_category
  1022. *
  1023. * @param string $name
  1024. * @param bool $findpath
  1025. * @return mixed Object (this) if name == this->name, else returns null
  1026. */
  1027. public function locate($name, $findpath=false) {
  1028. if ($this->name == $name) {
  1029. if ($findpath) {
  1030. $this->visiblepath = array($this->visiblename);
  1031. $this->path = array($this->name);
  1032. }
  1033. return $this;
  1034. } else {
  1035. $return = NULL;
  1036. return $return;
  1037. }
  1038. }
  1039. /**
  1040. * Search string in settings page.
  1041. *
  1042. * @param string $query
  1043. * @return array
  1044. */
  1045. public function search($query) {
  1046. $found = array();
  1047. foreach ($this->settings as $setting) {
  1048. if ($setting->is_related($query)) {
  1049. $found[] = $setting;
  1050. }
  1051. }
  1052. if ($found) {
  1053. $result = new stdClass();
  1054. $result->page = $this;
  1055. $result->settings = $found;
  1056. return array($this->name => $result);
  1057. }
  1058. $textlib = textlib_get_instance();
  1059. $found = false;
  1060. if (strpos(strtolower($this->name), $query) !== false) {
  1061. $found = true;
  1062. } else if (strpos($textlib->strtolower($this->visiblename), $query) !== false) {
  1063. $found = true;
  1064. }
  1065. if ($found) {
  1066. $result = new stdClass();
  1067. $result->page = $this;
  1068. $result->settings = array();
  1069. return array($this->name => $result);
  1070. } else {
  1071. return array();
  1072. }
  1073. }
  1074. /**
  1075. * This function always returns false, required by interface
  1076. *
  1077. * @param string $name
  1078. * @return bool Always false
  1079. */
  1080. public function prune($name) {
  1081. return false;
  1082. }
  1083. /**
  1084. * adds an admin_setting to this admin_settingpage
  1085. *
  1086. * 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
  1087. * n.b. each admin_setting in an admin_settingpage must have a unique internal name
  1088. *
  1089. * @param object $setting is the admin_setting object you want to add
  1090. * @return bool true if successful, false if not
  1091. */
  1092. public function add($setting) {
  1093. if (!($setting instanceof admin_setting)) {
  1094. debugging('error - not a setting instance');
  1095. return false;
  1096. }
  1097. $this->settings->{$setting->name} = $setting;
  1098. return true;
  1099. }
  1100. /**
  1101. * see admin_externalpage
  1102. *
  1103. * @return bool Returns true for yes false for no
  1104. */
  1105. public function check_access() {
  1106. global $CFG;
  1107. $context = empty($this->context) ? get_context_instance(CONTEXT_SYSTEM) : $this->context;
  1108. foreach($this->req_capability as $cap) {
  1109. if (has_capability($cap, $context)) {
  1110. return true;
  1111. }
  1112. }
  1113. return false;
  1114. }
  1115. /**
  1116. * outputs this page as html in a table (suitable for inclusion in an admin pagetype)
  1117. * @return string Returns an XHTML string
  1118. */
  1119. public function output_html() {
  1120. $adminroot = admin_get_root();
  1121. $return = '<fieldset>'."\n".'<div class="clearer"><!-- --></div>'."\n";
  1122. foreach($this->settings as $setting) {
  1123. $fullname = $setting->get_full_name();
  1124. if (array_key_exists($fullname, $adminroot->errors)) {
  1125. $data = $adminroot->errors[$fullname]->data;
  1126. } else {
  1127. $data = $setting->get_setting();
  1128. // do not use defaults if settings not available - upgrade settings handles the defaults!
  1129. }
  1130. $return .= $setting->output_html($data);
  1131. }
  1132. $return .= '</fieldset>';
  1133. return $return;
  1134. }
  1135. /**
  1136. * Is this settings page hidden in admin tree block?
  1137. *
  1138. * @return bool True if hidden
  1139. */
  1140. public function is_hidden() {
  1141. return $this->hidden;
  1142. }
  1143. /**
  1144. * Show we display Save button at the page bottom?
  1145. * @return bool
  1146. */
  1147. public function show_save() {
  1148. foreach($this->settings as $setting) {
  1149. if (empty($setting->nosave)) {
  1150. return true;
  1151. }
  1152. }
  1153. return false;
  1154. }
  1155. }
  1156. /**
  1157. * Admin settings class. Only exists on setting pages.
  1158. * Read & write happens at this level; no authentication.
  1159. *
  1160. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  1161. */
  1162. abstract class admin_setting {
  1163. /** @var string unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting' for ones in config_plugins. */
  1164. public $name;
  1165. /** @var string localised name */
  1166. public $visiblename;
  1167. /** @var string localised long description in Markdown format */
  1168. public $description;
  1169. /** @var mixed Can be string or array of string */
  1170. public $defaultsetting;
  1171. /** @var string */
  1172. public $updatedcallback;
  1173. /** @var mixed can be String or Null. Null means main config table */
  1174. public $plugin; // null means main config table
  1175. /** @var bool true indicates this setting does not actually save anything, just information */
  1176. public $nosave = false;
  1177. /**
  1178. * Constructor
  1179. * @param string $name unique ascii name, either 'mysetting' for settings that in config,
  1180. * or 'myplugin/mysetting' for ones in config_plugins.
  1181. * @param string $visiblename localised name
  1182. * @param string $description localised long description
  1183. * @param mixed $defaultsetting string or array depending on implementation
  1184. */
  1185. public function __construct($name, $visiblename, $description, $defaultsetting) {
  1186. $this->parse_setting_name($name);
  1187. $this->visiblename = $visiblename;
  1188. $this->description = $description;
  1189. $this->defaultsetting = $defaultsetting;
  1190. }
  1191. /**
  1192. * Set up $this->name and potentially $this->plugin
  1193. *
  1194. * Set up $this->name and possibly $this->plugin based on whether $name looks
  1195. * like 'settingname' or 'plugin/settingname'. Also, do some sanity checking
  1196. * on the names, that is, output a developer debug warning if the name
  1197. * contains anything other than [a-zA-Z0-9_]+.
  1198. *
  1199. * @param string $name the setting name passed in to the constructor.
  1200. */
  1201. private function parse_setting_name($name) {
  1202. $bits = explode('/', $name);
  1203. if (count($bits) > 2) {
  1204. throw new moodle_exception('invalidadminsettingname', '', '', $name);
  1205. }
  1206. $this->name = array_pop($bits);
  1207. if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->name)) {
  1208. throw new moodle_exception('invalidadminsettingname', '', '', $name);
  1209. }
  1210. if (!empty($bits)) {
  1211. $this->plugin = array_pop($bits);
  1212. if ($this->plugin === 'moodle') {
  1213. $this->plugin = null;
  1214. } else if (!preg_match('/^[a-zA-Z0-9_]+$/', $this->plugin)) {
  1215. throw new moodle_exception('invalidadminsettingname', '', '', $name);
  1216. }
  1217. }
  1218. }
  1219. /**
  1220. * Returns the fullname prefixed by the plugin
  1221. * @return string
  1222. */
  1223. public function get_full_name() {
  1224. return 's_'.$this->plugin.'_'.$this->name;
  1225. }
  1226. /**
  1227. * Returns the ID string based on plugin and name
  1228. * @return string
  1229. */
  1230. public function get_id() {
  1231. return 'id_s_'.$this->plugin.'_'.$this->name;
  1232. }
  1233. /**
  1234. * Returns the config if possible
  1235. *
  1236. * @return mixed returns config if successfull else null
  1237. */
  1238. public function config_read($name) {
  1239. global $CFG;
  1240. if (!empty($this->plugin)) {
  1241. $value = get_config($this->plugin, $name);
  1242. return $value === false ? NULL : $value;
  1243. } else {
  1244. if (isset($CFG->$name)) {
  1245. return $CFG->$name;
  1246. } else {
  1247. return NULL;
  1248. }
  1249. }
  1250. }
  1251. /**
  1252. * Used to set a config pair and log change
  1253. *
  1254. * @param string $name
  1255. * @param mixed $value Gets converted to string if not null
  1256. * @return bool Write setting to config table
  1257. */
  1258. public function config_write($name, $value) {
  1259. global $DB, $USER, $CFG;
  1260. if ($this->nosave) {
  1261. return true;
  1262. }
  1263. // make sure it is a real change
  1264. $oldvalue = get_config($this->plugin, $name);
  1265. $oldvalue = ($oldvalue === false) ? null : $oldvalue; // normalise
  1266. $value = is_null($value) ? null : (string)$value;
  1267. if ($oldvalue === $value) {
  1268. return true;
  1269. }
  1270. // store change
  1271. set_config($name, $value, $this->plugin);
  1272. // log change
  1273. $log = new stdClass();
  1274. $log->userid = during_initial_install() ? 0 :$USER->id; // 0 as user id during install
  1275. $log->timemodified = time();
  1276. $log->plugin = $this->plugin;
  1277. $log->name = $name;
  1278. $log->value = $value;
  1279. $log->oldvalue = $oldvalue;
  1280. $DB->insert_record('config_log', $log);
  1281. return true; // BC only
  1282. }
  1283. /**
  1284. * Returns current value of this setting
  1285. * @return mixed array or string depending on instance, NULL means not set yet
  1286. */
  1287. public abstract function get_setting();
  1288. /**
  1289. * Returns default setting if exists
  1290. * @return mixed array or string depending on instance; NULL means no default, u…

Large files files are truncated, but you can click here to view the full file