PageRenderTime 122ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 2ms

/lib/moodlelib.php

https://bitbucket.org/ceu/moodle_demo
PHP | 8638 lines | 5266 code | 1197 blank | 2175 comment | 1270 complexity | 12a22e63472578b4f37557a8a5fa5727 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1
  1. <?php // $Id$
  2. ///////////////////////////////////////////////////////////////////////////
  3. // //
  4. // NOTICE OF COPYRIGHT //
  5. // //
  6. // Moodle - Modular Object-Oriented Dynamic Learning Environment //
  7. // http://moodle.org //
  8. // //
  9. // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
  10. // //
  11. // This program is free software; you can redistribute it and/or modify //
  12. // it under the terms of the GNU General Public License as published by //
  13. // the Free Software Foundation; either version 2 of the License, or //
  14. // (at your option) any later version. //
  15. // //
  16. // This program is distributed in the hope that it will be useful, //
  17. // but WITHOUT ANY WARRANTY; without even the implied warranty of //
  18. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
  19. // GNU General Public License for more details: //
  20. // //
  21. // http://www.gnu.org/copyleft/gpl.html //
  22. // //
  23. ///////////////////////////////////////////////////////////////////////////
  24. /**
  25. * moodlelib.php - Moodle main library
  26. *
  27. * Main library file of miscellaneous general-purpose Moodle functions.
  28. * Other main libraries:
  29. * - weblib.php - functions that produce web output
  30. * - datalib.php - functions that access the database
  31. * @author Martin Dougiamas
  32. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  33. * @package moodlecore
  34. */
  35. /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
  36. /**
  37. * Used by some scripts to check they are being called by Moodle
  38. */
  39. define('MOODLE_INTERNAL', true);
  40. /// Date and time constants ///
  41. /**
  42. * Time constant - the number of seconds in a year
  43. */
  44. define('YEARSECS', 31536000);
  45. /**
  46. * Time constant - the number of seconds in a week
  47. */
  48. define('WEEKSECS', 604800);
  49. /**
  50. * Time constant - the number of seconds in a day
  51. */
  52. define('DAYSECS', 86400);
  53. /**
  54. * Time constant - the number of seconds in an hour
  55. */
  56. define('HOURSECS', 3600);
  57. /**
  58. * Time constant - the number of seconds in a minute
  59. */
  60. define('MINSECS', 60);
  61. /**
  62. * Time constant - the number of minutes in a day
  63. */
  64. define('DAYMINS', 1440);
  65. /**
  66. * Time constant - the number of minutes in an hour
  67. */
  68. define('HOURMINS', 60);
  69. /// Parameter constants - every call to optional_param(), required_param() ///
  70. /// or clean_param() should have a specified type of parameter. //////////////
  71. /**
  72. * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
  73. * originally was 0, but changed because we need to detect unknown
  74. * parameter types and swiched order in clean_param().
  75. */
  76. define('PARAM_RAW', 666);
  77. /**
  78. * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
  79. * It was one of the first types, that is why it is abused so much ;-)
  80. */
  81. define('PARAM_CLEAN', 0x0001);
  82. /**
  83. * PARAM_INT - integers only, use when expecting only numbers.
  84. */
  85. define('PARAM_INT', 0x0002);
  86. /**
  87. * PARAM_INTEGER - an alias for PARAM_INT
  88. */
  89. define('PARAM_INTEGER', 0x0002);
  90. /**
  91. * PARAM_NUMBER - a real/floating point number.
  92. */
  93. define('PARAM_NUMBER', 0x000a);
  94. /**
  95. * PARAM_ALPHA - contains only english letters.
  96. */
  97. define('PARAM_ALPHA', 0x0004);
  98. /**
  99. * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
  100. * @TODO: should we alias it to PARAM_ALPHANUM ?
  101. */
  102. define('PARAM_ACTION', 0x0004);
  103. /**
  104. * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
  105. * @TODO: should we alias it to PARAM_ALPHANUM ?
  106. */
  107. define('PARAM_FORMAT', 0x0004);
  108. /**
  109. * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
  110. */
  111. define('PARAM_NOTAGS', 0x0008);
  112. /**
  113. * PARAM_MULTILANG - alias of PARAM_TEXT.
  114. */
  115. define('PARAM_MULTILANG', 0x0009);
  116. /**
  117. * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
  118. */
  119. define('PARAM_TEXT', 0x0009);
  120. /**
  121. * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
  122. */
  123. define('PARAM_FILE', 0x0010);
  124. /**
  125. * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
  126. */
  127. define('PARAM_TAG', 0x0011);
  128. /**
  129. * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
  130. */
  131. define('PARAM_TAGLIST', 0x0012);
  132. /**
  133. * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
  134. * note: the leading slash is not removed, window drive letter is not allowed
  135. */
  136. define('PARAM_PATH', 0x0020);
  137. /**
  138. * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
  139. */
  140. define('PARAM_HOST', 0x0040);
  141. /**
  142. * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
  143. */
  144. define('PARAM_URL', 0x0080);
  145. /**
  146. * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the others! Implies PARAM_URL!)
  147. */
  148. define('PARAM_LOCALURL', 0x0180);
  149. /**
  150. * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
  151. * use when you want to store a new file submitted by students
  152. */
  153. define('PARAM_CLEANFILE',0x0200);
  154. /**
  155. * PARAM_ALPHANUM - expected numbers and letters only.
  156. */
  157. define('PARAM_ALPHANUM', 0x0400);
  158. /**
  159. * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
  160. */
  161. define('PARAM_BOOL', 0x0800);
  162. /**
  163. * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
  164. * note: do not forget to addslashes() before storing into database!
  165. */
  166. define('PARAM_CLEANHTML',0x1000);
  167. /**
  168. * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
  169. * suitable for include() and require()
  170. * @TODO: should we rename this function to PARAM_SAFEDIRS??
  171. */
  172. define('PARAM_ALPHAEXT', 0x2000);
  173. /**
  174. * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
  175. */
  176. define('PARAM_SAFEDIR', 0x4000);
  177. /**
  178. * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
  179. */
  180. define('PARAM_SEQUENCE', 0x8000);
  181. /**
  182. * PARAM_PEM - Privacy Enhanced Mail format
  183. */
  184. define('PARAM_PEM', 0x10000);
  185. /**
  186. * PARAM_BASE64 - Base 64 encoded format
  187. */
  188. define('PARAM_BASE64', 0x20000);
  189. /// Page types ///
  190. /**
  191. * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
  192. */
  193. define('PAGE_COURSE_VIEW', 'course-view');
  194. /// Debug levels ///
  195. /** no warnings at all */
  196. define ('DEBUG_NONE', 0);
  197. /** E_ERROR | E_PARSE */
  198. define ('DEBUG_MINIMAL', 5);
  199. /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
  200. define ('DEBUG_NORMAL', 15);
  201. /** E_ALL without E_STRICT for now, do show recoverable fatal errors */
  202. define ('DEBUG_ALL', 6143);
  203. /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
  204. define ('DEBUG_DEVELOPER', 38911);
  205. /**
  206. * Blog access level constant declaration
  207. */
  208. define ('BLOG_USER_LEVEL', 1);
  209. define ('BLOG_GROUP_LEVEL', 2);
  210. define ('BLOG_COURSE_LEVEL', 3);
  211. define ('BLOG_SITE_LEVEL', 4);
  212. define ('BLOG_GLOBAL_LEVEL', 5);
  213. /**
  214. * Tag constanst
  215. */
  216. //To prevent problems with multibytes strings, this should not exceed the
  217. //length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
  218. define('TAG_MAX_LENGTH', 50);
  219. /**
  220. * Password policy constants
  221. */
  222. define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
  223. define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  224. define ('PASSWORD_DIGITS', '0123456789');
  225. define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
  226. if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
  227. define('SORT_LOCALE_STRING', SORT_STRING);
  228. }
  229. /// PARAMETER HANDLING ////////////////////////////////////////////////////
  230. /**
  231. * Returns a particular value for the named variable, taken from
  232. * POST or GET. If the parameter doesn't exist then an error is
  233. * thrown because we require this variable.
  234. *
  235. * This function should be used to initialise all required values
  236. * in a script that are based on parameters. Usually it will be
  237. * used like this:
  238. * $id = required_param('id');
  239. *
  240. * @param string $parname the name of the page parameter we want
  241. * @param int $type expected type of parameter
  242. * @return mixed
  243. */
  244. function required_param($parname, $type=PARAM_CLEAN) {
  245. // detect_unchecked_vars addition
  246. global $CFG;
  247. if (!empty($CFG->detect_unchecked_vars)) {
  248. global $UNCHECKED_VARS;
  249. unset ($UNCHECKED_VARS->vars[$parname]);
  250. }
  251. if (isset($_POST[$parname])) { // POST has precedence
  252. $param = $_POST[$parname];
  253. } else if (isset($_GET[$parname])) {
  254. $param = $_GET[$parname];
  255. } else {
  256. error('A required parameter ('.$parname.') was missing');
  257. }
  258. return clean_param($param, $type);
  259. }
  260. /**
  261. * Returns a particular value for the named variable, taken from
  262. * POST or GET, otherwise returning a given default.
  263. *
  264. * This function should be used to initialise all optional values
  265. * in a script that are based on parameters. Usually it will be
  266. * used like this:
  267. * $name = optional_param('name', 'Fred');
  268. *
  269. * @param string $parname the name of the page parameter we want
  270. * @param mixed $default the default value to return if nothing is found
  271. * @param int $type expected type of parameter
  272. * @return mixed
  273. */
  274. function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
  275. // detect_unchecked_vars addition
  276. global $CFG;
  277. if (!empty($CFG->detect_unchecked_vars)) {
  278. global $UNCHECKED_VARS;
  279. unset ($UNCHECKED_VARS->vars[$parname]);
  280. }
  281. if (isset($_POST[$parname])) { // POST has precedence
  282. $param = $_POST[$parname];
  283. } else if (isset($_GET[$parname])) {
  284. $param = $_GET[$parname];
  285. } else {
  286. return $default;
  287. }
  288. return clean_param($param, $type);
  289. }
  290. /**
  291. * Used by {@link optional_param()} and {@link required_param()} to
  292. * clean the variables and/or cast to specific types, based on
  293. * an options field.
  294. * <code>
  295. * $course->format = clean_param($course->format, PARAM_ALPHA);
  296. * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
  297. * </code>
  298. *
  299. * @uses $CFG
  300. * @uses PARAM_RAW
  301. * @uses PARAM_CLEAN
  302. * @uses PARAM_CLEANHTML
  303. * @uses PARAM_INT
  304. * @uses PARAM_NUMBER
  305. * @uses PARAM_ALPHA
  306. * @uses PARAM_ALPHANUM
  307. * @uses PARAM_ALPHAEXT
  308. * @uses PARAM_SEQUENCE
  309. * @uses PARAM_BOOL
  310. * @uses PARAM_NOTAGS
  311. * @uses PARAM_TEXT
  312. * @uses PARAM_SAFEDIR
  313. * @uses PARAM_CLEANFILE
  314. * @uses PARAM_FILE
  315. * @uses PARAM_PATH
  316. * @uses PARAM_HOST
  317. * @uses PARAM_URL
  318. * @uses PARAM_LOCALURL
  319. * @uses PARAM_PEM
  320. * @uses PARAM_BASE64
  321. * @uses PARAM_TAG
  322. * @uses PARAM_SEQUENCE
  323. * @param mixed $param the variable we are cleaning
  324. * @param int $type expected format of param after cleaning.
  325. * @return mixed
  326. */
  327. function clean_param($param, $type) {
  328. global $CFG;
  329. if (is_array($param)) { // Let's loop
  330. $newparam = array();
  331. foreach ($param as $key => $value) {
  332. $newparam[$key] = clean_param($value, $type);
  333. }
  334. return $newparam;
  335. }
  336. switch ($type) {
  337. case PARAM_RAW: // no cleaning at all
  338. return $param;
  339. case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
  340. if (is_numeric($param)) {
  341. return $param;
  342. }
  343. $param = stripslashes($param); // Needed for kses to work fine
  344. $param = clean_text($param); // Sweep for scripts, etc
  345. return addslashes($param); // Restore original request parameter slashes
  346. case PARAM_CLEANHTML: // prepare html fragment for display, do not store it into db!!
  347. $param = stripslashes($param); // Remove any slashes
  348. $param = clean_text($param); // Sweep for scripts, etc
  349. return trim($param);
  350. case PARAM_INT:
  351. return (int)$param; // Convert to integer
  352. case PARAM_NUMBER:
  353. return (float)$param; // Convert to integer
  354. case PARAM_ALPHA: // Remove everything not a-z
  355. return preg_replace('/[^a-zA-Z]/i', '', $param);
  356. case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
  357. return preg_replace('/[^A-Za-z0-9]/i', '', $param);
  358. case PARAM_ALPHAEXT: // Remove everything not a-zA-Z/_-
  359. return preg_replace('/[^a-zA-Z\/_-]/i', '', $param);
  360. case PARAM_SEQUENCE: // Remove everything not 0-9,
  361. return preg_replace('/[^0-9,]/i', '', $param);
  362. case PARAM_BOOL: // Convert to 1 or 0
  363. $tempstr = strtolower($param);
  364. if ($tempstr == 'on' or $tempstr == 'yes' ) {
  365. $param = 1;
  366. } else if ($tempstr == 'off' or $tempstr == 'no') {
  367. $param = 0;
  368. } else {
  369. $param = empty($param) ? 0 : 1;
  370. }
  371. return $param;
  372. case PARAM_NOTAGS: // Strip all tags
  373. return strip_tags($param);
  374. case PARAM_TEXT: // leave only tags needed for multilang
  375. return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
  376. case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
  377. return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
  378. case PARAM_CLEANFILE: // allow only safe characters
  379. return clean_filename($param);
  380. case PARAM_FILE: // Strip all suspicious characters from filename
  381. $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
  382. $param = preg_replace('~\.\.+~', '', $param);
  383. if ($param === '.') {
  384. $param = '';
  385. }
  386. return $param;
  387. case PARAM_PATH: // Strip all suspicious characters from file path
  388. $param = str_replace('\\', '/', $param);
  389. $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
  390. $param = preg_replace('~\.\.+~', '', $param);
  391. $param = preg_replace('~//+~', '/', $param);
  392. return preg_replace('~/(\./)+~', '/', $param);
  393. case PARAM_HOST: // allow FQDN or IPv4 dotted quad
  394. $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
  395. // match ipv4 dotted quad
  396. if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
  397. // confirm values are ok
  398. if ( $match[0] > 255
  399. || $match[1] > 255
  400. || $match[3] > 255
  401. || $match[4] > 255 ) {
  402. // hmmm, what kind of dotted quad is this?
  403. $param = '';
  404. }
  405. } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
  406. && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
  407. && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
  408. ) {
  409. // all is ok - $param is respected
  410. } else {
  411. // all is not ok...
  412. $param='';
  413. }
  414. return $param;
  415. case PARAM_URL: // allow safe ftp, http, mailto urls
  416. include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
  417. if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
  418. // all is ok, param is respected
  419. } else {
  420. $param =''; // not really ok
  421. }
  422. return $param;
  423. case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
  424. $param = clean_param($param, PARAM_URL);
  425. if (!empty($param)) {
  426. if (preg_match(':^/:', $param)) {
  427. // root-relative, ok!
  428. } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
  429. // absolute, and matches our wwwroot
  430. } else {
  431. // relative - let's make sure there are no tricks
  432. if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
  433. // looks ok.
  434. } else {
  435. $param = '';
  436. }
  437. }
  438. }
  439. return $param;
  440. case PARAM_PEM:
  441. $param = trim($param);
  442. // PEM formatted strings may contain letters/numbers and the symbols
  443. // forward slash: /
  444. // plus sign: +
  445. // equal sign: =
  446. // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
  447. if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
  448. list($wholething, $body) = $matches;
  449. unset($wholething, $matches);
  450. $b64 = clean_param($body, PARAM_BASE64);
  451. if (!empty($b64)) {
  452. return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
  453. } else {
  454. return '';
  455. }
  456. }
  457. return '';
  458. case PARAM_BASE64:
  459. if (!empty($param)) {
  460. // PEM formatted strings may contain letters/numbers and the symbols
  461. // forward slash: /
  462. // plus sign: +
  463. // equal sign: =
  464. if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
  465. return '';
  466. }
  467. $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
  468. // Each line of base64 encoded data must be 64 characters in
  469. // length, except for the last line which may be less than (or
  470. // equal to) 64 characters long.
  471. for ($i=0, $j=count($lines); $i < $j; $i++) {
  472. if ($i + 1 == $j) {
  473. if (64 < strlen($lines[$i])) {
  474. return '';
  475. }
  476. continue;
  477. }
  478. if (64 != strlen($lines[$i])) {
  479. return '';
  480. }
  481. }
  482. return implode("\n",$lines);
  483. } else {
  484. return '';
  485. }
  486. case PARAM_TAG:
  487. // Please note it is not safe to use the tag name directly anywhere,
  488. // it must be processed with s(), urlencode() before embedding anywhere.
  489. // remove some nasties
  490. $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
  491. //as long as magic_quotes_gpc is used, a backslash will be a
  492. //problem, so remove *all* backslash - BUT watch out for SQL injections caused by this sloppy design (skodak)
  493. $param = str_replace('\\', '', $param);
  494. //convert many whitespace chars into one
  495. $param = preg_replace('/\s+/', ' ', $param);
  496. $textlib = textlib_get_instance();
  497. $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
  498. return $param;
  499. case PARAM_TAGLIST:
  500. $tags = explode(',', $param);
  501. $result = array();
  502. foreach ($tags as $tag) {
  503. $res = clean_param($tag, PARAM_TAG);
  504. if ($res != '') {
  505. $result[] = $res;
  506. }
  507. }
  508. if ($result) {
  509. return implode(',', $result);
  510. } else {
  511. return '';
  512. }
  513. default: // throw error, switched parameters in optional_param or another serious problem
  514. error("Unknown parameter type: $type");
  515. }
  516. }
  517. /**
  518. * Return true if given value is integer or string with integer value
  519. *
  520. * @param mixed $value String or Int
  521. * @return bool true if number, false if not
  522. */
  523. function is_number($value) {
  524. if (is_int($value)) {
  525. return true;
  526. } else if (is_string($value)) {
  527. return ((string)(int)$value) === $value;
  528. } else {
  529. return false;
  530. }
  531. }
  532. /**
  533. * This function is useful for testing whether something you got back from
  534. * the HTML editor actually contains anything. Sometimes the HTML editor
  535. * appear to be empty, but actually you get back a <br> tag or something.
  536. *
  537. * @param string $string a string containing HTML.
  538. * @return boolean does the string contain any actual content - that is text,
  539. * images, objcts, etc.
  540. */
  541. function html_is_blank($string) {
  542. return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
  543. }
  544. /**
  545. * Set a key in global configuration
  546. *
  547. * Set a key/value pair in both this session's {@link $CFG} global variable
  548. * and in the 'config' database table for future sessions.
  549. *
  550. * Can also be used to update keys for plugin-scoped configs in config_plugin table.
  551. * In that case it doesn't affect $CFG.
  552. *
  553. * A NULL value will delete the entry.
  554. *
  555. * @param string $name the key to set
  556. * @param string $value the value to set (without magic quotes)
  557. * @param string $plugin (optional) the plugin scope
  558. * @uses $CFG
  559. * @return bool
  560. */
  561. function set_config($name, $value, $plugin=NULL) {
  562. /// No need for get_config because they are usually always available in $CFG
  563. global $CFG;
  564. if (empty($plugin)) {
  565. if (!array_key_exists($name, $CFG->config_php_settings)) {
  566. // So it's defined for this invocation at least
  567. if (is_null($value)) {
  568. unset($CFG->$name);
  569. } else {
  570. $CFG->$name = (string)$value; // settings from db are always strings
  571. }
  572. }
  573. if (get_field('config', 'name', 'name', $name)) {
  574. if ($value===null) {
  575. return delete_records('config', 'name', $name);
  576. } else {
  577. return set_field('config', 'value', addslashes($value), 'name', $name);
  578. }
  579. } else {
  580. if ($value===null) {
  581. return true;
  582. }
  583. $config = new object();
  584. $config->name = $name;
  585. $config->value = addslashes($value);
  586. return insert_record('config', $config);
  587. }
  588. } else { // plugin scope
  589. if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
  590. if ($value===null) {
  591. return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
  592. } else {
  593. return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
  594. }
  595. } else {
  596. if ($value===null) {
  597. return true;
  598. }
  599. $config = new object();
  600. $config->plugin = addslashes($plugin);
  601. $config->name = $name;
  602. $config->value = addslashes($value);
  603. return insert_record('config_plugins', $config);
  604. }
  605. }
  606. }
  607. /**
  608. * Get configuration values from the global config table
  609. * or the config_plugins table.
  610. *
  611. * If called with no parameters it will do the right thing
  612. * generating $CFG safely from the database without overwriting
  613. * existing values.
  614. *
  615. * If called with 2 parameters it will return a $string single
  616. * value or false of the value is not found.
  617. *
  618. * @param string $plugin
  619. * @param string $name
  620. * @uses $CFG
  621. * @return hash-like object or single value
  622. *
  623. */
  624. function get_config($plugin=NULL, $name=NULL) {
  625. global $CFG;
  626. if (!empty($name)) { // the user is asking for a specific value
  627. if (!empty($plugin)) {
  628. return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
  629. } else {
  630. return get_field('config', 'value', 'name', $name);
  631. }
  632. }
  633. // the user is after a recordset
  634. if (!empty($plugin)) {
  635. if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
  636. $configs = (array)$configs;
  637. $localcfg = array();
  638. foreach ($configs as $config) {
  639. $localcfg[$config->name] = $config->value;
  640. }
  641. return (object)$localcfg;
  642. } else {
  643. return false;
  644. }
  645. } else {
  646. // this was originally in setup.php
  647. if ($configs = get_records('config')) {
  648. $localcfg = (array)$CFG;
  649. foreach ($configs as $config) {
  650. if (!isset($localcfg[$config->name])) {
  651. $localcfg[$config->name] = $config->value;
  652. }
  653. // do not complain anymore if config.php overrides settings from db
  654. }
  655. $localcfg = (object)$localcfg;
  656. return $localcfg;
  657. } else {
  658. // preserve $CFG if DB returns nothing or error
  659. return $CFG;
  660. }
  661. }
  662. }
  663. /**
  664. * Removes a key from global configuration
  665. *
  666. * @param string $name the key to set
  667. * @param string $plugin (optional) the plugin scope
  668. * @uses $CFG
  669. * @return bool
  670. */
  671. function unset_config($name, $plugin=NULL) {
  672. global $CFG;
  673. unset($CFG->$name);
  674. if (empty($plugin)) {
  675. return delete_records('config', 'name', $name);
  676. } else {
  677. return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
  678. }
  679. }
  680. /**
  681. * Get volatile flags
  682. *
  683. * @param string $type
  684. * @param int $changedsince
  685. * @return records array
  686. *
  687. */
  688. function get_cache_flags($type, $changedsince=NULL) {
  689. $type = addslashes($type);
  690. $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
  691. if ($changedsince !== NULL) {
  692. $changedsince = (int)$changedsince;
  693. $sqlwhere .= ' AND timemodified > ' . $changedsince;
  694. }
  695. $cf = array();
  696. if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
  697. foreach ($flags as $flag) {
  698. $cf[$flag->name] = $flag->value;
  699. }
  700. }
  701. return $cf;
  702. }
  703. /**
  704. * Use this funciton to get a list of users from a config setting of type admin_setting_users_with_capability.
  705. * @param string $value the value of the config setting.
  706. * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
  707. * @return array of user objects.
  708. */
  709. function get_users_from_config($value, $capability) {
  710. global $CFG;
  711. if ($value == '$@ALL@$') {
  712. $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
  713. } else if ($value) {
  714. $usernames = explode(',', $value);
  715. $users = get_records_select('user', "username IN ('" . implode("','", $usernames) . "') AND mnethostid = " . $CFG->mnet_localhost_id);
  716. } else {
  717. $users = array();
  718. }
  719. return $users;
  720. }
  721. /**
  722. * Get volatile flags
  723. *
  724. * @param string $type
  725. * @param string $name
  726. * @param int $changedsince
  727. * @return records array
  728. *
  729. */
  730. function get_cache_flag($type, $name, $changedsince=NULL) {
  731. $type = addslashes($type);
  732. $name = addslashes($name);
  733. $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
  734. if ($changedsince !== NULL) {
  735. $changedsince = (int)$changedsince;
  736. $sqlwhere .= ' AND timemodified > ' . $changedsince;
  737. }
  738. return get_field_select('cache_flags', 'value', $sqlwhere);
  739. }
  740. /**
  741. * Set a volatile flag
  742. *
  743. * @param string $type the "type" namespace for the key
  744. * @param string $name the key to set
  745. * @param string $value the value to set (without magic quotes) - NULL will remove the flag
  746. * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
  747. * @return bool
  748. */
  749. function set_cache_flag($type, $name, $value, $expiry=NULL) {
  750. $timemodified = time();
  751. if ($expiry===NULL || $expiry < $timemodified) {
  752. $expiry = $timemodified + 24 * 60 * 60;
  753. } else {
  754. $expiry = (int)$expiry;
  755. }
  756. if ($value === NULL) {
  757. return unset_cache_flag($type,$name);
  758. }
  759. $type = addslashes($type);
  760. $name = addslashes($name);
  761. if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
  762. if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
  763. return true; //no need to update; helps rcache too
  764. }
  765. $f->value = addslashes($value);
  766. $f->expiry = $expiry;
  767. $f->timemodified = $timemodified;
  768. return update_record('cache_flags', $f);
  769. } else {
  770. $f = new object();
  771. $f->flagtype = $type;
  772. $f->name = $name;
  773. $f->value = addslashes($value);
  774. $f->expiry = $expiry;
  775. $f->timemodified = $timemodified;
  776. return (bool)insert_record('cache_flags', $f);
  777. }
  778. }
  779. /**
  780. * Removes a single volatile flag
  781. *
  782. * @param string $type the "type" namespace for the key
  783. * @param string $name the key to set
  784. * @uses $CFG
  785. * @return bool
  786. */
  787. function unset_cache_flag($type, $name) {
  788. return delete_records('cache_flags',
  789. 'name', addslashes($name),
  790. 'flagtype', addslashes($type));
  791. }
  792. /**
  793. * Garbage-collect volatile flags
  794. *
  795. */
  796. function gc_cache_flags() {
  797. return delete_records_select('cache_flags', 'expiry < ' . time());
  798. }
  799. /**
  800. * Refresh current $USER session global variable with all their current preferences.
  801. * @uses $USER
  802. */
  803. function reload_user_preferences() {
  804. global $USER;
  805. //reset preference
  806. $USER->preference = array();
  807. if (!isloggedin() or isguestuser()) {
  808. // no permanent storage for not-logged-in user and guest
  809. } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
  810. foreach ($preferences as $preference) {
  811. $USER->preference[$preference->name] = $preference->value;
  812. }
  813. }
  814. return true;
  815. }
  816. /**
  817. * Sets a preference for the current user
  818. * Optionally, can set a preference for a different user object
  819. * @uses $USER
  820. * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
  821. * @param string $name The key to set as preference for the specified user
  822. * @param string $value The value to set forthe $name key in the specified user's record
  823. * @param int $otheruserid A moodle user ID
  824. * @return bool
  825. */
  826. function set_user_preference($name, $value, $otheruserid=NULL) {
  827. global $USER;
  828. if (!isset($USER->preference)) {
  829. reload_user_preferences();
  830. }
  831. if (empty($name)) {
  832. return false;
  833. }
  834. $nostore = false;
  835. if (empty($otheruserid)){
  836. if (!isloggedin() or isguestuser()) {
  837. $nostore = true;
  838. }
  839. $userid = $USER->id;
  840. } else {
  841. if (isguestuser($otheruserid)) {
  842. $nostore = true;
  843. }
  844. $userid = $otheruserid;
  845. }
  846. $return = true;
  847. if ($nostore) {
  848. // no permanent storage for not-logged-in user and guest
  849. } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
  850. if ($preference->value === $value) {
  851. return true;
  852. }
  853. if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
  854. $return = false;
  855. }
  856. } else {
  857. $preference = new object();
  858. $preference->userid = $userid;
  859. $preference->name = addslashes($name);
  860. $preference->value = addslashes((string)$value);
  861. if (!insert_record('user_preferences', $preference)) {
  862. $return = false;
  863. }
  864. }
  865. // update value in USER session if needed
  866. if ($userid == $USER->id) {
  867. $USER->preference[$name] = (string)$value;
  868. }
  869. return $return;
  870. }
  871. /**
  872. * Unsets a preference completely by deleting it from the database
  873. * Optionally, can set a preference for a different user id
  874. * @uses $USER
  875. * @param string $name The key to unset as preference for the specified user
  876. * @param int $otheruserid A moodle user ID
  877. */
  878. function unset_user_preference($name, $otheruserid=NULL) {
  879. global $USER;
  880. if (!isset($USER->preference)) {
  881. reload_user_preferences();
  882. }
  883. if (empty($otheruserid)){
  884. $userid = $USER->id;
  885. } else {
  886. $userid = $otheruserid;
  887. }
  888. //Delete the preference from $USER if needed
  889. if ($userid == $USER->id) {
  890. unset($USER->preference[$name]);
  891. }
  892. //Then from DB
  893. return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
  894. }
  895. /**
  896. * Sets a whole array of preferences for the current user
  897. * @param array $prefarray An array of key/value pairs to be set
  898. * @param int $otheruserid A moodle user ID
  899. * @return bool
  900. */
  901. function set_user_preferences($prefarray, $otheruserid=NULL) {
  902. if (!is_array($prefarray) or empty($prefarray)) {
  903. return false;
  904. }
  905. $return = true;
  906. foreach ($prefarray as $name => $value) {
  907. // The order is important; test for return is done first
  908. $return = (set_user_preference($name, $value, $otheruserid) && $return);
  909. }
  910. return $return;
  911. }
  912. /**
  913. * If no arguments are supplied this function will return
  914. * all of the current user preferences as an array.
  915. * If a name is specified then this function
  916. * attempts to return that particular preference value. If
  917. * none is found, then the optional value $default is returned,
  918. * otherwise NULL.
  919. * @param string $name Name of the key to use in finding a preference value
  920. * @param string $default Value to be returned if the $name key is not set in the user preferences
  921. * @param int $otheruserid A moodle user ID
  922. * @uses $USER
  923. * @return string
  924. */
  925. function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
  926. global $USER;
  927. if (!isset($USER->preference)) {
  928. reload_user_preferences();
  929. }
  930. if (empty($otheruserid)){
  931. $userid = $USER->id;
  932. } else {
  933. $userid = $otheruserid;
  934. }
  935. if ($userid == $USER->id) {
  936. $preference = $USER->preference;
  937. } else {
  938. $preference = array();
  939. if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
  940. foreach ($prefdata as $pref) {
  941. $preference[$pref->name] = $pref->value;
  942. }
  943. }
  944. }
  945. if (empty($name)) {
  946. return $preference; // All values
  947. } else if (array_key_exists($name, $preference)) {
  948. return $preference[$name]; // The single value
  949. } else {
  950. return $default; // Default value (or NULL)
  951. }
  952. }
  953. /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
  954. /**
  955. * Given date parts in user time produce a GMT timestamp.
  956. *
  957. * @param int $year The year part to create timestamp of
  958. * @param int $month The month part to create timestamp of
  959. * @param int $day The day part to create timestamp of
  960. * @param int $hour The hour part to create timestamp of
  961. * @param int $minute The minute part to create timestamp of
  962. * @param int $second The second part to create timestamp of
  963. * @param mixed $timezone Timezone modifier, if 99 then use default user's timezone
  964. * @param bool $applydst Toggle Daylight Saving Time, default true, will be
  965. * applied only if timezone is 99 or string.
  966. * @return int timestamp
  967. * @todo Finish documenting this function
  968. */
  969. function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
  970. //save input timezone, required for dst offset check.
  971. $passedtimezone = $timezone;
  972. $timezone = get_user_timezone_offset($timezone);
  973. if (abs($timezone) > 13) { //server time
  974. $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
  975. } else {
  976. $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
  977. $time = usertime($time, $timezone);
  978. //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
  979. if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
  980. $time -= dst_offset_on($time, $passedtimezone);
  981. }
  982. }
  983. return $time;
  984. }
  985. /**
  986. * Given an amount of time in seconds, returns string
  987. * formatted nicely as weeks, days, hours etc as needed
  988. *
  989. * @uses MINSECS
  990. * @uses HOURSECS
  991. * @uses DAYSECS
  992. * @uses YEARSECS
  993. * @param int $totalsecs ?
  994. * @param array $str ?
  995. * @return string
  996. */
  997. function format_time($totalsecs, $str=NULL) {
  998. $totalsecs = abs($totalsecs);
  999. if (!$str) { // Create the str structure the slow way
  1000. $str->day = get_string('day');
  1001. $str->days = get_string('days');
  1002. $str->hour = get_string('hour');
  1003. $str->hours = get_string('hours');
  1004. $str->min = get_string('min');
  1005. $str->mins = get_string('mins');
  1006. $str->sec = get_string('sec');
  1007. $str->secs = get_string('secs');
  1008. $str->year = get_string('year');
  1009. $str->years = get_string('years');
  1010. }
  1011. $years = floor($totalsecs/YEARSECS);
  1012. $remainder = $totalsecs - ($years*YEARSECS);
  1013. $days = floor($remainder/DAYSECS);
  1014. $remainder = $totalsecs - ($days*DAYSECS);
  1015. $hours = floor($remainder/HOURSECS);
  1016. $remainder = $remainder - ($hours*HOURSECS);
  1017. $mins = floor($remainder/MINSECS);
  1018. $secs = $remainder - ($mins*MINSECS);
  1019. $ss = ($secs == 1) ? $str->sec : $str->secs;
  1020. $sm = ($mins == 1) ? $str->min : $str->mins;
  1021. $sh = ($hours == 1) ? $str->hour : $str->hours;
  1022. $sd = ($days == 1) ? $str->day : $str->days;
  1023. $sy = ($years == 1) ? $str->year : $str->years;
  1024. $oyears = '';
  1025. $odays = '';
  1026. $ohours = '';
  1027. $omins = '';
  1028. $osecs = '';
  1029. if ($years) $oyears = $years .' '. $sy;
  1030. if ($days) $odays = $days .' '. $sd;
  1031. if ($hours) $ohours = $hours .' '. $sh;
  1032. if ($mins) $omins = $mins .' '. $sm;
  1033. if ($secs) $osecs = $secs .' '. $ss;
  1034. if ($years) return trim($oyears .' '. $odays);
  1035. if ($days) return trim($odays .' '. $ohours);
  1036. if ($hours) return trim($ohours .' '. $omins);
  1037. if ($mins) return trim($omins .' '. $osecs);
  1038. if ($secs) return $osecs;
  1039. return get_string('now');
  1040. }
  1041. /**
  1042. * Returns a formatted string that represents a date in user time
  1043. * <b>WARNING: note that the format is for strftime(), not date().</b>
  1044. * Because of a bug in most Windows time libraries, we can't use
  1045. * the nicer %e, so we have to use %d which has leading zeroes.
  1046. * A lot of the fuss in the function is just getting rid of these leading
  1047. * zeroes as efficiently as possible.
  1048. *
  1049. * If parameter fixday = true (default), then take off leading
  1050. * zero from %d, else mantain it.
  1051. *
  1052. * @uses HOURSECS
  1053. * @param int $date timestamp in GMT
  1054. * @param string $format strftime format
  1055. * @param mixed $timezone by default, uses the user's time zone. if numeric and
  1056. * not 99 then daylight saving will not be added.
  1057. * @param bool $fixday If true (default) then the leading
  1058. * zero from %d is removed. If false then the leading zero is mantained.
  1059. * @return string
  1060. */
  1061. function userdate($date, $format='', $timezone=99, $fixday = true) {
  1062. global $CFG;
  1063. if (empty($format)) {
  1064. $format = get_string('strftimedaydatetime');
  1065. }
  1066. if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
  1067. $fixday = false;
  1068. } else if ($fixday) {
  1069. $formatnoday = str_replace('%d', 'DD', $format);
  1070. $fixday = ($formatnoday != $format);
  1071. }
  1072. //add daylight saving offset for string timezones only, as we can't get dst for
  1073. //float values. if timezone is 99 (user default timezone), then try update dst.
  1074. if ((99 == $timezone) || !is_numeric($timezone)) {
  1075. $date += dst_offset_on($date, $timezone);
  1076. }
  1077. $timezone = get_user_timezone_offset($timezone);
  1078. if (abs($timezone) > 13) { /// Server time
  1079. if ($fixday) {
  1080. $datestring = strftime($formatnoday, $date);
  1081. $daystring = str_replace(' 0', '', strftime(' %d', $date));
  1082. $datestring = str_replace('DD', $daystring, $datestring);
  1083. } else {
  1084. $datestring = strftime($format, $date);
  1085. }
  1086. } else {
  1087. $date += (int)($timezone * 3600);
  1088. if ($fixday) {
  1089. $datestring = gmstrftime($formatnoday, $date);
  1090. $daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
  1091. $datestring = str_replace('DD', $daystring, $datestring);
  1092. } else {
  1093. $datestring = gmstrftime($format, $date);
  1094. }
  1095. }
  1096. /// If we are running under Windows convert from windows encoding to UTF-8
  1097. /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
  1098. if ($CFG->ostype == 'WINDOWS') {
  1099. if ($localewincharset = get_string('localewincharset')) {
  1100. $textlib = textlib_get_instance();
  1101. $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
  1102. }
  1103. }
  1104. return $datestring;
  1105. }
  1106. /**
  1107. * Given a $time timestamp in GMT (seconds since epoch),
  1108. * returns an array that represents the date in user time
  1109. *
  1110. * @uses HOURSECS
  1111. * @param int $time Timestamp in GMT
  1112. * @param mixed $timezone offset time with timezone, if float and not 99, then no
  1113. * dst offset is applyed
  1114. * @return array An array that represents the date in user time
  1115. * @todo Finish documenting this function
  1116. */
  1117. function usergetdate($time, $timezone=99) {
  1118. //save input timezone, required for dst offset check.
  1119. $passedtimezone = $timezone;
  1120. $timezone = get_user_timezone_offset($timezone);
  1121. if (abs($timezone) > 13) { // Server time
  1122. return getdate($time);
  1123. }
  1124. //add daylight saving offset for string timezones only, as we can't get dst for
  1125. //float values. if timezone is 99 (user default timezone), then try update dst.
  1126. if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
  1127. $time += dst_offset_on($time, $passedtimezone);
  1128. }
  1129. $time += intval((float)$timezone * HOURSECS);
  1130. $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
  1131. //be careful to ensure the returned array matches that produced by getdate() above
  1132. list(
  1133. $getdate['month'],
  1134. $getdate['weekday'],
  1135. $getdate['yday'],
  1136. $getdate['year'],
  1137. $getdate['mon'],
  1138. $getdate['wday'],
  1139. $getdate['mday'],
  1140. $getdate['hours'],
  1141. $getdate['minutes'],
  1142. $getdate['seconds']
  1143. ) = explode('_', $datestring);
  1144. return $getdate;
  1145. }
  1146. /**
  1147. * Given a GMT timestamp (seconds since epoch), offsets it by
  1148. * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
  1149. *
  1150. * @uses HOURSECS
  1151. * @param int $date Timestamp in GMT
  1152. * @param float $timezone
  1153. * @return int
  1154. */
  1155. function usertime($date, $timezone=99) {
  1156. $timezone = get_user_timezone_offset($timezone);
  1157. if (abs($timezone) > 13) {
  1158. return $date;
  1159. }
  1160. return $date - (int)($timezone * HOURSECS);
  1161. }
  1162. /**
  1163. * Given a time, return the GMT timestamp of the most recent midnight
  1164. * for the current user.
  1165. *
  1166. * @param int $date Timestamp in GMT
  1167. * @param float $timezone ?
  1168. * @return ?
  1169. */
  1170. function usergetmidnight($date, $timezone=99) {
  1171. $userdate = usergetdate($date, $timezone);
  1172. // Time of midnight of this user's day, in GMT
  1173. return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
  1174. }
  1175. /**
  1176. * Returns a string that prints the user's timezone
  1177. *
  1178. * @param float $timezone The user's timezone
  1179. * @return string
  1180. */
  1181. function usertimezone($timezone=99) {
  1182. $tz = get_user_timezone($timezone);
  1183. if (!is_float($tz)) {
  1184. return $tz;
  1185. }
  1186. if(abs($tz) > 13) { // Server time
  1187. return get_string('serverlocaltime');
  1188. }
  1189. if($tz == intval($tz)) {
  1190. // Don't show .0 for whole hours
  1191. $tz = intval($tz);
  1192. }
  1193. if($tz == 0) {
  1194. return 'UTC';
  1195. }
  1196. else if($tz > 0) {
  1197. return 'UTC+'.$tz;
  1198. }
  1199. else {
  1200. return 'UTC'.$tz;
  1201. }
  1202. }
  1203. /**
  1204. * Returns a float which represents the user's timezone difference from GMT in hours
  1205. * Checks various settings and picks the most dominant of those which have a value
  1206. *
  1207. * @uses $CFG
  1208. * @uses $USER
  1209. * @param float $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
  1210. * @return int
  1211. */
  1212. function get_user_timezone_offset($tz = 99) {
  1213. global $USER, $CFG;
  1214. $tz = get_user_timezone($tz);
  1215. if (is_float($tz)) {
  1216. return $tz;
  1217. } else {
  1218. $tzrecord = get_timezone_record($tz);
  1219. if (empty($tzrecord)) {
  1220. return 99.0;
  1221. }
  1222. return (float)$tzrecord->gmtoff / HOURMINS;
  1223. }
  1224. }
  1225. /**
  1226. * Returns an int which represents the systems's timezone difference from GMT in seconds
  1227. * @param mixed $tz timezone
  1228. * @return int if found, false is timezone 99 or error
  1229. */
  1230. function get_timezone_offset($tz) {
  1231. global $CFG;
  1232. if ($tz == 99) {
  1233. return false;
  1234. }
  1235. if (is_numeric($tz)) {
  1236. return intval($tz * 60*60);
  1237. }
  1238. if (!$tzrecord = get_timezone_record($tz)) {
  1239. return false;
  1240. }
  1241. return intval($tzrecord->gmtoff * 60);
  1242. }
  1243. /**
  1244. * Returns a float or a string which denotes the user's timezone
  1245. * A float value means that a simple offset from GMT is used, while a string (it will be the name of a timezone in the database)
  1246. * means that for this timezone there are also DST rules to be taken into account
  1247. * Checks various settings and picks the most dominant of those which have a value
  1248. *
  1249. * @uses $USER
  1250. * @uses $CFG
  1251. * @param mixed $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
  1252. * @return mixed
  1253. */
  1254. function get_user_timezone($tz = 99) {
  1255. global $USER, $CFG;
  1256. $timezones = array(
  1257. $tz,
  1258. isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
  1259. isset($USER->timezone) ? $USER->timezone : 99,
  1260. isset($CFG->timezone) ? $CFG->timezone : 99,
  1261. );
  1262. $tz = 99;
  1263. while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
  1264. $tz = $next['value'];
  1265. }
  1266. return is_numeric($tz) ? (float) $tz : $tz;
  1267. }
  1268. /**
  1269. * ?
  1270. *
  1271. * @uses $CFG
  1272. * @uses $db
  1273. * @param string $timezonename ?
  1274. * @return object
  1275. */
  1276. function get_timezone_record($timezonename) {
  1277. global $CFG, $db;
  1278. static $cache = NULL;
  1279. if ($cache === NULL) {
  1280. $cache = array();
  1281. }
  1282. if (isset($cache[$timezonename])) {
  1283. return $cache[$timezonename];
  1284. }
  1285. return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
  1286. WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
  1287. }
  1288. /**
  1289. * ?
  1290. *
  1291. * @uses $CFG
  1292. * @uses $USER
  1293. * @param ? $fromyear ?
  1294. * @param ? $to_year ?
  1295. * @return bool
  1296. */
  1297. function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
  1298. global $CFG, $SESSION;
  1299. $usertz = get_user_timezone($strtimezone);
  1300. if (is_float($usertz)) {
  1301. // Trivial timezone, no DST
  1302. return false;
  1303. }
  1304. if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
  1305. // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
  1306. unset($SESSION->dst_offsets);
  1307. unset($SESSION->dst_range);
  1308. }
  1309. if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
  1310. // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
  1311. // This will be the return path most of the time, pretty light computationally
  1312. return true;
  1313. }
  1314. // Reaching here means we either need to extend our table or create it from scratch
  1315. // Remember which TZ we calculated these changes for
  1316. $SESSION->dst_offsettz = $usertz;
  1317. if(empty($SESSION->dst_offsets)) {
  1318. // If we 're creating from scratch, put the two guard elements in there
  1319. $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
  1320. }
  1321. if(empty($SESSION->dst_range)) {
  1322. // If creating from scratch
  1323. $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
  1324. $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
  1325. // Fill in the array with the extra years we need to process
  1326. $yearstoprocess = array();
  1327. for($i = $from; $i <= $to; ++$i) {
  1328. $yearstoprocess[] = $i;
  1329. }
  1330. // Take note of which years we have processed for future calls
  1331. $SESSION->dst_range = array($from, $to);
  1332. }
  1333. else {
  1334. // If needing to extend the table, do the same
  1335. $yearstoprocess = array();
  1336. $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
  1337. $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
  1338. if($from < $SESSION->dst_range[0]) {
  1339. // Take note of which years we need to process and then note that we have processed them for future calls
  1340. for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
  1341. $yearstoprocess[] = $i;
  1342. }
  1343. $SESSION->dst_range[0] = $from;
  1344. }
  1345. if($to > $SESSION->dst_range[1]) {
  1346. // Take note of which years we need to process and then note that we have processed them for future calls
  1347. for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
  1348. $yearstoprocess[] = $i;
  1349. }
  1350. $SESSION->dst_range[1] = $to;
  1351. }
  1352. }
  1353. if(empty($yearstoprocess)) {
  1354. // This means that there was a call requesting a SMALLER range than we have already calculated
  1355. return true;
  1356. }
  1357. // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
  1358. // Also, the array is sorted in descending timestamp order!
  1359. // Get DB data
  1360. static $presets_cache = array();
  1361. if (!isset($presets_cache[$usertz])) {
  1362. $presets_cache[$usertz] = get_records('timezone', 'name', $usertz, 'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, std_startday, std_weekday, std_skipweeks, std_time');
  1363. }
  1364. if(empty($presets_cache[$usertz])) {
  1365. return false;
  1366. }
  1367. // Remove ending guard (first element of the array)
  1368. reset($SESSION->dst_offsets);
  1369. unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
  1370. // Add all required change timestamps
  1371. foreach($yearstoprocess as $y) {
  1372. // Find the record which is in effect for the year $y
  1373. foreach($presets_cache[$usertz] as $year => $preset) {
  1374. if($year <= $y) {
  1375. break;
  1376. }
  1377. }
  1378. $changes = dst_changes_for_year($y, $preset);
  1379. if($changes === NULL) {
  1380. continue;
  1381. }
  1382. if($changes['dst'] != 0) {
  1383. $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
  1384. }
  1385. if($changes['std'] != 0) {
  1386. $SESSION->dst_offsets[$changes['std']] = 0;
  1387. }
  1388. }
  1389. // Put in a guard element at the top
  1390. $maxtimestamp = max(array_keys($SESSION->dst_offsets));
  1391. $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
  1392. // Sort again
  1393. krsort($SESSION->dst_offsets);
  1394. return true;
  1395. }
  1396. function dst_changes_for_year($year, $timezone) {
  1397. if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
  1398. return NULL;
  1399. }
  1400. $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
  1401. $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
  1402. list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
  1403. list($std_hour, $std_min) = explode(':', $timezone->std_time);
  1404. $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
  1405. $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
  1406. // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
  1407. // This has the advantage of being able to have negative values for hour, i.e. for timezones
  1408. // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
  1409. $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
  1410. $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
  1411. return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
  1412. }
  1413. // $time must NOT be compensated at all, it has to be a pure timestamp
  1414. function dst_offset_on($time, $strtimezone = NULL) {
  1415. global $SESSION;
  1416. if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
  1417. return 0;
  1418. }
  1419. reset($SESSION->dst_offsets);
  1420. while(list($from, $offset) = each($SESSION->dst_offsets)) {
  1421. if($from <= $time) {
  1422. break;
  1423. }
  1424. }
  1425. // This is the normal return path
  1426. if($offset !== NULL) {
  1427. return $offset;
  1428. }
  1429. // Reaching this point means we haven't calculated far enough, do it now:
  1430. // Calculate extra DST changes if needed and recurse. The recursion always
  1431. // moves toward the stopping condition, so will always end.
  1432. if($from == 0) {
  1433. // We need a year smaller than $SESSION->dst_range[0]
  1434. if($SESSION->dst_range[0] == 1971) {
  1435. return 0;
  1436. }
  1437. calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
  1438. return dst_offset_on($time, $strtimezone);
  1439. }
  1440. else {
  1441. // We need a year larger than $SESSION->dst_range[1]
  1442. if($SESSION->dst_range[1] == 2035) {
  1443. return 0;
  1444. }
  1445. calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
  1446. return dst_offset_on($time, $strtimezone);
  1447. }
  1448. }
  1449. function find_day_in_month($startday, $weekday, $month, $year) {
  1450. $daysinmonth = days_in_month($month, $year);
  1451. if($weekday == -1) {
  1452. // Don't care about weekday, so return:
  1453. // abs($startday) if $startday != -1
  1454. // $daysinmonth otherwise
  1455. return ($startday == -1) ? $daysinmonth : abs($startday);
  1456. }
  1457. // From now on we 're looking for a specific weekday
  1458. // Give "end of month" its actual value, since we know it
  1459. if($startday == -1) {
  1460. $startday = -1 * $daysinmonth;
  1461. }
  1462. // Starting from day $startday, the sign is the direction
  1463. if($startday < 1) {
  1464. $startday = abs($startday);
  1465. $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
  1466. // This is the last such weekday of the month
  1467. $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
  1468. if($lastinmonth > $daysinmonth) {
  1469. $lastinmonth -= 7;
  1470. }
  1471. // Find the first such weekday <= $startday
  1472. while($lastinmonth > $startday) {
  1473. $lastinmonth -= 7;
  1474. }
  1475. return $lastinmonth;
  1476. }
  1477. else {
  1478. $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
  1479. $diff = $weekday - $indexweekday;
  1480. if($diff < 0) {
  1481. $diff += 7;
  1482. }
  1483. // This is the first such weekday of the month equal to or after $startday
  1484. $firstfromindex = $startday + $diff;
  1485. return $firstfromindex;
  1486. }
  1487. }
  1488. /**
  1489. * Calculate the number of days in a given month
  1490. *
  1491. * @param int $month The month whose day count is sought
  1492. * @param int $year The year of the month whose day count is sought
  1493. * @return int
  1494. */
  1495. function days_in_month($month, $year) {
  1496. return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
  1497. }
  1498. /**
  1499. * Calculate the position in the week of a specific calendar day
  1500. *
  1501. * @param int $day The day of the date whose position in the week is sought
  1502. * @param int $month The month of the date whose position in the week is sought
  1503. * @param int $year The year of the date whose position in the week is sought
  1504. * @return int
  1505. */
  1506. function dayofweek($day, $month, $year) {
  1507. // I wonder if this is any different from
  1508. // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
  1509. return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
  1510. }
  1511. /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
  1512. /**
  1513. * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
  1514. * if one does not already exist, but does not overwrite existing sesskeys. Returns the
  1515. * sesskey string if $USER exists, or boolean false if not.
  1516. *
  1517. * @uses $USER
  1518. * @return string
  1519. */
  1520. function sesskey() {
  1521. global $USER;
  1522. if(!isset($USER)) {
  1523. return false;
  1524. }
  1525. if (empty($USER->sesskey)) {
  1526. $USER->sesskey = random_string(10);
  1527. }
  1528. return $USER->sesskey;
  1529. }
  1530. /**
  1531. * For security purposes, this function will check that the currently
  1532. * given sesskey (passed as a parameter to the script or this function)
  1533. * matches that of the current user.
  1534. *
  1535. * @param string $sesskey optionally provided sesskey
  1536. * @return bool
  1537. */
  1538. function confirm_sesskey($sesskey=NULL) {
  1539. global $USER;
  1540. if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
  1541. return true;
  1542. }
  1543. if (empty($sesskey)) {
  1544. $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
  1545. }
  1546. if (!isset($USER->sesskey)) {
  1547. return false;
  1548. }
  1549. return ($USER->sesskey === $sesskey);
  1550. }
  1551. /**
  1552. * Check the session key using {@link confirm_sesskey()},
  1553. * and cause a fatal error if it does not match.
  1554. */
  1555. function require_sesskey() {
  1556. if (!confirm_sesskey()) {
  1557. print_error('invalidsesskey');
  1558. }
  1559. }
  1560. /**
  1561. * Setup all global $CFG course variables, set locale and also themes
  1562. * This function can be used on pages that do not require login instead of require_login()
  1563. *
  1564. * @param mixed $courseorid id of the course or course object
  1565. */
  1566. function course_setup($courseorid=0) {
  1567. global $COURSE, $CFG, $SITE;
  1568. /// Redefine global $COURSE if needed
  1569. if (empty($courseorid)) {
  1570. // no change in global $COURSE - for backwards compatibiltiy
  1571. // if require_rogin() used after require_login($courseid);
  1572. } else if (is_object($courseorid)) {
  1573. $COURSE = clone($courseorid);
  1574. } else {
  1575. global $course; // used here only to prevent repeated fetching from DB - may be removed later
  1576. if ($courseorid == SITEID) {
  1577. $COURSE = clone($SITE);
  1578. } else if (!empty($course->id) and $course->id == $courseorid) {
  1579. $COURSE = clone($course);
  1580. } else {
  1581. if (!$COURSE = get_record('course', 'id', $courseorid)) {
  1582. error('Invalid course ID');
  1583. }
  1584. }
  1585. }
  1586. /// set locale and themes
  1587. moodle_setlocale();
  1588. theme_setup();
  1589. }
  1590. /**
  1591. * This function checks that the current user is logged in and has the
  1592. * required privileges
  1593. *
  1594. * This function checks that the current user is logged in, and optionally
  1595. * whether they are allowed to be in a particular course and view a particular
  1596. * course module.
  1597. * If they are not logged in, then it redirects them to the site login unless
  1598. * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
  1599. * case they are automatically logged in as guests.
  1600. * If $courseid is given and the user is not enrolled in that course then the
  1601. * user is redirected to the course enrolment page.
  1602. * If $cm is given and the coursemodule is hidden and the user is not a teacher
  1603. * in the course then the user is redirected to the course home page.
  1604. *
  1605. * @uses $CFG
  1606. * @uses $SESSION
  1607. * @uses $USER
  1608. * @uses $FULLME
  1609. * @uses SITEID
  1610. * @uses $COURSE
  1611. * @param mixed $courseorid id of the course or course object
  1612. * @param bool $autologinguest
  1613. * @param object $cm course module object
  1614. * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
  1615. * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
  1616. * in order to keep redirects working properly. MDL-14495
  1617. */
  1618. function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
  1619. global $CFG, $SESSION, $USER, $COURSE, $FULLME;
  1620. /// setup global $COURSE, themes, language and locale
  1621. course_setup($courseorid);
  1622. /// If the user is not even logged in yet then make sure they are
  1623. if (!isloggedin()) {
  1624. //NOTE: $USER->site check was obsoleted by session test cookie,
  1625. // $USER->confirmed test is in login/index.php
  1626. if ($setwantsurltome) {
  1627. $SESSION->wantsurl = $FULLME;
  1628. }
  1629. if (!empty($_SERVER['HTTP_REFERER'])) {
  1630. $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
  1631. }
  1632. if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
  1633. $loginguest = '?loginguest=true';
  1634. } else {
  1635. $loginguest = '';
  1636. }
  1637. if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
  1638. redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
  1639. } else {
  1640. $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
  1641. redirect($wwwroot .'/login/index.php');
  1642. }
  1643. exit;
  1644. }
  1645. /// loginas as redirection if needed
  1646. if ($COURSE->id != SITEID and !empty($USER->realuser)) {
  1647. if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
  1648. if ($USER->loginascontext->instanceid != $COURSE->id) {
  1649. print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
  1650. }
  1651. }
  1652. }
  1653. /// check whether the user should be changing password (but only if it is REALLY them)
  1654. if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
  1655. $userauth = get_auth_plugin($USER->auth);
  1656. if ($userauth->can_change_password()) {
  1657. $SESSION->wantsurl = $FULLME;
  1658. if ($changeurl = $userauth->change_password_url()) {
  1659. //use plugin custom url
  1660. redirect($changeurl);
  1661. } else {
  1662. //use moodle internal method
  1663. if (empty($CFG->loginhttps)) {
  1664. redirect($CFG->wwwroot .'/login/change_password.php');
  1665. } else {
  1666. $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
  1667. redirect($wwwroot .'/login/change_password.php');
  1668. }
  1669. }
  1670. } else {
  1671. print_error('nopasswordchangeforced', 'auth');
  1672. }
  1673. }
  1674. /// Check that the user account is properly set up
  1675. if (user_not_fully_set_up($USER)) {
  1676. $SESSION->wantsurl = $FULLME;
  1677. redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
  1678. }
  1679. /// Make sure current IP matches the one for this session (if required)
  1680. if (!empty($CFG->tracksessionip)) {
  1681. if ($USER->sessionIP != md5(getremoteaddr())) {
  1682. print_error('sessionipnomatch', 'error');
  1683. }
  1684. }
  1685. /// Make sure the USER has a sesskey set up. Used for checking script parameters.
  1686. sesskey();
  1687. // Check that the user has agreed to a site policy if there is one
  1688. if (!empty($CFG->sitepolicy)) {
  1689. if (!$USER->policyagreed) {
  1690. $SESSION->wantsurl = $FULLME;
  1691. redirect($CFG->wwwroot .'/user/policy.php');
  1692. }
  1693. }
  1694. // Fetch the system context, we are going to use it a lot.
  1695. $sysctx = get_context_instance(CONTEXT_SYSTEM);
  1696. /// If the site is currently under maintenance, then print a message
  1697. if (!has_capability('moodle/site:config', $sysctx)) {
  1698. if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
  1699. print_maintenance_message();
  1700. exit;
  1701. }
  1702. }
  1703. /// groupmembersonly access control
  1704. if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
  1705. if (isguestuser() or !groups_has_membership($cm)) {
  1706. print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
  1707. }
  1708. }
  1709. // Fetch the course context, and prefetch its child contexts
  1710. if (!isset($COURSE->context)) {
  1711. if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
  1712. print_error('nocontext');
  1713. }
  1714. }
  1715. if (!empty($cm) && !isset($cm->context)) {
  1716. if ( ! $cm->context = get_context_instance(CONTEXT_MODULE, $cm->id) ) {
  1717. print_error('nocontext');
  1718. }
  1719. }
  1720. if ($COURSE->id == SITEID) {
  1721. /// Eliminate hidden site activities straight away
  1722. if (!empty($cm) && !$cm->visible
  1723. && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
  1724. redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
  1725. }
  1726. user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
  1727. return;
  1728. } else {
  1729. /// Check if the user can be in a particular course
  1730. if (empty($USER->access['rsw'][$COURSE->context->path])) {
  1731. //
  1732. // MDL-13900 - If the course or the parent category are hidden
  1733. // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
  1734. //
  1735. if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
  1736. !has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
  1737. print_header_simple();
  1738. notice(get_string('coursehidden'), $CFG->wwwroot .'/');
  1739. }
  1740. }
  1741. /// Non-guests who don't currently have access, check if they can be allowed in as a guest
  1742. if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
  1743. if ($COURSE->guest == 1) {
  1744. // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
  1745. $USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
  1746. }
  1747. }
  1748. /// If the user is a guest then treat them according to the course policy about guests
  1749. if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
  1750. if (has_capability('moodle/site:doanything', $sysctx)) {
  1751. // administrators must be able to access any course - even if somebody gives them guest access
  1752. user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
  1753. return;
  1754. }
  1755. switch ($COURSE->guest) { /// Check course policy about guest access
  1756. case 1: /// Guests always allowed
  1757. if (!has_capability('moodle/course:view', $COURSE->context)) { // Prohibited by capability
  1758. print_header_simple();
  1759. notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
  1760. }
  1761. if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
  1762. redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
  1763. get_string('activityiscurrentlyhidden'));
  1764. }
  1765. user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
  1766. return; // User is allowed to see this course
  1767. break;
  1768. case 2: /// Guests allowed with key
  1769. if (!empty($USER->enrolkey[$COURSE->id])) { // Set by enrol/manual/enrol.php
  1770. user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
  1771. return true;
  1772. }
  1773. // otherwise drop through to logic below (--> enrol.php)
  1774. break;
  1775. default: /// Guests not allowed
  1776. $strloggedinasguest = get_string('loggedinasguest');
  1777. print_header_simple('', '',
  1778. build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
  1779. if (empty($USER->access['rsw'][$COURSE->context->path])) { // Normal guest
  1780. $loginurl = "$CFG->wwwroot/login/index.php";
  1781. if (!empty($CFG->loginhttps)) {
  1782. $loginurl = str_replace('http:','https:', $loginurl);
  1783. }
  1784. notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), $loginurl);
  1785. } else {
  1786. notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
  1787. echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
  1788. print_footer($COURSE);
  1789. exit;
  1790. }
  1791. break;
  1792. }
  1793. /// For non-guests, check if they have course view access
  1794. } else if (has_capability('moodle/course:view', $COURSE->context)) {
  1795. if (!empty($USER->realuser)) { // Make sure the REAL person can also access this course
  1796. if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
  1797. print_header_simple();
  1798. notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
  1799. }
  1800. }
  1801. /// Make sure they can read this activity too, if specified
  1802. if (!empty($cm) && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
  1803. redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
  1804. }
  1805. user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
  1806. return; // User is allowed to see this course
  1807. }
  1808. /// Currently not enrolled in the course, so see if they want to enrol
  1809. $SESSION->wantsurl = $FULLME;
  1810. redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
  1811. die;
  1812. }
  1813. }
  1814. /**
  1815. * This function just makes sure a user is logged out.
  1816. *
  1817. * @uses $CFG
  1818. * @uses $USER
  1819. */
  1820. function require_logout() {
  1821. global $USER, $CFG, $SESSION;
  1822. if (isloggedin()) {
  1823. add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
  1824. $authsequence = get_enabled_auth_plugins(); // auths, in sequence
  1825. foreach($authsequence as $authname) {
  1826. $authplugin = get_auth_plugin($authname);
  1827. $authplugin->prelogout_hook();
  1828. }
  1829. }
  1830. if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
  1831. // This method is just to try to avoid silly warnings from PHP 4.3.0
  1832. session_unregister("USER");
  1833. session_unregister("SESSION");
  1834. }
  1835. // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
  1836. $file = $line = null;
  1837. if (headers_sent($file, $line)) {
  1838. error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
  1839. error_log('Headers were already sent in file: '.$file.' on line '.$line);
  1840. } else {
  1841. if (check_php_version('5.2.0')) {
  1842. setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
  1843. } else {
  1844. setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
  1845. }
  1846. }
  1847. unset($_SESSION['USER']);
  1848. unset($_SESSION['SESSION']);
  1849. unset($SESSION);
  1850. unset($USER);
  1851. }
  1852. /**
  1853. * This is a weaker version of {@link require_login()} which only requires login
  1854. * when called from within a course rather than the site page, unless
  1855. * the forcelogin option is turned on.
  1856. *
  1857. * @uses $CFG
  1858. * @param mixed $courseorid The course object or id in question
  1859. * @param bool $autologinguest Allow autologin guests if that is wanted
  1860. * @param object $cm Course activity module if known
  1861. * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
  1862. * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
  1863. * in order to keep redirects working properly. MDL-14495
  1864. */
  1865. function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
  1866. global $CFG;
  1867. if (!empty($CFG->forcelogin)) {
  1868. // login required for both SITE and courses
  1869. require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
  1870. } else if (!empty($cm) and !$cm->visible) {
  1871. // always login for hidden activities
  1872. require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
  1873. } else if ((is_object($courseorid) and $courseorid->id == SITEID)
  1874. or (!is_object($courseorid) and $courseorid == SITEID)) {
  1875. //login for SITE not required
  1876. if ($cm and empty($cm->visible)) {
  1877. // hidden activities are not accessible without login
  1878. require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
  1879. } else if ($cm and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
  1880. // not-logged-in users do not have any group membership
  1881. require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
  1882. } else {
  1883. user_accesstime_log(SITEID);
  1884. return;
  1885. }
  1886. } else {
  1887. // course login always required
  1888. require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
  1889. }
  1890. }
  1891. /**
  1892. * Require key login. Function terminates with error if key not found or incorrect.
  1893. * @param string $script unique script identifier
  1894. * @param int $instance optional instance id
  1895. */
  1896. function require_user_key_login($script, $instance=null) {
  1897. global $nomoodlecookie, $USER, $SESSION, $CFG;
  1898. if (empty($nomoodlecookie)) {
  1899. error('Incorrect use of require_key_login() - session cookies must be disabled!');
  1900. }
  1901. /// extra safety
  1902. @session_write_close();
  1903. $keyvalue = required_param('key', PARAM_ALPHANUM);
  1904. if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
  1905. error('Incorrect key');
  1906. }
  1907. if (!empty($key->validuntil) and $key->validuntil < time()) {
  1908. error('Expired key');
  1909. }
  1910. if ($key->iprestriction) {
  1911. $remoteaddr = getremoteaddr();
  1912. if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
  1913. error('Client IP address mismatch');
  1914. }
  1915. }
  1916. if (!$user = get_record('user', 'id', $key->userid)) {
  1917. error('Incorrect user record');
  1918. }
  1919. /// emulate normal session
  1920. $SESSION = new object();
  1921. $USER = $user;
  1922. /// note we are not using normal login
  1923. if (!defined('USER_KEY_LOGIN')) {
  1924. define('USER_KEY_LOGIN', true);
  1925. }
  1926. load_all_capabilities();
  1927. /// return isntance id - it might be empty
  1928. return $key->instance;
  1929. }
  1930. /**
  1931. * Creates a new private user access key.
  1932. * @param string $script unique target identifier
  1933. * @param int $userid
  1934. * @param instance $int optional instance id
  1935. * @param string $iprestriction optional ip restricted access
  1936. * @param timestamp $validuntil key valid only until given data
  1937. * @return string access key value
  1938. */
  1939. function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
  1940. $key = new object();
  1941. $key->script = $script;
  1942. $key->userid = $userid;
  1943. $key->instance = $instance;
  1944. $key->iprestriction = $iprestriction;
  1945. $key->validuntil = $validuntil;
  1946. $key->timecreated = time();
  1947. $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
  1948. while (record_exists('user_private_key', 'value', $key->value)) {
  1949. // must be unique
  1950. $key->value = md5($userid.'_'.time().random_string(40));
  1951. }
  1952. if (!insert_record('user_private_key', $key)) {
  1953. error('Can not insert new key');
  1954. }
  1955. return $key->value;
  1956. }
  1957. /**
  1958. * Modify the user table by setting the currently logged in user's
  1959. * last login to now.
  1960. *
  1961. * @uses $USER
  1962. * @return bool
  1963. */
  1964. function update_user_login_times() {
  1965. global $USER;
  1966. $user = new object();
  1967. $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
  1968. $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
  1969. $user->id = $USER->id;
  1970. return update_record('user', $user);
  1971. }
  1972. /**
  1973. * Determines if a user has completed setting up their account.
  1974. *
  1975. * @param user $user A {@link $USER} object to test for the existance of a valid name and email
  1976. * @return bool
  1977. */
  1978. function user_not_fully_set_up($user) {
  1979. return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
  1980. }
  1981. function over_bounce_threshold($user) {
  1982. global $CFG;
  1983. if (empty($CFG->handlebounces)) {
  1984. return false;
  1985. }
  1986. if (empty($user->id)) { /// No real (DB) user, nothing to do here.
  1987. return false;
  1988. }
  1989. // set sensible defaults
  1990. if (empty($CFG->minbounces)) {
  1991. $CFG->minbounces = 10;
  1992. }
  1993. if (empty($CFG->bounceratio)) {
  1994. $CFG->bounceratio = .20;
  1995. }
  1996. $bouncecount = 0;
  1997. $sendcount = 0;
  1998. if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
  1999. $bouncecount = $bounce->value;
  2000. }
  2001. if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
  2002. $sendcount = $send->value;
  2003. }
  2004. return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
  2005. }
  2006. /**
  2007. * @param $user - object containing an id
  2008. * @param $reset - will reset the count to 0
  2009. */
  2010. function set_send_count($user,$reset=false) {
  2011. if (empty($user->id)) { /// No real (DB) user, nothing to do here.
  2012. return;
  2013. }
  2014. if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
  2015. $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
  2016. update_record('user_preferences',$pref);
  2017. }
  2018. else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
  2019. // make a new one
  2020. $pref->name = 'email_send_count';
  2021. $pref->value = 1;
  2022. $pref->userid = $user->id;
  2023. insert_record('user_preferences',$pref, false);
  2024. }
  2025. }
  2026. /**
  2027. * @param $user - object containing an id
  2028. * @param $reset - will reset the count to 0
  2029. */
  2030. function set_bounce_count($user,$reset=false) {
  2031. if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
  2032. $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
  2033. update_record('user_preferences',$pref);
  2034. }
  2035. else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
  2036. // make a new one
  2037. $pref->name = 'email_bounce_count';
  2038. $pref->value = 1;
  2039. $pref->userid = $user->id;
  2040. insert_record('user_preferences',$pref, false);
  2041. }
  2042. }
  2043. /**
  2044. * Keeps track of login attempts
  2045. *
  2046. * @uses $SESSION
  2047. */
  2048. function update_login_count() {
  2049. global $SESSION;
  2050. $max_logins = 10;
  2051. if (empty($SESSION->logincount)) {
  2052. $SESSION->logincount = 1;
  2053. } else {
  2054. $SESSION->logincount++;
  2055. }
  2056. if ($SESSION->logincount > $max_logins) {
  2057. unset($SESSION->wantsurl);
  2058. print_error('errortoomanylogins');
  2059. }
  2060. }
  2061. /**
  2062. * Resets login attempts
  2063. *
  2064. * @uses $SESSION
  2065. */
  2066. function reset_login_count() {
  2067. global $SESSION;
  2068. $SESSION->logincount = 0;
  2069. }
  2070. function sync_metacourses() {
  2071. global $CFG;
  2072. if (!$courses = get_records('course', 'metacourse', 1)) {
  2073. return;
  2074. }
  2075. foreach ($courses as $course) {
  2076. sync_metacourse($course);
  2077. }
  2078. }
  2079. /**
  2080. * Goes through all enrolment records for the courses inside the metacourse and sync with them.
  2081. *
  2082. * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
  2083. */
  2084. function sync_metacourse($course) {
  2085. global $CFG;
  2086. // Check the course is valid.
  2087. if (!is_object($course)) {
  2088. if (!$course = get_record('course', 'id', $course)) {
  2089. return false; // invalid course id
  2090. }
  2091. }
  2092. // Check that we actually have a metacourse.
  2093. if (empty($course->metacourse)) {
  2094. return false;
  2095. }
  2096. // Get a list of roles that should not be synced.
  2097. if (!empty($CFG->nonmetacoursesyncroleids)) {
  2098. $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
  2099. } else {
  2100. $roleexclusions = '';
  2101. }
  2102. // Get the context of the metacourse.
  2103. $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
  2104. // We do not ever want to unassign the list of metacourse manager, so get a list of them.
  2105. if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
  2106. $managers = array_keys($users);
  2107. } else {
  2108. $managers = array();
  2109. }
  2110. // Get assignments of a user to a role that exist in a child course, but
  2111. // not in the meta coure. That is, get a list of the assignments that need to be made.
  2112. if (!$assignments = get_records_sql("
  2113. SELECT
  2114. ra.id, ra.roleid, ra.userid, ra.hidden
  2115. FROM
  2116. {$CFG->prefix}role_assignments ra,
  2117. {$CFG->prefix}context con,
  2118. {$CFG->prefix}course_meta cm
  2119. WHERE
  2120. ra.contextid = con.id AND
  2121. con.contextlevel = " . CONTEXT_COURSE . " AND
  2122. con.instanceid = cm.child_course AND
  2123. cm.parent_course = {$course->id} AND
  2124. $roleexclusions
  2125. NOT EXISTS (
  2126. SELECT 1 FROM
  2127. {$CFG->prefix}role_assignments ra2
  2128. WHERE
  2129. ra2.userid = ra.userid AND
  2130. ra2.roleid = ra.roleid AND
  2131. ra2.contextid = {$context->id}
  2132. )
  2133. ")) {
  2134. $assignments = array();
  2135. }
  2136. // Get assignments of a user to a role that exist in the meta course, but
  2137. // not in any child courses. That is, get a list of the unassignments that need to be made.
  2138. if (!$unassignments = get_records_sql("
  2139. SELECT
  2140. ra.id, ra.roleid, ra.userid
  2141. FROM
  2142. {$CFG->prefix}role_assignments ra
  2143. WHERE
  2144. ra.contextid = {$context->id} AND
  2145. $roleexclusions
  2146. NOT EXISTS (
  2147. SELECT 1 FROM
  2148. {$CFG->prefix}role_assignments ra2,
  2149. {$CFG->prefix}context con2,
  2150. {$CFG->prefix}course_meta cm
  2151. WHERE
  2152. ra2.userid = ra.userid AND
  2153. ra2.roleid = ra.roleid AND
  2154. ra2.contextid = con2.id AND
  2155. con2.contextlevel = " . CONTEXT_COURSE . " AND
  2156. con2.instanceid = cm.child_course AND
  2157. cm.parent_course = {$course->id}
  2158. )
  2159. ")) {
  2160. $unassignments = array();
  2161. }
  2162. $success = true;
  2163. // Make the unassignments, if they are not managers.
  2164. foreach ($unassignments as $unassignment) {
  2165. if (!in_array($unassignment->userid, $managers)) {
  2166. $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
  2167. }
  2168. }
  2169. // Make the assignments.
  2170. foreach ($assignments as $assignment) {
  2171. $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id, 0, 0, $assignment->hidden) && $success;
  2172. }
  2173. return $success;
  2174. // TODO: finish timeend and timestart
  2175. // maybe we could rely on cron job to do the cleaning from time to time
  2176. }
  2177. /**
  2178. * Adds a record to the metacourse table and calls sync_metacoures
  2179. */
  2180. function add_to_metacourse ($metacourseid, $courseid) {
  2181. if (!$metacourse = get_record("course","id",$metacourseid)) {
  2182. return false;
  2183. }
  2184. if (!$course = get_record("course","id",$courseid)) {
  2185. return false;
  2186. }
  2187. if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
  2188. $rec = new object();
  2189. $rec->parent_course = $metacourseid;
  2190. $rec->child_course = $courseid;
  2191. if (!insert_record('course_meta',$rec)) {
  2192. return false;
  2193. }
  2194. return sync_metacourse($metacourseid);
  2195. }
  2196. return true;
  2197. }
  2198. /**
  2199. * Removes the record from the metacourse table and calls sync_metacourse
  2200. */
  2201. function remove_from_metacourse($metacourseid, $courseid) {
  2202. if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
  2203. return sync_metacourse($metacourseid);
  2204. }
  2205. return false;
  2206. }
  2207. /**
  2208. * Determines if a user is currently logged in
  2209. *
  2210. * @uses $USER
  2211. * @return bool
  2212. */
  2213. function isloggedin() {
  2214. global $USER;
  2215. return (!empty($USER->id));
  2216. }
  2217. /**
  2218. * Determines if a user is logged in as real guest user with username 'guest'.
  2219. * This function is similar to original isguest() in 1.6 and earlier.
  2220. * Current isguest() is deprecated - do not use it anymore.
  2221. *
  2222. * @param $user mixed user object or id, $USER if not specified
  2223. * @return bool true if user is the real guest user, false if not logged in or other user
  2224. */
  2225. function isguestuser($user=NULL) {
  2226. global $USER, $CFG;
  2227. if ($user === NULL) {
  2228. $user = $USER;
  2229. } else if (is_numeric($user)) {
  2230. $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
  2231. }
  2232. if (empty($user->id)) {
  2233. return false; // not logged in, can not be guest
  2234. }
  2235. return ($user->username == 'guest' and $user->mnethostid == $CFG->mnet_localhost_id);
  2236. }
  2237. /**
  2238. * Determines if the currently logged in user is in editing mode.
  2239. * Note: originally this function had $userid parameter - it was not usable anyway
  2240. *
  2241. * @uses $USER, $PAGE
  2242. * @return bool
  2243. */
  2244. function isediting() {
  2245. global $USER, $PAGE;
  2246. if (empty($USER->editing)) {
  2247. return false;
  2248. } elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
  2249. return $PAGE->user_allowed_editing();
  2250. }
  2251. return true;//false;
  2252. }
  2253. /**
  2254. * Determines if the logged in user is currently moving an activity
  2255. *
  2256. * @uses $USER
  2257. * @param int $courseid The id of the course being tested
  2258. * @return bool
  2259. */
  2260. function ismoving($courseid) {
  2261. global $USER;
  2262. if (!empty($USER->activitycopy)) {
  2263. return ($USER->activitycopycourse == $courseid);
  2264. }
  2265. return false;
  2266. }
  2267. /**
  2268. * Given an object containing firstname and lastname
  2269. * values, this function returns a string with the
  2270. * full name of the person.
  2271. * The result may depend on system settings
  2272. * or language. 'override' will force both names
  2273. * to be used even if system settings specify one.
  2274. *
  2275. * @uses $CFG
  2276. * @uses $SESSION
  2277. * @param object $user A {@link $USER} object to get full name of
  2278. * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
  2279. */
  2280. function fullname($user, $override=false) {
  2281. global $CFG, $SESSION;
  2282. if (!isset($user->firstname) and !isset($user->lastname)) {
  2283. return '';
  2284. }
  2285. if (!$override) {
  2286. if (!empty($CFG->forcefirstname)) {
  2287. $user->firstname = $CFG->forcefirstname;
  2288. }
  2289. if (!empty($CFG->forcelastname)) {
  2290. $user->lastname = $CFG->forcelastname;
  2291. }
  2292. }
  2293. if (!empty($SESSION->fullnamedisplay)) {
  2294. $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
  2295. }
  2296. if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
  2297. return $user->firstname .' '. $user->lastname;
  2298. } else if ($CFG->fullnamedisplay == 'lastname firstname') {
  2299. return $user->lastname .' '. $user->firstname;
  2300. } else if ($CFG->fullnamedisplay == 'firstname') {
  2301. if ($override) {
  2302. return get_string('fullnamedisplay', '', $user);
  2303. } else {
  2304. return $user->firstname;
  2305. }
  2306. }
  2307. return get_string('fullnamedisplay', '', $user);
  2308. }
  2309. /**
  2310. * Sets a moodle cookie with an encrypted string
  2311. *
  2312. * @uses $CFG
  2313. * @uses DAYSECS
  2314. * @uses HOURSECS
  2315. * @param string $thing The string to encrypt and place in a cookie
  2316. */
  2317. function set_moodle_cookie($thing) {
  2318. global $CFG;
  2319. if ($thing == 'guest') { // Ignore guest account
  2320. return;
  2321. }
  2322. $cookiename = 'MOODLEID1_'.$CFG->sessioncookie;
  2323. $days = 60;
  2324. $seconds = DAYSECS*$days;
  2325. setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
  2326. setCookie($cookiename, rc4encrypt($thing, true), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
  2327. }
  2328. /**
  2329. * Gets a moodle cookie with an encrypted string
  2330. *
  2331. * @uses $CFG
  2332. * @return string
  2333. */
  2334. function get_moodle_cookie() {
  2335. global $CFG;
  2336. $cookiename = 'MOODLEID1_'.$CFG->sessioncookie;
  2337. if (empty($_COOKIE[$cookiename])) {
  2338. return '';
  2339. } else {
  2340. $thing = rc4decrypt($_COOKIE[$cookiename], true);
  2341. return ($thing == 'guest') ? '': $thing; // Ignore guest account
  2342. }
  2343. }
  2344. /**
  2345. * Returns whether a given authentication plugin exists.
  2346. *
  2347. * @uses $CFG
  2348. * @param string $auth Form of authentication to check for. Defaults to the
  2349. * global setting in {@link $CFG}.
  2350. * @return boolean Whether the plugin is available.
  2351. */
  2352. function exists_auth_plugin($auth) {
  2353. global $CFG;
  2354. if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
  2355. return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
  2356. }
  2357. return false;
  2358. }
  2359. /**
  2360. * Checks if a given plugin is in the list of enabled authentication plugins.
  2361. *
  2362. * @param string $auth Authentication plugin.
  2363. * @return boolean Whether the plugin is enabled.
  2364. */
  2365. function is_enabled_auth($auth) {
  2366. if (empty($auth)) {
  2367. return false;
  2368. }
  2369. $enabled = get_enabled_auth_plugins();
  2370. return in_array($auth, $enabled);
  2371. }
  2372. /**
  2373. * Returns an authentication plugin instance.
  2374. *
  2375. * @uses $CFG
  2376. * @param string $auth name of authentication plugin
  2377. * @return object An instance of the required authentication plugin.
  2378. */
  2379. function get_auth_plugin($auth) {
  2380. global $CFG;
  2381. // check the plugin exists first
  2382. if (! exists_auth_plugin($auth)) {
  2383. error("Authentication plugin '$auth' not found.");
  2384. }
  2385. // return auth plugin instance
  2386. require_once "{$CFG->dirroot}/auth/$auth/auth.php";
  2387. $class = "auth_plugin_$auth";
  2388. return new $class;
  2389. }
  2390. /**
  2391. * Returns array of active auth plugins.
  2392. *
  2393. * @param bool $fix fix $CFG->auth if needed
  2394. * @return array
  2395. */
  2396. function get_enabled_auth_plugins($fix=false) {
  2397. global $CFG;
  2398. $default = array('manual', 'nologin');
  2399. if (empty($CFG->auth)) {
  2400. $auths = array();
  2401. } else {
  2402. $auths = explode(',', $CFG->auth);
  2403. }
  2404. if ($fix) {
  2405. $auths = array_unique($auths);
  2406. foreach($auths as $k=>$authname) {
  2407. if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
  2408. unset($auths[$k]);
  2409. }
  2410. }
  2411. $newconfig = implode(',', $auths);
  2412. if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
  2413. set_config('auth', $newconfig);
  2414. }
  2415. }
  2416. return (array_merge($default, $auths));
  2417. }
  2418. /**
  2419. * Returns true if an internal authentication method is being used.
  2420. * if method not specified then, global default is assumed
  2421. *
  2422. * @uses $CFG
  2423. * @param string $auth Form of authentication required
  2424. * @return bool
  2425. */
  2426. function is_internal_auth($auth) {
  2427. $authplugin = get_auth_plugin($auth); // throws error if bad $auth
  2428. return $authplugin->is_internal();
  2429. }
  2430. /**
  2431. * Returns true if the user is a 'restored' one
  2432. *
  2433. * Used in the login process to inform the user
  2434. * and allow him/her to reset the password
  2435. *
  2436. * @uses $CFG
  2437. * @param string $username username to be checked
  2438. * @return bool
  2439. */
  2440. function is_restored_user($username) {
  2441. global $CFG;
  2442. return record_exists('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, 'password', 'restored');
  2443. }
  2444. /**
  2445. * Returns an array of user fields
  2446. *
  2447. * @uses $CFG
  2448. * @uses $db
  2449. * @return array User field/column names
  2450. */
  2451. function get_user_fieldnames() {
  2452. global $CFG, $db;
  2453. $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
  2454. unset($fieldarray['ID']);
  2455. return $fieldarray;
  2456. }
  2457. /**
  2458. * Creates the default "guest" user. Used both from
  2459. * admin/index.php and login/index.php
  2460. * @return mixed user object created or boolean false if the creation has failed
  2461. */
  2462. function create_guest_record() {
  2463. global $CFG;
  2464. $guest = new stdClass();
  2465. $guest->auth = 'manual';
  2466. $guest->username = 'guest';
  2467. $guest->password = hash_internal_user_password('guest');
  2468. $guest->firstname = addslashes(get_string('guestuser'));
  2469. $guest->lastname = ' ';
  2470. $guest->email = 'root@localhost';
  2471. $guest->description = addslashes(get_string('guestuserinfo'));
  2472. $guest->mnethostid = $CFG->mnet_localhost_id;
  2473. $guest->confirmed = 1;
  2474. $guest->lang = $CFG->lang;
  2475. $guest->timemodified= time();
  2476. if (! $guest->id = insert_record("user", $guest)) {
  2477. return false;
  2478. }
  2479. return $guest;
  2480. }
  2481. /**
  2482. * Creates a bare-bones user record
  2483. *
  2484. * @uses $CFG
  2485. * @param string $username New user's username to add to record
  2486. * @param string $password New user's password to add to record
  2487. * @param string $auth Form of authentication required
  2488. * @return object A {@link $USER} object
  2489. * @todo Outline auth types and provide code example
  2490. */
  2491. function create_user_record($username, $password, $auth='manual') {
  2492. global $CFG;
  2493. //just in case check text case
  2494. $username = trim(moodle_strtolower($username));
  2495. $authplugin = get_auth_plugin($auth);
  2496. if ($newinfo = $authplugin->get_userinfo($username)) {
  2497. $newinfo = truncate_userinfo($newinfo);
  2498. foreach ($newinfo as $key => $value){
  2499. $newuser->$key = addslashes($value);
  2500. }
  2501. }
  2502. if (!empty($newuser->email)) {
  2503. if (email_is_not_allowed($newuser->email)) {
  2504. unset($newuser->email);
  2505. }
  2506. }
  2507. if (!isset($newuser->city)) {
  2508. $newuser->city = '';
  2509. }
  2510. $newuser->auth = $auth;
  2511. $newuser->username = $username;
  2512. // fix for MDL-8480
  2513. // user CFG lang for user if $newuser->lang is empty
  2514. // or $user->lang is not an installed language
  2515. $sitelangs = array_keys(get_list_of_languages());
  2516. if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
  2517. $newuser -> lang = $CFG->lang;
  2518. }
  2519. $newuser->confirmed = 1;
  2520. $newuser->lastip = getremoteaddr();
  2521. if (empty($newuser->lastip)) {
  2522. $newuser->lastip = '0.0.0.0';
  2523. }
  2524. $newuser->timemodified = time();
  2525. $newuser->mnethostid = $CFG->mnet_localhost_id;
  2526. if (insert_record('user', $newuser)) {
  2527. $user = get_complete_user_data('username', $newuser->username, $CFG->mnet_localhost_id);
  2528. if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
  2529. set_user_preference('auth_forcepasswordchange', 1, $user->id);
  2530. }
  2531. update_internal_user_password($user, $password);
  2532. return $user;
  2533. }
  2534. return false;
  2535. }
  2536. /**
  2537. * Will update a local user record from an external source
  2538. *
  2539. * @uses $CFG
  2540. * @param string $username New user's username to add to record
  2541. * @return user A {@link $USER} object
  2542. */
  2543. function update_user_record($username, $unused) {
  2544. global $CFG;
  2545. $username = trim(moodle_strtolower($username)); /// just in case check text case
  2546. $oldinfo = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, '','', 'id, username, auth');
  2547. $userauth = get_auth_plugin($oldinfo->auth);
  2548. if ($newinfo = $userauth->get_userinfo($username)) {
  2549. $newinfo = truncate_userinfo($newinfo);
  2550. foreach ($newinfo as $key => $value){
  2551. if ($key === 'username' or $key === 'id' or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
  2552. // these fields must not be changed
  2553. continue;
  2554. }
  2555. $confval = $userauth->config->{'field_updatelocal_' . $key};
  2556. $lockval = $userauth->config->{'field_lock_' . $key};
  2557. if (empty($confval) || empty($lockval)) {
  2558. continue;
  2559. }
  2560. if ($confval === 'onlogin') {
  2561. $value = addslashes($value);
  2562. // MDL-4207 Don't overwrite modified user profile values with
  2563. // empty LDAP values when 'unlocked if empty' is set. The purpose
  2564. // of the setting 'unlocked if empty' is to allow the user to fill
  2565. // in a value for the selected field _if LDAP is giving
  2566. // nothing_ for this field. Thus it makes sense to let this value
  2567. // stand in until LDAP is giving a value for this field.
  2568. if (!(empty($value) && $lockval === 'unlockedifempty')) {
  2569. set_field('user', $key, $value, 'id', $oldinfo->id)
  2570. || error_log("Error updating $key for $username");
  2571. }
  2572. }
  2573. }
  2574. }
  2575. return get_complete_user_data('username', $username, $CFG->mnet_localhost_id);
  2576. }
  2577. function truncate_userinfo($info) {
  2578. /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
  2579. /// which may have large fields
  2580. // define the limits
  2581. $limit = array(
  2582. 'username' => 100,
  2583. 'idnumber' => 255,
  2584. 'firstname' => 100,
  2585. 'lastname' => 100,
  2586. 'email' => 100,
  2587. 'icq' => 15,
  2588. 'phone1' => 20,
  2589. 'phone2' => 20,
  2590. 'institution' => 40,
  2591. 'department' => 30,
  2592. 'address' => 70,
  2593. 'city' => 20,
  2594. 'country' => 2,
  2595. 'url' => 255,
  2596. );
  2597. // apply where needed
  2598. $textlib = textlib_get_instance();
  2599. foreach (array_keys($info) as $key) {
  2600. if (!empty($limit[$key])) {
  2601. $info[$key] = trim($textlib->substr($info[$key],0, $limit[$key]));
  2602. }
  2603. }
  2604. return $info;
  2605. }
  2606. /**
  2607. * Marks user deleted in internal user database and notifies the auth plugin.
  2608. * Also unenrols user from all roles and does other cleanup.
  2609. * @param object $user Userobject before delete (without system magic quotes)
  2610. * @return boolean success
  2611. */
  2612. function delete_user($user) {
  2613. global $CFG;
  2614. require_once($CFG->libdir.'/grouplib.php');
  2615. require_once($CFG->libdir.'/gradelib.php');
  2616. require_once($CFG->dirroot.'/message/lib.php');
  2617. begin_sql();
  2618. // delete all grades - backup is kept in grade_grades_history table
  2619. if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
  2620. foreach ($grades as $grade) {
  2621. $grade->delete('userdelete');
  2622. }
  2623. }
  2624. //move unread messages from this user to read
  2625. message_move_userfrom_unread2read($user->id);
  2626. // remove from all groups
  2627. delete_records('groups_members', 'userid', $user->id);
  2628. // unenrol from all roles in all contexts
  2629. role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
  2630. // now do a final accesslib cleanup - removes all role assingments in user context and context itself
  2631. delete_context(CONTEXT_USER, $user->id);
  2632. require_once($CFG->dirroot.'/tag/lib.php');
  2633. tag_set('user', $user->id, array());
  2634. // workaround for bulk deletes of users with the same email address
  2635. $delname = addslashes("$user->email.".time());
  2636. while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
  2637. $delname++;
  2638. }
  2639. // mark internal user record as "deleted"
  2640. $updateuser = new object();
  2641. $updateuser->id = $user->id;
  2642. $updateuser->deleted = 1;
  2643. $updateuser->username = $delname; // Remember it just in case
  2644. $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
  2645. $updateuser->idnumber = ''; // Clear this field to free it up
  2646. $updateuser->timemodified = time();
  2647. if (update_record('user', $updateuser)) {
  2648. commit_sql();
  2649. // notify auth plugin - do not block the delete even when plugin fails
  2650. $authplugin = get_auth_plugin($user->auth);
  2651. $authplugin->user_delete($user);
  2652. events_trigger('user_deleted', $user);
  2653. return true;
  2654. } else {
  2655. rollback_sql();
  2656. return false;
  2657. }
  2658. }
  2659. /**
  2660. * Retrieve the guest user object
  2661. *
  2662. * @uses $CFG
  2663. * @return user A {@link $USER} object
  2664. */
  2665. function guest_user() {
  2666. global $CFG;
  2667. if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id)) {
  2668. $newuser->confirmed = 1;
  2669. $newuser->lang = $CFG->lang;
  2670. $newuser->lastip = getremoteaddr();
  2671. if (empty($newuser->lastip)) {
  2672. $newuser->lastip = '0.0.0.0';
  2673. }
  2674. }
  2675. return $newuser;
  2676. }
  2677. /**
  2678. * Given a username and password, this function looks them
  2679. * up using the currently selected authentication mechanism,
  2680. * and if the authentication is successful, it returns a
  2681. * valid $user object from the 'user' table.
  2682. *
  2683. * Uses auth_ functions from the currently active auth module
  2684. *
  2685. * After authenticate_user_login() returns success, you will need to
  2686. * log that the user has logged in, and call complete_user_login() to set
  2687. * the session up.
  2688. *
  2689. * @uses $CFG
  2690. * @param string $username User's username (with system magic quotes)
  2691. * @param string $password User's password (with system magic quotes)
  2692. * @return user|flase A {@link $USER} object or false if error
  2693. */
  2694. function authenticate_user_login($username, $password) {
  2695. global $CFG;
  2696. $authsenabled = get_enabled_auth_plugins();
  2697. if ($user = get_complete_user_data('username', $username)) {
  2698. $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
  2699. if ($auth=='nologin' or !is_enabled_auth($auth)) {
  2700. add_to_log(0, 'login', 'error', 'index.php', $username);
  2701. error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  2702. return false;
  2703. }
  2704. $auths = array($auth);
  2705. } else {
  2706. // check if there's a deleted record (cheaply)
  2707. if (get_field('user', 'id', 'username', $username, 'deleted', 1, '')) {
  2708. error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  2709. return false;
  2710. }
  2711. $auths = $authsenabled;
  2712. $user = new object();
  2713. $user->id = 0; // User does not exist
  2714. }
  2715. foreach ($auths as $auth) {
  2716. $authplugin = get_auth_plugin($auth);
  2717. // on auth fail fall through to the next plugin
  2718. if (!$authplugin->user_login($username, $password)) {
  2719. continue;
  2720. }
  2721. // successful authentication
  2722. if ($user->id) { // User already exists in database
  2723. if (empty($user->auth)) { // For some reason auth isn't set yet
  2724. set_field('user', 'auth', $auth, 'username', $username);
  2725. $user->auth = $auth;
  2726. }
  2727. if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
  2728. set_field('user','firstaccess', $user->timemodified, 'id', $user->id);
  2729. $user->firstaccess = $user->timemodified;
  2730. }
  2731. update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
  2732. if (!$authplugin->is_internal()) { // update user record from external DB
  2733. $user = update_user_record($username, get_auth_plugin($user->auth));
  2734. }
  2735. } else {
  2736. // if user not found, create him
  2737. $user = create_user_record($username, $password, $auth);
  2738. }
  2739. $authplugin->sync_roles($user);
  2740. foreach ($authsenabled as $hau) {
  2741. $hauth = get_auth_plugin($hau);
  2742. $hauth->user_authenticated_hook($user, $username, $password);
  2743. }
  2744. /// Log in to a second system if necessary
  2745. /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
  2746. if (!empty($CFG->sso)) {
  2747. include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
  2748. if (function_exists('sso_user_login')) {
  2749. if (!sso_user_login($username, $password)) { // Perform the signon process
  2750. notify('Second sign-on failed');
  2751. }
  2752. }
  2753. }
  2754. if ($user->id===0) {
  2755. return false;
  2756. }
  2757. return $user;
  2758. }
  2759. // failed if all the plugins have failed
  2760. add_to_log(0, 'login', 'error', 'index.php', $username);
  2761. if (debugging('', DEBUG_ALL)) {
  2762. error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  2763. }
  2764. return false;
  2765. }
  2766. /**
  2767. * Call to complete the user login process after authenticate_user_login()
  2768. * has succeeded. It will setup the $USER variable and other required bits
  2769. * and pieces.
  2770. *
  2771. * NOTE:
  2772. * - It will NOT log anything -- up to the caller to decide what to log.
  2773. *
  2774. *
  2775. *
  2776. * @uses $CFG, $USER
  2777. * @param string $user obj
  2778. * @return user|flase A {@link $USER} object or false if error
  2779. */
  2780. function complete_user_login($user) {
  2781. global $CFG, $USER;
  2782. $USER = $user; // this is required because we need to access preferences here!
  2783. if (!empty($CFG->regenloginsession)) {
  2784. // please note this setting may break some auth plugins
  2785. session_regenerate_id();
  2786. }
  2787. reload_user_preferences();
  2788. update_user_login_times();
  2789. if (empty($CFG->nolastloggedin)) {
  2790. set_moodle_cookie($USER->username);
  2791. } else {
  2792. // do not store last logged in user in cookie
  2793. // auth plugins can temporarily override this from loginpage_hook()
  2794. // do not save $CFG->nolastloggedin in database!
  2795. set_moodle_cookie('nobody');
  2796. }
  2797. set_login_session_preferences();
  2798. // Call enrolment plugins
  2799. check_enrolment_plugins($user);
  2800. /// This is what lets the user do anything on the site :-)
  2801. load_all_capabilities();
  2802. /// Select password change url
  2803. $userauth = get_auth_plugin($USER->auth);
  2804. /// check whether the user should be changing password
  2805. if (get_user_preferences('auth_forcepasswordchange', false)){
  2806. if ($userauth->can_change_password()) {
  2807. if ($changeurl = $userauth->change_password_url()) {
  2808. redirect($changeurl);
  2809. } else {
  2810. redirect($CFG->httpswwwroot.'/login/change_password.php');
  2811. }
  2812. } else {
  2813. print_error('nopasswordchangeforced', 'auth');
  2814. }
  2815. }
  2816. return $USER;
  2817. }
  2818. /**
  2819. * Compare password against hash stored in internal user table.
  2820. * If necessary it also updates the stored hash to new format.
  2821. *
  2822. * @param object user
  2823. * @param string plain text password
  2824. * @return bool is password valid?
  2825. */
  2826. function validate_internal_user_password(&$user, $password) {
  2827. global $CFG;
  2828. if (!isset($CFG->passwordsaltmain)) {
  2829. $CFG->passwordsaltmain = '';
  2830. }
  2831. $validated = false;
  2832. // get password original encoding in case it was not updated to unicode yet
  2833. $textlib = textlib_get_instance();
  2834. $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
  2835. if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
  2836. or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
  2837. $validated = true;
  2838. } else {
  2839. for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
  2840. $alt = 'passwordsaltalt'.$i;
  2841. if (!empty($CFG->$alt)) {
  2842. if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
  2843. $validated = true;
  2844. break;
  2845. }
  2846. }
  2847. }
  2848. }
  2849. if ($validated) {
  2850. // force update of password hash using latest main password salt and encoding if needed
  2851. update_internal_user_password($user, $password);
  2852. }
  2853. return $validated;
  2854. }
  2855. /**
  2856. * Calculate hashed value from password using current hash mechanism.
  2857. *
  2858. * @param string password
  2859. * @return string password hash
  2860. */
  2861. function hash_internal_user_password($password) {
  2862. global $CFG;
  2863. if (isset($CFG->passwordsaltmain)) {
  2864. return md5($password.$CFG->passwordsaltmain);
  2865. } else {
  2866. return md5($password);
  2867. }
  2868. }
  2869. /**
  2870. * Update pssword hash in user object.
  2871. *
  2872. * @param object user
  2873. * @param string plain text password
  2874. * @param bool store changes also in db, default true
  2875. * @return true if hash changed
  2876. */
  2877. function update_internal_user_password(&$user, $password) {
  2878. global $CFG;
  2879. $authplugin = get_auth_plugin($user->auth);
  2880. if ($authplugin->prevent_local_passwords()) {
  2881. $hashedpassword = 'not cached';
  2882. } else {
  2883. $hashedpassword = hash_internal_user_password($password);
  2884. }
  2885. return set_field('user', 'password', $hashedpassword, 'id', $user->id);
  2886. }
  2887. /**
  2888. * Get a complete user record, which includes all the info
  2889. * in the user record
  2890. * Intended for setting as $USER session variable
  2891. *
  2892. * @uses $CFG
  2893. * @uses SITEID
  2894. * @param string $field The user field to be checked for a given value.
  2895. * @param string $value The value to match for $field.
  2896. * @return user A {@link $USER} object.
  2897. */
  2898. function get_complete_user_data($field, $value, $mnethostid=null) {
  2899. global $CFG;
  2900. if (!$field || !$value) {
  2901. return false;
  2902. }
  2903. /// Build the WHERE clause for an SQL query
  2904. $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
  2905. // If we are loading user data based on anything other than id,
  2906. // we must also restrict our search based on mnet host.
  2907. if ($field != 'id') {
  2908. if (empty($mnethostid)) {
  2909. // if empty, we restrict to local users
  2910. $mnethostid = $CFG->mnet_localhost_id;
  2911. }
  2912. }
  2913. if (!empty($mnethostid)) {
  2914. $mnethostid = (int)$mnethostid;
  2915. $constraints .= ' AND mnethostid = ' . $mnethostid;
  2916. }
  2917. /// Get all the basic user data
  2918. if (! $user = get_record_select('user', $constraints)) {
  2919. return false;
  2920. }
  2921. /// Get various settings and preferences
  2922. if ($displays = get_records('course_display', 'userid', $user->id)) {
  2923. foreach ($displays as $display) {
  2924. $user->display[$display->course] = $display->display;
  2925. }
  2926. }
  2927. $user->preference = get_user_preferences(null, null, $user->id);
  2928. $user->lastcourseaccess = array(); // during last session
  2929. $user->currentcourseaccess = array(); // during current session
  2930. if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
  2931. foreach ($lastaccesses as $lastaccess) {
  2932. $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
  2933. }
  2934. }
  2935. $sql = "SELECT g.id, g.courseid
  2936. FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
  2937. WHERE gm.groupid=g.id AND gm.userid={$user->id}";
  2938. // this is a special hack to speedup calendar display
  2939. $user->groupmember = array();
  2940. if ($groups = get_records_sql($sql)) {
  2941. foreach ($groups as $group) {
  2942. if (!array_key_exists($group->courseid, $user->groupmember)) {
  2943. $user->groupmember[$group->courseid] = array();
  2944. }
  2945. $user->groupmember[$group->courseid][$group->id] = $group->id;
  2946. }
  2947. }
  2948. /// Add the custom profile fields to the user record
  2949. include_once($CFG->dirroot.'/user/profile/lib.php');
  2950. $customfields = (array)profile_user_record($user->id);
  2951. foreach ($customfields as $cname=>$cvalue) {
  2952. if (!isset($user->$cname)) { // Don't overwrite any standard fields
  2953. $user->$cname = $cvalue;
  2954. }
  2955. }
  2956. /// Rewrite some variables if necessary
  2957. if (!empty($user->description)) {
  2958. $user->description = true; // No need to cart all of it around
  2959. }
  2960. if ($user->username == 'guest') {
  2961. $user->lang = $CFG->lang; // Guest language always same as site
  2962. $user->firstname = get_string('guestuser'); // Name always in current language
  2963. $user->lastname = ' ';
  2964. }
  2965. if (isset($_SERVER['REMOTE_ADDR'])) {
  2966. $user->sesskey = random_string(10);
  2967. $user->sessionIP = md5(getremoteaddr()); // Store the current IP in the session
  2968. }
  2969. return $user;
  2970. }
  2971. /**
  2972. * @uses $CFG
  2973. * @param string $password the password to be checked agains the password policy
  2974. * @param string $errmsg the error message to display when the password doesn't comply with the policy.
  2975. * @return bool true if the password is valid according to the policy. false otherwise.
  2976. */
  2977. function check_password_policy($password, &$errmsg) {
  2978. global $CFG;
  2979. if (empty($CFG->passwordpolicy)) {
  2980. return true;
  2981. }
  2982. $textlib = textlib_get_instance();
  2983. $errmsg = '';
  2984. if ($textlib->strlen($password) < $CFG->minpasswordlength) {
  2985. $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
  2986. }
  2987. if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
  2988. $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
  2989. }
  2990. if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
  2991. $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
  2992. }
  2993. if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
  2994. $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
  2995. }
  2996. if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
  2997. $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
  2998. }
  2999. if ($errmsg == '') {
  3000. return true;
  3001. } else {
  3002. return false;
  3003. }
  3004. }
  3005. /**
  3006. * When logging in, this function is run to set certain preferences
  3007. * for the current SESSION
  3008. */
  3009. function set_login_session_preferences() {
  3010. global $SESSION, $CFG;
  3011. $SESSION->justloggedin = true;
  3012. unset($SESSION->lang);
  3013. // Restore the calendar filters, if saved
  3014. if (intval(get_user_preferences('calendar_persistflt', 0))) {
  3015. include_once($CFG->dirroot.'/calendar/lib.php');
  3016. calendar_set_filters_status(get_user_preferences('calendar_savedflt', 0xff));
  3017. }
  3018. }
  3019. /**
  3020. * Delete a course, including all related data from the database,
  3021. * and any associated files from the moodledata folder.
  3022. *
  3023. * @param mixed $courseorid The id of the course or course object to delete.
  3024. * @param bool $showfeedback Whether to display notifications of each action the function performs.
  3025. * @return bool true if all the removals succeeded. false if there were any failures. If this
  3026. * method returns false, some of the removals will probably have succeeded, and others
  3027. * failed, but you have no way of knowing which.
  3028. */
  3029. function delete_course($courseorid, $showfeedback = true) {
  3030. global $CFG;
  3031. $result = true;
  3032. if (is_object($courseorid)) {
  3033. $courseid = $courseorid->id;
  3034. $course = $courseorid;
  3035. } else {
  3036. $courseid = $courseorid;
  3037. if (!$course = get_record('course', 'id', $courseid)) {
  3038. return false;
  3039. }
  3040. }
  3041. // frontpage course can not be deleted!!
  3042. if ($courseid == SITEID) {
  3043. return false;
  3044. }
  3045. if (!remove_course_contents($courseid, $showfeedback)) {
  3046. if ($showfeedback) {
  3047. notify("An error occurred while deleting some of the course contents.");
  3048. }
  3049. $result = false;
  3050. }
  3051. if (!delete_records("course", "id", $courseid)) {
  3052. if ($showfeedback) {
  3053. notify("An error occurred while deleting the main course record.");
  3054. }
  3055. $result = false;
  3056. }
  3057. /// Delete all roles and overiddes in the course context
  3058. if (!delete_context(CONTEXT_COURSE, $courseid)) {
  3059. if ($showfeedback) {
  3060. notify("An error occurred while deleting the main course context.");
  3061. }
  3062. $result = false;
  3063. }
  3064. if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
  3065. if ($showfeedback) {
  3066. notify("An error occurred while deleting the course files.");
  3067. }
  3068. $result = false;
  3069. }
  3070. if ($result) {
  3071. //trigger events
  3072. events_trigger('course_deleted', $course);
  3073. }
  3074. return $result;
  3075. }
  3076. /**
  3077. * Clear a course out completely, deleting all content
  3078. * but don't delete the course itself
  3079. *
  3080. * @uses $CFG
  3081. * @param int $courseid The id of the course that is being deleted
  3082. * @param bool $showfeedback Whether to display notifications of each action the function performs.
  3083. * @return bool true if all the removals succeeded. false if there were any failures. If this
  3084. * method returns false, some of the removals will probably have succeeded, and others
  3085. * failed, but you have no way of knowing which.
  3086. */
  3087. function remove_course_contents($courseid, $showfeedback=true) {
  3088. global $CFG;
  3089. require_once($CFG->libdir.'/questionlib.php');
  3090. require_once($CFG->libdir.'/gradelib.php');
  3091. $result = true;
  3092. if (! $course = get_record('course', 'id', $courseid)) {
  3093. error('Course ID was incorrect (can\'t find it)');
  3094. }
  3095. $strdeleted = get_string('deleted');
  3096. /// Clean up course formats (iterate through all formats in the even the course format was ever changed)
  3097. $formats = get_list_of_plugins('course/format');
  3098. foreach ($formats as $format) {
  3099. $formatdelete = $format.'_course_format_delete_course';
  3100. $formatlib = "$CFG->dirroot/course/format/$format/lib.php";
  3101. if (file_exists($formatlib)) {
  3102. include_once($formatlib);
  3103. if (function_exists($formatdelete)) {
  3104. if ($showfeedback) {
  3105. notify($strdeleted.' '.$format);
  3106. }
  3107. $formatdelete($course->id);
  3108. }
  3109. }
  3110. }
  3111. /// Delete every instance of every module
  3112. if ($allmods = get_records('modules') ) {
  3113. foreach ($allmods as $mod) {
  3114. $modname = $mod->name;
  3115. $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
  3116. $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
  3117. $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
  3118. $count=0;
  3119. if (file_exists($modfile)) {
  3120. include_once($modfile);
  3121. if (function_exists($moddelete)) {
  3122. if ($instances = get_records($modname, 'course', $course->id)) {
  3123. foreach ($instances as $instance) {
  3124. if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
  3125. /// Delete activity context questions and question categories
  3126. question_delete_activity($cm, $showfeedback);
  3127. }
  3128. if ($moddelete($instance->id)) {
  3129. $count++;
  3130. } else {
  3131. notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
  3132. $result = false;
  3133. }
  3134. if ($cm) {
  3135. // delete cm and its context in correct order
  3136. delete_records('course_modules', 'id', $cm->id);
  3137. delete_context(CONTEXT_MODULE, $cm->id);
  3138. }
  3139. }
  3140. }
  3141. } else {
  3142. notify('Function '.$moddelete.'() doesn\'t exist!');
  3143. $result = false;
  3144. }
  3145. if (function_exists($moddeletecourse)) {
  3146. $moddeletecourse($course, $showfeedback);
  3147. }
  3148. }
  3149. if ($showfeedback) {
  3150. notify($strdeleted .' '. $count .' x '. $modname);
  3151. }
  3152. }
  3153. } else {
  3154. error('No modules are installed!');
  3155. }
  3156. /// Give local code a chance to delete its references to this course.
  3157. require_once($CFG->libdir.'/locallib.php');
  3158. notify_local_delete_course($courseid, $showfeedback);
  3159. /// Delete course blocks
  3160. if ($blocks = get_records_sql("SELECT *
  3161. FROM {$CFG->prefix}block_instance
  3162. WHERE pagetype = '".PAGE_COURSE_VIEW."'
  3163. AND pageid = $course->id")) {
  3164. if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
  3165. if ($showfeedback) {
  3166. notify($strdeleted .' block_instance');
  3167. }
  3168. require_once($CFG->libdir.'/blocklib.php');
  3169. foreach ($blocks as $block) { /// Delete any associated contexts for this block
  3170. delete_context(CONTEXT_BLOCK, $block->id);
  3171. // fix for MDL-7164
  3172. // Get the block object and call instance_delete()
  3173. if (!$record = blocks_get_record($block->blockid)) {
  3174. $result = false;
  3175. continue;
  3176. }
  3177. if (!$obj = block_instance($record->name, $block)) {
  3178. $result = false;
  3179. continue;
  3180. }
  3181. // Return value ignored, in core mods this does not do anything, but just in case
  3182. // third party blocks might have stuff to clean up
  3183. // we execute this anyway
  3184. $obj->instance_delete();
  3185. }
  3186. } else {
  3187. $result = false;
  3188. }
  3189. }
  3190. /// Delete any groups, removing members and grouping/course links first.
  3191. require_once($CFG->dirroot.'/group/lib.php');
  3192. groups_delete_groupings($courseid, $showfeedback);
  3193. groups_delete_groups($courseid, $showfeedback);
  3194. /// Delete all related records in other tables that may have a courseid
  3195. /// This array stores the tables that need to be cleared, as
  3196. /// table_name => column_name that contains the course id.
  3197. $tablestoclear = array(
  3198. 'event' => 'courseid', // Delete events
  3199. 'log' => 'course', // Delete logs
  3200. 'course_sections' => 'course', // Delete any course stuff
  3201. 'course_modules' => 'course',
  3202. 'backup_courses' => 'courseid', // Delete scheduled backup stuff
  3203. 'user_lastaccess' => 'courseid',
  3204. 'backup_log' => 'courseid'
  3205. );
  3206. foreach ($tablestoclear as $table => $col) {
  3207. if (delete_records($table, $col, $course->id)) {
  3208. if ($showfeedback) {
  3209. notify($strdeleted . ' ' . $table);
  3210. }
  3211. } else {
  3212. $result = false;
  3213. }
  3214. }
  3215. /// Clean up metacourse stuff
  3216. if ($course->metacourse) {
  3217. delete_records("course_meta","parent_course",$course->id);
  3218. sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
  3219. if ($showfeedback) {
  3220. notify("$strdeleted course_meta");
  3221. }
  3222. } else {
  3223. if ($parents = get_records("course_meta","child_course",$course->id)) {
  3224. foreach ($parents as $parent) {
  3225. remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
  3226. }
  3227. if ($showfeedback) {
  3228. notify("$strdeleted course_meta");
  3229. }
  3230. }
  3231. }
  3232. /// Delete questions and question categories
  3233. question_delete_course($course, $showfeedback);
  3234. /// Remove all data from gradebook
  3235. $context = get_context_instance(CONTEXT_COURSE, $courseid);
  3236. remove_course_grades($courseid, $showfeedback);
  3237. remove_grade_letters($context, $showfeedback);
  3238. return $result;
  3239. }
  3240. /**
  3241. * Change dates in module - used from course reset.
  3242. * @param strin $modname forum, assignent, etc
  3243. * @param array $fields array of date fields from mod table
  3244. * @param int $timeshift time difference
  3245. * @return success
  3246. */
  3247. function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
  3248. global $CFG;
  3249. include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
  3250. $return = true;
  3251. foreach ($fields as $field) {
  3252. $updatesql = "UPDATE {$CFG->prefix}$modname
  3253. SET $field = $field + ($timeshift)
  3254. WHERE course=$courseid AND $field<>0 AND $field<>0";
  3255. $return = execute_sql($updatesql, false) && $return;
  3256. }
  3257. $refreshfunction = $modname.'_refresh_events';
  3258. if (function_exists($refreshfunction)) {
  3259. $refreshfunction($courseid);
  3260. }
  3261. return $return;
  3262. }
  3263. /**
  3264. * This function will empty a course of user data.
  3265. * It will retain the activities and the structure of the course.
  3266. * @param object $data an object containing all the settings including courseid (without magic quotes)
  3267. * @return array status array of array component, item, error
  3268. */
  3269. function reset_course_userdata($data) {
  3270. global $CFG, $USER;
  3271. require_once($CFG->libdir.'/gradelib.php');
  3272. require_once($CFG->dirroot.'/group/lib.php');
  3273. $data->courseid = $data->id;
  3274. $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
  3275. // calculate the time shift of dates
  3276. if (!empty($data->reset_start_date)) {
  3277. // time part of course startdate should be zero
  3278. $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
  3279. } else {
  3280. $data->timeshift = 0;
  3281. }
  3282. // result array: component, item, error
  3283. $status = array();
  3284. // start the resetting
  3285. $componentstr = get_string('general');
  3286. // move the course start time
  3287. if (!empty($data->reset_start_date) and $data->timeshift) {
  3288. // change course start data
  3289. set_field('course', 'startdate', $data->reset_start_date, 'id', $data->courseid);
  3290. // update all course and group events - do not move activity events
  3291. $updatesql = "UPDATE {$CFG->prefix}event
  3292. SET timestart = timestart + ({$data->timeshift})
  3293. WHERE courseid={$data->courseid} AND instance=0";
  3294. execute_sql($updatesql, false);
  3295. $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
  3296. }
  3297. if (!empty($data->reset_logs)) {
  3298. delete_records('log', 'course', $data->courseid);
  3299. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
  3300. }
  3301. if (!empty($data->reset_events)) {
  3302. delete_records('event', 'courseid', $data->courseid);
  3303. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
  3304. }
  3305. if (!empty($data->reset_notes)) {
  3306. require_once($CFG->dirroot.'/notes/lib.php');
  3307. note_delete_all($data->courseid);
  3308. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
  3309. }
  3310. $componentstr = get_string('roles');
  3311. if (!empty($data->reset_roles_overrides)) {
  3312. $children = get_child_contexts($context);
  3313. foreach ($children as $child) {
  3314. delete_records('role_capabilities', 'contextid', $child->id);
  3315. }
  3316. delete_records('role_capabilities', 'contextid', $context->id);
  3317. //force refresh for logged in users
  3318. mark_context_dirty($context->path);
  3319. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
  3320. }
  3321. if (!empty($data->reset_roles_local)) {
  3322. $children = get_child_contexts($context);
  3323. foreach ($children as $child) {
  3324. role_unassign(0, 0, 0, $child->id);
  3325. }
  3326. //force refresh for logged in users
  3327. mark_context_dirty($context->path);
  3328. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
  3329. }
  3330. // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
  3331. $data->unenrolled = array();
  3332. if (!empty($data->reset_roles)) {
  3333. foreach($data->reset_roles as $roleid) {
  3334. if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) {
  3335. foreach ($users as $user) {
  3336. role_unassign($roleid, $user->id, 0, $context->id);
  3337. if (!has_capability('moodle/course:view', $context, $user->id)) {
  3338. $data->unenrolled[$user->id] = $user->id;
  3339. }
  3340. }
  3341. }
  3342. }
  3343. }
  3344. if (!empty($data->unenrolled)) {
  3345. $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol').' ('.count($data->unenrolled).')', 'error'=>false);
  3346. }
  3347. $componentstr = get_string('groups');
  3348. // remove all group members
  3349. if (!empty($data->reset_groups_members)) {
  3350. groups_delete_group_members($data->courseid);
  3351. $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
  3352. }
  3353. // remove all groups
  3354. if (!empty($data->reset_groups_remove)) {
  3355. groups_delete_groups($data->courseid, false);
  3356. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
  3357. }
  3358. // remove all grouping members
  3359. if (!empty($data->reset_groupings_members)) {
  3360. groups_delete_groupings_groups($data->courseid, false);
  3361. $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
  3362. }
  3363. // remove all groupings
  3364. if (!empty($data->reset_groupings_remove)) {
  3365. groups_delete_groupings($data->courseid, false);
  3366. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
  3367. }
  3368. // Look in every instance of every module for data to delete
  3369. $unsupported_mods = array();
  3370. if ($allmods = get_records('modules') ) {
  3371. foreach ($allmods as $mod) {
  3372. $modname = $mod->name;
  3373. if (!count_records($modname, 'course', $data->courseid)) {
  3374. continue; // skip mods with no instances
  3375. }
  3376. $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
  3377. $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
  3378. if (file_exists($modfile)) {
  3379. include_once($modfile);
  3380. if (function_exists($moddeleteuserdata)) {
  3381. $modstatus = $moddeleteuserdata($data);
  3382. if (is_array($modstatus)) {
  3383. $status = array_merge($status, $modstatus);
  3384. } else {
  3385. debugging('Module '.$modname.' returned incorrect staus - must be an array!');
  3386. }
  3387. } else {
  3388. $unsupported_mods[] = $mod;
  3389. }
  3390. } else {
  3391. debugging('Missing lib.php in '.$modname.' module!');
  3392. }
  3393. }
  3394. }
  3395. // mention unsupported mods
  3396. if (!empty($unsupported_mods)) {
  3397. foreach($unsupported_mods as $mod) {
  3398. $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
  3399. }
  3400. }
  3401. $componentstr = get_string('gradebook', 'grades');
  3402. // reset gradebook
  3403. if (!empty($data->reset_gradebook_items)) {
  3404. remove_course_grades($data->courseid, false);
  3405. grade_grab_course_grades($data->courseid);
  3406. grade_regrade_final_grades($data->courseid);
  3407. $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
  3408. } else if (!empty($data->reset_gradebook_grades)) {
  3409. grade_course_reset($data->courseid);
  3410. $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
  3411. }
  3412. return $status;
  3413. }
  3414. function generate_email_processing_address($modid,$modargs) {
  3415. global $CFG;
  3416. $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
  3417. return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
  3418. }
  3419. function moodle_process_email($modargs,$body) {
  3420. // the first char should be an unencoded letter. We'll take this as an action
  3421. switch ($modargs{0}) {
  3422. case 'B': { // bounce
  3423. list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
  3424. if ($user = get_record_select("user","id=$userid","id,email")) {
  3425. // check the half md5 of their email
  3426. $md5check = substr(md5($user->email),0,16);
  3427. if ($md5check == substr($modargs, -16)) {
  3428. set_bounce_count($user);
  3429. }
  3430. // else maybe they've already changed it?
  3431. }
  3432. }
  3433. break;
  3434. // maybe more later?
  3435. }
  3436. }
  3437. /// CORRESPONDENCE ////////////////////////////////////////////////
  3438. /**
  3439. * Get mailer instance, enable buffering, flush buffer or disable buffering.
  3440. * @param $action string 'get', 'buffer', 'close' or 'flush'
  3441. * @return reference to mailer instance if 'get' used or nothing
  3442. */
  3443. function &get_mailer($action='get') {
  3444. global $CFG;
  3445. static $mailer = null;
  3446. static $counter = 0;
  3447. if (!isset($CFG->smtpmaxbulk)) {
  3448. $CFG->smtpmaxbulk = 1;
  3449. }
  3450. if ($action == 'get') {
  3451. $prevkeepalive = false;
  3452. if (isset($mailer) and $mailer->Mailer == 'smtp') {
  3453. if ($counter < $CFG->smtpmaxbulk and empty($mailer->error_count)) {
  3454. $counter++;
  3455. // reset the mailer
  3456. $mailer->Priority = 3;
  3457. $mailer->CharSet = 'UTF-8'; // our default
  3458. $mailer->ContentType = "text/plain";
  3459. $mailer->Encoding = "8bit";
  3460. $mailer->From = "root@localhost";
  3461. $mailer->FromName = "Root User";
  3462. $mailer->Sender = "";
  3463. $mailer->Subject = "";
  3464. $mailer->Body = "";
  3465. $mailer->AltBody = "";
  3466. $mailer->ConfirmReadingTo = "";
  3467. $mailer->ClearAllRecipients();
  3468. $mailer->ClearReplyTos();
  3469. $mailer->ClearAttachments();
  3470. $mailer->ClearCustomHeaders();
  3471. return $mailer;
  3472. }
  3473. $prevkeepalive = $mailer->SMTPKeepAlive;
  3474. get_mailer('flush');
  3475. }
  3476. include_once($CFG->libdir.'/phpmailer/class.phpmailer.php');
  3477. $mailer = new phpmailer();
  3478. $counter = 1;
  3479. $mailer->Version = 'Moodle '.$CFG->version; // mailer version
  3480. $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
  3481. $mailer->CharSet = 'UTF-8';
  3482. // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
  3483. // hmm, this is a bit hacky because LE should be private
  3484. if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
  3485. $mailer->LE = "\r\n";
  3486. } else {
  3487. $mailer->LE = "\n";
  3488. }
  3489. if ($CFG->smtphosts == 'qmail') {
  3490. $mailer->IsQmail(); // use Qmail system
  3491. } else if (empty($CFG->smtphosts)) {
  3492. $mailer->IsMail(); // use PHP mail() = sendmail
  3493. } else {
  3494. $mailer->IsSMTP(); // use SMTP directly
  3495. if (!empty($CFG->debugsmtp)) {
  3496. $mailer->SMTPDebug = true;
  3497. }
  3498. $mailer->Host = $CFG->smtphosts; // specify main and backup servers
  3499. $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
  3500. if ($CFG->smtpuser) { // Use SMTP authentication
  3501. $mailer->SMTPAuth = true;
  3502. $mailer->Username = $CFG->smtpuser;
  3503. $mailer->Password = $CFG->smtppass;
  3504. }
  3505. }
  3506. return $mailer;
  3507. }
  3508. $nothing = null;
  3509. // keep smtp session open after sending
  3510. if ($action == 'buffer') {
  3511. if (!empty($CFG->smtpmaxbulk)) {
  3512. get_mailer('flush');
  3513. $m =& get_mailer();
  3514. if ($m->Mailer == 'smtp') {
  3515. $m->SMTPKeepAlive = true;
  3516. }
  3517. }
  3518. return $nothing;
  3519. }
  3520. // close smtp session, but continue buffering
  3521. if ($action == 'flush') {
  3522. if (isset($mailer) and $mailer->Mailer == 'smtp') {
  3523. if (!empty($mailer->SMTPDebug)) {
  3524. echo '<pre>'."\n";
  3525. }
  3526. $mailer->SmtpClose();
  3527. if (!empty($mailer->SMTPDebug)) {
  3528. echo '</pre>';
  3529. }
  3530. }
  3531. return $nothing;
  3532. }
  3533. // close smtp session, do not buffer anymore
  3534. if ($action == 'close') {
  3535. if (isset($mailer) and $mailer->Mailer == 'smtp') {
  3536. get_mailer('flush');
  3537. $mailer->SMTPKeepAlive = false;
  3538. }
  3539. $mailer = null; // better force new instance
  3540. return $nothing;
  3541. }
  3542. }
  3543. /**
  3544. * Send an email to a specified user
  3545. *
  3546. * @uses $CFG
  3547. * @uses $FULLME
  3548. * @uses $MNETIDPJUMPURL IdentityProvider(IDP) URL user hits to jump to mnet peer.
  3549. * @uses SITEID
  3550. * @param user $user A {@link $USER} object
  3551. * @param user $from A {@link $USER} object
  3552. * @param string $subject plain text subject line of the email
  3553. * @param string $messagetext plain text version of the message
  3554. * @param string $messagehtml complete html version of the message (optional)
  3555. * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
  3556. * @param string $attachname the name of the file (extension indicates MIME)
  3557. * @param bool $usetrueaddress determines whether $from email address should
  3558. * be sent out. Will be overruled by user profile setting for maildisplay
  3559. * @param int $wordwrapwidth custom word wrap width
  3560. * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
  3561. * was blocked by user and "false" if there was another sort of error.
  3562. */
  3563. function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
  3564. global $CFG, $FULLME, $MNETIDPJUMPURL;
  3565. static $mnetjumps = array();
  3566. if (empty($user) || empty($user->email)) {
  3567. return false;
  3568. }
  3569. if (!empty($user->deleted)) {
  3570. // do not mail delted users
  3571. return false;
  3572. }
  3573. if (!empty($CFG->noemailever)) {
  3574. // hidden setting for development sites, set in config.php if needed
  3575. return true;
  3576. }
  3577. if (!empty($CFG->divertallemailsto)) {
  3578. $subject = "[DIVERTED {$user->email}] $subject";
  3579. $user = clone($user);
  3580. $user->email = $CFG->divertallemailsto;
  3581. }
  3582. // skip mail to suspended users
  3583. if (isset($user->auth) && $user->auth=='nologin') {
  3584. return true;
  3585. }
  3586. if (!empty($user->emailstop)) {
  3587. return 'emailstop';
  3588. }
  3589. if (over_bounce_threshold($user)) {
  3590. error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
  3591. return false;
  3592. }
  3593. // If the user is a remote mnet user, parse the email text for URL to the
  3594. // wwwroot and modify the url to direct the user's browser to login at their
  3595. // home site (identity provider - idp) before hitting the link itself
  3596. if (is_mnet_remote_user($user)) {
  3597. require_once($CFG->dirroot.'/mnet/lib.php');
  3598. // Form the request url to hit the idp's jump.php
  3599. if (isset($mnetjumps[$user->mnethostid])) {
  3600. $MNETIDPJUMPURL = $mnetjumps[$user->mnethostid];
  3601. } else {
  3602. $idp = mnet_get_peer_host($user->mnethostid);
  3603. $idpjumppath = '/auth/mnet/jump.php';
  3604. $MNETIDPJUMPURL = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
  3605. $mnetjumps[$user->mnethostid] = $MNETIDPJUMPURL;
  3606. }
  3607. $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
  3608. 'mnet_sso_apply_indirection',
  3609. $messagetext);
  3610. $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
  3611. 'mnet_sso_apply_indirection',
  3612. $messagehtml);
  3613. }
  3614. $mail =& get_mailer();
  3615. if (!empty($mail->SMTPDebug)) {
  3616. echo '<pre>' . "\n";
  3617. }
  3618. /// We are going to use textlib services here
  3619. $textlib = textlib_get_instance();
  3620. $supportuser = generate_email_supportuser();
  3621. // make up an email address for handling bounces
  3622. if (!empty($CFG->handlebounces)) {
  3623. $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
  3624. $mail->Sender = generate_email_processing_address(0,$modargs);
  3625. } else {
  3626. $mail->Sender = $supportuser->email;
  3627. }
  3628. if (is_string($from)) { // So we can pass whatever we want if there is need
  3629. $mail->From = $CFG->noreplyaddress;
  3630. $mail->FromName = $from;
  3631. } else if ($usetrueaddress and $from->maildisplay) {
  3632. $mail->From = stripslashes($from->email);
  3633. $mail->FromName = fullname($from);
  3634. } else {
  3635. $mail->From = $CFG->noreplyaddress;
  3636. $mail->FromName = fullname($from);
  3637. if (empty($replyto)) {
  3638. $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
  3639. }
  3640. }
  3641. if (!empty($replyto)) {
  3642. $mail->AddReplyTo($replyto,$replytoname);
  3643. }
  3644. $mail->Subject = substr(stripslashes($subject), 0, 900);
  3645. $mail->AddAddress(stripslashes($user->email), fullname($user) );
  3646. $mail->WordWrap = $wordwrapwidth; // set word wrap
  3647. if (!empty($from->customheaders)) { // Add custom headers
  3648. if (is_array($from->customheaders)) {
  3649. foreach ($from->customheaders as $customheader) {
  3650. $mail->AddCustomHeader($customheader);
  3651. }
  3652. } else {
  3653. $mail->AddCustomHeader($from->customheaders);
  3654. }
  3655. }
  3656. if (!empty($from->priority)) {
  3657. $mail->Priority = $from->priority;
  3658. }
  3659. if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
  3660. $mail->IsHTML(true);
  3661. $mail->Encoding = 'quoted-printable'; // Encoding to use
  3662. $mail->Body = $messagehtml;
  3663. $mail->AltBody = "\n$messagetext\n";
  3664. } else {
  3665. $mail->IsHTML(false);
  3666. $mail->Body = "\n$messagetext\n";
  3667. }
  3668. if ($attachment && $attachname) {
  3669. if (ereg( "\\.\\." ,$attachment )) { // Security check for ".." in dir path
  3670. $mail->AddAddress($supportuser->email, fullname($supportuser, true) );
  3671. $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
  3672. } else {
  3673. require_once($CFG->libdir.'/filelib.php');
  3674. $mimetype = mimeinfo('type', $attachname);
  3675. $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
  3676. }
  3677. }
  3678. /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
  3679. /// encoding to the specified one
  3680. if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
  3681. /// Set it to site mail charset
  3682. $charset = $CFG->sitemailcharset;
  3683. /// Overwrite it with the user mail charset
  3684. if (!empty($CFG->allowusermailcharset)) {
  3685. if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
  3686. $charset = $useremailcharset;
  3687. }
  3688. }
  3689. /// If it has changed, convert all the necessary strings
  3690. $charsets = get_list_of_charsets();
  3691. unset($charsets['UTF-8']);
  3692. if (in_array($charset, $charsets)) {
  3693. /// Save the new mail charset
  3694. $mail->CharSet = $charset;
  3695. /// And convert some strings
  3696. $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
  3697. foreach ($mail->ReplyTo as $key => $rt) { //ReplyTo Names
  3698. $mail->ReplyTo[$key][1] = $textlib->convert($rt[1], 'utf-8', $mail->CharSet);
  3699. }
  3700. $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet); //Subject
  3701. foreach ($mail->to as $key => $to) {
  3702. $mail->to[$key][1] = $textlib->convert($to[1], 'utf-8', $mail->CharSet); //To Names
  3703. }
  3704. $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet); //Body
  3705. $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet); //Subject
  3706. }
  3707. }
  3708. if ($mail->Send()) {
  3709. set_send_count($user);
  3710. $mail->IsSMTP(); // use SMTP directly
  3711. if (!empty($mail->SMTPDebug)) {
  3712. echo '</pre>';
  3713. }
  3714. return true;
  3715. } else {
  3716. mtrace('ERROR: '. $mail->ErrorInfo);
  3717. add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
  3718. if (!empty($mail->SMTPDebug)) {
  3719. echo '</pre>';
  3720. }
  3721. return false;
  3722. }
  3723. }
  3724. /**
  3725. * Generate a signoff for emails based on support settings
  3726. *
  3727. */
  3728. function generate_email_signoff() {
  3729. global $CFG;
  3730. $signoff = "\n";
  3731. if (!empty($CFG->supportname)) {
  3732. $signoff .= $CFG->supportname."\n";
  3733. }
  3734. if (!empty($CFG->supportemail)) {
  3735. $signoff .= $CFG->supportemail."\n";
  3736. }
  3737. if (!empty($CFG->supportpage)) {
  3738. $signoff .= $CFG->supportpage."\n";
  3739. }
  3740. return $signoff;
  3741. }
  3742. /**
  3743. * Generate a fake user for emails based on support settings
  3744. *
  3745. */
  3746. function generate_email_supportuser() {
  3747. global $CFG;
  3748. static $supportuser;
  3749. if (!empty($supportuser)) {
  3750. return $supportuser;
  3751. }
  3752. $supportuser = new object;
  3753. $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
  3754. $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
  3755. $supportuser->lastname = '';
  3756. $supportuser->maildisplay = true;
  3757. return $supportuser;
  3758. }
  3759. /**
  3760. * Sets specified user's password and send the new password to the user via email.
  3761. *
  3762. * @uses $CFG
  3763. * @param user $user A {@link $USER} object
  3764. * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
  3765. * was blocked by user and "false" if there was another sort of error.
  3766. */
  3767. function setnew_password_and_mail($user) {
  3768. global $CFG;
  3769. $site = get_site();
  3770. $supportuser = generate_email_supportuser();
  3771. $newpassword = generate_password();
  3772. if (! set_field('user', 'password', hash_internal_user_password($newpassword), 'id', $user->id) ) {
  3773. trigger_error('Could not set user password!');
  3774. return false;
  3775. }
  3776. $a = new object();
  3777. $a->firstname = fullname($user, true);
  3778. $a->sitename = format_string($site->fullname);
  3779. $a->username = $user->username;
  3780. $a->newpassword = $newpassword;
  3781. $a->link = $CFG->wwwroot .'/login/';
  3782. $a->signoff = generate_email_signoff();
  3783. $message = get_string('newusernewpasswordtext', '', $a);
  3784. $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
  3785. return email_to_user($user, $supportuser, $subject, $message);
  3786. }
  3787. /**
  3788. * Resets specified user's password and send the new password to the user via email.
  3789. *
  3790. * @uses $CFG
  3791. * @param user $user A {@link $USER} object
  3792. * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
  3793. * was blocked by user and "false" if there was another sort of error.
  3794. */
  3795. function reset_password_and_mail($user) {
  3796. global $CFG;
  3797. $site = get_site();
  3798. $supportuser = generate_email_supportuser();
  3799. $userauth = get_auth_plugin($user->auth);
  3800. if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
  3801. trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
  3802. return false;
  3803. }
  3804. $newpassword = generate_password();
  3805. if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
  3806. error("Could not set user password!");
  3807. }
  3808. $a = new object();
  3809. $a->firstname = $user->firstname;
  3810. $a->lastname = $user->lastname;
  3811. $a->sitename = format_string($site->fullname);
  3812. $a->username = $user->username;
  3813. $a->newpassword = $newpassword;
  3814. $a->link = $CFG->httpswwwroot .'/login/change_password.php';
  3815. $a->signoff = generate_email_signoff();
  3816. $message = get_string('newpasswordtext', '', $a);
  3817. $subject = format_string($site->fullname) .': '. get_string('changedpassword');
  3818. return email_to_user($user, $supportuser, $subject, $message);
  3819. }
  3820. /**
  3821. * Send email to specified user with confirmation text and activation link.
  3822. *
  3823. * @uses $CFG
  3824. * @param user $user A {@link $USER} object
  3825. * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
  3826. * was blocked by user and "false" if there was another sort of error.
  3827. */
  3828. function send_confirmation_email($user) {
  3829. global $CFG;
  3830. $site = get_site();
  3831. $supportuser = generate_email_supportuser();
  3832. $data = new object();
  3833. $data->firstname = fullname($user);
  3834. $data->sitename = format_string($site->fullname);
  3835. $data->admin = generate_email_signoff();
  3836. $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
  3837. $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
  3838. $message = get_string('emailconfirmation', '', $data);
  3839. $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
  3840. $user->mailformat = 1; // Always send HTML version as well
  3841. return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
  3842. }
  3843. /**
  3844. * send_password_change_confirmation_email.
  3845. *
  3846. * @uses $CFG
  3847. * @param user $user A {@link $USER} object
  3848. * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
  3849. * was blocked by user and "false" if there was another sort of error.
  3850. */
  3851. function send_password_change_confirmation_email($user) {
  3852. global $CFG;
  3853. $site = get_site();
  3854. $supportuser = generate_email_supportuser();
  3855. $data = new object();
  3856. $data->firstname = $user->firstname;
  3857. $data->lastname = $user->lastname;
  3858. $data->sitename = format_string($site->fullname);
  3859. $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
  3860. $data->admin = generate_email_signoff();
  3861. $message = get_string('emailpasswordconfirmation', '', $data);
  3862. $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
  3863. return email_to_user($user, $supportuser, $subject, $message);
  3864. }
  3865. /**
  3866. * send_password_change_info.
  3867. *
  3868. * @uses $CFG
  3869. * @param user $user A {@link $USER} object
  3870. * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
  3871. * was blocked by user and "false" if there was another sort of error.
  3872. */
  3873. function send_password_change_info($user) {
  3874. global $CFG;
  3875. $site = get_site();
  3876. $supportuser = generate_email_supportuser();
  3877. $systemcontext = get_context_instance(CONTEXT_SYSTEM);
  3878. $data = new object();
  3879. $data->firstname = $user->firstname;
  3880. $data->lastname = $user->lastname;
  3881. $data->sitename = format_string($site->fullname);
  3882. $data->admin = generate_email_signoff();
  3883. $userauth = get_auth_plugin($user->auth);
  3884. if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
  3885. $message = get_string('emailpasswordchangeinfodisabled', '', $data);
  3886. $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
  3887. return email_to_user($user, $supportuser, $subject, $message);
  3888. }
  3889. if ($userauth->can_change_password() and $userauth->change_password_url()) {
  3890. // we have some external url for password changing
  3891. $data->link .= $userauth->change_password_url();
  3892. } else {
  3893. //no way to change password, sorry
  3894. $data->link = '';
  3895. }
  3896. if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
  3897. $message = get_string('emailpasswordchangeinfo', '', $data);
  3898. $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
  3899. } else {
  3900. $message = get_string('emailpasswordchangeinfofail', '', $data);
  3901. $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
  3902. }
  3903. return email_to_user($user, $supportuser, $subject, $message);
  3904. }
  3905. /**
  3906. * Check that an email is allowed. It returns an error message if there
  3907. * was a problem.
  3908. *
  3909. * @uses $CFG
  3910. * @param string $email Content of email
  3911. * @return string|false
  3912. */
  3913. function email_is_not_allowed($email) {
  3914. global $CFG;
  3915. if (!empty($CFG->allowemailaddresses)) {
  3916. $allowed = explode(' ', $CFG->allowemailaddresses);
  3917. foreach ($allowed as $allowedpattern) {
  3918. $allowedpattern = trim($allowedpattern);
  3919. if (!$allowedpattern) {
  3920. continue;
  3921. }
  3922. if (strpos($allowedpattern, '.') === 0) {
  3923. if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
  3924. // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
  3925. return false;
  3926. }
  3927. } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
  3928. return false;
  3929. }
  3930. }
  3931. return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
  3932. } else if (!empty($CFG->denyemailaddresses)) {
  3933. $denied = explode(' ', $CFG->denyemailaddresses);
  3934. foreach ($denied as $deniedpattern) {
  3935. $deniedpattern = trim($deniedpattern);
  3936. if (!$deniedpattern) {
  3937. continue;
  3938. }
  3939. if (strpos($deniedpattern, '.') === 0) {
  3940. if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
  3941. // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
  3942. return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
  3943. }
  3944. } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
  3945. return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
  3946. }
  3947. }
  3948. }
  3949. return false;
  3950. }
  3951. function email_welcome_message_to_user($course, $user=NULL) {
  3952. global $CFG, $USER;
  3953. if (isset($CFG->sendcoursewelcomemessage) and !$CFG->sendcoursewelcomemessage) {
  3954. return;
  3955. }
  3956. if (empty($user)) {
  3957. if (!isloggedin()) {
  3958. return false;
  3959. }
  3960. $user = $USER;
  3961. }
  3962. if (!empty($course->welcomemessage)) {
  3963. $message = $course->welcomemessage;
  3964. } else {
  3965. $a = new Object();
  3966. $a->coursename = $course->fullname;
  3967. $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id";
  3968. $message = get_string("welcometocoursetext", "", $a);
  3969. }
  3970. /// If you don't want a welcome message sent, then make the message string blank.
  3971. if (!empty($message)) {
  3972. $subject = get_string('welcometocourse', '', format_string($course->fullname));
  3973. if (! $teacher = get_teacher($course->id)) {
  3974. $teacher = get_admin();
  3975. }
  3976. email_to_user($user, $teacher, $subject, $message);
  3977. }
  3978. }
  3979. /// FILE HANDLING /////////////////////////////////////////////
  3980. /**
  3981. * Makes an upload directory for a particular module.
  3982. *
  3983. * @uses $CFG
  3984. * @param int $courseid The id of the course in question - maps to id field of 'course' table.
  3985. * @return string|false Returns full path to directory if successful, false if not
  3986. */
  3987. function make_mod_upload_directory($courseid) {
  3988. global $CFG;
  3989. if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
  3990. return false;
  3991. }
  3992. $strreadme = get_string('readme');
  3993. if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
  3994. copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
  3995. } else {
  3996. copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
  3997. }
  3998. return $moddata;
  3999. }
  4000. /**
  4001. * Makes a directory for a particular user.
  4002. *
  4003. * @uses $CFG
  4004. * @param int $userid The id of the user in question - maps to id field of 'user' table.
  4005. * @param bool $test Whether we are only testing the return value (do not create the directory)
  4006. * @return string|false Returns full path to directory if successful, false if not
  4007. */
  4008. function make_user_directory($userid, $test=false) {
  4009. global $CFG;
  4010. if (is_bool($userid) || $userid < 0 || !ereg('^[0-9]{1,10}$', $userid) || $userid > 2147483647) {
  4011. if (!$test) {
  4012. notify("Given userid was not a valid integer! (" . gettype($userid) . " $userid)");
  4013. }
  4014. return false;
  4015. }
  4016. // Generate a two-level path for the userid. First level groups them by slices of 1000 users, second level is userid
  4017. $level1 = floor($userid / 1000) * 1000;
  4018. $userdir = "user/$level1/$userid";
  4019. if ($test) {
  4020. return $CFG->dataroot . '/' . $userdir;
  4021. } else {
  4022. return make_upload_directory($userdir);
  4023. }
  4024. }
  4025. /**
  4026. * Returns an array of full paths to user directories, indexed by their userids.
  4027. *
  4028. * @param bool $only_non_empty Only return directories that contain files
  4029. * @param bool $legacy Search for user directories in legacy location (dataroot/users/userid) instead of (dataroot/user/section/userid)
  4030. * @return array An associative array: userid=>array(basedir => $basedir, userfolder => $userfolder)
  4031. */
  4032. function get_user_directories($only_non_empty=true, $legacy=false) {
  4033. global $CFG;
  4034. $rootdir = $CFG->dataroot."/user";
  4035. if ($legacy) {
  4036. $rootdir = $CFG->dataroot."/users";
  4037. }
  4038. $dirlist = array();
  4039. //Check if directory exists
  4040. if (check_dir_exists($rootdir, true)) {
  4041. if ($legacy) {
  4042. if ($userlist = get_directory_list($rootdir, '', true, true, false)) {
  4043. foreach ($userlist as $userid) {
  4044. $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $userid);
  4045. }
  4046. } else {
  4047. notify("no directories found under $rootdir");
  4048. }
  4049. } else {
  4050. if ($grouplist =get_directory_list($rootdir, '', true, true, false)) { // directories will be in the form 0, 1000, 2000 etc...
  4051. foreach ($grouplist as $group) {
  4052. if ($userlist = get_directory_list("$rootdir/$group", '', true, true, false)) {
  4053. foreach ($userlist as $userid) {
  4054. $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $group . '/' . $userid);
  4055. }
  4056. }
  4057. }
  4058. }
  4059. }
  4060. } else {
  4061. notify("$rootdir does not exist!");
  4062. return false;
  4063. }
  4064. return $dirlist;
  4065. }
  4066. /**
  4067. * Returns current name of file on disk if it exists.
  4068. *
  4069. * @param string $newfile File to be verified
  4070. * @return string Current name of file on disk if true
  4071. */
  4072. function valid_uploaded_file($newfile) {
  4073. if (empty($newfile)) {
  4074. return '';
  4075. }
  4076. if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
  4077. return $newfile['tmp_name'];
  4078. } else {
  4079. return '';
  4080. }
  4081. }
  4082. /**
  4083. * Returns the maximum size for uploading files.
  4084. *
  4085. * There are seven possible upload limits:
  4086. * 1. in Apache using LimitRequestBody (no way of checking or changing this)
  4087. * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
  4088. * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
  4089. * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
  4090. * 5. by the Moodle admin in $CFG->maxbytes
  4091. * 6. by the teacher in the current course $course->maxbytes
  4092. * 7. by the teacher for the current module, eg $assignment->maxbytes
  4093. *
  4094. * These last two are passed to this function as arguments (in bytes).
  4095. * Anything defined as 0 is ignored.
  4096. * The smallest of all the non-zero numbers is returned.
  4097. *
  4098. * @param int $sizebytes ?
  4099. * @param int $coursebytes Current course $course->maxbytes (in bytes)
  4100. * @param int $modulebytes Current module ->maxbytes (in bytes)
  4101. * @return int The maximum size for uploading files.
  4102. * @todo Finish documenting this function
  4103. */
  4104. function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
  4105. if (! $filesize = ini_get('upload_max_filesize')) {
  4106. $filesize = '5M';
  4107. }
  4108. $minimumsize = get_real_size($filesize);
  4109. if ($postsize = ini_get('post_max_size')) {
  4110. $postsize = get_real_size($postsize);
  4111. if ($postsize < $minimumsize) {
  4112. $minimumsize = $postsize;
  4113. }
  4114. }
  4115. if ($sitebytes and $sitebytes < $minimumsize) {
  4116. $minimumsize = $sitebytes;
  4117. }
  4118. if ($coursebytes and $coursebytes < $minimumsize) {
  4119. $minimumsize = $coursebytes;
  4120. }
  4121. if ($modulebytes and $modulebytes < $minimumsize) {
  4122. $minimumsize = $modulebytes;
  4123. }
  4124. return $minimumsize;
  4125. }
  4126. /**
  4127. * Related to {@link get_max_upload_file_size()} - this function returns an
  4128. * array of possible sizes in an array, translated to the
  4129. * local language.
  4130. *
  4131. * @uses SORT_NUMERIC
  4132. * @param int $sizebytes ?
  4133. * @param int $coursebytes Current course $course->maxbytes (in bytes)
  4134. * @param int $modulebytes Current module ->maxbytes (in bytes)
  4135. * @return int
  4136. * @todo Finish documenting this function
  4137. */
  4138. function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
  4139. global $CFG;
  4140. if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
  4141. return array();
  4142. }
  4143. $filesize[$maxsize] = display_size($maxsize);
  4144. $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
  4145. 5242880, 10485760, 20971520, 52428800, 104857600);
  4146. // Allow maxbytes to be selected if it falls outside the above boundaries
  4147. if( isset($CFG->maxbytes) && !in_array($CFG->maxbytes, $sizelist) ){
  4148. $sizelist[] = $CFG->maxbytes;
  4149. }
  4150. foreach ($sizelist as $sizebytes) {
  4151. if ($sizebytes < $maxsize) {
  4152. $filesize[$sizebytes] = display_size($sizebytes);
  4153. }
  4154. }
  4155. krsort($filesize, SORT_NUMERIC);
  4156. return $filesize;
  4157. }
  4158. /**
  4159. * If there has been an error uploading a file, print the appropriate error message
  4160. * Numerical constants used as constant definitions not added until PHP version 4.2.0
  4161. *
  4162. * $filearray is a 1-dimensional sub-array of the $_FILES array
  4163. * eg $filearray = $_FILES['userfile1']
  4164. * If left empty then the first element of the $_FILES array will be used
  4165. *
  4166. * @uses $_FILES
  4167. * @param array $filearray A 1-dimensional sub-array of the $_FILES array
  4168. * @param bool $returnerror If true then a string error message will be returned. Otherwise the user will be notified of the error in a notify() call.
  4169. * @return bool|string
  4170. */
  4171. function print_file_upload_error($filearray = '', $returnerror = false) {
  4172. if ($filearray == '' or !isset($filearray['error'])) {
  4173. if (empty($_FILES)) return false;
  4174. $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
  4175. $filearray = array_shift($files); /// use first element of array
  4176. }
  4177. switch ($filearray['error']) {
  4178. case 0: // UPLOAD_ERR_OK
  4179. if ($filearray['size'] > 0) {
  4180. $errmessage = get_string('uploadproblem', $filearray['name']);
  4181. } else {
  4182. $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
  4183. }
  4184. break;
  4185. case 1: // UPLOAD_ERR_INI_SIZE
  4186. $errmessage = get_string('uploadserverlimit');
  4187. break;
  4188. case 2: // UPLOAD_ERR_FORM_SIZE
  4189. $errmessage = get_string('uploadformlimit');
  4190. break;
  4191. case 3: // UPLOAD_ERR_PARTIAL
  4192. $errmessage = get_string('uploadpartialfile');
  4193. break;
  4194. case 4: // UPLOAD_ERR_NO_FILE
  4195. $errmessage = get_string('uploadnofilefound');
  4196. break;
  4197. default:
  4198. $errmessage = get_string('uploadproblem', $filearray['name']);
  4199. }
  4200. if ($returnerror) {
  4201. return $errmessage;
  4202. } else {
  4203. notify($errmessage);
  4204. return true;
  4205. }
  4206. }
  4207. /**
  4208. * handy function to loop through an array of files and resolve any filename conflicts
  4209. * both in the array of filenames and for what is already on disk.
  4210. * not really compatible with the similar function in uploadlib.php
  4211. * but this could be used for files/index.php for moving files around.
  4212. */
  4213. function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
  4214. foreach ($files as $k => $f) {
  4215. if (check_potential_filename($destination,$f,$files)) {
  4216. $bits = explode('.', $f);
  4217. for ($i = 1; true; $i++) {
  4218. $try = sprintf($format, $bits[0], $i, $bits[1]);
  4219. if (!check_potential_filename($destination,$try,$files)) {
  4220. $files[$k] = $try;
  4221. break;
  4222. }
  4223. }
  4224. }
  4225. }
  4226. return $files;
  4227. }
  4228. /**
  4229. * @used by resolve_filename_collisions
  4230. */
  4231. function check_potential_filename($destination,$filename,$files) {
  4232. if (file_exists($destination.'/'.$filename)) {
  4233. return true;
  4234. }
  4235. if (count(array_keys($files,$filename)) > 1) {
  4236. return true;
  4237. }
  4238. return false;
  4239. }
  4240. /**
  4241. * Returns an array with all the filenames in
  4242. * all subdirectories, relative to the given rootdir.
  4243. * If excludefile is defined, then that file/directory is ignored
  4244. * If getdirs is true, then (sub)directories are included in the output
  4245. * If getfiles is true, then files are included in the output
  4246. * (at least one of these must be true!)
  4247. *
  4248. * @param string $rootdir ?
  4249. * @param string $excludefile If defined then the specified file/directory is ignored
  4250. * @param bool $descend ?
  4251. * @param bool $getdirs If true then (sub)directories are included in the output
  4252. * @param bool $getfiles If true then files are included in the output
  4253. * @return array An array with all the filenames in
  4254. * all subdirectories, relative to the given rootdir
  4255. * @todo Finish documenting this function. Add examples of $excludefile usage.
  4256. */
  4257. function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
  4258. $dirs = array();
  4259. if (!$getdirs and !$getfiles) { // Nothing to show
  4260. return $dirs;
  4261. }
  4262. if (!is_dir($rootdir)) { // Must be a directory
  4263. return $dirs;
  4264. }
  4265. if (!$dir = opendir($rootdir)) { // Can't open it for some reason
  4266. return $dirs;
  4267. }
  4268. if (!is_array($excludefiles)) {
  4269. $excludefiles = array($excludefiles);
  4270. }
  4271. while (false !== ($file = readdir($dir))) {
  4272. $firstchar = substr($file, 0, 1);
  4273. if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
  4274. continue;
  4275. }
  4276. $fullfile = $rootdir .'/'. $file;
  4277. if (filetype($fullfile) == 'dir') {
  4278. if ($getdirs) {
  4279. $dirs[] = $file;
  4280. }
  4281. if ($descend) {
  4282. $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
  4283. foreach ($subdirs as $subdir) {
  4284. $dirs[] = $file .'/'. $subdir;
  4285. }
  4286. }
  4287. } else if ($getfiles) {
  4288. $dirs[] = $file;
  4289. }
  4290. }
  4291. closedir($dir);
  4292. asort($dirs);
  4293. return $dirs;
  4294. }
  4295. /**
  4296. * Adds up all the files in a directory and works out the size.
  4297. *
  4298. * @param string $rootdir ?
  4299. * @param string $excludefile ?
  4300. * @return array
  4301. * @todo Finish documenting this function
  4302. */
  4303. function get_directory_size($rootdir, $excludefile='') {
  4304. global $CFG;
  4305. // do it this way if we can, it's much faster
  4306. if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
  4307. $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
  4308. $output = null;
  4309. $return = null;
  4310. exec($command,$output,$return);
  4311. if (is_array($output)) {
  4312. return get_real_size(intval($output[0]).'k'); // we told it to return k.
  4313. }
  4314. }
  4315. if (!is_dir($rootdir)) { // Must be a directory
  4316. return 0;
  4317. }
  4318. if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
  4319. return 0;
  4320. }
  4321. $size = 0;
  4322. while (false !== ($file = readdir($dir))) {
  4323. $firstchar = substr($file, 0, 1);
  4324. if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
  4325. continue;
  4326. }
  4327. $fullfile = $rootdir .'/'. $file;
  4328. if (filetype($fullfile) == 'dir') {
  4329. $size += get_directory_size($fullfile, $excludefile);
  4330. } else {
  4331. $size += filesize($fullfile);
  4332. }
  4333. }
  4334. closedir($dir);
  4335. return $size;
  4336. }
  4337. /**
  4338. * Converts bytes into display form
  4339. *
  4340. * @param string $size ?
  4341. * @return string
  4342. * @staticvar string $gb Localized string for size in gigabytes
  4343. * @staticvar string $mb Localized string for size in megabytes
  4344. * @staticvar string $kb Localized string for size in kilobytes
  4345. * @staticvar string $b Localized string for size in bytes
  4346. * @todo Finish documenting this function. Verify return type.
  4347. */
  4348. function display_size($size) {
  4349. static $gb, $mb, $kb, $b;
  4350. if (empty($gb)) {
  4351. $gb = get_string('sizegb');
  4352. $mb = get_string('sizemb');
  4353. $kb = get_string('sizekb');
  4354. $b = get_string('sizeb');
  4355. }
  4356. if ($size >= 1073741824) {
  4357. $size = round($size / 1073741824 * 10) / 10 . $gb;
  4358. } else if ($size >= 1048576) {
  4359. $size = round($size / 1048576 * 10) / 10 . $mb;
  4360. } else if ($size >= 1024) {
  4361. $size = round($size / 1024 * 10) / 10 . $kb;
  4362. } else {
  4363. $size = $size .' '. $b;
  4364. }
  4365. return $size;
  4366. }
  4367. /**
  4368. * Cleans a given filename by removing suspicious or troublesome characters
  4369. * Only these are allowed: alphanumeric _ - .
  4370. * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
  4371. *
  4372. * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
  4373. * because native zip binaries do weird character conversions. Use PHP zipping instead.
  4374. *
  4375. * @param string $string file name
  4376. * @return string cleaned file name
  4377. */
  4378. function clean_filename($string) {
  4379. global $CFG;
  4380. if (empty($CFG->unicodecleanfilename)) {
  4381. $textlib = textlib_get_instance();
  4382. $string = $textlib->specialtoascii($string);
  4383. $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
  4384. } else {
  4385. //clean only ascii range
  4386. $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
  4387. }
  4388. $string = preg_replace("/_+/", '_', $string);
  4389. $string = preg_replace("/\.\.+/", '.', $string);
  4390. return $string;
  4391. }
  4392. /// STRING TRANSLATION ////////////////////////////////////////
  4393. /**
  4394. * Returns the code for the current language
  4395. *
  4396. * @uses $CFG
  4397. * @param $USER
  4398. * @param $SESSION
  4399. * @return string
  4400. */
  4401. function current_language() {
  4402. global $CFG, $USER, $SESSION, $COURSE;
  4403. if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
  4404. $return = $COURSE->lang;
  4405. } else if (!empty($SESSION->lang)) { // Session language can override other settings
  4406. $return = $SESSION->lang;
  4407. } else if (!empty($USER->lang)) {
  4408. $return = $USER->lang;
  4409. } else {
  4410. $return = $CFG->lang;
  4411. }
  4412. if ($return == 'en') {
  4413. $return = 'en_utf8';
  4414. }
  4415. return $return;
  4416. }
  4417. /**
  4418. * Prints out a translated string.
  4419. *
  4420. * Prints out a translated string using the return value from the {@link get_string()} function.
  4421. *
  4422. * Example usage of this function when the string is in the moodle.php file:<br/>
  4423. * <code>
  4424. * echo '<strong>';
  4425. * print_string('wordforstudent');
  4426. * echo '</strong>';
  4427. * </code>
  4428. *
  4429. * Example usage of this function when the string is not in the moodle.php file:<br/>
  4430. * <code>
  4431. * echo '<h1>';
  4432. * print_string('typecourse', 'calendar');
  4433. * echo '</h1>';
  4434. * </code>
  4435. *
  4436. * @param string $identifier The key identifier for the localized string
  4437. * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
  4438. * @param mixed $a An object, string or number that can be used
  4439. * within translation strings
  4440. */
  4441. function print_string($identifier, $module='', $a=NULL) {
  4442. echo get_string($identifier, $module, $a);
  4443. }
  4444. /**
  4445. * fix up the optional data in get_string()/print_string() etc
  4446. * ensure possible sprintf() format characters are escaped correctly
  4447. * needs to handle arbitrary strings and objects
  4448. * @param mixed $a An object, string or number that can be used
  4449. * @return mixed the supplied parameter 'cleaned'
  4450. */
  4451. function clean_getstring_data( $a ) {
  4452. if (is_string($a)) {
  4453. return str_replace( '%','%%',$a );
  4454. }
  4455. elseif (is_object($a)) {
  4456. $a_vars = get_object_vars( $a );
  4457. $new_a_vars = array();
  4458. foreach ($a_vars as $fname => $a_var) {
  4459. $new_a_vars[$fname] = clean_getstring_data( $a_var );
  4460. }
  4461. return (object)$new_a_vars;
  4462. }
  4463. else {
  4464. return $a;
  4465. }
  4466. }
  4467. /**
  4468. * @return array places to look for lang strings based on the prefix to the
  4469. * module name. For example qtype_ in question/type. Used by get_string and
  4470. * help.php.
  4471. */
  4472. function places_to_search_for_lang_strings() {
  4473. global $CFG;
  4474. return array(
  4475. '__exceptions' => array('moodle', 'langconfig'),
  4476. 'assignment_' => array('mod/assignment/type'),
  4477. 'auth_' => array('auth'),
  4478. 'block_' => array('blocks'),
  4479. 'datafield_' => array('mod/data/field'),
  4480. 'datapreset_' => array('mod/data/preset'),
  4481. 'enrol_' => array('enrol'),
  4482. 'filter_' => array('filter'),
  4483. 'format_' => array('course/format'),
  4484. 'qtype_' => array('question/type'),
  4485. 'report_' => array($CFG->admin.'/report', 'course/report', 'mod/quiz/report'),
  4486. 'resource_' => array('mod/resource/type'),
  4487. 'gradereport_' => array('grade/report'),
  4488. 'gradeimport_' => array('grade/import'),
  4489. 'gradeexport_' => array('grade/export'),
  4490. 'qformat_' => array('question/format'),
  4491. 'profilefield_' => array('user/profile/field'),
  4492. '' => array('mod')
  4493. );
  4494. }
  4495. /**
  4496. * Returns a localized string.
  4497. *
  4498. * Returns the translated string specified by $identifier as
  4499. * for $module. Uses the same format files as STphp.
  4500. * $a is an object, string or number that can be used
  4501. * within translation strings
  4502. *
  4503. * eg "hello \$a->firstname \$a->lastname"
  4504. * or "hello \$a"
  4505. *
  4506. * If you would like to directly echo the localized string use
  4507. * the function {@link print_string()}
  4508. *
  4509. * Example usage of this function involves finding the string you would
  4510. * like a local equivalent of and using its identifier and module information
  4511. * to retrive it.<br/>
  4512. * If you open moodle/lang/en/moodle.php and look near line 1031
  4513. * you will find a string to prompt a user for their word for student
  4514. * <code>
  4515. * $string['wordforstudent'] = 'Your word for Student';
  4516. * </code>
  4517. * So if you want to display the string 'Your word for student'
  4518. * in any language that supports it on your site
  4519. * you just need to use the identifier 'wordforstudent'
  4520. * <code>
  4521. * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
  4522. or
  4523. * </code>
  4524. * If the string you want is in another file you'd take a slightly
  4525. * different approach. Looking in moodle/lang/en/calendar.php you find
  4526. * around line 75:
  4527. * <code>
  4528. * $string['typecourse'] = 'Course event';
  4529. * </code>
  4530. * If you want to display the string "Course event" in any language
  4531. * supported you would use the identifier 'typecourse' and the module 'calendar'
  4532. * (because it is in the file calendar.php):
  4533. * <code>
  4534. * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
  4535. * </code>
  4536. *
  4537. * As a last resort, should the identifier fail to map to a string
  4538. * the returned string will be [[ $identifier ]]
  4539. *
  4540. * @uses $CFG
  4541. * @param string $identifier The key identifier for the localized string
  4542. * @param string $module The module where the key identifier is stored, usually expressed as the filename in the language pack without the .php on the end but can also be written as mod/forum or grade/export/xls. If none is specified then moodle.php is used.
  4543. * @param mixed $a An object, string or number that can be used
  4544. * within translation strings
  4545. * @param array $extralocations An array of strings with other locations to look for string files
  4546. * @return string The localized string.
  4547. */
  4548. function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
  4549. global $CFG;
  4550. /// originally these special strings were stored in moodle.php now we are only in langconfig.php
  4551. $langconfigstrs = array('alphabet', 'backupnameformat', 'decsep', 'firstdayofweek', 'listsep', 'locale',
  4552. 'localewin', 'localewincharset', 'oldcharset',
  4553. 'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
  4554. 'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
  4555. 'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
  4556. 'thischarset', 'thisdirection', 'thislanguage', 'strftimedatetimeshort', 'thousandssep');
  4557. $filetocheck = 'langconfig.php';
  4558. $defaultlang = 'en_utf8';
  4559. if (in_array($identifier, $langconfigstrs)) {
  4560. $module = 'langconfig'; //This strings are under langconfig.php for 1.6 lang packs
  4561. }
  4562. $lang = current_language();
  4563. if ($module == '') {
  4564. $module = 'moodle';
  4565. }
  4566. /// If the "module" is actually a pathname, then automatically derive the proper module name
  4567. if (strpos($module, '/') !== false) {
  4568. $modulepath = split('/', $module);
  4569. switch ($modulepath[0]) {
  4570. case 'mod':
  4571. $module = $modulepath[1];
  4572. break;
  4573. case 'blocks':
  4574. case 'block':
  4575. $module = 'block_'.$modulepath[1];
  4576. break;
  4577. case 'enrol':
  4578. $module = 'enrol_'.$modulepath[1];
  4579. break;
  4580. case 'format':
  4581. $module = 'format_'.$modulepath[1];
  4582. break;
  4583. case 'grade':
  4584. $module = 'grade'.$modulepath[1].'_'.$modulepath[2];
  4585. break;
  4586. }
  4587. }
  4588. /// if $a happens to have % in it, double it so sprintf() doesn't break
  4589. if ($a) {
  4590. $a = clean_getstring_data( $a );
  4591. }
  4592. /// Define the two or three major locations of language strings for this module
  4593. $locations = array();
  4594. if (!empty($extralocations)) { // Calling code has a good idea where to look
  4595. if (is_array($extralocations)) {
  4596. $locations += $extralocations;
  4597. } else if (is_string($extralocations)) {
  4598. $locations[] = $extralocations;
  4599. } else {
  4600. debugging('Bad lang path provided');
  4601. }
  4602. }
  4603. if (isset($CFG->running_installer)) {
  4604. $module = 'installer';
  4605. $filetocheck = 'installer.php';
  4606. $locations[] = $CFG->dirroot.'/install/lang/';
  4607. $locations[] = $CFG->dataroot.'/lang/';
  4608. $locations[] = $CFG->dirroot.'/lang/';
  4609. $defaultlang = 'en_utf8';
  4610. } else {
  4611. $locations[] = $CFG->dataroot.'/lang/';
  4612. $locations[] = $CFG->dirroot.'/lang/';
  4613. }
  4614. /// Add extra places to look for strings for particular plugin types.
  4615. $rules = places_to_search_for_lang_strings();
  4616. $exceptions = $rules['__exceptions'];
  4617. unset($rules['__exceptions']);
  4618. if (!in_array($module, $exceptions)) {
  4619. $dividerpos = strpos($module, '_');
  4620. if ($dividerpos === false) {
  4621. $type = '';
  4622. $plugin = $module;
  4623. } else {
  4624. $type = substr($module, 0, $dividerpos + 1);
  4625. $plugin = substr($module, $dividerpos + 1);
  4626. }
  4627. if ($module == 'local') {
  4628. $locations[] = $CFG->dirroot . '/local/lang/';
  4629. } if (!empty($rules[$type])) {
  4630. foreach ($rules[$type] as $location) {
  4631. $locations[] = $CFG->dirroot . "/$location/$plugin/lang/";
  4632. }
  4633. }
  4634. }
  4635. /// First check all the normal locations for the string in the current language
  4636. $resultstring = '';
  4637. foreach ($locations as $location) {
  4638. $locallangfile = $location.$lang.'_local'.'/'.$module.'.php'; //first, see if there's a local file
  4639. if (file_exists($locallangfile)) {
  4640. if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
  4641. if (eval($result) === FALSE) {
  4642. trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
  4643. }
  4644. return $resultstring;
  4645. }
  4646. }
  4647. //if local directory not found, or particular string does not exist in local direcotry
  4648. $langfile = $location.$lang.'/'.$module.'.php';
  4649. if (file_exists($langfile)) {
  4650. if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
  4651. if (eval($result) === FALSE) {
  4652. trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
  4653. }
  4654. return $resultstring;
  4655. }
  4656. }
  4657. }
  4658. /// If the preferred language was English (utf8) we can abort now
  4659. /// saving some checks beacuse it's the only "root" lang
  4660. if ($lang == 'en_utf8') {
  4661. return '[['. $identifier .']]';
  4662. }
  4663. /// Is a parent language defined? If so, try to find this string in a parent language file
  4664. foreach ($locations as $location) {
  4665. $langfile = $location.$lang.'/'.$filetocheck;
  4666. if (file_exists($langfile)) {
  4667. if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
  4668. if (eval($result) === FALSE) {
  4669. trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
  4670. }
  4671. if (!empty($parentlang)) { // found it!
  4672. //first, see if there's a local file for parent
  4673. $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
  4674. if (file_exists($locallangfile)) {
  4675. if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
  4676. if (eval($result) === FALSE) {
  4677. trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
  4678. }
  4679. return $resultstring;
  4680. }
  4681. }
  4682. //if local directory not found, or particular string does not exist in local direcotry
  4683. $langfile = $location.$parentlang.'/'.$module.'.php';
  4684. if (file_exists($langfile)) {
  4685. if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
  4686. eval($result);
  4687. return $resultstring;
  4688. }
  4689. }
  4690. }
  4691. }
  4692. }
  4693. }
  4694. /// Our only remaining option is to try English
  4695. foreach ($locations as $location) {
  4696. $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
  4697. if (file_exists($locallangfile)) {
  4698. if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
  4699. eval($result);
  4700. return $resultstring;
  4701. }
  4702. }
  4703. //if local_en not found, or string not found in local_en
  4704. $langfile = $location.$defaultlang.'/'.$module.'.php';
  4705. if (file_exists($langfile)) {
  4706. if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
  4707. eval($result);
  4708. return $resultstring;
  4709. }
  4710. }
  4711. }
  4712. /// And, because under 1.6 en is defined as en_utf8 child, me must try
  4713. /// if it hasn't been queried before.
  4714. if ($defaultlang == 'en') {
  4715. $defaultlang = 'en_utf8';
  4716. foreach ($locations as $location) {
  4717. $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
  4718. if (file_exists($locallangfile)) {
  4719. if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
  4720. eval($result);
  4721. return $resultstring;
  4722. }
  4723. }
  4724. //if local_en not found, or string not found in local_en
  4725. $langfile = $location.$defaultlang.'/'.$module.'.php';
  4726. if (file_exists($langfile)) {
  4727. if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
  4728. eval($result);
  4729. return $resultstring;
  4730. }
  4731. }
  4732. }
  4733. }
  4734. return '[['.$identifier.']]'; // Last resort
  4735. }
  4736. /**
  4737. * This function is only used from {@link get_string()}.
  4738. *
  4739. * @internal Only used from get_string, not meant to be public API
  4740. * @param string $identifier ?
  4741. * @param string $langfile ?
  4742. * @param string $destination ?
  4743. * @return string|false ?
  4744. * @staticvar array $strings Localized strings
  4745. * @access private
  4746. * @todo Finish documenting this function.
  4747. */
  4748. function get_string_from_file($identifier, $langfile, $destination) {
  4749. static $strings; // Keep the strings cached in memory.
  4750. if (empty($strings[$langfile])) {
  4751. $string = array();
  4752. include ($langfile);
  4753. $strings[$langfile] = $string;
  4754. } else {
  4755. $string = &$strings[$langfile];
  4756. }
  4757. if (!isset ($string[$identifier])) {
  4758. return false;
  4759. }
  4760. return $destination .'= sprintf("'. $string[$identifier] .'");';
  4761. }
  4762. /**
  4763. * Converts an array of strings to their localized value.
  4764. *
  4765. * @param array $array An array of strings
  4766. * @param string $module The language module that these strings can be found in.
  4767. * @return string
  4768. */
  4769. function get_strings($array, $module='') {
  4770. $string = NULL;
  4771. foreach ($array as $item) {
  4772. $string->$item = get_string($item, $module);
  4773. }
  4774. return $string;
  4775. }
  4776. /**
  4777. * Returns a list of language codes and their full names
  4778. * hides the _local files from everyone.
  4779. * @param bool refreshcache force refreshing of lang cache
  4780. * @param bool returnall ignore langlist, return all languages available
  4781. * @return array An associative array with contents in the form of LanguageCode => LanguageName
  4782. */
  4783. function get_list_of_languages($refreshcache=false, $returnall=false) {
  4784. global $CFG;
  4785. $languages = array();
  4786. $filetocheck = 'langconfig.php';
  4787. if (!$refreshcache && !$returnall && !empty($CFG->langcache) && file_exists($CFG->dataroot .'/cache/languages')) {
  4788. /// read available langs from cache
  4789. $lines = file($CFG->dataroot .'/cache/languages');
  4790. foreach ($lines as $line) {
  4791. $line = trim($line);
  4792. if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
  4793. $languages[$matches[1]] = $matches[2];
  4794. }
  4795. }
  4796. unset($lines); unset($line); unset($matches);
  4797. return $languages;
  4798. }
  4799. if (!$returnall && !empty($CFG->langlist)) {
  4800. /// return only languages allowed in langlist admin setting
  4801. $langlist = explode(',', $CFG->langlist);
  4802. // fix short lang names first - non existing langs are skipped anyway...
  4803. foreach ($langlist as $lang) {
  4804. if (strpos($lang, '_utf8') === false) {
  4805. $langlist[] = $lang.'_utf8';
  4806. }
  4807. }
  4808. // find existing langs from langlist
  4809. foreach ($langlist as $lang) {
  4810. $lang = trim($lang); //Just trim spaces to be a bit more permissive
  4811. if (strstr($lang, '_local')!==false) {
  4812. continue;
  4813. }
  4814. if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
  4815. $shortlang = substr($lang, 0, -5);
  4816. } else {
  4817. $shortlang = $lang;
  4818. }
  4819. /// Search under dirroot/lang
  4820. if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
  4821. include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
  4822. if (!empty($string['thislanguage'])) {
  4823. $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
  4824. }
  4825. unset($string);
  4826. }
  4827. /// And moodledata/lang
  4828. if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
  4829. include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
  4830. if (!empty($string['thislanguage'])) {
  4831. $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
  4832. }
  4833. unset($string);
  4834. }
  4835. }
  4836. } else {
  4837. /// return all languages available in system
  4838. /// Fetch langs from moodle/lang directory
  4839. $langdirs = get_list_of_plugins('lang');
  4840. /// Fetch langs from moodledata/lang directory
  4841. $langdirs2 = get_list_of_plugins('lang', '', $CFG->dataroot);
  4842. /// Merge both lists of langs
  4843. $langdirs = array_merge($langdirs, $langdirs2);
  4844. /// Sort all
  4845. asort($langdirs);
  4846. /// Get some info from each lang (first from moodledata, then from moodle)
  4847. foreach ($langdirs as $lang) {
  4848. if (strstr($lang, '_local')!==false) {
  4849. continue;
  4850. }
  4851. if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
  4852. $shortlang = substr($lang, 0, -5);
  4853. } else {
  4854. $shortlang = $lang;
  4855. }
  4856. /// Search under moodledata/lang
  4857. if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
  4858. include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
  4859. if (!empty($string['thislanguage'])) {
  4860. $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
  4861. }
  4862. unset($string);
  4863. }
  4864. /// And dirroot/lang
  4865. if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
  4866. include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
  4867. if (!empty($string['thislanguage'])) {
  4868. $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
  4869. }
  4870. unset($string);
  4871. }
  4872. }
  4873. }
  4874. if ($refreshcache && !empty($CFG->langcache)) {
  4875. if ($returnall) {
  4876. // we have a list of all langs only, just delete old cache
  4877. @unlink($CFG->dataroot.'/cache/languages');
  4878. } else {
  4879. // store the list of allowed languages
  4880. if ($file = fopen($CFG->dataroot .'/cache/languages', 'w')) {
  4881. foreach ($languages as $key => $value) {
  4882. fwrite($file, "$key $value\n");
  4883. }
  4884. fclose($file);
  4885. }
  4886. }
  4887. }
  4888. return $languages;
  4889. }
  4890. /**
  4891. * Returns a list of charset codes. It's hardcoded, so they should be added manually
  4892. * (cheking that such charset is supported by the texlib library!)
  4893. *
  4894. * @return array And associative array with contents in the form of charset => charset
  4895. */
  4896. function get_list_of_charsets() {
  4897. $charsets = array(
  4898. 'EUC-JP' => 'EUC-JP',
  4899. 'ISO-2022-JP'=> 'ISO-2022-JP',
  4900. 'ISO-8859-1' => 'ISO-8859-1',
  4901. 'SHIFT-JIS' => 'SHIFT-JIS',
  4902. 'GB2312' => 'GB2312',
  4903. 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
  4904. 'UTF-8' => 'UTF-8');
  4905. asort($charsets);
  4906. return $charsets;
  4907. }
  4908. /**
  4909. * For internal use only.
  4910. * @return array with two elements, the path to use and the name of the lang.
  4911. */
  4912. function get_list_of_countries_language() {
  4913. global $CFG;
  4914. $lang = current_language();
  4915. if (is_readable($CFG->dataroot.'/lang/'. $lang .'/countries.php')) {
  4916. return array($CFG->dataroot, $lang);
  4917. }
  4918. if (is_readable($CFG->dirroot .'/lang/'. $lang .'/countries.php')) {
  4919. return array($CFG->dirroot , $lang);
  4920. }
  4921. if ($lang == 'en_utf8') {
  4922. return;
  4923. }
  4924. $parentlang = get_string('parentlanguage');
  4925. if (substr($parentlang, 0, 1) != '[') {
  4926. if (is_readable($CFG->dataroot.'/lang/'. $parentlang .'/countries.php')) {
  4927. return array($CFG->dataroot, $parentlang);
  4928. }
  4929. if (is_readable($CFG->dirroot .'/lang/'. $parentlang .'/countries.php')) {
  4930. return array($CFG->dirroot , $parentlang);
  4931. }
  4932. if ($parentlang == 'en_utf8') {
  4933. return;
  4934. }
  4935. }
  4936. if (is_readable($CFG->dataroot.'/lang/en_utf8/countries.php')) {
  4937. return array($CFG->dataroot, 'en_utf8');
  4938. }
  4939. if (is_readable($CFG->dirroot .'/lang/en_utf8/countries.php')) {
  4940. return array($CFG->dirroot , 'en_utf8');
  4941. }
  4942. return array(null, null);
  4943. }
  4944. /**
  4945. * Returns a list of country names in the current language
  4946. *
  4947. * @uses $CFG
  4948. * @uses $USER
  4949. * @return array
  4950. */
  4951. function get_list_of_countries() {
  4952. global $CFG;
  4953. list($path, $lang) = get_list_of_countries_language();
  4954. if (empty($path)) {
  4955. print_error('countriesphpempty', '', '', $lang);
  4956. }
  4957. // Load all the strings into $string.
  4958. include($path . '/lang/' . $lang . '/countries.php');
  4959. // See if there are local overrides to countries.php.
  4960. // If so, override those elements of $string.
  4961. if (is_readable($CFG->dirroot .'/lang/' . $lang . '_local/countries.php')) {
  4962. include($CFG->dirroot .'/lang/' . $lang . '_local/countries.php');
  4963. }
  4964. if (is_readable($CFG->dataroot.'/lang/' . $lang . '_local/countries.php')) {
  4965. include($CFG->dataroot.'/lang/' . $lang . '_local/countries.php');
  4966. }
  4967. if (empty($string)) {
  4968. print_error('countriesphpempty', '', '', $lang);
  4969. }
  4970. uasort($string, 'strcoll');
  4971. return $string;
  4972. }
  4973. /**
  4974. * Returns a list of valid and compatible themes
  4975. *
  4976. * @uses $CFG
  4977. * @return array
  4978. */
  4979. function get_list_of_themes() {
  4980. global $CFG;
  4981. $themes = array();
  4982. if (!empty($CFG->themelist)) { // use admin's list of themes
  4983. $themelist = explode(',', $CFG->themelist);
  4984. } else {
  4985. $themelist = get_list_of_plugins("theme");
  4986. }
  4987. foreach ($themelist as $key => $theme) {
  4988. if (!file_exists("$CFG->themedir/$theme/config.php")) { // bad folder
  4989. continue;
  4990. }
  4991. $THEME = new object(); // Note this is not the global one!! :-)
  4992. include("$CFG->themedir/$theme/config.php");
  4993. if (!isset($THEME->sheets)) { // Not a valid 1.5 theme
  4994. continue;
  4995. }
  4996. $themes[$theme] = $theme;
  4997. }
  4998. asort($themes);
  4999. return $themes;
  5000. }
  5001. /**
  5002. * Returns a list of picture names in the current or specified language
  5003. *
  5004. * @uses $CFG
  5005. * @return array
  5006. */
  5007. function get_list_of_pixnames($lang = '') {
  5008. global $CFG;
  5009. if (empty($lang)) {
  5010. $lang = current_language();
  5011. }
  5012. $string = array();
  5013. $path = $CFG->dirroot .'/lang/en_utf8/pix.php'; // always exists
  5014. if (file_exists($CFG->dataroot .'/lang/'. $lang .'_local/pix.php')) {
  5015. $path = $CFG->dataroot .'/lang/'. $lang .'_local/pix.php';
  5016. } else if (file_exists($CFG->dirroot .'/lang/'. $lang .'/pix.php')) {
  5017. $path = $CFG->dirroot .'/lang/'. $lang .'/pix.php';
  5018. } else if (file_exists($CFG->dataroot .'/lang/'. $lang .'/pix.php')) {
  5019. $path = $CFG->dataroot .'/lang/'. $lang .'/pix.php';
  5020. } else if ($parentlang = get_string('parentlanguage') and $parentlang != '[[parentlanguage]]') {
  5021. return get_list_of_pixnames($parentlang); //return pixnames from parent language instead
  5022. }
  5023. include($path);
  5024. return $string;
  5025. }
  5026. /**
  5027. * Returns a list of timezones in the current language
  5028. *
  5029. * @uses $CFG
  5030. * @return array
  5031. */
  5032. function get_list_of_timezones() {
  5033. global $CFG;
  5034. static $timezones;
  5035. if (!empty($timezones)) { // This function has been called recently
  5036. return $timezones;
  5037. }
  5038. $timezones = array();
  5039. if ($rawtimezones = get_records_sql('SELECT MAX(id), name FROM '.$CFG->prefix.'timezone GROUP BY name')) {
  5040. foreach($rawtimezones as $timezone) {
  5041. if (!empty($timezone->name)) {
  5042. $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
  5043. if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
  5044. $timezones[$timezone->name] = $timezone->name;
  5045. }
  5046. }
  5047. }
  5048. }
  5049. asort($timezones);
  5050. for ($i = -13; $i <= 13; $i += .5) {
  5051. $tzstring = 'UTC';
  5052. if ($i < 0) {
  5053. $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
  5054. } else if ($i > 0) {
  5055. $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
  5056. } else {
  5057. $timezones[sprintf("%.1f", $i)] = $tzstring;
  5058. }
  5059. }
  5060. return $timezones;
  5061. }
  5062. /**
  5063. * Returns a list of currencies in the current language
  5064. *
  5065. * @uses $CFG
  5066. * @uses $USER
  5067. * @return array
  5068. */
  5069. function get_list_of_currencies() {
  5070. global $CFG, $USER;
  5071. $lang = current_language();
  5072. if (!file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
  5073. if ($parentlang = get_string('parentlanguage')) {
  5074. if (file_exists($CFG->dataroot .'/lang/'. $parentlang .'/currencies.php')) {
  5075. $lang = $parentlang;
  5076. } else {
  5077. $lang = 'en_utf8'; // currencies.php must exist in this pack
  5078. }
  5079. } else {
  5080. $lang = 'en_utf8'; // currencies.php must exist in this pack
  5081. }
  5082. }
  5083. if (file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
  5084. include_once($CFG->dataroot .'/lang/'. $lang .'/currencies.php');
  5085. } else { //if en_utf8 is not installed in dataroot
  5086. include_once($CFG->dirroot .'/lang/'. $lang .'/currencies.php');
  5087. }
  5088. if (!empty($string)) {
  5089. asort($string);
  5090. }
  5091. return $string;
  5092. }
  5093. /// ENCRYPTION ////////////////////////////////////////////////
  5094. /**
  5095. * rc4encrypt
  5096. *
  5097. * @param string $data Data to encrypt.
  5098. * @param bool $usesecurekey Lets us know if we are using the old or new password.
  5099. * @return string The now encrypted data.
  5100. */
  5101. function rc4encrypt($data, $usesecurekey = false) {
  5102. if (!$usesecurekey) {
  5103. $passwordkey = 'nfgjeingjk';
  5104. } else {
  5105. $passwordkey = get_site_identifier();
  5106. }
  5107. return endecrypt($passwordkey, $data, '');
  5108. }
  5109. /**
  5110. * rc4decrypt
  5111. *
  5112. * @param string $data Data to decrypt.
  5113. * @param bool $usesecurekey Lets us know if we are using the old or new password.
  5114. * @return string The now decrypted data.
  5115. */
  5116. function rc4decrypt($data, $usesecurekey = false) {
  5117. if (!$usesecurekey) {
  5118. $passwordkey = 'nfgjeingjk';
  5119. } else {
  5120. $passwordkey = get_site_identifier();
  5121. }
  5122. return endecrypt($passwordkey, $data, 'de');
  5123. }
  5124. /**
  5125. * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
  5126. *
  5127. * @param string $pwd ?
  5128. * @param string $data ?
  5129. * @param string $case ?
  5130. * @return string
  5131. * @todo Finish documenting this function
  5132. */
  5133. function endecrypt ($pwd, $data, $case) {
  5134. if ($case == 'de') {
  5135. $data = urldecode($data);
  5136. }
  5137. $key[] = '';
  5138. $box[] = '';
  5139. $temp_swap = '';
  5140. $pwd_length = 0;
  5141. $pwd_length = strlen($pwd);
  5142. for ($i = 0; $i <= 255; $i++) {
  5143. $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
  5144. $box[$i] = $i;
  5145. }
  5146. $x = 0;
  5147. for ($i = 0; $i <= 255; $i++) {
  5148. $x = ($x + $box[$i] + $key[$i]) % 256;
  5149. $temp_swap = $box[$i];
  5150. $box[$i] = $box[$x];
  5151. $box[$x] = $temp_swap;
  5152. }
  5153. $temp = '';
  5154. $k = '';
  5155. $cipherby = '';
  5156. $cipher = '';
  5157. $a = 0;
  5158. $j = 0;
  5159. for ($i = 0; $i < strlen($data); $i++) {
  5160. $a = ($a + 1) % 256;
  5161. $j = ($j + $box[$a]) % 256;
  5162. $temp = $box[$a];
  5163. $box[$a] = $box[$j];
  5164. $box[$j] = $temp;
  5165. $k = $box[(($box[$a] + $box[$j]) % 256)];
  5166. $cipherby = ord(substr($data, $i, 1)) ^ $k;
  5167. $cipher .= chr($cipherby);
  5168. }
  5169. if ($case == 'de') {
  5170. $cipher = urldecode(urlencode($cipher));
  5171. } else {
  5172. $cipher = urlencode($cipher);
  5173. }
  5174. return $cipher;
  5175. }
  5176. /// CALENDAR MANAGEMENT ////////////////////////////////////////////////////////////////
  5177. /**
  5178. * Call this function to add an event to the calendar table
  5179. * and to call any calendar plugins
  5180. *
  5181. * @uses $CFG
  5182. * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field. The object event should include the following:
  5183. * <ul>
  5184. * <li><b>$event->name</b> - Name for the event
  5185. * <li><b>$event->description</b> - Description of the event (defaults to '')
  5186. * <li><b>$event->format</b> - Format for the description (using formatting types defined at the top of weblib.php)
  5187. * <li><b>$event->courseid</b> - The id of the course this event belongs to (0 = all courses)
  5188. * <li><b>$event->groupid</b> - The id of the group this event belongs to (0 = no group)
  5189. * <li><b>$event->userid</b> - The id of the user this event belongs to (0 = no user)
  5190. * <li><b>$event->modulename</b> - Name of the module that creates this event
  5191. * <li><b>$event->instance</b> - Instance of the module that owns this event
  5192. * <li><b>$event->eventtype</b> - The type info together with the module info could
  5193. * be used by calendar plugins to decide how to display event
  5194. * <li><b>$event->timestart</b>- Timestamp for start of event
  5195. * <li><b>$event->timeduration</b> - Duration (defaults to zero)
  5196. * <li><b>$event->visible</b> - 0 if the event should be hidden (e.g. because the activity that created it is hidden)
  5197. * </ul>
  5198. * @return int The id number of the resulting record
  5199. */
  5200. function add_event($event) {
  5201. global $CFG;
  5202. $event->timemodified = time();
  5203. if (!$event->id = insert_record('event', $event)) {
  5204. return false;
  5205. }
  5206. if (!empty($CFG->calendar)) { // call the add_event function of the selected calendar
  5207. if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
  5208. include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
  5209. $calendar_add_event = $CFG->calendar.'_add_event';
  5210. if (function_exists($calendar_add_event)) {
  5211. $calendar_add_event($event);
  5212. }
  5213. }
  5214. }
  5215. return $event->id;
  5216. }
  5217. /**
  5218. * Call this function to update an event in the calendar table
  5219. * the event will be identified by the id field of the $event object.
  5220. *
  5221. * @uses $CFG
  5222. * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
  5223. * @return bool
  5224. */
  5225. function update_event($event) {
  5226. global $CFG;
  5227. $event->timemodified = time();
  5228. if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
  5229. if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
  5230. include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
  5231. $calendar_update_event = $CFG->calendar.'_update_event';
  5232. if (function_exists($calendar_update_event)) {
  5233. $calendar_update_event($event);
  5234. }
  5235. }
  5236. }
  5237. return update_record('event', $event);
  5238. }
  5239. /**
  5240. * Call this function to delete the event with id $id from calendar table.
  5241. *
  5242. * @uses $CFG
  5243. * @param int $id The id of an event from the 'calendar' table.
  5244. * @return array An associative array with the results from the SQL call.
  5245. * @todo Verify return type
  5246. */
  5247. function delete_event($id) {
  5248. global $CFG;
  5249. if (!empty($CFG->calendar)) { // call the delete_event function of the selected calendar
  5250. if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
  5251. include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
  5252. $calendar_delete_event = $CFG->calendar.'_delete_event';
  5253. if (function_exists($calendar_delete_event)) {
  5254. $calendar_delete_event($id);
  5255. }
  5256. }
  5257. }
  5258. return delete_records('event', 'id', $id);
  5259. }
  5260. /**
  5261. * Call this function to hide an event in the calendar table
  5262. * the event will be identified by the id field of the $event object.
  5263. *
  5264. * @uses $CFG
  5265. * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
  5266. * @return array An associative array with the results from the SQL call.
  5267. * @todo Verify return type
  5268. */
  5269. function hide_event($event) {
  5270. global $CFG;
  5271. if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
  5272. if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
  5273. include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
  5274. $calendar_hide_event = $CFG->calendar.'_hide_event';
  5275. if (function_exists($calendar_hide_event)) {
  5276. $calendar_hide_event($event);
  5277. }
  5278. }
  5279. }
  5280. return set_field('event', 'visible', 0, 'id', $event->id);
  5281. }
  5282. /**
  5283. * Call this function to unhide an event in the calendar table
  5284. * the event will be identified by the id field of the $event object.
  5285. *
  5286. * @uses $CFG
  5287. * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
  5288. * @return array An associative array with the results from the SQL call.
  5289. * @todo Verify return type
  5290. */
  5291. function show_event($event) {
  5292. global $CFG;
  5293. if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
  5294. if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
  5295. include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
  5296. $calendar_show_event = $CFG->calendar.'_show_event';
  5297. if (function_exists($calendar_show_event)) {
  5298. $calendar_show_event($event);
  5299. }
  5300. }
  5301. }
  5302. return set_field('event', 'visible', 1, 'id', $event->id);
  5303. }
  5304. /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
  5305. /**
  5306. * Lists plugin directories within some directory
  5307. *
  5308. * @uses $CFG
  5309. * @param string $plugin dir under we'll look for plugins (defaults to 'mod')
  5310. * @param string $exclude dir name to exclude from the list (defaults to none)
  5311. * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
  5312. * @return array of plugins found under the requested parameters
  5313. */
  5314. function get_list_of_plugins($plugin='mod', $exclude='', $basedir='') {
  5315. global $CFG;
  5316. $plugins = array();
  5317. if (empty($basedir)) {
  5318. # This switch allows us to use the appropiate theme directory - and potentialy alternatives for other plugins
  5319. switch ($plugin) {
  5320. case "theme":
  5321. $basedir = $CFG->themedir;
  5322. break;
  5323. default:
  5324. $basedir = $CFG->dirroot .'/'. $plugin;
  5325. }
  5326. } else {
  5327. $basedir = $basedir .'/'. $plugin;
  5328. }
  5329. if (file_exists($basedir) && filetype($basedir) == 'dir') {
  5330. $dirhandle = opendir($basedir);
  5331. while (false !== ($dir = readdir($dirhandle))) {
  5332. $firstchar = substr($dir, 0, 1);
  5333. if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == 'simpletest' or $dir == $exclude) {
  5334. continue;
  5335. }
  5336. if (filetype($basedir .'/'. $dir) != 'dir') {
  5337. continue;
  5338. }
  5339. $plugins[] = $dir;
  5340. }
  5341. closedir($dirhandle);
  5342. }
  5343. if ($plugins) {
  5344. asort($plugins);
  5345. }
  5346. return $plugins;
  5347. }
  5348. /**
  5349. * Returns true if the current version of PHP is greater that the specified one.
  5350. *
  5351. * @param string $version The version of php being tested.
  5352. * @return bool
  5353. */
  5354. function check_php_version($version='4.1.0') {
  5355. return (version_compare(phpversion(), $version) >= 0);
  5356. }
  5357. /**
  5358. * Checks to see if is the browser operating system matches the specified
  5359. * brand.
  5360. *
  5361. * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
  5362. *
  5363. * @uses $_SERVER
  5364. * @param string $brand The operating system identifier being tested
  5365. * @return bool true if the given brand below to the detected operating system
  5366. */
  5367. function check_browser_operating_system($brand) {
  5368. if (empty($_SERVER['HTTP_USER_AGENT'])) {
  5369. return false;
  5370. }
  5371. if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
  5372. return true;
  5373. }
  5374. return false;
  5375. }
  5376. /**
  5377. * Checks to see if is a browser matches the specified
  5378. * brand and is equal or better version.
  5379. *
  5380. * @uses $_SERVER
  5381. * @param string $brand The browser identifier being tested
  5382. * @param int $version The version of the browser
  5383. * @return bool true if the given version is below that of the detected browser
  5384. */
  5385. function check_browser_version($brand='MSIE', $version=5.5) {
  5386. if (empty($_SERVER['HTTP_USER_AGENT'])) {
  5387. return false;
  5388. }
  5389. $agent = $_SERVER['HTTP_USER_AGENT'];
  5390. switch ($brand) {
  5391. case 'Camino': /// Mozilla Firefox browsers
  5392. if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
  5393. if (version_compare($match[1], $version) >= 0) {
  5394. return true;
  5395. }
  5396. }
  5397. break;
  5398. case 'Firefox': /// Mozilla Firefox browsers
  5399. if (preg_match("/Firefox\/([0-9\.]+)/i", $agent, $match)) {
  5400. if (version_compare($match[1], $version) >= 0) {
  5401. return true;
  5402. }
  5403. }
  5404. break;
  5405. case 'Gecko': /// Gecko based browsers
  5406. if (substr_count($agent, 'Camino')) {
  5407. // MacOS X Camino support
  5408. $version = 20041110;
  5409. }
  5410. // the proper string - Gecko/CCYYMMDD Vendor/Version
  5411. // Faster version and work-a-round No IDN problem.
  5412. if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
  5413. if ($match[1] > $version) {
  5414. return true;
  5415. }
  5416. }
  5417. break;
  5418. case 'MSIE': /// Internet Explorer
  5419. if (strpos($agent, 'Opera')) { // Reject Opera
  5420. return false;
  5421. }
  5422. $string = explode(';', $agent);
  5423. if (!isset($string[1])) {
  5424. return false;
  5425. }
  5426. $string = explode(' ', trim($string[1]));
  5427. if (!isset($string[0]) and !isset($string[1])) {
  5428. return false;
  5429. }
  5430. if ($string[0] == $brand and (float)$string[1] >= $version ) {
  5431. return true;
  5432. }
  5433. break;
  5434. case 'Opera': /// Opera
  5435. if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
  5436. if (version_compare($match[1], $version) >= 0) {
  5437. return true;
  5438. }
  5439. }
  5440. break;
  5441. case 'Chrome':
  5442. if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
  5443. if (version_compare($match[1], $version) >= 0) {
  5444. return true;
  5445. }
  5446. }
  5447. break;
  5448. case 'Safari': /// Safari
  5449. // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS
  5450. if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
  5451. return false;
  5452. } elseif (strpos($agent, 'Shiira')) { // Reject Shiira
  5453. return false;
  5454. } elseif (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
  5455. return false;
  5456. }
  5457. if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
  5458. // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
  5459. return false;
  5460. }
  5461. if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
  5462. if (version_compare($match[1], $version) >= 0) {
  5463. return true;
  5464. }
  5465. }
  5466. break;
  5467. }
  5468. return false;
  5469. }
  5470. /**
  5471. * Returns one or several CSS class names that match the user's browser. These can be put
  5472. * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
  5473. */
  5474. function get_browser_version_classes() {
  5475. $classes = '';
  5476. if (check_browser_version("MSIE", "0")) {
  5477. $classes .= 'ie ';
  5478. if (check_browser_version("MSIE", 8)) {
  5479. $classes .= 'ie8 ';
  5480. } elseif (check_browser_version("MSIE", 7)) {
  5481. $classes .= 'ie7 ';
  5482. } elseif (check_browser_version("MSIE", 6)) {
  5483. $classes .= 'ie6 ';
  5484. }
  5485. } elseif (check_browser_version("Firefox", 0) || check_browser_version("Gecko", 0) || check_browser_version("Camino", 0)) {
  5486. $classes .= 'gecko ';
  5487. if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
  5488. $classes .= "gecko{$matches[1]}{$matches[2]} ";
  5489. }
  5490. } elseif (check_browser_version("Safari", 0)) {
  5491. $classes .= 'safari ';
  5492. } elseif (check_browser_version("Opera", 0)) {
  5493. $classes .= 'opera ';
  5494. }
  5495. return $classes;
  5496. }
  5497. /**
  5498. * This function makes the return value of ini_get consistent if you are
  5499. * setting server directives through the .htaccess file in apache.
  5500. * Current behavior for value set from php.ini On = 1, Off = [blank]
  5501. * Current behavior for value set from .htaccess On = On, Off = Off
  5502. * Contributed by jdell @ unr.edu
  5503. *
  5504. * @param string $ini_get_arg ?
  5505. * @return bool
  5506. * @todo Finish documenting this function
  5507. */
  5508. function ini_get_bool($ini_get_arg) {
  5509. $temp = ini_get($ini_get_arg);
  5510. if ($temp == '1' or strtolower($temp) == 'on') {
  5511. return true;
  5512. }
  5513. return false;
  5514. }
  5515. /**
  5516. * Compatibility stub to provide backward compatibility
  5517. *
  5518. * Determines if the HTML editor is enabled.
  5519. * @deprecated Use {@link can_use_html_editor()} instead.
  5520. */
  5521. function can_use_richtext_editor() {
  5522. return can_use_html_editor();
  5523. }
  5524. /**
  5525. * Determines if the HTML editor is enabled.
  5526. *
  5527. * This depends on site and user
  5528. * settings, as well as the current browser being used.
  5529. *
  5530. * @return string|false Returns false if editor is not being used, otherwise
  5531. * returns 'MSIE' or 'Gecko'.
  5532. */
  5533. function can_use_html_editor() {
  5534. global $USER, $CFG;
  5535. if (!empty($USER->htmleditor) and !empty($CFG->htmleditor)) {
  5536. if (check_browser_version('MSIE', 5.5)) {
  5537. return 'MSIE';
  5538. } else if (check_browser_version('Gecko', 20030516)) {
  5539. return 'Gecko';
  5540. } else if (check_browser_version('Safari', 531)) {
  5541. return 'AppleWebKit';
  5542. }
  5543. }
  5544. return false;
  5545. }
  5546. /**
  5547. * Hack to find out the GD version by parsing phpinfo output
  5548. *
  5549. * @return int GD version (1, 2, or 0)
  5550. */
  5551. function check_gd_version() {
  5552. $gdversion = 0;
  5553. if (function_exists('gd_info')){
  5554. $gd_info = gd_info();
  5555. if (substr_count($gd_info['GD Version'], '2.')) {
  5556. $gdversion = 2;
  5557. } else if (substr_count($gd_info['GD Version'], '1.')) {
  5558. $gdversion = 1;
  5559. }
  5560. } else {
  5561. ob_start();
  5562. phpinfo(INFO_MODULES);
  5563. $phpinfo = ob_get_contents();
  5564. ob_end_clean();
  5565. $phpinfo = explode("\n", $phpinfo);
  5566. foreach ($phpinfo as $text) {
  5567. $parts = explode('</td>', $text);
  5568. foreach ($parts as $key => $val) {
  5569. $parts[$key] = trim(strip_tags($val));
  5570. }
  5571. if ($parts[0] == 'GD Version') {
  5572. if (substr_count($parts[1], '2.0')) {
  5573. $parts[1] = '2.0';
  5574. }
  5575. $gdversion = intval($parts[1]);
  5576. }
  5577. }
  5578. }
  5579. return $gdversion; // 1, 2 or 0
  5580. }
  5581. /**
  5582. * Determine if moodle installation requires update
  5583. *
  5584. * Checks version numbers of main code and all modules to see
  5585. * if there are any mismatches
  5586. *
  5587. * @uses $CFG
  5588. * @return bool
  5589. */
  5590. function moodle_needs_upgrading() {
  5591. global $CFG;
  5592. $version = null;
  5593. include_once($CFG->dirroot .'/version.php'); # defines $version and upgrades
  5594. if ($CFG->version) {
  5595. if ($version > $CFG->version) {
  5596. return true;
  5597. }
  5598. if ($mods = get_list_of_plugins('mod')) {
  5599. foreach ($mods as $mod) {
  5600. $fullmod = $CFG->dirroot .'/mod/'. $mod;
  5601. $module = new object();
  5602. if (!is_readable($fullmod .'/version.php')) {
  5603. notify('Module "'. $mod .'" is not readable - check permissions');
  5604. continue;
  5605. }
  5606. include_once($fullmod .'/version.php'); # defines $module with version etc
  5607. if ($currmodule = get_record('modules', 'name', $mod)) {
  5608. if ($module->version > $currmodule->version) {
  5609. return true;
  5610. }
  5611. }
  5612. }
  5613. }
  5614. } else {
  5615. return true;
  5616. }
  5617. return false;
  5618. }
  5619. /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
  5620. /**
  5621. * Notify admin users or admin user of any failed logins (since last notification).
  5622. *
  5623. * Note that this function must be only executed from the cron script
  5624. * It uses the cache_flags system to store temporary records, deleting them
  5625. * by name before finishing
  5626. *
  5627. * @uses $CFG
  5628. * @uses $db
  5629. * @uses HOURSECS
  5630. */
  5631. function notify_login_failures() {
  5632. global $CFG, $db;
  5633. switch ($CFG->notifyloginfailures) {
  5634. case 'mainadmin' :
  5635. $recip = array(get_admin());
  5636. break;
  5637. case 'alladmins':
  5638. $recip = get_admins();
  5639. break;
  5640. }
  5641. if (empty($CFG->lastnotifyfailure)) {
  5642. $CFG->lastnotifyfailure=0;
  5643. }
  5644. // we need to deal with the threshold stuff first.
  5645. if (empty($CFG->notifyloginthreshold)) {
  5646. $CFG->notifyloginthreshold = 10; // default to something sensible.
  5647. }
  5648. /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
  5649. /// and insert them into the cache_flags temp table
  5650. $iprs = get_recordset_sql("SELECT ip, count(*)
  5651. FROM {$CFG->prefix}log
  5652. WHERE module = 'login'
  5653. AND action = 'error'
  5654. AND time > $CFG->lastnotifyfailure
  5655. GROUP BY ip
  5656. HAVING count(*) >= $CFG->notifyloginthreshold");
  5657. while ($iprec = rs_fetch_next_record($iprs)) {
  5658. if (!empty($iprec->ip)) {
  5659. set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
  5660. }
  5661. }
  5662. rs_close($iprs);
  5663. /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
  5664. /// and insert them into the cache_flags temp table
  5665. $infors = get_recordset_sql("SELECT info, count(*)
  5666. FROM {$CFG->prefix}log
  5667. WHERE module = 'login'
  5668. AND action = 'error'
  5669. AND time > $CFG->lastnotifyfailure
  5670. GROUP BY info
  5671. HAVING count(*) >= $CFG->notifyloginthreshold");
  5672. while ($inforec = rs_fetch_next_record($infors)) {
  5673. if (!empty($inforec->info)) {
  5674. set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
  5675. }
  5676. }
  5677. rs_close($infors);
  5678. /// Now, select all the login error logged records belonging to the ips and infos
  5679. /// since lastnotifyfailure, that we have stored in the cache_flags table
  5680. $logsrs = get_recordset_sql("SELECT l.*, u.firstname, u.lastname
  5681. FROM {$CFG->prefix}log l
  5682. JOIN {$CFG->prefix}cache_flags cf ON (l.ip = cf.name)
  5683. LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
  5684. WHERE l.module = 'login'
  5685. AND l.action = 'error'
  5686. AND l.time > $CFG->lastnotifyfailure
  5687. AND cf.flagtype = 'login_failure_by_ip'
  5688. UNION ALL
  5689. SELECT l.*, u.firstname, u.lastname
  5690. FROM {$CFG->prefix}log l
  5691. JOIN {$CFG->prefix}cache_flags cf ON (l.info = cf.name)
  5692. LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
  5693. WHERE l.module = 'login'
  5694. AND l.action = 'error'
  5695. AND l.time > $CFG->lastnotifyfailure
  5696. AND cf.flagtype = 'login_failure_by_info'
  5697. ORDER BY time DESC");
  5698. /// Init some variables
  5699. $count = 0;
  5700. $messages = '';
  5701. /// Iterate over the logs recordset
  5702. while ($log = rs_fetch_next_record($logsrs)) {
  5703. $log->time = userdate($log->time);
  5704. $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
  5705. $count++;
  5706. }
  5707. rs_close($logsrs);
  5708. /// If we haven't run in the last hour and
  5709. /// we have something useful to report and we
  5710. /// are actually supposed to be reporting to somebody
  5711. if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
  5712. $site = get_site();
  5713. $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
  5714. /// Calculate the complete body of notification (start + messages + end)
  5715. $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
  5716. (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
  5717. $messages .
  5718. "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
  5719. /// For each destination, send mail
  5720. mtrace('Emailing admins about '. $count .' failed login attempts');
  5721. foreach ($recip as $admin) {
  5722. email_to_user($admin,get_admin(), $subject, $body);
  5723. }
  5724. /// Update lastnotifyfailure with current time
  5725. set_config('lastnotifyfailure', time());
  5726. }
  5727. /// Finally, delete all the temp records we have created in cache_flags
  5728. delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
  5729. }
  5730. /**
  5731. * moodle_setlocale
  5732. *
  5733. * @uses $CFG
  5734. * @param string $locale ?
  5735. * @todo Finish documenting this function
  5736. */
  5737. function moodle_setlocale($locale='') {
  5738. global $CFG;
  5739. static $currentlocale = ''; // last locale caching
  5740. $oldlocale = $currentlocale;
  5741. /// Fetch the correct locale based on ostype
  5742. if($CFG->ostype == 'WINDOWS') {
  5743. $stringtofetch = 'localewin';
  5744. } else {
  5745. $stringtofetch = 'locale';
  5746. }
  5747. /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
  5748. if (!empty($locale)) {
  5749. $currentlocale = $locale;
  5750. } else if (!empty($CFG->locale)) { // override locale for all language packs
  5751. $currentlocale = $CFG->locale;
  5752. } else {
  5753. $currentlocale = get_string($stringtofetch);
  5754. }
  5755. /// do nothing if locale already set up
  5756. if ($oldlocale == $currentlocale) {
  5757. return;
  5758. }
  5759. /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
  5760. /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
  5761. /// Some day, numeric, monetary and other categories should be set too, I think. :-/
  5762. /// Get current values
  5763. $monetary= setlocale (LC_MONETARY, 0);
  5764. $numeric = setlocale (LC_NUMERIC, 0);
  5765. $ctype = setlocale (LC_CTYPE, 0);
  5766. if ($CFG->ostype != 'WINDOWS') {
  5767. $messages= setlocale (LC_MESSAGES, 0);
  5768. }
  5769. /// Set locale to all
  5770. setlocale (LC_ALL, $currentlocale);
  5771. /// Set old values
  5772. setlocale (LC_MONETARY, $monetary);
  5773. setlocale (LC_NUMERIC, $numeric);
  5774. if ($CFG->ostype != 'WINDOWS') {
  5775. setlocale (LC_MESSAGES, $messages);
  5776. }
  5777. if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
  5778. setlocale (LC_CTYPE, $ctype);
  5779. }
  5780. }
  5781. /**
  5782. * Converts string to lowercase using most compatible function available.
  5783. *
  5784. * @param string $string The string to convert to all lowercase characters.
  5785. * @param string $encoding The encoding on the string.
  5786. * @return string
  5787. * @todo Add examples of calling this function with/without encoding types
  5788. * @deprecated Use textlib->strtolower($text) instead.
  5789. */
  5790. function moodle_strtolower ($string, $encoding='') {
  5791. //If not specified use utf8
  5792. if (empty($encoding)) {
  5793. $encoding = 'UTF-8';
  5794. }
  5795. //Use text services
  5796. $textlib = textlib_get_instance();
  5797. return $textlib->strtolower($string, $encoding);
  5798. }
  5799. /**
  5800. * Count words in a string.
  5801. *
  5802. * Words are defined as things between whitespace.
  5803. *
  5804. * @param string $string The text to be searched for words.
  5805. * @return int The count of words in the specified string
  5806. */
  5807. function count_words($string) {
  5808. $string = strip_tags($string);
  5809. return count(preg_split("/\w\b/", $string)) - 1;
  5810. }
  5811. /** Count letters in a string.
  5812. *
  5813. * Letters are defined as chars not in tags and different from whitespace.
  5814. *
  5815. * @param string $string The text to be searched for letters.
  5816. * @return int The count of letters in the specified text.
  5817. */
  5818. function count_letters($string) {
  5819. /// Loading the textlib singleton instance. We are going to need it.
  5820. $textlib = textlib_get_instance();
  5821. $string = strip_tags($string); // Tags are out now
  5822. $string = ereg_replace('[[:space:]]*','',$string); //Whitespace are out now
  5823. return $textlib->strlen($string);
  5824. }
  5825. /**
  5826. * Generate and return a random string of the specified length.
  5827. *
  5828. * @param int $length The length of the string to be created.
  5829. * @return string
  5830. */
  5831. function random_string ($length=15) {
  5832. $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  5833. $pool .= 'abcdefghijklmnopqrstuvwxyz';
  5834. $pool .= '0123456789';
  5835. $poollen = strlen($pool);
  5836. mt_srand ((double) microtime() * 1000000);
  5837. $string = '';
  5838. for ($i = 0; $i < $length; $i++) {
  5839. $string .= substr($pool, (mt_rand()%($poollen)), 1);
  5840. }
  5841. return $string;
  5842. }
  5843. /**
  5844. * Generate a complex random string (usefull for md5 salts)
  5845. *
  5846. * This function is based on the above {@link random_string()} however it uses a
  5847. * larger pool of characters and generates a string between 24 and 32 characters
  5848. *
  5849. * @param int $length Optional if set generates a string to exactly this length
  5850. * @return string
  5851. */
  5852. function complex_random_string($length=null) {
  5853. $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  5854. $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
  5855. $poollen = strlen($pool);
  5856. mt_srand ((double) microtime() * 1000000);
  5857. if ($length===null) {
  5858. $length = floor(rand(24,32));
  5859. }
  5860. $string = '';
  5861. for ($i = 0; $i < $length; $i++) {
  5862. $string .= $pool[(mt_rand()%$poollen)];
  5863. }
  5864. return $string;
  5865. }
  5866. /*
  5867. * Given some text (which may contain HTML) and an ideal length,
  5868. * this function truncates the text neatly on a word boundary if possible
  5869. * @param string $text - text to be shortened
  5870. * @param int $ideal - ideal string length
  5871. * @param boolean $exact if false, $text will not be cut mid-word
  5872. * @return string $truncate - shortened string
  5873. */
  5874. function shorten_text($text, $ideal=30, $exact = false) {
  5875. global $CFG;
  5876. $ending = '...';
  5877. // if the plain text is shorter than the maximum length, return the whole text
  5878. if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
  5879. return $text;
  5880. }
  5881. // Splits on HTML tags. Each open/close/empty tag will be the first thing
  5882. // and only tag in its 'line'
  5883. preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
  5884. $total_length = strlen($ending);
  5885. $truncate = '';
  5886. // This array stores information about open and close tags and their position
  5887. // in the truncated string. Each item in the array is an object with fields
  5888. // ->open (true if open), ->tag (tag name in lower case), and ->pos
  5889. // (byte position in truncated text)
  5890. $tagdetails = array();
  5891. foreach ($lines as $line_matchings) {
  5892. // if there is any html-tag in this line, handle it and add it (uncounted) to the output
  5893. if (!empty($line_matchings[1])) {
  5894. // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
  5895. if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
  5896. // do nothing
  5897. // if tag is a closing tag (f.e. </b>)
  5898. } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
  5899. // record closing tag
  5900. $tagdetails[] = (object)array('open'=>false,
  5901. 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
  5902. // if tag is an opening tag (f.e. <b>)
  5903. } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
  5904. // record opening tag
  5905. $tagdetails[] = (object)array('open'=>true,
  5906. 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
  5907. }
  5908. // add html-tag to $truncate'd text
  5909. $truncate .= $line_matchings[1];
  5910. }
  5911. // calculate the length of the plain text part of the line; handle entities as one character
  5912. $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
  5913. if ($total_length+$content_length > $ideal) {
  5914. // the number of characters which are left
  5915. $left = $ideal - $total_length;
  5916. $entities_length = 0;
  5917. // search for html entities
  5918. if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
  5919. // calculate the real length of all entities in the legal range
  5920. foreach ($entities[0] as $entity) {
  5921. if ($entity[1]+1-$entities_length <= $left) {
  5922. $left--;
  5923. $entities_length += strlen($entity[0]);
  5924. } else {
  5925. // no more characters left
  5926. break;
  5927. }
  5928. }
  5929. }
  5930. $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
  5931. // maximum lenght is reached, so get off the loop
  5932. break;
  5933. } else {
  5934. $truncate .= $line_matchings[2];
  5935. $total_length += $content_length;
  5936. }
  5937. // if the maximum length is reached, get off the loop
  5938. if($total_length >= $ideal) {
  5939. break;
  5940. }
  5941. }
  5942. // if the words shouldn't be cut in the middle...
  5943. if (!$exact) {
  5944. // ...search the last occurance of a space...
  5945. for ($k=strlen($truncate);$k>0;$k--) {
  5946. if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
  5947. if ($char == '.' or $char == ' ') {
  5948. $breakpos = $k+1;
  5949. break;
  5950. } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
  5951. $breakpos = $k; // can be truncated at any UTF-8
  5952. break; // character boundary.
  5953. }
  5954. }
  5955. }
  5956. if (isset($breakpos)) {
  5957. // ...and cut the text in this position
  5958. $truncate = substr($truncate, 0, $breakpos);
  5959. }
  5960. }
  5961. // add the defined ending to the text
  5962. $truncate .= $ending;
  5963. // Now calculate the list of open html tags based on the truncate position
  5964. $open_tags = array();
  5965. foreach ($tagdetails as $taginfo) {
  5966. if(isset($breakpos) && $taginfo->pos >= $breakpos) {
  5967. // Don't include tags after we made the break!
  5968. break;
  5969. }
  5970. if($taginfo->open) {
  5971. // add tag to the beginning of $open_tags list
  5972. array_unshift($open_tags, $taginfo->tag);
  5973. } else {
  5974. $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
  5975. if ($pos !== false) {
  5976. unset($open_tags[$pos]);
  5977. }
  5978. }
  5979. }
  5980. // close all unclosed html-tags
  5981. foreach ($open_tags as $tag) {
  5982. $truncate .= '</' . $tag . '>';
  5983. }
  5984. return $truncate;
  5985. }
  5986. /**
  5987. * Given dates in seconds, how many weeks is the date from startdate
  5988. * The first week is 1, the second 2 etc ...
  5989. *
  5990. * @uses WEEKSECS
  5991. * @param ? $startdate ?
  5992. * @param ? $thedate ?
  5993. * @return string
  5994. * @todo Finish documenting this function
  5995. */
  5996. function getweek ($startdate, $thedate) {
  5997. if ($thedate < $startdate) { // error
  5998. return 0;
  5999. }
  6000. return floor(($thedate - $startdate) / WEEKSECS) + 1;
  6001. }
  6002. /**
  6003. * returns a randomly generated password of length $maxlen. inspired by
  6004. * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
  6005. * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
  6006. *
  6007. * @param int $maxlen The maximum size of the password being generated.
  6008. * @return string
  6009. */
  6010. function generate_password($maxlen=10) {
  6011. global $CFG;
  6012. if (empty($CFG->passwordpolicy)) {
  6013. $fillers = PASSWORD_DIGITS;
  6014. $wordlist = file($CFG->wordlist);
  6015. $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
  6016. $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
  6017. $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
  6018. $password = $word1 . $filler1 . $word2;
  6019. } else {
  6020. $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
  6021. $digits = $CFG->minpassworddigits;
  6022. $lower = $CFG->minpasswordlower;
  6023. $upper = $CFG->minpasswordupper;
  6024. $nonalphanum = $CFG->minpasswordnonalphanum;
  6025. $total = $lower + $upper + $digits + $nonalphanum;
  6026. // minlength should be the greater one of the two ( $minlen and $total )
  6027. $minlen = $minlen < $total ? $total : $minlen;
  6028. // maxlen can never be smaller than minlen
  6029. $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
  6030. $additional = $maxlen - $total;
  6031. // Make sure we have enough characters to fulfill
  6032. // complexity requirements
  6033. $passworddigits = PASSWORD_DIGITS;
  6034. while ($digits > strlen($passworddigits)) {
  6035. $passworddigits .= PASSWORD_DIGITS;
  6036. }
  6037. $passwordlower = PASSWORD_LOWER;
  6038. while ($lower > strlen($passwordlower)) {
  6039. $passwordlower .= PASSWORD_LOWER;
  6040. }
  6041. $passwordupper = PASSWORD_UPPER;
  6042. while ($upper > strlen($passwordupper)) {
  6043. $passwordupper .= PASSWORD_UPPER;
  6044. }
  6045. $passwordnonalphanum = PASSWORD_NONALPHANUM;
  6046. while ($nonalphanum > strlen($passwordnonalphanum)) {
  6047. $passwordnonalphanum .= PASSWORD_NONALPHANUM;
  6048. }
  6049. // Now mix and shuffle it all
  6050. $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
  6051. substr(str_shuffle ($passwordupper), 0, $upper) .
  6052. substr(str_shuffle ($passworddigits), 0, $digits) .
  6053. substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
  6054. substr(str_shuffle ($passwordlower .
  6055. $passwordupper .
  6056. $passworddigits .
  6057. $passwordnonalphanum), 0 , $additional));
  6058. }
  6059. return substr ($password, 0, $maxlen);
  6060. }
  6061. /**
  6062. * Given a float, prints it nicely.
  6063. * Localized floats must not be used in calculations!
  6064. *
  6065. * @param float $flaot The float to print
  6066. * @param int $places The number of decimal places to print.
  6067. * @param bool $localized use localized decimal separator
  6068. * @return string locale float
  6069. */
  6070. function format_float($float, $decimalpoints=1, $localized=true) {
  6071. if (is_null($float)) {
  6072. return '';
  6073. }
  6074. if ($localized) {
  6075. return number_format($float, $decimalpoints, get_string('decsep'), '');
  6076. } else {
  6077. return number_format($float, $decimalpoints, '.', '');
  6078. }
  6079. }
  6080. /**
  6081. * Converts locale specific floating point/comma number back to standard PHP float value
  6082. * Do NOT try to do any math operations before this conversion on any user submitted floats!
  6083. *
  6084. * @param string $locale_float locale aware float representation
  6085. */
  6086. function unformat_float($locale_float) {
  6087. $locale_float = trim($locale_float);
  6088. if ($locale_float == '') {
  6089. return null;
  6090. }
  6091. $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
  6092. return (float)str_replace(get_string('decsep'), '.', $locale_float);
  6093. }
  6094. /**
  6095. * Given a simple array, this shuffles it up just like shuffle()
  6096. * Unlike PHP's shuffle() this function works on any machine.
  6097. *
  6098. * @param array $array The array to be rearranged
  6099. * @return array
  6100. */
  6101. function swapshuffle($array) {
  6102. srand ((double) microtime() * 10000000);
  6103. $last = count($array) - 1;
  6104. for ($i=0;$i<=$last;$i++) {
  6105. $from = rand(0,$last);
  6106. $curr = $array[$i];
  6107. $array[$i] = $array[$from];
  6108. $array[$from] = $curr;
  6109. }
  6110. return $array;
  6111. }
  6112. /**
  6113. * Like {@link swapshuffle()}, but works on associative arrays
  6114. *
  6115. * @param array $array The associative array to be rearranged
  6116. * @return array
  6117. */
  6118. function swapshuffle_assoc($array) {
  6119. $newarray = array();
  6120. $newkeys = swapshuffle(array_keys($array));
  6121. foreach ($newkeys as $newkey) {
  6122. $newarray[$newkey] = $array[$newkey];
  6123. }
  6124. return $newarray;
  6125. }
  6126. /**
  6127. * Given an arbitrary array, and a number of draws,
  6128. * this function returns an array with that amount
  6129. * of items. The indexes are retained.
  6130. *
  6131. * @param array $array ?
  6132. * @param ? $draws ?
  6133. * @return ?
  6134. * @todo Finish documenting this function
  6135. */
  6136. function draw_rand_array($array, $draws) {
  6137. srand ((double) microtime() * 10000000);
  6138. $return = array();
  6139. $last = count($array);
  6140. if ($draws > $last) {
  6141. $draws = $last;
  6142. }
  6143. while ($draws > 0) {
  6144. $last--;
  6145. $keys = array_keys($array);
  6146. $rand = rand(0, $last);
  6147. $return[$keys[$rand]] = $array[$keys[$rand]];
  6148. unset($array[$keys[$rand]]);
  6149. $draws--;
  6150. }
  6151. return $return;
  6152. }
  6153. /**
  6154. * microtime_diff
  6155. *
  6156. * @param string $a ?
  6157. * @param string $b ?
  6158. * @return string
  6159. * @todo Finish documenting this function
  6160. */
  6161. function microtime_diff($a, $b) {
  6162. list($a_dec, $a_sec) = explode(' ', $a);
  6163. list($b_dec, $b_sec) = explode(' ', $b);
  6164. return $b_sec - $a_sec + $b_dec - $a_dec;
  6165. }
  6166. /**
  6167. * Given a list (eg a,b,c,d,e) this function returns
  6168. * an array of 1->a, 2->b, 3->c etc
  6169. *
  6170. * @param array $list ?
  6171. * @param string $separator ?
  6172. * @todo Finish documenting this function
  6173. */
  6174. function make_menu_from_list($list, $separator=',') {
  6175. $array = array_reverse(explode($separator, $list), true);
  6176. foreach ($array as $key => $item) {
  6177. $outarray[$key+1] = trim($item);
  6178. }
  6179. return $outarray;
  6180. }
  6181. /**
  6182. * Creates an array that represents all the current grades that
  6183. * can be chosen using the given grading type. Negative numbers
  6184. * are scales, zero is no grade, and positive numbers are maximum
  6185. * grades.
  6186. *
  6187. * @param int $gradingtype ?
  6188. * return int
  6189. * @todo Finish documenting this function
  6190. */
  6191. function make_grades_menu($gradingtype) {
  6192. $grades = array();
  6193. if ($gradingtype < 0) {
  6194. if ($scale = get_record('scale', 'id', - $gradingtype)) {
  6195. return make_menu_from_list($scale->scale);
  6196. }
  6197. } else if ($gradingtype > 0) {
  6198. for ($i=$gradingtype; $i>=0; $i--) {
  6199. $grades[$i] = $i .' / '. $gradingtype;
  6200. }
  6201. return $grades;
  6202. }
  6203. return $grades;
  6204. }
  6205. /**
  6206. * This function returns the nummber of activities
  6207. * using scaleid in a courseid
  6208. *
  6209. * @param int $courseid ?
  6210. * @param int $scaleid ?
  6211. * @return int
  6212. * @todo Finish documenting this function
  6213. */
  6214. function course_scale_used($courseid, $scaleid) {
  6215. global $CFG;
  6216. $return = 0;
  6217. if (!empty($scaleid)) {
  6218. if ($cms = get_course_mods($courseid)) {
  6219. foreach ($cms as $cm) {
  6220. //Check cm->name/lib.php exists
  6221. if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
  6222. include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
  6223. $function_name = $cm->modname.'_scale_used';
  6224. if (function_exists($function_name)) {
  6225. if ($function_name($cm->instance,$scaleid)) {
  6226. $return++;
  6227. }
  6228. }
  6229. }
  6230. }
  6231. }
  6232. // check if any course grade item makes use of the scale
  6233. $return += count_records('grade_items', 'courseid', $courseid, 'scaleid', $scaleid);
  6234. // check if any outcome in the course makes use of the scale
  6235. $return += count_records_sql("SELECT COUNT(*)
  6236. FROM {$CFG->prefix}grade_outcomes_courses goc,
  6237. {$CFG->prefix}grade_outcomes go
  6238. WHERE go.id = goc.outcomeid
  6239. AND go.scaleid = $scaleid
  6240. AND goc.courseid = $courseid");
  6241. }
  6242. return $return;
  6243. }
  6244. /**
  6245. * This function returns the nummber of activities
  6246. * using scaleid in the entire site
  6247. *
  6248. * @param int $scaleid ?
  6249. * @return int
  6250. * @todo Finish documenting this function. Is return type correct?
  6251. */
  6252. function site_scale_used($scaleid,&$courses) {
  6253. global $CFG;
  6254. $return = 0;
  6255. if (!is_array($courses) || count($courses) == 0) {
  6256. $courses = get_courses("all",false,"c.id,c.shortname");
  6257. }
  6258. if (!empty($scaleid)) {
  6259. if (is_array($courses) && count($courses) > 0) {
  6260. foreach ($courses as $course) {
  6261. $return += course_scale_used($course->id,$scaleid);
  6262. }
  6263. }
  6264. }
  6265. return $return;
  6266. }
  6267. /**
  6268. * make_unique_id_code
  6269. *
  6270. * @param string $extra ?
  6271. * @return string
  6272. * @todo Finish documenting this function
  6273. */
  6274. function make_unique_id_code($extra='') {
  6275. $hostname = 'unknownhost';
  6276. if (!empty($_SERVER['HTTP_HOST'])) {
  6277. $hostname = $_SERVER['HTTP_HOST'];
  6278. } else if (!empty($_ENV['HTTP_HOST'])) {
  6279. $hostname = $_ENV['HTTP_HOST'];
  6280. } else if (!empty($_SERVER['SERVER_NAME'])) {
  6281. $hostname = $_SERVER['SERVER_NAME'];
  6282. } else if (!empty($_ENV['SERVER_NAME'])) {
  6283. $hostname = $_ENV['SERVER_NAME'];
  6284. }
  6285. $date = gmdate("ymdHis");
  6286. $random = random_string(6);
  6287. if ($extra) {
  6288. return $hostname .'+'. $date .'+'. $random .'+'. $extra;
  6289. } else {
  6290. return $hostname .'+'. $date .'+'. $random;
  6291. }
  6292. }
  6293. /**
  6294. * Function to check the passed address is within the passed subnet
  6295. *
  6296. * The parameter is a comma separated string of subnet definitions.
  6297. * Subnet strings can be in one of three formats:
  6298. * 1: xxx.xxx.xxx.xxx/xx
  6299. * 2: xxx.xxx
  6300. * 3: xxx.xxx.xxx.xxx-xxx //a range of IP addresses in the last group.
  6301. * Code for type 1 modified from user posted comments by mediator at
  6302. * {@link http://au.php.net/manual/en/function.ip2long.php}
  6303. *
  6304. * TODO one day we will have to make this work with IP6.
  6305. *
  6306. * @param string $addr The address you are checking
  6307. * @param string $subnetstr The string of subnet addresses
  6308. * @return bool
  6309. */
  6310. function address_in_subnet($addr, $subnetstr) {
  6311. $subnets = explode(',', $subnetstr);
  6312. $found = false;
  6313. $addr = trim($addr);
  6314. foreach ($subnets as $subnet) {
  6315. $subnet = trim($subnet);
  6316. if (strpos($subnet, '/') !== false) { /// type 1
  6317. list($ip, $mask) = explode('/', $subnet);
  6318. if (!is_number($mask) || $mask < 0 || $mask > 32) {
  6319. continue;
  6320. }
  6321. if ($mask == 0) {
  6322. return true;
  6323. }
  6324. if ($mask == 32) {
  6325. if ($ip === $addr) {
  6326. return true;
  6327. }
  6328. continue;
  6329. }
  6330. $mask = 0xffffffff << (32 - $mask);
  6331. $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
  6332. } else if (strpos($subnet, '-') !== false) {/// type 3
  6333. $subnetparts = explode('.', $subnet);
  6334. $addrparts = explode('.', $addr);
  6335. $subnetrange = explode('-', array_pop($subnetparts));
  6336. if (count($subnetrange) == 2) {
  6337. $lastaddrpart = array_pop($addrparts);
  6338. $found = ($subnetparts == $addrparts &&
  6339. $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
  6340. }
  6341. } else { /// type 2
  6342. if ($subnet[strlen($subnet) - 1] != '.') {
  6343. $subnet .= '.';
  6344. }
  6345. $found = (strpos($addr . '.', $subnet) === 0);
  6346. }
  6347. if ($found) {
  6348. break;
  6349. }
  6350. }
  6351. return $found;
  6352. }
  6353. /**
  6354. * This function sets the $HTTPSPAGEREQUIRED global
  6355. * (used in some parts of moodle to change some links)
  6356. * and calculate the proper wwwroot to be used
  6357. *
  6358. * By using this function properly, we can ensure 100% https-ized pages
  6359. * at our entire discretion (login, forgot_password, change_password)
  6360. */
  6361. function httpsrequired() {
  6362. global $CFG, $HTTPSPAGEREQUIRED;
  6363. if (!empty($CFG->loginhttps)) {
  6364. $HTTPSPAGEREQUIRED = true;
  6365. $CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
  6366. $CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
  6367. // change theme URLs to https
  6368. theme_setup();
  6369. } else {
  6370. $CFG->httpswwwroot = $CFG->wwwroot;
  6371. $CFG->httpsthemewww = $CFG->themewww;
  6372. }
  6373. }
  6374. /**
  6375. * For outputting debugging info
  6376. *
  6377. * @uses STDOUT
  6378. * @param string $string ?
  6379. * @param string $eol ?
  6380. * @todo Finish documenting this function
  6381. */
  6382. function mtrace($string, $eol="\n", $sleep=0) {
  6383. if (defined('STDOUT')) {
  6384. fwrite(STDOUT, $string.$eol);
  6385. } else {
  6386. echo $string . $eol;
  6387. }
  6388. flush();
  6389. //delay to keep message on user's screen in case of subsequent redirect
  6390. if ($sleep) {
  6391. sleep($sleep);
  6392. }
  6393. }
  6394. //Replace 1 or more slashes or backslashes to 1 slash
  6395. function cleardoubleslashes ($path) {
  6396. return preg_replace('/(\/|\\\){1,}/','/',$path);
  6397. }
  6398. function zip_files ($originalfiles, $destination) {
  6399. //Zip an array of files/dirs to a destination zip file
  6400. //Both parameters must be FULL paths to the files/dirs
  6401. global $CFG;
  6402. //Extract everything from destination
  6403. $path_parts = pathinfo(cleardoubleslashes($destination));
  6404. $destpath = $path_parts["dirname"]; //The path of the zip file
  6405. $destfilename = $path_parts["basename"]; //The name of the zip file
  6406. $extension = $path_parts["extension"]; //The extension of the file
  6407. //If no file, error
  6408. if (empty($destfilename)) {
  6409. return false;
  6410. }
  6411. //If no extension, add it
  6412. if (empty($extension)) {
  6413. $extension = 'zip';
  6414. $destfilename = $destfilename.'.'.$extension;
  6415. }
  6416. //Check destination path exists
  6417. if (!is_dir($destpath)) {
  6418. return false;
  6419. }
  6420. //Check destination path is writable. TODO!!
  6421. //Clean destination filename
  6422. $destfilename = clean_filename($destfilename);
  6423. //Now check and prepare every file
  6424. $files = array();
  6425. $origpath = NULL;
  6426. foreach ($originalfiles as $file) { //Iterate over each file
  6427. //Check for every file
  6428. $tempfile = cleardoubleslashes($file); // no doubleslashes!
  6429. //Calculate the base path for all files if it isn't set
  6430. if ($origpath === NULL) {
  6431. $origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
  6432. }
  6433. //See if the file is readable
  6434. if (!is_readable($tempfile)) { //Is readable
  6435. continue;
  6436. }
  6437. //See if the file/dir is in the same directory than the rest
  6438. if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
  6439. continue;
  6440. }
  6441. //Add the file to the array
  6442. $files[] = $tempfile;
  6443. }
  6444. //Everything is ready:
  6445. // -$origpath is the path where ALL the files to be compressed reside (dir).
  6446. // -$destpath is the destination path where the zip file will go (dir).
  6447. // -$files is an array of files/dirs to compress (fullpath)
  6448. // -$destfilename is the name of the zip file (without path)
  6449. //print_object($files); //Debug
  6450. if (empty($CFG->zip)) { // Use built-in php-based zip function
  6451. include_once("$CFG->libdir/pclzip/pclzip.lib.php");
  6452. //rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
  6453. $zipfiles = array();
  6454. $start = strlen($origpath)+1;
  6455. foreach($files as $file) {
  6456. $tf = array();
  6457. $tf[PCLZIP_ATT_FILE_NAME] = $file;
  6458. $tf[PCLZIP_ATT_FILE_NEW_FULL_NAME] = substr($file, $start);
  6459. $zipfiles[] = $tf;
  6460. }
  6461. //create the archive
  6462. $archive = new PclZip(cleardoubleslashes("$destpath/$destfilename"));
  6463. if (($list = $archive->create($zipfiles) == 0)) {
  6464. notice($archive->errorInfo(true));
  6465. return false;
  6466. }
  6467. } else { // Use external zip program
  6468. $filestozip = "";
  6469. foreach ($files as $filetozip) {
  6470. $filestozip .= escapeshellarg(basename($filetozip));
  6471. $filestozip .= " ";
  6472. }
  6473. //Construct the command
  6474. $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
  6475. $command = 'cd '.escapeshellarg($origpath).$separator.
  6476. escapeshellarg($CFG->zip).' -r '.
  6477. escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
  6478. //All converted to backslashes in WIN
  6479. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  6480. $command = str_replace('/','\\',$command);
  6481. }
  6482. Exec($command);
  6483. }
  6484. return true;
  6485. }
  6486. function unzip_file ($zipfile, $destination = '', $showstatus = true) {
  6487. //Unzip one zip file to a destination dir
  6488. //Both parameters must be FULL paths
  6489. //If destination isn't specified, it will be the
  6490. //SAME directory where the zip file resides.
  6491. global $CFG;
  6492. //Extract everything from zipfile
  6493. $path_parts = pathinfo(cleardoubleslashes($zipfile));
  6494. $zippath = $path_parts["dirname"]; //The path of the zip file
  6495. $zipfilename = $path_parts["basename"]; //The name of the zip file
  6496. $extension = $path_parts["extension"]; //The extension of the file
  6497. //If no file, error
  6498. if (empty($zipfilename)) {
  6499. return false;
  6500. }
  6501. //If no extension, error
  6502. if (empty($extension)) {
  6503. return false;
  6504. }
  6505. //Clear $zipfile
  6506. $zipfile = cleardoubleslashes($zipfile);
  6507. //Check zipfile exists
  6508. if (!file_exists($zipfile)) {
  6509. return false;
  6510. }
  6511. //If no destination, passed let's go with the same directory
  6512. if (empty($destination)) {
  6513. $destination = $zippath;
  6514. }
  6515. //Clear $destination
  6516. $destpath = rtrim(cleardoubleslashes($destination), "/");
  6517. //Check destination path exists
  6518. if (!is_dir($destpath)) {
  6519. return false;
  6520. }
  6521. //Check destination path is writable. TODO!!
  6522. //Everything is ready:
  6523. // -$zippath is the path where the zip file resides (dir)
  6524. // -$zipfilename is the name of the zip file (without path)
  6525. // -$destpath is the destination path where the zip file will uncompressed (dir)
  6526. $list = array();
  6527. require_once("$CFG->libdir/filelib.php");
  6528. do {
  6529. $temppath = "$CFG->dataroot/temp/unzip/".random_string(10);
  6530. } while (file_exists($temppath));
  6531. if (!check_dir_exists($temppath, true, true)) {
  6532. return false;
  6533. }
  6534. if (empty($CFG->unzip)) { // Use built-in php-based unzip function
  6535. include_once("$CFG->libdir/pclzip/pclzip.lib.php");
  6536. $archive = new PclZip(cleardoubleslashes("$zippath/$zipfilename"));
  6537. if (!$list = $archive->extract(PCLZIP_OPT_PATH, $temppath,
  6538. PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename',
  6539. PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $temppath)) {
  6540. if (!empty($showstatus)) {
  6541. notice($archive->errorInfo(true));
  6542. }
  6543. return false;
  6544. }
  6545. } else { // Use external unzip program
  6546. $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
  6547. $redirection = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? '' : ' 2>&1';
  6548. $command = 'cd '.escapeshellarg($zippath).$separator.
  6549. escapeshellarg($CFG->unzip).' -o '.
  6550. escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
  6551. escapeshellarg($temppath).$redirection;
  6552. //All converted to backslashes in WIN
  6553. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  6554. $command = str_replace('/','\\',$command);
  6555. }
  6556. Exec($command,$list);
  6557. }
  6558. unzip_process_temp_dir($temppath, $destpath);
  6559. fulldelete($temppath);
  6560. //Display some info about the unzip execution
  6561. if ($showstatus) {
  6562. unzip_show_status($list, $temppath, $destpath);
  6563. }
  6564. return true;
  6565. }
  6566. /**
  6567. * Sanitize temporary unzipped files and move to target dir.
  6568. * @param string $temppath path to temporary dir with unzip output
  6569. * @param string $destpath destination path
  6570. * @return void
  6571. */
  6572. function unzip_process_temp_dir($temppath, $destpath) {
  6573. global $CFG;
  6574. $filepermissions = ($CFG->directorypermissions & 0666); // strip execute flags
  6575. if (check_dir_exists($destpath, true, true)) {
  6576. $currdir = opendir($temppath);
  6577. while (false !== ($file = readdir($currdir))) {
  6578. if ($file <> ".." && $file <> ".") {
  6579. $fullfile = "$temppath/$file";
  6580. if (is_link($fullfile)) {
  6581. //somebody tries to sneak in symbolik link - no way!
  6582. continue;
  6583. }
  6584. $cleanfile = clean_param($file, PARAM_FILE); // no dangerous chars
  6585. if ($cleanfile === '') {
  6586. // invalid file name
  6587. continue;
  6588. }
  6589. if ($cleanfile !== $file and file_exists("$temppath/$cleanfile")) {
  6590. // eh, weird chars collision detected
  6591. continue;
  6592. }
  6593. $descfile = "$destpath/$cleanfile";
  6594. if (is_dir($fullfile)) {
  6595. // recurse into subdirs
  6596. unzip_process_temp_dir($fullfile, $descfile);
  6597. }
  6598. if (is_file($fullfile)) {
  6599. // rename and move the file
  6600. if (file_exists($descfile)) {
  6601. //override existing files
  6602. unlink($descfile);
  6603. }
  6604. rename($fullfile, $descfile);
  6605. chmod($descfile, $filepermissions);
  6606. }
  6607. }
  6608. }
  6609. closedir($currdir);
  6610. }
  6611. }
  6612. function unzip_cleanfilename ($p_event, &$p_header) {
  6613. //This function is used as callback in unzip_file() function
  6614. //to clean illegal characters for given platform and to prevent directory traversal.
  6615. //Produces the same result as info-zip unzip.
  6616. $p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
  6617. $p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
  6618. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  6619. $p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
  6620. $p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
  6621. } else {
  6622. //Add filtering for other systems here
  6623. // BSD: none (tested)
  6624. // Linux: ??
  6625. // MacosX: ??
  6626. }
  6627. $p_header['filename'] = cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
  6628. return 1;
  6629. }
  6630. function unzip_show_status($list, $removepath, $removepath2) {
  6631. //This function shows the results of the unzip execution
  6632. //depending of the value of the $CFG->zip, results will be
  6633. //text or an array of files.
  6634. global $CFG;
  6635. if (empty($CFG->unzip)) { // Use built-in php-based zip function
  6636. $strname = get_string("name");
  6637. $strsize = get_string("size");
  6638. $strmodified = get_string("modified");
  6639. $strstatus = get_string("status");
  6640. echo "<table width=\"640\">";
  6641. echo "<tr><th class=\"header\" scope=\"col\">$strname</th>";
  6642. echo "<th class=\"header\" align=\"right\" scope=\"col\">$strsize</th>";
  6643. echo "<th class=\"header\" align=\"right\" scope=\"col\">$strmodified</th>";
  6644. echo "<th class=\"header\" align=\"right\" scope=\"col\">$strstatus</th></tr>";
  6645. foreach ($list as $item) {
  6646. echo "<tr>";
  6647. $item['filename'] = str_replace(cleardoubleslashes($removepath).'/', "", $item['filename']);
  6648. $item['filename'] = str_replace(cleardoubleslashes($removepath2).'/', "", $item['filename']);
  6649. echo '<td align="left" style="white-space:nowrap ">'.s(clean_param($item['filename'], PARAM_PATH)).'</td>';
  6650. if (! $item['folder']) {
  6651. echo '<td align="right" style="white-space:nowrap ">'.display_size($item['size']).'</td>';
  6652. } else {
  6653. echo "<td>&nbsp;</td>";
  6654. }
  6655. $filedate = userdate($item['mtime'], get_string("strftimedatetime"));
  6656. echo '<td align="right" style="white-space:nowrap ">'.$filedate.'</td>';
  6657. echo '<td align="right" style="white-space:nowrap ">'.$item['status'].'</td>';
  6658. echo "</tr>";
  6659. }
  6660. echo "</table>";
  6661. } else { // Use external zip program
  6662. print_simple_box_start("center");
  6663. echo "<pre>";
  6664. foreach ($list as $item) {
  6665. $item = str_replace(cleardoubleslashes($removepath.'/'), '', $item);
  6666. $item = str_replace(cleardoubleslashes($removepath2.'/'), '', $item);
  6667. echo s($item).'<br />';
  6668. }
  6669. echo "</pre>";
  6670. print_simple_box_end();
  6671. }
  6672. }
  6673. /**
  6674. * Returns most reliable client address
  6675. *
  6676. * @return string The remote IP address
  6677. */
  6678. define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
  6679. define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
  6680. function getremoteaddr() {
  6681. global $CFG;
  6682. if (empty($CFG->getremoteaddrconf)) {
  6683. // This will happen, for example, before just after the upgrade, as the
  6684. // user is redirected to the admin screen.
  6685. $variablestoskip = 0;
  6686. } else {
  6687. $variablestoskip = $CFG->getremoteaddrconf;
  6688. }
  6689. if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
  6690. if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
  6691. return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
  6692. }
  6693. }
  6694. if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
  6695. if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  6696. return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
  6697. }
  6698. }
  6699. if (!empty($_SERVER['REMOTE_ADDR'])) {
  6700. return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
  6701. } else {
  6702. return null;
  6703. }
  6704. }
  6705. /**
  6706. * Cleans a remote address ready to put into the log table
  6707. */
  6708. function cleanremoteaddr($addr) {
  6709. $originaladdr = $addr;
  6710. $matches = array();
  6711. // first get all things that look like IP addresses.
  6712. if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER)) {
  6713. return '';
  6714. }
  6715. $goodmatches = array();
  6716. $lanmatches = array();
  6717. foreach ($matches as $match) {
  6718. // print_r($match);
  6719. // check to make sure it's not an internal address.
  6720. // the following are reserved for private lans...
  6721. // 10.0.0.0 - 10.255.255.255
  6722. // 172.16.0.0 - 172.31.255.255
  6723. // 192.168.0.0 - 192.168.255.255
  6724. // 169.254.0.0 -169.254.255.255
  6725. $bits = explode('.',$match[0]);
  6726. if (count($bits) != 4) {
  6727. // weird, preg match shouldn't give us it.
  6728. continue;
  6729. }
  6730. if (($bits[0] == 10)
  6731. || ($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
  6732. || ($bits[0] == 192 && $bits[1] == 168)
  6733. || ($bits[0] == 169 && $bits[1] == 254)) {
  6734. $lanmatches[] = $match[0];
  6735. continue;
  6736. }
  6737. // finally, it's ok
  6738. $goodmatches[] = $match[0];
  6739. }
  6740. if (!count($goodmatches)) {
  6741. // perhaps we have a lan match, it's probably better to return that.
  6742. if (!count($lanmatches)) {
  6743. return '';
  6744. } else {
  6745. return array_pop($lanmatches);
  6746. }
  6747. }
  6748. if (count($goodmatches) == 1) {
  6749. return $goodmatches[0];
  6750. }
  6751. //Commented out following because there are so many, and it clogs the logs MDL-13544
  6752. //error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
  6753. // We need to return something, so return the first
  6754. return array_pop($goodmatches);
  6755. }
  6756. /**
  6757. * file_put_contents is only supported by php 5.0 and higher
  6758. * so if it is not predefined, define it here
  6759. *
  6760. * @param $file full path of the file to write
  6761. * @param $contents contents to be sent
  6762. * @return number of bytes written (false on error)
  6763. */
  6764. if(!function_exists('file_put_contents')) {
  6765. function file_put_contents($file, $contents) {
  6766. $result = false;
  6767. if ($f = fopen($file, 'w')) {
  6768. $result = fwrite($f, $contents);
  6769. fclose($f);
  6770. }
  6771. return $result;
  6772. }
  6773. }
  6774. /**
  6775. * The clone keyword is only supported from PHP 5 onwards.
  6776. * The behaviour of $obj2 = $obj1 differs fundamentally
  6777. * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
  6778. * created, in PHP 5 $obj1 is referenced. To create a copy
  6779. * in PHP 5 the clone keyword was introduced. This function
  6780. * simulates this behaviour for PHP < 5.0.0.
  6781. * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
  6782. *
  6783. * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
  6784. * Found a better implementation (more checks and possibilities) from PEAR:
  6785. * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
  6786. *
  6787. * @param object $obj
  6788. * @return object
  6789. */
  6790. if(!check_php_version('5.0.0')) {
  6791. // the eval is needed to prevent PHP 5 from getting a parse error!
  6792. eval('
  6793. function clone($obj) {
  6794. /// Sanity check
  6795. if (!is_object($obj)) {
  6796. user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
  6797. return;
  6798. }
  6799. /// Use serialize/unserialize trick to deep copy the object
  6800. $obj = unserialize(serialize($obj));
  6801. /// If there is a __clone method call it on the "new" class
  6802. if (method_exists($obj, \'__clone\')) {
  6803. $obj->__clone();
  6804. }
  6805. return $obj;
  6806. }
  6807. // Supply the PHP5 function scandir() to older versions.
  6808. function scandir($directory) {
  6809. $files = array();
  6810. if ($dh = opendir($directory)) {
  6811. while (($file = readdir($dh)) !== false) {
  6812. $files[] = $file;
  6813. }
  6814. closedir($dh);
  6815. }
  6816. return $files;
  6817. }
  6818. // Supply the PHP5 function array_combine() to older versions.
  6819. function array_combine($keys, $values) {
  6820. if (!is_array($keys) || !is_array($values) || count($keys) != count($values)) {
  6821. return false;
  6822. }
  6823. reset($values);
  6824. $result = array();
  6825. foreach ($keys as $key) {
  6826. $result[$key] = current($values);
  6827. next($values);
  6828. }
  6829. return $result;
  6830. }
  6831. ');
  6832. }
  6833. /**
  6834. * This function will make a complete copy of anything it's given,
  6835. * regardless of whether it's an object or not.
  6836. * @param mixed $thing
  6837. * @return mixed
  6838. */
  6839. function fullclone($thing) {
  6840. return unserialize(serialize($thing));
  6841. }
  6842. /*
  6843. * This function expects to called during shutdown
  6844. * should be set via register_shutdown_function()
  6845. * in lib/setup.php .
  6846. *
  6847. * Right now we do it only if we are under apache, to
  6848. * make sure apache children that hog too much mem are
  6849. * killed.
  6850. *
  6851. */
  6852. function moodle_request_shutdown() {
  6853. global $CFG;
  6854. // initially, we are only ever called under apache
  6855. // but check just in case
  6856. if (function_exists('apache_child_terminate')
  6857. && function_exists('memory_get_usage')
  6858. && ini_get_bool('child_terminate')) {
  6859. if (empty($CFG->apachemaxmem)) {
  6860. $CFG->apachemaxmem = 25000000; // default 25MiB
  6861. }
  6862. if (memory_get_usage() > (int)$CFG->apachemaxmem) {
  6863. trigger_error('Mem usage over $CFG->apachemaxmem: marking child for reaping.');
  6864. @apache_child_terminate();
  6865. }
  6866. }
  6867. if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
  6868. if (defined('MDL_PERFTOLOG')) {
  6869. $perf = get_performance_info();
  6870. error_log("PERF: " . $perf['txt']);
  6871. }
  6872. if (defined('MDL_PERFINC')) {
  6873. $inc = get_included_files();
  6874. $ts = 0;
  6875. foreach($inc as $f) {
  6876. if (preg_match(':^/:', $f)) {
  6877. $fs = filesize($f);
  6878. $ts += $fs;
  6879. $hfs = display_size($fs);
  6880. error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
  6881. , NULL, NULL, 0);
  6882. } else {
  6883. error_log($f , NULL, NULL, 0);
  6884. }
  6885. }
  6886. if ($ts > 0 ) {
  6887. $hts = display_size($ts);
  6888. error_log("Total size of files included: $ts ($hts)");
  6889. }
  6890. }
  6891. }
  6892. }
  6893. /**
  6894. * If new messages are waiting for the current user, then return
  6895. * Javascript code to create a popup window
  6896. *
  6897. * @return string Javascript code
  6898. */
  6899. function message_popup_window() {
  6900. global $USER;
  6901. $popuplimit = 30; // Minimum seconds between popups
  6902. if (!defined('MESSAGE_WINDOW')) {
  6903. if (!empty($USER->id) and !isguestuser()) {
  6904. if (!isset($USER->message_lastpopup)) {
  6905. $USER->message_lastpopup = 0;
  6906. }
  6907. if ((time() - $USER->message_lastpopup) > $popuplimit) { /// It's been long enough
  6908. if (get_user_preferences('message_showmessagewindow', 1) == 1) {
  6909. if (count_records_select('message', 'useridto = \''.$USER->id.'\' AND timecreated > \''.$USER->message_lastpopup.'\'')) {
  6910. $USER->message_lastpopup = time();
  6911. return '<script type="text/javascript">'."\n//<![CDATA[\n openpopup('/message/index.php', 'message',
  6912. 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0);\n//]]>\n</script>";
  6913. }
  6914. }
  6915. }
  6916. }
  6917. }
  6918. return '';
  6919. }
  6920. // Used to make sure that $min <= $value <= $max
  6921. function bounded_number($min, $value, $max) {
  6922. if($value < $min) {
  6923. return $min;
  6924. }
  6925. if($value > $max) {
  6926. return $max;
  6927. }
  6928. return $value;
  6929. }
  6930. function array_is_nested($array) {
  6931. foreach ($array as $value) {
  6932. if (is_array($value)) {
  6933. return true;
  6934. }
  6935. }
  6936. return false;
  6937. }
  6938. /**
  6939. *** get_performance_info() pairs up with init_performance_info()
  6940. *** loaded in setup.php. Returns an array with 'html' and 'txt'
  6941. *** values ready for use, and each of the individual stats provided
  6942. *** separately as well.
  6943. ***
  6944. **/
  6945. function get_performance_info() {
  6946. global $CFG, $PERF, $rcache;
  6947. $info = array();
  6948. $info['html'] = ''; // holds userfriendly HTML representation
  6949. $info['txt'] = me() . ' '; // holds log-friendly representation
  6950. $info['realtime'] = microtime_diff($PERF->starttime, microtime());
  6951. $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
  6952. $info['txt'] .= 'time: '.$info['realtime'].'s ';
  6953. if (function_exists('memory_get_usage')) {
  6954. $info['memory_total'] = memory_get_usage();
  6955. $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
  6956. $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
  6957. $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
  6958. }
  6959. if (function_exists('memory_get_peak_usage')) {
  6960. $info['memory_peak'] = memory_get_peak_usage();
  6961. $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
  6962. $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
  6963. }
  6964. $inc = get_included_files();
  6965. //error_log(print_r($inc,1));
  6966. $info['includecount'] = count($inc);
  6967. $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
  6968. $info['txt'] .= 'includecount: '.$info['includecount'].' ';
  6969. if (!empty($PERF->dbqueries)) {
  6970. $info['dbqueries'] = $PERF->dbqueries;
  6971. $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
  6972. $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
  6973. }
  6974. if (!empty($PERF->logwrites)) {
  6975. $info['logwrites'] = $PERF->logwrites;
  6976. $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
  6977. $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
  6978. }
  6979. if (!empty($PERF->profiling) && $PERF->profiling) {
  6980. require_once($CFG->dirroot .'/lib/profilerlib.php');
  6981. $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
  6982. }
  6983. if (function_exists('posix_times')) {
  6984. $ptimes = posix_times();
  6985. if (is_array($ptimes)) {
  6986. foreach ($ptimes as $key => $val) {
  6987. $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
  6988. }
  6989. $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
  6990. $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
  6991. }
  6992. }
  6993. // Grab the load average for the last minute
  6994. // /proc will only work under some linux configurations
  6995. // while uptime is there under MacOSX/Darwin and other unices
  6996. if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
  6997. list($server_load) = explode(' ', $loadavg[0]);
  6998. unset($loadavg);
  6999. } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
  7000. if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
  7001. $server_load = $matches[1];
  7002. } else {
  7003. trigger_error('Could not parse uptime output!');
  7004. }
  7005. }
  7006. if (!empty($server_load)) {
  7007. $info['serverload'] = $server_load;
  7008. $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
  7009. $info['txt'] .= "serverload: {$info['serverload']} ";
  7010. }
  7011. if (isset($rcache->hits) && isset($rcache->misses)) {
  7012. $info['rcachehits'] = $rcache->hits;
  7013. $info['rcachemisses'] = $rcache->misses;
  7014. $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
  7015. "{$rcache->hits}/{$rcache->misses}</span> ";
  7016. $info['txt'] .= 'rcache: '.
  7017. "{$rcache->hits}/{$rcache->misses} ";
  7018. }
  7019. $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
  7020. return $info;
  7021. }
  7022. function apd_get_profiling() {
  7023. return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
  7024. }
  7025. /**
  7026. * Delete directory or only it's content
  7027. * @param string $dir directory path
  7028. * @param bool $content_only
  7029. * @return bool success, true also if dir does not exist
  7030. */
  7031. function remove_dir($dir, $content_only=false) {
  7032. if (!file_exists($dir)) {
  7033. // nothing to do
  7034. return true;
  7035. }
  7036. $handle = opendir($dir);
  7037. $result = true;
  7038. while (false!==($item = readdir($handle))) {
  7039. if($item != '.' && $item != '..') {
  7040. if(is_dir($dir.'/'.$item)) {
  7041. $result = remove_dir($dir.'/'.$item) && $result;
  7042. }else{
  7043. $result = unlink($dir.'/'.$item) && $result;
  7044. }
  7045. }
  7046. }
  7047. closedir($handle);
  7048. if ($content_only) {
  7049. return $result;
  7050. }
  7051. return rmdir($dir); // if anything left the result will be false, noo need for && $result
  7052. }
  7053. /**
  7054. * Function to check if a directory exists and optionally create it.
  7055. *
  7056. * @param string absolute directory path (must be under $CFG->dataroot)
  7057. * @param boolean create directory if does not exist
  7058. * @param boolean create directory recursively
  7059. *
  7060. * @return boolean true if directory exists or created
  7061. */
  7062. function check_dir_exists($dir, $create=false, $recursive=false) {
  7063. global $CFG;
  7064. if (strstr(cleardoubleslashes($dir), cleardoubleslashes($CFG->dataroot.'/')) === false) {
  7065. debugging('Warning. Wrong call to check_dir_exists(). $dir must be an absolute path under $CFG->dataroot ("' . $dir . '" is incorrect)', DEBUG_DEVELOPER);
  7066. }
  7067. $status = true;
  7068. if(!is_dir($dir)) {
  7069. if (!$create) {
  7070. $status = false;
  7071. } else {
  7072. umask(0000);
  7073. if ($recursive) {
  7074. /// We are going to make it recursive under $CFG->dataroot only
  7075. /// (will help sites running open_basedir security and others)
  7076. $dir = str_replace(cleardoubleslashes($CFG->dataroot . '/'), '', cleardoubleslashes($dir));
  7077. /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-(
  7078. $dirs = explode('/', $dir); /// Extract path parts
  7079. /// Iterate over each part with start point $CFG->dataroot
  7080. $dir = $CFG->dataroot . '/';
  7081. foreach ($dirs as $part) {
  7082. if ($part == '') {
  7083. continue;
  7084. }
  7085. $dir .= $part.'/';
  7086. if (!is_dir($dir)) {
  7087. if (!mkdir($dir, $CFG->directorypermissions)) {
  7088. $status = false;
  7089. break;
  7090. }
  7091. }
  7092. }
  7093. } else {
  7094. $status = mkdir($dir, $CFG->directorypermissions);
  7095. }
  7096. }
  7097. }
  7098. return $status;
  7099. }
  7100. function report_session_error() {
  7101. global $CFG, $FULLME;
  7102. if (empty($CFG->lang)) {
  7103. $CFG->lang = "en";
  7104. }
  7105. // Set up default theme and locale
  7106. theme_setup();
  7107. moodle_setlocale();
  7108. //clear session cookies
  7109. if (check_php_version('5.2.0')) {
  7110. //PHP 5.2.0
  7111. setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
  7112. setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
  7113. } else {
  7114. setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
  7115. setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
  7116. }
  7117. //increment database error counters
  7118. if (isset($CFG->session_error_counter)) {
  7119. set_config('session_error_counter', 1 + $CFG->session_error_counter);
  7120. } else {
  7121. set_config('session_error_counter', 1);
  7122. }
  7123. redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
  7124. }
  7125. /**
  7126. * Detect if an object or a class contains a given property
  7127. * will take an actual object or the name of a class
  7128. * @param mix $obj Name of class or real object to test
  7129. * @param string $property name of property to find
  7130. * @return bool true if property exists
  7131. */
  7132. function object_property_exists( $obj, $property ) {
  7133. if (is_string( $obj )) {
  7134. $properties = get_class_vars( $obj );
  7135. }
  7136. else {
  7137. $properties = get_object_vars( $obj );
  7138. }
  7139. return array_key_exists( $property, $properties );
  7140. }
  7141. /**
  7142. * Detect a custom script replacement in the data directory that will
  7143. * replace an existing moodle script
  7144. * @param string $urlpath path to the original script
  7145. * @return string full path name if a custom script exists
  7146. * @return bool false if no custom script exists
  7147. */
  7148. function custom_script_path($urlpath='') {
  7149. global $CFG;
  7150. // set default $urlpath, if necessary
  7151. if (empty($urlpath)) {
  7152. $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
  7153. }
  7154. // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
  7155. if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
  7156. return false;
  7157. }
  7158. // replace wwwroot with the path to the customscripts folder and clean path
  7159. $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
  7160. // remove the query string, if any
  7161. if (($strpos = strpos($scriptpath, '?')) !== false) {
  7162. $scriptpath = substr($scriptpath, 0, $strpos);
  7163. }
  7164. // remove trailing slashes, if any
  7165. $scriptpath = rtrim($scriptpath, '/\\');
  7166. // append index.php, if necessary
  7167. if (is_dir($scriptpath)) {
  7168. $scriptpath .= '/index.php';
  7169. }
  7170. // check the custom script exists
  7171. if (file_exists($scriptpath)) {
  7172. return $scriptpath;
  7173. } else {
  7174. return false;
  7175. }
  7176. }
  7177. /**
  7178. * Wrapper function to load necessary editor scripts
  7179. * to $CFG->editorsrc array. Params can be coursei id
  7180. * or associative array('courseid' => value, 'name' => 'editorname').
  7181. * @uses $CFG
  7182. * @param mixed $args Courseid or associative array.
  7183. */
  7184. function loadeditor($args) {
  7185. global $CFG;
  7186. include($CFG->libdir .'/editorlib.php');
  7187. return editorObject::loadeditor($args);
  7188. }
  7189. /**
  7190. * Returns whether or not the user object is a remote MNET user. This function
  7191. * is in moodlelib because it does not rely on loading any of the MNET code.
  7192. *
  7193. * @param object $user A valid user object
  7194. * @return bool True if the user is from a remote Moodle.
  7195. */
  7196. function is_mnet_remote_user($user) {
  7197. global $CFG;
  7198. if (!isset($CFG->mnet_localhost_id)) {
  7199. include_once $CFG->dirroot . '/mnet/lib.php';
  7200. $env = new mnet_environment();
  7201. $env->init();
  7202. unset($env);
  7203. }
  7204. return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
  7205. }
  7206. /**
  7207. * Checks if a given plugin is in the list of enabled enrolment plugins.
  7208. *
  7209. * @param string $auth Enrolment plugin.
  7210. * @return boolean Whether the plugin is enabled.
  7211. */
  7212. function is_enabled_enrol($enrol='') {
  7213. global $CFG;
  7214. // use the global default if not specified
  7215. if ($enrol == '') {
  7216. $enrol = $CFG->enrol;
  7217. }
  7218. return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
  7219. }
  7220. /**
  7221. * This function will search for browser prefereed languages, setting Moodle
  7222. * to use the best one available if $SESSION->lang is undefined
  7223. */
  7224. function setup_lang_from_browser() {
  7225. global $CFG, $SESSION, $USER;
  7226. if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
  7227. // Lang is defined in session or user profile, nothing to do
  7228. return;
  7229. }
  7230. if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
  7231. return;
  7232. }
  7233. /// Extract and clean langs from headers
  7234. $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  7235. $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
  7236. $rawlangs = explode(',', $rawlangs); // Convert to array
  7237. $langs = array();
  7238. $order = 1.0;
  7239. foreach ($rawlangs as $lang) {
  7240. if (strpos($lang, ';') === false) {
  7241. $langs[(string)$order] = $lang;
  7242. $order = $order-0.01;
  7243. } else {
  7244. $parts = explode(';', $lang);
  7245. $pos = strpos($parts[1], '=');
  7246. $langs[substr($parts[1], $pos+1)] = $parts[0];
  7247. }
  7248. }
  7249. krsort($langs, SORT_NUMERIC);
  7250. $langlist = get_list_of_languages();
  7251. /// Look for such langs under standard locations
  7252. foreach ($langs as $lang) {
  7253. $lang = strtolower(clean_param($lang.'_utf8', PARAM_SAFEDIR)); // clean it properly for include
  7254. if (!array_key_exists($lang, $langlist)) {
  7255. continue; // language not allowed, try next one
  7256. }
  7257. if (file_exists($CFG->dataroot .'/lang/'. $lang) or file_exists($CFG->dirroot .'/lang/'. $lang)) {
  7258. $SESSION->lang = $lang; /// Lang exists, set it in session
  7259. break; /// We have finished. Go out
  7260. }
  7261. }
  7262. return;
  7263. }
  7264. ////////////////////////////////////////////////////////////////////////////////
  7265. function is_newnav($navigation) {
  7266. if (is_array($navigation) && !empty($navigation['newnav'])) {
  7267. return true;
  7268. } else {
  7269. return false;
  7270. }
  7271. }
  7272. /**
  7273. * Checks whether the given variable name is defined as a variable within the given object.
  7274. * @note This will NOT work with stdClass objects, which have no class variables.
  7275. * @param string $var The variable name
  7276. * @param object $object The object to check
  7277. * @return boolean
  7278. */
  7279. function in_object_vars($var, $object) {
  7280. $class_vars = get_class_vars(get_class($object));
  7281. $class_vars = array_keys($class_vars);
  7282. return in_array($var, $class_vars);
  7283. }
  7284. /**
  7285. * Returns an array without repeated objects.
  7286. * This function is similar to array_unique, but for arrays that have objects as values
  7287. *
  7288. * @param unknown_type $array
  7289. * @param unknown_type $keep_key_assoc
  7290. * @return unknown
  7291. */
  7292. function object_array_unique($array, $keep_key_assoc = true) {
  7293. $duplicate_keys = array();
  7294. $tmp = array();
  7295. foreach ($array as $key=>$val) {
  7296. // convert objects to arrays, in_array() does not support objects
  7297. if (is_object($val)) {
  7298. $val = (array)$val;
  7299. }
  7300. if (!in_array($val, $tmp)) {
  7301. $tmp[] = $val;
  7302. } else {
  7303. $duplicate_keys[] = $key;
  7304. }
  7305. }
  7306. foreach ($duplicate_keys as $key) {
  7307. unset($array[$key]);
  7308. }
  7309. return $keep_key_assoc ? $array : array_values($array);
  7310. }
  7311. /**
  7312. * Returns the language string for the given plugin.
  7313. *
  7314. * @param string $plugin the plugin code name
  7315. * @param string $type the type of plugin (mod, block, filter)
  7316. * @return string The plugin language string
  7317. */
  7318. function get_plugin_name($plugin, $type='mod') {
  7319. $plugin_name = '';
  7320. switch ($type) {
  7321. case 'mod':
  7322. $plugin_name = get_string('modulename', $plugin);
  7323. break;
  7324. case 'blocks':
  7325. $plugin_name = get_string('blockname', "block_$plugin");
  7326. if (empty($plugin_name) || $plugin_name == '[[blockname]]') {
  7327. if (($block = block_instance($plugin)) !== false) {
  7328. $plugin_name = $block->get_title();
  7329. } else {
  7330. $plugin_name = "[[$plugin]]";
  7331. }
  7332. }
  7333. break;
  7334. case 'filter':
  7335. $plugin_name = trim(get_string('filtername', $plugin));
  7336. if (empty($plugin_name) or ($plugin_name == '[[filtername]]')) {
  7337. $textlib = textlib_get_instance();
  7338. $plugin_name = $textlib->strtotitle($plugin);
  7339. }
  7340. break;
  7341. default:
  7342. $plugin_name = $plugin;
  7343. break;
  7344. }
  7345. return $plugin_name;
  7346. }
  7347. /**
  7348. * Is a userid the primary administrator?
  7349. *
  7350. * @param $userid int id of user to check
  7351. * @return boolean
  7352. */
  7353. function is_primary_admin($userid){
  7354. $primaryadmin = get_admin();
  7355. if($userid == $primaryadmin->id){
  7356. return true;
  7357. }else{
  7358. return false;
  7359. }
  7360. }
  7361. /**
  7362. * @return string $CFG->siteidentifier, first making sure it is properly initialised.
  7363. */
  7364. function get_site_identifier() {
  7365. global $CFG;
  7366. // Check to see if it is missing. If so, initialise it.
  7367. if (empty($CFG->siteidentifier)) {
  7368. set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
  7369. }
  7370. // Return it.
  7371. return $CFG->siteidentifier;
  7372. }
  7373. // vim:autoindent:expandtab:shiftwidth=4:tabstop=4:tw=140:
  7374. ?>