PageRenderTime 118ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 2ms

/lib/moodlelib.php

https://github.com/glovenone/moodle
PHP | 9965 lines | 5787 code | 1274 blank | 2904 comment | 1353 complexity | 45dc033818c0dcbdeda38730183a6082 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, BSD-3-Clause, AGPL-3.0, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * moodlelib.php - Moodle main library
  18. *
  19. * Main library file of miscellaneous general-purpose Moodle functions.
  20. * Other main libraries:
  21. * - weblib.php - functions that produce web output
  22. * - datalib.php - functions that access the database
  23. *
  24. * @package core
  25. * @subpackage lib
  26. * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
  27. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28. */
  29. defined('MOODLE_INTERNAL') || die();
  30. /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
  31. /// Date and time constants ///
  32. /**
  33. * Time constant - the number of seconds in a year
  34. */
  35. define('YEARSECS', 31536000);
  36. /**
  37. * Time constant - the number of seconds in a week
  38. */
  39. define('WEEKSECS', 604800);
  40. /**
  41. * Time constant - the number of seconds in a day
  42. */
  43. define('DAYSECS', 86400);
  44. /**
  45. * Time constant - the number of seconds in an hour
  46. */
  47. define('HOURSECS', 3600);
  48. /**
  49. * Time constant - the number of seconds in a minute
  50. */
  51. define('MINSECS', 60);
  52. /**
  53. * Time constant - the number of minutes in a day
  54. */
  55. define('DAYMINS', 1440);
  56. /**
  57. * Time constant - the number of minutes in an hour
  58. */
  59. define('HOURMINS', 60);
  60. /// Parameter constants - every call to optional_param(), required_param() ///
  61. /// or clean_param() should have a specified type of parameter. //////////////
  62. /**
  63. * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
  64. */
  65. define('PARAM_ALPHA', 'alpha');
  66. /**
  67. * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "_-" allowed
  68. * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
  69. */
  70. define('PARAM_ALPHAEXT', 'alphaext');
  71. /**
  72. * PARAM_ALPHANUM - expected numbers and letters only.
  73. */
  74. define('PARAM_ALPHANUM', 'alphanum');
  75. /**
  76. * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
  77. */
  78. define('PARAM_ALPHANUMEXT', 'alphanumext');
  79. /**
  80. * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
  81. */
  82. define('PARAM_AUTH', 'auth');
  83. /**
  84. * PARAM_BASE64 - Base 64 encoded format
  85. */
  86. define('PARAM_BASE64', 'base64');
  87. /**
  88. * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
  89. */
  90. define('PARAM_BOOL', 'bool');
  91. /**
  92. * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
  93. * checked against the list of capabilities in the database.
  94. */
  95. define('PARAM_CAPABILITY', 'capability');
  96. /**
  97. * PARAM_CLEANHTML - cleans submitted HTML code. use only for text in HTML format. This cleaning may fix xhtml strictness too.
  98. */
  99. define('PARAM_CLEANHTML', 'cleanhtml');
  100. /**
  101. * PARAM_EMAIL - an email address following the RFC
  102. */
  103. define('PARAM_EMAIL', 'email');
  104. /**
  105. * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
  106. */
  107. define('PARAM_FILE', 'file');
  108. /**
  109. * PARAM_FLOAT - a real/floating point number.
  110. */
  111. define('PARAM_FLOAT', 'float');
  112. /**
  113. * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
  114. */
  115. define('PARAM_HOST', 'host');
  116. /**
  117. * PARAM_INT - integers only, use when expecting only numbers.
  118. */
  119. define('PARAM_INT', 'int');
  120. /**
  121. * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
  122. */
  123. define('PARAM_LANG', 'lang');
  124. /**
  125. * 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!)
  126. */
  127. define('PARAM_LOCALURL', 'localurl');
  128. /**
  129. * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
  130. */
  131. define('PARAM_NOTAGS', 'notags');
  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', 'path');
  137. /**
  138. * PARAM_PEM - Privacy Enhanced Mail format
  139. */
  140. define('PARAM_PEM', 'pem');
  141. /**
  142. * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
  143. */
  144. define('PARAM_PERMISSION', 'permission');
  145. /**
  146. * PARAM_RAW specifies a parameter that is not cleaned/processed in any way
  147. */
  148. define('PARAM_RAW', 'raw');
  149. /**
  150. * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
  151. */
  152. define('PARAM_RAW_TRIMMED', 'raw_trimmed');
  153. /**
  154. * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
  155. */
  156. define('PARAM_SAFEDIR', 'safedir');
  157. /**
  158. * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
  159. */
  160. define('PARAM_SAFEPATH', 'safepath');
  161. /**
  162. * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
  163. */
  164. define('PARAM_SEQUENCE', 'sequence');
  165. /**
  166. * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
  167. */
  168. define('PARAM_TAG', 'tag');
  169. /**
  170. * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
  171. */
  172. define('PARAM_TAGLIST', 'taglist');
  173. /**
  174. * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
  175. */
  176. define('PARAM_TEXT', 'text');
  177. /**
  178. * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
  179. */
  180. define('PARAM_THEME', 'theme');
  181. /**
  182. * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
  183. */
  184. define('PARAM_URL', 'url');
  185. /**
  186. * PARAM_USERNAME - Clean username to only contains allowed characters. This is to be used ONLY when manually creating user accounts, do NOT use when syncing with external systems!!
  187. */
  188. define('PARAM_USERNAME', 'username');
  189. /**
  190. * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
  191. */
  192. define('PARAM_STRINGID', 'stringid');
  193. ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
  194. /**
  195. * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
  196. * It was one of the first types, that is why it is abused so much ;-)
  197. * @deprecated since 2.0
  198. */
  199. define('PARAM_CLEAN', 'clean');
  200. /**
  201. * PARAM_INTEGER - deprecated alias for PARAM_INT
  202. */
  203. define('PARAM_INTEGER', 'int');
  204. /**
  205. * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
  206. */
  207. define('PARAM_NUMBER', 'float');
  208. /**
  209. * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
  210. * NOTE: originally alias for PARAM_APLHA
  211. */
  212. define('PARAM_ACTION', 'alphanumext');
  213. /**
  214. * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
  215. * NOTE: originally alias for PARAM_APLHA
  216. */
  217. define('PARAM_FORMAT', 'alphanumext');
  218. /**
  219. * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
  220. */
  221. define('PARAM_MULTILANG', 'text');
  222. /**
  223. * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
  224. */
  225. define('PARAM_CLEANFILE', 'file');
  226. /// Web Services ///
  227. /**
  228. * VALUE_REQUIRED - if the parameter is not supplied, there is an error
  229. */
  230. define('VALUE_REQUIRED', 1);
  231. /**
  232. * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
  233. */
  234. define('VALUE_OPTIONAL', 2);
  235. /**
  236. * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
  237. */
  238. define('VALUE_DEFAULT', 0);
  239. /**
  240. * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
  241. */
  242. define('NULL_NOT_ALLOWED', false);
  243. /**
  244. * NULL_ALLOWED - the parameter can be set to null in the database
  245. */
  246. define('NULL_ALLOWED', true);
  247. /// Page types ///
  248. /**
  249. * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
  250. */
  251. define('PAGE_COURSE_VIEW', 'course-view');
  252. /** Get remote addr constant */
  253. define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
  254. /** Get remote addr constant */
  255. define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
  256. /// Blog access level constant declaration ///
  257. define ('BLOG_USER_LEVEL', 1);
  258. define ('BLOG_GROUP_LEVEL', 2);
  259. define ('BLOG_COURSE_LEVEL', 3);
  260. define ('BLOG_SITE_LEVEL', 4);
  261. define ('BLOG_GLOBAL_LEVEL', 5);
  262. ///Tag constants///
  263. /**
  264. * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
  265. * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
  266. * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
  267. *
  268. * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
  269. */
  270. define('TAG_MAX_LENGTH', 50);
  271. /// Password policy constants ///
  272. define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
  273. define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  274. define ('PASSWORD_DIGITS', '0123456789');
  275. define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
  276. /// Feature constants ///
  277. // Used for plugin_supports() to report features that are, or are not, supported by a module.
  278. /** True if module can provide a grade */
  279. define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
  280. /** True if module supports outcomes */
  281. define('FEATURE_GRADE_OUTCOMES', 'outcomes');
  282. /** True if module has code to track whether somebody viewed it */
  283. define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
  284. /** True if module has custom completion rules */
  285. define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
  286. /** True if module has no 'view' page (like label) */
  287. define('FEATURE_NO_VIEW_LINK', 'viewlink');
  288. /** True if module supports outcomes */
  289. define('FEATURE_IDNUMBER', 'idnumber');
  290. /** True if module supports groups */
  291. define('FEATURE_GROUPS', 'groups');
  292. /** True if module supports groupings */
  293. define('FEATURE_GROUPINGS', 'groupings');
  294. /** True if module supports groupmembersonly */
  295. define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
  296. /** Type of module */
  297. define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
  298. /** True if module supports intro editor */
  299. define('FEATURE_MOD_INTRO', 'mod_intro');
  300. /** True if module has default completion */
  301. define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
  302. define('FEATURE_COMMENT', 'comment');
  303. define('FEATURE_RATE', 'rate');
  304. /** True if module supports backup/restore of moodle2 format */
  305. define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
  306. /** Unspecified module archetype */
  307. define('MOD_ARCHETYPE_OTHER', 0);
  308. /** Resource-like type module */
  309. define('MOD_ARCHETYPE_RESOURCE', 1);
  310. /** Assignment module archetype */
  311. define('MOD_ARCHETYPE_ASSIGNMENT', 2);
  312. /**
  313. * Security token used for allowing access
  314. * from external application such as web services.
  315. * Scripts do not use any session, performance is relatively
  316. * low because we need to load access info in each request.
  317. * Scripts are executed in parallel.
  318. */
  319. define('EXTERNAL_TOKEN_PERMANENT', 0);
  320. /**
  321. * Security token used for allowing access
  322. * of embedded applications, the code is executed in the
  323. * active user session. Token is invalidated after user logs out.
  324. * Scripts are executed serially - normal session locking is used.
  325. */
  326. define('EXTERNAL_TOKEN_EMBEDDED', 1);
  327. /**
  328. * The home page should be the site home
  329. */
  330. define('HOMEPAGE_SITE', 0);
  331. /**
  332. * The home page should be the users my page
  333. */
  334. define('HOMEPAGE_MY', 1);
  335. /**
  336. * The home page can be chosen by the user
  337. */
  338. define('HOMEPAGE_USER', 2);
  339. /**
  340. * Hub directory url (should be moodle.org)
  341. */
  342. define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
  343. /**
  344. * Moodle.org url (should be moodle.org)
  345. */
  346. define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
  347. /// PARAMETER HANDLING ////////////////////////////////////////////////////
  348. /**
  349. * Returns a particular value for the named variable, taken from
  350. * POST or GET. If the parameter doesn't exist then an error is
  351. * thrown because we require this variable.
  352. *
  353. * This function should be used to initialise all required values
  354. * in a script that are based on parameters. Usually it will be
  355. * used like this:
  356. * $id = required_param('id', PARAM_INT);
  357. *
  358. * Please note the $type parameter is now required,
  359. * for now PARAM_CLEAN is used for backwards compatibility only.
  360. *
  361. * @param string $parname the name of the page parameter we want
  362. * @param string $type expected type of parameter
  363. * @return mixed
  364. */
  365. function required_param($parname, $type) {
  366. if (!isset($type)) {
  367. debugging('required_param() requires $type to be specified.');
  368. $type = PARAM_CLEAN; // for now let's use this deprecated type
  369. }
  370. if (isset($_POST[$parname])) { // POST has precedence
  371. $param = $_POST[$parname];
  372. } else if (isset($_GET[$parname])) {
  373. $param = $_GET[$parname];
  374. } else {
  375. print_error('missingparam', '', '', $parname);
  376. }
  377. return clean_param($param, $type);
  378. }
  379. /**
  380. * Returns a particular value for the named variable, taken from
  381. * POST or GET, otherwise returning a given default.
  382. *
  383. * This function should be used to initialise all optional values
  384. * in a script that are based on parameters. Usually it will be
  385. * used like this:
  386. * $name = optional_param('name', 'Fred', PARAM_TEXT);
  387. *
  388. * Please note $default and $type parameters are now required,
  389. * for now PARAM_CLEAN is used for backwards compatibility only.
  390. *
  391. * @param string $parname the name of the page parameter we want
  392. * @param mixed $default the default value to return if nothing is found
  393. * @param string $type expected type of parameter
  394. * @return mixed
  395. */
  396. function optional_param($parname, $default, $type) {
  397. if (!isset($type)) {
  398. debugging('optional_param() requires $default and $type to be specified.');
  399. $type = PARAM_CLEAN; // for now let's use this deprecated type
  400. }
  401. if (!isset($default)) {
  402. $default = null;
  403. }
  404. if (isset($_POST[$parname])) { // POST has precedence
  405. $param = $_POST[$parname];
  406. } else if (isset($_GET[$parname])) {
  407. $param = $_GET[$parname];
  408. } else {
  409. return $default;
  410. }
  411. return clean_param($param, $type);
  412. }
  413. /**
  414. * Strict validation of parameter values, the values are only converted
  415. * to requested PHP type. Internally it is using clean_param, the values
  416. * before and after cleaning must be equal - otherwise
  417. * an invalid_parameter_exception is thrown.
  418. * Objects and classes are not accepted.
  419. *
  420. * @param mixed $param
  421. * @param int $type PARAM_ constant
  422. * @param bool $allownull are nulls valid value?
  423. * @param string $debuginfo optional debug information
  424. * @return mixed the $param value converted to PHP type or invalid_parameter_exception
  425. */
  426. function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
  427. if (is_null($param)) {
  428. if ($allownull == NULL_ALLOWED) {
  429. return null;
  430. } else {
  431. throw new invalid_parameter_exception($debuginfo);
  432. }
  433. }
  434. if (is_array($param) or is_object($param)) {
  435. throw new invalid_parameter_exception($debuginfo);
  436. }
  437. $cleaned = clean_param($param, $type);
  438. if ((string)$param !== (string)$cleaned) {
  439. // conversion to string is usually lossless
  440. throw new invalid_parameter_exception($debuginfo);
  441. }
  442. return $cleaned;
  443. }
  444. /**
  445. * Used by {@link optional_param()} and {@link required_param()} to
  446. * clean the variables and/or cast to specific types, based on
  447. * an options field.
  448. * <code>
  449. * $course->format = clean_param($course->format, PARAM_ALPHA);
  450. * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
  451. * </code>
  452. *
  453. * @param mixed $param the variable we are cleaning
  454. * @param int $type expected format of param after cleaning.
  455. * @return mixed
  456. */
  457. function clean_param($param, $type) {
  458. global $CFG;
  459. if (is_array($param)) { // Let's loop
  460. $newparam = array();
  461. foreach ($param as $key => $value) {
  462. $newparam[$key] = clean_param($value, $type);
  463. }
  464. return $newparam;
  465. }
  466. switch ($type) {
  467. case PARAM_RAW: // no cleaning at all
  468. return $param;
  469. case PARAM_RAW_TRIMMED: // no cleaning, but strip leading and trailing whitespace.
  470. return trim($param);
  471. case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
  472. // this is deprecated!, please use more specific type instead
  473. if (is_numeric($param)) {
  474. return $param;
  475. }
  476. return clean_text($param); // Sweep for scripts, etc
  477. case PARAM_CLEANHTML: // clean html fragment
  478. $param = clean_text($param, FORMAT_HTML); // Sweep for scripts, etc
  479. return trim($param);
  480. case PARAM_INT:
  481. return (int)$param; // Convert to integer
  482. case PARAM_FLOAT:
  483. case PARAM_NUMBER:
  484. return (float)$param; // Convert to float
  485. case PARAM_ALPHA: // Remove everything not a-z
  486. return preg_replace('/[^a-zA-Z]/i', '', $param);
  487. case PARAM_ALPHAEXT: // Remove everything not a-zA-Z_- (originally allowed "/" too)
  488. return preg_replace('/[^a-zA-Z_-]/i', '', $param);
  489. case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
  490. return preg_replace('/[^A-Za-z0-9]/i', '', $param);
  491. case PARAM_ALPHANUMEXT: // Remove everything not a-zA-Z0-9_-
  492. return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
  493. case PARAM_SEQUENCE: // Remove everything not 0-9,
  494. return preg_replace('/[^0-9,]/i', '', $param);
  495. case PARAM_BOOL: // Convert to 1 or 0
  496. $tempstr = strtolower($param);
  497. if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
  498. $param = 1;
  499. } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
  500. $param = 0;
  501. } else {
  502. $param = empty($param) ? 0 : 1;
  503. }
  504. return $param;
  505. case PARAM_NOTAGS: // Strip all tags
  506. return strip_tags($param);
  507. case PARAM_TEXT: // leave only tags needed for multilang
  508. // if the multilang syntax is not correct we strip all tags
  509. // because it would break xhtml strict which is required for accessibility standards
  510. // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
  511. do {
  512. if (strpos($param, '</lang>') !== false) {
  513. // old and future mutilang syntax
  514. $param = strip_tags($param, '<lang>');
  515. if (!preg_match_all('/<.*>/suU', $param, $matches)) {
  516. break;
  517. }
  518. $open = false;
  519. foreach ($matches[0] as $match) {
  520. if ($match === '</lang>') {
  521. if ($open) {
  522. $open = false;
  523. continue;
  524. } else {
  525. break 2;
  526. }
  527. }
  528. if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
  529. break 2;
  530. } else {
  531. $open = true;
  532. }
  533. }
  534. if ($open) {
  535. break;
  536. }
  537. return $param;
  538. } else if (strpos($param, '</span>') !== false) {
  539. // current problematic multilang syntax
  540. $param = strip_tags($param, '<span>');
  541. if (!preg_match_all('/<.*>/suU', $param, $matches)) {
  542. break;
  543. }
  544. $open = false;
  545. foreach ($matches[0] as $match) {
  546. if ($match === '</span>') {
  547. if ($open) {
  548. $open = false;
  549. continue;
  550. } else {
  551. break 2;
  552. }
  553. }
  554. if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
  555. break 2;
  556. } else {
  557. $open = true;
  558. }
  559. }
  560. if ($open) {
  561. break;
  562. }
  563. return $param;
  564. }
  565. } while (false);
  566. // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
  567. return strip_tags($param);
  568. case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
  569. return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
  570. case PARAM_SAFEPATH: // Remove everything not a-zA-Z0-9/_-
  571. return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
  572. case PARAM_FILE: // Strip all suspicious characters from filename
  573. $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
  574. $param = preg_replace('~\.\.+~', '', $param);
  575. if ($param === '.') {
  576. $param = '';
  577. }
  578. return $param;
  579. case PARAM_PATH: // Strip all suspicious characters from file path
  580. $param = str_replace('\\', '/', $param);
  581. $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
  582. $param = preg_replace('~\.\.+~', '', $param);
  583. $param = preg_replace('~//+~', '/', $param);
  584. return preg_replace('~/(\./)+~', '/', $param);
  585. case PARAM_HOST: // allow FQDN or IPv4 dotted quad
  586. $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
  587. // match ipv4 dotted quad
  588. if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
  589. // confirm values are ok
  590. if ( $match[0] > 255
  591. || $match[1] > 255
  592. || $match[3] > 255
  593. || $match[4] > 255 ) {
  594. // hmmm, what kind of dotted quad is this?
  595. $param = '';
  596. }
  597. } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
  598. && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
  599. && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
  600. ) {
  601. // all is ok - $param is respected
  602. } else {
  603. // all is not ok...
  604. $param='';
  605. }
  606. return $param;
  607. case PARAM_URL: // allow safe ftp, http, mailto urls
  608. include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
  609. if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
  610. // all is ok, param is respected
  611. } else {
  612. $param =''; // not really ok
  613. }
  614. return $param;
  615. case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
  616. $param = clean_param($param, PARAM_URL);
  617. if (!empty($param)) {
  618. if (preg_match(':^/:', $param)) {
  619. // root-relative, ok!
  620. } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
  621. // absolute, and matches our wwwroot
  622. } else {
  623. // relative - let's make sure there are no tricks
  624. if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
  625. // looks ok.
  626. } else {
  627. $param = '';
  628. }
  629. }
  630. }
  631. return $param;
  632. case PARAM_PEM:
  633. $param = trim($param);
  634. // PEM formatted strings may contain letters/numbers and the symbols
  635. // forward slash: /
  636. // plus sign: +
  637. // equal sign: =
  638. // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
  639. if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
  640. list($wholething, $body) = $matches;
  641. unset($wholething, $matches);
  642. $b64 = clean_param($body, PARAM_BASE64);
  643. if (!empty($b64)) {
  644. return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
  645. } else {
  646. return '';
  647. }
  648. }
  649. return '';
  650. case PARAM_BASE64:
  651. if (!empty($param)) {
  652. // PEM formatted strings may contain letters/numbers and the symbols
  653. // forward slash: /
  654. // plus sign: +
  655. // equal sign: =
  656. if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
  657. return '';
  658. }
  659. $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
  660. // Each line of base64 encoded data must be 64 characters in
  661. // length, except for the last line which may be less than (or
  662. // equal to) 64 characters long.
  663. for ($i=0, $j=count($lines); $i < $j; $i++) {
  664. if ($i + 1 == $j) {
  665. if (64 < strlen($lines[$i])) {
  666. return '';
  667. }
  668. continue;
  669. }
  670. if (64 != strlen($lines[$i])) {
  671. return '';
  672. }
  673. }
  674. return implode("\n",$lines);
  675. } else {
  676. return '';
  677. }
  678. case PARAM_TAG:
  679. // Please note it is not safe to use the tag name directly anywhere,
  680. // it must be processed with s(), urlencode() before embedding anywhere.
  681. // remove some nasties
  682. $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
  683. //convert many whitespace chars into one
  684. $param = preg_replace('/\s+/', ' ', $param);
  685. $textlib = textlib_get_instance();
  686. $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
  687. return $param;
  688. case PARAM_TAGLIST:
  689. $tags = explode(',', $param);
  690. $result = array();
  691. foreach ($tags as $tag) {
  692. $res = clean_param($tag, PARAM_TAG);
  693. if ($res !== '') {
  694. $result[] = $res;
  695. }
  696. }
  697. if ($result) {
  698. return implode(',', $result);
  699. } else {
  700. return '';
  701. }
  702. case PARAM_CAPABILITY:
  703. if (get_capability_info($param)) {
  704. return $param;
  705. } else {
  706. return '';
  707. }
  708. case PARAM_PERMISSION:
  709. $param = (int)$param;
  710. if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
  711. return $param;
  712. } else {
  713. return CAP_INHERIT;
  714. }
  715. case PARAM_AUTH:
  716. $param = clean_param($param, PARAM_SAFEDIR);
  717. if (exists_auth_plugin($param)) {
  718. return $param;
  719. } else {
  720. return '';
  721. }
  722. case PARAM_LANG:
  723. $param = clean_param($param, PARAM_SAFEDIR);
  724. if (get_string_manager()->translation_exists($param)) {
  725. return $param;
  726. } else {
  727. return ''; // Specified language is not installed or param malformed
  728. }
  729. case PARAM_THEME:
  730. $param = clean_param($param, PARAM_SAFEDIR);
  731. if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
  732. return $param;
  733. } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
  734. return $param;
  735. } else {
  736. return ''; // Specified theme is not installed
  737. }
  738. case PARAM_USERNAME:
  739. $param = str_replace(" " , "", $param);
  740. $param = moodle_strtolower($param); // Convert uppercase to lowercase MDL-16919
  741. if (empty($CFG->extendedusernamechars)) {
  742. // regular expression, eliminate all chars EXCEPT:
  743. // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
  744. $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
  745. }
  746. return $param;
  747. case PARAM_EMAIL:
  748. if (validate_email($param)) {
  749. return $param;
  750. } else {
  751. return '';
  752. }
  753. case PARAM_STRINGID:
  754. if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
  755. return $param;
  756. } else {
  757. return '';
  758. }
  759. default: // throw error, switched parameters in optional_param or another serious problem
  760. print_error("unknownparamtype", '', '', $type);
  761. }
  762. }
  763. /**
  764. * Return true if given value is integer or string with integer value
  765. *
  766. * @param mixed $value String or Int
  767. * @return bool true if number, false if not
  768. */
  769. function is_number($value) {
  770. if (is_int($value)) {
  771. return true;
  772. } else if (is_string($value)) {
  773. return ((string)(int)$value) === $value;
  774. } else {
  775. return false;
  776. }
  777. }
  778. /**
  779. * Returns host part from url
  780. * @param string $url full url
  781. * @return string host, null if not found
  782. */
  783. function get_host_from_url($url) {
  784. preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
  785. if ($matches) {
  786. return $matches[1];
  787. }
  788. return null;
  789. }
  790. /**
  791. * Tests whether anything was returned by text editor
  792. *
  793. * This function is useful for testing whether something you got back from
  794. * the HTML editor actually contains anything. Sometimes the HTML editor
  795. * appear to be empty, but actually you get back a <br> tag or something.
  796. *
  797. * @param string $string a string containing HTML.
  798. * @return boolean does the string contain any actual content - that is text,
  799. * images, objects, etc.
  800. */
  801. function html_is_blank($string) {
  802. return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
  803. }
  804. /**
  805. * Set a key in global configuration
  806. *
  807. * Set a key/value pair in both this session's {@link $CFG} global variable
  808. * and in the 'config' database table for future sessions.
  809. *
  810. * Can also be used to update keys for plugin-scoped configs in config_plugin table.
  811. * In that case it doesn't affect $CFG.
  812. *
  813. * A NULL value will delete the entry.
  814. *
  815. * @global object
  816. * @global object
  817. * @param string $name the key to set
  818. * @param string $value the value to set (without magic quotes)
  819. * @param string $plugin (optional) the plugin scope, default NULL
  820. * @return bool true or exception
  821. */
  822. function set_config($name, $value, $plugin=NULL) {
  823. global $CFG, $DB;
  824. if (empty($plugin)) {
  825. if (!array_key_exists($name, $CFG->config_php_settings)) {
  826. // So it's defined for this invocation at least
  827. if (is_null($value)) {
  828. unset($CFG->$name);
  829. } else {
  830. $CFG->$name = (string)$value; // settings from db are always strings
  831. }
  832. }
  833. if ($DB->get_field('config', 'name', array('name'=>$name))) {
  834. if ($value === null) {
  835. $DB->delete_records('config', array('name'=>$name));
  836. } else {
  837. $DB->set_field('config', 'value', $value, array('name'=>$name));
  838. }
  839. } else {
  840. if ($value !== null) {
  841. $config = new stdClass();
  842. $config->name = $name;
  843. $config->value = $value;
  844. $DB->insert_record('config', $config, false);
  845. }
  846. }
  847. } else { // plugin scope
  848. if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
  849. if ($value===null) {
  850. $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
  851. } else {
  852. $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
  853. }
  854. } else {
  855. if ($value !== null) {
  856. $config = new stdClass();
  857. $config->plugin = $plugin;
  858. $config->name = $name;
  859. $config->value = $value;
  860. $DB->insert_record('config_plugins', $config, false);
  861. }
  862. }
  863. }
  864. return true;
  865. }
  866. /**
  867. * Get configuration values from the global config table
  868. * or the config_plugins table.
  869. *
  870. * If called with one parameter, it will load all the config
  871. * variables for one plugin, and return them as an object.
  872. *
  873. * If called with 2 parameters it will return a string single
  874. * value or false if the value is not found.
  875. *
  876. * @param string $plugin full component name
  877. * @param string $name default NULL
  878. * @return mixed hash-like object or single value, return false no config found
  879. */
  880. function get_config($plugin, $name = NULL) {
  881. global $CFG, $DB;
  882. // normalise component name
  883. if ($plugin === 'moodle' or $plugin === 'core') {
  884. $plugin = NULL;
  885. }
  886. if (!empty($name)) { // the user is asking for a specific value
  887. if (!empty($plugin)) {
  888. if (isset($CFG->forced_plugin_settings[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings[$plugin])) {
  889. // setting forced in config file
  890. return $CFG->forced_plugin_settings[$plugin][$name];
  891. } else {
  892. return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
  893. }
  894. } else {
  895. if (array_key_exists($name, $CFG->config_php_settings)) {
  896. // setting force in config file
  897. return $CFG->config_php_settings[$name];
  898. } else {
  899. return $DB->get_field('config', 'value', array('name'=>$name));
  900. }
  901. }
  902. }
  903. // the user is after a recordset
  904. if ($plugin) {
  905. $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
  906. if (isset($CFG->forced_plugin_settings[$plugin])) {
  907. foreach($CFG->forced_plugin_settings[$plugin] as $n=>$v) {
  908. if (is_null($v) or is_array($v) or is_object($v)) {
  909. // we do not want any extra mess here, just real settings that could be saved in db
  910. unset($localcfg[$n]);
  911. } else {
  912. //convert to string as if it went through the DB
  913. $localcfg[$n] = (string)$v;
  914. }
  915. }
  916. }
  917. if ($localcfg) {
  918. return (object)$localcfg;
  919. } else {
  920. return null;
  921. }
  922. } else {
  923. // this part is not really used any more, but anyway...
  924. $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
  925. foreach($CFG->config_php_settings as $n=>$v) {
  926. if (is_null($v) or is_array($v) or is_object($v)) {
  927. // we do not want any extra mess here, just real settings that could be saved in db
  928. unset($localcfg[$n]);
  929. } else {
  930. //convert to string as if it went through the DB
  931. $localcfg[$n] = (string)$v;
  932. }
  933. }
  934. return (object)$localcfg;
  935. }
  936. }
  937. /**
  938. * Removes a key from global configuration
  939. *
  940. * @param string $name the key to set
  941. * @param string $plugin (optional) the plugin scope
  942. * @global object
  943. * @return boolean whether the operation succeeded.
  944. */
  945. function unset_config($name, $plugin=NULL) {
  946. global $CFG, $DB;
  947. if (empty($plugin)) {
  948. unset($CFG->$name);
  949. $DB->delete_records('config', array('name'=>$name));
  950. } else {
  951. $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
  952. }
  953. return true;
  954. }
  955. /**
  956. * Remove all the config variables for a given plugin.
  957. *
  958. * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
  959. * @return boolean whether the operation succeeded.
  960. */
  961. function unset_all_config_for_plugin($plugin) {
  962. global $DB;
  963. $DB->delete_records('config_plugins', array('plugin' => $plugin));
  964. $DB->delete_records_select('config', 'name LIKE ?', array($plugin . '_%'));
  965. return true;
  966. }
  967. /**
  968. * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
  969. *
  970. * All users are verified if they still have the necessary capability.
  971. *
  972. * @param string $value the value of the config setting.
  973. * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
  974. * @param bool $include admins, include administrators
  975. * @return array of user objects.
  976. */
  977. function get_users_from_config($value, $capability, $includeadmins = true) {
  978. global $CFG, $DB;
  979. if (empty($value) or $value === '$@NONE@$') {
  980. return array();
  981. }
  982. // we have to make sure that users still have the necessary capability,
  983. // it should be faster to fetch them all first and then test if they are present
  984. // instead of validating them one-by-one
  985. $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
  986. if ($includeadmins) {
  987. $admins = get_admins();
  988. foreach ($admins as $admin) {
  989. $users[$admin->id] = $admin;
  990. }
  991. }
  992. if ($value === '$@ALL@$') {
  993. return $users;
  994. }
  995. $result = array(); // result in correct order
  996. $allowed = explode(',', $value);
  997. foreach ($allowed as $uid) {
  998. if (isset($users[$uid])) {
  999. $user = $users[$uid];
  1000. $result[$user->id] = $user;
  1001. }
  1002. }
  1003. return $result;
  1004. }
  1005. /**
  1006. * Invalidates browser caches and cached data in temp
  1007. * @return void
  1008. */
  1009. function purge_all_caches() {
  1010. global $CFG;
  1011. reset_text_filters_cache();
  1012. js_reset_all_caches();
  1013. theme_reset_all_caches();
  1014. get_string_manager()->reset_caches();
  1015. // purge all other caches: rss, simplepie, etc.
  1016. remove_dir($CFG->dataroot.'/cache', true);
  1017. // make sure cache dir is writable, throws exception if not
  1018. make_upload_directory('cache');
  1019. clearstatcache();
  1020. }
  1021. /**
  1022. * Get volatile flags
  1023. *
  1024. * @param string $type
  1025. * @param int $changedsince default null
  1026. * @return records array
  1027. */
  1028. function get_cache_flags($type, $changedsince=NULL) {
  1029. global $DB;
  1030. $params = array('type'=>$type, 'expiry'=>time());
  1031. $sqlwhere = "flagtype = :type AND expiry >= :expiry";
  1032. if ($changedsince !== NULL) {
  1033. $params['changedsince'] = $changedsince;
  1034. $sqlwhere .= " AND timemodified > :changedsince";
  1035. }
  1036. $cf = array();
  1037. if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
  1038. foreach ($flags as $flag) {
  1039. $cf[$flag->name] = $flag->value;
  1040. }
  1041. }
  1042. return $cf;
  1043. }
  1044. /**
  1045. * Get volatile flags
  1046. *
  1047. * @param string $type
  1048. * @param string $name
  1049. * @param int $changedsince default null
  1050. * @return records array
  1051. */
  1052. function get_cache_flag($type, $name, $changedsince=NULL) {
  1053. global $DB;
  1054. $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
  1055. $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
  1056. if ($changedsince !== NULL) {
  1057. $params['changedsince'] = $changedsince;
  1058. $sqlwhere .= " AND timemodified > :changedsince";
  1059. }
  1060. return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
  1061. }
  1062. /**
  1063. * Set a volatile flag
  1064. *
  1065. * @param string $type the "type" namespace for the key
  1066. * @param string $name the key to set
  1067. * @param string $value the value to set (without magic quotes) - NULL will remove the flag
  1068. * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
  1069. * @return bool Always returns true
  1070. */
  1071. function set_cache_flag($type, $name, $value, $expiry=NULL) {
  1072. global $DB;
  1073. $timemodified = time();
  1074. if ($expiry===NULL || $expiry < $timemodified) {
  1075. $expiry = $timemodified + 24 * 60 * 60;
  1076. } else {
  1077. $expiry = (int)$expiry;
  1078. }
  1079. if ($value === NULL) {
  1080. unset_cache_flag($type,$name);
  1081. return true;
  1082. }
  1083. if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE)) { // this is a potential problem in DEBUG_DEVELOPER
  1084. if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
  1085. return true; //no need to update; helps rcache too
  1086. }
  1087. $f->value = $value;
  1088. $f->expiry = $expiry;
  1089. $f->timemodified = $timemodified;
  1090. $DB->update_record('cache_flags', $f);
  1091. } else {
  1092. $f = new stdClass();
  1093. $f->flagtype = $type;
  1094. $f->name = $name;
  1095. $f->value = $value;
  1096. $f->expiry = $expiry;
  1097. $f->timemodified = $timemodified;
  1098. $DB->insert_record('cache_flags', $f);
  1099. }
  1100. return true;
  1101. }
  1102. /**
  1103. * Removes a single volatile flag
  1104. *
  1105. * @global object
  1106. * @param string $type the "type" namespace for the key
  1107. * @param string $name the key to set
  1108. * @return bool
  1109. */
  1110. function unset_cache_flag($type, $name) {
  1111. global $DB;
  1112. $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
  1113. return true;
  1114. }
  1115. /**
  1116. * Garbage-collect volatile flags
  1117. *
  1118. * @return bool Always returns true
  1119. */
  1120. function gc_cache_flags() {
  1121. global $DB;
  1122. $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
  1123. return true;
  1124. }
  1125. /// FUNCTIONS FOR HANDLING USER PREFERENCES ////////////////////////////////////
  1126. /**
  1127. * Refresh user preference cache. This is used most often for $USER
  1128. * object that is stored in session, but it also helps with performance in cron script.
  1129. *
  1130. * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
  1131. *
  1132. * @param stdClass $user user object, preferences are preloaded into ->preference property
  1133. * @param int $cachelifetime cache life time on the current page (ins seconds)
  1134. * @return void
  1135. */
  1136. function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
  1137. global $DB;
  1138. static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
  1139. if (!isset($user->id)) {
  1140. throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
  1141. }
  1142. if (empty($user->id) or isguestuser($user->id)) {
  1143. // No permanent storage for not-logged-in users and guest
  1144. if (!isset($user->preference)) {
  1145. $user->preference = array();
  1146. }
  1147. return;
  1148. }
  1149. $timenow = time();
  1150. if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
  1151. // Already loaded at least once on this page. Are we up to date?
  1152. if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
  1153. // no need to reload - we are on the same page and we loaded prefs just a moment ago
  1154. return;
  1155. } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
  1156. // no change since the lastcheck on this page
  1157. $user->preference['_lastloaded'] = $timenow;
  1158. return;
  1159. }
  1160. }
  1161. // OK, so we have to reload all preferences
  1162. $loadedusers[$user->id] = true;
  1163. $user->preference = $DB->get_records_menu('user_preferences', array('userid'=>$user->id), '', 'name,value'); // All values
  1164. $user->preference['_lastloaded'] = $timenow;
  1165. }
  1166. /**
  1167. * Called from set/delete_user_preferences, so that the prefs can
  1168. * be correctly reloaded in different sessions.
  1169. *
  1170. * NOTE: internal function, do not call from other code.
  1171. *
  1172. * @param integer $userid the user whose prefs were changed.
  1173. * @return void
  1174. */
  1175. function mark_user_preferences_changed($userid) {
  1176. global $CFG;
  1177. if (empty($userid) or isguestuser($userid)) {
  1178. // no cache flags for guest and not-logged-in users
  1179. return;
  1180. }
  1181. set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
  1182. }
  1183. /**
  1184. * Sets a preference for the specified user.
  1185. *
  1186. * If user object submitted, 'preference' property contains the preferences cache.
  1187. *
  1188. * @param string $name The key to set as preference for the specified user
  1189. * @param string $value The value to set for the $name key in the specified user's record,
  1190. * null means delete current value
  1191. * @param stdClass|int $user A moodle user object or id, null means current user
  1192. * @return bool always true or exception
  1193. */
  1194. function set_user_preference($name, $value, $user = null) {
  1195. global $USER, $DB;
  1196. if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
  1197. throw new coding_exception('Invalid preference name in set_user_preference() call');
  1198. }
  1199. if (is_null($value)) {
  1200. // null means delete current
  1201. return unset_user_preference($name, $user);
  1202. } else if (is_object($value)) {
  1203. throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
  1204. } else if (is_array($value)) {
  1205. throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
  1206. }
  1207. $value = (string)$value;
  1208. if (is_null($user)) {
  1209. $user = $USER;
  1210. } else if (isset($user->id)) {
  1211. // $user is valid object
  1212. } else if (is_numeric($user)) {
  1213. $user = (object)array('id'=>(int)$user);
  1214. } else {
  1215. throw new coding_exception('Invalid $user parameter in set_user_preference() call');
  1216. }
  1217. check_user_preferences_loaded($user);
  1218. if (empty($user->id) or isguestuser($user->id)) {
  1219. // no permanent storage for not-logged-in users and guest
  1220. $user->preference[$name] = $value;
  1221. return true;
  1222. }
  1223. if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>$name))) {
  1224. if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
  1225. // preference already set to this value
  1226. return true;
  1227. }
  1228. $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id));
  1229. } else {
  1230. $preference = new stdClass();
  1231. $preference->userid = $user->id;
  1232. $preference->name = $name;
  1233. $preference->value = $value;
  1234. $DB->insert_record('user_preferences', $preference);
  1235. }
  1236. // update value in cache
  1237. $user->preference[$name] = $value;
  1238. // set reload flag for other sessions
  1239. mark_user_preferences_changed($user->id);
  1240. return true;
  1241. }
  1242. /**
  1243. * Sets a whole array of preferences for the current user
  1244. *
  1245. * If user object submitted, 'preference' property contains the preferences cache.
  1246. *
  1247. * @param array $prefarray An array of key/value pairs to be set
  1248. * @param stdClass|int $user A moodle user object or id, null means current user
  1249. * @return bool always true or exception
  1250. */
  1251. function set_user_preferences(array $prefarray, $user = null) {
  1252. foreach ($prefarray as $name => $value) {
  1253. set_user_preference($name, $value, $user);
  1254. }
  1255. return true;
  1256. }
  1257. /**
  1258. * Unsets a preference completely by deleting it from the database
  1259. *
  1260. * If user object submitted, 'preference' property contains the preferences cache.
  1261. *
  1262. * @param string $name The key to unset as preference for the specified user
  1263. * @param stdClass|int $user A moodle user object or id, null means current user
  1264. * @return bool always true or exception
  1265. */
  1266. function unset_user_preference($name, $user = null) {
  1267. global $USER, $DB;
  1268. if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
  1269. throw new coding_exception('Invalid preference name in unset_user_preference() call');
  1270. }
  1271. if (is_null($user)) {
  1272. $user = $USER;
  1273. } else if (isset($user->id)) {
  1274. // $user is valid object
  1275. } else if (is_numeric($user)) {
  1276. $user = (object)array('id'=>(int)$user);
  1277. } else {
  1278. throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
  1279. }
  1280. check_user_preferences_loaded($user);
  1281. if (empty($user->id) or isguestuser($user->id)) {
  1282. // no permanent storage for not-logged-in user and guest
  1283. unset($user->preference[$name]);
  1284. return true;
  1285. }
  1286. // delete from DB
  1287. $DB->delete_records('user_preferences', array('userid'=>$user->id, 'name'=>$name));
  1288. // delete the preference from cache
  1289. unset($user->preference[$name]);
  1290. // set reload flag for other sessions
  1291. mark_user_preferences_changed($user->id);
  1292. return true;
  1293. }
  1294. /**
  1295. * Used to fetch user preference(s)
  1296. *
  1297. * If no arguments are supplied this function will return
  1298. * all of the current user preferences as an array.
  1299. *
  1300. * If a name is specified then this function
  1301. * attempts to return that particular preference value. If
  1302. * none is found, then the optional value $default is returned,
  1303. * otherwise NULL.
  1304. *
  1305. * If user object submitted, 'preference' property contains the preferences cache.
  1306. *
  1307. * @param string $name Name of the key to use in finding a preference value
  1308. * @param mixed $default Value to be returned if the $name key is not set in the user preferences
  1309. * @param stdClass|int $user A moodle user object or id, null means current user
  1310. * @return mixed string value or default
  1311. */
  1312. function get_user_preferences($name = null, $default = null, $user = null) {
  1313. global $USER;
  1314. if (is_null($name)) {
  1315. // all prefs
  1316. } else if (is_numeric($name) or $name === '_lastloaded') {
  1317. throw new coding_exception('Invalid preference name in get_user_preferences() call');
  1318. }
  1319. if (is_null($user)) {
  1320. $user = $USER;
  1321. } else if (isset($user->id)) {
  1322. // $user is valid object
  1323. } else if (is_numeric($user)) {
  1324. $user = (object)array('id'=>(int)$user);
  1325. } else {
  1326. throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
  1327. }
  1328. check_user_preferences_loaded($user);
  1329. if (empty($name)) {
  1330. return $user->preference; // All values
  1331. } else if (isset($user->preference[$name])) {
  1332. return $user->preference[$name]; // The single string value
  1333. } else {
  1334. return $default; // Default value (null if not specified)
  1335. }
  1336. }
  1337. /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
  1338. /**
  1339. * Given date parts in user time produce a GMT timestamp.
  1340. *
  1341. * @todo Finish documenting this function
  1342. * @param int $year The year part to create timestamp of
  1343. * @param int $month The month part to create timestamp of
  1344. * @param int $day The day part to create timestamp of
  1345. * @param int $hour The hour part to create timestamp of
  1346. * @param int $minute The minute part to create timestamp of
  1347. * @param int $second The second part to create timestamp of
  1348. * @param mixed $timezone Timezone modifier, if 99 then use default user's timezone
  1349. * @param bool $applydst Toggle Daylight Saving Time, default true, will be
  1350. * applied only if timezone is 99 or string.
  1351. * @return int timestamp
  1352. */
  1353. function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
  1354. //save input timezone, required for dst offset check.
  1355. $passedtimezone = $timezone;
  1356. $timezone = get_user_timezone_offset($timezone);
  1357. if (abs($timezone) > 13) { //server time
  1358. $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
  1359. } else {
  1360. $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
  1361. $time = usertime($time, $timezone);
  1362. //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
  1363. if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
  1364. $time -= dst_offset_on($time, $passedtimezone);
  1365. }
  1366. }
  1367. return $time;
  1368. }
  1369. /**
  1370. * Format a date/time (seconds) as weeks, days, hours etc as needed
  1371. *
  1372. * Given an amount of time in seconds, returns string
  1373. * formatted nicely as weeks, days, hours etc as needed
  1374. *
  1375. * @uses MINSECS
  1376. * @uses HOURSECS
  1377. * @uses DAYSECS
  1378. * @uses YEARSECS
  1379. * @param int $totalsecs Time in seconds
  1380. * @param object $str Should be a time object
  1381. * @return string A nicely formatted date/time string
  1382. */
  1383. function format_time($totalsecs, $str=NULL) {
  1384. $totalsecs = abs($totalsecs);
  1385. if (!$str) { // Create the str structure the slow way
  1386. $str->day = get_string('day');
  1387. $str->days = get_string('days');
  1388. $str->hour = get_string('hour');
  1389. $str->hours = get_string('hours');
  1390. $str->min = get_string('min');
  1391. $str->mins = get_string('mins');
  1392. $str->sec = get_string('sec');
  1393. $str->secs = get_string('secs');
  1394. $str->year = get_string('year');
  1395. $str->years = get_string('years');
  1396. }
  1397. $years = floor($totalsecs/YEARSECS);
  1398. $remainder = $totalsecs - ($years*YEARSECS);
  1399. $days = floor($remainder/DAYSECS);
  1400. $remainder = $totalsecs - ($days*DAYSECS);
  1401. $hours = floor($remainder/HOURSECS);
  1402. $remainder = $remainder - ($hours*HOURSECS);
  1403. $mins = floor($remainder/MINSECS);
  1404. $secs = $remainder - ($mins*MINSECS);
  1405. $ss = ($secs == 1) ? $str->sec : $str->secs;
  1406. $sm = ($mins == 1) ? $str->min : $str->mins;
  1407. $sh = ($hours == 1) ? $str->hour : $str->hours;
  1408. $sd = ($days == 1) ? $str->day : $str->days;
  1409. $sy = ($years == 1) ? $str->year : $str->years;
  1410. $oyears = '';
  1411. $odays = '';
  1412. $ohours = '';
  1413. $omins = '';
  1414. $osecs = '';
  1415. if ($years) $oyears = $years .' '. $sy;
  1416. if ($days) $odays = $days .' '. $sd;
  1417. if ($hours) $ohours = $hours .' '. $sh;
  1418. if ($mins) $omins = $mins .' '. $sm;
  1419. if ($secs) $osecs = $secs .' '. $ss;
  1420. if ($years) return trim($oyears .' '. $odays);
  1421. if ($days) return trim($odays .' '. $ohours);
  1422. if ($hours) return trim($ohours .' '. $omins);
  1423. if ($mins) return trim($omins .' '. $osecs);
  1424. if ($secs) return $osecs;
  1425. return get_string('now');
  1426. }
  1427. /**
  1428. * Returns a formatted string that represents a date in user time
  1429. *
  1430. * Returns a formatted string that represents a date in user time
  1431. * <b>WARNING: note that the format is for strftime(), not date().</b>
  1432. * Because of a bug in most Windows time libraries, we can't use
  1433. * the nicer %e, so we have to use %d which has leading zeroes.
  1434. * A lot of the fuss in the function is just getting rid of these leading
  1435. * zeroes as efficiently as possible.
  1436. *
  1437. * If parameter fixday = true (default), then take off leading
  1438. * zero from %d, else maintain it.
  1439. *
  1440. * @param int $date the timestamp in UTC, as obtained from the database.
  1441. * @param string $format strftime format. You should probably get this using
  1442. * get_string('strftime...', 'langconfig');
  1443. * @param mixed $timezone by default, uses the user's time zone. if numeric and
  1444. * not 99 then daylight saving will not be added.
  1445. * @param bool $fixday If true (default) then the leading zero from %d is removed.
  1446. * If false then the leading zero is maintained.
  1447. * @return string the formatted date/time.
  1448. */
  1449. function userdate($date, $format = '', $timezone = 99, $fixday = true) {
  1450. global $CFG;
  1451. if (empty($format)) {
  1452. $format = get_string('strftimedaydatetime', 'langconfig');
  1453. }
  1454. if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
  1455. $fixday = false;
  1456. } else if ($fixday) {
  1457. $formatnoday = str_replace('%d', 'DD', $format);
  1458. $fixday = ($formatnoday != $format);
  1459. }
  1460. //add daylight saving offset for string timezones only, as we can't get dst for
  1461. //float values. if timezone is 99 (user default timezone), then try update dst.
  1462. if ((99 == $timezone) || !is_numeric($timezone)) {
  1463. $date += dst_offset_on($date, $timezone);
  1464. }
  1465. $timezone = get_user_timezone_offset($timezone);
  1466. if (abs($timezone) > 13) { /// Server time
  1467. if ($fixday) {
  1468. $datestring = strftime($formatnoday, $date);
  1469. $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
  1470. $datestring = str_replace('DD', $daystring, $datestring);
  1471. } else {
  1472. $datestring = strftime($format, $date);
  1473. }
  1474. } else {
  1475. $date += (int)($timezone * 3600);
  1476. if ($fixday) {
  1477. $datestring = gmstrftime($formatnoday, $date);
  1478. $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
  1479. $datestring = str_replace('DD', $daystring, $datestring);
  1480. } else {
  1481. $datestring = gmstrftime($format, $date);
  1482. }
  1483. }
  1484. /// If we are running under Windows convert from windows encoding to UTF-8
  1485. /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
  1486. if ($CFG->ostype == 'WINDOWS') {
  1487. if ($localewincharset = get_string('localewincharset', 'langconfig')) {
  1488. $textlib = textlib_get_instance();
  1489. $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
  1490. }
  1491. }
  1492. return $datestring;
  1493. }
  1494. /**
  1495. * Given a $time timestamp in GMT (seconds since epoch),
  1496. * returns an array that represents the date in user time
  1497. *
  1498. * @todo Finish documenting this function
  1499. * @uses HOURSECS
  1500. * @param int $time Timestamp in GMT
  1501. * @param mixed $timezone offset time with timezone, if float and not 99, then no
  1502. * dst offset is applyed
  1503. * @return array An array that represents the date in user time
  1504. */
  1505. function usergetdate($time, $timezone=99) {
  1506. //save input timezone, required for dst offset check.
  1507. $passedtimezone = $timezone;
  1508. $timezone = get_user_timezone_offset($timezone);
  1509. if (abs($timezone) > 13) { // Server time
  1510. return getdate($time);
  1511. }
  1512. //add daylight saving offset for string timezones only, as we can't get dst for
  1513. //float values. if timezone is 99 (user default timezone), then try update dst.
  1514. if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
  1515. $time += dst_offset_on($time, $passedtimezone);
  1516. }
  1517. $time += intval((float)$timezone * HOURSECS);
  1518. $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
  1519. //be careful to ensure the returned array matches that produced by getdate() above
  1520. list(
  1521. $getdate['month'],
  1522. $getdate['weekday'],
  1523. $getdate['yday'],
  1524. $getdate['year'],
  1525. $getdate['mon'],
  1526. $getdate['wday'],
  1527. $getdate['mday'],
  1528. $getdate['hours'],
  1529. $getdate['minutes'],
  1530. $getdate['seconds']
  1531. ) = explode('_', $datestring);
  1532. return $getdate;
  1533. }
  1534. /**
  1535. * Given a GMT timestamp (seconds since epoch), offsets it by
  1536. * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
  1537. *
  1538. * @uses HOURSECS
  1539. * @param int $date Timestamp in GMT
  1540. * @param float $timezone
  1541. * @return int
  1542. */
  1543. function usertime($date, $timezone=99) {
  1544. $timezone = get_user_timezone_offset($timezone);
  1545. if (abs($timezone) > 13) {
  1546. return $date;
  1547. }
  1548. return $date - (int)($timezone * HOURSECS);
  1549. }
  1550. /**
  1551. * Given a time, return the GMT timestamp of the most recent midnight
  1552. * for the current user.
  1553. *
  1554. * @param int $date Timestamp in GMT
  1555. * @param float $timezone Defaults to user's timezone
  1556. * @return int Returns a GMT timestamp
  1557. */
  1558. function usergetmidnight($date, $timezone=99) {
  1559. $userdate = usergetdate($date, $timezone);
  1560. // Time of midnight of this user's day, in GMT
  1561. return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
  1562. }
  1563. /**
  1564. * Returns a string that prints the user's timezone
  1565. *
  1566. * @param float $timezone The user's timezone
  1567. * @return string
  1568. */
  1569. function usertimezone($timezone=99) {
  1570. $tz = get_user_timezone($timezone);
  1571. if (!is_float($tz)) {
  1572. return $tz;
  1573. }
  1574. if(abs($tz) > 13) { // Server time
  1575. return get_string('serverlocaltime');
  1576. }
  1577. if($tz == intval($tz)) {
  1578. // Don't show .0 for whole hours
  1579. $tz = intval($tz);
  1580. }
  1581. if($tz == 0) {
  1582. return 'UTC';
  1583. }
  1584. else if($tz > 0) {
  1585. return 'UTC+'.$tz;
  1586. }
  1587. else {
  1588. return 'UTC'.$tz;
  1589. }
  1590. }
  1591. /**
  1592. * Returns a float which represents the user's timezone difference from GMT in hours
  1593. * Checks various settings and picks the most dominant of those which have a value
  1594. *
  1595. * @global object
  1596. * @global object
  1597. * @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
  1598. * @return float
  1599. */
  1600. function get_user_timezone_offset($tz = 99) {
  1601. global $USER, $CFG;
  1602. $tz = get_user_timezone($tz);
  1603. if (is_float($tz)) {
  1604. return $tz;
  1605. } else {
  1606. $tzrecord = get_timezone_record($tz);
  1607. if (empty($tzrecord)) {
  1608. return 99.0;
  1609. }
  1610. return (float)$tzrecord->gmtoff / HOURMINS;
  1611. }
  1612. }
  1613. /**
  1614. * Returns an int which represents the systems's timezone difference from GMT in seconds
  1615. *
  1616. * @global object
  1617. * @param mixed $tz timezone
  1618. * @return int if found, false is timezone 99 or error
  1619. */
  1620. function get_timezone_offset($tz) {
  1621. global $CFG;
  1622. if ($tz == 99) {
  1623. return false;
  1624. }
  1625. if (is_numeric($tz)) {
  1626. return intval($tz * 60*60);
  1627. }
  1628. if (!$tzrecord = get_timezone_record($tz)) {
  1629. return false;
  1630. }
  1631. return intval($tzrecord->gmtoff * 60);
  1632. }
  1633. /**
  1634. * Returns a float or a string which denotes the user's timezone
  1635. * 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)
  1636. * means that for this timezone there are also DST rules to be taken into account
  1637. * Checks various settings and picks the most dominant of those which have a value
  1638. *
  1639. * @global object
  1640. * @global object
  1641. * @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
  1642. * @return mixed
  1643. */
  1644. function get_user_timezone($tz = 99) {
  1645. global $USER, $CFG;
  1646. $timezones = array(
  1647. $tz,
  1648. isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
  1649. isset($USER->timezone) ? $USER->timezone : 99,
  1650. isset($CFG->timezone) ? $CFG->timezone : 99,
  1651. );
  1652. $tz = 99;
  1653. while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
  1654. $tz = $next['value'];
  1655. }
  1656. return is_numeric($tz) ? (float) $tz : $tz;
  1657. }
  1658. /**
  1659. * Returns cached timezone record for given $timezonename
  1660. *
  1661. * @global object
  1662. * @global object
  1663. * @param string $timezonename
  1664. * @return mixed timezonerecord object or false
  1665. */
  1666. function get_timezone_record($timezonename) {
  1667. global $CFG, $DB;
  1668. static $cache = NULL;
  1669. if ($cache === NULL) {
  1670. $cache = array();
  1671. }
  1672. if (isset($cache[$timezonename])) {
  1673. return $cache[$timezonename];
  1674. }
  1675. return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
  1676. WHERE name = ? ORDER BY year DESC', array($timezonename), true);
  1677. }
  1678. /**
  1679. * Build and store the users Daylight Saving Time (DST) table
  1680. *
  1681. * @global object
  1682. * @global object
  1683. * @global object
  1684. * @param mixed $from_year Start year for the table, defaults to 1971
  1685. * @param mixed $to_year End year for the table, defaults to 2035
  1686. * @param mixed $strtimezone, if null or 99 then user's default timezone is used
  1687. * @return bool
  1688. */
  1689. function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
  1690. global $CFG, $SESSION, $DB;
  1691. $usertz = get_user_timezone($strtimezone);
  1692. if (is_float($usertz)) {
  1693. // Trivial timezone, no DST
  1694. return false;
  1695. }
  1696. if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
  1697. // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
  1698. unset($SESSION->dst_offsets);
  1699. unset($SESSION->dst_range);
  1700. }
  1701. if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
  1702. // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
  1703. // This will be the return path most of the time, pretty light computationally
  1704. return true;
  1705. }
  1706. // Reaching here means we either need to extend our table or create it from scratch
  1707. // Remember which TZ we calculated these changes for
  1708. $SESSION->dst_offsettz = $usertz;
  1709. if(empty($SESSION->dst_offsets)) {
  1710. // If we 're creating from scratch, put the two guard elements in there
  1711. $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
  1712. }
  1713. if(empty($SESSION->dst_range)) {
  1714. // If creating from scratch
  1715. $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
  1716. $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
  1717. // Fill in the array with the extra years we need to process
  1718. $yearstoprocess = array();
  1719. for($i = $from; $i <= $to; ++$i) {
  1720. $yearstoprocess[] = $i;
  1721. }
  1722. // Take note of which years we have processed for future calls
  1723. $SESSION->dst_range = array($from, $to);
  1724. }
  1725. else {
  1726. // If needing to extend the table, do the same
  1727. $yearstoprocess = array();
  1728. $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
  1729. $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
  1730. if($from < $SESSION->dst_range[0]) {
  1731. // Take note of which years we need to process and then note that we have processed them for future calls
  1732. for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
  1733. $yearstoprocess[] = $i;
  1734. }
  1735. $SESSION->dst_range[0] = $from;
  1736. }
  1737. if($to > $SESSION->dst_range[1]) {
  1738. // Take note of which years we need to process and then note that we have processed them for future calls
  1739. for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
  1740. $yearstoprocess[] = $i;
  1741. }
  1742. $SESSION->dst_range[1] = $to;
  1743. }
  1744. }
  1745. if(empty($yearstoprocess)) {
  1746. // This means that there was a call requesting a SMALLER range than we have already calculated
  1747. return true;
  1748. }
  1749. // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
  1750. // Also, the array is sorted in descending timestamp order!
  1751. // Get DB data
  1752. static $presets_cache = array();
  1753. if (!isset($presets_cache[$usertz])) {
  1754. $presets_cache[$usertz] = $DB->get_records('timezone', array('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');
  1755. }
  1756. if(empty($presets_cache[$usertz])) {
  1757. return false;
  1758. }
  1759. // Remove ending guard (first element of the array)
  1760. reset($SESSION->dst_offsets);
  1761. unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
  1762. // Add all required change timestamps
  1763. foreach($yearstoprocess as $y) {
  1764. // Find the record which is in effect for the year $y
  1765. foreach($presets_cache[$usertz] as $year => $preset) {
  1766. if($year <= $y) {
  1767. break;
  1768. }
  1769. }
  1770. $changes = dst_changes_for_year($y, $preset);
  1771. if($changes === NULL) {
  1772. continue;
  1773. }
  1774. if($changes['dst'] != 0) {
  1775. $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
  1776. }
  1777. if($changes['std'] != 0) {
  1778. $SESSION->dst_offsets[$changes['std']] = 0;
  1779. }
  1780. }
  1781. // Put in a guard element at the top
  1782. $maxtimestamp = max(array_keys($SESSION->dst_offsets));
  1783. $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
  1784. // Sort again
  1785. krsort($SESSION->dst_offsets);
  1786. return true;
  1787. }
  1788. /**
  1789. * Calculates the required DST change and returns a Timestamp Array
  1790. *
  1791. * @uses HOURSECS
  1792. * @uses MINSECS
  1793. * @param mixed $year Int or String Year to focus on
  1794. * @param object $timezone Instatiated Timezone object
  1795. * @return mixed Null, or Array dst=>xx, 0=>xx, std=>yy, 1=>yy
  1796. */
  1797. function dst_changes_for_year($year, $timezone) {
  1798. if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
  1799. return NULL;
  1800. }
  1801. $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
  1802. $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
  1803. list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
  1804. list($std_hour, $std_min) = explode(':', $timezone->std_time);
  1805. $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
  1806. $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
  1807. // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
  1808. // This has the advantage of being able to have negative values for hour, i.e. for timezones
  1809. // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
  1810. $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
  1811. $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
  1812. return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
  1813. }
  1814. /**
  1815. * Calculates the Daylight Saving Offset for a given date/time (timestamp)
  1816. * - Note: Daylight saving only works for string timezones and not for float.
  1817. *
  1818. * @global object
  1819. * @param int $time must NOT be compensated at all, it has to be a pure timestamp
  1820. * @param mixed $strtimezone timezone for which offset is expected, if 99 or null
  1821. * then user's default timezone is used.
  1822. * @return int
  1823. */
  1824. function dst_offset_on($time, $strtimezone = NULL) {
  1825. global $SESSION;
  1826. if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
  1827. return 0;
  1828. }
  1829. reset($SESSION->dst_offsets);
  1830. while(list($from, $offset) = each($SESSION->dst_offsets)) {
  1831. if($from <= $time) {
  1832. break;
  1833. }
  1834. }
  1835. // This is the normal return path
  1836. if($offset !== NULL) {
  1837. return $offset;
  1838. }
  1839. // Reaching this point means we haven't calculated far enough, do it now:
  1840. // Calculate extra DST changes if needed and recurse. The recursion always
  1841. // moves toward the stopping condition, so will always end.
  1842. if($from == 0) {
  1843. // We need a year smaller than $SESSION->dst_range[0]
  1844. if($SESSION->dst_range[0] == 1971) {
  1845. return 0;
  1846. }
  1847. calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
  1848. return dst_offset_on($time, $strtimezone);
  1849. }
  1850. else {
  1851. // We need a year larger than $SESSION->dst_range[1]
  1852. if($SESSION->dst_range[1] == 2035) {
  1853. return 0;
  1854. }
  1855. calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
  1856. return dst_offset_on($time, $strtimezone);
  1857. }
  1858. }
  1859. /**
  1860. * ?
  1861. *
  1862. * @todo Document what this function does
  1863. * @param int $startday
  1864. * @param int $weekday
  1865. * @param int $month
  1866. * @param int $year
  1867. * @return int
  1868. */
  1869. function find_day_in_month($startday, $weekday, $month, $year) {
  1870. $daysinmonth = days_in_month($month, $year);
  1871. if($weekday == -1) {
  1872. // Don't care about weekday, so return:
  1873. // abs($startday) if $startday != -1
  1874. // $daysinmonth otherwise
  1875. return ($startday == -1) ? $daysinmonth : abs($startday);
  1876. }
  1877. // From now on we 're looking for a specific weekday
  1878. // Give "end of month" its actual value, since we know it
  1879. if($startday == -1) {
  1880. $startday = -1 * $daysinmonth;
  1881. }
  1882. // Starting from day $startday, the sign is the direction
  1883. if($startday < 1) {
  1884. $startday = abs($startday);
  1885. $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
  1886. // This is the last such weekday of the month
  1887. $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
  1888. if($lastinmonth > $daysinmonth) {
  1889. $lastinmonth -= 7;
  1890. }
  1891. // Find the first such weekday <= $startday
  1892. while($lastinmonth > $startday) {
  1893. $lastinmonth -= 7;
  1894. }
  1895. return $lastinmonth;
  1896. }
  1897. else {
  1898. $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
  1899. $diff = $weekday - $indexweekday;
  1900. if($diff < 0) {
  1901. $diff += 7;
  1902. }
  1903. // This is the first such weekday of the month equal to or after $startday
  1904. $firstfromindex = $startday + $diff;
  1905. return $firstfromindex;
  1906. }
  1907. }
  1908. /**
  1909. * Calculate the number of days in a given month
  1910. *
  1911. * @param int $month The month whose day count is sought
  1912. * @param int $year The year of the month whose day count is sought
  1913. * @return int
  1914. */
  1915. function days_in_month($month, $year) {
  1916. return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
  1917. }
  1918. /**
  1919. * Calculate the position in the week of a specific calendar day
  1920. *
  1921. * @param int $day The day of the date whose position in the week is sought
  1922. * @param int $month The month of the date whose position in the week is sought
  1923. * @param int $year The year of the date whose position in the week is sought
  1924. * @return int
  1925. */
  1926. function dayofweek($day, $month, $year) {
  1927. // I wonder if this is any different from
  1928. // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
  1929. return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
  1930. }
  1931. /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
  1932. /**
  1933. * Returns full login url.
  1934. *
  1935. * @return string login url
  1936. */
  1937. function get_login_url() {
  1938. global $CFG;
  1939. $url = "$CFG->wwwroot/login/index.php";
  1940. if (!empty($CFG->loginhttps)) {
  1941. $url = str_replace('http:', 'https:', $url);
  1942. }
  1943. return $url;
  1944. }
  1945. /**
  1946. * This function checks that the current user is logged in and has the
  1947. * required privileges
  1948. *
  1949. * This function checks that the current user is logged in, and optionally
  1950. * whether they are allowed to be in a particular course and view a particular
  1951. * course module.
  1952. * If they are not logged in, then it redirects them to the site login unless
  1953. * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
  1954. * case they are automatically logged in as guests.
  1955. * If $courseid is given and the user is not enrolled in that course then the
  1956. * user is redirected to the course enrolment page.
  1957. * If $cm is given and the course module is hidden and the user is not a teacher
  1958. * in the course then the user is redirected to the course home page.
  1959. *
  1960. * When $cm parameter specified, this function sets page layout to 'module'.
  1961. * You need to change it manually later if some other layout needed.
  1962. *
  1963. * @param mixed $courseorid id of the course or course object
  1964. * @param bool $autologinguest default true
  1965. * @param object $cm course module object
  1966. * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
  1967. * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
  1968. * in order to keep redirects working properly. MDL-14495
  1969. * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
  1970. * @return mixed Void, exit, and die depending on path
  1971. */
  1972. function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
  1973. global $CFG, $SESSION, $USER, $FULLME, $PAGE, $SITE, $DB, $OUTPUT;
  1974. // setup global $COURSE, themes, language and locale
  1975. if (!empty($courseorid)) {
  1976. if (is_object($courseorid)) {
  1977. $course = $courseorid;
  1978. } else if ($courseorid == SITEID) {
  1979. $course = clone($SITE);
  1980. } else {
  1981. $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
  1982. }
  1983. if ($cm) {
  1984. if ($cm->course != $course->id) {
  1985. throw new coding_exception('course and cm parameters in require_login() call do not match!!');
  1986. }
  1987. // make sure we have a $cm from get_fast_modinfo as this contains activity access details
  1988. if (!($cm instanceof cm_info)) {
  1989. // note: nearly all pages call get_fast_modinfo anyway and it does not make any
  1990. // db queries so this is not really a performance concern, however it is obviously
  1991. // better if you use get_fast_modinfo to get the cm before calling this.
  1992. $modinfo = get_fast_modinfo($course);
  1993. $cm = $modinfo->get_cm($cm->id);
  1994. }
  1995. $PAGE->set_cm($cm, $course); // set's up global $COURSE
  1996. $PAGE->set_pagelayout('incourse');
  1997. } else {
  1998. $PAGE->set_course($course); // set's up global $COURSE
  1999. }
  2000. } else {
  2001. // do not touch global $COURSE via $PAGE->set_course(),
  2002. // the reasons is we need to be able to call require_login() at any time!!
  2003. $course = $SITE;
  2004. if ($cm) {
  2005. throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
  2006. }
  2007. }
  2008. // If the user is not even logged in yet then make sure they are
  2009. if (!isloggedin()) {
  2010. if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
  2011. if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
  2012. // misconfigured site guest, just redirect to login page
  2013. redirect(get_login_url());
  2014. exit; // never reached
  2015. }
  2016. $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
  2017. complete_user_login($guest, false);
  2018. $USER->autologinguest = true;
  2019. $SESSION->lang = $lang;
  2020. } else {
  2021. //NOTE: $USER->site check was obsoleted by session test cookie,
  2022. // $USER->confirmed test is in login/index.php
  2023. if ($preventredirect) {
  2024. throw new require_login_exception('You are not logged in');
  2025. }
  2026. if ($setwantsurltome) {
  2027. // TODO: switch to PAGE->url
  2028. $SESSION->wantsurl = $FULLME;
  2029. }
  2030. if (!empty($_SERVER['HTTP_REFERER'])) {
  2031. $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
  2032. }
  2033. redirect(get_login_url());
  2034. exit; // never reached
  2035. }
  2036. }
  2037. // loginas as redirection if needed
  2038. if ($course->id != SITEID and session_is_loggedinas()) {
  2039. if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
  2040. if ($USER->loginascontext->instanceid != $course->id) {
  2041. print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
  2042. }
  2043. }
  2044. }
  2045. // check whether the user should be changing password (but only if it is REALLY them)
  2046. if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
  2047. $userauth = get_auth_plugin($USER->auth);
  2048. if ($userauth->can_change_password() and !$preventredirect) {
  2049. $SESSION->wantsurl = $FULLME;
  2050. if ($changeurl = $userauth->change_password_url()) {
  2051. //use plugin custom url
  2052. redirect($changeurl);
  2053. } else {
  2054. //use moodle internal method
  2055. if (empty($CFG->loginhttps)) {
  2056. redirect($CFG->wwwroot .'/login/change_password.php');
  2057. } else {
  2058. $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
  2059. redirect($wwwroot .'/login/change_password.php');
  2060. }
  2061. }
  2062. } else {
  2063. print_error('nopasswordchangeforced', 'auth');
  2064. }
  2065. }
  2066. // Check that the user account is properly set up
  2067. if (user_not_fully_set_up($USER)) {
  2068. if ($preventredirect) {
  2069. throw new require_login_exception('User not fully set-up');
  2070. }
  2071. $SESSION->wantsurl = $FULLME;
  2072. redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
  2073. }
  2074. // Make sure the USER has a sesskey set up. Used for CSRF protection.
  2075. sesskey();
  2076. // Do not bother admins with any formalities
  2077. if (is_siteadmin()) {
  2078. //set accesstime or the user will appear offline which messes up messaging
  2079. user_accesstime_log($course->id);
  2080. return;
  2081. }
  2082. // Check that the user has agreed to a site policy if there is one - do not test in case of admins
  2083. if (!$USER->policyagreed and !is_siteadmin()) {
  2084. if (!empty($CFG->sitepolicy) and !isguestuser()) {
  2085. if ($preventredirect) {
  2086. throw new require_login_exception('Policy not agreed');
  2087. }
  2088. $SESSION->wantsurl = $FULLME;
  2089. redirect($CFG->wwwroot .'/user/policy.php');
  2090. } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
  2091. if ($preventredirect) {
  2092. throw new require_login_exception('Policy not agreed');
  2093. }
  2094. $SESSION->wantsurl = $FULLME;
  2095. redirect($CFG->wwwroot .'/user/policy.php');
  2096. }
  2097. }
  2098. // Fetch the system context, the course context, and prefetch its child contexts
  2099. $sysctx = get_context_instance(CONTEXT_SYSTEM);
  2100. $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
  2101. if ($cm) {
  2102. $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
  2103. } else {
  2104. $cmcontext = null;
  2105. }
  2106. // If the site is currently under maintenance, then print a message
  2107. if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
  2108. if ($preventredirect) {
  2109. throw new require_login_exception('Maintenance in progress');
  2110. }
  2111. print_maintenance_message();
  2112. }
  2113. // make sure the course itself is not hidden
  2114. if ($course->id == SITEID) {
  2115. // frontpage can not be hidden
  2116. } else {
  2117. if (is_role_switched($course->id)) {
  2118. // when switching roles ignore the hidden flag - user had to be in course to do the switch
  2119. } else {
  2120. if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
  2121. // originally there was also test of parent category visibility,
  2122. // BUT is was very slow in complex queries involving "my courses"
  2123. // now it is also possible to simply hide all courses user is not enrolled in :-)
  2124. if ($preventredirect) {
  2125. throw new require_login_exception('Course is hidden');
  2126. }
  2127. notice(get_string('coursehidden'), $CFG->wwwroot .'/');
  2128. }
  2129. }
  2130. }
  2131. // is the user enrolled?
  2132. if ($course->id == SITEID) {
  2133. // everybody is enrolled on the frontpage
  2134. } else {
  2135. if (session_is_loggedinas()) {
  2136. // Make sure the REAL person can access this course first
  2137. $realuser = session_get_realuser();
  2138. if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
  2139. if ($preventredirect) {
  2140. throw new require_login_exception('Invalid course login-as access');
  2141. }
  2142. echo $OUTPUT->header();
  2143. notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
  2144. }
  2145. }
  2146. // very simple enrolment caching - changes in course setting are not reflected immediately
  2147. if (!isset($USER->enrol)) {
  2148. $USER->enrol = array();
  2149. $USER->enrol['enrolled'] = array();
  2150. $USER->enrol['tempguest'] = array();
  2151. }
  2152. $access = false;
  2153. if (is_viewing($coursecontext, $USER)) {
  2154. // ok, no need to mess with enrol
  2155. $access = true;
  2156. } else {
  2157. if (isset($USER->enrol['enrolled'][$course->id])) {
  2158. if ($USER->enrol['enrolled'][$course->id] == 0) {
  2159. $access = true;
  2160. } else if ($USER->enrol['enrolled'][$course->id] > time()) {
  2161. $access = true;
  2162. } else {
  2163. //expired
  2164. unset($USER->enrol['enrolled'][$course->id]);
  2165. }
  2166. }
  2167. if (isset($USER->enrol['tempguest'][$course->id])) {
  2168. if ($USER->enrol['tempguest'][$course->id] == 0) {
  2169. $access = true;
  2170. } else if ($USER->enrol['tempguest'][$course->id] > time()) {
  2171. $access = true;
  2172. } else {
  2173. //expired
  2174. unset($USER->enrol['tempguest'][$course->id]);
  2175. $USER->access = remove_temp_roles($coursecontext, $USER->access);
  2176. }
  2177. }
  2178. if ($access) {
  2179. // cache ok
  2180. } else if (is_enrolled($coursecontext, $USER, '', true)) {
  2181. // active participants may always access
  2182. // TODO: refactor this into some new function
  2183. $now = time();
  2184. $sql = "SELECT MAX(ue.timeend)
  2185. FROM {user_enrolments} ue
  2186. JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
  2187. JOIN {user} u ON u.id = ue.userid
  2188. WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0
  2189. AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
  2190. $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE,
  2191. 'userid'=>$USER->id, 'courseid'=>$coursecontext->instanceid, 'now1'=>$now, 'now2'=>$now);
  2192. $until = $DB->get_field_sql($sql, $params);
  2193. if (!$until or $until > time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD) {
  2194. $until = time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD;
  2195. }
  2196. $USER->enrol['enrolled'][$course->id] = $until;
  2197. $access = true;
  2198. // remove traces of previous temp guest access
  2199. $USER->access = remove_temp_roles($coursecontext, $USER->access);
  2200. } else {
  2201. $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
  2202. $enrols = enrol_get_plugins(true);
  2203. // first ask all enabled enrol instances in course if they want to auto enrol user
  2204. foreach($instances as $instance) {
  2205. if (!isset($enrols[$instance->enrol])) {
  2206. continue;
  2207. }
  2208. // Get a duration for the guestaccess, a timestamp in the future or false.
  2209. $until = $enrols[$instance->enrol]->try_autoenrol($instance);
  2210. if ($until !== false) {
  2211. $USER->enrol['enrolled'][$course->id] = $until;
  2212. $USER->access = remove_temp_roles($coursecontext, $USER->access);
  2213. $access = true;
  2214. break;
  2215. }
  2216. }
  2217. // if not enrolled yet try to gain temporary guest access
  2218. if (!$access) {
  2219. foreach($instances as $instance) {
  2220. if (!isset($enrols[$instance->enrol])) {
  2221. continue;
  2222. }
  2223. // Get a duration for the guestaccess, a timestamp in the future or false.
  2224. $until = $enrols[$instance->enrol]->try_guestaccess($instance);
  2225. if ($until !== false) {
  2226. $USER->enrol['tempguest'][$course->id] = $until;
  2227. $access = true;
  2228. break;
  2229. }
  2230. }
  2231. }
  2232. }
  2233. }
  2234. if (!$access) {
  2235. if ($preventredirect) {
  2236. throw new require_login_exception('Not enrolled');
  2237. }
  2238. $SESSION->wantsurl = $FULLME;
  2239. redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
  2240. }
  2241. }
  2242. // Check visibility of activity to current user; includes visible flag, groupmembersonly,
  2243. // conditional availability, etc
  2244. if ($cm && !$cm->uservisible) {
  2245. if ($preventredirect) {
  2246. throw new require_login_exception('Activity is hidden');
  2247. }
  2248. redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
  2249. }
  2250. // Finally access granted, update lastaccess times
  2251. user_accesstime_log($course->id);
  2252. }
  2253. /**
  2254. * This function just makes sure a user is logged out.
  2255. *
  2256. * @global object
  2257. */
  2258. function require_logout() {
  2259. global $USER;
  2260. $params = $USER;
  2261. if (isloggedin()) {
  2262. add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
  2263. $authsequence = get_enabled_auth_plugins(); // auths, in sequence
  2264. foreach($authsequence as $authname) {
  2265. $authplugin = get_auth_plugin($authname);
  2266. $authplugin->prelogout_hook();
  2267. }
  2268. }
  2269. events_trigger('user_logout', $params);
  2270. session_get_instance()->terminate_current();
  2271. unset($params);
  2272. }
  2273. /**
  2274. * Weaker version of require_login()
  2275. *
  2276. * This is a weaker version of {@link require_login()} which only requires login
  2277. * when called from within a course rather than the site page, unless
  2278. * the forcelogin option is turned on.
  2279. * @see require_login()
  2280. *
  2281. * @global object
  2282. * @param mixed $courseorid The course object or id in question
  2283. * @param bool $autologinguest Allow autologin guests if that is wanted
  2284. * @param object $cm Course activity module if known
  2285. * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
  2286. * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
  2287. * in order to keep redirects working properly. MDL-14495
  2288. * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
  2289. * @return void
  2290. */
  2291. function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
  2292. global $CFG, $PAGE, $SITE;
  2293. $issite = (is_object($courseorid) and $courseorid->id == SITEID)
  2294. or (!is_object($courseorid) and $courseorid == SITEID);
  2295. if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
  2296. // note: nearly all pages call get_fast_modinfo anyway and it does not make any
  2297. // db queries so this is not really a performance concern, however it is obviously
  2298. // better if you use get_fast_modinfo to get the cm before calling this.
  2299. if (is_object($courseorid)) {
  2300. $course = $courseorid;
  2301. } else {
  2302. $course = clone($SITE);
  2303. }
  2304. $modinfo = get_fast_modinfo($course);
  2305. $cm = $modinfo->get_cm($cm->id);
  2306. }
  2307. if (!empty($CFG->forcelogin)) {
  2308. // login required for both SITE and courses
  2309. require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
  2310. } else if ($issite && !empty($cm) and !$cm->uservisible) {
  2311. // always login for hidden activities
  2312. require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
  2313. } else if ($issite) {
  2314. //login for SITE not required
  2315. if ($cm and empty($cm->visible)) {
  2316. // hidden activities are not accessible without login
  2317. require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
  2318. } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
  2319. // not-logged-in users do not have any group membership
  2320. require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
  2321. } else {
  2322. // We still need to instatiate PAGE vars properly so that things
  2323. // that rely on it like navigation function correctly.
  2324. if (!empty($courseorid)) {
  2325. if (is_object($courseorid)) {
  2326. $course = $courseorid;
  2327. } else {
  2328. $course = clone($SITE);
  2329. }
  2330. if ($cm) {
  2331. if ($cm->course != $course->id) {
  2332. throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
  2333. }
  2334. $PAGE->set_cm($cm, $course);
  2335. $PAGE->set_pagelayout('incourse');
  2336. } else {
  2337. $PAGE->set_course($course);
  2338. }
  2339. } else {
  2340. // If $PAGE->course, and hence $PAGE->context, have not already been set
  2341. // up properly, set them up now.
  2342. $PAGE->set_course($PAGE->course);
  2343. }
  2344. //TODO: verify conditional activities here
  2345. user_accesstime_log(SITEID);
  2346. return;
  2347. }
  2348. } else {
  2349. // course login always required
  2350. require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
  2351. }
  2352. }
  2353. /**
  2354. * Require key login. Function terminates with error if key not found or incorrect.
  2355. *
  2356. * @global object
  2357. * @global object
  2358. * @global object
  2359. * @global object
  2360. * @uses NO_MOODLE_COOKIES
  2361. * @uses PARAM_ALPHANUM
  2362. * @param string $script unique script identifier
  2363. * @param int $instance optional instance id
  2364. * @return int Instance ID
  2365. */
  2366. function require_user_key_login($script, $instance=null) {
  2367. global $USER, $SESSION, $CFG, $DB;
  2368. if (!NO_MOODLE_COOKIES) {
  2369. print_error('sessioncookiesdisable');
  2370. }
  2371. /// extra safety
  2372. @session_write_close();
  2373. $keyvalue = required_param('key', PARAM_ALPHANUM);
  2374. if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
  2375. print_error('invalidkey');
  2376. }
  2377. if (!empty($key->validuntil) and $key->validuntil < time()) {
  2378. print_error('expiredkey');
  2379. }
  2380. if ($key->iprestriction) {
  2381. $remoteaddr = getremoteaddr(null);
  2382. if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
  2383. print_error('ipmismatch');
  2384. }
  2385. }
  2386. if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
  2387. print_error('invaliduserid');
  2388. }
  2389. /// emulate normal session
  2390. session_set_user($user);
  2391. /// note we are not using normal login
  2392. if (!defined('USER_KEY_LOGIN')) {
  2393. define('USER_KEY_LOGIN', true);
  2394. }
  2395. /// return instance id - it might be empty
  2396. return $key->instance;
  2397. }
  2398. /**
  2399. * Creates a new private user access key.
  2400. *
  2401. * @global object
  2402. * @param string $script unique target identifier
  2403. * @param int $userid
  2404. * @param int $instance optional instance id
  2405. * @param string $iprestriction optional ip restricted access
  2406. * @param timestamp $validuntil key valid only until given data
  2407. * @return string access key value
  2408. */
  2409. function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
  2410. global $DB;
  2411. $key = new stdClass();
  2412. $key->script = $script;
  2413. $key->userid = $userid;
  2414. $key->instance = $instance;
  2415. $key->iprestriction = $iprestriction;
  2416. $key->validuntil = $validuntil;
  2417. $key->timecreated = time();
  2418. $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
  2419. while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
  2420. // must be unique
  2421. $key->value = md5($userid.'_'.time().random_string(40));
  2422. }
  2423. $DB->insert_record('user_private_key', $key);
  2424. return $key->value;
  2425. }
  2426. /**
  2427. * Delete the user's new private user access keys for a particular script.
  2428. *
  2429. * @global object
  2430. * @param string $script unique target identifier
  2431. * @param int $userid
  2432. * @return void
  2433. */
  2434. function delete_user_key($script,$userid) {
  2435. global $DB;
  2436. $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
  2437. }
  2438. /**
  2439. * Gets a private user access key (and creates one if one doesn't exist).
  2440. *
  2441. * @global object
  2442. * @param string $script unique target identifier
  2443. * @param int $userid
  2444. * @param int $instance optional instance id
  2445. * @param string $iprestriction optional ip restricted access
  2446. * @param timestamp $validuntil key valid only until given data
  2447. * @return string access key value
  2448. */
  2449. function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
  2450. global $DB;
  2451. if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
  2452. 'instance'=>$instance, 'iprestriction'=>$iprestriction,
  2453. 'validuntil'=>$validuntil))) {
  2454. return $key->value;
  2455. } else {
  2456. return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
  2457. }
  2458. }
  2459. /**
  2460. * Modify the user table by setting the currently logged in user's
  2461. * last login to now.
  2462. *
  2463. * @global object
  2464. * @global object
  2465. * @return bool Always returns true
  2466. */
  2467. function update_user_login_times() {
  2468. global $USER, $DB;
  2469. $user = new stdClass();
  2470. $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
  2471. $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
  2472. $user->id = $USER->id;
  2473. $DB->update_record('user', $user);
  2474. return true;
  2475. }
  2476. /**
  2477. * Determines if a user has completed setting up their account.
  2478. *
  2479. * @param user $user A {@link $USER} object to test for the existence of a valid name and email
  2480. * @return bool
  2481. */
  2482. function user_not_fully_set_up($user) {
  2483. if (isguestuser($user)) {
  2484. return false;
  2485. }
  2486. return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
  2487. }
  2488. /**
  2489. * Check whether the user has exceeded the bounce threshold
  2490. *
  2491. * @global object
  2492. * @global object
  2493. * @param user $user A {@link $USER} object
  2494. * @return bool true=>User has exceeded bounce threshold
  2495. */
  2496. function over_bounce_threshold($user) {
  2497. global $CFG, $DB;
  2498. if (empty($CFG->handlebounces)) {
  2499. return false;
  2500. }
  2501. if (empty($user->id)) { /// No real (DB) user, nothing to do here.
  2502. return false;
  2503. }
  2504. // set sensible defaults
  2505. if (empty($CFG->minbounces)) {
  2506. $CFG->minbounces = 10;
  2507. }
  2508. if (empty($CFG->bounceratio)) {
  2509. $CFG->bounceratio = .20;
  2510. }
  2511. $bouncecount = 0;
  2512. $sendcount = 0;
  2513. if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
  2514. $bouncecount = $bounce->value;
  2515. }
  2516. if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
  2517. $sendcount = $send->value;
  2518. }
  2519. return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
  2520. }
  2521. /**
  2522. * Used to increment or reset email sent count
  2523. *
  2524. * @global object
  2525. * @param user $user object containing an id
  2526. * @param bool $reset will reset the count to 0
  2527. * @return void
  2528. */
  2529. function set_send_count($user,$reset=false) {
  2530. global $DB;
  2531. if (empty($user->id)) { /// No real (DB) user, nothing to do here.
  2532. return;
  2533. }
  2534. if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
  2535. $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
  2536. $DB->update_record('user_preferences', $pref);
  2537. }
  2538. else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
  2539. // make a new one
  2540. $pref = new stdClass();
  2541. $pref->name = 'email_send_count';
  2542. $pref->value = 1;
  2543. $pref->userid = $user->id;
  2544. $DB->insert_record('user_preferences', $pref, false);
  2545. }
  2546. }
  2547. /**
  2548. * Increment or reset user's email bounce count
  2549. *
  2550. * @global object
  2551. * @param user $user object containing an id
  2552. * @param bool $reset will reset the count to 0
  2553. */
  2554. function set_bounce_count($user,$reset=false) {
  2555. global $DB;
  2556. if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
  2557. $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
  2558. $DB->update_record('user_preferences', $pref);
  2559. }
  2560. else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
  2561. // make a new one
  2562. $pref = new stdClass();
  2563. $pref->name = 'email_bounce_count';
  2564. $pref->value = 1;
  2565. $pref->userid = $user->id;
  2566. $DB->insert_record('user_preferences', $pref, false);
  2567. }
  2568. }
  2569. /**
  2570. * Keeps track of login attempts
  2571. *
  2572. * @global object
  2573. */
  2574. function update_login_count() {
  2575. global $SESSION;
  2576. $max_logins = 10;
  2577. if (empty($SESSION->logincount)) {
  2578. $SESSION->logincount = 1;
  2579. } else {
  2580. $SESSION->logincount++;
  2581. }
  2582. if ($SESSION->logincount > $max_logins) {
  2583. unset($SESSION->wantsurl);
  2584. print_error('errortoomanylogins');
  2585. }
  2586. }
  2587. /**
  2588. * Resets login attempts
  2589. *
  2590. * @global object
  2591. */
  2592. function reset_login_count() {
  2593. global $SESSION;
  2594. $SESSION->logincount = 0;
  2595. }
  2596. /**
  2597. * Determines if the currently logged in user is in editing mode.
  2598. * Note: originally this function had $userid parameter - it was not usable anyway
  2599. *
  2600. * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
  2601. * @todo Deprecated function remove when ready
  2602. *
  2603. * @global object
  2604. * @uses DEBUG_DEVELOPER
  2605. * @return bool
  2606. */
  2607. function isediting() {
  2608. global $PAGE;
  2609. debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
  2610. return $PAGE->user_is_editing();
  2611. }
  2612. /**
  2613. * Determines if the logged in user is currently moving an activity
  2614. *
  2615. * @global object
  2616. * @param int $courseid The id of the course being tested
  2617. * @return bool
  2618. */
  2619. function ismoving($courseid) {
  2620. global $USER;
  2621. if (!empty($USER->activitycopy)) {
  2622. return ($USER->activitycopycourse == $courseid);
  2623. }
  2624. return false;
  2625. }
  2626. /**
  2627. * Returns a persons full name
  2628. *
  2629. * Given an object containing firstname and lastname
  2630. * values, this function returns a string with the
  2631. * full name of the person.
  2632. * The result may depend on system settings
  2633. * or language. 'override' will force both names
  2634. * to be used even if system settings specify one.
  2635. *
  2636. * @global object
  2637. * @global object
  2638. * @param object $user A {@link $USER} object to get full name of
  2639. * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
  2640. * @return string
  2641. */
  2642. function fullname($user, $override=false) {
  2643. global $CFG, $SESSION;
  2644. if (!isset($user->firstname) and !isset($user->lastname)) {
  2645. return '';
  2646. }
  2647. if (!$override) {
  2648. if (!empty($CFG->forcefirstname)) {
  2649. $user->firstname = $CFG->forcefirstname;
  2650. }
  2651. if (!empty($CFG->forcelastname)) {
  2652. $user->lastname = $CFG->forcelastname;
  2653. }
  2654. }
  2655. if (!empty($SESSION->fullnamedisplay)) {
  2656. $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
  2657. }
  2658. /**
  2659. * If firstname and lastname are the same in database and the string is not a single character,
  2660. * only display the firstname string to avoid repeating.
  2661. * For more, visit https://github.com/hit-moodle/moodle/issues#issue/1
  2662. */
  2663. if ($user->firstname == $user->lastname and mb_strlen($user->firstname, 'UTF8') > 1) {
  2664. return $user->firstname;
  2665. }
  2666. if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
  2667. return $user->firstname .' '. $user->lastname;
  2668. } else if ($CFG->fullnamedisplay == 'lastname firstname') {
  2669. return $user->lastname .' '. $user->firstname;
  2670. } else if ($CFG->fullnamedisplay == 'firstname') {
  2671. if ($override) {
  2672. return get_string('fullnamedisplay', '', $user);
  2673. } else {
  2674. return $user->firstname;
  2675. }
  2676. }
  2677. return get_string('fullnamedisplay', '', $user);
  2678. }
  2679. /**
  2680. * Returns the proper SQL (for the dbms in use) to concatenate firstname and lastname
  2681. *
  2682. * The order of firstname and lastname depends on system settings or language.
  2683. *
  2684. * @global object
  2685. * @param string $prefix prefix of firstname and lastname. Must include '.' when necessary.
  2686. * @return string
  2687. */
  2688. function fullname_sql($prefix='') {
  2689. global $DB;
  2690. $firstname = $prefix . 'firstname';
  2691. $lastname = $prefix . 'lastname';
  2692. $nameordercheck = new stdClass();
  2693. $nameordercheck->firstname = 'a';
  2694. $nameordercheck->lastname = 'b';
  2695. $ordered_fullname = fullname($nameordercheck);
  2696. switch ($ordered_fullname) {
  2697. case 'b a':
  2698. return $DB->sql_concat($lastname, "' '", $firstname);
  2699. case 'ba': // zh-cn and zh-tw use this style fullname
  2700. return $DB->sql_concat($lastname, $firstname);
  2701. case 'ab':
  2702. return $DB->sql_concat($firstname, $lastname);
  2703. default:
  2704. return $DB->sql_concat($firstname, "' '", $lastname);
  2705. }
  2706. }
  2707. /**
  2708. * Whether the firstname is before lastname in the system settings and language.
  2709. *
  2710. * @return boolean Whether the firstname is before lastname
  2711. */
  2712. function firstname_first() {
  2713. $nameordercheck = new stdClass();
  2714. $nameordercheck->firstname = 'a';
  2715. $nameordercheck->lastname = 'b';
  2716. $ordered_fullname = fullname($nameordercheck);
  2717. return $ordered_fullname == 'a b' || $ordered_fullname == 'ab';
  2718. }
  2719. /**
  2720. * Returns whether a given authentication plugin exists.
  2721. *
  2722. * @global object
  2723. * @param string $auth Form of authentication to check for. Defaults to the
  2724. * global setting in {@link $CFG}.
  2725. * @return boolean Whether the plugin is available.
  2726. */
  2727. function exists_auth_plugin($auth) {
  2728. global $CFG;
  2729. if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
  2730. return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
  2731. }
  2732. return false;
  2733. }
  2734. /**
  2735. * Checks if a given plugin is in the list of enabled authentication plugins.
  2736. *
  2737. * @param string $auth Authentication plugin.
  2738. * @return boolean Whether the plugin is enabled.
  2739. */
  2740. function is_enabled_auth($auth) {
  2741. if (empty($auth)) {
  2742. return false;
  2743. }
  2744. $enabled = get_enabled_auth_plugins();
  2745. return in_array($auth, $enabled);
  2746. }
  2747. /**
  2748. * Returns an authentication plugin instance.
  2749. *
  2750. * @global object
  2751. * @param string $auth name of authentication plugin
  2752. * @return auth_plugin_base An instance of the required authentication plugin.
  2753. */
  2754. function get_auth_plugin($auth) {
  2755. global $CFG;
  2756. // check the plugin exists first
  2757. if (! exists_auth_plugin($auth)) {
  2758. print_error('authpluginnotfound', 'debug', '', $auth);
  2759. }
  2760. // return auth plugin instance
  2761. require_once "{$CFG->dirroot}/auth/$auth/auth.php";
  2762. $class = "auth_plugin_$auth";
  2763. return new $class;
  2764. }
  2765. /**
  2766. * Returns array of active auth plugins.
  2767. *
  2768. * @param bool $fix fix $CFG->auth if needed
  2769. * @return array
  2770. */
  2771. function get_enabled_auth_plugins($fix=false) {
  2772. global $CFG;
  2773. $default = array('manual', 'nologin');
  2774. if (empty($CFG->auth)) {
  2775. $auths = array();
  2776. } else {
  2777. $auths = explode(',', $CFG->auth);
  2778. }
  2779. if ($fix) {
  2780. $auths = array_unique($auths);
  2781. foreach($auths as $k=>$authname) {
  2782. if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
  2783. unset($auths[$k]);
  2784. }
  2785. }
  2786. $newconfig = implode(',', $auths);
  2787. if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
  2788. set_config('auth', $newconfig);
  2789. }
  2790. }
  2791. return (array_merge($default, $auths));
  2792. }
  2793. /**
  2794. * Returns true if an internal authentication method is being used.
  2795. * if method not specified then, global default is assumed
  2796. *
  2797. * @param string $auth Form of authentication required
  2798. * @return bool
  2799. */
  2800. function is_internal_auth($auth) {
  2801. $authplugin = get_auth_plugin($auth); // throws error if bad $auth
  2802. return $authplugin->is_internal();
  2803. }
  2804. /**
  2805. * Returns true if the user is a 'restored' one
  2806. *
  2807. * Used in the login process to inform the user
  2808. * and allow him/her to reset the password
  2809. *
  2810. * @uses $CFG
  2811. * @uses $DB
  2812. * @param string $username username to be checked
  2813. * @return bool
  2814. */
  2815. function is_restored_user($username) {
  2816. global $CFG, $DB;
  2817. return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
  2818. }
  2819. /**
  2820. * Returns an array of user fields
  2821. *
  2822. * @return array User field/column names
  2823. */
  2824. function get_user_fieldnames() {
  2825. global $DB;
  2826. $fieldarray = $DB->get_columns('user');
  2827. unset($fieldarray['id']);
  2828. $fieldarray = array_keys($fieldarray);
  2829. return $fieldarray;
  2830. }
  2831. /**
  2832. * Creates a bare-bones user record
  2833. *
  2834. * @todo Outline auth types and provide code example
  2835. *
  2836. * @param string $username New user's username to add to record
  2837. * @param string $password New user's password to add to record
  2838. * @param string $auth Form of authentication required
  2839. * @return stdClass A complete user object
  2840. */
  2841. function create_user_record($username, $password, $auth = 'manual') {
  2842. global $CFG, $DB;
  2843. //just in case check text case
  2844. $username = trim(moodle_strtolower($username));
  2845. $authplugin = get_auth_plugin($auth);
  2846. $newuser = new stdClass();
  2847. if ($newinfo = $authplugin->get_userinfo($username)) {
  2848. $newinfo = truncate_userinfo($newinfo);
  2849. foreach ($newinfo as $key => $value){
  2850. $newuser->$key = $value;
  2851. }
  2852. }
  2853. if (!empty($newuser->email)) {
  2854. if (email_is_not_allowed($newuser->email)) {
  2855. unset($newuser->email);
  2856. }
  2857. }
  2858. if (!isset($newuser->city)) {
  2859. $newuser->city = '';
  2860. }
  2861. $newuser->auth = $auth;
  2862. $newuser->username = $username;
  2863. // fix for MDL-8480
  2864. // user CFG lang for user if $newuser->lang is empty
  2865. // or $user->lang is not an installed language
  2866. if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
  2867. $newuser->lang = $CFG->lang;
  2868. }
  2869. $newuser->confirmed = 1;
  2870. $newuser->lastip = getremoteaddr();
  2871. $newuser->timemodified = time();
  2872. $newuser->mnethostid = $CFG->mnet_localhost_id;
  2873. $newuser->id = $DB->insert_record('user', $newuser);
  2874. $user = get_complete_user_data('id', $newuser->id);
  2875. if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
  2876. set_user_preference('auth_forcepasswordchange', 1, $user);
  2877. }
  2878. update_internal_user_password($user, $password);
  2879. // fetch full user record for the event, the complete user data contains too much info
  2880. // and we want to be consistent with other places that trigger this event
  2881. events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
  2882. return $user;
  2883. }
  2884. /**
  2885. * Will update a local user record from an external source.
  2886. * (MNET users can not be updated using this method!)
  2887. *
  2888. * @param string $username user's username to update the record
  2889. * @return stdClass A complete user object
  2890. */
  2891. function update_user_record($username) {
  2892. global $DB, $CFG;
  2893. $username = trim(moodle_strtolower($username)); /// just in case check text case
  2894. $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
  2895. $newuser = array();
  2896. $userauth = get_auth_plugin($oldinfo->auth);
  2897. if ($newinfo = $userauth->get_userinfo($username)) {
  2898. $newinfo = truncate_userinfo($newinfo);
  2899. foreach ($newinfo as $key => $value){
  2900. $key = strtolower($key);
  2901. if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
  2902. or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
  2903. // unknown or must not be changed
  2904. continue;
  2905. }
  2906. $confval = $userauth->config->{'field_updatelocal_' . $key};
  2907. $lockval = $userauth->config->{'field_lock_' . $key};
  2908. if (empty($confval) || empty($lockval)) {
  2909. continue;
  2910. }
  2911. if ($confval === 'onlogin') {
  2912. // MDL-4207 Don't overwrite modified user profile values with
  2913. // empty LDAP values when 'unlocked if empty' is set. The purpose
  2914. // of the setting 'unlocked if empty' is to allow the user to fill
  2915. // in a value for the selected field _if LDAP is giving
  2916. // nothing_ for this field. Thus it makes sense to let this value
  2917. // stand in until LDAP is giving a value for this field.
  2918. if (!(empty($value) && $lockval === 'unlockedifempty')) {
  2919. if ((string)$oldinfo->$key !== (string)$value) {
  2920. if ($key == 'institution' || $key == 'department') {
  2921. /// Change cohort the user belongs to
  2922. hit_cohort_remove_member((string)$oldinfo->{$key}, $oldinfo->id);
  2923. hit_cohort_add_member((string)$value, $oldinfo->id);
  2924. }
  2925. $newuser[$key] = (string)$value;
  2926. }
  2927. }
  2928. }
  2929. }
  2930. if ($newuser) {
  2931. $newuser['id'] = $oldinfo->id;
  2932. $DB->update_record('user', $newuser);
  2933. // fetch full user record for the event, the complete user data contains too much info
  2934. // and we want to be consistent with other places that trigger this event
  2935. events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
  2936. }
  2937. }
  2938. return get_complete_user_data('id', $oldinfo->id);
  2939. }
  2940. /**
  2941. * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
  2942. * which may have large fields
  2943. *
  2944. * @todo Add vartype handling to ensure $info is an array
  2945. *
  2946. * @param array $info Array of user properties to truncate if needed
  2947. * @return array The now truncated information that was passed in
  2948. */
  2949. function truncate_userinfo($info) {
  2950. // define the limits
  2951. $limit = array(
  2952. 'username' => 100,
  2953. 'idnumber' => 255,
  2954. 'firstname' => 100,
  2955. 'lastname' => 100,
  2956. 'email' => 100,
  2957. 'icq' => 15,
  2958. 'phone1' => 20,
  2959. 'phone2' => 20,
  2960. 'institution' => 40,
  2961. 'department' => 30,
  2962. 'address' => 70,
  2963. 'city' => 120,
  2964. 'country' => 2,
  2965. 'url' => 255,
  2966. );
  2967. $textlib = textlib_get_instance();
  2968. // apply where needed
  2969. foreach (array_keys($info) as $key) {
  2970. if (!empty($limit[$key])) {
  2971. $info[$key] = trim($textlib->substr($info[$key],0, $limit[$key]));
  2972. }
  2973. }
  2974. return $info;
  2975. }
  2976. /**
  2977. * Marks user deleted in internal user database and notifies the auth plugin.
  2978. * Also unenrols user from all roles and does other cleanup.
  2979. *
  2980. * Any plugin that needs to purge user data should register the 'user_deleted' event.
  2981. *
  2982. * @param object $user User object before delete
  2983. * @return boolean always true
  2984. */
  2985. function delete_user($user) {
  2986. global $CFG, $DB;
  2987. require_once($CFG->libdir.'/grouplib.php');
  2988. require_once($CFG->libdir.'/gradelib.php');
  2989. require_once($CFG->dirroot.'/message/lib.php');
  2990. require_once($CFG->dirroot.'/tag/lib.php');
  2991. // delete all grades - backup is kept in grade_grades_history table
  2992. grade_user_delete($user->id);
  2993. //move unread messages from this user to read
  2994. message_move_userfrom_unread2read($user->id);
  2995. // TODO: remove from cohorts using standard API here
  2996. // remove user tags
  2997. tag_set('user', $user->id, array());
  2998. // unconditionally unenrol from all courses
  2999. enrol_user_delete($user);
  3000. // unenrol from all roles in all contexts
  3001. role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
  3002. //now do a brute force cleanup
  3003. // remove from all cohorts
  3004. $DB->delete_records('cohort_members', array('userid'=>$user->id));
  3005. // remove from all groups
  3006. $DB->delete_records('groups_members', array('userid'=>$user->id));
  3007. // brute force unenrol from all courses
  3008. $DB->delete_records('user_enrolments', array('userid'=>$user->id));
  3009. // purge user preferences
  3010. $DB->delete_records('user_preferences', array('userid'=>$user->id));
  3011. // purge user extra profile info
  3012. $DB->delete_records('user_info_data', array('userid'=>$user->id));
  3013. // last course access not necessary either
  3014. $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
  3015. // now do a final accesslib cleanup - removes all role assignments in user context and context itself
  3016. delete_context(CONTEXT_USER, $user->id);
  3017. // workaround for bulk deletes of users with the same email address
  3018. $delname = "$user->email.".time();
  3019. while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
  3020. $delname++;
  3021. }
  3022. // mark internal user record as "deleted"
  3023. $updateuser = new stdClass();
  3024. $updateuser->id = $user->id;
  3025. $updateuser->deleted = 1;
  3026. $updateuser->username = $delname; // Remember it just in case
  3027. $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
  3028. $updateuser->idnumber = ''; // Clear this field to free it up
  3029. $updateuser->timemodified = time();
  3030. $DB->update_record('user', $updateuser);
  3031. // notify auth plugin - do not block the delete even when plugin fails
  3032. $authplugin = get_auth_plugin($user->auth);
  3033. $authplugin->user_delete($user);
  3034. // any plugin that needs to cleanup should register this event
  3035. events_trigger('user_deleted', $user);
  3036. return true;
  3037. }
  3038. /**
  3039. * Retrieve the guest user object
  3040. *
  3041. * @global object
  3042. * @global object
  3043. * @return user A {@link $USER} object
  3044. */
  3045. function guest_user() {
  3046. global $CFG, $DB;
  3047. if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
  3048. $newuser->confirmed = 1;
  3049. $newuser->lang = $CFG->lang;
  3050. $newuser->lastip = getremoteaddr();
  3051. }
  3052. return $newuser;
  3053. }
  3054. /**
  3055. * Authenticates a user against the chosen authentication mechanism
  3056. *
  3057. * Given a username and password, this function looks them
  3058. * up using the currently selected authentication mechanism,
  3059. * and if the authentication is successful, it returns a
  3060. * valid $user object from the 'user' table.
  3061. *
  3062. * Uses auth_ functions from the currently active auth module
  3063. *
  3064. * After authenticate_user_login() returns success, you will need to
  3065. * log that the user has logged in, and call complete_user_login() to set
  3066. * the session up.
  3067. *
  3068. * Note: this function works only with non-mnet accounts!
  3069. *
  3070. * @param string $username User's username
  3071. * @param string $password User's password
  3072. * @return user|flase A {@link $USER} object or false if error
  3073. */
  3074. function authenticate_user_login($username, $password) {
  3075. global $CFG, $DB;
  3076. $authsenabled = get_enabled_auth_plugins();
  3077. if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
  3078. $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
  3079. if (!empty($user->suspended)) {
  3080. add_to_log(SITEID, 'login', 'error', 'index.php', $username);
  3081. error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  3082. return false;
  3083. }
  3084. if ($auth=='nologin' or !is_enabled_auth($auth)) {
  3085. add_to_log(SITEID, 'login', 'error', 'index.php', $username);
  3086. error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  3087. return false;
  3088. }
  3089. $auths = array($auth);
  3090. } else {
  3091. // check if there's a deleted record (cheaply)
  3092. if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
  3093. error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  3094. return false;
  3095. }
  3096. // User does not exist
  3097. $auths = $authsenabled;
  3098. $user = new stdClass();
  3099. $user->id = 0;
  3100. }
  3101. foreach ($auths as $auth) {
  3102. $authplugin = get_auth_plugin($auth);
  3103. // on auth fail fall through to the next plugin
  3104. if (!$authplugin->user_login($username, $password)) {
  3105. continue;
  3106. }
  3107. // successful authentication
  3108. if ($user->id) { // User already exists in database
  3109. if (empty($user->auth)) { // For some reason auth isn't set yet
  3110. $DB->set_field('user', 'auth', $auth, array('username'=>$username));
  3111. $user->auth = $auth;
  3112. }
  3113. if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
  3114. $DB->set_field('user','firstaccess', $user->timemodified, array('id' => $user->id));
  3115. $user->firstaccess = $user->timemodified;
  3116. }
  3117. update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
  3118. if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
  3119. $user = update_user_record($username);
  3120. }
  3121. } else {
  3122. // if user not found, create him
  3123. $user = create_user_record($username, $password, $auth);
  3124. }
  3125. $authplugin->sync_roles($user);
  3126. foreach ($authsenabled as $hau) {
  3127. $hauth = get_auth_plugin($hau);
  3128. $hauth->user_authenticated_hook($user, $username, $password);
  3129. }
  3130. if (empty($user->id)) {
  3131. return false;
  3132. }
  3133. if (!empty($user->suspended)) {
  3134. // just in case some auth plugin suspended account
  3135. add_to_log(SITEID, 'login', 'error', 'index.php', $username);
  3136. error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  3137. return false;
  3138. }
  3139. return $user;
  3140. }
  3141. // failed if all the plugins have failed
  3142. add_to_log(SITEID, 'login', 'error', 'index.php', $username);
  3143. if (debugging('', DEBUG_ALL)) {
  3144. error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
  3145. }
  3146. return false;
  3147. }
  3148. /**
  3149. * Call to complete the user login process after authenticate_user_login()
  3150. * has succeeded. It will setup the $USER variable and other required bits
  3151. * and pieces.
  3152. *
  3153. * NOTE:
  3154. * - It will NOT log anything -- up to the caller to decide what to log.
  3155. *
  3156. * @param object $user
  3157. * @param bool $setcookie
  3158. * @return object A {@link $USER} object - BC only, do not use
  3159. */
  3160. function complete_user_login($user, $setcookie=true) {
  3161. global $CFG, $USER;
  3162. // regenerate session id and delete old session,
  3163. // this helps prevent session fixation attacks from the same domain
  3164. session_regenerate_id(true);
  3165. // check enrolments, load caps and setup $USER object
  3166. session_set_user($user);
  3167. // reload preferences from DB
  3168. unset($user->preference);
  3169. check_user_preferences_loaded($user);
  3170. // update login times
  3171. update_user_login_times();
  3172. // extra session prefs init
  3173. set_login_session_preferences();
  3174. if (isguestuser()) {
  3175. // no need to continue when user is THE guest
  3176. return $USER;
  3177. }
  3178. if ($setcookie) {
  3179. if (empty($CFG->nolastloggedin)) {
  3180. set_moodle_cookie($USER->username);
  3181. } else {
  3182. // do not store last logged in user in cookie
  3183. // auth plugins can temporarily override this from loginpage_hook()
  3184. // do not save $CFG->nolastloggedin in database!
  3185. set_moodle_cookie('');
  3186. }
  3187. }
  3188. /// Select password change url
  3189. $userauth = get_auth_plugin($USER->auth);
  3190. /// check whether the user should be changing password
  3191. if (get_user_preferences('auth_forcepasswordchange', false)){
  3192. if ($userauth->can_change_password()) {
  3193. if ($changeurl = $userauth->change_password_url()) {
  3194. redirect($changeurl);
  3195. } else {
  3196. redirect($CFG->httpswwwroot.'/login/change_password.php');
  3197. }
  3198. } else {
  3199. print_error('nopasswordchangeforced', 'auth');
  3200. }
  3201. }
  3202. return $USER;
  3203. }
  3204. /**
  3205. * Compare password against hash stored in internal user table.
  3206. * If necessary it also updates the stored hash to new format.
  3207. *
  3208. * @param stdClass $user (password property may be updated)
  3209. * @param string $password plain text password
  3210. * @return bool is password valid?
  3211. */
  3212. function validate_internal_user_password($user, $password) {
  3213. global $CFG;
  3214. if (!isset($CFG->passwordsaltmain)) {
  3215. $CFG->passwordsaltmain = '';
  3216. }
  3217. $validated = false;
  3218. if ($user->password === 'not cached') {
  3219. // internal password is not used at all, it can not validate
  3220. } else if ($user->password === md5($password.$CFG->passwordsaltmain)
  3221. or $user->password === md5($password)
  3222. or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
  3223. or $user->password === md5(addslashes($password))) {
  3224. // note: we are intentionally using the addslashes() here because we
  3225. // need to accept old password hashes of passwords with magic quotes
  3226. $validated = true;
  3227. } else {
  3228. for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
  3229. $alt = 'passwordsaltalt'.$i;
  3230. if (!empty($CFG->$alt)) {
  3231. if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
  3232. $validated = true;
  3233. break;
  3234. }
  3235. }
  3236. }
  3237. }
  3238. if ($validated) {
  3239. // force update of password hash using latest main password salt and encoding if needed
  3240. update_internal_user_password($user, $password);
  3241. }
  3242. return $validated;
  3243. }
  3244. /**
  3245. * Calculate hashed value from password using current hash mechanism.
  3246. *
  3247. * @param string $password
  3248. * @return string password hash
  3249. */
  3250. function hash_internal_user_password($password) {
  3251. global $CFG;
  3252. if (isset($CFG->passwordsaltmain)) {
  3253. return md5($password.$CFG->passwordsaltmain);
  3254. } else {
  3255. return md5($password);
  3256. }
  3257. }
  3258. /**
  3259. * Update password hash in user object.
  3260. *
  3261. * @param stdClass $user (password property may be updated)
  3262. * @param string $password plain text password
  3263. * @return bool always returns true
  3264. */
  3265. function update_internal_user_password($user, $password) {
  3266. global $DB;
  3267. $authplugin = get_auth_plugin($user->auth);
  3268. if ($authplugin->prevent_local_passwords()) {
  3269. $hashedpassword = 'not cached';
  3270. } else {
  3271. $hashedpassword = hash_internal_user_password($password);
  3272. }
  3273. if ($user->password !== $hashedpassword) {
  3274. $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
  3275. $user->password = $hashedpassword;
  3276. }
  3277. return true;
  3278. }
  3279. /**
  3280. * Get a complete user record, which includes all the info
  3281. * in the user record.
  3282. *
  3283. * Intended for setting as $USER session variable
  3284. *
  3285. * @param string $field The user field to be checked for a given value.
  3286. * @param string $value The value to match for $field.
  3287. * @param int $mnethostid
  3288. * @return mixed False, or A {@link $USER} object.
  3289. */
  3290. function get_complete_user_data($field, $value, $mnethostid = null) {
  3291. global $CFG, $DB;
  3292. if (!$field || !$value) {
  3293. return false;
  3294. }
  3295. /// Build the WHERE clause for an SQL query
  3296. $params = array('fieldval'=>$value);
  3297. $constraints = "$field = :fieldval AND deleted <> 1";
  3298. // If we are loading user data based on anything other than id,
  3299. // we must also restrict our search based on mnet host.
  3300. if ($field != 'id') {
  3301. if (empty($mnethostid)) {
  3302. // if empty, we restrict to local users
  3303. $mnethostid = $CFG->mnet_localhost_id;
  3304. }
  3305. }
  3306. if (!empty($mnethostid)) {
  3307. $params['mnethostid'] = $mnethostid;
  3308. $constraints .= " AND mnethostid = :mnethostid";
  3309. }
  3310. /// Get all the basic user data
  3311. if (! $user = $DB->get_record_select('user', $constraints, $params)) {
  3312. return false;
  3313. }
  3314. /// Get various settings and preferences
  3315. // preload preference cache
  3316. check_user_preferences_loaded($user);
  3317. // load course enrolment related stuff
  3318. $user->lastcourseaccess = array(); // during last session
  3319. $user->currentcourseaccess = array(); // during current session
  3320. if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
  3321. foreach ($lastaccesses as $lastaccess) {
  3322. $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
  3323. }
  3324. }
  3325. $sql = "SELECT g.id, g.courseid
  3326. FROM {groups} g, {groups_members} gm
  3327. WHERE gm.groupid=g.id AND gm.userid=?";
  3328. // this is a special hack to speedup calendar display
  3329. $user->groupmember = array();
  3330. if (!isguestuser($user)) {
  3331. if ($groups = $DB->get_records_sql($sql, array($user->id))) {
  3332. foreach ($groups as $group) {
  3333. if (!array_key_exists($group->courseid, $user->groupmember)) {
  3334. $user->groupmember[$group->courseid] = array();
  3335. }
  3336. $user->groupmember[$group->courseid][$group->id] = $group->id;
  3337. }
  3338. }
  3339. }
  3340. /// Add the custom profile fields to the user record
  3341. $user->profile = array();
  3342. if (!isguestuser($user)) {
  3343. require_once($CFG->dirroot.'/user/profile/lib.php');
  3344. profile_load_custom_fields($user);
  3345. }
  3346. /// Rewrite some variables if necessary
  3347. if (!empty($user->description)) {
  3348. $user->description = true; // No need to cart all of it around
  3349. }
  3350. if (isguestuser($user)) {
  3351. $user->lang = $CFG->lang; // Guest language always same as site
  3352. $user->firstname = get_string('guestuser'); // Name always in current language
  3353. $user->lastname = ' ';
  3354. }
  3355. return $user;
  3356. }
  3357. /**
  3358. * Validate a password against the configured password policy
  3359. *
  3360. * @global object
  3361. * @param string $password the password to be checked against the password policy
  3362. * @param string $errmsg the error message to display when the password doesn't comply with the policy.
  3363. * @return bool true if the password is valid according to the policy. false otherwise.
  3364. */
  3365. function check_password_policy($password, &$errmsg) {
  3366. global $CFG;
  3367. if (empty($CFG->passwordpolicy)) {
  3368. return true;
  3369. }
  3370. $textlib = textlib_get_instance();
  3371. $errmsg = '';
  3372. if ($textlib->strlen($password) < $CFG->minpasswordlength) {
  3373. $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
  3374. }
  3375. if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
  3376. $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
  3377. }
  3378. if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
  3379. $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
  3380. }
  3381. if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
  3382. $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
  3383. }
  3384. if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
  3385. $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
  3386. }
  3387. if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
  3388. $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
  3389. }
  3390. if ($errmsg == '') {
  3391. return true;
  3392. } else {
  3393. return false;
  3394. }
  3395. }
  3396. /**
  3397. * When logging in, this function is run to set certain preferences
  3398. * for the current SESSION
  3399. *
  3400. * @global object
  3401. * @global object
  3402. */
  3403. function set_login_session_preferences() {
  3404. global $SESSION, $CFG;
  3405. $SESSION->justloggedin = true;
  3406. unset($SESSION->lang);
  3407. // Restore the calendar filters, if saved
  3408. if (intval(get_user_preferences('calendar_persistflt', 0))) {
  3409. include_once($CFG->dirroot.'/calendar/lib.php');
  3410. calendar_set_filters_status(get_user_preferences('calendar_savedflt', 0xff));
  3411. }
  3412. }
  3413. /**
  3414. * Delete a course, including all related data from the database,
  3415. * and any associated files.
  3416. *
  3417. * @global object
  3418. * @global object
  3419. * @param mixed $courseorid The id of the course or course object to delete.
  3420. * @param bool $showfeedback Whether to display notifications of each action the function performs.
  3421. * @return bool true if all the removals succeeded. false if there were any failures. If this
  3422. * method returns false, some of the removals will probably have succeeded, and others
  3423. * failed, but you have no way of knowing which.
  3424. */
  3425. function delete_course($courseorid, $showfeedback = true) {
  3426. global $DB;
  3427. if (is_object($courseorid)) {
  3428. $courseid = $courseorid->id;
  3429. $course = $courseorid;
  3430. } else {
  3431. $courseid = $courseorid;
  3432. if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
  3433. return false;
  3434. }
  3435. }
  3436. $context = get_context_instance(CONTEXT_COURSE, $courseid);
  3437. // frontpage course can not be deleted!!
  3438. if ($courseid == SITEID) {
  3439. return false;
  3440. }
  3441. // make the course completely empty
  3442. remove_course_contents($courseid, $showfeedback);
  3443. // delete the course and related context instance
  3444. delete_context(CONTEXT_COURSE, $courseid);
  3445. $DB->delete_records("course", array("id"=>$courseid));
  3446. //trigger events
  3447. $course->context = $context; // you can not fetch context in the event because it was already deleted
  3448. events_trigger('course_deleted', $course);
  3449. return true;
  3450. }
  3451. /**
  3452. * Clear a course out completely, deleting all content
  3453. * but don't delete the course itself.
  3454. * This function does not verify any permissions.
  3455. *
  3456. * Please note this function also deletes all user enrolments,
  3457. * enrolment instances and role assignments.
  3458. *
  3459. * @param int $courseid The id of the course that is being deleted
  3460. * @param bool $showfeedback Whether to display notifications of each action the function performs.
  3461. * @return bool true if all the removals succeeded. false if there were any failures. If this
  3462. * method returns false, some of the removals will probably have succeeded, and others
  3463. * failed, but you have no way of knowing which.
  3464. */
  3465. function remove_course_contents($courseid, $showfeedback = true) {
  3466. global $CFG, $DB, $OUTPUT;
  3467. require_once($CFG->libdir.'/completionlib.php');
  3468. require_once($CFG->libdir.'/questionlib.php');
  3469. require_once($CFG->libdir.'/gradelib.php');
  3470. require_once($CFG->dirroot.'/group/lib.php');
  3471. require_once($CFG->dirroot.'/tag/coursetagslib.php');
  3472. $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
  3473. $context = get_context_instance(CONTEXT_COURSE, $courseid, MUST_EXIST);
  3474. $strdeleted = get_string('deleted');
  3475. // Delete course completion information,
  3476. // this has to be done before grades and enrols
  3477. $cc = new completion_info($course);
  3478. $cc->clear_criteria();
  3479. // remove roles and enrolments
  3480. role_unassign_all(array('contextid'=>$context->id), true);
  3481. enrol_course_delete($course);
  3482. // Clean up course formats (iterate through all formats in the even the course format was ever changed)
  3483. $formats = get_plugin_list('format');
  3484. foreach ($formats as $format=>$formatdir) {
  3485. $formatdelete = 'format_'.$format.'_delete_course';
  3486. $formatlib = "$formatdir/lib.php";
  3487. if (file_exists($formatlib)) {
  3488. include_once($formatlib);
  3489. if (function_exists($formatdelete)) {
  3490. if ($showfeedback) {
  3491. echo $OUTPUT->notification($strdeleted.' '.$format);
  3492. }
  3493. $formatdelete($course->id);
  3494. }
  3495. }
  3496. }
  3497. // Remove all data from gradebook - this needs to be done before course modules
  3498. // because while deleting this information, the system may need to reference
  3499. // the course modules that own the grades.
  3500. remove_course_grades($courseid, $showfeedback);
  3501. remove_grade_letters($context, $showfeedback);
  3502. // Remove all data from availability and completion tables that is associated
  3503. // with course-modules belonging to this course. Note this is done even if the
  3504. // features are not enabled now, in case they were enabled previously
  3505. $DB->delete_records_select('course_modules_completion',
  3506. 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
  3507. array($courseid));
  3508. $DB->delete_records_select('course_modules_availability',
  3509. 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
  3510. array($courseid));
  3511. // Delete course blocks - they may depend on modules so delete them first
  3512. blocks_delete_all_for_context($context->id);
  3513. // Delete every instance of every module
  3514. if ($allmods = $DB->get_records('modules') ) {
  3515. foreach ($allmods as $mod) {
  3516. $modname = $mod->name;
  3517. $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
  3518. $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
  3519. $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
  3520. $count=0;
  3521. if (file_exists($modfile)) {
  3522. include_once($modfile);
  3523. if (function_exists($moddelete)) {
  3524. if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
  3525. foreach ($instances as $instance) {
  3526. if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
  3527. /// Delete activity context questions and question categories
  3528. question_delete_activity($cm, $showfeedback);
  3529. }
  3530. if ($moddelete($instance->id)) {
  3531. $count++;
  3532. } else {
  3533. echo $OUTPUT->notification('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
  3534. }
  3535. if ($cm) {
  3536. // delete cm and its context in correct order
  3537. delete_context(CONTEXT_MODULE, $cm->id); // some callbacks may try to fetch context, better delete first
  3538. $DB->delete_records('course_modules', array('id'=>$cm->id));
  3539. }
  3540. }
  3541. }
  3542. } else {
  3543. //note: we should probably delete these anyway
  3544. echo $OUTPUT->notification('Function '.$moddelete.'() doesn\'t exist!');
  3545. }
  3546. if (function_exists($moddeletecourse)) {
  3547. $moddeletecourse($course, $showfeedback);
  3548. }
  3549. }
  3550. if ($showfeedback) {
  3551. echo $OUTPUT->notification($strdeleted .' '. $count .' x '. $modname);
  3552. }
  3553. }
  3554. }
  3555. // Delete any groups, removing members and grouping/course links first.
  3556. groups_delete_groupings($course->id, $showfeedback);
  3557. groups_delete_groups($course->id, $showfeedback);
  3558. // Delete questions and question categories
  3559. question_delete_course($course, $showfeedback);
  3560. // Delete course tags
  3561. coursetag_delete_course_tags($course->id, $showfeedback);
  3562. // Delete legacy files (just in case some files are still left there after conversion to new file api)
  3563. fulldelete($CFG->dataroot.'/'.$course->id);
  3564. // cleanup course record - remove links to delted stuff
  3565. $oldcourse = new stdClass();
  3566. $oldcourse->id = $course->id;
  3567. $oldcourse->summary = '';
  3568. $oldcourse->modinfo = NULL;
  3569. $oldcourse->legacyfiles = 0;
  3570. $oldcourse->defaultgroupingid = 0;
  3571. $oldcourse->enablecompletion = 0;
  3572. $DB->update_record('course', $oldcourse);
  3573. // Delete all related records in other tables that may have a courseid
  3574. // This array stores the tables that need to be cleared, as
  3575. // table_name => column_name that contains the course id.
  3576. $tablestoclear = array(
  3577. 'event' => 'courseid', // Delete events
  3578. 'log' => 'course', // Delete logs
  3579. 'course_sections' => 'course', // Delete any course stuff
  3580. 'course_modules' => 'course',
  3581. 'course_display' => 'course',
  3582. 'backup_courses' => 'courseid', // Delete scheduled backup stuff
  3583. 'user_lastaccess' => 'courseid',
  3584. 'backup_log' => 'courseid'
  3585. );
  3586. foreach ($tablestoclear as $table => $col) {
  3587. $DB->delete_records($table, array($col=>$course->id));
  3588. }
  3589. // Delete all remaining stuff linked to context,
  3590. // such as remaining roles, files, comments, etc.
  3591. // Keep the context record for now.
  3592. delete_context(CONTEXT_COURSE, $course->id, false);
  3593. //trigger events
  3594. $course->context = $context; // you can not access context in cron event later after course is deleted
  3595. events_trigger('course_content_removed', $course);
  3596. return true;
  3597. }
  3598. /**
  3599. * Change dates in module - used from course reset.
  3600. *
  3601. * @global object
  3602. * @global object
  3603. * @param string $modname forum, assignment, etc
  3604. * @param array $fields array of date fields from mod table
  3605. * @param int $timeshift time difference
  3606. * @param int $courseid
  3607. * @return bool success
  3608. */
  3609. function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
  3610. global $CFG, $DB;
  3611. include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
  3612. $return = true;
  3613. foreach ($fields as $field) {
  3614. $updatesql = "UPDATE {".$modname."}
  3615. SET $field = $field + ?
  3616. WHERE course=? AND $field<>0 AND $field<>0";
  3617. $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
  3618. }
  3619. $refreshfunction = $modname.'_refresh_events';
  3620. if (function_exists($refreshfunction)) {
  3621. $refreshfunction($courseid);
  3622. }
  3623. return $return;
  3624. }
  3625. /**
  3626. * This function will empty a course of user data.
  3627. * It will retain the activities and the structure of the course.
  3628. *
  3629. * @param object $data an object containing all the settings including courseid (without magic quotes)
  3630. * @return array status array of array component, item, error
  3631. */
  3632. function reset_course_userdata($data) {
  3633. global $CFG, $USER, $DB;
  3634. require_once($CFG->libdir.'/gradelib.php');
  3635. require_once($CFG->libdir.'/completionlib.php');
  3636. require_once($CFG->dirroot.'/group/lib.php');
  3637. $data->courseid = $data->id;
  3638. $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
  3639. // calculate the time shift of dates
  3640. if (!empty($data->reset_start_date)) {
  3641. // time part of course startdate should be zero
  3642. $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
  3643. } else {
  3644. $data->timeshift = 0;
  3645. }
  3646. // result array: component, item, error
  3647. $status = array();
  3648. // start the resetting
  3649. $componentstr = get_string('general');
  3650. // move the course start time
  3651. if (!empty($data->reset_start_date) and $data->timeshift) {
  3652. // change course start data
  3653. $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
  3654. // update all course and group events - do not move activity events
  3655. $updatesql = "UPDATE {event}
  3656. SET timestart = timestart + ?
  3657. WHERE courseid=? AND instance=0";
  3658. $DB->execute($updatesql, array($data->timeshift, $data->courseid));
  3659. $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
  3660. }
  3661. if (!empty($data->reset_logs)) {
  3662. $DB->delete_records('log', array('course'=>$data->courseid));
  3663. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
  3664. }
  3665. if (!empty($data->reset_events)) {
  3666. $DB->delete_records('event', array('courseid'=>$data->courseid));
  3667. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
  3668. }
  3669. if (!empty($data->reset_notes)) {
  3670. require_once($CFG->dirroot.'/notes/lib.php');
  3671. note_delete_all($data->courseid);
  3672. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
  3673. }
  3674. if (!empty($data->delete_blog_associations)) {
  3675. require_once($CFG->dirroot.'/blog/lib.php');
  3676. blog_remove_associations_for_course($data->courseid);
  3677. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
  3678. }
  3679. if (!empty($data->reset_course_completion)) {
  3680. // Delete course completion information
  3681. $course = $DB->get_record('course', array('id'=>$data->courseid));
  3682. $cc = new completion_info($course);
  3683. $cc->delete_course_completion_data();
  3684. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecoursecompletiondata', 'completion'), 'error'=>false);
  3685. }
  3686. $componentstr = get_string('roles');
  3687. if (!empty($data->reset_roles_overrides)) {
  3688. $children = get_child_contexts($context);
  3689. foreach ($children as $child) {
  3690. $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
  3691. }
  3692. $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
  3693. //force refresh for logged in users
  3694. mark_context_dirty($context->path);
  3695. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
  3696. }
  3697. if (!empty($data->reset_roles_local)) {
  3698. $children = get_child_contexts($context);
  3699. foreach ($children as $child) {
  3700. role_unassign_all(array('contextid'=>$child->id));
  3701. }
  3702. //force refresh for logged in users
  3703. mark_context_dirty($context->path);
  3704. $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
  3705. }
  3706. // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
  3707. $data->unenrolled = array();
  3708. if (!empty($data->unenrol_users)) {
  3709. $plugins = enrol_get_plugins(true);
  3710. $instances = enrol_get_instances($data->courseid, true);
  3711. foreach ($instances as $key=>$instance) {
  3712. if (!isset($plugins[$instance->enrol])) {
  3713. unset($instances[$key]);
  3714. continue;
  3715. }
  3716. if (!$plugins[$instance->enrol]->allow_unenrol($instance)) {
  3717. unset($instances[$key]);
  3718. }
  3719. }
  3720. $sqlempty = $DB->sql_empty();
  3721. foreach($data->unenrol_users as $withroleid) {
  3722. $sql = "SELECT DISTINCT ue.userid, ue.enrolid
  3723. FROM {user_enrolments} ue
  3724. JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
  3725. JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
  3726. JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
  3727. $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
  3728. $rs = $DB->get_recordset_sql($sql, $params);
  3729. foreach ($rs as $ue) {
  3730. if (!isset($instances[$ue->enrolid])) {
  3731. continue;
  3732. }
  3733. $plugins[$instances[$ue->enrolid]->enrol]->unenrol_user($instances[$ue->enrolid], $ue->userid);
  3734. $data->unenrolled[$ue->userid] = $ue->userid;
  3735. }
  3736. }
  3737. }
  3738. if (!empty($data->unenrolled)) {
  3739. $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
  3740. }
  3741. $componentstr = get_string('groups');
  3742. // remove all group members
  3743. if (!empty($data->reset_groups_members)) {
  3744. groups_delete_group_members($data->courseid);
  3745. $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
  3746. }
  3747. // remove all groups
  3748. if (!empty($data->reset_groups_remove)) {
  3749. groups_delete_groups($data->courseid, false);
  3750. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
  3751. }
  3752. // remove all grouping members
  3753. if (!empty($data->reset_groupings_members)) {
  3754. groups_delete_groupings_groups($data->courseid, false);
  3755. $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
  3756. }
  3757. // remove all groupings
  3758. if (!empty($data->reset_groupings_remove)) {
  3759. groups_delete_groupings($data->courseid, false);
  3760. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
  3761. }
  3762. // Look in every instance of every module for data to delete
  3763. $unsupported_mods = array();
  3764. if ($allmods = $DB->get_records('modules') ) {
  3765. foreach ($allmods as $mod) {
  3766. $modname = $mod->name;
  3767. if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
  3768. continue; // skip mods with no instances
  3769. }
  3770. $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
  3771. $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
  3772. if (file_exists($modfile)) {
  3773. include_once($modfile);
  3774. if (function_exists($moddeleteuserdata)) {
  3775. $modstatus = $moddeleteuserdata($data);
  3776. if (is_array($modstatus)) {
  3777. $status = array_merge($status, $modstatus);
  3778. } else {
  3779. debugging('Module '.$modname.' returned incorrect staus - must be an array!');
  3780. }
  3781. } else {
  3782. $unsupported_mods[] = $mod;
  3783. }
  3784. } else {
  3785. debugging('Missing lib.php in '.$modname.' module!');
  3786. }
  3787. }
  3788. }
  3789. // mention unsupported mods
  3790. if (!empty($unsupported_mods)) {
  3791. foreach($unsupported_mods as $mod) {
  3792. $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
  3793. }
  3794. }
  3795. $componentstr = get_string('gradebook', 'grades');
  3796. // reset gradebook
  3797. if (!empty($data->reset_gradebook_items)) {
  3798. remove_course_grades($data->courseid, false);
  3799. grade_grab_course_grades($data->courseid);
  3800. grade_regrade_final_grades($data->courseid);
  3801. $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
  3802. } else if (!empty($data->reset_gradebook_grades)) {
  3803. grade_course_reset($data->courseid);
  3804. $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
  3805. }
  3806. // reset comments
  3807. if (!empty($data->reset_comments)) {
  3808. require_once($CFG->dirroot.'/comment/lib.php');
  3809. comment::reset_course_page_comments($context);
  3810. }
  3811. return $status;
  3812. }
  3813. /**
  3814. * Generate an email processing address
  3815. *
  3816. * @param int $modid
  3817. * @param string $modargs
  3818. * @return string Returns email processing address
  3819. */
  3820. function generate_email_processing_address($modid,$modargs) {
  3821. global $CFG;
  3822. $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
  3823. return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
  3824. }
  3825. /**
  3826. * ?
  3827. *
  3828. * @todo Finish documenting this function
  3829. *
  3830. * @global object
  3831. * @param string $modargs
  3832. * @param string $body Currently unused
  3833. */
  3834. function moodle_process_email($modargs,$body) {
  3835. global $DB;
  3836. // the first char should be an unencoded letter. We'll take this as an action
  3837. switch ($modargs{0}) {
  3838. case 'B': { // bounce
  3839. list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
  3840. if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
  3841. // check the half md5 of their email
  3842. $md5check = substr(md5($user->email),0,16);
  3843. if ($md5check == substr($modargs, -16)) {
  3844. set_bounce_count($user);
  3845. }
  3846. // else maybe they've already changed it?
  3847. }
  3848. }
  3849. break;
  3850. // maybe more later?
  3851. }
  3852. }
  3853. /// CORRESPONDENCE ////////////////////////////////////////////////
  3854. /**
  3855. * Get mailer instance, enable buffering, flush buffer or disable buffering.
  3856. *
  3857. * @global object
  3858. * @param string $action 'get', 'buffer', 'close' or 'flush'
  3859. * @return object|null mailer instance if 'get' used or nothing
  3860. */
  3861. function get_mailer($action='get') {
  3862. global $CFG;
  3863. static $mailer = null;
  3864. static $counter = 0;
  3865. if (!isset($CFG->smtpmaxbulk)) {
  3866. $CFG->smtpmaxbulk = 1;
  3867. }
  3868. if ($action == 'get') {
  3869. $prevkeepalive = false;
  3870. if (isset($mailer) and $mailer->Mailer == 'smtp') {
  3871. if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
  3872. $counter++;
  3873. // reset the mailer
  3874. $mailer->Priority = 3;
  3875. $mailer->CharSet = 'UTF-8'; // our default
  3876. $mailer->ContentType = "text/plain";
  3877. $mailer->Encoding = "8bit";
  3878. $mailer->From = "root@localhost";
  3879. $mailer->FromName = "Root User";
  3880. $mailer->Sender = "";
  3881. $mailer->Subject = "";
  3882. $mailer->Body = "";
  3883. $mailer->AltBody = "";
  3884. $mailer->ConfirmReadingTo = "";
  3885. $mailer->ClearAllRecipients();
  3886. $mailer->ClearReplyTos();
  3887. $mailer->ClearAttachments();
  3888. $mailer->ClearCustomHeaders();
  3889. return $mailer;
  3890. }
  3891. $prevkeepalive = $mailer->SMTPKeepAlive;
  3892. get_mailer('flush');
  3893. }
  3894. include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
  3895. $mailer = new moodle_phpmailer();
  3896. $counter = 1;
  3897. $mailer->Version = 'Moodle '.$CFG->version; // mailer version
  3898. $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
  3899. $mailer->CharSet = 'UTF-8';
  3900. // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
  3901. if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
  3902. $mailer->LE = "\r\n";
  3903. } else {
  3904. $mailer->LE = "\n";
  3905. }
  3906. if ($CFG->smtphosts == 'qmail') {
  3907. $mailer->IsQmail(); // use Qmail system
  3908. } else if (empty($CFG->smtphosts)) {
  3909. $mailer->IsMail(); // use PHP mail() = sendmail
  3910. } else {
  3911. $mailer->IsSMTP(); // use SMTP directly
  3912. if (!empty($CFG->debugsmtp)) {
  3913. $mailer->SMTPDebug = true;
  3914. }
  3915. $mailer->Host = $CFG->smtphosts; // specify main and backup servers
  3916. $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
  3917. if ($CFG->smtpuser) { // Use SMTP authentication
  3918. $mailer->SMTPAuth = true;
  3919. $mailer->Username = $CFG->smtpuser;
  3920. $mailer->Password = $CFG->smtppass;
  3921. }
  3922. }
  3923. return $mailer;
  3924. }
  3925. $nothing = null;
  3926. // keep smtp session open after sending
  3927. if ($action == 'buffer') {
  3928. if (!empty($CFG->smtpmaxbulk)) {
  3929. get_mailer('flush');
  3930. $m = get_mailer();
  3931. if ($m->Mailer == 'smtp') {
  3932. $m->SMTPKeepAlive = true;
  3933. }
  3934. }
  3935. return $nothing;
  3936. }
  3937. // close smtp session, but continue buffering
  3938. if ($action == 'flush') {
  3939. if (isset($mailer) and $mailer->Mailer == 'smtp') {
  3940. if (!empty($mailer->SMTPDebug)) {
  3941. echo '<pre>'."\n";
  3942. }
  3943. $mailer->SmtpClose();
  3944. if (!empty($mailer->SMTPDebug)) {
  3945. echo '</pre>';
  3946. }
  3947. }
  3948. return $nothing;
  3949. }
  3950. // close smtp session, do not buffer anymore
  3951. if ($action == 'close') {
  3952. if (isset($mailer) and $mailer->Mailer == 'smtp') {
  3953. get_mailer('flush');
  3954. $mailer->SMTPKeepAlive = false;
  3955. }
  3956. $mailer = null; // better force new instance
  3957. return $nothing;
  3958. }
  3959. }
  3960. /**
  3961. * Send an email to a specified user
  3962. *
  3963. * @global object
  3964. * @global string
  3965. * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
  3966. * @uses SITEID
  3967. * @param stdClass $user A {@link $USER} object
  3968. * @param stdClass $from A {@link $USER} object
  3969. * @param string $subject plain text subject line of the email
  3970. * @param string $messagetext plain text version of the message
  3971. * @param string $messagehtml complete html version of the message (optional)
  3972. * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
  3973. * @param string $attachname the name of the file (extension indicates MIME)
  3974. * @param bool $usetrueaddress determines whether $from email address should
  3975. * be sent out. Will be overruled by user profile setting for maildisplay
  3976. * @param string $replyto Email address to reply to
  3977. * @param string $replytoname Name of reply to recipient
  3978. * @param int $wordwrapwidth custom word wrap width, default 79
  3979. * @return bool Returns true if mail was sent OK and false if there was an error.
  3980. */
  3981. function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
  3982. global $CFG, $FULLME;
  3983. if (empty($user) || empty($user->email)) {
  3984. mtrace('Error: lib/moodlelib.php email_to_user(): User is null or has no email');
  3985. return false;
  3986. }
  3987. if (!empty($user->deleted)) {
  3988. // do not mail delted users
  3989. mtrace('Error: lib/moodlelib.php email_to_user(): User is deleted');
  3990. return false;
  3991. }
  3992. if (!empty($CFG->noemailever)) {
  3993. // hidden setting for development sites, set in config.php if needed
  3994. mtrace('Error: lib/moodlelib.php email_to_user(): Not sending email due to noemailever config setting');
  3995. return true;
  3996. }
  3997. if (!empty($CFG->divertallemailsto)) {
  3998. $subject = "[DIVERTED {$user->email}] $subject";
  3999. $user = clone($user);
  4000. $user->email = $CFG->divertallemailsto;
  4001. }
  4002. // skip mail to suspended users
  4003. if (isset($user->auth) && $user->auth=='nologin') {
  4004. return true;
  4005. }
  4006. if (over_bounce_threshold($user)) {
  4007. $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
  4008. error_log($bouncemsg);
  4009. mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
  4010. return false;
  4011. }
  4012. // If the user is a remote mnet user, parse the email text for URL to the
  4013. // wwwroot and modify the url to direct the user's browser to login at their
  4014. // home site (identity provider - idp) before hitting the link itself
  4015. if (is_mnet_remote_user($user)) {
  4016. require_once($CFG->dirroot.'/mnet/lib.php');
  4017. $jumpurl = mnet_get_idp_jump_url($user);
  4018. $callback = partial('mnet_sso_apply_indirection', $jumpurl);
  4019. $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
  4020. $callback,
  4021. $messagetext);
  4022. $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
  4023. $callback,
  4024. $messagehtml);
  4025. }
  4026. $mail = get_mailer();
  4027. if (!empty($mail->SMTPDebug)) {
  4028. echo '<pre>' . "\n";
  4029. }
  4030. $temprecipients = array();
  4031. $tempreplyto = array();
  4032. $supportuser = generate_email_supportuser();
  4033. // make up an email address for handling bounces
  4034. if (!empty($CFG->handlebounces)) {
  4035. $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
  4036. $mail->Sender = generate_email_processing_address(0,$modargs);
  4037. } else {
  4038. $mail->Sender = $supportuser->email;
  4039. }
  4040. if (is_string($from)) { // So we can pass whatever we want if there is need
  4041. $mail->From = $CFG->noreplyaddress;
  4042. $mail->FromName = $from;
  4043. } else if ($usetrueaddress and $from->maildisplay) {
  4044. $mail->From = $from->email;
  4045. $mail->FromName = fullname($from);
  4046. } else {
  4047. $mail->From = $CFG->noreplyaddress;
  4048. $mail->FromName = fullname($from);
  4049. if (empty($replyto)) {
  4050. $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
  4051. }
  4052. }
  4053. if (!empty($replyto)) {
  4054. $tempreplyto[] = array($replyto, $replytoname);
  4055. }
  4056. $mail->Subject = substr($subject, 0, 900);
  4057. $temprecipients[] = array($user->email, fullname($user));
  4058. $mail->WordWrap = $wordwrapwidth; // set word wrap
  4059. if (!empty($from->customheaders)) { // Add custom headers
  4060. if (is_array($from->customheaders)) {
  4061. foreach ($from->customheaders as $customheader) {
  4062. $mail->AddCustomHeader($customheader);
  4063. }
  4064. } else {
  4065. $mail->AddCustomHeader($from->customheaders);
  4066. }
  4067. }
  4068. if (!empty($from->priority)) {
  4069. $mail->Priority = $from->priority;
  4070. }
  4071. if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
  4072. $mail->IsHTML(true);
  4073. $mail->Encoding = 'quoted-printable'; // Encoding to use
  4074. $mail->Body = $messagehtml;
  4075. $mail->AltBody = "\n$messagetext\n";
  4076. } else {
  4077. $mail->IsHTML(false);
  4078. $mail->Body = "\n$messagetext\n";
  4079. }
  4080. if ($attachment && $attachname) {
  4081. if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
  4082. $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
  4083. $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
  4084. } else {
  4085. require_once($CFG->libdir.'/filelib.php');
  4086. $mimetype = mimeinfo('type', $attachname);
  4087. $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
  4088. }
  4089. }
  4090. // Check if the email should be sent in an other charset then the default UTF-8
  4091. if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
  4092. // use the defined site mail charset or eventually the one preferred by the recipient
  4093. $charset = $CFG->sitemailcharset;
  4094. if (!empty($CFG->allowusermailcharset)) {
  4095. if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
  4096. $charset = $useremailcharset;
  4097. }
  4098. }
  4099. // convert all the necessary strings if the charset is supported
  4100. $charsets = get_list_of_charsets();
  4101. unset($charsets['UTF-8']);
  4102. if (in_array($charset, $charsets)) {
  4103. $textlib = textlib_get_instance();
  4104. $mail->CharSet = $charset;
  4105. $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', strtolower($charset));
  4106. $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', strtolower($charset));
  4107. $mail->Body = $textlib->convert($mail->Body, 'utf-8', strtolower($charset));
  4108. $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', strtolower($charset));
  4109. foreach ($temprecipients as $key => $values) {
  4110. $temprecipients[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
  4111. }
  4112. foreach ($tempreplyto as $key => $values) {
  4113. $tempreplyto[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
  4114. }
  4115. }
  4116. }
  4117. foreach ($temprecipients as $values) {
  4118. $mail->AddAddress($values[0], $values[1]);
  4119. }
  4120. foreach ($tempreplyto as $values) {
  4121. $mail->AddReplyTo($values[0], $values[1]);
  4122. }
  4123. if ($mail->Send()) {
  4124. set_send_count($user);
  4125. $mail->IsSMTP(); // use SMTP directly
  4126. if (!empty($mail->SMTPDebug)) {
  4127. echo '</pre>';
  4128. }
  4129. return true;
  4130. } else {
  4131. mtrace('ERROR: '. $mail->ErrorInfo);
  4132. add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
  4133. if (!empty($mail->SMTPDebug)) {
  4134. echo '</pre>';
  4135. }
  4136. return false;
  4137. }
  4138. }
  4139. /**
  4140. * Generate a signoff for emails based on support settings
  4141. *
  4142. * @global object
  4143. * @return string
  4144. */
  4145. function generate_email_signoff() {
  4146. global $CFG;
  4147. $signoff = "\n";
  4148. if (!empty($CFG->supportname)) {
  4149. $signoff .= $CFG->supportname."\n";
  4150. }
  4151. if (!empty($CFG->supportemail)) {
  4152. $signoff .= $CFG->supportemail."\n";
  4153. }
  4154. if (!empty($CFG->supportpage)) {
  4155. $signoff .= $CFG->supportpage."\n";
  4156. }
  4157. return $signoff;
  4158. }
  4159. /**
  4160. * Generate a fake user for emails based on support settings
  4161. * @global object
  4162. * @return object user info
  4163. */
  4164. function generate_email_supportuser() {
  4165. global $CFG;
  4166. static $supportuser;
  4167. if (!empty($supportuser)) {
  4168. return $supportuser;
  4169. }
  4170. $supportuser = new stdClass();
  4171. $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
  4172. $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
  4173. $supportuser->lastname = '';
  4174. $supportuser->maildisplay = true;
  4175. return $supportuser;
  4176. }
  4177. /**
  4178. * Sets specified user's password and send the new password to the user via email.
  4179. *
  4180. * @global object
  4181. * @global object
  4182. * @param user $user A {@link $USER} object
  4183. * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
  4184. */
  4185. function setnew_password_and_mail($user) {
  4186. global $CFG, $DB;
  4187. $site = get_site();
  4188. $supportuser = generate_email_supportuser();
  4189. $newpassword = generate_password();
  4190. $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
  4191. $a = new stdClass();
  4192. $a->firstname = fullname($user, true);
  4193. $a->sitename = format_string($site->fullname);
  4194. $a->username = $user->username;
  4195. $a->newpassword = $newpassword;
  4196. $a->link = $CFG->wwwroot .'/login/';
  4197. $a->signoff = generate_email_signoff();
  4198. $message = get_string('newusernewpasswordtext', '', $a);
  4199. $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
  4200. //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
  4201. return email_to_user($user, $supportuser, $subject, $message);
  4202. }
  4203. /**
  4204. * Resets specified user's password and send the new password to the user via email.
  4205. *
  4206. * @param stdClass $user A {@link $USER} object
  4207. * @return bool Returns true if mail was sent OK and false if there was an error.
  4208. */
  4209. function reset_password_and_mail($user) {
  4210. global $CFG;
  4211. $site = get_site();
  4212. $supportuser = generate_email_supportuser();
  4213. $userauth = get_auth_plugin($user->auth);
  4214. if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
  4215. trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
  4216. return false;
  4217. }
  4218. $newpassword = generate_password();
  4219. if (!$userauth->user_update_password($user, $newpassword)) {
  4220. print_error("cannotsetpassword");
  4221. }
  4222. $a = new stdClass();
  4223. $a->firstname = $user->firstname;
  4224. $a->lastname = $user->lastname;
  4225. $a->sitename = format_string($site->fullname);
  4226. $a->username = $user->username;
  4227. $a->newpassword = $newpassword;
  4228. $a->link = $CFG->httpswwwroot .'/login/change_password.php';
  4229. $a->signoff = generate_email_signoff();
  4230. $message = get_string('newpasswordtext', '', $a);
  4231. $subject = format_string($site->fullname) .': '. get_string('changedpassword');
  4232. unset_user_preference('create_password', $user); // prevent cron from generating the password
  4233. //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
  4234. return email_to_user($user, $supportuser, $subject, $message);
  4235. }
  4236. /**
  4237. * Send email to specified user with confirmation text and activation link.
  4238. *
  4239. * @global object
  4240. * @param user $user A {@link $USER} object
  4241. * @return bool Returns true if mail was sent OK and false if there was an error.
  4242. */
  4243. function send_confirmation_email($user) {
  4244. global $CFG;
  4245. $site = get_site();
  4246. $supportuser = generate_email_supportuser();
  4247. $data = new stdClass();
  4248. $data->firstname = fullname($user);
  4249. $data->sitename = format_string($site->fullname);
  4250. $data->admin = generate_email_signoff();
  4251. $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
  4252. $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
  4253. $message = get_string('emailconfirmation', '', $data);
  4254. $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
  4255. $user->mailformat = 1; // Always send HTML version as well
  4256. //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
  4257. return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
  4258. }
  4259. /**
  4260. * send_password_change_confirmation_email.
  4261. *
  4262. * @global object
  4263. * @param user $user A {@link $USER} object
  4264. * @return bool Returns true if mail was sent OK and false if there was an error.
  4265. */
  4266. function send_password_change_confirmation_email($user) {
  4267. global $CFG;
  4268. $site = get_site();
  4269. $supportuser = generate_email_supportuser();
  4270. $data = new stdClass();
  4271. $data->firstname = $user->firstname;
  4272. $data->lastname = $user->lastname;
  4273. $data->sitename = format_string($site->fullname);
  4274. $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
  4275. $data->admin = generate_email_signoff();
  4276. $message = get_string('emailpasswordconfirmation', '', $data);
  4277. $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
  4278. //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
  4279. return email_to_user($user, $supportuser, $subject, $message);
  4280. }
  4281. /**
  4282. * send_password_change_info.
  4283. *
  4284. * @global object
  4285. * @param user $user A {@link $USER} object
  4286. * @return bool Returns true if mail was sent OK and false if there was an error.
  4287. */
  4288. function send_password_change_info($user) {
  4289. global $CFG;
  4290. $site = get_site();
  4291. $supportuser = generate_email_supportuser();
  4292. $systemcontext = get_context_instance(CONTEXT_SYSTEM);
  4293. $data = new stdClass();
  4294. $data->firstname = $user->firstname;
  4295. $data->lastname = $user->lastname;
  4296. $data->sitename = format_string($site->fullname);
  4297. $data->admin = generate_email_signoff();
  4298. $userauth = get_auth_plugin($user->auth);
  4299. if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
  4300. $message = get_string('emailpasswordchangeinfodisabled', '', $data);
  4301. $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
  4302. //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
  4303. return email_to_user($user, $supportuser, $subject, $message);
  4304. }
  4305. if ($userauth->can_change_password() and $userauth->change_password_url()) {
  4306. // we have some external url for password changing
  4307. $data->link .= $userauth->change_password_url();
  4308. } else {
  4309. //no way to change password, sorry
  4310. $data->link = '';
  4311. }
  4312. if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
  4313. $message = get_string('emailpasswordchangeinfo', '', $data);
  4314. $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
  4315. } else {
  4316. $message = get_string('emailpasswordchangeinfofail', '', $data);
  4317. $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
  4318. }
  4319. //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
  4320. return email_to_user($user, $supportuser, $subject, $message);
  4321. }
  4322. /**
  4323. * Check that an email is allowed. It returns an error message if there
  4324. * was a problem.
  4325. *
  4326. * @global object
  4327. * @param string $email Content of email
  4328. * @return string|false
  4329. */
  4330. function email_is_not_allowed($email) {
  4331. global $CFG;
  4332. if (!empty($CFG->allowemailaddresses)) {
  4333. $allowed = explode(' ', $CFG->allowemailaddresses);
  4334. foreach ($allowed as $allowedpattern) {
  4335. $allowedpattern = trim($allowedpattern);
  4336. if (!$allowedpattern) {
  4337. continue;
  4338. }
  4339. if (strpos($allowedpattern, '.') === 0) {
  4340. if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
  4341. // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
  4342. return false;
  4343. }
  4344. } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
  4345. return false;
  4346. }
  4347. }
  4348. return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
  4349. } else if (!empty($CFG->denyemailaddresses)) {
  4350. $denied = explode(' ', $CFG->denyemailaddresses);
  4351. foreach ($denied as $deniedpattern) {
  4352. $deniedpattern = trim($deniedpattern);
  4353. if (!$deniedpattern) {
  4354. continue;
  4355. }
  4356. if (strpos($deniedpattern, '.') === 0) {
  4357. if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
  4358. // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
  4359. return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
  4360. }
  4361. } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
  4362. return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
  4363. }
  4364. }
  4365. }
  4366. return false;
  4367. }
  4368. /// FILE HANDLING /////////////////////////////////////////////
  4369. /**
  4370. * Returns local file storage instance
  4371. *
  4372. * @return file_storage
  4373. */
  4374. function get_file_storage() {
  4375. global $CFG;
  4376. static $fs = null;
  4377. if ($fs) {
  4378. return $fs;
  4379. }
  4380. require_once("$CFG->libdir/filelib.php");
  4381. if (isset($CFG->filedir)) {
  4382. $filedir = $CFG->filedir;
  4383. } else {
  4384. $filedir = $CFG->dataroot.'/filedir';
  4385. }
  4386. if (isset($CFG->trashdir)) {
  4387. $trashdirdir = $CFG->trashdir;
  4388. } else {
  4389. $trashdirdir = $CFG->dataroot.'/trashdir';
  4390. }
  4391. $fs = new file_storage($filedir, $trashdirdir, "$CFG->dataroot/temp/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
  4392. return $fs;
  4393. }
  4394. /**
  4395. * Returns local file storage instance
  4396. *
  4397. * @return file_browser
  4398. */
  4399. function get_file_browser() {
  4400. global $CFG;
  4401. static $fb = null;
  4402. if ($fb) {
  4403. return $fb;
  4404. }
  4405. require_once("$CFG->libdir/filelib.php");
  4406. $fb = new file_browser();
  4407. return $fb;
  4408. }
  4409. /**
  4410. * Returns file packer
  4411. *
  4412. * @param string $mimetype default application/zip
  4413. * @return file_packer
  4414. */
  4415. function get_file_packer($mimetype='application/zip') {
  4416. global $CFG;
  4417. static $fp = array();;
  4418. if (isset($fp[$mimetype])) {
  4419. return $fp[$mimetype];
  4420. }
  4421. switch ($mimetype) {
  4422. case 'application/zip':
  4423. case 'application/vnd.moodle.backup':
  4424. $classname = 'zip_packer';
  4425. break;
  4426. case 'application/x-tar':
  4427. // $classname = 'tar_packer';
  4428. // break;
  4429. default:
  4430. return false;
  4431. }
  4432. require_once("$CFG->libdir/filestorage/$classname.php");
  4433. $fp[$mimetype] = new $classname();
  4434. return $fp[$mimetype];
  4435. }
  4436. /**
  4437. * Returns current name of file on disk if it exists.
  4438. *
  4439. * @param string $newfile File to be verified
  4440. * @return string Current name of file on disk if true
  4441. */
  4442. function valid_uploaded_file($newfile) {
  4443. if (empty($newfile)) {
  4444. return '';
  4445. }
  4446. if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
  4447. return $newfile['tmp_name'];
  4448. } else {
  4449. return '';
  4450. }
  4451. }
  4452. /**
  4453. * Returns the maximum size for uploading files.
  4454. *
  4455. * There are seven possible upload limits:
  4456. * 1. in Apache using LimitRequestBody (no way of checking or changing this)
  4457. * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
  4458. * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
  4459. * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
  4460. * 5. by the Moodle admin in $CFG->maxbytes
  4461. * 6. by the teacher in the current course $course->maxbytes
  4462. * 7. by the teacher for the current module, eg $assignment->maxbytes
  4463. *
  4464. * These last two are passed to this function as arguments (in bytes).
  4465. * Anything defined as 0 is ignored.
  4466. * The smallest of all the non-zero numbers is returned.
  4467. *
  4468. * @todo Finish documenting this function
  4469. *
  4470. * @param int $sizebytes Set maximum size
  4471. * @param int $coursebytes Current course $course->maxbytes (in bytes)
  4472. * @param int $modulebytes Current module ->maxbytes (in bytes)
  4473. * @return int The maximum size for uploading files.
  4474. */
  4475. function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
  4476. if (! $filesize = ini_get('upload_max_filesize')) {
  4477. $filesize = '5M';
  4478. }
  4479. $minimumsize = get_real_size($filesize);
  4480. if ($postsize = ini_get('post_max_size')) {
  4481. $postsize = get_real_size($postsize);
  4482. if ($postsize < $minimumsize) {
  4483. $minimumsize = $postsize;
  4484. }
  4485. }
  4486. if ($sitebytes and $sitebytes < $minimumsize) {
  4487. $minimumsize = $sitebytes;
  4488. }
  4489. if ($coursebytes and $coursebytes < $minimumsize) {
  4490. $minimumsize = $coursebytes;
  4491. }
  4492. if ($modulebytes and $modulebytes < $minimumsize) {
  4493. $minimumsize = $modulebytes;
  4494. }
  4495. return $minimumsize;
  4496. }
  4497. /**
  4498. * Returns an array of possible sizes in local language
  4499. *
  4500. * Related to {@link get_max_upload_file_size()} - this function returns an
  4501. * array of possible sizes in an array, translated to the
  4502. * local language.
  4503. *
  4504. * @todo Finish documenting this function
  4505. *
  4506. * @global object
  4507. * @uses SORT_NUMERIC
  4508. * @param int $sizebytes Set maximum size
  4509. * @param int $coursebytes Current course $course->maxbytes (in bytes)
  4510. * @param int $modulebytes Current module ->maxbytes (in bytes)
  4511. * @return array
  4512. */
  4513. function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
  4514. global $CFG;
  4515. if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
  4516. return array();
  4517. }
  4518. $filesize[intval($maxsize)] = display_size($maxsize);
  4519. $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
  4520. 5242880, 10485760, 20971520, 52428800, 104857600);
  4521. // Allow maxbytes to be selected if it falls outside the above boundaries
  4522. if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
  4523. // note: get_real_size() is used in order to prevent problems with invalid values
  4524. $sizelist[] = get_real_size($CFG->maxbytes);
  4525. }
  4526. foreach ($sizelist as $sizebytes) {
  4527. if ($sizebytes < $maxsize) {
  4528. $filesize[intval($sizebytes)] = display_size($sizebytes);
  4529. }
  4530. }
  4531. krsort($filesize, SORT_NUMERIC);
  4532. return $filesize;
  4533. }
  4534. /**
  4535. * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
  4536. *
  4537. * If excludefiles is defined, then that file/directory is ignored
  4538. * If getdirs is true, then (sub)directories are included in the output
  4539. * If getfiles is true, then files are included in the output
  4540. * (at least one of these must be true!)
  4541. *
  4542. * @todo Finish documenting this function. Add examples of $excludefile usage.
  4543. *
  4544. * @param string $rootdir A given root directory to start from
  4545. * @param string|array $excludefile If defined then the specified file/directory is ignored
  4546. * @param bool $descend If true then subdirectories are recursed as well
  4547. * @param bool $getdirs If true then (sub)directories are included in the output
  4548. * @param bool $getfiles If true then files are included in the output
  4549. * @return array An array with all the filenames in
  4550. * all subdirectories, relative to the given rootdir
  4551. */
  4552. function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
  4553. $dirs = array();
  4554. if (!$getdirs and !$getfiles) { // Nothing to show
  4555. return $dirs;
  4556. }
  4557. if (!is_dir($rootdir)) { // Must be a directory
  4558. return $dirs;
  4559. }
  4560. if (!$dir = opendir($rootdir)) { // Can't open it for some reason
  4561. return $dirs;
  4562. }
  4563. if (!is_array($excludefiles)) {
  4564. $excludefiles = array($excludefiles);
  4565. }
  4566. while (false !== ($file = readdir($dir))) {
  4567. $firstchar = substr($file, 0, 1);
  4568. if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
  4569. continue;
  4570. }
  4571. $fullfile = $rootdir .'/'. $file;
  4572. if (filetype($fullfile) == 'dir') {
  4573. if ($getdirs) {
  4574. $dirs[] = $file;
  4575. }
  4576. if ($descend) {
  4577. $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
  4578. foreach ($subdirs as $subdir) {
  4579. $dirs[] = $file .'/'. $subdir;
  4580. }
  4581. }
  4582. } else if ($getfiles) {
  4583. $dirs[] = $file;
  4584. }
  4585. }
  4586. closedir($dir);
  4587. asort($dirs);
  4588. return $dirs;
  4589. }
  4590. /**
  4591. * Adds up all the files in a directory and works out the size.
  4592. *
  4593. * @todo Finish documenting this function
  4594. *
  4595. * @param string $rootdir The directory to start from
  4596. * @param string $excludefile A file to exclude when summing directory size
  4597. * @return int The summed size of all files and subfiles within the root directory
  4598. */
  4599. function get_directory_size($rootdir, $excludefile='') {
  4600. global $CFG;
  4601. // do it this way if we can, it's much faster
  4602. if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
  4603. $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
  4604. $output = null;
  4605. $return = null;
  4606. exec($command,$output,$return);
  4607. if (is_array($output)) {
  4608. return get_real_size(intval($output[0]).'k'); // we told it to return k.
  4609. }
  4610. }
  4611. if (!is_dir($rootdir)) { // Must be a directory
  4612. return 0;
  4613. }
  4614. if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
  4615. return 0;
  4616. }
  4617. $size = 0;
  4618. while (false !== ($file = readdir($dir))) {
  4619. $firstchar = substr($file, 0, 1);
  4620. if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
  4621. continue;
  4622. }
  4623. $fullfile = $rootdir .'/'. $file;
  4624. if (filetype($fullfile) == 'dir') {
  4625. $size += get_directory_size($fullfile, $excludefile);
  4626. } else {
  4627. $size += filesize($fullfile);
  4628. }
  4629. }
  4630. closedir($dir);
  4631. return $size;
  4632. }
  4633. /**
  4634. * Converts bytes into display form
  4635. *
  4636. * @todo Finish documenting this function. Verify return type.
  4637. *
  4638. * @staticvar string $gb Localized string for size in gigabytes
  4639. * @staticvar string $mb Localized string for size in megabytes
  4640. * @staticvar string $kb Localized string for size in kilobytes
  4641. * @staticvar string $b Localized string for size in bytes
  4642. * @param int $size The size to convert to human readable form
  4643. * @return string
  4644. */
  4645. function display_size($size) {
  4646. static $gb, $mb, $kb, $b;
  4647. if (empty($gb)) {
  4648. $gb = get_string('sizegb');
  4649. $mb = get_string('sizemb');
  4650. $kb = get_string('sizekb');
  4651. $b = get_string('sizeb');
  4652. }
  4653. if ($size >= 1073741824) {
  4654. $size = round($size / 1073741824 * 10) / 10 . $gb;
  4655. } else if ($size >= 1048576) {
  4656. $size = round($size / 1048576 * 10) / 10 . $mb;
  4657. } else if ($size >= 1024) {
  4658. $size = round($size / 1024 * 10) / 10 . $kb;
  4659. } else {
  4660. $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
  4661. }
  4662. return $size;
  4663. }
  4664. /**
  4665. * Cleans a given filename by removing suspicious or troublesome characters
  4666. * @see clean_param()
  4667. *
  4668. * @uses PARAM_FILE
  4669. * @param string $string file name
  4670. * @return string cleaned file name
  4671. */
  4672. function clean_filename($string) {
  4673. return clean_param($string, PARAM_FILE);
  4674. }
  4675. /// STRING TRANSLATION ////////////////////////////////////////
  4676. /**
  4677. * Returns the code for the current language
  4678. *
  4679. * @return string
  4680. */
  4681. function current_language() {
  4682. global $CFG, $USER, $SESSION, $COURSE;
  4683. if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
  4684. $return = $COURSE->lang;
  4685. } else if (!empty($SESSION->lang)) { // Session language can override other settings
  4686. $return = $SESSION->lang;
  4687. } else if (!empty($USER->lang)) {
  4688. $return = $USER->lang;
  4689. } else if (isset($CFG->lang)) {
  4690. $return = $CFG->lang;
  4691. } else {
  4692. $return = 'en';
  4693. }
  4694. $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
  4695. return $return;
  4696. }
  4697. /**
  4698. * Returns parent language of current active language if defined
  4699. *
  4700. * @uses COURSE
  4701. * @uses SESSION
  4702. * @param string $lang null means current language
  4703. * @return string
  4704. */
  4705. function get_parent_language($lang=null) {
  4706. global $COURSE, $SESSION;
  4707. //let's hack around the current language
  4708. if (!empty($lang)) {
  4709. $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
  4710. $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
  4711. $COURSE->lang = '';
  4712. $SESSION->lang = $lang;
  4713. }
  4714. $parentlang = get_string('parentlanguage', 'langconfig');
  4715. if ($parentlang === 'en') {
  4716. $parentlang = '';
  4717. }
  4718. //let's hack around the current language
  4719. if (!empty($lang)) {
  4720. $COURSE->lang = $old_course_lang;
  4721. $SESSION->lang = $old_session_lang;
  4722. }
  4723. return $parentlang;
  4724. }
  4725. /**
  4726. * Returns current string_manager instance.
  4727. *
  4728. * The param $forcereload is needed for CLI installer only where the string_manager instance
  4729. * must be replaced during the install.php script life time.
  4730. *
  4731. * @param bool $forcereload shall the singleton be released and new instance created instead?
  4732. * @return string_manager
  4733. */
  4734. function get_string_manager($forcereload=false) {
  4735. global $CFG;
  4736. static $singleton = null;
  4737. if ($forcereload) {
  4738. $singleton = null;
  4739. }
  4740. if ($singleton === null) {
  4741. if (empty($CFG->early_install_lang)) {
  4742. if (empty($CFG->langcacheroot)) {
  4743. $langcacheroot = $CFG->dataroot . '/cache/lang';
  4744. } else {
  4745. $langcacheroot = $CFG->langcacheroot;
  4746. }
  4747. if (empty($CFG->langlist)) {
  4748. $translist = array();
  4749. } else {
  4750. $translist = explode(',', $CFG->langlist);
  4751. }
  4752. if (empty($CFG->langmenucachefile)) {
  4753. $langmenucache = $CFG->dataroot . '/cache/languages';
  4754. } else {
  4755. $langmenucache = $CFG->langmenucachefile;
  4756. }
  4757. $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
  4758. !empty($CFG->langstringcache), $translist, $langmenucache);
  4759. } else {
  4760. $singleton = new install_string_manager();
  4761. }
  4762. }
  4763. return $singleton;
  4764. }
  4765. /**
  4766. * Interface describing class which is responsible for getting
  4767. * of localised strings from language packs.
  4768. *
  4769. * @package moodlecore
  4770. * @copyright 2010 Petr Skoda (http://skodak.org)
  4771. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  4772. */
  4773. interface string_manager {
  4774. /**
  4775. * Get String returns a requested string
  4776. *
  4777. * @param string $identifier The identifier of the string to search for
  4778. * @param string $component The module the string is associated with
  4779. * @param string|object|array $a An object, string or number that can be used
  4780. * within translation strings
  4781. * @param string $lang moodle translation language, NULL means use current
  4782. * @return string The String !
  4783. */
  4784. public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
  4785. /**
  4786. * Does the string actually exist?
  4787. *
  4788. * get_string() is throwing debug warnings, sometimes we do not want them
  4789. * or we want to display better explanation of the problem.
  4790. *
  4791. * Use with care!
  4792. *
  4793. * @param string $identifier The identifier of the string to search for
  4794. * @param string $component The module the string is associated with
  4795. * @return boot true if exists
  4796. */
  4797. public function string_exists($identifier, $component);
  4798. /**
  4799. * Returns a localised list of all country names, sorted by country keys.
  4800. * @param bool $returnall return all or just enabled
  4801. * @param string $lang moodle translation language, NULL means use current
  4802. * @return array two-letter country code => translated name.
  4803. */
  4804. public function get_list_of_countries($returnall = false, $lang = NULL);
  4805. /**
  4806. * Returns a localised list of languages, sorted by code keys.
  4807. *
  4808. * @param string $lang moodle translation language, NULL means use current
  4809. * @param string $standard language list standard
  4810. * iso6392: three-letter language code (ISO 639-2/T) => translated name.
  4811. * @return array language code => translated name
  4812. */
  4813. public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
  4814. /**
  4815. * Does the translation exist?
  4816. *
  4817. * @param string $lang moodle translation language code
  4818. * @param bool include also disabled translations?
  4819. * @return boot true if exists
  4820. */
  4821. public function translation_exists($lang, $includeall = true);
  4822. /**
  4823. * Returns localised list of installed translations
  4824. * @param bool $returnall return all or just enabled
  4825. * @return array moodle translation code => localised translation name
  4826. */
  4827. public function get_list_of_translations($returnall = false);
  4828. /**
  4829. * Returns localised list of currencies.
  4830. *
  4831. * @param string $lang moodle translation language, NULL means use current
  4832. * @return array currency code => localised currency name
  4833. */
  4834. public function get_list_of_currencies($lang = NULL);
  4835. /**
  4836. * Load all strings for one component
  4837. * @param string $component The module the string is associated with
  4838. * @param string $lang
  4839. * @param bool $disablecache Do not use caches, force fetching the strings from sources
  4840. * @param bool $disablelocal Do not use customized strings in xx_local language packs
  4841. * @return array of all string for given component and lang
  4842. */
  4843. public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
  4844. /**
  4845. * Invalidates all caches, should the implementation use any
  4846. */
  4847. public function reset_caches();
  4848. }
  4849. /**
  4850. * Standard string_manager implementation
  4851. *
  4852. * @package moodlecore
  4853. * @copyright 2010 Petr Skoda (http://skodak.org)
  4854. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  4855. */
  4856. class core_string_manager implements string_manager {
  4857. /** @var string location of all packs except 'en' */
  4858. protected $otherroot;
  4859. /** @var string location of all lang pack local modifications */
  4860. protected $localroot;
  4861. /** @var string location of on-disk cache of merged strings */
  4862. protected $cacheroot;
  4863. /** @var array lang string cache - it will be optimised more later */
  4864. protected $cache = array();
  4865. /** @var int get_string() counter */
  4866. protected $countgetstring = 0;
  4867. /** @var int in-memory cache hits counter */
  4868. protected $countmemcache = 0;
  4869. /** @var int on-disk cache hits counter */
  4870. protected $countdiskcache = 0;
  4871. /** @var bool use disk cache */
  4872. protected $usediskcache;
  4873. /* @var array limit list of translations */
  4874. protected $translist;
  4875. /** @var string location of a file that caches the list of available translations */
  4876. protected $menucache;
  4877. /**
  4878. * Create new instance of string manager
  4879. *
  4880. * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
  4881. * @param string $localroot usually the same as $otherroot
  4882. * @param string $cacheroot usually lang dir in cache folder
  4883. * @param bool $usediskcache use disk cache
  4884. * @param array $translist limit list of visible translations
  4885. * @param string $menucache the location of a file that caches the list of available translations
  4886. */
  4887. public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
  4888. $this->otherroot = $otherroot;
  4889. $this->localroot = $localroot;
  4890. $this->cacheroot = $cacheroot;
  4891. $this->usediskcache = $usediskcache;
  4892. $this->translist = $translist;
  4893. $this->menucache = $menucache;
  4894. }
  4895. /**
  4896. * Returns dependencies of current language, en is not included.
  4897. * @param string $lang
  4898. * @return array all parents, the lang itself is last
  4899. */
  4900. public function get_language_dependencies($lang) {
  4901. if ($lang === 'en') {
  4902. return array();
  4903. }
  4904. if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
  4905. return array();
  4906. }
  4907. $string = array();
  4908. include("$this->otherroot/$lang/langconfig.php");
  4909. if (empty($string['parentlanguage'])) {
  4910. return array($lang);
  4911. } else {
  4912. $parentlang = $string['parentlanguage'];
  4913. unset($string);
  4914. return array_merge($this->get_language_dependencies($parentlang), array($lang));
  4915. }
  4916. }
  4917. /**
  4918. * Load all strings for one component
  4919. * @param string $component The module the string is associated with
  4920. * @param string $lang
  4921. * @param bool $disablecache Do not use caches, force fetching the strings from sources
  4922. * @param bool $disablelocal Do not use customized strings in xx_local language packs
  4923. * @return array of all string for given component and lang
  4924. */
  4925. public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
  4926. global $CFG;
  4927. list($plugintype, $pluginname) = normalize_component($component);
  4928. if ($plugintype == 'core' and is_null($pluginname)) {
  4929. $component = 'core';
  4930. } else {
  4931. $component = $plugintype . '_' . $pluginname;
  4932. }
  4933. if (!$disablecache) {
  4934. // try in-memory cache first
  4935. if (isset($this->cache[$lang][$component])) {
  4936. $this->countmemcache++;
  4937. return $this->cache[$lang][$component];
  4938. }
  4939. // try on-disk cache then
  4940. if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
  4941. $this->countdiskcache++;
  4942. include($this->cacheroot . "/$lang/$component.php");
  4943. return $this->cache[$lang][$component];
  4944. }
  4945. }
  4946. // no cache found - let us merge all possible sources of the strings
  4947. if ($plugintype === 'core') {
  4948. $file = $pluginname;
  4949. if ($file === null) {
  4950. $file = 'moodle';
  4951. }
  4952. $string = array();
  4953. // first load english pack
  4954. if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
  4955. return array();
  4956. }
  4957. include("$CFG->dirroot/lang/en/$file.php");
  4958. $originalkeys = array_keys($string);
  4959. $originalkeys = array_flip($originalkeys);
  4960. // and then corresponding local if present and allowed
  4961. if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
  4962. include("$this->localroot/en_local/$file.php");
  4963. }
  4964. // now loop through all langs in correct order
  4965. $deps = $this->get_language_dependencies($lang);
  4966. foreach ($deps as $dep) {
  4967. // the main lang string location
  4968. if (file_exists("$this->otherroot/$dep/$file.php")) {
  4969. include("$this->otherroot/$dep/$file.php");
  4970. }
  4971. if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
  4972. include("$this->localroot/{$dep}_local/$file.php");
  4973. }
  4974. }
  4975. } else {
  4976. if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
  4977. return array();
  4978. }
  4979. if ($plugintype === 'mod') {
  4980. // bloody mod hack
  4981. $file = $pluginname;
  4982. } else {
  4983. $file = $plugintype . '_' . $pluginname;
  4984. }
  4985. $string = array();
  4986. // first load English pack
  4987. if (!file_exists("$location/lang/en/$file.php")) {
  4988. //English pack does not exist, so do not try to load anything else
  4989. return array();
  4990. }
  4991. include("$location/lang/en/$file.php");
  4992. $originalkeys = array_keys($string);
  4993. $originalkeys = array_flip($originalkeys);
  4994. // and then corresponding local english if present
  4995. if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
  4996. include("$this->localroot/en_local/$file.php");
  4997. }
  4998. // now loop through all langs in correct order
  4999. $deps = $this->get_language_dependencies($lang);
  5000. foreach ($deps as $dep) {
  5001. // legacy location - used by contrib only
  5002. if (file_exists("$location/lang/$dep/$file.php")) {
  5003. include("$location/lang/$dep/$file.php");
  5004. }
  5005. // the main lang string location
  5006. if (file_exists("$this->otherroot/$dep/$file.php")) {
  5007. include("$this->otherroot/$dep/$file.php");
  5008. }
  5009. // local customisations
  5010. if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
  5011. include("$this->localroot/{$dep}_local/$file.php");
  5012. }
  5013. }
  5014. }
  5015. // we do not want any extra strings from other languages - everything must be in en lang pack
  5016. $string = array_intersect_key($string, $originalkeys);
  5017. // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
  5018. // caches so we do not need to do all this merging and dependencies resolving again
  5019. $this->cache[$lang][$component] = $string;
  5020. if ($this->usediskcache) {
  5021. check_dir_exists("$this->cacheroot/$lang");
  5022. file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
  5023. }
  5024. return $string;
  5025. }
  5026. /**
  5027. * Does the string actually exist?
  5028. *
  5029. * get_string() is throwing debug warnings, sometimes we do not want them
  5030. * or we want to display better explanation of the problem.
  5031. *
  5032. * Use with care!
  5033. *
  5034. * @param string $identifier The identifier of the string to search for
  5035. * @param string $component The module the string is associated with
  5036. * @return boot true if exists
  5037. */
  5038. public function string_exists($identifier, $component) {
  5039. $identifier = clean_param($identifier, PARAM_STRINGID);
  5040. if (empty($identifier)) {
  5041. return false;
  5042. }
  5043. $lang = current_language();
  5044. $string = $this->load_component_strings($component, $lang);
  5045. return isset($string[$identifier]);
  5046. }
  5047. /**
  5048. * Get String returns a requested string
  5049. *
  5050. * @param string $identifier The identifier of the string to search for
  5051. * @param string $component The module the string is associated with
  5052. * @param string|object|array $a An object, string or number that can be used
  5053. * within translation strings
  5054. * @param string $lang moodle translation language, NULL means use current
  5055. * @return string The String !
  5056. */
  5057. public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
  5058. $this->countgetstring++;
  5059. // there are very many uses of these time formating strings without the 'langconfig' component,
  5060. // it would not be reasonable to expect that all of them would be converted during 2.0 migration
  5061. static $langconfigstrs = array(
  5062. 'strftimedate' => 1,
  5063. 'strftimedatefullshort' => 1,
  5064. 'strftimedateshort' => 1,
  5065. 'strftimedatetime' => 1,
  5066. 'strftimedatetimeshort' => 1,
  5067. 'strftimedaydate' => 1,
  5068. 'strftimedaydatetime' => 1,
  5069. 'strftimedayshort' => 1,
  5070. 'strftimedaytime' => 1,
  5071. 'strftimemonthyear' => 1,
  5072. 'strftimerecent' => 1,
  5073. 'strftimerecentfull' => 1,
  5074. 'strftimetime' => 1);
  5075. if (empty($component)) {
  5076. if (isset($langconfigstrs[$identifier])) {
  5077. $component = 'langconfig';
  5078. } else {
  5079. $component = 'moodle';
  5080. }
  5081. }
  5082. if ($lang === NULL) {
  5083. $lang = current_language();
  5084. }
  5085. $string = $this->load_component_strings($component, $lang);
  5086. if (!isset($string[$identifier])) {
  5087. if ($component === 'pix' or $component === 'core_pix') {
  5088. // this component contains only alt tags for emoticons,
  5089. // not all of them are supposed to be defined
  5090. return '';
  5091. }
  5092. if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
  5093. // parentlanguage is a special string, undefined means use English if not defined
  5094. return 'en';
  5095. }
  5096. if ($this->usediskcache) {
  5097. // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources
  5098. $string = $this->load_component_strings($component, $lang, true);
  5099. }
  5100. if (!isset($string[$identifier])) {
  5101. // the string is still missing - should be fixed by developer
  5102. debugging("Invalid get_string() identifier: '$identifier' or component '$component'", DEBUG_DEVELOPER);
  5103. return "[[$identifier]]";
  5104. }
  5105. }
  5106. $string = $string[$identifier];
  5107. if ($a !== NULL) {
  5108. if (is_object($a) or is_array($a)) {
  5109. $a = (array)$a;
  5110. $search = array();
  5111. $replace = array();
  5112. foreach ($a as $key=>$value) {
  5113. if (is_int($key)) {
  5114. // we do not support numeric keys - sorry!
  5115. continue;
  5116. }
  5117. if (is_object($value) or is_array($value)) {
  5118. // we support just string as value
  5119. continue;
  5120. }
  5121. $search[] = '{$a->'.$key.'}';
  5122. $replace[] = (string)$value;
  5123. }
  5124. if ($search) {
  5125. $string = str_replace($search, $replace, $string);
  5126. }
  5127. } else {
  5128. $string = str_replace('{$a}', (string)$a, $string);
  5129. }
  5130. }
  5131. return $string;
  5132. }
  5133. /**
  5134. * Returns information about the string_manager performance
  5135. * @return array
  5136. */
  5137. public function get_performance_summary() {
  5138. return array(array(
  5139. 'langcountgetstring' => $this->countgetstring,
  5140. 'langcountmemcache' => $this->countmemcache,
  5141. 'langcountdiskcache' => $this->countdiskcache,
  5142. ), array(
  5143. 'langcountgetstring' => 'get_string calls',
  5144. 'langcountmemcache' => 'strings mem cache hits',
  5145. 'langcountdiskcache' => 'strings disk cache hits',
  5146. ));
  5147. }
  5148. /**
  5149. * Returns a localised list of all country names, sorted by localised name.
  5150. *
  5151. * @param bool $returnall return all or just enabled
  5152. * @param string $lang moodle translation language, NULL means use current
  5153. * @return array two-letter country code => translated name.
  5154. */
  5155. public function get_list_of_countries($returnall = false, $lang = NULL) {
  5156. global $CFG;
  5157. if ($lang === NULL) {
  5158. $lang = current_language();
  5159. }
  5160. $countries = $this->load_component_strings('core_countries', $lang);
  5161. textlib_get_instance()->asort($countries);
  5162. if (!$returnall and !empty($CFG->allcountrycodes)) {
  5163. $enabled = explode(',', $CFG->allcountrycodes);
  5164. $return = array();
  5165. foreach ($enabled as $c) {
  5166. if (isset($countries[$c])) {
  5167. $return[$c] = $countries[$c];
  5168. }
  5169. }
  5170. return $return;
  5171. }
  5172. return $countries;
  5173. }
  5174. /**
  5175. * Returns a localised list of languages, sorted by code keys.
  5176. *
  5177. * @param string $lang moodle translation language, NULL means use current
  5178. * @param string $standard language list standard
  5179. * - iso6392: three-letter language code (ISO 639-2/T) => translated name
  5180. * - iso6391: two-letter langauge code (ISO 639-1) => translated name
  5181. * @return array language code => translated name
  5182. */
  5183. public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
  5184. if ($lang === NULL) {
  5185. $lang = current_language();
  5186. }
  5187. if ($standard === 'iso6392') {
  5188. $langs = $this->load_component_strings('core_iso6392', $lang);
  5189. ksort($langs);
  5190. return $langs;
  5191. } else if ($standard === 'iso6391') {
  5192. $langs2 = $this->load_component_strings('core_iso6392', $lang);
  5193. static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
  5194. 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
  5195. 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
  5196. 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
  5197. 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
  5198. 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
  5199. 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
  5200. 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
  5201. 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
  5202. 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
  5203. 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
  5204. 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
  5205. 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
  5206. 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
  5207. 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
  5208. 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
  5209. 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
  5210. $langs1 = array();
  5211. foreach ($mapping as $c2=>$c1) {
  5212. $langs1[$c1] = $langs2[$c2];
  5213. }
  5214. ksort($langs1);
  5215. return $langs1;
  5216. } else {
  5217. debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
  5218. }
  5219. return array();
  5220. }
  5221. /**
  5222. * Does the translation exist?
  5223. *
  5224. * @param string $lang moodle translation language code
  5225. * @param bool include also disabled translations?
  5226. * @return boot true if exists
  5227. */
  5228. public function translation_exists($lang, $includeall = true) {
  5229. if (strpos($lang, '_local') !== false) {
  5230. // _local packs are not real translations
  5231. return false;
  5232. }
  5233. if (!$includeall and !empty($this->translist)) {
  5234. if (!in_array($lang, $this->translist)) {
  5235. return false;
  5236. }
  5237. }
  5238. if ($lang === 'en') {
  5239. // part of distribution
  5240. return true;
  5241. }
  5242. return file_exists("$this->otherroot/$lang/langconfig.php");
  5243. }
  5244. /**
  5245. * Returns localised list of installed translations
  5246. * @param bool $returnall return all or just enabled
  5247. * @return array moodle translation code => localised translation name
  5248. */
  5249. public function get_list_of_translations($returnall = false) {
  5250. global $CFG;
  5251. $languages = array();
  5252. if (!empty($CFG->langcache) and is_readable($this->menucache)) {
  5253. // try to re-use the cached list of all available languages
  5254. $cachedlist = json_decode(file_get_contents($this->menucache), true);
  5255. if (is_array($cachedlist) and !empty($cachedlist)) {
  5256. // the cache file is restored correctly
  5257. if (!$returnall and !empty($this->translist)) {
  5258. // return just enabled translations
  5259. foreach ($cachedlist as $langcode => $langname) {
  5260. if (in_array($langcode, $this->translist)) {
  5261. $languages[$langcode] = $langname;
  5262. }
  5263. }
  5264. return $languages;
  5265. } else {
  5266. // return all translations
  5267. return $cachedlist;
  5268. }
  5269. }
  5270. }
  5271. // the cached list of languages is not available, let us populate the list
  5272. if (!$returnall and !empty($this->translist)) {
  5273. // return only some translations
  5274. foreach ($this->translist as $lang) {
  5275. $lang = trim($lang); //Just trim spaces to be a bit more permissive
  5276. if (strstr($lang, '_local') !== false) {
  5277. continue;
  5278. }
  5279. if (strstr($lang, '_utf8') !== false) {
  5280. continue;
  5281. }
  5282. if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
  5283. // some broken or missing lang - can not switch to it anyway
  5284. continue;
  5285. }
  5286. $string = $this->load_component_strings('langconfig', $lang);
  5287. if (!empty($string['thislanguage'])) {
  5288. $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
  5289. }
  5290. unset($string);
  5291. }
  5292. } else {
  5293. // return all languages available in system
  5294. $langdirs = get_list_of_plugins('', '', $this->otherroot);
  5295. $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
  5296. // Sort all
  5297. // Loop through all langs and get info
  5298. foreach ($langdirs as $lang) {
  5299. if (strstr($lang, '_local') !== false) {
  5300. continue;
  5301. }
  5302. if (strstr($lang, '_utf8') !== false) {
  5303. continue;
  5304. }
  5305. $string = $this->load_component_strings('langconfig', $lang);
  5306. if (!empty($string['thislanguage'])) {
  5307. $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
  5308. }
  5309. unset($string);
  5310. }
  5311. if (!empty($CFG->langcache) and !empty($this->menucache)) {
  5312. // cache the list so that it can be used next time
  5313. textlib_get_instance()->asort($languages);
  5314. file_put_contents($this->menucache, json_encode($languages));
  5315. }
  5316. }
  5317. textlib_get_instance()->asort($languages);
  5318. return $languages;
  5319. }
  5320. /**
  5321. * Returns localised list of currencies.
  5322. *
  5323. * @param string $lang moodle translation language, NULL means use current
  5324. * @return array currency code => localised currency name
  5325. */
  5326. public function get_list_of_currencies($lang = NULL) {
  5327. if ($lang === NULL) {
  5328. $lang = current_language();
  5329. }
  5330. $currencies = $this->load_component_strings('core_currencies', $lang);
  5331. asort($currencies);
  5332. return $currencies;
  5333. }
  5334. /**
  5335. * Clears both in-memory and on-disk caches
  5336. */
  5337. public function reset_caches() {
  5338. global $CFG;
  5339. require_once("$CFG->libdir/filelib.php");
  5340. // clear the on-disk disk with aggregated string files
  5341. fulldelete($this->cacheroot);
  5342. // clear the in-memory cache of loaded strings
  5343. $this->cache = array();
  5344. // clear the cache containing the list of available translations
  5345. // and re-populate it again
  5346. fulldelete($this->menucache);
  5347. $this->get_list_of_translations(true);
  5348. }
  5349. }
  5350. /**
  5351. * Minimalistic string fetching implementation
  5352. * that is used in installer before we fetch the wanted
  5353. * language pack from moodle.org lang download site.
  5354. *
  5355. * @package moodlecore
  5356. * @copyright 2010 Petr Skoda (http://skodak.org)
  5357. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  5358. */
  5359. class install_string_manager implements string_manager {
  5360. /** @var string location of pre-install packs for all langs */
  5361. protected $installroot;
  5362. /**
  5363. * Crate new instance of install string manager
  5364. */
  5365. public function __construct() {
  5366. global $CFG;
  5367. $this->installroot = "$CFG->dirroot/install/lang";
  5368. }
  5369. /**
  5370. * Load all strings for one component
  5371. * @param string $component The module the string is associated with
  5372. * @param string $lang
  5373. * @param bool $disablecache Do not use caches, force fetching the strings from sources
  5374. * @param bool $disablelocal Do not use customized strings in xx_local language packs
  5375. * @return array of all string for given component and lang
  5376. */
  5377. public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
  5378. // not needed in installer
  5379. return array();
  5380. }
  5381. /**
  5382. * Does the string actually exist?
  5383. *
  5384. * get_string() is throwing debug warnings, sometimes we do not want them
  5385. * or we want to display better explanation of the problem.
  5386. *
  5387. * Use with care!
  5388. *
  5389. * @param string $identifier The identifier of the string to search for
  5390. * @param string $component The module the string is associated with
  5391. * @return boot true if exists
  5392. */
  5393. public function string_exists($identifier, $component) {
  5394. $identifier = clean_param($identifier, PARAM_STRINGID);
  5395. if (empty($identifier)) {
  5396. return false;
  5397. }
  5398. // simple old style hack ;)
  5399. $str = get_string($identifier, $component);
  5400. return (strpos($str, '[[') === false);
  5401. }
  5402. /**
  5403. * Get String returns a requested string
  5404. *
  5405. * @param string $identifier The identifier of the string to search for
  5406. * @param string $component The module the string is associated with
  5407. * @param string|object|array $a An object, string or number that can be used
  5408. * within translation strings
  5409. * @param string $lang moodle translation language, NULL means use current
  5410. * @return string The String !
  5411. */
  5412. public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
  5413. if (!$component) {
  5414. $component = 'moodle';
  5415. }
  5416. if ($lang === NULL) {
  5417. $lang = current_language();
  5418. }
  5419. //get parent lang
  5420. $parent = '';
  5421. if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
  5422. if (file_exists("$this->installroot/$lang/langconfig.php")) {
  5423. $string = array();
  5424. include("$this->installroot/$lang/langconfig.php");
  5425. if (isset($string['parentlanguage'])) {
  5426. $parent = $string['parentlanguage'];
  5427. }
  5428. unset($string);
  5429. }
  5430. }
  5431. // include en string first
  5432. if (!file_exists("$this->installroot/en/$component.php")) {
  5433. return "[[$identifier]]";
  5434. }
  5435. $string = array();
  5436. include("$this->installroot/en/$component.php");
  5437. // now override en with parent if defined
  5438. if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
  5439. include("$this->installroot/$parent/$component.php");
  5440. }
  5441. // finally override with requested language
  5442. if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
  5443. include("$this->installroot/$lang/$component.php");
  5444. }
  5445. if (!isset($string[$identifier])) {
  5446. return "[[$identifier]]";
  5447. }
  5448. $string = $string[$identifier];
  5449. if ($a !== NULL) {
  5450. if (is_object($a) or is_array($a)) {
  5451. $a = (array)$a;
  5452. $search = array();
  5453. $replace = array();
  5454. foreach ($a as $key=>$value) {
  5455. if (is_int($key)) {
  5456. // we do not support numeric keys - sorry!
  5457. continue;
  5458. }
  5459. $search[] = '{$a->'.$key.'}';
  5460. $replace[] = (string)$value;
  5461. }
  5462. if ($search) {
  5463. $string = str_replace($search, $replace, $string);
  5464. }
  5465. } else {
  5466. $string = str_replace('{$a}', (string)$a, $string);
  5467. }
  5468. }
  5469. return $string;
  5470. }
  5471. /**
  5472. * Returns a localised list of all country names, sorted by country keys.
  5473. *
  5474. * @param bool $returnall return all or just enabled
  5475. * @param string $lang moodle translation language, NULL means use current
  5476. * @return array two-letter country code => translated name.
  5477. */
  5478. public function get_list_of_countries($returnall = false, $lang = NULL) {
  5479. //not used in installer
  5480. return array();
  5481. }
  5482. /**
  5483. * Returns a localised list of languages, sorted by code keys.
  5484. *
  5485. * @param string $lang moodle translation language, NULL means use current
  5486. * @param string $standard language list standard
  5487. * iso6392: three-letter language code (ISO 639-2/T) => translated name.
  5488. * @return array language code => translated name
  5489. */
  5490. public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
  5491. //not used in installer
  5492. return array();
  5493. }
  5494. /**
  5495. * Does the translation exist?
  5496. *
  5497. * @param string $lang moodle translation language code
  5498. * @param bool include also disabled translations?
  5499. * @return boot true if exists
  5500. */
  5501. public function translation_exists($lang, $includeall = true) {
  5502. return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
  5503. }
  5504. /**
  5505. * Returns localised list of installed translations
  5506. * @param bool $returnall return all or just enabled
  5507. * @return array moodle translation code => localised translation name
  5508. */
  5509. public function get_list_of_translations($returnall = false) {
  5510. // return all is ignored here - we need to know all langs in installer
  5511. $languages = array();
  5512. // Get raw list of lang directories
  5513. $langdirs = get_list_of_plugins('install/lang');
  5514. asort($langdirs);
  5515. // Get some info from each lang
  5516. foreach ($langdirs as $lang) {
  5517. if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
  5518. $string = array();
  5519. include($this->installroot.'/'.$lang.'/langconfig.php');
  5520. if (!empty($string['thislanguage'])) {
  5521. $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
  5522. }
  5523. }
  5524. }
  5525. // Return array
  5526. return $languages;
  5527. }
  5528. /**
  5529. * Returns localised list of currencies.
  5530. *
  5531. * @param string $lang moodle translation language, NULL means use current
  5532. * @return array currency code => localised currency name
  5533. */
  5534. public function get_list_of_currencies($lang = NULL) {
  5535. // not used in installer
  5536. return array();
  5537. }
  5538. /**
  5539. * This implementation does not use any caches
  5540. */
  5541. public function reset_caches() {}
  5542. }
  5543. /**
  5544. * Returns a localized string.
  5545. *
  5546. * Returns the translated string specified by $identifier as
  5547. * for $module. Uses the same format files as STphp.
  5548. * $a is an object, string or number that can be used
  5549. * within translation strings
  5550. *
  5551. * eg 'hello {$a->firstname} {$a->lastname}'
  5552. * or 'hello {$a}'
  5553. *
  5554. * If you would like to directly echo the localized string use
  5555. * the function {@link print_string()}
  5556. *
  5557. * Example usage of this function involves finding the string you would
  5558. * like a local equivalent of and using its identifier and module information
  5559. * to retrieve it.<br/>
  5560. * If you open moodle/lang/en/moodle.php and look near line 278
  5561. * you will find a string to prompt a user for their word for 'course'
  5562. * <code>
  5563. * $string['course'] = 'Course';
  5564. * </code>
  5565. * So if you want to display the string 'Course'
  5566. * in any language that supports it on your site
  5567. * you just need to use the identifier 'course'
  5568. * <code>
  5569. * $mystring = '<strong>'. get_string('course') .'</strong>';
  5570. * or
  5571. * </code>
  5572. * If the string you want is in another file you'd take a slightly
  5573. * different approach. Looking in moodle/lang/en/calendar.php you find
  5574. * around line 75:
  5575. * <code>
  5576. * $string['typecourse'] = 'Course event';
  5577. * </code>
  5578. * If you want to display the string "Course event" in any language
  5579. * supported you would use the identifier 'typecourse' and the module 'calendar'
  5580. * (because it is in the file calendar.php):
  5581. * <code>
  5582. * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
  5583. * </code>
  5584. *
  5585. * As a last resort, should the identifier fail to map to a string
  5586. * the returned string will be [[ $identifier ]]
  5587. *
  5588. * @param string $identifier The key identifier for the localized string
  5589. * @param string $component The module where the key identifier is stored,
  5590. * usually expressed as the filename in the language pack without the
  5591. * .php on the end but can also be written as mod/forum or grade/export/xls.
  5592. * If none is specified then moodle.php is used.
  5593. * @param string|object|array $a An object, string or number that can be used
  5594. * within translation strings
  5595. * @return string The localized string.
  5596. */
  5597. function get_string($identifier, $component = '', $a = NULL) {
  5598. $identifier = clean_param($identifier, PARAM_STRINGID);
  5599. if (empty($identifier)) {
  5600. throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please fix your get_string() call and string definition');
  5601. }
  5602. if (func_num_args() > 3) {
  5603. debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
  5604. }
  5605. if (strpos($component, '/') !== false) {
  5606. debugging('The module name you passed to get_string is the deprecated format ' .
  5607. 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
  5608. $componentpath = explode('/', $component);
  5609. switch ($componentpath[0]) {
  5610. case 'mod':
  5611. $component = $componentpath[1];
  5612. break;
  5613. case 'blocks':
  5614. case 'block':
  5615. $component = 'block_'.$componentpath[1];
  5616. break;
  5617. case 'enrol':
  5618. $component = 'enrol_'.$componentpath[1];
  5619. break;
  5620. case 'format':
  5621. $component = 'format_'.$componentpath[1];
  5622. break;
  5623. case 'grade':
  5624. $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
  5625. break;
  5626. }
  5627. }
  5628. return get_string_manager()->get_string($identifier, $component, $a);
  5629. }
  5630. /**
  5631. * Converts an array of strings to their localized value.
  5632. *
  5633. * @param array $array An array of strings
  5634. * @param string $module The language module that these strings can be found in.
  5635. * @return array and array of translated strings.
  5636. */
  5637. function get_strings($array, $component = '') {
  5638. $string = new stdClass;
  5639. foreach ($array as $item) {
  5640. $string->$item = get_string($item, $component);
  5641. }
  5642. return $string;
  5643. }
  5644. /**
  5645. * Prints out a translated string.
  5646. *
  5647. * Prints out a translated string using the return value from the {@link get_string()} function.
  5648. *
  5649. * Example usage of this function when the string is in the moodle.php file:<br/>
  5650. * <code>
  5651. * echo '<strong>';
  5652. * print_string('course');
  5653. * echo '</strong>';
  5654. * </code>
  5655. *
  5656. * Example usage of this function when the string is not in the moodle.php file:<br/>
  5657. * <code>
  5658. * echo '<h1>';
  5659. * print_string('typecourse', 'calendar');
  5660. * echo '</h1>';
  5661. * </code>
  5662. *
  5663. * @param string $identifier The key identifier for the localized string
  5664. * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
  5665. * @param mixed $a An object, string or number that can be used within translation strings
  5666. */
  5667. function print_string($identifier, $component = '', $a = NULL) {
  5668. echo get_string($identifier, $component, $a);
  5669. }
  5670. /**
  5671. * Returns a list of charset codes
  5672. *
  5673. * Returns a list of charset codes. It's hardcoded, so they should be added manually
  5674. * (checking that such charset is supported by the texlib library!)
  5675. *
  5676. * @return array And associative array with contents in the form of charset => charset
  5677. */
  5678. function get_list_of_charsets() {
  5679. $charsets = array(
  5680. 'EUC-JP' => 'EUC-JP',
  5681. 'ISO-2022-JP'=> 'ISO-2022-JP',
  5682. 'ISO-8859-1' => 'ISO-8859-1',
  5683. 'SHIFT-JIS' => 'SHIFT-JIS',
  5684. 'GB2312' => 'GB2312',
  5685. 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
  5686. 'UTF-8' => 'UTF-8');
  5687. asort($charsets);
  5688. return $charsets;
  5689. }
  5690. /**
  5691. * Returns a list of valid and compatible themes
  5692. *
  5693. * @global object
  5694. * @return array
  5695. */
  5696. function get_list_of_themes() {
  5697. global $CFG;
  5698. $themes = array();
  5699. if (!empty($CFG->themelist)) { // use admin's list of themes
  5700. $themelist = explode(',', $CFG->themelist);
  5701. } else {
  5702. $themelist = array_keys(get_plugin_list("theme"));
  5703. }
  5704. foreach ($themelist as $key => $themename) {
  5705. $theme = theme_config::load($themename);
  5706. $themes[$themename] = $theme;
  5707. }
  5708. asort($themes);
  5709. return $themes;
  5710. }
  5711. /**
  5712. * Returns a list of timezones in the current language
  5713. *
  5714. * @global object
  5715. * @global object
  5716. * @return array
  5717. */
  5718. function get_list_of_timezones() {
  5719. global $CFG, $DB;
  5720. static $timezones;
  5721. if (!empty($timezones)) { // This function has been called recently
  5722. return $timezones;
  5723. }
  5724. $timezones = array();
  5725. if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
  5726. foreach($rawtimezones as $timezone) {
  5727. if (!empty($timezone->name)) {
  5728. if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
  5729. $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
  5730. } else {
  5731. $timezones[$timezone->name] = $timezone->name;
  5732. }
  5733. if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
  5734. $timezones[$timezone->name] = $timezone->name;
  5735. }
  5736. }
  5737. }
  5738. }
  5739. asort($timezones);
  5740. for ($i = -13; $i <= 13; $i += .5) {
  5741. $tzstring = 'UTC';
  5742. if ($i < 0) {
  5743. $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
  5744. } else if ($i > 0) {
  5745. $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
  5746. } else {
  5747. $timezones[sprintf("%.1f", $i)] = $tzstring;
  5748. }
  5749. }
  5750. return $timezones;
  5751. }
  5752. /**
  5753. * Factory function for emoticon_manager
  5754. *
  5755. * @return emoticon_manager singleton
  5756. */
  5757. function get_emoticon_manager() {
  5758. static $singleton = null;
  5759. if (is_null($singleton)) {
  5760. $singleton = new emoticon_manager();
  5761. }
  5762. return $singleton;
  5763. }
  5764. /**
  5765. * Provides core support for plugins that have to deal with
  5766. * emoticons (like HTML editor or emoticon filter).
  5767. *
  5768. * Whenever this manager mentiones 'emoticon object', the following data
  5769. * structure is expected: stdClass with properties text, imagename, imagecomponent,
  5770. * altidentifier and altcomponent
  5771. *
  5772. * @see admin_setting_emoticons
  5773. */
  5774. class emoticon_manager {
  5775. /**
  5776. * Returns the currently enabled emoticons
  5777. *
  5778. * @return array of emoticon objects
  5779. */
  5780. public function get_emoticons() {
  5781. global $CFG;
  5782. if (empty($CFG->emoticons)) {
  5783. return array();
  5784. }
  5785. $emoticons = $this->decode_stored_config($CFG->emoticons);
  5786. if (!is_array($emoticons)) {
  5787. // something is wrong with the format of stored setting
  5788. debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
  5789. return array();
  5790. }
  5791. return $emoticons;
  5792. }
  5793. /**
  5794. * Converts emoticon object into renderable pix_emoticon object
  5795. *
  5796. * @param stdClass $emoticon emoticon object
  5797. * @param array $attributes explicit HTML attributes to set
  5798. * @return pix_emoticon
  5799. */
  5800. public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
  5801. $stringmanager = get_string_manager();
  5802. if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
  5803. $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
  5804. } else {
  5805. $alt = s($emoticon->text);
  5806. }
  5807. return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
  5808. }
  5809. /**
  5810. * Encodes the array of emoticon objects into a string storable in config table
  5811. *
  5812. * @see self::decode_stored_config()
  5813. * @param array $emoticons array of emtocion objects
  5814. * @return string
  5815. */
  5816. public function encode_stored_config(array $emoticons) {
  5817. return json_encode($emoticons);
  5818. }
  5819. /**
  5820. * Decodes the string into an array of emoticon objects
  5821. *
  5822. * @see self::encode_stored_config()
  5823. * @param string $encoded
  5824. * @return string|null
  5825. */
  5826. public function decode_stored_config($encoded) {
  5827. $decoded = json_decode($encoded);
  5828. if (!is_array($decoded)) {
  5829. return null;
  5830. }
  5831. return $decoded;
  5832. }
  5833. /**
  5834. * Returns default set of emoticons supported by Moodle
  5835. *
  5836. * @return array of sdtClasses
  5837. */
  5838. public function default_emoticons() {
  5839. return array(
  5840. $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
  5841. $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
  5842. $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
  5843. $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
  5844. $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
  5845. $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
  5846. $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
  5847. $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
  5848. $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
  5849. $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
  5850. $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
  5851. $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
  5852. $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
  5853. $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
  5854. $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
  5855. $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
  5856. $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
  5857. $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
  5858. $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
  5859. $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
  5860. $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
  5861. $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
  5862. $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
  5863. $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
  5864. $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
  5865. $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
  5866. $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
  5867. $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
  5868. $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
  5869. $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
  5870. );
  5871. }
  5872. /**
  5873. * Helper method preparing the stdClass with the emoticon properties
  5874. *
  5875. * @param string|array $text or array of strings
  5876. * @param string $imagename to be used by {@see pix_emoticon}
  5877. * @param string $altidentifier alternative string identifier, null for no alt
  5878. * @param array $altcomponent where the alternative string is defined
  5879. * @param string $imagecomponent to be used by {@see pix_emoticon}
  5880. * @return stdClass
  5881. */
  5882. protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
  5883. return (object)array(
  5884. 'text' => $text,
  5885. 'imagename' => $imagename,
  5886. 'imagecomponent' => $imagecomponent,
  5887. 'altidentifier' => $altidentifier,
  5888. 'altcomponent' => $altcomponent,
  5889. );
  5890. }
  5891. }
  5892. /// ENCRYPTION ////////////////////////////////////////////////
  5893. /**
  5894. * rc4encrypt
  5895. *
  5896. * @todo Finish documenting this function
  5897. *
  5898. * @param string $data Data to encrypt
  5899. * @return string The now encrypted data
  5900. */
  5901. function rc4encrypt($data) {
  5902. $password = 'nfgjeingjk';
  5903. return endecrypt($password, $data, '');
  5904. }
  5905. /**
  5906. * rc4decrypt
  5907. *
  5908. * @todo Finish documenting this function
  5909. *
  5910. * @param string $data Data to decrypt
  5911. * @return string The now decrypted data
  5912. */
  5913. function rc4decrypt($data) {
  5914. $password = 'nfgjeingjk';
  5915. return endecrypt($password, $data, 'de');
  5916. }
  5917. /**
  5918. * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
  5919. *
  5920. * @todo Finish documenting this function
  5921. *
  5922. * @param string $pwd The password to use when encrypting or decrypting
  5923. * @param string $data The data to be decrypted/encrypted
  5924. * @param string $case Either 'de' for decrypt or '' for encrypt
  5925. * @return string
  5926. */
  5927. function endecrypt ($pwd, $data, $case) {
  5928. if ($case == 'de') {
  5929. $data = urldecode($data);
  5930. }
  5931. $key[] = '';
  5932. $box[] = '';
  5933. $temp_swap = '';
  5934. $pwd_length = 0;
  5935. $pwd_length = strlen($pwd);
  5936. for ($i = 0; $i <= 255; $i++) {
  5937. $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
  5938. $box[$i] = $i;
  5939. }
  5940. $x = 0;
  5941. for ($i = 0; $i <= 255; $i++) {
  5942. $x = ($x + $box[$i] + $key[$i]) % 256;
  5943. $temp_swap = $box[$i];
  5944. $box[$i] = $box[$x];
  5945. $box[$x] = $temp_swap;
  5946. }
  5947. $temp = '';
  5948. $k = '';
  5949. $cipherby = '';
  5950. $cipher = '';
  5951. $a = 0;
  5952. $j = 0;
  5953. for ($i = 0; $i < strlen($data); $i++) {
  5954. $a = ($a + 1) % 256;
  5955. $j = ($j + $box[$a]) % 256;
  5956. $temp = $box[$a];
  5957. $box[$a] = $box[$j];
  5958. $box[$j] = $temp;
  5959. $k = $box[(($box[$a] + $box[$j]) % 256)];
  5960. $cipherby = ord(substr($data, $i, 1)) ^ $k;
  5961. $cipher .= chr($cipherby);
  5962. }
  5963. if ($case == 'de') {
  5964. $cipher = urldecode(urlencode($cipher));
  5965. } else {
  5966. $cipher = urlencode($cipher);
  5967. }
  5968. return $cipher;
  5969. }
  5970. /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
  5971. /**
  5972. * Returns the exact absolute path to plugin directory.
  5973. *
  5974. * @param string $plugintype type of plugin
  5975. * @param string $name name of the plugin
  5976. * @return string full path to plugin directory; NULL if not found
  5977. */
  5978. function get_plugin_directory($plugintype, $name) {
  5979. global $CFG;
  5980. if ($plugintype === '') {
  5981. $plugintype = 'mod';
  5982. }
  5983. $types = get_plugin_types(true);
  5984. if (!array_key_exists($plugintype, $types)) {
  5985. return NULL;
  5986. }
  5987. $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
  5988. if (!empty($CFG->themedir) and $plugintype === 'theme') {
  5989. if (!is_dir($types['theme'] . '/' . $name)) {
  5990. // ok, so the theme is supposed to be in the $CFG->themedir
  5991. return $CFG->themedir . '/' . $name;
  5992. }
  5993. }
  5994. return $types[$plugintype].'/'.$name;
  5995. }
  5996. /**
  5997. * Return exact absolute path to a plugin directory,
  5998. * this method support "simpletest_" prefix designed for unit testing.
  5999. *
  6000. * @param string $component name such as 'moodle', 'mod_forum' or special simpletest value
  6001. * @return string full path to component directory; NULL if not found
  6002. */
  6003. function get_component_directory($component) {
  6004. global $CFG;
  6005. /*
  6006. $simpletest = false;
  6007. if (strpos($component, 'simpletest_') === 0) {
  6008. $subdir = substr($component, strlen('simpletest_'));
  6009. //TODO: this looks borked, where is it used actually?
  6010. return $subdir;
  6011. }
  6012. */
  6013. list($type, $plugin) = normalize_component($component);
  6014. if ($type === 'core') {
  6015. if ($plugin === NULL ) {
  6016. $path = $CFG->libdir;
  6017. } else {
  6018. $subsystems = get_core_subsystems();
  6019. if (isset($subsystems[$plugin])) {
  6020. $path = $CFG->dirroot.'/'.$subsystems[$plugin];
  6021. } else {
  6022. $path = NULL;
  6023. }
  6024. }
  6025. } else {
  6026. $path = get_plugin_directory($type, $plugin);
  6027. }
  6028. return $path;
  6029. }
  6030. /**
  6031. * Normalize the component name using the "frankenstyle" names.
  6032. * @param string $component
  6033. * @return array $type+$plugin elements
  6034. */
  6035. function normalize_component($component) {
  6036. if ($component === 'moodle' or $component === 'core') {
  6037. $type = 'core';
  6038. $plugin = NULL;
  6039. } else if (strpos($component, '_') === false) {
  6040. $subsystems = get_core_subsystems();
  6041. if (array_key_exists($component, $subsystems)) {
  6042. $type = 'core';
  6043. $plugin = $component;
  6044. } else {
  6045. // everything else is a module
  6046. $type = 'mod';
  6047. $plugin = $component;
  6048. }
  6049. } else {
  6050. list($type, $plugin) = explode('_', $component, 2);
  6051. $plugintypes = get_plugin_types(false);
  6052. if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
  6053. $type = 'mod';
  6054. $plugin = $component;
  6055. }
  6056. }
  6057. return array($type, $plugin);
  6058. }
  6059. /**
  6060. * List all core subsystems and their location
  6061. *
  6062. * This is a whitelist of components that are part of the core and their
  6063. * language strings are defined in /lang/en/<<subsystem>>.php. If a given
  6064. * plugin is not listed here and it does not have proper plugintype prefix,
  6065. * then it is considered as course activity module.
  6066. *
  6067. * The location is dirroot relative path. NULL means there is no special
  6068. * directory for this subsystem. If the location is set, the subsystem's
  6069. * renderer.php is expected to be there.
  6070. *
  6071. * @return array of (string)name => (string|null)location
  6072. */
  6073. function get_core_subsystems() {
  6074. global $CFG;
  6075. static $info = null;
  6076. if (!$info) {
  6077. $info = array(
  6078. 'access' => NULL,
  6079. 'admin' => $CFG->admin,
  6080. 'auth' => 'auth',
  6081. 'backup' => 'backup/util/ui',
  6082. 'block' => 'blocks',
  6083. 'blog' => 'blog',
  6084. 'bulkusers' => NULL,
  6085. 'calendar' => 'calendar',
  6086. 'cohort' => 'cohort',
  6087. 'condition' => NULL,
  6088. 'completion' => NULL,
  6089. 'countries' => NULL,
  6090. 'course' => 'course',
  6091. 'currencies' => NULL,
  6092. 'dbtransfer' => NULL,
  6093. 'debug' => NULL,
  6094. 'dock' => NULL,
  6095. 'editor' => 'lib/editor',
  6096. 'edufields' => NULL,
  6097. 'enrol' => 'enrol',
  6098. 'error' => NULL,
  6099. 'filepicker' => NULL,
  6100. 'files' => 'files',
  6101. 'filters' => NULL,
  6102. 'flashdetect' => NULL,
  6103. 'fonts' => NULL,
  6104. 'form' => 'lib/form',
  6105. 'grades' => 'grade',
  6106. 'group' => 'group',
  6107. 'help' => NULL,
  6108. 'hub' => NULL,
  6109. 'imscc' => NULL,
  6110. 'install' => NULL,
  6111. 'iso6392' => NULL,
  6112. 'langconfig' => NULL,
  6113. 'license' => NULL,
  6114. 'message' => 'message',
  6115. 'mimetypes' => NULL,
  6116. 'mnet' => 'mnet',
  6117. 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
  6118. 'my' => 'my',
  6119. 'notes' => 'notes',
  6120. 'pagetype' => NULL,
  6121. 'pix' => NULL,
  6122. 'plagiarism' => 'plagiarism',
  6123. 'portfolio' => 'portfolio',
  6124. 'publish' => 'course/publish',
  6125. 'question' => 'question',
  6126. 'rating' => 'rating',
  6127. 'register' => 'admin/registration',
  6128. 'repository' => 'repository',
  6129. 'rss' => 'rss',
  6130. 'role' => $CFG->admin.'/role',
  6131. 'simpletest' => NULL,
  6132. 'search' => 'search',
  6133. 'table' => NULL,
  6134. 'tag' => 'tag',
  6135. 'timezones' => NULL,
  6136. 'user' => 'user',
  6137. 'userkey' => NULL,
  6138. 'webservice' => 'webservice',
  6139. 'xmldb' => NULL,
  6140. );
  6141. }
  6142. return $info;
  6143. }
  6144. /**
  6145. * Lists all plugin types
  6146. * @param bool $fullpaths false means relative paths from dirroot
  6147. * @return array Array of strings - name=>location
  6148. */
  6149. function get_plugin_types($fullpaths=true) {
  6150. global $CFG;
  6151. static $info = null;
  6152. static $fullinfo = null;
  6153. if (!$info) {
  6154. $info = array('mod' => 'mod',
  6155. 'auth' => 'auth',
  6156. 'enrol' => 'enrol',
  6157. 'message' => 'message/output',
  6158. 'block' => 'blocks',
  6159. 'filter' => 'filter',
  6160. 'editor' => 'lib/editor',
  6161. 'format' => 'course/format',
  6162. 'profilefield' => 'user/profile/field',
  6163. 'report' => $CFG->admin.'/report',
  6164. 'coursereport' => 'course/report', // must be after system reports
  6165. 'gradeexport' => 'grade/export',
  6166. 'gradeimport' => 'grade/import',
  6167. 'gradereport' => 'grade/report',
  6168. 'mnetservice' => 'mnet/service',
  6169. 'webservice' => 'webservice',
  6170. 'repository' => 'repository',
  6171. 'portfolio' => 'portfolio',
  6172. 'qtype' => 'question/type',
  6173. 'qformat' => 'question/format',
  6174. 'plagiarism' => 'plagiarism',
  6175. 'theme' => 'theme'); // this is a bit hacky, themes may be in $CFG->themedir too
  6176. $mods = get_plugin_list('mod');
  6177. foreach ($mods as $mod => $moddir) {
  6178. if (file_exists("$moddir/db/subplugins.php")) {
  6179. $subplugins = array();
  6180. include("$moddir/db/subplugins.php");
  6181. foreach ($subplugins as $subtype=>$dir) {
  6182. $info[$subtype] = $dir;
  6183. }
  6184. }
  6185. }
  6186. // local is always last!
  6187. $info['local'] = 'local';
  6188. $fullinfo = array();
  6189. foreach ($info as $type => $dir) {
  6190. $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
  6191. }
  6192. }
  6193. return ($fullpaths ? $fullinfo : $info);
  6194. }
  6195. /**
  6196. * Simplified version of get_list_of_plugins()
  6197. * @param string $plugintype type of plugin
  6198. * @return array name=>fulllocation pairs of plugins of given type
  6199. */
  6200. function get_plugin_list($plugintype) {
  6201. global $CFG;
  6202. $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
  6203. if ($plugintype == 'auth') {
  6204. // Historically we have had an auth plugin called 'db', so allow a special case.
  6205. $key = array_search('db', $ignored);
  6206. if ($key !== false) {
  6207. unset($ignored[$key]);
  6208. }
  6209. }
  6210. if ($plugintype === '') {
  6211. $plugintype = 'mod';
  6212. }
  6213. $fulldirs = array();
  6214. if ($plugintype === 'mod') {
  6215. // mod is an exception because we have to call this function from get_plugin_types()
  6216. $fulldirs[] = $CFG->dirroot.'/mod';
  6217. } else if ($plugintype === 'theme') {
  6218. $fulldirs[] = $CFG->dirroot.'/theme';
  6219. // themes are special because they may be stored also in separate directory
  6220. if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
  6221. $fulldirs[] = $CFG->themedir;
  6222. }
  6223. } else {
  6224. $types = get_plugin_types(true);
  6225. if (!array_key_exists($plugintype, $types)) {
  6226. return array();
  6227. }
  6228. $fulldir = $types[$plugintype];
  6229. if (!file_exists($fulldir)) {
  6230. return array();
  6231. }
  6232. $fulldirs[] = $fulldir;
  6233. }
  6234. $result = array();
  6235. foreach ($fulldirs as $fulldir) {
  6236. if (!is_dir($fulldir)) {
  6237. continue;
  6238. }
  6239. $items = new DirectoryIterator($fulldir);
  6240. foreach ($items as $item) {
  6241. if ($item->isDot() or !$item->isDir()) {
  6242. continue;
  6243. }
  6244. $pluginname = $item->getFilename();
  6245. if (in_array($pluginname, $ignored)) {
  6246. continue;
  6247. }
  6248. if ($pluginname !== clean_param($pluginname, PARAM_SAFEDIR)) {
  6249. // better ignore plugins with problematic names here
  6250. continue;
  6251. }
  6252. $result[$pluginname] = $fulldir.'/'.$pluginname;
  6253. unset($item);
  6254. }
  6255. unset($items);
  6256. }
  6257. //TODO: implement better sorting once we migrated all plugin names to 'pluginname', ksort does not work for unicode, that is why we have to sort by the dir name, not the strings!
  6258. ksort($result);
  6259. return $result;
  6260. }
  6261. /**
  6262. * Gets a list of all plugin API functions for given plugin type, function
  6263. * name, and filename.
  6264. * @param string $plugintype Plugin type, e.g. 'mod' or 'report'
  6265. * @param string $function Name of function after the frankenstyle prefix;
  6266. * e.g. if the function is called report_courselist_hook then this value
  6267. * would be 'hook'
  6268. * @param string $file Name of file that includes function within plugin,
  6269. * default 'lib.php'
  6270. * @return Array of plugin frankenstyle (e.g. 'report_courselist', 'mod_forum')
  6271. * to valid, existing plugin function name (e.g. 'report_courselist_hook',
  6272. * 'forum_hook')
  6273. */
  6274. function get_plugin_list_with_function($plugintype, $function, $file='lib.php') {
  6275. global $CFG; // mandatory in case it is referenced by include()d PHP script
  6276. $result = array();
  6277. // Loop through list of plugins with given type
  6278. $list = get_plugin_list($plugintype);
  6279. foreach($list as $plugin => $dir) {
  6280. $path = $dir . '/' . $file;
  6281. // If file exists, require it and look for function
  6282. if (file_exists($path)) {
  6283. include_once($path);
  6284. $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
  6285. if (function_exists($fullfunction)) {
  6286. // Function exists with standard name. Store, indexed by
  6287. // frankenstyle name of plugin
  6288. $result[$plugintype . '_' . $plugin] = $fullfunction;
  6289. } else if ($plugintype === 'mod') {
  6290. // For modules, we also allow plugin without full frankenstyle
  6291. // but just starting with the module name
  6292. $shortfunction = $plugin . '_' . $function;
  6293. if (function_exists($shortfunction)) {
  6294. $result[$plugintype . '_' . $plugin] = $shortfunction;
  6295. }
  6296. }
  6297. }
  6298. }
  6299. return $result;
  6300. }
  6301. /**
  6302. * Lists plugin-like directories within specified directory
  6303. *
  6304. * This function was originally used for standard Moodle plugins, please use
  6305. * new get_plugin_list() now.
  6306. *
  6307. * This function is used for general directory listing and backwards compatility.
  6308. *
  6309. * @param string $directory relative directory from root
  6310. * @param string $exclude dir name to exclude from the list (defaults to none)
  6311. * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
  6312. * @return array Sorted array of directory names found under the requested parameters
  6313. */
  6314. function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
  6315. global $CFG;
  6316. $plugins = array();
  6317. if (empty($basedir)) {
  6318. $basedir = $CFG->dirroot .'/'. $directory;
  6319. } else {
  6320. $basedir = $basedir .'/'. $directory;
  6321. }
  6322. if (file_exists($basedir) && filetype($basedir) == 'dir') {
  6323. $dirhandle = opendir($basedir);
  6324. while (false !== ($dir = readdir($dirhandle))) {
  6325. $firstchar = substr($dir, 0, 1);
  6326. if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
  6327. continue;
  6328. }
  6329. if (filetype($basedir .'/'. $dir) != 'dir') {
  6330. continue;
  6331. }
  6332. $plugins[] = $dir;
  6333. }
  6334. closedir($dirhandle);
  6335. }
  6336. if ($plugins) {
  6337. asort($plugins);
  6338. }
  6339. return $plugins;
  6340. }
  6341. /**
  6342. * invoke plugin's callback functions
  6343. *
  6344. * @param string $type Plugin type e.g. 'mod'
  6345. * @param string $name Plugin name
  6346. * @param string $feature Feature code (FEATURE_xx constant)
  6347. * @param string $action Feature's action
  6348. * @param string $options parameters of callback function, should be an array
  6349. * @param mixed $default default value if callback function hasn't been defined
  6350. * @return mixed
  6351. */
  6352. function plugin_callback($type, $name, $feature, $action, $options = null, $default=null) {
  6353. global $CFG;
  6354. $name = clean_param($name, PARAM_SAFEDIR);
  6355. $function = $name.'_'.$feature.'_'.$action;
  6356. $file = get_component_directory($type . '_' . $name) . '/lib.php';
  6357. // Load library and look for function
  6358. if (file_exists($file)) {
  6359. require_once($file);
  6360. }
  6361. if (function_exists($function)) {
  6362. // Function exists, so just return function result
  6363. $ret = call_user_func_array($function, (array)$options);
  6364. if (is_null($ret)) {
  6365. return $default;
  6366. } else {
  6367. return $ret;
  6368. }
  6369. }
  6370. return $default;
  6371. }
  6372. /**
  6373. * Checks whether a plugin supports a specified feature.
  6374. *
  6375. * @param string $type Plugin type e.g. 'mod'
  6376. * @param string $name Plugin name e.g. 'forum'
  6377. * @param string $feature Feature code (FEATURE_xx constant)
  6378. * @param mixed $default default value if feature support unknown
  6379. * @return mixed Feature result (false if not supported, null if feature is unknown,
  6380. * otherwise usually true but may have other feature-specific value such as array)
  6381. */
  6382. function plugin_supports($type, $name, $feature, $default = NULL) {
  6383. global $CFG;
  6384. $name = clean_param($name, PARAM_SAFEDIR); //bit of extra security
  6385. $function = null;
  6386. if ($type === 'mod') {
  6387. // we need this special case because we support subplugins in modules,
  6388. // otherwise it would end up in infinite loop
  6389. if ($name === 'NEWMODULE') {
  6390. //somebody forgot to rename the module template
  6391. return false;
  6392. }
  6393. if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
  6394. include_once("$CFG->dirroot/mod/$name/lib.php");
  6395. $function = $type.'_'.$name.'_supports';
  6396. if (!function_exists($function)) {
  6397. // legacy non-frankenstyle function name
  6398. $function = $name.'_supports';
  6399. }
  6400. }
  6401. } else {
  6402. if (!$path = get_plugin_directory($type, $name)) {
  6403. // non existent plugin type
  6404. return false;
  6405. }
  6406. if (file_exists("$path/lib.php")) {
  6407. include_once("$path/lib.php");
  6408. $function = $type.'_'.$name.'_supports';
  6409. }
  6410. }
  6411. if ($function and function_exists($function)) {
  6412. $supports = $function($feature);
  6413. if (is_null($supports)) {
  6414. // plugin does not know - use default
  6415. return $default;
  6416. } else {
  6417. return $supports;
  6418. }
  6419. }
  6420. //plugin does not care, so use default
  6421. return $default;
  6422. }
  6423. /**
  6424. * Returns true if the current version of PHP is greater that the specified one.
  6425. *
  6426. * @todo Check PHP version being required here is it too low?
  6427. *
  6428. * @param string $version The version of php being tested.
  6429. * @return bool
  6430. */
  6431. function check_php_version($version='5.2.4') {
  6432. return (version_compare(phpversion(), $version) >= 0);
  6433. }
  6434. /**
  6435. * Checks to see if is the browser operating system matches the specified
  6436. * brand.
  6437. *
  6438. * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
  6439. *
  6440. * @uses $_SERVER
  6441. * @param string $brand The operating system identifier being tested
  6442. * @return bool true if the given brand below to the detected operating system
  6443. */
  6444. function check_browser_operating_system($brand) {
  6445. if (empty($_SERVER['HTTP_USER_AGENT'])) {
  6446. return false;
  6447. }
  6448. if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
  6449. return true;
  6450. }
  6451. return false;
  6452. }
  6453. /**
  6454. * Checks to see if is a browser matches the specified
  6455. * brand and is equal or better version.
  6456. *
  6457. * @uses $_SERVER
  6458. * @param string $brand The browser identifier being tested
  6459. * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
  6460. * @return bool true if the given version is below that of the detected browser
  6461. */
  6462. function check_browser_version($brand, $version = null) {
  6463. if (empty($_SERVER['HTTP_USER_AGENT'])) {
  6464. return false;
  6465. }
  6466. $agent = $_SERVER['HTTP_USER_AGENT'];
  6467. switch ($brand) {
  6468. case 'Camino': /// OSX browser using Gecke engine
  6469. if (strpos($agent, 'Camino') === false) {
  6470. return false;
  6471. }
  6472. if (empty($version)) {
  6473. return true; // no version specified
  6474. }
  6475. if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
  6476. if (version_compare($match[1], $version) >= 0) {
  6477. return true;
  6478. }
  6479. }
  6480. break;
  6481. case 'Firefox': /// Mozilla Firefox browsers
  6482. if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
  6483. return false;
  6484. }
  6485. if (empty($version)) {
  6486. return true; // no version specified
  6487. }
  6488. if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
  6489. if (version_compare($match[2], $version) >= 0) {
  6490. return true;
  6491. }
  6492. }
  6493. break;
  6494. case 'Gecko': /// Gecko based browsers
  6495. if (empty($version) and substr_count($agent, 'Camino')) {
  6496. // MacOS X Camino support
  6497. $version = 20041110;
  6498. }
  6499. // the proper string - Gecko/CCYYMMDD Vendor/Version
  6500. // Faster version and work-a-round No IDN problem.
  6501. if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
  6502. if ($match[1] > $version) {
  6503. return true;
  6504. }
  6505. }
  6506. break;
  6507. case 'MSIE': /// Internet Explorer
  6508. if (strpos($agent, 'Opera') !== false) { // Reject Opera
  6509. return false;
  6510. }
  6511. // in case of IE we have to deal with BC of the version parameter
  6512. if (is_null($version)) {
  6513. $version = 5.5; // anything older is not considered a browser at all!
  6514. }
  6515. //see: http://www.useragentstring.com/pages/Internet%20Explorer/
  6516. if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
  6517. if (version_compare($match[1], $version) >= 0) {
  6518. return true;
  6519. }
  6520. }
  6521. break;
  6522. case 'Opera': /// Opera
  6523. if (strpos($agent, 'Opera') === false) {
  6524. return false;
  6525. }
  6526. if (empty($version)) {
  6527. return true; // no version specified
  6528. }
  6529. if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
  6530. if (version_compare($match[1], $version) >= 0) {
  6531. return true;
  6532. }
  6533. }
  6534. break;
  6535. case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
  6536. if (strpos($agent, 'AppleWebKit') === false) {
  6537. return false;
  6538. }
  6539. if (empty($version)) {
  6540. return true; // no version specified
  6541. }
  6542. if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
  6543. if (version_compare($match[1], $version) >= 0) {
  6544. return true;
  6545. }
  6546. }
  6547. break;
  6548. case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
  6549. if (strpos($agent, 'AppleWebKit') === false) {
  6550. return false;
  6551. }
  6552. // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
  6553. if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
  6554. return false;
  6555. }
  6556. if (strpos($agent, 'Shiira')) { // Reject Shiira
  6557. return false;
  6558. }
  6559. if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
  6560. return false;
  6561. }
  6562. if (strpos($agent, 'Android')) { // Reject Androids too
  6563. return false;
  6564. }
  6565. if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
  6566. // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
  6567. return false;
  6568. }
  6569. if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
  6570. return false;
  6571. }
  6572. if (empty($version)) {
  6573. return true; // no version specified
  6574. }
  6575. if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
  6576. if (version_compare($match[1], $version) >= 0) {
  6577. return true;
  6578. }
  6579. }
  6580. break;
  6581. case 'Chrome':
  6582. if (strpos($agent, 'Chrome') === false) {
  6583. return false;
  6584. }
  6585. if (empty($version)) {
  6586. return true; // no version specified
  6587. }
  6588. if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
  6589. if (version_compare($match[1], $version) >= 0) {
  6590. return true;
  6591. }
  6592. }
  6593. break;
  6594. case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
  6595. if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
  6596. return false;
  6597. }
  6598. if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
  6599. return false;
  6600. }
  6601. if (empty($version)) {
  6602. return true; // no version specified
  6603. }
  6604. if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
  6605. if (version_compare($match[1], $version) >= 0) {
  6606. return true;
  6607. }
  6608. }
  6609. break;
  6610. case 'WebKit Android': /// WebKit browser on Android
  6611. if (strpos($agent, 'Linux; U; Android') === false) {
  6612. return false;
  6613. }
  6614. if (empty($version)) {
  6615. return true; // no version specified
  6616. }
  6617. if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
  6618. if (version_compare($match[1], $version) >= 0) {
  6619. return true;
  6620. }
  6621. }
  6622. break;
  6623. }
  6624. return false;
  6625. }
  6626. /**
  6627. * Returns one or several CSS class names that match the user's browser. These can be put
  6628. * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
  6629. *
  6630. * @return array An array of browser version classes
  6631. */
  6632. function get_browser_version_classes() {
  6633. $classes = array();
  6634. if (check_browser_version("MSIE", "0")) {
  6635. $classes[] = 'ie';
  6636. if (check_browser_version("MSIE", 9)) {
  6637. $classes[] = 'ie9';
  6638. } else if (check_browser_version("MSIE", 8)) {
  6639. $classes[] = 'ie8';
  6640. } elseif (check_browser_version("MSIE", 7)) {
  6641. $classes[] = 'ie7';
  6642. } elseif (check_browser_version("MSIE", 6)) {
  6643. $classes[] = 'ie6';
  6644. }
  6645. } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
  6646. $classes[] = 'gecko';
  6647. if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
  6648. $classes[] = "gecko{$matches[1]}{$matches[2]}";
  6649. }
  6650. } else if (check_browser_version("WebKit")) {
  6651. $classes[] = 'safari';
  6652. if (check_browser_version("Safari iOS")) {
  6653. $classes[] = 'ios';
  6654. } else if (check_browser_version("WebKit Android")) {
  6655. $classes[] = 'android';
  6656. }
  6657. } else if (check_browser_version("Opera")) {
  6658. $classes[] = 'opera';
  6659. }
  6660. return $classes;
  6661. }
  6662. /**
  6663. * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
  6664. *
  6665. * @return bool True for yes, false for no
  6666. */
  6667. function can_use_rotated_text() {
  6668. global $USER;
  6669. return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader;;
  6670. }
  6671. /**
  6672. * Hack to find out the GD version by parsing phpinfo output
  6673. *
  6674. * @return int GD version (1, 2, or 0)
  6675. */
  6676. function check_gd_version() {
  6677. $gdversion = 0;
  6678. if (function_exists('gd_info')){
  6679. $gd_info = gd_info();
  6680. if (substr_count($gd_info['GD Version'], '2.')) {
  6681. $gdversion = 2;
  6682. } else if (substr_count($gd_info['GD Version'], '1.')) {
  6683. $gdversion = 1;
  6684. }
  6685. } else {
  6686. ob_start();
  6687. phpinfo(INFO_MODULES);
  6688. $phpinfo = ob_get_contents();
  6689. ob_end_clean();
  6690. $phpinfo = explode("\n", $phpinfo);
  6691. foreach ($phpinfo as $text) {
  6692. $parts = explode('</td>', $text);
  6693. foreach ($parts as $key => $val) {
  6694. $parts[$key] = trim(strip_tags($val));
  6695. }
  6696. if ($parts[0] == 'GD Version') {
  6697. if (substr_count($parts[1], '2.0')) {
  6698. $parts[1] = '2.0';
  6699. }
  6700. $gdversion = intval($parts[1]);
  6701. }
  6702. }
  6703. }
  6704. return $gdversion; // 1, 2 or 0
  6705. }
  6706. /**
  6707. * Determine if moodle installation requires update
  6708. *
  6709. * Checks version numbers of main code and all modules to see
  6710. * if there are any mismatches
  6711. *
  6712. * @global object
  6713. * @global object
  6714. * @return bool
  6715. */
  6716. function moodle_needs_upgrading() {
  6717. global $CFG, $DB, $OUTPUT;
  6718. if (empty($CFG->version)) {
  6719. return true;
  6720. }
  6721. // main versio nfirst
  6722. $version = null;
  6723. include($CFG->dirroot.'/version.php'); // defines $version and upgrades
  6724. if ($version > $CFG->version) {
  6725. return true;
  6726. }
  6727. // modules
  6728. $mods = get_plugin_list('mod');
  6729. $installed = $DB->get_records('modules', array(), '', 'name, version');
  6730. foreach ($mods as $mod => $fullmod) {
  6731. if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
  6732. continue;
  6733. }
  6734. $module = new stdClass();
  6735. if (!is_readable($fullmod.'/version.php')) {
  6736. continue;
  6737. }
  6738. include($fullmod.'/version.php'); // defines $module with version etc
  6739. if (empty($installed[$mod])) {
  6740. return true;
  6741. } else if ($module->version > $installed[$mod]->version) {
  6742. return true;
  6743. }
  6744. }
  6745. unset($installed);
  6746. // blocks
  6747. $blocks = get_plugin_list('block');
  6748. $installed = $DB->get_records('block', array(), '', 'name, version');
  6749. require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
  6750. foreach ($blocks as $blockname=>$fullblock) {
  6751. if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
  6752. continue;
  6753. }
  6754. if (!is_readable($fullblock.'/version.php')) {
  6755. continue;
  6756. }
  6757. $plugin = new stdClass();
  6758. $plugin->version = NULL;
  6759. include($fullblock.'/version.php');
  6760. if (empty($installed[$blockname])) {
  6761. return true;
  6762. } else if ($plugin->version > $installed[$blockname]->version) {
  6763. return true;
  6764. }
  6765. }
  6766. unset($installed);
  6767. // now the rest of plugins
  6768. $plugintypes = get_plugin_types();
  6769. unset($plugintypes['mod']);
  6770. unset($plugintypes['block']);
  6771. foreach ($plugintypes as $type=>$unused) {
  6772. $plugs = get_plugin_list($type);
  6773. foreach ($plugs as $plug=>$fullplug) {
  6774. $component = $type.'_'.$plug;
  6775. if (!is_readable($fullplug.'/version.php')) {
  6776. continue;
  6777. }
  6778. $plugin = new stdClass();
  6779. include($fullplug.'/version.php'); // defines $plugin with version etc
  6780. $installedversion = get_config($component, 'version');
  6781. if (empty($installedversion)) { // new installation
  6782. return true;
  6783. } else if ($installedversion < $plugin->version) { // upgrade
  6784. return true;
  6785. }
  6786. }
  6787. }
  6788. return false;
  6789. }
  6790. /**
  6791. * Sets maximum expected time needed for upgrade task.
  6792. * Please always make sure that upgrade will not run longer!
  6793. *
  6794. * The script may be automatically aborted if upgrade times out.
  6795. *
  6796. * @global object
  6797. * @param int $max_execution_time in seconds (can not be less than 60 s)
  6798. */
  6799. function upgrade_set_timeout($max_execution_time=300) {
  6800. global $CFG;
  6801. if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
  6802. $upgraderunning = get_config(null, 'upgraderunning');
  6803. } else {
  6804. $upgraderunning = $CFG->upgraderunning;
  6805. }
  6806. if (!$upgraderunning) {
  6807. // upgrade not running or aborted
  6808. print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
  6809. die;
  6810. }
  6811. if ($max_execution_time < 60) {
  6812. // protection against 0 here
  6813. $max_execution_time = 60;
  6814. }
  6815. $expected_end = time() + $max_execution_time;
  6816. if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
  6817. // no need to store new end, it is nearly the same ;-)
  6818. return;
  6819. }
  6820. set_time_limit($max_execution_time);
  6821. set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
  6822. }
  6823. /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
  6824. /**
  6825. * Notify admin users or admin user of any failed logins (since last notification).
  6826. *
  6827. * Note that this function must be only executed from the cron script
  6828. * It uses the cache_flags system to store temporary records, deleting them
  6829. * by name before finishing
  6830. *
  6831. * @global object
  6832. * @global object
  6833. * @uses HOURSECS
  6834. */
  6835. function notify_login_failures() {
  6836. global $CFG, $DB, $OUTPUT;
  6837. $recip = get_users_from_config($CFG->notifyloginfailures, 'moodle/site:config');
  6838. if (empty($CFG->lastnotifyfailure)) {
  6839. $CFG->lastnotifyfailure=0;
  6840. }
  6841. // we need to deal with the threshold stuff first.
  6842. if (empty($CFG->notifyloginthreshold)) {
  6843. $CFG->notifyloginthreshold = 10; // default to something sensible.
  6844. }
  6845. /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
  6846. /// and insert them into the cache_flags temp table
  6847. $sql = "SELECT ip, COUNT(*)
  6848. FROM {log}
  6849. WHERE module = 'login' AND action = 'error'
  6850. AND time > ?
  6851. GROUP BY ip
  6852. HAVING COUNT(*) >= ?";
  6853. $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
  6854. $rs = $DB->get_recordset_sql($sql, $params);
  6855. foreach ($rs as $iprec) {
  6856. if (!empty($iprec->ip)) {
  6857. set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
  6858. }
  6859. }
  6860. $rs->close();
  6861. /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
  6862. /// and insert them into the cache_flags temp table
  6863. $sql = "SELECT info, count(*)
  6864. FROM {log}
  6865. WHERE module = 'login' AND action = 'error'
  6866. AND time > ?
  6867. GROUP BY info
  6868. HAVING count(*) >= ?";
  6869. $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
  6870. $rs = $DB->get_recordset_sql($sql, $params);
  6871. foreach ($rs as $inforec) {
  6872. if (!empty($inforec->info)) {
  6873. set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
  6874. }
  6875. }
  6876. $rs->close();
  6877. /// Now, select all the login error logged records belonging to the ips and infos
  6878. /// since lastnotifyfailure, that we have stored in the cache_flags table
  6879. $sql = "SELECT l.*, u.firstname, u.lastname
  6880. FROM {log} l
  6881. JOIN {cache_flags} cf ON l.ip = cf.name
  6882. LEFT JOIN {user} u ON l.userid = u.id
  6883. WHERE l.module = 'login' AND l.action = 'error'
  6884. AND l.time > ?
  6885. AND cf.flagtype = 'login_failure_by_ip'
  6886. UNION ALL
  6887. SELECT l.*, u.firstname, u.lastname
  6888. FROM {log} l
  6889. JOIN {cache_flags} cf ON l.info = cf.name
  6890. LEFT JOIN {user} u ON l.userid = u.id
  6891. WHERE l.module = 'login' AND l.action = 'error'
  6892. AND l.time > ?
  6893. AND cf.flagtype = 'login_failure_by_info'
  6894. ORDER BY time DESC";
  6895. $params = array($CFG->lastnotifyfailure, $CFG->lastnotifyfailure);
  6896. /// Init some variables
  6897. $count = 0;
  6898. $messages = '';
  6899. /// Iterate over the logs recordset
  6900. $rs = $DB->get_recordset_sql($sql, $params);
  6901. foreach ($rs as $log) {
  6902. $log->time = userdate($log->time);
  6903. $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
  6904. $count++;
  6905. }
  6906. $rs->close();
  6907. /// If we haven't run in the last hour and
  6908. /// we have something useful to report and we
  6909. /// are actually supposed to be reporting to somebody
  6910. if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
  6911. $site = get_site();
  6912. $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
  6913. /// Calculate the complete body of notification (start + messages + end)
  6914. $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
  6915. (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
  6916. $messages .
  6917. "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
  6918. /// For each destination, send mail
  6919. mtrace('Emailing admins about '. $count .' failed login attempts');
  6920. foreach ($recip as $admin) {
  6921. //emailing the admins directly rather than putting these through the messaging system
  6922. email_to_user($admin,get_admin(), $subject, $body);
  6923. }
  6924. /// Update lastnotifyfailure with current time
  6925. set_config('lastnotifyfailure', time());
  6926. }
  6927. /// Finally, delete all the temp records we have created in cache_flags
  6928. $DB->delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
  6929. }
  6930. /**
  6931. * Sets the system locale
  6932. *
  6933. * @todo Finish documenting this function
  6934. *
  6935. * @global object
  6936. * @param string $locale Can be used to force a locale
  6937. */
  6938. function moodle_setlocale($locale='') {
  6939. global $CFG;
  6940. static $currentlocale = ''; // last locale caching
  6941. $oldlocale = $currentlocale;
  6942. /// Fetch the correct locale based on ostype
  6943. if ($CFG->ostype == 'WINDOWS') {
  6944. $stringtofetch = 'localewin';
  6945. } else {
  6946. $stringtofetch = 'locale';
  6947. }
  6948. /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
  6949. if (!empty($locale)) {
  6950. $currentlocale = $locale;
  6951. } else if (!empty($CFG->locale)) { // override locale for all language packs
  6952. $currentlocale = $CFG->locale;
  6953. } else {
  6954. $currentlocale = get_string($stringtofetch, 'langconfig');
  6955. }
  6956. /// do nothing if locale already set up
  6957. if ($oldlocale == $currentlocale) {
  6958. return;
  6959. }
  6960. /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
  6961. /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
  6962. /// Some day, numeric, monetary and other categories should be set too, I think. :-/
  6963. /// Get current values
  6964. $monetary= setlocale (LC_MONETARY, 0);
  6965. $numeric = setlocale (LC_NUMERIC, 0);
  6966. $ctype = setlocale (LC_CTYPE, 0);
  6967. if ($CFG->ostype != 'WINDOWS') {
  6968. $messages= setlocale (LC_MESSAGES, 0);
  6969. }
  6970. /// Set locale to all
  6971. setlocale (LC_ALL, $currentlocale);
  6972. /// Set old values
  6973. setlocale (LC_MONETARY, $monetary);
  6974. setlocale (LC_NUMERIC, $numeric);
  6975. if ($CFG->ostype != 'WINDOWS') {
  6976. setlocale (LC_MESSAGES, $messages);
  6977. }
  6978. if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
  6979. setlocale (LC_CTYPE, $ctype);
  6980. }
  6981. }
  6982. /**
  6983. * Converts string to lowercase using most compatible function available.
  6984. *
  6985. * @todo Remove this function when no longer in use
  6986. * @deprecated Use textlib->strtolower($text) instead.
  6987. *
  6988. * @param string $string The string to convert to all lowercase characters.
  6989. * @param string $encoding The encoding on the string.
  6990. * @return string
  6991. */
  6992. function moodle_strtolower ($string, $encoding='') {
  6993. //If not specified use utf8
  6994. if (empty($encoding)) {
  6995. $encoding = 'UTF-8';
  6996. }
  6997. //Use text services
  6998. $textlib = textlib_get_instance();
  6999. return $textlib->strtolower($string, $encoding);
  7000. }
  7001. /**
  7002. * Count words in a string.
  7003. *
  7004. * Words are defined as things between whitespace.
  7005. *
  7006. * @param string $string The text to be searched for words.
  7007. * @return int The count of words in the specified string
  7008. */
  7009. function count_words($string) {
  7010. $string = strip_tags($string);
  7011. return count(preg_split("/\w\b/", $string)) - 1;
  7012. }
  7013. /** Count letters in a string.
  7014. *
  7015. * Letters are defined as chars not in tags and different from whitespace.
  7016. *
  7017. * @param string $string The text to be searched for letters.
  7018. * @return int The count of letters in the specified text.
  7019. */
  7020. function count_letters($string) {
  7021. /// Loading the textlib singleton instance. We are going to need it.
  7022. $textlib = textlib_get_instance();
  7023. $string = strip_tags($string); // Tags are out now
  7024. $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
  7025. return $textlib->strlen($string);
  7026. }
  7027. /**
  7028. * Generate and return a random string of the specified length.
  7029. *
  7030. * @param int $length The length of the string to be created.
  7031. * @return string
  7032. */
  7033. function random_string ($length=15) {
  7034. $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  7035. $pool .= 'abcdefghijklmnopqrstuvwxyz';
  7036. $pool .= '0123456789';
  7037. $poollen = strlen($pool);
  7038. mt_srand ((double) microtime() * 1000000);
  7039. $string = '';
  7040. for ($i = 0; $i < $length; $i++) {
  7041. $string .= substr($pool, (mt_rand()%($poollen)), 1);
  7042. }
  7043. return $string;
  7044. }
  7045. /**
  7046. * Generate a complex random string (useful for md5 salts)
  7047. *
  7048. * This function is based on the above {@link random_string()} however it uses a
  7049. * larger pool of characters and generates a string between 24 and 32 characters
  7050. *
  7051. * @param int $length Optional if set generates a string to exactly this length
  7052. * @return string
  7053. */
  7054. function complex_random_string($length=null) {
  7055. $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  7056. $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
  7057. $poollen = strlen($pool);
  7058. mt_srand ((double) microtime() * 1000000);
  7059. if ($length===null) {
  7060. $length = floor(rand(24,32));
  7061. }
  7062. $string = '';
  7063. for ($i = 0; $i < $length; $i++) {
  7064. $string .= $pool[(mt_rand()%$poollen)];
  7065. }
  7066. return $string;
  7067. }
  7068. /**
  7069. * Given some text (which may contain HTML) and an ideal length,
  7070. * this function truncates the text neatly on a word boundary if possible
  7071. *
  7072. * @global object
  7073. * @param string $text - text to be shortened
  7074. * @param int $ideal - ideal string length
  7075. * @param boolean $exact if false, $text will not be cut mid-word
  7076. * @param string $ending The string to append if the passed string is truncated
  7077. * @return string $truncate - shortened string
  7078. */
  7079. function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
  7080. global $CFG;
  7081. // if the plain text is shorter than the maximum length, return the whole text
  7082. if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
  7083. return $text;
  7084. }
  7085. // Splits on HTML tags. Each open/close/empty tag will be the first thing
  7086. // and only tag in its 'line'
  7087. preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
  7088. $total_length = strlen($ending);
  7089. $truncate = '';
  7090. // This array stores information about open and close tags and their position
  7091. // in the truncated string. Each item in the array is an object with fields
  7092. // ->open (true if open), ->tag (tag name in lower case), and ->pos
  7093. // (byte position in truncated text)
  7094. $tagdetails = array();
  7095. foreach ($lines as $line_matchings) {
  7096. // if there is any html-tag in this line, handle it and add it (uncounted) to the output
  7097. if (!empty($line_matchings[1])) {
  7098. // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
  7099. if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
  7100. // do nothing
  7101. // if tag is a closing tag (f.e. </b>)
  7102. } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
  7103. // record closing tag
  7104. $tagdetails[] = (object)array('open'=>false,
  7105. 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
  7106. // if tag is an opening tag (f.e. <b>)
  7107. } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
  7108. // record opening tag
  7109. $tagdetails[] = (object)array('open'=>true,
  7110. 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
  7111. }
  7112. // add html-tag to $truncate'd text
  7113. $truncate .= $line_matchings[1];
  7114. }
  7115. // calculate the length of the plain text part of the line; handle entities as one character
  7116. $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
  7117. if ($total_length+$content_length > $ideal) {
  7118. // the number of characters which are left
  7119. $left = $ideal - $total_length;
  7120. $entities_length = 0;
  7121. // search for html entities
  7122. 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)) {
  7123. // calculate the real length of all entities in the legal range
  7124. foreach ($entities[0] as $entity) {
  7125. if ($entity[1]+1-$entities_length <= $left) {
  7126. $left--;
  7127. $entities_length += strlen($entity[0]);
  7128. } else {
  7129. // no more characters left
  7130. break;
  7131. }
  7132. }
  7133. }
  7134. $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
  7135. // maximum length is reached, so get off the loop
  7136. break;
  7137. } else {
  7138. $truncate .= $line_matchings[2];
  7139. $total_length += $content_length;
  7140. }
  7141. // if the maximum length is reached, get off the loop
  7142. if($total_length >= $ideal) {
  7143. break;
  7144. }
  7145. }
  7146. // if the words shouldn't be cut in the middle...
  7147. if (!$exact) {
  7148. // ...search the last occurence of a space...
  7149. for ($k=strlen($truncate);$k>0;$k--) {
  7150. if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
  7151. if ($char == '.' or $char == ' ') {
  7152. $breakpos = $k+1;
  7153. break;
  7154. } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
  7155. $breakpos = $k; // can be truncated at any UTF-8
  7156. break; // character boundary.
  7157. }
  7158. }
  7159. }
  7160. if (isset($breakpos)) {
  7161. // ...and cut the text in this position
  7162. $truncate = substr($truncate, 0, $breakpos);
  7163. }
  7164. }
  7165. // add the defined ending to the text
  7166. $truncate .= $ending;
  7167. // Now calculate the list of open html tags based on the truncate position
  7168. $open_tags = array();
  7169. foreach ($tagdetails as $taginfo) {
  7170. if(isset($breakpos) && $taginfo->pos >= $breakpos) {
  7171. // Don't include tags after we made the break!
  7172. break;
  7173. }
  7174. if($taginfo->open) {
  7175. // add tag to the beginning of $open_tags list
  7176. array_unshift($open_tags, $taginfo->tag);
  7177. } else {
  7178. $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
  7179. if ($pos !== false) {
  7180. unset($open_tags[$pos]);
  7181. }
  7182. }
  7183. }
  7184. // close all unclosed html-tags
  7185. foreach ($open_tags as $tag) {
  7186. $truncate .= '</' . $tag . '>';
  7187. }
  7188. return $truncate;
  7189. }
  7190. /**
  7191. * Given dates in seconds, how many weeks is the date from startdate
  7192. * The first week is 1, the second 2 etc ...
  7193. *
  7194. * @todo Finish documenting this function
  7195. *
  7196. * @uses WEEKSECS
  7197. * @param int $startdate Timestamp for the start date
  7198. * @param int $thedate Timestamp for the end date
  7199. * @return string
  7200. */
  7201. function getweek ($startdate, $thedate) {
  7202. if ($thedate < $startdate) { // error
  7203. return 0;
  7204. }
  7205. return floor(($thedate - $startdate) / WEEKSECS) + 1;
  7206. }
  7207. /**
  7208. * returns a randomly generated password of length $maxlen. inspired by
  7209. *
  7210. * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
  7211. * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
  7212. *
  7213. * @global object
  7214. * @param int $maxlen The maximum size of the password being generated.
  7215. * @return string
  7216. */
  7217. function generate_password($maxlen=10) {
  7218. global $CFG;
  7219. if (empty($CFG->passwordpolicy)) {
  7220. $fillers = PASSWORD_DIGITS;
  7221. $wordlist = file($CFG->wordlist);
  7222. $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
  7223. $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
  7224. $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
  7225. $password = $word1 . $filler1 . $word2;
  7226. } else {
  7227. $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
  7228. $digits = $CFG->minpassworddigits;
  7229. $lower = $CFG->minpasswordlower;
  7230. $upper = $CFG->minpasswordupper;
  7231. $nonalphanum = $CFG->minpasswordnonalphanum;
  7232. $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
  7233. // Make sure we have enough characters to fulfill
  7234. // complexity requirements
  7235. $passworddigits = PASSWORD_DIGITS;
  7236. while ($digits > strlen($passworddigits)) {
  7237. $passworddigits .= PASSWORD_DIGITS;
  7238. }
  7239. $passwordlower = PASSWORD_LOWER;
  7240. while ($lower > strlen($passwordlower)) {
  7241. $passwordlower .= PASSWORD_LOWER;
  7242. }
  7243. $passwordupper = PASSWORD_UPPER;
  7244. while ($upper > strlen($passwordupper)) {
  7245. $passwordupper .= PASSWORD_UPPER;
  7246. }
  7247. $passwordnonalphanum = PASSWORD_NONALPHANUM;
  7248. while ($nonalphanum > strlen($passwordnonalphanum)) {
  7249. $passwordnonalphanum .= PASSWORD_NONALPHANUM;
  7250. }
  7251. // Now mix and shuffle it all
  7252. $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
  7253. substr(str_shuffle ($passwordupper), 0, $upper) .
  7254. substr(str_shuffle ($passworddigits), 0, $digits) .
  7255. substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
  7256. substr(str_shuffle ($passwordlower .
  7257. $passwordupper .
  7258. $passworddigits .
  7259. $passwordnonalphanum), 0 , $additional));
  7260. }
  7261. return substr ($password, 0, $maxlen);
  7262. }
  7263. /**
  7264. * Given a float, prints it nicely.
  7265. * Localized floats must not be used in calculations!
  7266. *
  7267. * @param float $float The float to print
  7268. * @param int $places The number of decimal places to print.
  7269. * @param bool $localized use localized decimal separator
  7270. * @return string locale float
  7271. */
  7272. function format_float($float, $decimalpoints=1, $localized=true) {
  7273. if (is_null($float)) {
  7274. return '';
  7275. }
  7276. if ($localized) {
  7277. return number_format($float, $decimalpoints, get_string('decsep', 'langconfig'), '');
  7278. } else {
  7279. return number_format($float, $decimalpoints, '.', '');
  7280. }
  7281. }
  7282. /**
  7283. * Converts locale specific floating point/comma number back to standard PHP float value
  7284. * Do NOT try to do any math operations before this conversion on any user submitted floats!
  7285. *
  7286. * @param string $locale_float locale aware float representation
  7287. * @return float
  7288. */
  7289. function unformat_float($locale_float) {
  7290. $locale_float = trim($locale_float);
  7291. if ($locale_float == '') {
  7292. return null;
  7293. }
  7294. $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
  7295. return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
  7296. }
  7297. /**
  7298. * Given a simple array, this shuffles it up just like shuffle()
  7299. * Unlike PHP's shuffle() this function works on any machine.
  7300. *
  7301. * @param array $array The array to be rearranged
  7302. * @return array
  7303. */
  7304. function swapshuffle($array) {
  7305. srand ((double) microtime() * 10000000);
  7306. $last = count($array) - 1;
  7307. for ($i=0;$i<=$last;$i++) {
  7308. $from = rand(0,$last);
  7309. $curr = $array[$i];
  7310. $array[$i] = $array[$from];
  7311. $array[$from] = $curr;
  7312. }
  7313. return $array;
  7314. }
  7315. /**
  7316. * Like {@link swapshuffle()}, but works on associative arrays
  7317. *
  7318. * @param array $array The associative array to be rearranged
  7319. * @return array
  7320. */
  7321. function swapshuffle_assoc($array) {
  7322. $newarray = array();
  7323. $newkeys = swapshuffle(array_keys($array));
  7324. foreach ($newkeys as $newkey) {
  7325. $newarray[$newkey] = $array[$newkey];
  7326. }
  7327. return $newarray;
  7328. }
  7329. /**
  7330. * Given an arbitrary array, and a number of draws,
  7331. * this function returns an array with that amount
  7332. * of items. The indexes are retained.
  7333. *
  7334. * @todo Finish documenting this function
  7335. *
  7336. * @param array $array
  7337. * @param int $draws
  7338. * @return array
  7339. */
  7340. function draw_rand_array($array, $draws) {
  7341. srand ((double) microtime() * 10000000);
  7342. $return = array();
  7343. $last = count($array);
  7344. if ($draws > $last) {
  7345. $draws = $last;
  7346. }
  7347. while ($draws > 0) {
  7348. $last--;
  7349. $keys = array_keys($array);
  7350. $rand = rand(0, $last);
  7351. $return[$keys[$rand]] = $array[$keys[$rand]];
  7352. unset($array[$keys[$rand]]);
  7353. $draws--;
  7354. }
  7355. return $return;
  7356. }
  7357. /**
  7358. * Calculate the difference between two microtimes
  7359. *
  7360. * @param string $a The first Microtime
  7361. * @param string $b The second Microtime
  7362. * @return string
  7363. */
  7364. function microtime_diff($a, $b) {
  7365. list($a_dec, $a_sec) = explode(' ', $a);
  7366. list($b_dec, $b_sec) = explode(' ', $b);
  7367. return $b_sec - $a_sec + $b_dec - $a_dec;
  7368. }
  7369. /**
  7370. * Given a list (eg a,b,c,d,e) this function returns
  7371. * an array of 1->a, 2->b, 3->c etc
  7372. *
  7373. * @param string $list The string to explode into array bits
  7374. * @param string $separator The separator used within the list string
  7375. * @return array The now assembled array
  7376. */
  7377. function make_menu_from_list($list, $separator=',') {
  7378. $array = array_reverse(explode($separator, $list), true);
  7379. foreach ($array as $key => $item) {
  7380. $outarray[$key+1] = trim($item);
  7381. }
  7382. return $outarray;
  7383. }
  7384. /**
  7385. * Creates an array that represents all the current grades that
  7386. * can be chosen using the given grading type.
  7387. *
  7388. * Negative numbers
  7389. * are scales, zero is no grade, and positive numbers are maximum
  7390. * grades.
  7391. *
  7392. * @todo Finish documenting this function or better deprecated this completely!
  7393. *
  7394. * @param int $gradingtype
  7395. * @return array
  7396. */
  7397. function make_grades_menu($gradingtype) {
  7398. global $DB;
  7399. $grades = array();
  7400. if ($gradingtype < 0) {
  7401. if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
  7402. return make_menu_from_list($scale->scale);
  7403. }
  7404. } else if ($gradingtype > 0) {
  7405. for ($i=$gradingtype; $i>=0; $i--) {
  7406. $grades[$i] = $i .' / '. $gradingtype;
  7407. }
  7408. return $grades;
  7409. }
  7410. return $grades;
  7411. }
  7412. /**
  7413. * This function returns the number of activities
  7414. * using scaleid in a courseid
  7415. *
  7416. * @todo Finish documenting this function
  7417. *
  7418. * @global object
  7419. * @global object
  7420. * @param int $courseid ?
  7421. * @param int $scaleid ?
  7422. * @return int
  7423. */
  7424. function course_scale_used($courseid, $scaleid) {
  7425. global $CFG, $DB;
  7426. $return = 0;
  7427. if (!empty($scaleid)) {
  7428. if ($cms = get_course_mods($courseid)) {
  7429. foreach ($cms as $cm) {
  7430. //Check cm->name/lib.php exists
  7431. if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
  7432. include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
  7433. $function_name = $cm->modname.'_scale_used';
  7434. if (function_exists($function_name)) {
  7435. if ($function_name($cm->instance,$scaleid)) {
  7436. $return++;
  7437. }
  7438. }
  7439. }
  7440. }
  7441. }
  7442. // check if any course grade item makes use of the scale
  7443. $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
  7444. // check if any outcome in the course makes use of the scale
  7445. $return += $DB->count_records_sql("SELECT COUNT('x')
  7446. FROM {grade_outcomes_courses} goc,
  7447. {grade_outcomes} go
  7448. WHERE go.id = goc.outcomeid
  7449. AND go.scaleid = ? AND goc.courseid = ?",
  7450. array($scaleid, $courseid));
  7451. }
  7452. return $return;
  7453. }
  7454. /**
  7455. * This function returns the number of activities
  7456. * using scaleid in the entire site
  7457. *
  7458. * @param int $scaleid
  7459. * @param array $courses
  7460. * @return int
  7461. */
  7462. function site_scale_used($scaleid, &$courses) {
  7463. $return = 0;
  7464. if (!is_array($courses) || count($courses) == 0) {
  7465. $courses = get_courses("all",false,"c.id,c.shortname");
  7466. }
  7467. if (!empty($scaleid)) {
  7468. if (is_array($courses) && count($courses) > 0) {
  7469. foreach ($courses as $course) {
  7470. $return += course_scale_used($course->id,$scaleid);
  7471. }
  7472. }
  7473. }
  7474. return $return;
  7475. }
  7476. /**
  7477. * make_unique_id_code
  7478. *
  7479. * @todo Finish documenting this function
  7480. *
  7481. * @uses $_SERVER
  7482. * @param string $extra Extra string to append to the end of the code
  7483. * @return string
  7484. */
  7485. function make_unique_id_code($extra='') {
  7486. $hostname = 'unknownhost';
  7487. if (!empty($_SERVER['HTTP_HOST'])) {
  7488. $hostname = $_SERVER['HTTP_HOST'];
  7489. } else if (!empty($_ENV['HTTP_HOST'])) {
  7490. $hostname = $_ENV['HTTP_HOST'];
  7491. } else if (!empty($_SERVER['SERVER_NAME'])) {
  7492. $hostname = $_SERVER['SERVER_NAME'];
  7493. } else if (!empty($_ENV['SERVER_NAME'])) {
  7494. $hostname = $_ENV['SERVER_NAME'];
  7495. }
  7496. $date = gmdate("ymdHis");
  7497. $random = random_string(6);
  7498. if ($extra) {
  7499. return $hostname .'+'. $date .'+'. $random .'+'. $extra;
  7500. } else {
  7501. return $hostname .'+'. $date .'+'. $random;
  7502. }
  7503. }
  7504. /**
  7505. * Function to check the passed address is within the passed subnet
  7506. *
  7507. * The parameter is a comma separated string of subnet definitions.
  7508. * Subnet strings can be in one of three formats:
  7509. * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
  7510. * 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group)
  7511. * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
  7512. * Code for type 1 modified from user posted comments by mediator at
  7513. * {@link http://au.php.net/manual/en/function.ip2long.php}
  7514. *
  7515. * @param string $addr The address you are checking
  7516. * @param string $subnetstr The string of subnet addresses
  7517. * @return bool
  7518. */
  7519. function address_in_subnet($addr, $subnetstr) {
  7520. if ($addr == '0.0.0.0') {
  7521. return false;
  7522. }
  7523. $subnets = explode(',', $subnetstr);
  7524. $found = false;
  7525. $addr = trim($addr);
  7526. $addr = cleanremoteaddr($addr, false); // normalise
  7527. if ($addr === null) {
  7528. return false;
  7529. }
  7530. $addrparts = explode(':', $addr);
  7531. $ipv6 = strpos($addr, ':');
  7532. foreach ($subnets as $subnet) {
  7533. $subnet = trim($subnet);
  7534. if ($subnet === '') {
  7535. continue;
  7536. }
  7537. if (strpos($subnet, '/') !== false) {
  7538. ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
  7539. list($ip, $mask) = explode('/', $subnet);
  7540. $mask = trim($mask);
  7541. if (!is_number($mask)) {
  7542. continue; // incorect mask number, eh?
  7543. }
  7544. $ip = cleanremoteaddr($ip, false); // normalise
  7545. if ($ip === null) {
  7546. continue;
  7547. }
  7548. if (strpos($ip, ':') !== false) {
  7549. // IPv6
  7550. if (!$ipv6) {
  7551. continue;
  7552. }
  7553. if ($mask > 128 or $mask < 0) {
  7554. continue; // nonsense
  7555. }
  7556. if ($mask == 0) {
  7557. return true; // any address
  7558. }
  7559. if ($mask == 128) {
  7560. if ($ip === $addr) {
  7561. return true;
  7562. }
  7563. continue;
  7564. }
  7565. $ipparts = explode(':', $ip);
  7566. $modulo = $mask % 16;
  7567. $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
  7568. $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
  7569. if (implode(':', $ipnet) === implode(':', $addrnet)) {
  7570. if ($modulo == 0) {
  7571. return true;
  7572. }
  7573. $pos = ($mask-$modulo)/16;
  7574. $ipnet = hexdec($ipparts[$pos]);
  7575. $addrnet = hexdec($addrparts[$pos]);
  7576. $mask = 0xffff << (16 - $modulo);
  7577. if (($addrnet & $mask) == ($ipnet & $mask)) {
  7578. return true;
  7579. }
  7580. }
  7581. } else {
  7582. // IPv4
  7583. if ($ipv6) {
  7584. continue;
  7585. }
  7586. if ($mask > 32 or $mask < 0) {
  7587. continue; // nonsense
  7588. }
  7589. if ($mask == 0) {
  7590. return true;
  7591. }
  7592. if ($mask == 32) {
  7593. if ($ip === $addr) {
  7594. return true;
  7595. }
  7596. continue;
  7597. }
  7598. $mask = 0xffffffff << (32 - $mask);
  7599. if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
  7600. return true;
  7601. }
  7602. }
  7603. } else if (strpos($subnet, '-') !== false) {
  7604. /// 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy ...a range of IP addresses in the last group.
  7605. $parts = explode('-', $subnet);
  7606. if (count($parts) != 2) {
  7607. continue;
  7608. }
  7609. if (strpos($subnet, ':') !== false) {
  7610. // IPv6
  7611. if (!$ipv6) {
  7612. continue;
  7613. }
  7614. $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
  7615. if ($ipstart === null) {
  7616. continue;
  7617. }
  7618. $ipparts = explode(':', $ipstart);
  7619. $start = hexdec(array_pop($ipparts));
  7620. $ipparts[] = trim($parts[1]);
  7621. $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
  7622. if ($ipend === null) {
  7623. continue;
  7624. }
  7625. $ipparts[7] = '';
  7626. $ipnet = implode(':', $ipparts);
  7627. if (strpos($addr, $ipnet) !== 0) {
  7628. continue;
  7629. }
  7630. $ipparts = explode(':', $ipend);
  7631. $end = hexdec($ipparts[7]);
  7632. $addrend = hexdec($addrparts[7]);
  7633. if (($addrend >= $start) and ($addrend <= $end)) {
  7634. return true;
  7635. }
  7636. } else {
  7637. // IPv4
  7638. if ($ipv6) {
  7639. continue;
  7640. }
  7641. $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
  7642. if ($ipstart === null) {
  7643. continue;
  7644. }
  7645. $ipparts = explode('.', $ipstart);
  7646. $ipparts[3] = trim($parts[1]);
  7647. $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
  7648. if ($ipend === null) {
  7649. continue;
  7650. }
  7651. if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
  7652. return true;
  7653. }
  7654. }
  7655. } else {
  7656. /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
  7657. if (strpos($subnet, ':') !== false) {
  7658. // IPv6
  7659. if (!$ipv6) {
  7660. continue;
  7661. }
  7662. $parts = explode(':', $subnet);
  7663. $count = count($parts);
  7664. if ($parts[$count-1] === '') {
  7665. unset($parts[$count-1]); // trim trailing :
  7666. $count--;
  7667. $subnet = implode('.', $parts);
  7668. }
  7669. $isip = cleanremoteaddr($subnet, false); // normalise
  7670. if ($isip !== null) {
  7671. if ($isip === $addr) {
  7672. return true;
  7673. }
  7674. continue;
  7675. } else if ($count > 8) {
  7676. continue;
  7677. }
  7678. $zeros = array_fill(0, 8-$count, '0');
  7679. $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
  7680. if (address_in_subnet($addr, $subnet)) {
  7681. return true;
  7682. }
  7683. } else {
  7684. // IPv4
  7685. if ($ipv6) {
  7686. continue;
  7687. }
  7688. $parts = explode('.', $subnet);
  7689. $count = count($parts);
  7690. if ($parts[$count-1] === '') {
  7691. unset($parts[$count-1]); // trim trailing .
  7692. $count--;
  7693. $subnet = implode('.', $parts);
  7694. }
  7695. if ($count == 4) {
  7696. $subnet = cleanremoteaddr($subnet, false); // normalise
  7697. if ($subnet === $addr) {
  7698. return true;
  7699. }
  7700. continue;
  7701. } else if ($count > 4) {
  7702. continue;
  7703. }
  7704. $zeros = array_fill(0, 4-$count, '0');
  7705. $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
  7706. if (address_in_subnet($addr, $subnet)) {
  7707. return true;
  7708. }
  7709. }
  7710. }
  7711. }
  7712. return false;
  7713. }
  7714. /**
  7715. * For outputting debugging info
  7716. *
  7717. * @uses STDOUT
  7718. * @param string $string The string to write
  7719. * @param string $eol The end of line char(s) to use
  7720. * @param string $sleep Period to make the application sleep
  7721. * This ensures any messages have time to display before redirect
  7722. */
  7723. function mtrace($string, $eol="\n", $sleep=0) {
  7724. if (defined('STDOUT')) {
  7725. fwrite(STDOUT, $string.$eol);
  7726. } else {
  7727. echo $string . $eol;
  7728. }
  7729. flush();
  7730. //delay to keep message on user's screen in case of subsequent redirect
  7731. if ($sleep) {
  7732. sleep($sleep);
  7733. }
  7734. }
  7735. /**
  7736. * Replace 1 or more slashes or backslashes to 1 slash
  7737. *
  7738. * @param string $path The path to strip
  7739. * @return string the path with double slashes removed
  7740. */
  7741. function cleardoubleslashes ($path) {
  7742. return preg_replace('/(\/|\\\){1,}/','/',$path);
  7743. }
  7744. /**
  7745. * Is current ip in give list?
  7746. *
  7747. * @param string $list
  7748. * @return bool
  7749. */
  7750. function remoteip_in_list($list){
  7751. $inlist = false;
  7752. $client_ip = getremoteaddr(null);
  7753. if(!$client_ip){
  7754. // ensure access on cli
  7755. return true;
  7756. }
  7757. $list = explode("\n", $list);
  7758. foreach($list as $subnet) {
  7759. $subnet = trim($subnet);
  7760. if (address_in_subnet($client_ip, $subnet)) {
  7761. $inlist = true;
  7762. break;
  7763. }
  7764. }
  7765. return $inlist;
  7766. }
  7767. /**
  7768. * Returns most reliable client address
  7769. *
  7770. * @global object
  7771. * @param string $default If an address can't be determined, then return this
  7772. * @return string The remote IP address
  7773. */
  7774. function getremoteaddr($default='0.0.0.0') {
  7775. global $CFG;
  7776. if (empty($CFG->getremoteaddrconf)) {
  7777. // This will happen, for example, before just after the upgrade, as the
  7778. // user is redirected to the admin screen.
  7779. $variablestoskip = 0;
  7780. } else {
  7781. $variablestoskip = $CFG->getremoteaddrconf;
  7782. }
  7783. if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
  7784. if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
  7785. $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
  7786. return $address ? $address : $default;
  7787. }
  7788. }
  7789. if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
  7790. if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  7791. $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
  7792. return $address ? $address : $default;
  7793. }
  7794. }
  7795. if (!empty($_SERVER['REMOTE_ADDR'])) {
  7796. $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
  7797. return $address ? $address : $default;
  7798. } else {
  7799. return $default;
  7800. }
  7801. }
  7802. /**
  7803. * Cleans an ip address. Internal addresses are now allowed.
  7804. * (Originally local addresses were not allowed.)
  7805. *
  7806. * @param string $addr IPv4 or IPv6 address
  7807. * @param bool $compress use IPv6 address compression
  7808. * @return string normalised ip address string, null if error
  7809. */
  7810. function cleanremoteaddr($addr, $compress=false) {
  7811. $addr = trim($addr);
  7812. //TODO: maybe add a separate function is_addr_public() or something like this
  7813. if (strpos($addr, ':') !== false) {
  7814. // can be only IPv6
  7815. $parts = explode(':', $addr);
  7816. $count = count($parts);
  7817. if (strpos($parts[$count-1], '.') !== false) {
  7818. //legacy ipv4 notation
  7819. $last = array_pop($parts);
  7820. $ipv4 = cleanremoteaddr($last, true);
  7821. if ($ipv4 === null) {
  7822. return null;
  7823. }
  7824. $bits = explode('.', $ipv4);
  7825. $parts[] = dechex($bits[0]).dechex($bits[1]);
  7826. $parts[] = dechex($bits[2]).dechex($bits[3]);
  7827. $count = count($parts);
  7828. $addr = implode(':', $parts);
  7829. }
  7830. if ($count < 3 or $count > 8) {
  7831. return null; // severly malformed
  7832. }
  7833. if ($count != 8) {
  7834. if (strpos($addr, '::') === false) {
  7835. return null; // malformed
  7836. }
  7837. // uncompress ::
  7838. $insertat = array_search('', $parts, true);
  7839. $missing = array_fill(0, 1 + 8 - $count, '0');
  7840. array_splice($parts, $insertat, 1, $missing);
  7841. foreach ($parts as $key=>$part) {
  7842. if ($part === '') {
  7843. $parts[$key] = '0';
  7844. }
  7845. }
  7846. }
  7847. $adr = implode(':', $parts);
  7848. if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
  7849. return null; // incorrect format - sorry
  7850. }
  7851. // normalise 0s and case
  7852. $parts = array_map('hexdec', $parts);
  7853. $parts = array_map('dechex', $parts);
  7854. $result = implode(':', $parts);
  7855. if (!$compress) {
  7856. return $result;
  7857. }
  7858. if ($result === '0:0:0:0:0:0:0:0') {
  7859. return '::'; // all addresses
  7860. }
  7861. $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
  7862. if ($compressed !== $result) {
  7863. return $compressed;
  7864. }
  7865. $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
  7866. if ($compressed !== $result) {
  7867. return $compressed;
  7868. }
  7869. $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
  7870. if ($compressed !== $result) {
  7871. return $compressed;
  7872. }
  7873. return $result;
  7874. }
  7875. // first get all things that look like IPv4 addresses
  7876. $parts = array();
  7877. if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
  7878. return null;
  7879. }
  7880. unset($parts[0]);
  7881. foreach ($parts as $key=>$match) {
  7882. if ($match > 255) {
  7883. return null;
  7884. }
  7885. $parts[$key] = (int)$match; // normalise 0s
  7886. }
  7887. return implode('.', $parts);
  7888. }
  7889. /**
  7890. * This function will make a complete copy of anything it's given,
  7891. * regardless of whether it's an object or not.
  7892. *
  7893. * @param mixed $thing Something you want cloned
  7894. * @return mixed What ever it is you passed it
  7895. */
  7896. function fullclone($thing) {
  7897. return unserialize(serialize($thing));
  7898. }
  7899. /**
  7900. * This function expects to called during shutdown
  7901. * should be set via register_shutdown_function()
  7902. * in lib/setup.php .
  7903. *
  7904. * @return void
  7905. */
  7906. function moodle_request_shutdown() {
  7907. global $CFG;
  7908. // help apache server if possible
  7909. $apachereleasemem = false;
  7910. if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
  7911. && ini_get_bool('child_terminate')) {
  7912. $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
  7913. if (memory_get_usage() > get_real_size($limit)) {
  7914. $apachereleasemem = $limit;
  7915. @apache_child_terminate();
  7916. }
  7917. }
  7918. // deal with perf logging
  7919. if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
  7920. if ($apachereleasemem) {
  7921. error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
  7922. }
  7923. if (defined('MDL_PERFTOLOG')) {
  7924. $perf = get_performance_info();
  7925. error_log("PERF: " . $perf['txt']);
  7926. }
  7927. if (defined('MDL_PERFINC')) {
  7928. $inc = get_included_files();
  7929. $ts = 0;
  7930. foreach($inc as $f) {
  7931. if (preg_match(':^/:', $f)) {
  7932. $fs = filesize($f);
  7933. $ts += $fs;
  7934. $hfs = display_size($fs);
  7935. error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
  7936. , NULL, NULL, 0);
  7937. } else {
  7938. error_log($f , NULL, NULL, 0);
  7939. }
  7940. }
  7941. if ($ts > 0 ) {
  7942. $hts = display_size($ts);
  7943. error_log("Total size of files included: $ts ($hts)");
  7944. }
  7945. }
  7946. }
  7947. }
  7948. /**
  7949. * If new messages are waiting for the current user, then insert
  7950. * JavaScript to pop up the messaging window into the page
  7951. *
  7952. * @global moodle_page $PAGE
  7953. * @return void
  7954. */
  7955. function message_popup_window() {
  7956. global $USER, $DB, $PAGE, $CFG, $SITE;
  7957. if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
  7958. return;
  7959. }
  7960. if (!isloggedin() || isguestuser()) {
  7961. return;
  7962. }
  7963. if (!isset($USER->message_lastpopup)) {
  7964. $USER->message_lastpopup = 0;
  7965. } else if ($USER->message_lastpopup > (time()-120)) {
  7966. //dont run the query to check whether to display a popup if its been run in the last 2 minutes
  7967. return;
  7968. }
  7969. //a quick query to check whether the user has new messages
  7970. $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
  7971. if ($messagecount<1) {
  7972. return;
  7973. }
  7974. //got unread messages so now do another query that joins with the user table
  7975. $messagesql = "SELECT m.id, m.smallmessage, m.notification, u.firstname, u.lastname FROM {message} m
  7976. JOIN {message_working} mw ON m.id=mw.unreadmessageid
  7977. JOIN {message_processors} p ON mw.processorid=p.id
  7978. JOIN {user} u ON m.useridfrom=u.id
  7979. WHERE m.useridto = :userid AND p.name='popup'";
  7980. //if the user was last notified over an hour ago we can renotify them of old messages
  7981. //so don't worry about when the new message was sent
  7982. $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
  7983. if (!$lastnotifiedlongago) {
  7984. $messagesql .= 'AND m.timecreated > :lastpopuptime';
  7985. }
  7986. $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
  7987. //if we have new messages to notify the user about
  7988. if (!empty($message_users)) {
  7989. $strmessages = '';
  7990. if (count($message_users)>1) {
  7991. $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
  7992. } else {
  7993. $message_users = reset($message_users);
  7994. //show who the message is from if its not a notification
  7995. if (!$message_users->notification) {
  7996. $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
  7997. }
  7998. //try to display the small version of the message
  7999. $smallmessage = null;
  8000. if (!empty($message_users->smallmessage)) {
  8001. //display the first 200 chars of the message in the popup
  8002. $smallmessage = null;
  8003. if (strlen($message_users->smallmessage>200)) {
  8004. $smallmessage = substr($message_users->smallmessage,0,200).'...';
  8005. } else {
  8006. $smallmessage = $message_users->smallmessage;
  8007. }
  8008. } else if ($message_users->notification) {
  8009. //its a notification with no smallmessage so just say they have a notification
  8010. $smallmessage = get_string('unreadnewnotification', 'message');
  8011. }
  8012. if (!empty($smallmessage)) {
  8013. $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
  8014. }
  8015. }
  8016. $strgomessage = get_string('gotomessages', 'message');
  8017. $strstaymessage = get_string('ignore','admin');
  8018. $url = $CFG->wwwroot.'/message/index.php';
  8019. $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
  8020. html_writer::start_tag('div', array('id'=>'newmessagetext')).
  8021. $strmessages.
  8022. html_writer::end_tag('div').
  8023. html_writer::start_tag('div', array('id'=>'newmessagelinks')).
  8024. html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
  8025. html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
  8026. html_writer::end_tag('div');
  8027. html_writer::end_tag('div');
  8028. $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
  8029. $USER->message_lastpopup = time();
  8030. }
  8031. }
  8032. /**
  8033. * Used to make sure that $min <= $value <= $max
  8034. *
  8035. * Make sure that value is between min, and max
  8036. *
  8037. * @param int $min The minimum value
  8038. * @param int $value The value to check
  8039. * @param int $max The maximum value
  8040. */
  8041. function bounded_number($min, $value, $max) {
  8042. if($value < $min) {
  8043. return $min;
  8044. }
  8045. if($value > $max) {
  8046. return $max;
  8047. }
  8048. return $value;
  8049. }
  8050. /**
  8051. * Check if there is a nested array within the passed array
  8052. *
  8053. * @param array $array
  8054. * @return bool true if there is a nested array false otherwise
  8055. */
  8056. function array_is_nested($array) {
  8057. foreach ($array as $value) {
  8058. if (is_array($value)) {
  8059. return true;
  8060. }
  8061. }
  8062. return false;
  8063. }
  8064. /**
  8065. * get_performance_info() pairs up with init_performance_info()
  8066. * loaded in setup.php. Returns an array with 'html' and 'txt'
  8067. * values ready for use, and each of the individual stats provided
  8068. * separately as well.
  8069. *
  8070. * @global object
  8071. * @global object
  8072. * @global object
  8073. * @return array
  8074. */
  8075. function get_performance_info() {
  8076. global $CFG, $PERF, $DB, $PAGE;
  8077. $info = array();
  8078. $info['html'] = ''; // holds userfriendly HTML representation
  8079. $info['txt'] = me() . ' '; // holds log-friendly representation
  8080. $info['realtime'] = microtime_diff($PERF->starttime, microtime());
  8081. $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
  8082. $info['txt'] .= 'time: '.$info['realtime'].'s ';
  8083. if (function_exists('memory_get_usage')) {
  8084. $info['memory_total'] = memory_get_usage();
  8085. $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
  8086. $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
  8087. $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
  8088. }
  8089. if (function_exists('memory_get_peak_usage')) {
  8090. $info['memory_peak'] = memory_get_peak_usage();
  8091. $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
  8092. $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
  8093. }
  8094. $inc = get_included_files();
  8095. //error_log(print_r($inc,1));
  8096. $info['includecount'] = count($inc);
  8097. $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
  8098. $info['txt'] .= 'includecount: '.$info['includecount'].' ';
  8099. $filtermanager = filter_manager::instance();
  8100. if (method_exists($filtermanager, 'get_performance_summary')) {
  8101. list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
  8102. $info = array_merge($filterinfo, $info);
  8103. foreach ($filterinfo as $key => $value) {
  8104. $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
  8105. $info['txt'] .= "$key: $value ";
  8106. }
  8107. }
  8108. $stringmanager = get_string_manager();
  8109. if (method_exists($stringmanager, 'get_performance_summary')) {
  8110. list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
  8111. $info = array_merge($filterinfo, $info);
  8112. foreach ($filterinfo as $key => $value) {
  8113. $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
  8114. $info['txt'] .= "$key: $value ";
  8115. }
  8116. }
  8117. $jsmodules = $PAGE->requires->get_loaded_modules();
  8118. if ($jsmodules) {
  8119. $yuicount = 0;
  8120. $othercount = 0;
  8121. $details = '';
  8122. foreach ($jsmodules as $module => $backtraces) {
  8123. if (strpos($module, 'yui') === 0) {
  8124. $yuicount += 1;
  8125. } else {
  8126. $othercount += 1;
  8127. }
  8128. $details .= "<div class='yui-module'><p>$module</p>";
  8129. foreach ($backtraces as $backtrace) {
  8130. $details .= "<div class='backtrace'>$backtrace</div>";
  8131. }
  8132. $details .= '</div>';
  8133. }
  8134. $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
  8135. $info['txt'] .= "includedyuimodules: $yuicount ";
  8136. $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
  8137. $info['txt'] .= "includedjsmodules: $othercount ";
  8138. // Slightly odd to output the details in a display: none div. The point
  8139. // Is that it takes a lot of space, and if you care you can reveal it
  8140. // using firebug.
  8141. $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
  8142. }
  8143. if (!empty($PERF->logwrites)) {
  8144. $info['logwrites'] = $PERF->logwrites;
  8145. $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
  8146. $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
  8147. }
  8148. $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
  8149. $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
  8150. $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
  8151. if (!empty($PERF->profiling) && $PERF->profiling) {
  8152. require_once($CFG->dirroot .'/lib/profilerlib.php');
  8153. $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
  8154. }
  8155. if (function_exists('posix_times')) {
  8156. $ptimes = posix_times();
  8157. if (is_array($ptimes)) {
  8158. foreach ($ptimes as $key => $val) {
  8159. $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
  8160. }
  8161. $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
  8162. $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
  8163. }
  8164. }
  8165. // Grab the load average for the last minute
  8166. // /proc will only work under some linux configurations
  8167. // while uptime is there under MacOSX/Darwin and other unices
  8168. if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
  8169. list($server_load) = explode(' ', $loadavg[0]);
  8170. unset($loadavg);
  8171. } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
  8172. if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
  8173. $server_load = $matches[1];
  8174. } else {
  8175. trigger_error('Could not parse uptime output!');
  8176. }
  8177. }
  8178. if (!empty($server_load)) {
  8179. $info['serverload'] = $server_load;
  8180. $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
  8181. $info['txt'] .= "serverload: {$info['serverload']} ";
  8182. }
  8183. // Display size of session if session started
  8184. if (session_id()) {
  8185. $info['sessionsize'] = display_size(strlen(session_encode()));
  8186. $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
  8187. $info['txt'] .= "Session: {$info['sessionsize']} ";
  8188. }
  8189. /* if (isset($rcache->hits) && isset($rcache->misses)) {
  8190. $info['rcachehits'] = $rcache->hits;
  8191. $info['rcachemisses'] = $rcache->misses;
  8192. $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
  8193. "{$rcache->hits}/{$rcache->misses}</span> ";
  8194. $info['txt'] .= 'rcache: '.
  8195. "{$rcache->hits}/{$rcache->misses} ";
  8196. }*/
  8197. $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
  8198. return $info;
  8199. }
  8200. /**
  8201. * @todo Document this function linux people
  8202. */
  8203. function apd_get_profiling() {
  8204. return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
  8205. }
  8206. /**
  8207. * Delete directory or only it's content
  8208. *
  8209. * @param string $dir directory path
  8210. * @param bool $content_only
  8211. * @return bool success, true also if dir does not exist
  8212. */
  8213. function remove_dir($dir, $content_only=false) {
  8214. if (!file_exists($dir)) {
  8215. // nothing to do
  8216. return true;
  8217. }
  8218. $handle = opendir($dir);
  8219. $result = true;
  8220. while (false!==($item = readdir($handle))) {
  8221. if($item != '.' && $item != '..') {
  8222. if(is_dir($dir.'/'.$item)) {
  8223. $result = remove_dir($dir.'/'.$item) && $result;
  8224. }else{
  8225. $result = unlink($dir.'/'.$item) && $result;
  8226. }
  8227. }
  8228. }
  8229. closedir($handle);
  8230. if ($content_only) {
  8231. return $result;
  8232. }
  8233. return rmdir($dir); // if anything left the result will be false, no need for && $result
  8234. }
  8235. /**
  8236. * Detect if an object or a class contains a given property
  8237. * will take an actual object or the name of a class
  8238. *
  8239. * @param mix $obj Name of class or real object to test
  8240. * @param string $property name of property to find
  8241. * @return bool true if property exists
  8242. */
  8243. function object_property_exists( $obj, $property ) {
  8244. if (is_string( $obj )) {
  8245. $properties = get_class_vars( $obj );
  8246. }
  8247. else {
  8248. $properties = get_object_vars( $obj );
  8249. }
  8250. return array_key_exists( $property, $properties );
  8251. }
  8252. /**
  8253. * Detect a custom script replacement in the data directory that will
  8254. * replace an existing moodle script
  8255. *
  8256. * @param string $urlpath path to the original script
  8257. * @return string|bool full path name if a custom script exists, false if no custom script exists
  8258. */
  8259. function custom_script_path($urlpath='') {
  8260. global $CFG;
  8261. // set default $urlpath, if necessary
  8262. if (empty($urlpath)) {
  8263. $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
  8264. }
  8265. // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
  8266. if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
  8267. return false;
  8268. }
  8269. // replace wwwroot with the path to the customscripts folder and clean path
  8270. $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
  8271. // remove the query string, if any
  8272. if (($strpos = strpos($scriptpath, '?')) !== false) {
  8273. $scriptpath = substr($scriptpath, 0, $strpos);
  8274. }
  8275. // remove trailing slashes, if any
  8276. $scriptpath = rtrim($scriptpath, '/\\');
  8277. // append index.php, if necessary
  8278. if (is_dir($scriptpath)) {
  8279. $scriptpath .= '/index.php';
  8280. }
  8281. // check the custom script exists
  8282. if (file_exists($scriptpath)) {
  8283. return $scriptpath;
  8284. } else {
  8285. return false;
  8286. }
  8287. }
  8288. /**
  8289. * Returns whether or not the user object is a remote MNET user. This function
  8290. * is in moodlelib because it does not rely on loading any of the MNET code.
  8291. *
  8292. * @global object
  8293. * @param object $user A valid user object
  8294. * @return bool True if the user is from a remote Moodle.
  8295. */
  8296. function is_mnet_remote_user($user) {
  8297. global $CFG;
  8298. if (!isset($CFG->mnet_localhost_id)) {
  8299. include_once $CFG->dirroot . '/mnet/lib.php';
  8300. $env = new mnet_environment();
  8301. $env->init();
  8302. unset($env);
  8303. }
  8304. return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
  8305. }
  8306. /**
  8307. * This function will search for browser prefereed languages, setting Moodle
  8308. * to use the best one available if $SESSION->lang is undefined
  8309. *
  8310. * @global object
  8311. * @global object
  8312. * @global object
  8313. */
  8314. function setup_lang_from_browser() {
  8315. global $CFG, $SESSION, $USER;
  8316. if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
  8317. // Lang is defined in session or user profile, nothing to do
  8318. return;
  8319. }
  8320. if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
  8321. return;
  8322. }
  8323. /// Extract and clean langs from headers
  8324. $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  8325. $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
  8326. $rawlangs = explode(',', $rawlangs); // Convert to array
  8327. $langs = array();
  8328. $order = 1.0;
  8329. foreach ($rawlangs as $lang) {
  8330. if (strpos($lang, ';') === false) {
  8331. $langs[(string)$order] = $lang;
  8332. $order = $order-0.01;
  8333. } else {
  8334. $parts = explode(';', $lang);
  8335. $pos = strpos($parts[1], '=');
  8336. $langs[substr($parts[1], $pos+1)] = $parts[0];
  8337. }
  8338. }
  8339. krsort($langs, SORT_NUMERIC);
  8340. /// Look for such langs under standard locations
  8341. foreach ($langs as $lang) {
  8342. $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
  8343. if (get_string_manager()->translation_exists($lang, false)) {
  8344. $SESSION->lang = $lang; /// Lang exists, set it in session
  8345. break; /// We have finished. Go out
  8346. }
  8347. }
  8348. return;
  8349. }
  8350. /**
  8351. * check if $url matches anything in proxybypass list
  8352. *
  8353. * any errors just result in the proxy being used (least bad)
  8354. *
  8355. * @global object
  8356. * @param string $url url to check
  8357. * @return boolean true if we should bypass the proxy
  8358. */
  8359. function is_proxybypass( $url ) {
  8360. global $CFG;
  8361. // sanity check
  8362. if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
  8363. return false;
  8364. }
  8365. // get the host part out of the url
  8366. if (!$host = parse_url( $url, PHP_URL_HOST )) {
  8367. return false;
  8368. }
  8369. // get the possible bypass hosts into an array
  8370. $matches = explode( ',', $CFG->proxybypass );
  8371. // check for a match
  8372. // (IPs need to match the left hand side and hosts the right of the url,
  8373. // but we can recklessly check both as there can't be a false +ve)
  8374. $bypass = false;
  8375. foreach ($matches as $match) {
  8376. $match = trim($match);
  8377. // try for IP match (Left side)
  8378. $lhs = substr($host,0,strlen($match));
  8379. if (strcasecmp($match,$lhs)==0) {
  8380. return true;
  8381. }
  8382. // try for host match (Right side)
  8383. $rhs = substr($host,-strlen($match));
  8384. if (strcasecmp($match,$rhs)==0) {
  8385. return true;
  8386. }
  8387. }
  8388. // nothing matched.
  8389. return false;
  8390. }
  8391. ////////////////////////////////////////////////////////////////////////////////
  8392. /**
  8393. * Check if the passed navigation is of the new style
  8394. *
  8395. * @param mixed $navigation
  8396. * @return bool true for yes false for no
  8397. */
  8398. function is_newnav($navigation) {
  8399. if (is_array($navigation) && !empty($navigation['newnav'])) {
  8400. return true;
  8401. } else {
  8402. return false;
  8403. }
  8404. }
  8405. /**
  8406. * Checks whether the given variable name is defined as a variable within the given object.
  8407. *
  8408. * This will NOT work with stdClass objects, which have no class variables.
  8409. *
  8410. * @param string $var The variable name
  8411. * @param object $object The object to check
  8412. * @return boolean
  8413. */
  8414. function in_object_vars($var, $object) {
  8415. $class_vars = get_class_vars(get_class($object));
  8416. $class_vars = array_keys($class_vars);
  8417. return in_array($var, $class_vars);
  8418. }
  8419. /**
  8420. * Returns an array without repeated objects.
  8421. * This function is similar to array_unique, but for arrays that have objects as values
  8422. *
  8423. * @param array $array
  8424. * @param bool $keep_key_assoc
  8425. * @return array
  8426. */
  8427. function object_array_unique($array, $keep_key_assoc = true) {
  8428. $duplicate_keys = array();
  8429. $tmp = array();
  8430. foreach ($array as $key=>$val) {
  8431. // convert objects to arrays, in_array() does not support objects
  8432. if (is_object($val)) {
  8433. $val = (array)$val;
  8434. }
  8435. if (!in_array($val, $tmp)) {
  8436. $tmp[] = $val;
  8437. } else {
  8438. $duplicate_keys[] = $key;
  8439. }
  8440. }
  8441. foreach ($duplicate_keys as $key) {
  8442. unset($array[$key]);
  8443. }
  8444. return $keep_key_assoc ? $array : array_values($array);
  8445. }
  8446. /**
  8447. * Returns the language string for the given plugin.
  8448. *
  8449. * @param string $plugin the plugin code name
  8450. * @param string $type the type of plugin (mod, block, filter)
  8451. * @return string The plugin language string
  8452. */
  8453. function get_plugin_name($plugin, $type='mod') {
  8454. $plugin_name = '';
  8455. switch ($type) {
  8456. case 'mod':
  8457. $plugin_name = get_string('modulename', $plugin);
  8458. break;
  8459. case 'blocks':
  8460. $plugin_name = get_string('pluginname', "block_$plugin");
  8461. if (empty($plugin_name) || $plugin_name == '[[pluginname]]') {
  8462. if (($block = block_instance($plugin)) !== false) {
  8463. $plugin_name = $block->get_title();
  8464. } else {
  8465. $plugin_name = "[[$plugin]]";
  8466. }
  8467. }
  8468. break;
  8469. case 'filter':
  8470. $plugin_name = filter_get_name('filter/' . $plugin);
  8471. break;
  8472. default:
  8473. $plugin_name = $plugin;
  8474. break;
  8475. }
  8476. return $plugin_name;
  8477. }
  8478. /**
  8479. * Is a userid the primary administrator?
  8480. *
  8481. * @param int $userid int id of user to check
  8482. * @return boolean
  8483. */
  8484. function is_primary_admin($userid){
  8485. $primaryadmin = get_admin();
  8486. if($userid == $primaryadmin->id){
  8487. return true;
  8488. }else{
  8489. return false;
  8490. }
  8491. }
  8492. /**
  8493. * Returns the site identifier
  8494. *
  8495. * @global object
  8496. * @return string $CFG->siteidentifier, first making sure it is properly initialised.
  8497. */
  8498. function get_site_identifier() {
  8499. global $CFG;
  8500. // Check to see if it is missing. If so, initialise it.
  8501. if (empty($CFG->siteidentifier)) {
  8502. set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
  8503. }
  8504. // Return it.
  8505. return $CFG->siteidentifier;
  8506. }
  8507. /**
  8508. * Check whether the given password has no more than the specified
  8509. * number of consecutive identical characters.
  8510. *
  8511. * @param string $password password to be checked against the password policy
  8512. * @param integer $maxchars maximum number of consecutive identical characters
  8513. */
  8514. function check_consecutive_identical_characters($password, $maxchars) {
  8515. if ($maxchars < 1) {
  8516. return true; // 0 is to disable this check
  8517. }
  8518. if (strlen($password) <= $maxchars) {
  8519. return true; // too short to fail this test
  8520. }
  8521. $previouschar = '';
  8522. $consecutivecount = 1;
  8523. foreach (str_split($password) as $char) {
  8524. if ($char != $previouschar) {
  8525. $consecutivecount = 1;
  8526. }
  8527. else {
  8528. $consecutivecount++;
  8529. if ($consecutivecount > $maxchars) {
  8530. return false; // check failed already
  8531. }
  8532. }
  8533. $previouschar = $char;
  8534. }
  8535. return true;
  8536. }
  8537. /**
  8538. * helper function to do partial function binding
  8539. * so we can use it for preg_replace_callback, for example
  8540. * this works with php functions, user functions, static methods and class methods
  8541. * it returns you a callback that you can pass on like so:
  8542. *
  8543. * $callback = partial('somefunction', $arg1, $arg2);
  8544. * or
  8545. * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
  8546. * or even
  8547. * $obj = new someclass();
  8548. * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
  8549. *
  8550. * and then the arguments that are passed through at calltime are appended to the argument list.
  8551. *
  8552. * @param mixed $function a php callback
  8553. * $param mixed $arg1.. $argv arguments to partially bind with
  8554. *
  8555. * @return callback
  8556. */
  8557. function partial() {
  8558. if (!class_exists('partial')) {
  8559. class partial{
  8560. var $values = array();
  8561. var $func;
  8562. function __construct($func, $args) {
  8563. $this->values = $args;
  8564. $this->func = $func;
  8565. }
  8566. function method() {
  8567. $args = func_get_args();
  8568. return call_user_func_array($this->func, array_merge($this->values, $args));
  8569. }
  8570. }
  8571. }
  8572. $args = func_get_args();
  8573. $func = array_shift($args);
  8574. $p = new partial($func, $args);
  8575. return array($p, 'method');
  8576. }
  8577. /**
  8578. * helper function to load up and initialise the mnet environment
  8579. * this must be called before you use mnet functions.
  8580. *
  8581. * @return mnet_environment the equivalent of old $MNET global
  8582. */
  8583. function get_mnet_environment() {
  8584. global $CFG;
  8585. require_once($CFG->dirroot . '/mnet/lib.php');
  8586. static $instance = null;
  8587. if (empty($instance)) {
  8588. $instance = new mnet_environment();
  8589. $instance->init();
  8590. }
  8591. return $instance;
  8592. }
  8593. /**
  8594. * during xmlrpc server code execution, any code wishing to access
  8595. * information about the remote peer must use this to get it.
  8596. *
  8597. * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
  8598. */
  8599. function get_mnet_remote_client() {
  8600. if (!defined('MNET_SERVER')) {
  8601. debugging(get_string('notinxmlrpcserver', 'mnet'));
  8602. return false;
  8603. }
  8604. global $MNET_REMOTE_CLIENT;
  8605. if (isset($MNET_REMOTE_CLIENT)) {
  8606. return $MNET_REMOTE_CLIENT;
  8607. }
  8608. return false;
  8609. }
  8610. /**
  8611. * during the xmlrpc server code execution, this will be called
  8612. * to setup the object returned by {@see get_mnet_remote_client}
  8613. *
  8614. * @param mnet_remote_client $client the client to set up
  8615. */
  8616. function set_mnet_remote_client($client) {
  8617. if (!defined('MNET_SERVER')) {
  8618. throw new moodle_exception('notinxmlrpcserver', 'mnet');
  8619. }
  8620. global $MNET_REMOTE_CLIENT;
  8621. $MNET_REMOTE_CLIENT = $client;
  8622. }
  8623. /**
  8624. * return the jump url for a given remote user
  8625. * this is used for rewriting forum post links in emails, etc
  8626. *
  8627. * @param stdclass $user the user to get the idp url for
  8628. */
  8629. function mnet_get_idp_jump_url($user) {
  8630. global $CFG;
  8631. static $mnetjumps = array();
  8632. if (!array_key_exists($user->mnethostid, $mnetjumps)) {
  8633. $idp = mnet_get_peer_host($user->mnethostid);
  8634. $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
  8635. $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
  8636. }
  8637. return $mnetjumps[$user->mnethostid];
  8638. }
  8639. /**
  8640. * Gets the homepage to use for the current user
  8641. *
  8642. * @return int One of HOMEPAGE_*
  8643. */
  8644. function get_home_page() {
  8645. global $CFG;
  8646. if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
  8647. if ($CFG->defaulthomepage == HOMEPAGE_MY) {
  8648. return HOMEPAGE_MY;
  8649. } else {
  8650. return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
  8651. }
  8652. }
  8653. return HOMEPAGE_SITE;
  8654. }