Merge branch 'MDL-27293-customlang-timeout_20_STABLE' of git://github.com/mudrd8mz...
[moodle.git] / lib / moodlelib.php
blobccc1e8da115622fdc260c4c5095ae5533bd85302
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * moodlelib.php - Moodle main library
21 * Main library file of miscellaneous general-purpose Moodle functions.
22 * Other main libraries:
23 * - weblib.php - functions that produce web output
24 * - datalib.php - functions that access the database
26 * @package core
27 * @subpackage lib
28 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32 defined('MOODLE_INTERNAL') || die();
34 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
36 /// Date and time constants ///
37 /**
38 * Time constant - the number of seconds in a year
40 define('YEARSECS', 31536000);
42 /**
43 * Time constant - the number of seconds in a week
45 define('WEEKSECS', 604800);
47 /**
48 * Time constant - the number of seconds in a day
50 define('DAYSECS', 86400);
52 /**
53 * Time constant - the number of seconds in an hour
55 define('HOURSECS', 3600);
57 /**
58 * Time constant - the number of seconds in a minute
60 define('MINSECS', 60);
62 /**
63 * Time constant - the number of minutes in a day
65 define('DAYMINS', 1440);
67 /**
68 * Time constant - the number of minutes in an hour
70 define('HOURMINS', 60);
72 /// Parameter constants - every call to optional_param(), required_param() ///
73 /// or clean_param() should have a specified type of parameter. //////////////
77 /**
78 * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
80 define('PARAM_ALPHA', 'alpha');
82 /**
83 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "_-" allowed
84 * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
86 define('PARAM_ALPHAEXT', 'alphaext');
88 /**
89 * PARAM_ALPHANUM - expected numbers and letters only.
91 define('PARAM_ALPHANUM', 'alphanum');
93 /**
94 * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
96 define('PARAM_ALPHANUMEXT', 'alphanumext');
98 /**
99 * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
101 define('PARAM_AUTH', 'auth');
104 * PARAM_BASE64 - Base 64 encoded format
106 define('PARAM_BASE64', 'base64');
109 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
111 define('PARAM_BOOL', 'bool');
114 * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
115 * checked against the list of capabilities in the database.
117 define('PARAM_CAPABILITY', 'capability');
120 * PARAM_CLEANHTML - cleans submitted HTML code. use only for text in HTML format. This cleaning may fix xhtml strictness too.
122 define('PARAM_CLEANHTML', 'cleanhtml');
125 * PARAM_EMAIL - an email address following the RFC
127 define('PARAM_EMAIL', 'email');
130 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
132 define('PARAM_FILE', 'file');
135 * PARAM_FLOAT - a real/floating point number.
137 define('PARAM_FLOAT', 'float');
140 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
142 define('PARAM_HOST', 'host');
145 * PARAM_INT - integers only, use when expecting only numbers.
147 define('PARAM_INT', 'int');
150 * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
152 define('PARAM_LANG', 'lang');
155 * 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!)
157 define('PARAM_LOCALURL', 'localurl');
160 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
162 define('PARAM_NOTAGS', 'notags');
165 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
166 * note: the leading slash is not removed, window drive letter is not allowed
168 define('PARAM_PATH', 'path');
171 * PARAM_PEM - Privacy Enhanced Mail format
173 define('PARAM_PEM', 'pem');
176 * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
178 define('PARAM_PERMISSION', 'permission');
181 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way
183 define('PARAM_RAW', 'raw');
186 * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
188 define('PARAM_RAW_TRIMMED', 'raw_trimmed');
191 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
193 define('PARAM_SAFEDIR', 'safedir');
196 * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
198 define('PARAM_SAFEPATH', 'safepath');
201 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
203 define('PARAM_SEQUENCE', 'sequence');
206 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
208 define('PARAM_TAG', 'tag');
211 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
213 define('PARAM_TAGLIST', 'taglist');
216 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
218 define('PARAM_TEXT', 'text');
221 * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
223 define('PARAM_THEME', 'theme');
226 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
228 define('PARAM_URL', 'url');
231 * 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!!
233 define('PARAM_USERNAME', 'username');
236 * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
238 define('PARAM_STRINGID', 'stringid');
240 ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
242 * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
243 * It was one of the first types, that is why it is abused so much ;-)
244 * @deprecated since 2.0
246 define('PARAM_CLEAN', 'clean');
249 * PARAM_INTEGER - deprecated alias for PARAM_INT
251 define('PARAM_INTEGER', 'int');
254 * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
256 define('PARAM_NUMBER', 'float');
259 * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
260 * NOTE: originally alias for PARAM_APLHA
262 define('PARAM_ACTION', 'alphanumext');
265 * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
266 * NOTE: originally alias for PARAM_APLHA
268 define('PARAM_FORMAT', 'alphanumext');
271 * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
273 define('PARAM_MULTILANG', 'text');
276 * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
278 define('PARAM_CLEANFILE', 'file');
280 /// Web Services ///
283 * VALUE_REQUIRED - if the parameter is not supplied, there is an error
285 define('VALUE_REQUIRED', 1);
288 * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
290 define('VALUE_OPTIONAL', 2);
293 * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
295 define('VALUE_DEFAULT', 0);
298 * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
300 define('NULL_NOT_ALLOWED', false);
303 * NULL_ALLOWED - the parameter can be set to null in the database
305 define('NULL_ALLOWED', true);
307 /// Page types ///
309 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
311 define('PAGE_COURSE_VIEW', 'course-view');
313 /** Get remote addr constant */
314 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
315 /** Get remote addr constant */
316 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
318 /// Blog access level constant declaration ///
319 define ('BLOG_USER_LEVEL', 1);
320 define ('BLOG_GROUP_LEVEL', 2);
321 define ('BLOG_COURSE_LEVEL', 3);
322 define ('BLOG_SITE_LEVEL', 4);
323 define ('BLOG_GLOBAL_LEVEL', 5);
326 ///Tag constants///
328 * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
329 * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
330 * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
332 * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
334 define('TAG_MAX_LENGTH', 50);
336 /// Password policy constants ///
337 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
338 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
339 define ('PASSWORD_DIGITS', '0123456789');
340 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
342 /// Feature constants ///
343 // Used for plugin_supports() to report features that are, or are not, supported by a module.
345 /** True if module can provide a grade */
346 define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
347 /** True if module supports outcomes */
348 define('FEATURE_GRADE_OUTCOMES', 'outcomes');
350 /** True if module has code to track whether somebody viewed it */
351 define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
352 /** True if module has custom completion rules */
353 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
355 /** True if module has no 'view' page (like label) */
356 define('FEATURE_NO_VIEW_LINK', 'viewlink');
357 /** True if module supports outcomes */
358 define('FEATURE_IDNUMBER', 'idnumber');
359 /** True if module supports groups */
360 define('FEATURE_GROUPS', 'groups');
361 /** True if module supports groupings */
362 define('FEATURE_GROUPINGS', 'groupings');
363 /** True if module supports groupmembersonly */
364 define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
366 /** Type of module */
367 define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
368 /** True if module supports intro editor */
369 define('FEATURE_MOD_INTRO', 'mod_intro');
370 /** True if module has default completion */
371 define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
373 define('FEATURE_COMMENT', 'comment');
375 define('FEATURE_RATE', 'rate');
376 /** True if module supports backup/restore of moodle2 format */
377 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
379 /** Unspecified module archetype */
380 define('MOD_ARCHETYPE_OTHER', 0);
381 /** Resource-like type module */
382 define('MOD_ARCHETYPE_RESOURCE', 1);
383 /** Assignment module archetype */
384 define('MOD_ARCHETYPE_ASSIGNMENT', 2);
387 * Security token used for allowing access
388 * from external application such as web services.
389 * Scripts do not use any session, performance is relatively
390 * low because we need to load access info in each request.
391 * Scripts are executed in parallel.
393 define('EXTERNAL_TOKEN_PERMANENT', 0);
396 * Security token used for allowing access
397 * of embedded applications, the code is executed in the
398 * active user session. Token is invalidated after user logs out.
399 * Scripts are executed serially - normal session locking is used.
401 define('EXTERNAL_TOKEN_EMBEDDED', 1);
404 * The home page should be the site home
406 define('HOMEPAGE_SITE', 0);
408 * The home page should be the users my page
410 define('HOMEPAGE_MY', 1);
412 * The home page can be chosen by the user
414 define('HOMEPAGE_USER', 2);
417 * Hub directory url (should be moodle.org)
419 define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
423 * Moodle.org url (should be moodle.org)
425 define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
428 /// PARAMETER HANDLING ////////////////////////////////////////////////////
431 * Returns a particular value for the named variable, taken from
432 * POST or GET. If the parameter doesn't exist then an error is
433 * thrown because we require this variable.
435 * This function should be used to initialise all required values
436 * in a script that are based on parameters. Usually it will be
437 * used like this:
438 * $id = required_param('id', PARAM_INT);
440 * Please note the $type parameter is now required,
441 * for now PARAM_CLEAN is used for backwards compatibility only.
443 * @param string $parname the name of the page parameter we want
444 * @param string $type expected type of parameter
445 * @return mixed
447 function required_param($parname, $type) {
448 if (!isset($type)) {
449 debugging('required_param() requires $type to be specified.');
450 $type = PARAM_CLEAN; // for now let's use this deprecated type
452 if (isset($_POST[$parname])) { // POST has precedence
453 $param = $_POST[$parname];
454 } else if (isset($_GET[$parname])) {
455 $param = $_GET[$parname];
456 } else {
457 print_error('missingparam', '', '', $parname);
460 return clean_param($param, $type);
464 * Returns a particular value for the named variable, taken from
465 * POST or GET, otherwise returning a given default.
467 * This function should be used to initialise all optional values
468 * in a script that are based on parameters. Usually it will be
469 * used like this:
470 * $name = optional_param('name', 'Fred', PARAM_TEXT);
472 * Please note $default and $type parameters are now required,
473 * for now PARAM_CLEAN is used for backwards compatibility only.
475 * @param string $parname the name of the page parameter we want
476 * @param mixed $default the default value to return if nothing is found
477 * @param string $type expected type of parameter
478 * @return mixed
480 function optional_param($parname, $default, $type) {
481 if (!isset($type)) {
482 debugging('optional_param() requires $default and $type to be specified.');
483 $type = PARAM_CLEAN; // for now let's use this deprecated type
485 if (!isset($default)) {
486 $default = null;
489 if (isset($_POST[$parname])) { // POST has precedence
490 $param = $_POST[$parname];
491 } else if (isset($_GET[$parname])) {
492 $param = $_GET[$parname];
493 } else {
494 return $default;
497 return clean_param($param, $type);
501 * Strict validation of parameter values, the values are only converted
502 * to requested PHP type. Internally it is using clean_param, the values
503 * before and after cleaning must be equal - otherwise
504 * an invalid_parameter_exception is thrown.
505 * Objects and classes are not accepted.
507 * @param mixed $param
508 * @param int $type PARAM_ constant
509 * @param bool $allownull are nulls valid value?
510 * @param string $debuginfo optional debug information
511 * @return mixed the $param value converted to PHP type or invalid_parameter_exception
513 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
514 if (is_null($param)) {
515 if ($allownull == NULL_ALLOWED) {
516 return null;
517 } else {
518 throw new invalid_parameter_exception($debuginfo);
521 if (is_array($param) or is_object($param)) {
522 throw new invalid_parameter_exception($debuginfo);
525 $cleaned = clean_param($param, $type);
526 if ((string)$param !== (string)$cleaned) {
527 // conversion to string is usually lossless
528 throw new invalid_parameter_exception($debuginfo);
531 return $cleaned;
535 * Used by {@link optional_param()} and {@link required_param()} to
536 * clean the variables and/or cast to specific types, based on
537 * an options field.
538 * <code>
539 * $course->format = clean_param($course->format, PARAM_ALPHA);
540 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
541 * </code>
543 * @param mixed $param the variable we are cleaning
544 * @param int $type expected format of param after cleaning.
545 * @return mixed
547 function clean_param($param, $type) {
549 global $CFG;
551 if (is_array($param)) { // Let's loop
552 $newparam = array();
553 foreach ($param as $key => $value) {
554 $newparam[$key] = clean_param($value, $type);
556 return $newparam;
559 switch ($type) {
560 case PARAM_RAW: // no cleaning at all
561 return $param;
563 case PARAM_RAW_TRIMMED: // no cleaning, but strip leading and trailing whitespace.
564 return trim($param);
566 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
567 // this is deprecated!, please use more specific type instead
568 if (is_numeric($param)) {
569 return $param;
571 return clean_text($param); // Sweep for scripts, etc
573 case PARAM_CLEANHTML: // clean html fragment
574 $param = clean_text($param, FORMAT_HTML); // Sweep for scripts, etc
575 return trim($param);
577 case PARAM_INT:
578 return (int)$param; // Convert to integer
580 case PARAM_FLOAT:
581 case PARAM_NUMBER:
582 return (float)$param; // Convert to float
584 case PARAM_ALPHA: // Remove everything not a-z
585 return preg_replace('/[^a-zA-Z]/i', '', $param);
587 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z_- (originally allowed "/" too)
588 return preg_replace('/[^a-zA-Z_-]/i', '', $param);
590 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
591 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
593 case PARAM_ALPHANUMEXT: // Remove everything not a-zA-Z0-9_-
594 return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
596 case PARAM_SEQUENCE: // Remove everything not 0-9,
597 return preg_replace('/[^0-9,]/i', '', $param);
599 case PARAM_BOOL: // Convert to 1 or 0
600 $tempstr = strtolower($param);
601 if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
602 $param = 1;
603 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
604 $param = 0;
605 } else {
606 $param = empty($param) ? 0 : 1;
608 return $param;
610 case PARAM_NOTAGS: // Strip all tags
611 return strip_tags($param);
613 case PARAM_TEXT: // leave only tags needed for multilang
614 // if the multilang syntax is not correct we strip all tags
615 // because it would break xhtml strict which is required for accessibility standards
616 // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
617 do {
618 if (strpos($param, '</lang>') !== false) {
619 // old and future mutilang syntax
620 $param = strip_tags($param, '<lang>');
621 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
622 break;
624 $open = false;
625 foreach ($matches[0] as $match) {
626 if ($match === '</lang>') {
627 if ($open) {
628 $open = false;
629 continue;
630 } else {
631 break 2;
634 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
635 break 2;
636 } else {
637 $open = true;
640 if ($open) {
641 break;
643 return $param;
645 } else if (strpos($param, '</span>') !== false) {
646 // current problematic multilang syntax
647 $param = strip_tags($param, '<span>');
648 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
649 break;
651 $open = false;
652 foreach ($matches[0] as $match) {
653 if ($match === '</span>') {
654 if ($open) {
655 $open = false;
656 continue;
657 } else {
658 break 2;
661 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
662 break 2;
663 } else {
664 $open = true;
667 if ($open) {
668 break;
670 return $param;
672 } while (false);
673 // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
674 return strip_tags($param);
676 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
677 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
679 case PARAM_SAFEPATH: // Remove everything not a-zA-Z0-9/_-
680 return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
682 case PARAM_FILE: // Strip all suspicious characters from filename
683 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
684 $param = preg_replace('~\.\.+~', '', $param);
685 if ($param === '.') {
686 $param = '';
688 return $param;
690 case PARAM_PATH: // Strip all suspicious characters from file path
691 $param = str_replace('\\', '/', $param);
692 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
693 $param = preg_replace('~\.\.+~', '', $param);
694 $param = preg_replace('~//+~', '/', $param);
695 return preg_replace('~/(\./)+~', '/', $param);
697 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
698 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
699 // match ipv4 dotted quad
700 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
701 // confirm values are ok
702 if ( $match[0] > 255
703 || $match[1] > 255
704 || $match[3] > 255
705 || $match[4] > 255 ) {
706 // hmmm, what kind of dotted quad is this?
707 $param = '';
709 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
710 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
711 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
713 // all is ok - $param is respected
714 } else {
715 // all is not ok...
716 $param='';
718 return $param;
720 case PARAM_URL: // allow safe ftp, http, mailto urls
721 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
722 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
723 // all is ok, param is respected
724 } else {
725 $param =''; // not really ok
727 return $param;
729 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
730 $param = clean_param($param, PARAM_URL);
731 if (!empty($param)) {
732 if (preg_match(':^/:', $param)) {
733 // root-relative, ok!
734 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
735 // absolute, and matches our wwwroot
736 } else {
737 // relative - let's make sure there are no tricks
738 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
739 // looks ok.
740 } else {
741 $param = '';
745 return $param;
747 case PARAM_PEM:
748 $param = trim($param);
749 // PEM formatted strings may contain letters/numbers and the symbols
750 // forward slash: /
751 // plus sign: +
752 // equal sign: =
753 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
754 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
755 list($wholething, $body) = $matches;
756 unset($wholething, $matches);
757 $b64 = clean_param($body, PARAM_BASE64);
758 if (!empty($b64)) {
759 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
760 } else {
761 return '';
764 return '';
766 case PARAM_BASE64:
767 if (!empty($param)) {
768 // PEM formatted strings may contain letters/numbers and the symbols
769 // forward slash: /
770 // plus sign: +
771 // equal sign: =
772 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
773 return '';
775 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
776 // Each line of base64 encoded data must be 64 characters in
777 // length, except for the last line which may be less than (or
778 // equal to) 64 characters long.
779 for ($i=0, $j=count($lines); $i < $j; $i++) {
780 if ($i + 1 == $j) {
781 if (64 < strlen($lines[$i])) {
782 return '';
784 continue;
787 if (64 != strlen($lines[$i])) {
788 return '';
791 return implode("\n",$lines);
792 } else {
793 return '';
796 case PARAM_TAG:
797 // Please note it is not safe to use the tag name directly anywhere,
798 // it must be processed with s(), urlencode() before embedding anywhere.
799 // remove some nasties
800 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
801 //convert many whitespace chars into one
802 $param = preg_replace('/\s+/', ' ', $param);
803 $textlib = textlib_get_instance();
804 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
805 return $param;
807 case PARAM_TAGLIST:
808 $tags = explode(',', $param);
809 $result = array();
810 foreach ($tags as $tag) {
811 $res = clean_param($tag, PARAM_TAG);
812 if ($res !== '') {
813 $result[] = $res;
816 if ($result) {
817 return implode(',', $result);
818 } else {
819 return '';
822 case PARAM_CAPABILITY:
823 if (get_capability_info($param)) {
824 return $param;
825 } else {
826 return '';
829 case PARAM_PERMISSION:
830 $param = (int)$param;
831 if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
832 return $param;
833 } else {
834 return CAP_INHERIT;
837 case PARAM_AUTH:
838 $param = clean_param($param, PARAM_SAFEDIR);
839 if (exists_auth_plugin($param)) {
840 return $param;
841 } else {
842 return '';
845 case PARAM_LANG:
846 $param = clean_param($param, PARAM_SAFEDIR);
847 if (get_string_manager()->translation_exists($param)) {
848 return $param;
849 } else {
850 return ''; // Specified language is not installed or param malformed
853 case PARAM_THEME:
854 $param = clean_param($param, PARAM_SAFEDIR);
855 if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
856 return $param;
857 } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
858 return $param;
859 } else {
860 return ''; // Specified theme is not installed
863 case PARAM_USERNAME:
864 $param = str_replace(" " , "", $param);
865 $param = moodle_strtolower($param); // Convert uppercase to lowercase MDL-16919
866 if (empty($CFG->extendedusernamechars)) {
867 // regular expression, eliminate all chars EXCEPT:
868 // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
869 $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
871 return $param;
873 case PARAM_EMAIL:
874 if (validate_email($param)) {
875 return $param;
876 } else {
877 return '';
880 case PARAM_STRINGID:
881 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
882 return $param;
883 } else {
884 return '';
887 default: // throw error, switched parameters in optional_param or another serious problem
888 print_error("unknownparamtype", '', '', $type);
893 * Return true if given value is integer or string with integer value
895 * @param mixed $value String or Int
896 * @return bool true if number, false if not
898 function is_number($value) {
899 if (is_int($value)) {
900 return true;
901 } else if (is_string($value)) {
902 return ((string)(int)$value) === $value;
903 } else {
904 return false;
909 * Returns host part from url
910 * @param string $url full url
911 * @return string host, null if not found
913 function get_host_from_url($url) {
914 preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
915 if ($matches) {
916 return $matches[1];
918 return null;
922 * Tests whether anything was returned by text editor
924 * This function is useful for testing whether something you got back from
925 * the HTML editor actually contains anything. Sometimes the HTML editor
926 * appear to be empty, but actually you get back a <br> tag or something.
928 * @param string $string a string containing HTML.
929 * @return boolean does the string contain any actual content - that is text,
930 * images, objects, etc.
932 function html_is_blank($string) {
933 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
937 * Set a key in global configuration
939 * Set a key/value pair in both this session's {@link $CFG} global variable
940 * and in the 'config' database table for future sessions.
942 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
943 * In that case it doesn't affect $CFG.
945 * A NULL value will delete the entry.
947 * @global object
948 * @global object
949 * @param string $name the key to set
950 * @param string $value the value to set (without magic quotes)
951 * @param string $plugin (optional) the plugin scope, default NULL
952 * @return bool true or exception
954 function set_config($name, $value, $plugin=NULL) {
955 global $CFG, $DB;
957 if (empty($plugin)) {
958 if (!array_key_exists($name, $CFG->config_php_settings)) {
959 // So it's defined for this invocation at least
960 if (is_null($value)) {
961 unset($CFG->$name);
962 } else {
963 $CFG->$name = (string)$value; // settings from db are always strings
967 if ($DB->get_field('config', 'name', array('name'=>$name))) {
968 if ($value === null) {
969 $DB->delete_records('config', array('name'=>$name));
970 } else {
971 $DB->set_field('config', 'value', $value, array('name'=>$name));
973 } else {
974 if ($value !== null) {
975 $config = new stdClass();
976 $config->name = $name;
977 $config->value = $value;
978 $DB->insert_record('config', $config, false);
982 } else { // plugin scope
983 if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
984 if ($value===null) {
985 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
986 } else {
987 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
989 } else {
990 if ($value !== null) {
991 $config = new stdClass();
992 $config->plugin = $plugin;
993 $config->name = $name;
994 $config->value = $value;
995 $DB->insert_record('config_plugins', $config, false);
1000 return true;
1004 * Get configuration values from the global config table
1005 * or the config_plugins table.
1007 * If called with one parameter, it will load all the config
1008 * variables for one plugin, and return them as an object.
1010 * If called with 2 parameters it will return a string single
1011 * value or false if the value is not found.
1013 * @param string $plugin full component name
1014 * @param string $name default NULL
1015 * @return mixed hash-like object or single value, return false no config found
1017 function get_config($plugin, $name = NULL) {
1018 global $CFG, $DB;
1020 // normalise component name
1021 if ($plugin === 'moodle' or $plugin === 'core') {
1022 $plugin = NULL;
1025 if (!empty($name)) { // the user is asking for a specific value
1026 if (!empty($plugin)) {
1027 if (isset($CFG->forced_plugin_settings[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings[$plugin])) {
1028 // setting forced in config file
1029 return $CFG->forced_plugin_settings[$plugin][$name];
1030 } else {
1031 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1033 } else {
1034 if (array_key_exists($name, $CFG->config_php_settings)) {
1035 // setting force in config file
1036 return $CFG->config_php_settings[$name];
1037 } else {
1038 return $DB->get_field('config', 'value', array('name'=>$name));
1043 // the user is after a recordset
1044 if ($plugin) {
1045 $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
1046 if (isset($CFG->forced_plugin_settings[$plugin])) {
1047 foreach($CFG->forced_plugin_settings[$plugin] as $n=>$v) {
1048 if (is_null($v) or is_array($v) or is_object($v)) {
1049 // we do not want any extra mess here, just real settings that could be saved in db
1050 unset($localcfg[$n]);
1051 } else {
1052 //convert to string as if it went through the DB
1053 $localcfg[$n] = (string)$v;
1057 if ($localcfg) {
1058 return (object)$localcfg;
1059 } else {
1060 return null;
1063 } else {
1064 // this part is not really used any more, but anyway...
1065 $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
1066 foreach($CFG->config_php_settings as $n=>$v) {
1067 if (is_null($v) or is_array($v) or is_object($v)) {
1068 // we do not want any extra mess here, just real settings that could be saved in db
1069 unset($localcfg[$n]);
1070 } else {
1071 //convert to string as if it went through the DB
1072 $localcfg[$n] = (string)$v;
1075 return (object)$localcfg;
1080 * Removes a key from global configuration
1082 * @param string $name the key to set
1083 * @param string $plugin (optional) the plugin scope
1084 * @global object
1085 * @return boolean whether the operation succeeded.
1087 function unset_config($name, $plugin=NULL) {
1088 global $CFG, $DB;
1090 if (empty($plugin)) {
1091 unset($CFG->$name);
1092 $DB->delete_records('config', array('name'=>$name));
1093 } else {
1094 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1097 return true;
1101 * Remove all the config variables for a given plugin.
1103 * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1104 * @return boolean whether the operation succeeded.
1106 function unset_all_config_for_plugin($plugin) {
1107 global $DB;
1108 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1109 $DB->delete_records_select('config', 'name LIKE ?', array($plugin . '_%'));
1110 return true;
1114 * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1116 * All users are verified if they still have the necessary capability.
1118 * @param string $value the value of the config setting.
1119 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1120 * @param bool $include admins, include administrators
1121 * @return array of user objects.
1123 function get_users_from_config($value, $capability, $includeadmins = true) {
1124 global $CFG, $DB;
1126 if (empty($value) or $value === '$@NONE@$') {
1127 return array();
1130 // we have to make sure that users still have the necessary capability,
1131 // it should be faster to fetch them all first and then test if they are present
1132 // instead of validating them one-by-one
1133 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
1134 if ($includeadmins) {
1135 $admins = get_admins();
1136 foreach ($admins as $admin) {
1137 $users[$admin->id] = $admin;
1141 if ($value === '$@ALL@$') {
1142 return $users;
1145 $result = array(); // result in correct order
1146 $allowed = explode(',', $value);
1147 foreach ($allowed as $uid) {
1148 if (isset($users[$uid])) {
1149 $user = $users[$uid];
1150 $result[$user->id] = $user;
1154 return $result;
1159 * Invalidates browser caches and cached data in temp
1160 * @return void
1162 function purge_all_caches() {
1163 global $CFG;
1165 reset_text_filters_cache();
1166 js_reset_all_caches();
1167 theme_reset_all_caches();
1168 get_string_manager()->reset_caches();
1170 // purge all other caches: rss, simplepie, etc.
1171 remove_dir($CFG->dataroot.'/cache', true);
1173 // make sure cache dir is writable, throws exception if not
1174 make_upload_directory('cache');
1176 clearstatcache();
1180 * Get volatile flags
1182 * @param string $type
1183 * @param int $changedsince default null
1184 * @return records array
1186 function get_cache_flags($type, $changedsince=NULL) {
1187 global $DB;
1189 $params = array('type'=>$type, 'expiry'=>time());
1190 $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1191 if ($changedsince !== NULL) {
1192 $params['changedsince'] = $changedsince;
1193 $sqlwhere .= " AND timemodified > :changedsince";
1195 $cf = array();
1197 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1198 foreach ($flags as $flag) {
1199 $cf[$flag->name] = $flag->value;
1202 return $cf;
1206 * Get volatile flags
1208 * @param string $type
1209 * @param string $name
1210 * @param int $changedsince default null
1211 * @return records array
1213 function get_cache_flag($type, $name, $changedsince=NULL) {
1214 global $DB;
1216 $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
1218 $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1219 if ($changedsince !== NULL) {
1220 $params['changedsince'] = $changedsince;
1221 $sqlwhere .= " AND timemodified > :changedsince";
1224 return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1228 * Set a volatile flag
1230 * @param string $type the "type" namespace for the key
1231 * @param string $name the key to set
1232 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
1233 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1234 * @return bool Always returns true
1236 function set_cache_flag($type, $name, $value, $expiry=NULL) {
1237 global $DB;
1239 $timemodified = time();
1240 if ($expiry===NULL || $expiry < $timemodified) {
1241 $expiry = $timemodified + 24 * 60 * 60;
1242 } else {
1243 $expiry = (int)$expiry;
1246 if ($value === NULL) {
1247 unset_cache_flag($type,$name);
1248 return true;
1251 if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE)) { // this is a potential problem in DEBUG_DEVELOPER
1252 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
1253 return true; //no need to update; helps rcache too
1255 $f->value = $value;
1256 $f->expiry = $expiry;
1257 $f->timemodified = $timemodified;
1258 $DB->update_record('cache_flags', $f);
1259 } else {
1260 $f = new stdClass();
1261 $f->flagtype = $type;
1262 $f->name = $name;
1263 $f->value = $value;
1264 $f->expiry = $expiry;
1265 $f->timemodified = $timemodified;
1266 $DB->insert_record('cache_flags', $f);
1268 return true;
1272 * Removes a single volatile flag
1274 * @global object
1275 * @param string $type the "type" namespace for the key
1276 * @param string $name the key to set
1277 * @return bool
1279 function unset_cache_flag($type, $name) {
1280 global $DB;
1281 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1282 return true;
1286 * Garbage-collect volatile flags
1288 * @return bool Always returns true
1290 function gc_cache_flags() {
1291 global $DB;
1292 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1293 return true;
1296 /// FUNCTIONS FOR HANDLING USER PREFERENCES ////////////////////////////////////
1299 * Refresh user preference cache. This is used most often for $USER
1300 * object that is stored in session, but it also helps with performance in cron script.
1302 * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1304 * @param stdClass $user user object, preferences are preloaded into ->preference property
1305 * @param int $cachelifetime cache life time on the current page (ins seconds)
1306 * @return void
1308 function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
1309 global $DB;
1310 static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
1312 if (!isset($user->id)) {
1313 throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1316 if (empty($user->id) or isguestuser($user->id)) {
1317 // No permanent storage for not-logged-in users and guest
1318 if (!isset($user->preference)) {
1319 $user->preference = array();
1321 return;
1324 $timenow = time();
1326 if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
1327 // Already loaded at least once on this page. Are we up to date?
1328 if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
1329 // no need to reload - we are on the same page and we loaded prefs just a moment ago
1330 return;
1332 } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
1333 // no change since the lastcheck on this page
1334 $user->preference['_lastloaded'] = $timenow;
1335 return;
1339 // OK, so we have to reload all preferences
1340 $loadedusers[$user->id] = true;
1341 $user->preference = $DB->get_records_menu('user_preferences', array('userid'=>$user->id), '', 'name,value'); // All values
1342 $user->preference['_lastloaded'] = $timenow;
1346 * Called from set/delete_user_preferences, so that the prefs can
1347 * be correctly reloaded in different sessions.
1349 * NOTE: internal function, do not call from other code.
1351 * @param integer $userid the user whose prefs were changed.
1352 * @return void
1354 function mark_user_preferences_changed($userid) {
1355 global $CFG;
1357 if (empty($userid) or isguestuser($userid)) {
1358 // no cache flags for guest and not-logged-in users
1359 return;
1362 set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
1366 * Sets a preference for the specified user.
1368 * If user object submitted, 'preference' property contains the preferences cache.
1370 * @param string $name The key to set as preference for the specified user
1371 * @param string $value The value to set for the $name key in the specified user's record,
1372 * null means delete current value
1373 * @param stdClass|int $user A moodle user object or id, null means current user
1374 * @return bool always true or exception
1376 function set_user_preference($name, $value, $user = null) {
1377 global $USER, $DB;
1379 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1380 throw new coding_exception('Invalid preference name in set_user_preference() call');
1383 if (is_null($value)) {
1384 // null means delete current
1385 return unset_user_preference($name, $user);
1386 } else if (is_object($value)) {
1387 throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1388 } else if (is_array($value)) {
1389 throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1391 $value = (string)$value;
1393 if (is_null($user)) {
1394 $user = $USER;
1395 } else if (isset($user->id)) {
1396 // $user is valid object
1397 } else if (is_numeric($user)) {
1398 $user = (object)array('id'=>(int)$user);
1399 } else {
1400 throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1403 check_user_preferences_loaded($user);
1405 if (empty($user->id) or isguestuser($user->id)) {
1406 // no permanent storage for not-logged-in users and guest
1407 $user->preference[$name] = $value;
1408 return true;
1411 if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>$name))) {
1412 if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
1413 // preference already set to this value
1414 return true;
1416 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id));
1418 } else {
1419 $preference = new stdClass();
1420 $preference->userid = $user->id;
1421 $preference->name = $name;
1422 $preference->value = $value;
1423 $DB->insert_record('user_preferences', $preference);
1426 // update value in cache
1427 $user->preference[$name] = $value;
1429 // set reload flag for other sessions
1430 mark_user_preferences_changed($user->id);
1432 return true;
1436 * Sets a whole array of preferences for the current user
1438 * If user object submitted, 'preference' property contains the preferences cache.
1440 * @param array $prefarray An array of key/value pairs to be set
1441 * @param stdClass|int $user A moodle user object or id, null means current user
1442 * @return bool always true or exception
1444 function set_user_preferences(array $prefarray, $user = null) {
1445 foreach ($prefarray as $name => $value) {
1446 set_user_preference($name, $value, $user);
1448 return true;
1452 * Unsets a preference completely by deleting it from the database
1454 * If user object submitted, 'preference' property contains the preferences cache.
1456 * @param string $name The key to unset as preference for the specified user
1457 * @param stdClass|int $user A moodle user object or id, null means current user
1458 * @return bool always true or exception
1460 function unset_user_preference($name, $user = null) {
1461 global $USER, $DB;
1463 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1464 throw new coding_exception('Invalid preference name in unset_user_preference() call');
1467 if (is_null($user)) {
1468 $user = $USER;
1469 } else if (isset($user->id)) {
1470 // $user is valid object
1471 } else if (is_numeric($user)) {
1472 $user = (object)array('id'=>(int)$user);
1473 } else {
1474 throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1477 check_user_preferences_loaded($user);
1479 if (empty($user->id) or isguestuser($user->id)) {
1480 // no permanent storage for not-logged-in user and guest
1481 unset($user->preference[$name]);
1482 return true;
1485 // delete from DB
1486 $DB->delete_records('user_preferences', array('userid'=>$user->id, 'name'=>$name));
1488 // delete the preference from cache
1489 unset($user->preference[$name]);
1491 // set reload flag for other sessions
1492 mark_user_preferences_changed($user->id);
1494 return true;
1498 * Used to fetch user preference(s)
1500 * If no arguments are supplied this function will return
1501 * all of the current user preferences as an array.
1503 * If a name is specified then this function
1504 * attempts to return that particular preference value. If
1505 * none is found, then the optional value $default is returned,
1506 * otherwise NULL.
1508 * If user object submitted, 'preference' property contains the preferences cache.
1510 * @param string $name Name of the key to use in finding a preference value
1511 * @param mixed $default Value to be returned if the $name key is not set in the user preferences
1512 * @param stdClass|int $user A moodle user object or id, null means current user
1513 * @return mixed string value or default
1515 function get_user_preferences($name = null, $default = null, $user = null) {
1516 global $USER;
1518 if (is_null($name)) {
1519 // all prefs
1520 } else if (is_numeric($name) or $name === '_lastloaded') {
1521 throw new coding_exception('Invalid preference name in get_user_preferences() call');
1524 if (is_null($user)) {
1525 $user = $USER;
1526 } else if (isset($user->id)) {
1527 // $user is valid object
1528 } else if (is_numeric($user)) {
1529 $user = (object)array('id'=>(int)$user);
1530 } else {
1531 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1534 check_user_preferences_loaded($user);
1536 if (empty($name)) {
1537 return $user->preference; // All values
1538 } else if (isset($user->preference[$name])) {
1539 return $user->preference[$name]; // The single string value
1540 } else {
1541 return $default; // Default value (null if not specified)
1545 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1548 * Given date parts in user time produce a GMT timestamp.
1550 * @todo Finish documenting this function
1551 * @param int $year The year part to create timestamp of
1552 * @param int $month The month part to create timestamp of
1553 * @param int $day The day part to create timestamp of
1554 * @param int $hour The hour part to create timestamp of
1555 * @param int $minute The minute part to create timestamp of
1556 * @param int $second The second part to create timestamp of
1557 * @param float $timezone Timezone modifier
1558 * @param bool $applydst Toggle Daylight Saving Time, default true
1559 * @return int timestamp
1561 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1563 $strtimezone = NULL;
1564 if (!is_numeric($timezone)) {
1565 $strtimezone = $timezone;
1568 $timezone = get_user_timezone_offset($timezone);
1570 if (abs($timezone) > 13) {
1571 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1572 } else {
1573 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1574 $time = usertime($time, $timezone);
1575 if($applydst) {
1576 $time -= dst_offset_on($time, $strtimezone);
1580 return $time;
1585 * Format a date/time (seconds) as weeks, days, hours etc as needed
1587 * Given an amount of time in seconds, returns string
1588 * formatted nicely as weeks, days, hours etc as needed
1590 * @uses MINSECS
1591 * @uses HOURSECS
1592 * @uses DAYSECS
1593 * @uses YEARSECS
1594 * @param int $totalsecs Time in seconds
1595 * @param object $str Should be a time object
1596 * @return string A nicely formatted date/time string
1598 function format_time($totalsecs, $str=NULL) {
1600 $totalsecs = abs($totalsecs);
1602 if (!$str) { // Create the str structure the slow way
1603 $str->day = get_string('day');
1604 $str->days = get_string('days');
1605 $str->hour = get_string('hour');
1606 $str->hours = get_string('hours');
1607 $str->min = get_string('min');
1608 $str->mins = get_string('mins');
1609 $str->sec = get_string('sec');
1610 $str->secs = get_string('secs');
1611 $str->year = get_string('year');
1612 $str->years = get_string('years');
1616 $years = floor($totalsecs/YEARSECS);
1617 $remainder = $totalsecs - ($years*YEARSECS);
1618 $days = floor($remainder/DAYSECS);
1619 $remainder = $totalsecs - ($days*DAYSECS);
1620 $hours = floor($remainder/HOURSECS);
1621 $remainder = $remainder - ($hours*HOURSECS);
1622 $mins = floor($remainder/MINSECS);
1623 $secs = $remainder - ($mins*MINSECS);
1625 $ss = ($secs == 1) ? $str->sec : $str->secs;
1626 $sm = ($mins == 1) ? $str->min : $str->mins;
1627 $sh = ($hours == 1) ? $str->hour : $str->hours;
1628 $sd = ($days == 1) ? $str->day : $str->days;
1629 $sy = ($years == 1) ? $str->year : $str->years;
1631 $oyears = '';
1632 $odays = '';
1633 $ohours = '';
1634 $omins = '';
1635 $osecs = '';
1637 if ($years) $oyears = $years .' '. $sy;
1638 if ($days) $odays = $days .' '. $sd;
1639 if ($hours) $ohours = $hours .' '. $sh;
1640 if ($mins) $omins = $mins .' '. $sm;
1641 if ($secs) $osecs = $secs .' '. $ss;
1643 if ($years) return trim($oyears .' '. $odays);
1644 if ($days) return trim($odays .' '. $ohours);
1645 if ($hours) return trim($ohours .' '. $omins);
1646 if ($mins) return trim($omins .' '. $osecs);
1647 if ($secs) return $osecs;
1648 return get_string('now');
1652 * Returns a formatted string that represents a date in user time
1654 * Returns a formatted string that represents a date in user time
1655 * <b>WARNING: note that the format is for strftime(), not date().</b>
1656 * Because of a bug in most Windows time libraries, we can't use
1657 * the nicer %e, so we have to use %d which has leading zeroes.
1658 * A lot of the fuss in the function is just getting rid of these leading
1659 * zeroes as efficiently as possible.
1661 * If parameter fixday = true (default), then take off leading
1662 * zero from %d, else maintain it.
1664 * @param int $date the timestamp in UTC, as obtained from the database.
1665 * @param string $format strftime format. You should probably get this using
1666 * get_string('strftime...', 'langconfig');
1667 * @param float $timezone by default, uses the user's time zone.
1668 * @param bool $fixday If true (default) then the leading zero from %d is removed.
1669 * If false then the leading zero is maintained.
1670 * @return string the formatted date/time.
1672 function userdate($date, $format = '', $timezone = 99, $fixday = true) {
1674 global $CFG;
1676 $strtimezone = NULL;
1677 if (!is_numeric($timezone)) {
1678 $strtimezone = $timezone;
1681 if (empty($format)) {
1682 $format = get_string('strftimedaydatetime', 'langconfig');
1685 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
1686 $fixday = false;
1687 } else if ($fixday) {
1688 $formatnoday = str_replace('%d', 'DD', $format);
1689 $fixday = ($formatnoday != $format);
1692 $date += dst_offset_on($date, $strtimezone);
1694 $timezone = get_user_timezone_offset($timezone);
1696 if (abs($timezone) > 13) { /// Server time
1697 if ($fixday) {
1698 $datestring = strftime($formatnoday, $date);
1699 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
1700 $datestring = str_replace('DD', $daystring, $datestring);
1701 } else {
1702 $datestring = strftime($format, $date);
1704 } else {
1705 $date += (int)($timezone * 3600);
1706 if ($fixday) {
1707 $datestring = gmstrftime($formatnoday, $date);
1708 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
1709 $datestring = str_replace('DD', $daystring, $datestring);
1710 } else {
1711 $datestring = gmstrftime($format, $date);
1715 /// If we are running under Windows convert from windows encoding to UTF-8
1716 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1718 if ($CFG->ostype == 'WINDOWS') {
1719 if ($localewincharset = get_string('localewincharset', 'langconfig')) {
1720 $textlib = textlib_get_instance();
1721 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1725 return $datestring;
1729 * Given a $time timestamp in GMT (seconds since epoch),
1730 * returns an array that represents the date in user time
1732 * @todo Finish documenting this function
1733 * @uses HOURSECS
1734 * @param int $time Timestamp in GMT
1735 * @param float $timezone ?
1736 * @return array An array that represents the date in user time
1738 function usergetdate($time, $timezone=99) {
1740 $strtimezone = NULL;
1741 if (!is_numeric($timezone)) {
1742 $strtimezone = $timezone;
1745 $timezone = get_user_timezone_offset($timezone);
1747 if (abs($timezone) > 13) { // Server time
1748 return getdate($time);
1751 // There is no gmgetdate so we use gmdate instead
1752 $time += dst_offset_on($time, $strtimezone);
1753 $time += intval((float)$timezone * HOURSECS);
1755 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
1757 //be careful to ensure the returned array matches that produced by getdate() above
1758 list(
1759 $getdate['month'],
1760 $getdate['weekday'],
1761 $getdate['yday'],
1762 $getdate['year'],
1763 $getdate['mon'],
1764 $getdate['wday'],
1765 $getdate['mday'],
1766 $getdate['hours'],
1767 $getdate['minutes'],
1768 $getdate['seconds']
1769 ) = explode('_', $datestring);
1771 return $getdate;
1775 * Given a GMT timestamp (seconds since epoch), offsets it by
1776 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1778 * @uses HOURSECS
1779 * @param int $date Timestamp in GMT
1780 * @param float $timezone
1781 * @return int
1783 function usertime($date, $timezone=99) {
1785 $timezone = get_user_timezone_offset($timezone);
1787 if (abs($timezone) > 13) {
1788 return $date;
1790 return $date - (int)($timezone * HOURSECS);
1794 * Given a time, return the GMT timestamp of the most recent midnight
1795 * for the current user.
1797 * @param int $date Timestamp in GMT
1798 * @param float $timezone Defaults to user's timezone
1799 * @return int Returns a GMT timestamp
1801 function usergetmidnight($date, $timezone=99) {
1803 $userdate = usergetdate($date, $timezone);
1805 // Time of midnight of this user's day, in GMT
1806 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1811 * Returns a string that prints the user's timezone
1813 * @param float $timezone The user's timezone
1814 * @return string
1816 function usertimezone($timezone=99) {
1818 $tz = get_user_timezone($timezone);
1820 if (!is_float($tz)) {
1821 return $tz;
1824 if(abs($tz) > 13) { // Server time
1825 return get_string('serverlocaltime');
1828 if($tz == intval($tz)) {
1829 // Don't show .0 for whole hours
1830 $tz = intval($tz);
1833 if($tz == 0) {
1834 return 'UTC';
1836 else if($tz > 0) {
1837 return 'UTC+'.$tz;
1839 else {
1840 return 'UTC'.$tz;
1846 * Returns a float which represents the user's timezone difference from GMT in hours
1847 * Checks various settings and picks the most dominant of those which have a value
1849 * @global object
1850 * @global object
1851 * @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
1852 * @return float
1854 function get_user_timezone_offset($tz = 99) {
1856 global $USER, $CFG;
1858 $tz = get_user_timezone($tz);
1860 if (is_float($tz)) {
1861 return $tz;
1862 } else {
1863 $tzrecord = get_timezone_record($tz);
1864 if (empty($tzrecord)) {
1865 return 99.0;
1867 return (float)$tzrecord->gmtoff / HOURMINS;
1872 * Returns an int which represents the systems's timezone difference from GMT in seconds
1874 * @global object
1875 * @param mixed $tz timezone
1876 * @return int if found, false is timezone 99 or error
1878 function get_timezone_offset($tz) {
1879 global $CFG;
1881 if ($tz == 99) {
1882 return false;
1885 if (is_numeric($tz)) {
1886 return intval($tz * 60*60);
1889 if (!$tzrecord = get_timezone_record($tz)) {
1890 return false;
1892 return intval($tzrecord->gmtoff * 60);
1896 * Returns a float or a string which denotes the user's timezone
1897 * 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)
1898 * means that for this timezone there are also DST rules to be taken into account
1899 * Checks various settings and picks the most dominant of those which have a value
1901 * @global object
1902 * @global object
1903 * @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
1904 * @return mixed
1906 function get_user_timezone($tz = 99) {
1907 global $USER, $CFG;
1909 $timezones = array(
1910 $tz,
1911 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1912 isset($USER->timezone) ? $USER->timezone : 99,
1913 isset($CFG->timezone) ? $CFG->timezone : 99,
1916 $tz = 99;
1918 while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
1919 $tz = $next['value'];
1922 return is_numeric($tz) ? (float) $tz : $tz;
1926 * Returns cached timezone record for given $timezonename
1928 * @global object
1929 * @global object
1930 * @param string $timezonename
1931 * @return mixed timezonerecord object or false
1933 function get_timezone_record($timezonename) {
1934 global $CFG, $DB;
1935 static $cache = NULL;
1937 if ($cache === NULL) {
1938 $cache = array();
1941 if (isset($cache[$timezonename])) {
1942 return $cache[$timezonename];
1945 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
1946 WHERE name = ? ORDER BY year DESC', array($timezonename), true);
1950 * Build and store the users Daylight Saving Time (DST) table
1952 * @global object
1953 * @global object
1954 * @global object
1955 * @param mixed $from_year Start year for the table, defaults to 1971
1956 * @param mixed $to_year End year for the table, defaults to 2035
1957 * @param mixed $strtimezone
1958 * @return bool
1960 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1961 global $CFG, $SESSION, $DB;
1963 $usertz = get_user_timezone($strtimezone);
1965 if (is_float($usertz)) {
1966 // Trivial timezone, no DST
1967 return false;
1970 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1971 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1972 unset($SESSION->dst_offsets);
1973 unset($SESSION->dst_range);
1976 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1977 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1978 // This will be the return path most of the time, pretty light computationally
1979 return true;
1982 // Reaching here means we either need to extend our table or create it from scratch
1984 // Remember which TZ we calculated these changes for
1985 $SESSION->dst_offsettz = $usertz;
1987 if(empty($SESSION->dst_offsets)) {
1988 // If we 're creating from scratch, put the two guard elements in there
1989 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1991 if(empty($SESSION->dst_range)) {
1992 // If creating from scratch
1993 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1994 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
1996 // Fill in the array with the extra years we need to process
1997 $yearstoprocess = array();
1998 for($i = $from; $i <= $to; ++$i) {
1999 $yearstoprocess[] = $i;
2002 // Take note of which years we have processed for future calls
2003 $SESSION->dst_range = array($from, $to);
2005 else {
2006 // If needing to extend the table, do the same
2007 $yearstoprocess = array();
2009 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
2010 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
2012 if($from < $SESSION->dst_range[0]) {
2013 // Take note of which years we need to process and then note that we have processed them for future calls
2014 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2015 $yearstoprocess[] = $i;
2017 $SESSION->dst_range[0] = $from;
2019 if($to > $SESSION->dst_range[1]) {
2020 // Take note of which years we need to process and then note that we have processed them for future calls
2021 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2022 $yearstoprocess[] = $i;
2024 $SESSION->dst_range[1] = $to;
2028 if(empty($yearstoprocess)) {
2029 // This means that there was a call requesting a SMALLER range than we have already calculated
2030 return true;
2033 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2034 // Also, the array is sorted in descending timestamp order!
2036 // Get DB data
2038 static $presets_cache = array();
2039 if (!isset($presets_cache[$usertz])) {
2040 $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');
2042 if(empty($presets_cache[$usertz])) {
2043 return false;
2046 // Remove ending guard (first element of the array)
2047 reset($SESSION->dst_offsets);
2048 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2050 // Add all required change timestamps
2051 foreach($yearstoprocess as $y) {
2052 // Find the record which is in effect for the year $y
2053 foreach($presets_cache[$usertz] as $year => $preset) {
2054 if($year <= $y) {
2055 break;
2059 $changes = dst_changes_for_year($y, $preset);
2061 if($changes === NULL) {
2062 continue;
2064 if($changes['dst'] != 0) {
2065 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2067 if($changes['std'] != 0) {
2068 $SESSION->dst_offsets[$changes['std']] = 0;
2072 // Put in a guard element at the top
2073 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2074 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2076 // Sort again
2077 krsort($SESSION->dst_offsets);
2079 return true;
2083 * Calculates the required DST change and returns a Timestamp Array
2085 * @uses HOURSECS
2086 * @uses MINSECS
2087 * @param mixed $year Int or String Year to focus on
2088 * @param object $timezone Instatiated Timezone object
2089 * @return mixed Null, or Array dst=>xx, 0=>xx, std=>yy, 1=>yy
2091 function dst_changes_for_year($year, $timezone) {
2093 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2094 return NULL;
2097 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2098 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2100 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
2101 list($std_hour, $std_min) = explode(':', $timezone->std_time);
2103 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2104 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2106 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2107 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2108 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2110 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
2111 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
2113 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2117 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2119 * @global object
2120 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2121 * @return int
2123 function dst_offset_on($time, $strtimezone = NULL) {
2124 global $SESSION;
2126 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
2127 return 0;
2130 reset($SESSION->dst_offsets);
2131 while(list($from, $offset) = each($SESSION->dst_offsets)) {
2132 if($from <= $time) {
2133 break;
2137 // This is the normal return path
2138 if($offset !== NULL) {
2139 return $offset;
2142 // Reaching this point means we haven't calculated far enough, do it now:
2143 // Calculate extra DST changes if needed and recurse. The recursion always
2144 // moves toward the stopping condition, so will always end.
2146 if($from == 0) {
2147 // We need a year smaller than $SESSION->dst_range[0]
2148 if($SESSION->dst_range[0] == 1971) {
2149 return 0;
2151 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
2152 return dst_offset_on($time, $strtimezone);
2154 else {
2155 // We need a year larger than $SESSION->dst_range[1]
2156 if($SESSION->dst_range[1] == 2035) {
2157 return 0;
2159 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
2160 return dst_offset_on($time, $strtimezone);
2167 * @todo Document what this function does
2168 * @param int $startday
2169 * @param int $weekday
2170 * @param int $month
2171 * @param int $year
2172 * @return int
2174 function find_day_in_month($startday, $weekday, $month, $year) {
2176 $daysinmonth = days_in_month($month, $year);
2178 if($weekday == -1) {
2179 // Don't care about weekday, so return:
2180 // abs($startday) if $startday != -1
2181 // $daysinmonth otherwise
2182 return ($startday == -1) ? $daysinmonth : abs($startday);
2185 // From now on we 're looking for a specific weekday
2187 // Give "end of month" its actual value, since we know it
2188 if($startday == -1) {
2189 $startday = -1 * $daysinmonth;
2192 // Starting from day $startday, the sign is the direction
2194 if($startday < 1) {
2196 $startday = abs($startday);
2197 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2199 // This is the last such weekday of the month
2200 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2201 if($lastinmonth > $daysinmonth) {
2202 $lastinmonth -= 7;
2205 // Find the first such weekday <= $startday
2206 while($lastinmonth > $startday) {
2207 $lastinmonth -= 7;
2210 return $lastinmonth;
2213 else {
2215 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2217 $diff = $weekday - $indexweekday;
2218 if($diff < 0) {
2219 $diff += 7;
2222 // This is the first such weekday of the month equal to or after $startday
2223 $firstfromindex = $startday + $diff;
2225 return $firstfromindex;
2231 * Calculate the number of days in a given month
2233 * @param int $month The month whose day count is sought
2234 * @param int $year The year of the month whose day count is sought
2235 * @return int
2237 function days_in_month($month, $year) {
2238 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2242 * Calculate the position in the week of a specific calendar day
2244 * @param int $day The day of the date whose position in the week is sought
2245 * @param int $month The month of the date whose position in the week is sought
2246 * @param int $year The year of the date whose position in the week is sought
2247 * @return int
2249 function dayofweek($day, $month, $year) {
2250 // I wonder if this is any different from
2251 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2252 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2255 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2258 * Returns full login url.
2260 * @return string login url
2262 function get_login_url() {
2263 global $CFG;
2265 $url = "$CFG->wwwroot/login/index.php";
2267 if (!empty($CFG->loginhttps)) {
2268 $url = str_replace('http:', 'https:', $url);
2271 return $url;
2275 * This function checks that the current user is logged in and has the
2276 * required privileges
2278 * This function checks that the current user is logged in, and optionally
2279 * whether they are allowed to be in a particular course and view a particular
2280 * course module.
2281 * If they are not logged in, then it redirects them to the site login unless
2282 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2283 * case they are automatically logged in as guests.
2284 * If $courseid is given and the user is not enrolled in that course then the
2285 * user is redirected to the course enrolment page.
2286 * If $cm is given and the course module is hidden and the user is not a teacher
2287 * in the course then the user is redirected to the course home page.
2289 * When $cm parameter specified, this function sets page layout to 'module'.
2290 * You need to change it manually later if some other layout needed.
2292 * @param mixed $courseorid id of the course or course object
2293 * @param bool $autologinguest default true
2294 * @param object $cm course module object
2295 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2296 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2297 * in order to keep redirects working properly. MDL-14495
2298 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2299 * @return mixed Void, exit, and die depending on path
2301 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2302 global $CFG, $SESSION, $USER, $FULLME, $PAGE, $SITE, $DB, $OUTPUT;
2304 // setup global $COURSE, themes, language and locale
2305 if (!empty($courseorid)) {
2306 if (is_object($courseorid)) {
2307 $course = $courseorid;
2308 } else if ($courseorid == SITEID) {
2309 $course = clone($SITE);
2310 } else {
2311 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2313 if ($cm) {
2314 if ($cm->course != $course->id) {
2315 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2317 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2318 if (!($cm instanceof cm_info)) {
2319 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2320 // db queries so this is not really a performance concern, however it is obviously
2321 // better if you use get_fast_modinfo to get the cm before calling this.
2322 $modinfo = get_fast_modinfo($course);
2323 $cm = $modinfo->get_cm($cm->id);
2325 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2326 $PAGE->set_pagelayout('incourse');
2327 } else {
2328 $PAGE->set_course($course); // set's up global $COURSE
2330 } else {
2331 // do not touch global $COURSE via $PAGE->set_course(),
2332 // the reasons is we need to be able to call require_login() at any time!!
2333 $course = $SITE;
2334 if ($cm) {
2335 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2339 // If the user is not even logged in yet then make sure they are
2340 if (!isloggedin()) {
2341 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2342 if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2343 // misconfigured site guest, just redirect to login page
2344 redirect(get_login_url());
2345 exit; // never reached
2347 $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2348 complete_user_login($guest, false);
2349 $USER->autologinguest = true;
2350 $SESSION->lang = $lang;
2351 } else {
2352 //NOTE: $USER->site check was obsoleted by session test cookie,
2353 // $USER->confirmed test is in login/index.php
2354 if ($preventredirect) {
2355 throw new require_login_exception('You are not logged in');
2358 if ($setwantsurltome) {
2359 // TODO: switch to PAGE->url
2360 $SESSION->wantsurl = $FULLME;
2362 if (!empty($_SERVER['HTTP_REFERER'])) {
2363 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
2365 redirect(get_login_url());
2366 exit; // never reached
2370 // loginas as redirection if needed
2371 if ($course->id != SITEID and session_is_loggedinas()) {
2372 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2373 if ($USER->loginascontext->instanceid != $course->id) {
2374 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2379 // check whether the user should be changing password (but only if it is REALLY them)
2380 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2381 $userauth = get_auth_plugin($USER->auth);
2382 if ($userauth->can_change_password() and !$preventredirect) {
2383 $SESSION->wantsurl = $FULLME;
2384 if ($changeurl = $userauth->change_password_url()) {
2385 //use plugin custom url
2386 redirect($changeurl);
2387 } else {
2388 //use moodle internal method
2389 if (empty($CFG->loginhttps)) {
2390 redirect($CFG->wwwroot .'/login/change_password.php');
2391 } else {
2392 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
2393 redirect($wwwroot .'/login/change_password.php');
2396 } else {
2397 print_error('nopasswordchangeforced', 'auth');
2401 // Check that the user account is properly set up
2402 if (user_not_fully_set_up($USER)) {
2403 if ($preventredirect) {
2404 throw new require_login_exception('User not fully set-up');
2406 $SESSION->wantsurl = $FULLME;
2407 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2410 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2411 sesskey();
2413 // Do not bother admins with any formalities
2414 if (is_siteadmin()) {
2415 //set accesstime or the user will appear offline which messes up messaging
2416 user_accesstime_log($course->id);
2417 return;
2420 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2421 if (!$USER->policyagreed and !is_siteadmin()) {
2422 if (!empty($CFG->sitepolicy) and !isguestuser()) {
2423 if ($preventredirect) {
2424 throw new require_login_exception('Policy not agreed');
2426 $SESSION->wantsurl = $FULLME;
2427 redirect($CFG->wwwroot .'/user/policy.php');
2428 } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
2429 if ($preventredirect) {
2430 throw new require_login_exception('Policy not agreed');
2432 $SESSION->wantsurl = $FULLME;
2433 redirect($CFG->wwwroot .'/user/policy.php');
2437 // Fetch the system context, the course context, and prefetch its child contexts
2438 $sysctx = get_context_instance(CONTEXT_SYSTEM);
2439 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
2440 if ($cm) {
2441 $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
2442 } else {
2443 $cmcontext = null;
2446 // If the site is currently under maintenance, then print a message
2447 if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
2448 if ($preventredirect) {
2449 throw new require_login_exception('Maintenance in progress');
2452 print_maintenance_message();
2455 // make sure the course itself is not hidden
2456 if ($course->id == SITEID) {
2457 // frontpage can not be hidden
2458 } else {
2459 if (is_role_switched($course->id)) {
2460 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2461 } else {
2462 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2463 // originally there was also test of parent category visibility,
2464 // BUT is was very slow in complex queries involving "my courses"
2465 // now it is also possible to simply hide all courses user is not enrolled in :-)
2466 if ($preventredirect) {
2467 throw new require_login_exception('Course is hidden');
2469 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2474 // is the user enrolled?
2475 if ($course->id == SITEID) {
2476 // everybody is enrolled on the frontpage
2478 } else {
2479 if (session_is_loggedinas()) {
2480 // Make sure the REAL person can access this course first
2481 $realuser = session_get_realuser();
2482 if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
2483 if ($preventredirect) {
2484 throw new require_login_exception('Invalid course login-as access');
2486 echo $OUTPUT->header();
2487 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2491 // very simple enrolment caching - changes in course setting are not reflected immediately
2492 if (!isset($USER->enrol)) {
2493 $USER->enrol = array();
2494 $USER->enrol['enrolled'] = array();
2495 $USER->enrol['tempguest'] = array();
2498 $access = false;
2500 if (is_viewing($coursecontext, $USER)) {
2501 // ok, no need to mess with enrol
2502 $access = true;
2504 } else {
2505 if (isset($USER->enrol['enrolled'][$course->id])) {
2506 if ($USER->enrol['enrolled'][$course->id] == 0) {
2507 $access = true;
2508 } else if ($USER->enrol['enrolled'][$course->id] > time()) {
2509 $access = true;
2510 } else {
2511 //expired
2512 unset($USER->enrol['enrolled'][$course->id]);
2515 if (isset($USER->enrol['tempguest'][$course->id])) {
2516 if ($USER->enrol['tempguest'][$course->id] == 0) {
2517 $access = true;
2518 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
2519 $access = true;
2520 } else {
2521 //expired
2522 unset($USER->enrol['tempguest'][$course->id]);
2523 $USER->access = remove_temp_roles($coursecontext, $USER->access);
2527 if ($access) {
2528 // cache ok
2529 } else if (is_enrolled($coursecontext, $USER, '', true)) {
2530 // active participants may always access
2531 // TODO: refactor this into some new function
2532 $now = time();
2533 $sql = "SELECT MAX(ue.timeend)
2534 FROM {user_enrolments} ue
2535 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2536 JOIN {user} u ON u.id = ue.userid
2537 WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0
2538 AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
2539 $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE,
2540 'userid'=>$USER->id, 'courseid'=>$coursecontext->instanceid, 'now1'=>$now, 'now2'=>$now);
2541 $until = $DB->get_field_sql($sql, $params);
2542 if (!$until or $until > time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD) {
2543 $until = time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD;
2546 $USER->enrol['enrolled'][$course->id] = $until;
2547 $access = true;
2549 // remove traces of previous temp guest access
2550 $USER->access = remove_temp_roles($coursecontext, $USER->access);
2552 } else {
2553 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2554 $enrols = enrol_get_plugins(true);
2555 // first ask all enabled enrol instances in course if they want to auto enrol user
2556 foreach($instances as $instance) {
2557 if (!isset($enrols[$instance->enrol])) {
2558 continue;
2560 // Get a duration for the guestaccess, a timestamp in the future or false.
2561 $until = $enrols[$instance->enrol]->try_autoenrol($instance);
2562 if ($until !== false) {
2563 $USER->enrol['enrolled'][$course->id] = $until;
2564 $USER->access = remove_temp_roles($coursecontext, $USER->access);
2565 $access = true;
2566 break;
2569 // if not enrolled yet try to gain temporary guest access
2570 if (!$access) {
2571 foreach($instances as $instance) {
2572 if (!isset($enrols[$instance->enrol])) {
2573 continue;
2575 // Get a duration for the guestaccess, a timestamp in the future or false.
2576 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
2577 if ($until !== false) {
2578 $USER->enrol['tempguest'][$course->id] = $until;
2579 $access = true;
2580 break;
2587 if (!$access) {
2588 if ($preventredirect) {
2589 throw new require_login_exception('Not enrolled');
2591 $SESSION->wantsurl = $FULLME;
2592 redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
2596 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
2597 // conditional availability, etc
2598 if ($cm && !$cm->uservisible) {
2599 if ($preventredirect) {
2600 throw new require_login_exception('Activity is hidden');
2602 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
2605 // Finally access granted, update lastaccess times
2606 user_accesstime_log($course->id);
2611 * This function just makes sure a user is logged out.
2613 * @global object
2615 function require_logout() {
2616 global $USER;
2618 $params = $USER;
2620 if (isloggedin()) {
2621 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2623 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2624 foreach($authsequence as $authname) {
2625 $authplugin = get_auth_plugin($authname);
2626 $authplugin->prelogout_hook();
2630 events_trigger('user_logout', $params);
2631 session_get_instance()->terminate_current();
2632 unset($params);
2636 * Weaker version of require_login()
2638 * This is a weaker version of {@link require_login()} which only requires login
2639 * when called from within a course rather than the site page, unless
2640 * the forcelogin option is turned on.
2641 * @see require_login()
2643 * @global object
2644 * @param mixed $courseorid The course object or id in question
2645 * @param bool $autologinguest Allow autologin guests if that is wanted
2646 * @param object $cm Course activity module if known
2647 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2648 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2649 * in order to keep redirects working properly. MDL-14495
2650 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2651 * @return void
2653 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2654 global $CFG, $PAGE, $SITE;
2655 $issite = (is_object($courseorid) and $courseorid->id == SITEID)
2656 or (!is_object($courseorid) and $courseorid == SITEID);
2657 if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
2658 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2659 // db queries so this is not really a performance concern, however it is obviously
2660 // better if you use get_fast_modinfo to get the cm before calling this.
2661 if (is_object($courseorid)) {
2662 $course = $courseorid;
2663 } else {
2664 $course = clone($SITE);
2666 $modinfo = get_fast_modinfo($course);
2667 $cm = $modinfo->get_cm($cm->id);
2669 if (!empty($CFG->forcelogin)) {
2670 // login required for both SITE and courses
2671 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2673 } else if ($issite && !empty($cm) and !$cm->uservisible) {
2674 // always login for hidden activities
2675 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2677 } else if ($issite) {
2678 //login for SITE not required
2679 if ($cm and empty($cm->visible)) {
2680 // hidden activities are not accessible without login
2681 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2682 } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
2683 // not-logged-in users do not have any group membership
2684 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2685 } else {
2686 // We still need to instatiate PAGE vars properly so that things
2687 // that rely on it like navigation function correctly.
2688 if (!empty($courseorid)) {
2689 if (is_object($courseorid)) {
2690 $course = $courseorid;
2691 } else {
2692 $course = clone($SITE);
2694 if ($cm) {
2695 if ($cm->course != $course->id) {
2696 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
2698 $PAGE->set_cm($cm, $course);
2699 $PAGE->set_pagelayout('incourse');
2700 } else {
2701 $PAGE->set_course($course);
2703 } else {
2704 // If $PAGE->course, and hence $PAGE->context, have not already been set
2705 // up properly, set them up now.
2706 $PAGE->set_course($PAGE->course);
2708 //TODO: verify conditional activities here
2709 user_accesstime_log(SITEID);
2710 return;
2713 } else {
2714 // course login always required
2715 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2720 * Require key login. Function terminates with error if key not found or incorrect.
2722 * @global object
2723 * @global object
2724 * @global object
2725 * @global object
2726 * @uses NO_MOODLE_COOKIES
2727 * @uses PARAM_ALPHANUM
2728 * @param string $script unique script identifier
2729 * @param int $instance optional instance id
2730 * @return int Instance ID
2732 function require_user_key_login($script, $instance=null) {
2733 global $USER, $SESSION, $CFG, $DB;
2735 if (!NO_MOODLE_COOKIES) {
2736 print_error('sessioncookiesdisable');
2739 /// extra safety
2740 @session_write_close();
2742 $keyvalue = required_param('key', PARAM_ALPHANUM);
2744 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
2745 print_error('invalidkey');
2748 if (!empty($key->validuntil) and $key->validuntil < time()) {
2749 print_error('expiredkey');
2752 if ($key->iprestriction) {
2753 $remoteaddr = getremoteaddr(null);
2754 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
2755 print_error('ipmismatch');
2759 if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
2760 print_error('invaliduserid');
2763 /// emulate normal session
2764 session_set_user($user);
2766 /// note we are not using normal login
2767 if (!defined('USER_KEY_LOGIN')) {
2768 define('USER_KEY_LOGIN', true);
2771 /// return instance id - it might be empty
2772 return $key->instance;
2776 * Creates a new private user access key.
2778 * @global object
2779 * @param string $script unique target identifier
2780 * @param int $userid
2781 * @param int $instance optional instance id
2782 * @param string $iprestriction optional ip restricted access
2783 * @param timestamp $validuntil key valid only until given data
2784 * @return string access key value
2786 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2787 global $DB;
2789 $key = new stdClass();
2790 $key->script = $script;
2791 $key->userid = $userid;
2792 $key->instance = $instance;
2793 $key->iprestriction = $iprestriction;
2794 $key->validuntil = $validuntil;
2795 $key->timecreated = time();
2797 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
2798 while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
2799 // must be unique
2800 $key->value = md5($userid.'_'.time().random_string(40));
2802 $DB->insert_record('user_private_key', $key);
2803 return $key->value;
2807 * Delete the user's new private user access keys for a particular script.
2809 * @global object
2810 * @param string $script unique target identifier
2811 * @param int $userid
2812 * @return void
2814 function delete_user_key($script,$userid) {
2815 global $DB;
2816 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
2820 * Gets a private user access key (and creates one if one doesn't exist).
2822 * @global object
2823 * @param string $script unique target identifier
2824 * @param int $userid
2825 * @param int $instance optional instance id
2826 * @param string $iprestriction optional ip restricted access
2827 * @param timestamp $validuntil key valid only until given data
2828 * @return string access key value
2830 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2831 global $DB;
2833 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
2834 'instance'=>$instance, 'iprestriction'=>$iprestriction,
2835 'validuntil'=>$validuntil))) {
2836 return $key->value;
2837 } else {
2838 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
2844 * Modify the user table by setting the currently logged in user's
2845 * last login to now.
2847 * @global object
2848 * @global object
2849 * @return bool Always returns true
2851 function update_user_login_times() {
2852 global $USER, $DB;
2854 $user = new stdClass();
2855 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
2856 $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
2858 $user->id = $USER->id;
2860 $DB->update_record('user', $user);
2861 return true;
2865 * Determines if a user has completed setting up their account.
2867 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
2868 * @return bool
2870 function user_not_fully_set_up($user) {
2871 if (isguestuser($user)) {
2872 return false;
2874 return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
2878 * Check whether the user has exceeded the bounce threshold
2880 * @global object
2881 * @global object
2882 * @param user $user A {@link $USER} object
2883 * @return bool true=>User has exceeded bounce threshold
2885 function over_bounce_threshold($user) {
2886 global $CFG, $DB;
2888 if (empty($CFG->handlebounces)) {
2889 return false;
2892 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2893 return false;
2896 // set sensible defaults
2897 if (empty($CFG->minbounces)) {
2898 $CFG->minbounces = 10;
2900 if (empty($CFG->bounceratio)) {
2901 $CFG->bounceratio = .20;
2903 $bouncecount = 0;
2904 $sendcount = 0;
2905 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
2906 $bouncecount = $bounce->value;
2908 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
2909 $sendcount = $send->value;
2911 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
2915 * Used to increment or reset email sent count
2917 * @global object
2918 * @param user $user object containing an id
2919 * @param bool $reset will reset the count to 0
2920 * @return void
2922 function set_send_count($user,$reset=false) {
2923 global $DB;
2925 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2926 return;
2929 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
2930 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2931 $DB->update_record('user_preferences', $pref);
2933 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2934 // make a new one
2935 $pref = new stdClass();
2936 $pref->name = 'email_send_count';
2937 $pref->value = 1;
2938 $pref->userid = $user->id;
2939 $DB->insert_record('user_preferences', $pref, false);
2944 * Increment or reset user's email bounce count
2946 * @global object
2947 * @param user $user object containing an id
2948 * @param bool $reset will reset the count to 0
2950 function set_bounce_count($user,$reset=false) {
2951 global $DB;
2953 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
2954 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2955 $DB->update_record('user_preferences', $pref);
2957 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2958 // make a new one
2959 $pref = new stdClass();
2960 $pref->name = 'email_bounce_count';
2961 $pref->value = 1;
2962 $pref->userid = $user->id;
2963 $DB->insert_record('user_preferences', $pref, false);
2968 * Keeps track of login attempts
2970 * @global object
2972 function update_login_count() {
2973 global $SESSION;
2975 $max_logins = 10;
2977 if (empty($SESSION->logincount)) {
2978 $SESSION->logincount = 1;
2979 } else {
2980 $SESSION->logincount++;
2983 if ($SESSION->logincount > $max_logins) {
2984 unset($SESSION->wantsurl);
2985 print_error('errortoomanylogins');
2990 * Resets login attempts
2992 * @global object
2994 function reset_login_count() {
2995 global $SESSION;
2997 $SESSION->logincount = 0;
3001 * Determines if the currently logged in user is in editing mode.
3002 * Note: originally this function had $userid parameter - it was not usable anyway
3004 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3005 * @todo Deprecated function remove when ready
3007 * @global object
3008 * @uses DEBUG_DEVELOPER
3009 * @return bool
3011 function isediting() {
3012 global $PAGE;
3013 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
3014 return $PAGE->user_is_editing();
3018 * Determines if the logged in user is currently moving an activity
3020 * @global object
3021 * @param int $courseid The id of the course being tested
3022 * @return bool
3024 function ismoving($courseid) {
3025 global $USER;
3027 if (!empty($USER->activitycopy)) {
3028 return ($USER->activitycopycourse == $courseid);
3030 return false;
3034 * Returns a persons full name
3036 * Given an object containing firstname and lastname
3037 * values, this function returns a string with the
3038 * full name of the person.
3039 * The result may depend on system settings
3040 * or language. 'override' will force both names
3041 * to be used even if system settings specify one.
3043 * @global object
3044 * @global object
3045 * @param object $user A {@link $USER} object to get full name of
3046 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3047 * @return string
3049 function fullname($user, $override=false) {
3050 global $CFG, $SESSION;
3052 if (!isset($user->firstname) and !isset($user->lastname)) {
3053 return '';
3056 if (!$override) {
3057 if (!empty($CFG->forcefirstname)) {
3058 $user->firstname = $CFG->forcefirstname;
3060 if (!empty($CFG->forcelastname)) {
3061 $user->lastname = $CFG->forcelastname;
3065 if (!empty($SESSION->fullnamedisplay)) {
3066 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3069 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
3070 return $user->firstname .' '. $user->lastname;
3072 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
3073 return $user->lastname .' '. $user->firstname;
3075 } else if ($CFG->fullnamedisplay == 'firstname') {
3076 if ($override) {
3077 return get_string('fullnamedisplay', '', $user);
3078 } else {
3079 return $user->firstname;
3083 return get_string('fullnamedisplay', '', $user);
3087 * Returns whether a given authentication plugin exists.
3089 * @global object
3090 * @param string $auth Form of authentication to check for. Defaults to the
3091 * global setting in {@link $CFG}.
3092 * @return boolean Whether the plugin is available.
3094 function exists_auth_plugin($auth) {
3095 global $CFG;
3097 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3098 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3100 return false;
3104 * Checks if a given plugin is in the list of enabled authentication plugins.
3106 * @param string $auth Authentication plugin.
3107 * @return boolean Whether the plugin is enabled.
3109 function is_enabled_auth($auth) {
3110 if (empty($auth)) {
3111 return false;
3114 $enabled = get_enabled_auth_plugins();
3116 return in_array($auth, $enabled);
3120 * Returns an authentication plugin instance.
3122 * @global object
3123 * @param string $auth name of authentication plugin
3124 * @return auth_plugin_base An instance of the required authentication plugin.
3126 function get_auth_plugin($auth) {
3127 global $CFG;
3129 // check the plugin exists first
3130 if (! exists_auth_plugin($auth)) {
3131 print_error('authpluginnotfound', 'debug', '', $auth);
3134 // return auth plugin instance
3135 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3136 $class = "auth_plugin_$auth";
3137 return new $class;
3141 * Returns array of active auth plugins.
3143 * @param bool $fix fix $CFG->auth if needed
3144 * @return array
3146 function get_enabled_auth_plugins($fix=false) {
3147 global $CFG;
3149 $default = array('manual', 'nologin');
3151 if (empty($CFG->auth)) {
3152 $auths = array();
3153 } else {
3154 $auths = explode(',', $CFG->auth);
3157 if ($fix) {
3158 $auths = array_unique($auths);
3159 foreach($auths as $k=>$authname) {
3160 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3161 unset($auths[$k]);
3164 $newconfig = implode(',', $auths);
3165 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
3166 set_config('auth', $newconfig);
3170 return (array_merge($default, $auths));
3174 * Returns true if an internal authentication method is being used.
3175 * if method not specified then, global default is assumed
3177 * @param string $auth Form of authentication required
3178 * @return bool
3180 function is_internal_auth($auth) {
3181 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3182 return $authplugin->is_internal();
3186 * Returns true if the user is a 'restored' one
3188 * Used in the login process to inform the user
3189 * and allow him/her to reset the password
3191 * @uses $CFG
3192 * @uses $DB
3193 * @param string $username username to be checked
3194 * @return bool
3196 function is_restored_user($username) {
3197 global $CFG, $DB;
3199 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
3203 * Returns an array of user fields
3205 * @return array User field/column names
3207 function get_user_fieldnames() {
3208 global $DB;
3210 $fieldarray = $DB->get_columns('user');
3211 unset($fieldarray['id']);
3212 $fieldarray = array_keys($fieldarray);
3214 return $fieldarray;
3218 * Creates a bare-bones user record
3220 * @todo Outline auth types and provide code example
3222 * @param string $username New user's username to add to record
3223 * @param string $password New user's password to add to record
3224 * @param string $auth Form of authentication required
3225 * @return stdClass A complete user object
3227 function create_user_record($username, $password, $auth = 'manual') {
3228 global $CFG, $DB;
3230 //just in case check text case
3231 $username = trim(moodle_strtolower($username));
3233 $authplugin = get_auth_plugin($auth);
3235 $newuser = new stdClass();
3237 if ($newinfo = $authplugin->get_userinfo($username)) {
3238 $newinfo = truncate_userinfo($newinfo);
3239 foreach ($newinfo as $key => $value){
3240 $newuser->$key = $value;
3244 if (!empty($newuser->email)) {
3245 if (email_is_not_allowed($newuser->email)) {
3246 unset($newuser->email);
3250 if (!isset($newuser->city)) {
3251 $newuser->city = '';
3254 $newuser->auth = $auth;
3255 $newuser->username = $username;
3257 // fix for MDL-8480
3258 // user CFG lang for user if $newuser->lang is empty
3259 // or $user->lang is not an installed language
3260 if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
3261 $newuser->lang = $CFG->lang;
3263 $newuser->confirmed = 1;
3264 $newuser->lastip = getremoteaddr();
3265 $newuser->timemodified = time();
3266 $newuser->mnethostid = $CFG->mnet_localhost_id;
3268 $newuser->id = $DB->insert_record('user', $newuser);
3269 $user = get_complete_user_data('id', $newuser->id);
3270 if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
3271 set_user_preference('auth_forcepasswordchange', 1, $user);
3273 update_internal_user_password($user, $password);
3275 // fetch full user record for the event, the complete user data contains too much info
3276 // and we want to be consistent with other places that trigger this event
3277 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
3279 return $user;
3283 * Will update a local user record from an external source.
3284 * (MNET users can not be updated using this method!)
3286 * @param string $username user's username to update the record
3287 * @return stdClass A complete user object
3289 function update_user_record($username) {
3290 global $DB, $CFG;
3292 $username = trim(moodle_strtolower($username)); /// just in case check text case
3294 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
3295 $newuser = array();
3296 $userauth = get_auth_plugin($oldinfo->auth);
3298 if ($newinfo = $userauth->get_userinfo($username)) {
3299 $newinfo = truncate_userinfo($newinfo);
3300 foreach ($newinfo as $key => $value){
3301 $key = strtolower($key);
3302 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3303 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3304 // unknown or must not be changed
3305 continue;
3307 $confval = $userauth->config->{'field_updatelocal_' . $key};
3308 $lockval = $userauth->config->{'field_lock_' . $key};
3309 if (empty($confval) || empty($lockval)) {
3310 continue;
3312 if ($confval === 'onlogin') {
3313 // MDL-4207 Don't overwrite modified user profile values with
3314 // empty LDAP values when 'unlocked if empty' is set. The purpose
3315 // of the setting 'unlocked if empty' is to allow the user to fill
3316 // in a value for the selected field _if LDAP is giving
3317 // nothing_ for this field. Thus it makes sense to let this value
3318 // stand in until LDAP is giving a value for this field.
3319 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3320 if ((string)$oldinfo->$key !== (string)$value) {
3321 $newuser[$key] = (string)$value;
3326 if ($newuser) {
3327 $newuser['id'] = $oldinfo->id;
3328 $DB->update_record('user', $newuser);
3329 // fetch full user record for the event, the complete user data contains too much info
3330 // and we want to be consistent with other places that trigger this event
3331 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
3335 return get_complete_user_data('id', $oldinfo->id);
3339 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3340 * which may have large fields
3342 * @todo Add vartype handling to ensure $info is an array
3344 * @param array $info Array of user properties to truncate if needed
3345 * @return array The now truncated information that was passed in
3347 function truncate_userinfo($info) {
3348 // define the limits
3349 $limit = array(
3350 'username' => 100,
3351 'idnumber' => 255,
3352 'firstname' => 100,
3353 'lastname' => 100,
3354 'email' => 100,
3355 'icq' => 15,
3356 'phone1' => 20,
3357 'phone2' => 20,
3358 'institution' => 40,
3359 'department' => 30,
3360 'address' => 70,
3361 'city' => 120,
3362 'country' => 2,
3363 'url' => 255,
3366 $textlib = textlib_get_instance();
3367 // apply where needed
3368 foreach (array_keys($info) as $key) {
3369 if (!empty($limit[$key])) {
3370 $info[$key] = trim($textlib->substr($info[$key],0, $limit[$key]));
3374 return $info;
3378 * Marks user deleted in internal user database and notifies the auth plugin.
3379 * Also unenrols user from all roles and does other cleanup.
3381 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3383 * @param object $user User object before delete
3384 * @return boolean always true
3386 function delete_user($user) {
3387 global $CFG, $DB;
3388 require_once($CFG->libdir.'/grouplib.php');
3389 require_once($CFG->libdir.'/gradelib.php');
3390 require_once($CFG->dirroot.'/message/lib.php');
3391 require_once($CFG->dirroot.'/tag/lib.php');
3393 // delete all grades - backup is kept in grade_grades_history table
3394 grade_user_delete($user->id);
3396 //move unread messages from this user to read
3397 message_move_userfrom_unread2read($user->id);
3399 // TODO: remove from cohorts using standard API here
3401 // remove user tags
3402 tag_set('user', $user->id, array());
3404 // unconditionally unenrol from all courses
3405 enrol_user_delete($user);
3407 // unenrol from all roles in all contexts
3408 role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
3410 //now do a brute force cleanup
3412 // remove from all cohorts
3413 $DB->delete_records('cohort_members', array('userid'=>$user->id));
3415 // remove from all groups
3416 $DB->delete_records('groups_members', array('userid'=>$user->id));
3418 // brute force unenrol from all courses
3419 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
3421 // purge user preferences
3422 $DB->delete_records('user_preferences', array('userid'=>$user->id));
3424 // purge user extra profile info
3425 $DB->delete_records('user_info_data', array('userid'=>$user->id));
3427 // last course access not necessary either
3428 $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
3430 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
3431 delete_context(CONTEXT_USER, $user->id);
3433 // workaround for bulk deletes of users with the same email address
3434 $delname = "$user->email.".time();
3435 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
3436 $delname++;
3439 // mark internal user record as "deleted"
3440 $updateuser = new stdClass();
3441 $updateuser->id = $user->id;
3442 $updateuser->deleted = 1;
3443 $updateuser->username = $delname; // Remember it just in case
3444 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
3445 $updateuser->idnumber = ''; // Clear this field to free it up
3446 $updateuser->timemodified = time();
3448 $DB->update_record('user', $updateuser);
3450 // notify auth plugin - do not block the delete even when plugin fails
3451 $authplugin = get_auth_plugin($user->auth);
3452 $authplugin->user_delete($user);
3454 // any plugin that needs to cleanup should register this event
3455 events_trigger('user_deleted', $user);
3457 return true;
3461 * Retrieve the guest user object
3463 * @global object
3464 * @global object
3465 * @return user A {@link $USER} object
3467 function guest_user() {
3468 global $CFG, $DB;
3470 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
3471 $newuser->confirmed = 1;
3472 $newuser->lang = $CFG->lang;
3473 $newuser->lastip = getremoteaddr();
3476 return $newuser;
3480 * Authenticates a user against the chosen authentication mechanism
3482 * Given a username and password, this function looks them
3483 * up using the currently selected authentication mechanism,
3484 * and if the authentication is successful, it returns a
3485 * valid $user object from the 'user' table.
3487 * Uses auth_ functions from the currently active auth module
3489 * After authenticate_user_login() returns success, you will need to
3490 * log that the user has logged in, and call complete_user_login() to set
3491 * the session up.
3493 * Note: this function works only with non-mnet accounts!
3495 * @param string $username User's username
3496 * @param string $password User's password
3497 * @return user|flase A {@link $USER} object or false if error
3499 function authenticate_user_login($username, $password) {
3500 global $CFG, $DB;
3502 $authsenabled = get_enabled_auth_plugins();
3504 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
3505 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
3506 if (!empty($user->suspended)) {
3507 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
3508 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3509 return false;
3511 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3512 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
3513 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3514 return false;
3516 $auths = array($auth);
3518 } else {
3519 // check if there's a deleted record (cheaply)
3520 if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
3521 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3522 return false;
3525 // User does not exist
3526 $auths = $authsenabled;
3527 $user = new stdClass();
3528 $user->id = 0;
3531 foreach ($auths as $auth) {
3532 $authplugin = get_auth_plugin($auth);
3534 // on auth fail fall through to the next plugin
3535 if (!$authplugin->user_login($username, $password)) {
3536 continue;
3539 // successful authentication
3540 if ($user->id) { // User already exists in database
3541 if (empty($user->auth)) { // For some reason auth isn't set yet
3542 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
3543 $user->auth = $auth;
3545 if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
3546 $DB->set_field('user','firstaccess', $user->timemodified, array('id' => $user->id));
3547 $user->firstaccess = $user->timemodified;
3550 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3552 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
3553 $user = update_user_record($username);
3555 } else {
3556 // if user not found, create him
3557 $user = create_user_record($username, $password, $auth);
3560 $authplugin->sync_roles($user);
3562 foreach ($authsenabled as $hau) {
3563 $hauth = get_auth_plugin($hau);
3564 $hauth->user_authenticated_hook($user, $username, $password);
3567 if (empty($user->id)) {
3568 return false;
3571 if (!empty($user->suspended)) {
3572 // just in case some auth plugin suspended account
3573 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
3574 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3575 return false;
3578 return $user;
3581 // failed if all the plugins have failed
3582 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
3583 if (debugging('', DEBUG_ALL)) {
3584 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3586 return false;
3590 * Call to complete the user login process after authenticate_user_login()
3591 * has succeeded. It will setup the $USER variable and other required bits
3592 * and pieces.
3594 * NOTE:
3595 * - It will NOT log anything -- up to the caller to decide what to log.
3597 * @param object $user
3598 * @param bool $setcookie
3599 * @return object A {@link $USER} object - BC only, do not use
3601 function complete_user_login($user, $setcookie=true) {
3602 global $CFG, $USER;
3604 // regenerate session id and delete old session,
3605 // this helps prevent session fixation attacks from the same domain
3606 session_regenerate_id(true);
3608 // check enrolments, load caps and setup $USER object
3609 session_set_user($user);
3611 // reload preferences from DB
3612 unset($user->preference);
3613 check_user_preferences_loaded($user);
3615 // update login times
3616 update_user_login_times();
3618 // extra session prefs init
3619 set_login_session_preferences();
3621 if (isguestuser()) {
3622 // no need to continue when user is THE guest
3623 return $USER;
3626 if ($setcookie) {
3627 if (empty($CFG->nolastloggedin)) {
3628 set_moodle_cookie($USER->username);
3629 } else {
3630 // do not store last logged in user in cookie
3631 // auth plugins can temporarily override this from loginpage_hook()
3632 // do not save $CFG->nolastloggedin in database!
3633 set_moodle_cookie('');
3637 /// Select password change url
3638 $userauth = get_auth_plugin($USER->auth);
3640 /// check whether the user should be changing password
3641 if (get_user_preferences('auth_forcepasswordchange', false)){
3642 if ($userauth->can_change_password()) {
3643 if ($changeurl = $userauth->change_password_url()) {
3644 redirect($changeurl);
3645 } else {
3646 redirect($CFG->httpswwwroot.'/login/change_password.php');
3648 } else {
3649 print_error('nopasswordchangeforced', 'auth');
3652 return $USER;
3656 * Compare password against hash stored in internal user table.
3657 * If necessary it also updates the stored hash to new format.
3659 * @param stdClass $user (password property may be updated)
3660 * @param string $password plain text password
3661 * @return bool is password valid?
3663 function validate_internal_user_password($user, $password) {
3664 global $CFG;
3666 if (!isset($CFG->passwordsaltmain)) {
3667 $CFG->passwordsaltmain = '';
3670 $validated = false;
3672 if ($user->password === 'not cached') {
3673 // internal password is not used at all, it can not validate
3675 } else if ($user->password === md5($password.$CFG->passwordsaltmain)
3676 or $user->password === md5($password)
3677 or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
3678 or $user->password === md5(addslashes($password))) {
3679 // note: we are intentionally using the addslashes() here because we
3680 // need to accept old password hashes of passwords with magic quotes
3681 $validated = true;
3683 } else {
3684 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
3685 $alt = 'passwordsaltalt'.$i;
3686 if (!empty($CFG->$alt)) {
3687 if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
3688 $validated = true;
3689 break;
3695 if ($validated) {
3696 // force update of password hash using latest main password salt and encoding if needed
3697 update_internal_user_password($user, $password);
3700 return $validated;
3704 * Calculate hashed value from password using current hash mechanism.
3706 * @param string $password
3707 * @return string password hash
3709 function hash_internal_user_password($password) {
3710 global $CFG;
3712 if (isset($CFG->passwordsaltmain)) {
3713 return md5($password.$CFG->passwordsaltmain);
3714 } else {
3715 return md5($password);
3720 * Update password hash in user object.
3722 * @param stdClass $user (password property may be updated)
3723 * @param string $password plain text password
3724 * @return bool always returns true
3726 function update_internal_user_password($user, $password) {
3727 global $DB;
3729 $authplugin = get_auth_plugin($user->auth);
3730 if ($authplugin->prevent_local_passwords()) {
3731 $hashedpassword = 'not cached';
3732 } else {
3733 $hashedpassword = hash_internal_user_password($password);
3736 if ($user->password !== $hashedpassword) {
3737 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
3738 $user->password = $hashedpassword;
3741 return true;
3745 * Get a complete user record, which includes all the info
3746 * in the user record.
3748 * Intended for setting as $USER session variable
3750 * @param string $field The user field to be checked for a given value.
3751 * @param string $value The value to match for $field.
3752 * @param int $mnethostid
3753 * @return mixed False, or A {@link $USER} object.
3755 function get_complete_user_data($field, $value, $mnethostid = null) {
3756 global $CFG, $DB;
3758 if (!$field || !$value) {
3759 return false;
3762 /// Build the WHERE clause for an SQL query
3763 $params = array('fieldval'=>$value);
3764 $constraints = "$field = :fieldval AND deleted <> 1";
3766 // If we are loading user data based on anything other than id,
3767 // we must also restrict our search based on mnet host.
3768 if ($field != 'id') {
3769 if (empty($mnethostid)) {
3770 // if empty, we restrict to local users
3771 $mnethostid = $CFG->mnet_localhost_id;
3774 if (!empty($mnethostid)) {
3775 $params['mnethostid'] = $mnethostid;
3776 $constraints .= " AND mnethostid = :mnethostid";
3779 /// Get all the basic user data
3781 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
3782 return false;
3785 /// Get various settings and preferences
3787 // preload preference cache
3788 check_user_preferences_loaded($user);
3790 // load course enrolment related stuff
3791 $user->lastcourseaccess = array(); // during last session
3792 $user->currentcourseaccess = array(); // during current session
3793 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
3794 foreach ($lastaccesses as $lastaccess) {
3795 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
3799 $sql = "SELECT g.id, g.courseid
3800 FROM {groups} g, {groups_members} gm
3801 WHERE gm.groupid=g.id AND gm.userid=?";
3803 // this is a special hack to speedup calendar display
3804 $user->groupmember = array();
3805 if (!isguestuser($user)) {
3806 if ($groups = $DB->get_records_sql($sql, array($user->id))) {
3807 foreach ($groups as $group) {
3808 if (!array_key_exists($group->courseid, $user->groupmember)) {
3809 $user->groupmember[$group->courseid] = array();
3811 $user->groupmember[$group->courseid][$group->id] = $group->id;
3816 /// Add the custom profile fields to the user record
3817 $user->profile = array();
3818 if (!isguestuser($user)) {
3819 require_once($CFG->dirroot.'/user/profile/lib.php');
3820 profile_load_custom_fields($user);
3823 /// Rewrite some variables if necessary
3824 if (!empty($user->description)) {
3825 $user->description = true; // No need to cart all of it around
3827 if (isguestuser($user)) {
3828 $user->lang = $CFG->lang; // Guest language always same as site
3829 $user->firstname = get_string('guestuser'); // Name always in current language
3830 $user->lastname = ' ';
3833 return $user;
3837 * Validate a password against the configured password policy
3839 * @global object
3840 * @param string $password the password to be checked against the password policy
3841 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3842 * @return bool true if the password is valid according to the policy. false otherwise.
3844 function check_password_policy($password, &$errmsg) {
3845 global $CFG;
3847 if (empty($CFG->passwordpolicy)) {
3848 return true;
3851 $textlib = textlib_get_instance();
3852 $errmsg = '';
3853 if ($textlib->strlen($password) < $CFG->minpasswordlength) {
3854 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
3857 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
3858 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
3861 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
3862 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
3865 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
3866 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
3869 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
3870 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
3872 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
3873 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
3876 if ($errmsg == '') {
3877 return true;
3878 } else {
3879 return false;
3885 * When logging in, this function is run to set certain preferences
3886 * for the current SESSION
3888 * @global object
3889 * @global object
3891 function set_login_session_preferences() {
3892 global $SESSION, $CFG;
3894 $SESSION->justloggedin = true;
3896 unset($SESSION->lang);
3898 // Restore the calendar filters, if saved
3899 if (intval(get_user_preferences('calendar_persistflt', 0))) {
3900 include_once($CFG->dirroot.'/calendar/lib.php');
3901 calendar_set_filters_status(get_user_preferences('calendar_savedflt', 0xff));
3907 * Delete a course, including all related data from the database,
3908 * and any associated files.
3910 * @global object
3911 * @global object
3912 * @param mixed $courseorid The id of the course or course object to delete.
3913 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3914 * @return bool true if all the removals succeeded. false if there were any failures. If this
3915 * method returns false, some of the removals will probably have succeeded, and others
3916 * failed, but you have no way of knowing which.
3918 function delete_course($courseorid, $showfeedback = true) {
3919 global $DB;
3921 if (is_object($courseorid)) {
3922 $courseid = $courseorid->id;
3923 $course = $courseorid;
3924 } else {
3925 $courseid = $courseorid;
3926 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
3927 return false;
3930 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3932 // frontpage course can not be deleted!!
3933 if ($courseid == SITEID) {
3934 return false;
3937 // make the course completely empty
3938 remove_course_contents($courseid, $showfeedback);
3940 // delete the course and related context instance
3941 delete_context(CONTEXT_COURSE, $courseid);
3942 $DB->delete_records("course", array("id"=>$courseid));
3944 //trigger events
3945 $course->context = $context; // you can not fetch context in the event because it was already deleted
3946 events_trigger('course_deleted', $course);
3948 return true;
3952 * Clear a course out completely, deleting all content
3953 * but don't delete the course itself.
3954 * This function does not verify any permissions.
3956 * Please note this function also deletes all user enrolments,
3957 * enrolment instances and role assignments.
3959 * @param int $courseid The id of the course that is being deleted
3960 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3961 * @return bool true if all the removals succeeded. false if there were any failures. If this
3962 * method returns false, some of the removals will probably have succeeded, and others
3963 * failed, but you have no way of knowing which.
3965 function remove_course_contents($courseid, $showfeedback = true) {
3966 global $CFG, $DB, $OUTPUT;
3967 require_once($CFG->libdir.'/completionlib.php');
3968 require_once($CFG->libdir.'/questionlib.php');
3969 require_once($CFG->libdir.'/gradelib.php');
3970 require_once($CFG->dirroot.'/group/lib.php');
3971 require_once($CFG->dirroot.'/tag/coursetagslib.php');
3973 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
3974 $context = get_context_instance(CONTEXT_COURSE, $courseid, MUST_EXIST);
3976 $strdeleted = get_string('deleted');
3978 // Delete course completion information,
3979 // this has to be done before grades and enrols
3980 $cc = new completion_info($course);
3981 $cc->clear_criteria();
3983 // remove roles and enrolments
3984 role_unassign_all(array('contextid'=>$context->id), true);
3985 enrol_course_delete($course);
3987 // Clean up course formats (iterate through all formats in the even the course format was ever changed)
3988 $formats = get_plugin_list('format');
3989 foreach ($formats as $format=>$formatdir) {
3990 $formatdelete = 'format_'.$format.'_delete_course';
3991 $formatlib = "$formatdir/lib.php";
3992 if (file_exists($formatlib)) {
3993 include_once($formatlib);
3994 if (function_exists($formatdelete)) {
3995 if ($showfeedback) {
3996 echo $OUTPUT->notification($strdeleted.' '.$format);
3998 $formatdelete($course->id);
4003 // Remove all data from gradebook - this needs to be done before course modules
4004 // because while deleting this information, the system may need to reference
4005 // the course modules that own the grades.
4006 remove_course_grades($courseid, $showfeedback);
4007 remove_grade_letters($context, $showfeedback);
4009 // Remove all data from availability and completion tables that is associated
4010 // with course-modules belonging to this course. Note this is done even if the
4011 // features are not enabled now, in case they were enabled previously
4012 $DB->delete_records_select('course_modules_completion',
4013 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4014 array($courseid));
4015 $DB->delete_records_select('course_modules_availability',
4016 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4017 array($courseid));
4019 // Delete course blocks - they may depend on modules so delete them first
4020 blocks_delete_all_for_context($context->id);
4022 // Delete every instance of every module
4023 if ($allmods = $DB->get_records('modules') ) {
4024 foreach ($allmods as $mod) {
4025 $modname = $mod->name;
4026 $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
4027 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4028 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4029 $count=0;
4030 if (file_exists($modfile)) {
4031 include_once($modfile);
4032 if (function_exists($moddelete)) {
4033 if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
4034 foreach ($instances as $instance) {
4035 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
4036 /// Delete activity context questions and question categories
4037 question_delete_activity($cm, $showfeedback);
4039 if ($moddelete($instance->id)) {
4040 $count++;
4042 } else {
4043 echo $OUTPUT->notification('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
4045 if ($cm) {
4046 // delete cm and its context in correct order
4047 delete_context(CONTEXT_MODULE, $cm->id); // some callbacks may try to fetch context, better delete first
4048 $DB->delete_records('course_modules', array('id'=>$cm->id));
4052 } else {
4053 //note: we should probably delete these anyway
4054 echo $OUTPUT->notification('Function '.$moddelete.'() doesn\'t exist!');
4057 if (function_exists($moddeletecourse)) {
4058 $moddeletecourse($course, $showfeedback);
4061 if ($showfeedback) {
4062 echo $OUTPUT->notification($strdeleted .' '. $count .' x '. $modname);
4067 // Delete any groups, removing members and grouping/course links first.
4068 groups_delete_groupings($course->id, $showfeedback);
4069 groups_delete_groups($course->id, $showfeedback);
4071 // Delete questions and question categories
4072 question_delete_course($course, $showfeedback);
4074 // Delete course tags
4075 coursetag_delete_course_tags($course->id, $showfeedback);
4077 // Delete legacy files (just in case some files are still left there after conversion to new file api)
4078 fulldelete($CFG->dataroot.'/'.$course->id);
4080 // cleanup course record - remove links to delted stuff
4081 $oldcourse = new stdClass();
4082 $oldcourse->id = $course->id;
4083 $oldcourse->summary = '';
4084 $oldcourse->modinfo = NULL;
4085 $oldcourse->legacyfiles = 0;
4086 $oldcourse->defaultgroupingid = 0;
4087 $oldcourse->enablecompletion = 0;
4088 $DB->update_record('course', $oldcourse);
4090 // Delete all related records in other tables that may have a courseid
4091 // This array stores the tables that need to be cleared, as
4092 // table_name => column_name that contains the course id.
4093 $tablestoclear = array(
4094 'event' => 'courseid', // Delete events
4095 'log' => 'course', // Delete logs
4096 'course_sections' => 'course', // Delete any course stuff
4097 'course_modules' => 'course',
4098 'course_display' => 'course',
4099 'backup_courses' => 'courseid', // Delete scheduled backup stuff
4100 'user_lastaccess' => 'courseid',
4101 'backup_log' => 'courseid'
4103 foreach ($tablestoclear as $table => $col) {
4104 $DB->delete_records($table, array($col=>$course->id));
4107 // Delete all remaining stuff linked to context,
4108 // such as remaining roles, files, comments, etc.
4109 // Keep the context record for now.
4110 delete_context(CONTEXT_COURSE, $course->id, false);
4112 //trigger events
4113 $course->context = $context; // you can not access context in cron event later after course is deleted
4114 events_trigger('course_content_removed', $course);
4116 return true;
4120 * Change dates in module - used from course reset.
4122 * @global object
4123 * @global object
4124 * @param string $modname forum, assignment, etc
4125 * @param array $fields array of date fields from mod table
4126 * @param int $timeshift time difference
4127 * @param int $courseid
4128 * @return bool success
4130 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4131 global $CFG, $DB;
4132 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
4134 $return = true;
4135 foreach ($fields as $field) {
4136 $updatesql = "UPDATE {".$modname."}
4137 SET $field = $field + ?
4138 WHERE course=? AND $field<>0 AND $field<>0";
4139 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4142 $refreshfunction = $modname.'_refresh_events';
4143 if (function_exists($refreshfunction)) {
4144 $refreshfunction($courseid);
4147 return $return;
4151 * This function will empty a course of user data.
4152 * It will retain the activities and the structure of the course.
4154 * @param object $data an object containing all the settings including courseid (without magic quotes)
4155 * @return array status array of array component, item, error
4157 function reset_course_userdata($data) {
4158 global $CFG, $USER, $DB;
4159 require_once($CFG->libdir.'/gradelib.php');
4160 require_once($CFG->libdir.'/completionlib.php');
4161 require_once($CFG->dirroot.'/group/lib.php');
4163 $data->courseid = $data->id;
4164 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
4166 // calculate the time shift of dates
4167 if (!empty($data->reset_start_date)) {
4168 // time part of course startdate should be zero
4169 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
4170 } else {
4171 $data->timeshift = 0;
4174 // result array: component, item, error
4175 $status = array();
4177 // start the resetting
4178 $componentstr = get_string('general');
4180 // move the course start time
4181 if (!empty($data->reset_start_date) and $data->timeshift) {
4182 // change course start data
4183 $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
4184 // update all course and group events - do not move activity events
4185 $updatesql = "UPDATE {event}
4186 SET timestart = timestart + ?
4187 WHERE courseid=? AND instance=0";
4188 $DB->execute($updatesql, array($data->timeshift, $data->courseid));
4190 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4193 if (!empty($data->reset_logs)) {
4194 $DB->delete_records('log', array('course'=>$data->courseid));
4195 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4198 if (!empty($data->reset_events)) {
4199 $DB->delete_records('event', array('courseid'=>$data->courseid));
4200 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4203 if (!empty($data->reset_notes)) {
4204 require_once($CFG->dirroot.'/notes/lib.php');
4205 note_delete_all($data->courseid);
4206 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4209 if (!empty($data->delete_blog_associations)) {
4210 require_once($CFG->dirroot.'/blog/lib.php');
4211 blog_remove_associations_for_course($data->courseid);
4212 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4215 if (!empty($data->reset_course_completion)) {
4216 // Delete course completion information
4217 $course = $DB->get_record('course', array('id'=>$data->courseid));
4218 $cc = new completion_info($course);
4219 $cc->delete_course_completion_data();
4220 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecoursecompletiondata', 'completion'), 'error'=>false);
4223 $componentstr = get_string('roles');
4225 if (!empty($data->reset_roles_overrides)) {
4226 $children = get_child_contexts($context);
4227 foreach ($children as $child) {
4228 $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
4230 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
4231 //force refresh for logged in users
4232 mark_context_dirty($context->path);
4233 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4236 if (!empty($data->reset_roles_local)) {
4237 $children = get_child_contexts($context);
4238 foreach ($children as $child) {
4239 role_unassign_all(array('contextid'=>$child->id));
4241 //force refresh for logged in users
4242 mark_context_dirty($context->path);
4243 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4246 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4247 $data->unenrolled = array();
4248 if (!empty($data->unenrol_users)) {
4249 $plugins = enrol_get_plugins(true);
4250 $instances = enrol_get_instances($data->courseid, true);
4251 foreach ($instances as $key=>$instance) {
4252 if (!isset($plugins[$instance->enrol])) {
4253 unset($instances[$key]);
4254 continue;
4256 if (!$plugins[$instance->enrol]->allow_unenrol($instance)) {
4257 unset($instances[$key]);
4261 $sqlempty = $DB->sql_empty();
4262 foreach($data->unenrol_users as $withroleid) {
4263 $sql = "SELECT DISTINCT ue.userid, ue.enrolid
4264 FROM {user_enrolments} ue
4265 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4266 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4267 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4268 $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
4270 $rs = $DB->get_recordset_sql($sql, $params);
4271 foreach ($rs as $ue) {
4272 if (!isset($instances[$ue->enrolid])) {
4273 continue;
4275 $plugins[$instances[$ue->enrolid]->enrol]->unenrol_user($instances[$ue->enrolid], $ue->userid);
4276 $data->unenrolled[$ue->userid] = $ue->userid;
4280 if (!empty($data->unenrolled)) {
4281 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
4285 $componentstr = get_string('groups');
4287 // remove all group members
4288 if (!empty($data->reset_groups_members)) {
4289 groups_delete_group_members($data->courseid);
4290 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
4293 // remove all groups
4294 if (!empty($data->reset_groups_remove)) {
4295 groups_delete_groups($data->courseid, false);
4296 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
4299 // remove all grouping members
4300 if (!empty($data->reset_groupings_members)) {
4301 groups_delete_groupings_groups($data->courseid, false);
4302 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
4305 // remove all groupings
4306 if (!empty($data->reset_groupings_remove)) {
4307 groups_delete_groupings($data->courseid, false);
4308 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
4311 // Look in every instance of every module for data to delete
4312 $unsupported_mods = array();
4313 if ($allmods = $DB->get_records('modules') ) {
4314 foreach ($allmods as $mod) {
4315 $modname = $mod->name;
4316 if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
4317 continue; // skip mods with no instances
4319 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
4320 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
4321 if (file_exists($modfile)) {
4322 include_once($modfile);
4323 if (function_exists($moddeleteuserdata)) {
4324 $modstatus = $moddeleteuserdata($data);
4325 if (is_array($modstatus)) {
4326 $status = array_merge($status, $modstatus);
4327 } else {
4328 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
4330 } else {
4331 $unsupported_mods[] = $mod;
4333 } else {
4334 debugging('Missing lib.php in '.$modname.' module!');
4339 // mention unsupported mods
4340 if (!empty($unsupported_mods)) {
4341 foreach($unsupported_mods as $mod) {
4342 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
4347 $componentstr = get_string('gradebook', 'grades');
4348 // reset gradebook
4349 if (!empty($data->reset_gradebook_items)) {
4350 remove_course_grades($data->courseid, false);
4351 grade_grab_course_grades($data->courseid);
4352 grade_regrade_final_grades($data->courseid);
4353 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4355 } else if (!empty($data->reset_gradebook_grades)) {
4356 grade_course_reset($data->courseid);
4357 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4359 // reset comments
4360 if (!empty($data->reset_comments)) {
4361 require_once($CFG->dirroot.'/comment/lib.php');
4362 comment::reset_course_page_comments($context);
4365 return $status;
4369 * Generate an email processing address
4371 * @param int $modid
4372 * @param string $modargs
4373 * @return string Returns email processing address
4375 function generate_email_processing_address($modid,$modargs) {
4376 global $CFG;
4378 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4379 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
4385 * @todo Finish documenting this function
4387 * @global object
4388 * @param string $modargs
4389 * @param string $body Currently unused
4391 function moodle_process_email($modargs,$body) {
4392 global $DB;
4394 // the first char should be an unencoded letter. We'll take this as an action
4395 switch ($modargs{0}) {
4396 case 'B': { // bounce
4397 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4398 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
4399 // check the half md5 of their email
4400 $md5check = substr(md5($user->email),0,16);
4401 if ($md5check == substr($modargs, -16)) {
4402 set_bounce_count($user);
4404 // else maybe they've already changed it?
4407 break;
4408 // maybe more later?
4412 /// CORRESPONDENCE ////////////////////////////////////////////////
4415 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4417 * @global object
4418 * @param string $action 'get', 'buffer', 'close' or 'flush'
4419 * @return object|null mailer instance if 'get' used or nothing
4421 function get_mailer($action='get') {
4422 global $CFG;
4424 static $mailer = null;
4425 static $counter = 0;
4427 if (!isset($CFG->smtpmaxbulk)) {
4428 $CFG->smtpmaxbulk = 1;
4431 if ($action == 'get') {
4432 $prevkeepalive = false;
4434 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4435 if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
4436 $counter++;
4437 // reset the mailer
4438 $mailer->Priority = 3;
4439 $mailer->CharSet = 'UTF-8'; // our default
4440 $mailer->ContentType = "text/plain";
4441 $mailer->Encoding = "8bit";
4442 $mailer->From = "root@localhost";
4443 $mailer->FromName = "Root User";
4444 $mailer->Sender = "";
4445 $mailer->Subject = "";
4446 $mailer->Body = "";
4447 $mailer->AltBody = "";
4448 $mailer->ConfirmReadingTo = "";
4450 $mailer->ClearAllRecipients();
4451 $mailer->ClearReplyTos();
4452 $mailer->ClearAttachments();
4453 $mailer->ClearCustomHeaders();
4454 return $mailer;
4457 $prevkeepalive = $mailer->SMTPKeepAlive;
4458 get_mailer('flush');
4461 include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
4462 $mailer = new moodle_phpmailer();
4464 $counter = 1;
4466 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
4467 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
4468 $mailer->CharSet = 'UTF-8';
4470 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
4471 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
4472 $mailer->LE = "\r\n";
4473 } else {
4474 $mailer->LE = "\n";
4477 if ($CFG->smtphosts == 'qmail') {
4478 $mailer->IsQmail(); // use Qmail system
4480 } else if (empty($CFG->smtphosts)) {
4481 $mailer->IsMail(); // use PHP mail() = sendmail
4483 } else {
4484 $mailer->IsSMTP(); // use SMTP directly
4485 if (!empty($CFG->debugsmtp)) {
4486 $mailer->SMTPDebug = true;
4488 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
4489 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
4491 if ($CFG->smtpuser) { // Use SMTP authentication
4492 $mailer->SMTPAuth = true;
4493 $mailer->Username = $CFG->smtpuser;
4494 $mailer->Password = $CFG->smtppass;
4498 return $mailer;
4501 $nothing = null;
4503 // keep smtp session open after sending
4504 if ($action == 'buffer') {
4505 if (!empty($CFG->smtpmaxbulk)) {
4506 get_mailer('flush');
4507 $m = get_mailer();
4508 if ($m->Mailer == 'smtp') {
4509 $m->SMTPKeepAlive = true;
4512 return $nothing;
4515 // close smtp session, but continue buffering
4516 if ($action == 'flush') {
4517 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4518 if (!empty($mailer->SMTPDebug)) {
4519 echo '<pre>'."\n";
4521 $mailer->SmtpClose();
4522 if (!empty($mailer->SMTPDebug)) {
4523 echo '</pre>';
4526 return $nothing;
4529 // close smtp session, do not buffer anymore
4530 if ($action == 'close') {
4531 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4532 get_mailer('flush');
4533 $mailer->SMTPKeepAlive = false;
4535 $mailer = null; // better force new instance
4536 return $nothing;
4541 * Send an email to a specified user
4543 * @global object
4544 * @global string
4545 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
4546 * @uses SITEID
4547 * @param stdClass $user A {@link $USER} object
4548 * @param stdClass $from A {@link $USER} object
4549 * @param string $subject plain text subject line of the email
4550 * @param string $messagetext plain text version of the message
4551 * @param string $messagehtml complete html version of the message (optional)
4552 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4553 * @param string $attachname the name of the file (extension indicates MIME)
4554 * @param bool $usetrueaddress determines whether $from email address should
4555 * be sent out. Will be overruled by user profile setting for maildisplay
4556 * @param string $replyto Email address to reply to
4557 * @param string $replytoname Name of reply to recipient
4558 * @param int $wordwrapwidth custom word wrap width, default 79
4559 * @return bool Returns true if mail was sent OK and false if there was an error.
4561 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4563 global $CFG, $FULLME;
4565 if (empty($user) || empty($user->email)) {
4566 mtrace('Error: lib/moodlelib.php email_to_user(): User is null or has no email');
4567 return false;
4570 if (!empty($user->deleted)) {
4571 // do not mail delted users
4572 mtrace('Error: lib/moodlelib.php email_to_user(): User is deleted');
4573 return false;
4576 if (!empty($CFG->noemailever)) {
4577 // hidden setting for development sites, set in config.php if needed
4578 mtrace('Error: lib/moodlelib.php email_to_user(): Not sending email due to noemailever config setting');
4579 return true;
4582 if (!empty($CFG->divertallemailsto)) {
4583 $subject = "[DIVERTED {$user->email}] $subject";
4584 $user = clone($user);
4585 $user->email = $CFG->divertallemailsto;
4588 // skip mail to suspended users
4589 if (isset($user->auth) && $user->auth=='nologin') {
4590 return true;
4593 if (over_bounce_threshold($user)) {
4594 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
4595 error_log($bouncemsg);
4596 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
4597 return false;
4600 // If the user is a remote mnet user, parse the email text for URL to the
4601 // wwwroot and modify the url to direct the user's browser to login at their
4602 // home site (identity provider - idp) before hitting the link itself
4603 if (is_mnet_remote_user($user)) {
4604 require_once($CFG->dirroot.'/mnet/lib.php');
4606 $jumpurl = mnet_get_idp_jump_url($user);
4607 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
4609 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4610 $callback,
4611 $messagetext);
4612 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4613 $callback,
4614 $messagehtml);
4616 $mail = get_mailer();
4618 if (!empty($mail->SMTPDebug)) {
4619 echo '<pre>' . "\n";
4622 $temprecipients = array();
4623 $tempreplyto = array();
4625 $supportuser = generate_email_supportuser();
4627 // make up an email address for handling bounces
4628 if (!empty($CFG->handlebounces)) {
4629 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
4630 $mail->Sender = generate_email_processing_address(0,$modargs);
4631 } else {
4632 $mail->Sender = $supportuser->email;
4635 if (is_string($from)) { // So we can pass whatever we want if there is need
4636 $mail->From = $CFG->noreplyaddress;
4637 $mail->FromName = $from;
4638 } else if ($usetrueaddress and $from->maildisplay) {
4639 $mail->From = $from->email;
4640 $mail->FromName = fullname($from);
4641 } else {
4642 $mail->From = $CFG->noreplyaddress;
4643 $mail->FromName = fullname($from);
4644 if (empty($replyto)) {
4645 $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
4649 if (!empty($replyto)) {
4650 $tempreplyto[] = array($replyto, $replytoname);
4653 $mail->Subject = substr($subject, 0, 900);
4655 $temprecipients[] = array($user->email, fullname($user));
4657 $mail->WordWrap = $wordwrapwidth; // set word wrap
4659 if (!empty($from->customheaders)) { // Add custom headers
4660 if (is_array($from->customheaders)) {
4661 foreach ($from->customheaders as $customheader) {
4662 $mail->AddCustomHeader($customheader);
4664 } else {
4665 $mail->AddCustomHeader($from->customheaders);
4669 if (!empty($from->priority)) {
4670 $mail->Priority = $from->priority;
4673 if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
4674 $mail->IsHTML(true);
4675 $mail->Encoding = 'quoted-printable'; // Encoding to use
4676 $mail->Body = $messagehtml;
4677 $mail->AltBody = "\n$messagetext\n";
4678 } else {
4679 $mail->IsHTML(false);
4680 $mail->Body = "\n$messagetext\n";
4683 if ($attachment && $attachname) {
4684 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
4685 $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
4686 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4687 } else {
4688 require_once($CFG->libdir.'/filelib.php');
4689 $mimetype = mimeinfo('type', $attachname);
4690 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
4694 // Check if the email should be sent in an other charset then the default UTF-8
4695 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
4697 // use the defined site mail charset or eventually the one preferred by the recipient
4698 $charset = $CFG->sitemailcharset;
4699 if (!empty($CFG->allowusermailcharset)) {
4700 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
4701 $charset = $useremailcharset;
4705 // convert all the necessary strings if the charset is supported
4706 $charsets = get_list_of_charsets();
4707 unset($charsets['UTF-8']);
4708 if (in_array($charset, $charsets)) {
4709 $textlib = textlib_get_instance();
4710 $mail->CharSet = $charset;
4711 $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', strtolower($charset));
4712 $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', strtolower($charset));
4713 $mail->Body = $textlib->convert($mail->Body, 'utf-8', strtolower($charset));
4714 $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', strtolower($charset));
4716 foreach ($temprecipients as $key => $values) {
4717 $temprecipients[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
4719 foreach ($tempreplyto as $key => $values) {
4720 $tempreplyto[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
4725 foreach ($temprecipients as $values) {
4726 $mail->AddAddress($values[0], $values[1]);
4728 foreach ($tempreplyto as $values) {
4729 $mail->AddReplyTo($values[0], $values[1]);
4732 if ($mail->Send()) {
4733 set_send_count($user);
4734 $mail->IsSMTP(); // use SMTP directly
4735 if (!empty($mail->SMTPDebug)) {
4736 echo '</pre>';
4738 return true;
4739 } else {
4740 mtrace('ERROR: '. $mail->ErrorInfo);
4741 add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
4742 if (!empty($mail->SMTPDebug)) {
4743 echo '</pre>';
4745 return false;
4750 * Generate a signoff for emails based on support settings
4752 * @global object
4753 * @return string
4755 function generate_email_signoff() {
4756 global $CFG;
4758 $signoff = "\n";
4759 if (!empty($CFG->supportname)) {
4760 $signoff .= $CFG->supportname."\n";
4762 if (!empty($CFG->supportemail)) {
4763 $signoff .= $CFG->supportemail."\n";
4765 if (!empty($CFG->supportpage)) {
4766 $signoff .= $CFG->supportpage."\n";
4768 return $signoff;
4772 * Generate a fake user for emails based on support settings
4773 * @global object
4774 * @return object user info
4776 function generate_email_supportuser() {
4777 global $CFG;
4779 static $supportuser;
4781 if (!empty($supportuser)) {
4782 return $supportuser;
4785 $supportuser = new stdClass();
4786 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
4787 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
4788 $supportuser->lastname = '';
4789 $supportuser->maildisplay = true;
4791 return $supportuser;
4796 * Sets specified user's password and send the new password to the user via email.
4798 * @global object
4799 * @global object
4800 * @param user $user A {@link $USER} object
4801 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
4803 function setnew_password_and_mail($user) {
4804 global $CFG, $DB;
4806 $site = get_site();
4808 $supportuser = generate_email_supportuser();
4810 $newpassword = generate_password();
4812 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
4814 $a = new stdClass();
4815 $a->firstname = fullname($user, true);
4816 $a->sitename = format_string($site->fullname);
4817 $a->username = $user->username;
4818 $a->newpassword = $newpassword;
4819 $a->link = $CFG->wwwroot .'/login/';
4820 $a->signoff = generate_email_signoff();
4822 $message = get_string('newusernewpasswordtext', '', $a);
4824 $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
4826 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4827 return email_to_user($user, $supportuser, $subject, $message);
4832 * Resets specified user's password and send the new password to the user via email.
4834 * @param stdClass $user A {@link $USER} object
4835 * @return bool Returns true if mail was sent OK and false if there was an error.
4837 function reset_password_and_mail($user) {
4838 global $CFG;
4840 $site = get_site();
4841 $supportuser = generate_email_supportuser();
4843 $userauth = get_auth_plugin($user->auth);
4844 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
4845 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4846 return false;
4849 $newpassword = generate_password();
4851 if (!$userauth->user_update_password($user, $newpassword)) {
4852 print_error("cannotsetpassword");
4855 $a = new stdClass();
4856 $a->firstname = $user->firstname;
4857 $a->lastname = $user->lastname;
4858 $a->sitename = format_string($site->fullname);
4859 $a->username = $user->username;
4860 $a->newpassword = $newpassword;
4861 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
4862 $a->signoff = generate_email_signoff();
4864 $message = get_string('newpasswordtext', '', $a);
4866 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
4868 unset_user_preference('create_password', $user); // prevent cron from generating the password
4870 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4871 return email_to_user($user, $supportuser, $subject, $message);
4876 * Send email to specified user with confirmation text and activation link.
4878 * @global object
4879 * @param user $user A {@link $USER} object
4880 * @return bool Returns true if mail was sent OK and false if there was an error.
4882 function send_confirmation_email($user) {
4883 global $CFG;
4885 $site = get_site();
4886 $supportuser = generate_email_supportuser();
4888 $data = new stdClass();
4889 $data->firstname = fullname($user);
4890 $data->sitename = format_string($site->fullname);
4891 $data->admin = generate_email_signoff();
4893 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
4895 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
4896 $message = get_string('emailconfirmation', '', $data);
4897 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4899 $user->mailformat = 1; // Always send HTML version as well
4901 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4902 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4907 * send_password_change_confirmation_email.
4909 * @global object
4910 * @param user $user A {@link $USER} object
4911 * @return bool Returns true if mail was sent OK and false if there was an error.
4913 function send_password_change_confirmation_email($user) {
4914 global $CFG;
4916 $site = get_site();
4917 $supportuser = generate_email_supportuser();
4919 $data = new stdClass();
4920 $data->firstname = $user->firstname;
4921 $data->lastname = $user->lastname;
4922 $data->sitename = format_string($site->fullname);
4923 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
4924 $data->admin = generate_email_signoff();
4926 $message = get_string('emailpasswordconfirmation', '', $data);
4927 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
4929 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4930 return email_to_user($user, $supportuser, $subject, $message);
4935 * send_password_change_info.
4937 * @global object
4938 * @param user $user A {@link $USER} object
4939 * @return bool Returns true if mail was sent OK and false if there was an error.
4941 function send_password_change_info($user) {
4942 global $CFG;
4944 $site = get_site();
4945 $supportuser = generate_email_supportuser();
4946 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4948 $data = new stdClass();
4949 $data->firstname = $user->firstname;
4950 $data->lastname = $user->lastname;
4951 $data->sitename = format_string($site->fullname);
4952 $data->admin = generate_email_signoff();
4954 $userauth = get_auth_plugin($user->auth);
4956 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
4957 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4958 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4959 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4960 return email_to_user($user, $supportuser, $subject, $message);
4963 if ($userauth->can_change_password() and $userauth->change_password_url()) {
4964 // we have some external url for password changing
4965 $data->link .= $userauth->change_password_url();
4967 } else {
4968 //no way to change password, sorry
4969 $data->link = '';
4972 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
4973 $message = get_string('emailpasswordchangeinfo', '', $data);
4974 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4975 } else {
4976 $message = get_string('emailpasswordchangeinfofail', '', $data);
4977 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4980 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4981 return email_to_user($user, $supportuser, $subject, $message);
4986 * Check that an email is allowed. It returns an error message if there
4987 * was a problem.
4989 * @global object
4990 * @param string $email Content of email
4991 * @return string|false
4993 function email_is_not_allowed($email) {
4994 global $CFG;
4996 if (!empty($CFG->allowemailaddresses)) {
4997 $allowed = explode(' ', $CFG->allowemailaddresses);
4998 foreach ($allowed as $allowedpattern) {
4999 $allowedpattern = trim($allowedpattern);
5000 if (!$allowedpattern) {
5001 continue;
5003 if (strpos($allowedpattern, '.') === 0) {
5004 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5005 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5006 return false;
5009 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5010 return false;
5013 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5015 } else if (!empty($CFG->denyemailaddresses)) {
5016 $denied = explode(' ', $CFG->denyemailaddresses);
5017 foreach ($denied as $deniedpattern) {
5018 $deniedpattern = trim($deniedpattern);
5019 if (!$deniedpattern) {
5020 continue;
5022 if (strpos($deniedpattern, '.') === 0) {
5023 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5024 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5025 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5028 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5029 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5034 return false;
5037 /// FILE HANDLING /////////////////////////////////////////////
5040 * Returns local file storage instance
5042 * @return file_storage
5044 function get_file_storage() {
5045 global $CFG;
5047 static $fs = null;
5049 if ($fs) {
5050 return $fs;
5053 require_once("$CFG->libdir/filelib.php");
5055 if (isset($CFG->filedir)) {
5056 $filedir = $CFG->filedir;
5057 } else {
5058 $filedir = $CFG->dataroot.'/filedir';
5061 if (isset($CFG->trashdir)) {
5062 $trashdirdir = $CFG->trashdir;
5063 } else {
5064 $trashdirdir = $CFG->dataroot.'/trashdir';
5067 $fs = new file_storage($filedir, $trashdirdir, "$CFG->dataroot/temp/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5069 return $fs;
5073 * Returns local file storage instance
5075 * @return file_browser
5077 function get_file_browser() {
5078 global $CFG;
5080 static $fb = null;
5082 if ($fb) {
5083 return $fb;
5086 require_once("$CFG->libdir/filelib.php");
5088 $fb = new file_browser();
5090 return $fb;
5094 * Returns file packer
5096 * @param string $mimetype default application/zip
5097 * @return file_packer
5099 function get_file_packer($mimetype='application/zip') {
5100 global $CFG;
5102 static $fp = array();;
5104 if (isset($fp[$mimetype])) {
5105 return $fp[$mimetype];
5108 switch ($mimetype) {
5109 case 'application/zip':
5110 case 'application/vnd.moodle.backup':
5111 $classname = 'zip_packer';
5112 break;
5113 case 'application/x-tar':
5114 // $classname = 'tar_packer';
5115 // break;
5116 default:
5117 return false;
5120 require_once("$CFG->libdir/filestorage/$classname.php");
5121 $fp[$mimetype] = new $classname();
5123 return $fp[$mimetype];
5127 * Returns current name of file on disk if it exists.
5129 * @param string $newfile File to be verified
5130 * @return string Current name of file on disk if true
5132 function valid_uploaded_file($newfile) {
5133 if (empty($newfile)) {
5134 return '';
5136 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5137 return $newfile['tmp_name'];
5138 } else {
5139 return '';
5144 * Returns the maximum size for uploading files.
5146 * There are seven possible upload limits:
5147 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5148 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5149 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5150 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5151 * 5. by the Moodle admin in $CFG->maxbytes
5152 * 6. by the teacher in the current course $course->maxbytes
5153 * 7. by the teacher for the current module, eg $assignment->maxbytes
5155 * These last two are passed to this function as arguments (in bytes).
5156 * Anything defined as 0 is ignored.
5157 * The smallest of all the non-zero numbers is returned.
5159 * @todo Finish documenting this function
5161 * @param int $sizebytes Set maximum size
5162 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5163 * @param int $modulebytes Current module ->maxbytes (in bytes)
5164 * @return int The maximum size for uploading files.
5166 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5168 if (! $filesize = ini_get('upload_max_filesize')) {
5169 $filesize = '5M';
5171 $minimumsize = get_real_size($filesize);
5173 if ($postsize = ini_get('post_max_size')) {
5174 $postsize = get_real_size($postsize);
5175 if ($postsize < $minimumsize) {
5176 $minimumsize = $postsize;
5180 if ($sitebytes and $sitebytes < $minimumsize) {
5181 $minimumsize = $sitebytes;
5184 if ($coursebytes and $coursebytes < $minimumsize) {
5185 $minimumsize = $coursebytes;
5188 if ($modulebytes and $modulebytes < $minimumsize) {
5189 $minimumsize = $modulebytes;
5192 return $minimumsize;
5196 * Returns an array of possible sizes in local language
5198 * Related to {@link get_max_upload_file_size()} - this function returns an
5199 * array of possible sizes in an array, translated to the
5200 * local language.
5202 * @todo Finish documenting this function
5204 * @global object
5205 * @uses SORT_NUMERIC
5206 * @param int $sizebytes Set maximum size
5207 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5208 * @param int $modulebytes Current module ->maxbytes (in bytes)
5209 * @return array
5211 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5212 global $CFG;
5214 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5215 return array();
5218 $filesize[intval($maxsize)] = display_size($maxsize);
5220 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5221 5242880, 10485760, 20971520, 52428800, 104857600);
5223 // Allow maxbytes to be selected if it falls outside the above boundaries
5224 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
5225 // note: get_real_size() is used in order to prevent problems with invalid values
5226 $sizelist[] = get_real_size($CFG->maxbytes);
5229 foreach ($sizelist as $sizebytes) {
5230 if ($sizebytes < $maxsize) {
5231 $filesize[intval($sizebytes)] = display_size($sizebytes);
5235 krsort($filesize, SORT_NUMERIC);
5237 return $filesize;
5241 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
5243 * If excludefiles is defined, then that file/directory is ignored
5244 * If getdirs is true, then (sub)directories are included in the output
5245 * If getfiles is true, then files are included in the output
5246 * (at least one of these must be true!)
5248 * @todo Finish documenting this function. Add examples of $excludefile usage.
5250 * @param string $rootdir A given root directory to start from
5251 * @param string|array $excludefile If defined then the specified file/directory is ignored
5252 * @param bool $descend If true then subdirectories are recursed as well
5253 * @param bool $getdirs If true then (sub)directories are included in the output
5254 * @param bool $getfiles If true then files are included in the output
5255 * @return array An array with all the filenames in
5256 * all subdirectories, relative to the given rootdir
5258 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5260 $dirs = array();
5262 if (!$getdirs and !$getfiles) { // Nothing to show
5263 return $dirs;
5266 if (!is_dir($rootdir)) { // Must be a directory
5267 return $dirs;
5270 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5271 return $dirs;
5274 if (!is_array($excludefiles)) {
5275 $excludefiles = array($excludefiles);
5278 while (false !== ($file = readdir($dir))) {
5279 $firstchar = substr($file, 0, 1);
5280 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5281 continue;
5283 $fullfile = $rootdir .'/'. $file;
5284 if (filetype($fullfile) == 'dir') {
5285 if ($getdirs) {
5286 $dirs[] = $file;
5288 if ($descend) {
5289 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5290 foreach ($subdirs as $subdir) {
5291 $dirs[] = $file .'/'. $subdir;
5294 } else if ($getfiles) {
5295 $dirs[] = $file;
5298 closedir($dir);
5300 asort($dirs);
5302 return $dirs;
5307 * Adds up all the files in a directory and works out the size.
5309 * @todo Finish documenting this function
5311 * @param string $rootdir The directory to start from
5312 * @param string $excludefile A file to exclude when summing directory size
5313 * @return int The summed size of all files and subfiles within the root directory
5315 function get_directory_size($rootdir, $excludefile='') {
5316 global $CFG;
5318 // do it this way if we can, it's much faster
5319 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5320 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5321 $output = null;
5322 $return = null;
5323 exec($command,$output,$return);
5324 if (is_array($output)) {
5325 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5329 if (!is_dir($rootdir)) { // Must be a directory
5330 return 0;
5333 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
5334 return 0;
5337 $size = 0;
5339 while (false !== ($file = readdir($dir))) {
5340 $firstchar = substr($file, 0, 1);
5341 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5342 continue;
5344 $fullfile = $rootdir .'/'. $file;
5345 if (filetype($fullfile) == 'dir') {
5346 $size += get_directory_size($fullfile, $excludefile);
5347 } else {
5348 $size += filesize($fullfile);
5351 closedir($dir);
5353 return $size;
5357 * Converts bytes into display form
5359 * @todo Finish documenting this function. Verify return type.
5361 * @staticvar string $gb Localized string for size in gigabytes
5362 * @staticvar string $mb Localized string for size in megabytes
5363 * @staticvar string $kb Localized string for size in kilobytes
5364 * @staticvar string $b Localized string for size in bytes
5365 * @param int $size The size to convert to human readable form
5366 * @return string
5368 function display_size($size) {
5370 static $gb, $mb, $kb, $b;
5372 if (empty($gb)) {
5373 $gb = get_string('sizegb');
5374 $mb = get_string('sizemb');
5375 $kb = get_string('sizekb');
5376 $b = get_string('sizeb');
5379 if ($size >= 1073741824) {
5380 $size = round($size / 1073741824 * 10) / 10 . $gb;
5381 } else if ($size >= 1048576) {
5382 $size = round($size / 1048576 * 10) / 10 . $mb;
5383 } else if ($size >= 1024) {
5384 $size = round($size / 1024 * 10) / 10 . $kb;
5385 } else {
5386 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
5388 return $size;
5392 * Cleans a given filename by removing suspicious or troublesome characters
5393 * @see clean_param()
5395 * @uses PARAM_FILE
5396 * @param string $string file name
5397 * @return string cleaned file name
5399 function clean_filename($string) {
5400 return clean_param($string, PARAM_FILE);
5404 /// STRING TRANSLATION ////////////////////////////////////////
5407 * Returns the code for the current language
5409 * @return string
5411 function current_language() {
5412 global $CFG, $USER, $SESSION, $COURSE;
5414 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
5415 $return = $COURSE->lang;
5417 } else if (!empty($SESSION->lang)) { // Session language can override other settings
5418 $return = $SESSION->lang;
5420 } else if (!empty($USER->lang)) {
5421 $return = $USER->lang;
5423 } else if (isset($CFG->lang)) {
5424 $return = $CFG->lang;
5426 } else {
5427 $return = 'en';
5430 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
5432 return $return;
5436 * Returns parent language of current active language if defined
5438 * @uses COURSE
5439 * @uses SESSION
5440 * @param string $lang null means current language
5441 * @return string
5443 function get_parent_language($lang=null) {
5444 global $COURSE, $SESSION;
5446 //let's hack around the current language
5447 if (!empty($lang)) {
5448 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
5449 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
5450 $COURSE->lang = '';
5451 $SESSION->lang = $lang;
5454 $parentlang = get_string('parentlanguage', 'langconfig');
5455 if ($parentlang === 'en') {
5456 $parentlang = '';
5459 //let's hack around the current language
5460 if (!empty($lang)) {
5461 $COURSE->lang = $old_course_lang;
5462 $SESSION->lang = $old_session_lang;
5465 return $parentlang;
5469 * Returns current string_manager instance.
5471 * The param $forcereload is needed for CLI installer only where the string_manager instance
5472 * must be replaced during the install.php script life time.
5474 * @param bool $forcereload shall the singleton be released and new instance created instead?
5475 * @return string_manager
5477 function get_string_manager($forcereload=false) {
5478 global $CFG;
5480 static $singleton = null;
5482 if ($forcereload) {
5483 $singleton = null;
5485 if ($singleton === null) {
5486 if (empty($CFG->early_install_lang)) {
5488 if (empty($CFG->langcacheroot)) {
5489 $langcacheroot = $CFG->dataroot . '/cache/lang';
5490 } else {
5491 $langcacheroot = $CFG->langcacheroot;
5494 if (empty($CFG->langlist)) {
5495 $translist = array();
5496 } else {
5497 $translist = explode(',', $CFG->langlist);
5500 if (empty($CFG->langmenucachefile)) {
5501 $langmenucache = $CFG->dataroot . '/cache/languages';
5502 } else {
5503 $langmenucache = $CFG->langmenucachefile;
5506 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
5507 !empty($CFG->langstringcache), $translist, $langmenucache);
5509 } else {
5510 $singleton = new install_string_manager();
5514 return $singleton;
5519 * Interface describing class which is responsible for getting
5520 * of localised strings from language packs.
5522 * @package moodlecore
5523 * @copyright 2010 Petr Skoda (http://skodak.org)
5524 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5526 interface string_manager {
5528 * Get String returns a requested string
5530 * @param string $identifier The identifier of the string to search for
5531 * @param string $component The module the string is associated with
5532 * @param string|object|array $a An object, string or number that can be used
5533 * within translation strings
5534 * @param string $lang moodle translation language, NULL means use current
5535 * @return string The String !
5537 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
5540 * Does the string actually exist?
5542 * get_string() is throwing debug warnings, sometimes we do not want them
5543 * or we want to display better explanation of the problem.
5545 * Use with care!
5547 * @param string $identifier The identifier of the string to search for
5548 * @param string $component The module the string is associated with
5549 * @return boot true if exists
5551 public function string_exists($identifier, $component);
5554 * Returns a localised list of all country names, sorted by country keys.
5555 * @param bool $returnall return all or just enabled
5556 * @param string $lang moodle translation language, NULL means use current
5557 * @return array two-letter country code => translated name.
5559 public function get_list_of_countries($returnall = false, $lang = NULL);
5562 * Returns a localised list of languages, sorted by code keys.
5564 * @param string $lang moodle translation language, NULL means use current
5565 * @param string $standard language list standard
5566 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
5567 * @return array language code => translated name
5569 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
5572 * Does the translation exist?
5574 * @param string $lang moodle translation language code
5575 * @param bool include also disabled translations?
5576 * @return boot true if exists
5578 public function translation_exists($lang, $includeall = true);
5581 * Returns localised list of installed translations
5582 * @param bool $returnall return all or just enabled
5583 * @return array moodle translation code => localised translation name
5585 public function get_list_of_translations($returnall = false);
5588 * Returns localised list of currencies.
5590 * @param string $lang moodle translation language, NULL means use current
5591 * @return array currency code => localised currency name
5593 public function get_list_of_currencies($lang = NULL);
5596 * Load all strings for one component
5597 * @param string $component The module the string is associated with
5598 * @param string $lang
5599 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5600 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5601 * @return array of all string for given component and lang
5603 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
5606 * Invalidates all caches, should the implementation use any
5608 public function reset_caches();
5613 * Standard string_manager implementation
5615 * @package moodlecore
5616 * @copyright 2010 Petr Skoda (http://skodak.org)
5617 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5619 class core_string_manager implements string_manager {
5620 /** @var string location of all packs except 'en' */
5621 protected $otherroot;
5622 /** @var string location of all lang pack local modifications */
5623 protected $localroot;
5624 /** @var string location of on-disk cache of merged strings */
5625 protected $cacheroot;
5626 /** @var array lang string cache - it will be optimised more later */
5627 protected $cache = array();
5628 /** @var int get_string() counter */
5629 protected $countgetstring = 0;
5630 /** @var int in-memory cache hits counter */
5631 protected $countmemcache = 0;
5632 /** @var int on-disk cache hits counter */
5633 protected $countdiskcache = 0;
5634 /** @var bool use disk cache */
5635 protected $usediskcache;
5636 /* @var array limit list of translations */
5637 protected $translist;
5638 /** @var string location of a file that caches the list of available translations */
5639 protected $menucache;
5642 * Create new instance of string manager
5644 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
5645 * @param string $localroot usually the same as $otherroot
5646 * @param string $cacheroot usually lang dir in cache folder
5647 * @param bool $usediskcache use disk cache
5648 * @param array $translist limit list of visible translations
5649 * @param string $menucache the location of a file that caches the list of available translations
5651 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
5652 $this->otherroot = $otherroot;
5653 $this->localroot = $localroot;
5654 $this->cacheroot = $cacheroot;
5655 $this->usediskcache = $usediskcache;
5656 $this->translist = $translist;
5657 $this->menucache = $menucache;
5661 * Returns dependencies of current language, en is not included.
5662 * @param string $lang
5663 * @return array all parents, the lang itself is last
5665 public function get_language_dependencies($lang) {
5666 if ($lang === 'en') {
5667 return array();
5669 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
5670 return array();
5672 $string = array();
5673 include("$this->otherroot/$lang/langconfig.php");
5675 if (empty($string['parentlanguage'])) {
5676 return array($lang);
5677 } else {
5678 $parentlang = $string['parentlanguage'];
5679 unset($string);
5680 return array_merge($this->get_language_dependencies($parentlang), array($lang));
5685 * Load all strings for one component
5686 * @param string $component The module the string is associated with
5687 * @param string $lang
5688 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5689 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5690 * @return array of all string for given component and lang
5692 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
5693 global $CFG;
5695 list($plugintype, $pluginname) = normalize_component($component);
5696 if ($plugintype == 'core' and is_null($pluginname)) {
5697 $component = 'core';
5698 } else {
5699 $component = $plugintype . '_' . $pluginname;
5702 if (!$disablecache) {
5703 // try in-memory cache first
5704 if (isset($this->cache[$lang][$component])) {
5705 $this->countmemcache++;
5706 return $this->cache[$lang][$component];
5709 // try on-disk cache then
5710 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
5711 $this->countdiskcache++;
5712 include($this->cacheroot . "/$lang/$component.php");
5713 return $this->cache[$lang][$component];
5717 // no cache found - let us merge all possible sources of the strings
5718 if ($plugintype === 'core') {
5719 $file = $pluginname;
5720 if ($file === null) {
5721 $file = 'moodle';
5723 $string = array();
5724 // first load english pack
5725 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
5726 return array();
5728 include("$CFG->dirroot/lang/en/$file.php");
5729 $originalkeys = array_keys($string);
5730 $originalkeys = array_flip($originalkeys);
5732 // and then corresponding local if present and allowed
5733 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5734 include("$this->localroot/en_local/$file.php");
5736 // now loop through all langs in correct order
5737 $deps = $this->get_language_dependencies($lang);
5738 foreach ($deps as $dep) {
5739 // the main lang string location
5740 if (file_exists("$this->otherroot/$dep/$file.php")) {
5741 include("$this->otherroot/$dep/$file.php");
5743 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5744 include("$this->localroot/{$dep}_local/$file.php");
5748 } else {
5749 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
5750 return array();
5752 if ($plugintype === 'mod') {
5753 // bloody mod hack
5754 $file = $pluginname;
5755 } else {
5756 $file = $plugintype . '_' . $pluginname;
5758 $string = array();
5759 // first load English pack
5760 if (!file_exists("$location/lang/en/$file.php")) {
5761 //English pack does not exist, so do not try to load anything else
5762 return array();
5764 include("$location/lang/en/$file.php");
5765 $originalkeys = array_keys($string);
5766 $originalkeys = array_flip($originalkeys);
5767 // and then corresponding local english if present
5768 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5769 include("$this->localroot/en_local/$file.php");
5772 // now loop through all langs in correct order
5773 $deps = $this->get_language_dependencies($lang);
5774 foreach ($deps as $dep) {
5775 // legacy location - used by contrib only
5776 if (file_exists("$location/lang/$dep/$file.php")) {
5777 include("$location/lang/$dep/$file.php");
5779 // the main lang string location
5780 if (file_exists("$this->otherroot/$dep/$file.php")) {
5781 include("$this->otherroot/$dep/$file.php");
5783 // local customisations
5784 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5785 include("$this->localroot/{$dep}_local/$file.php");
5790 // we do not want any extra strings from other languages - everything must be in en lang pack
5791 $string = array_intersect_key($string, $originalkeys);
5793 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
5794 // caches so we do not need to do all this merging and dependencies resolving again
5795 $this->cache[$lang][$component] = $string;
5796 if ($this->usediskcache) {
5797 check_dir_exists("$this->cacheroot/$lang");
5798 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
5800 return $string;
5804 * Does the string actually exist?
5806 * get_string() is throwing debug warnings, sometimes we do not want them
5807 * or we want to display better explanation of the problem.
5809 * Use with care!
5811 * @param string $identifier The identifier of the string to search for
5812 * @param string $component The module the string is associated with
5813 * @return boot true if exists
5815 public function string_exists($identifier, $component) {
5816 $identifier = clean_param($identifier, PARAM_STRINGID);
5817 if (empty($identifier)) {
5818 return false;
5820 $lang = current_language();
5821 $string = $this->load_component_strings($component, $lang);
5822 return isset($string[$identifier]);
5826 * Get String returns a requested string
5828 * @param string $identifier The identifier of the string to search for
5829 * @param string $component The module the string is associated with
5830 * @param string|object|array $a An object, string or number that can be used
5831 * within translation strings
5832 * @param string $lang moodle translation language, NULL means use current
5833 * @return string The String !
5835 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
5836 $this->countgetstring++;
5837 // there are very many uses of these time formating strings without the 'langconfig' component,
5838 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
5839 static $langconfigstrs = array(
5840 'strftimedate' => 1,
5841 'strftimedatefullshort' => 1,
5842 'strftimedateshort' => 1,
5843 'strftimedatetime' => 1,
5844 'strftimedatetimeshort' => 1,
5845 'strftimedaydate' => 1,
5846 'strftimedaydatetime' => 1,
5847 'strftimedayshort' => 1,
5848 'strftimedaytime' => 1,
5849 'strftimemonthyear' => 1,
5850 'strftimerecent' => 1,
5851 'strftimerecentfull' => 1,
5852 'strftimetime' => 1);
5854 if (empty($component)) {
5855 if (isset($langconfigstrs[$identifier])) {
5856 $component = 'langconfig';
5857 } else {
5858 $component = 'moodle';
5862 if ($lang === NULL) {
5863 $lang = current_language();
5866 $string = $this->load_component_strings($component, $lang);
5868 if (!isset($string[$identifier])) {
5869 if ($component === 'pix' or $component === 'core_pix') {
5870 // this component contains only alt tags for emoticons,
5871 // not all of them are supposed to be defined
5872 return '';
5874 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
5875 // parentlanguage is a special string, undefined means use English if not defined
5876 return 'en';
5878 if ($this->usediskcache) {
5879 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources
5880 $string = $this->load_component_strings($component, $lang, true);
5882 if (!isset($string[$identifier])) {
5883 // the string is still missing - should be fixed by developer
5884 debugging("Invalid get_string() identifier: '$identifier' or component '$component'", DEBUG_DEVELOPER);
5885 return "[[$identifier]]";
5889 $string = $string[$identifier];
5891 if ($a !== NULL) {
5892 if (is_object($a) or is_array($a)) {
5893 $a = (array)$a;
5894 $search = array();
5895 $replace = array();
5896 foreach ($a as $key=>$value) {
5897 if (is_int($key)) {
5898 // we do not support numeric keys - sorry!
5899 continue;
5901 if (is_object($value) or is_array($value)) {
5902 // we support just string as value
5903 continue;
5905 $search[] = '{$a->'.$key.'}';
5906 $replace[] = (string)$value;
5908 if ($search) {
5909 $string = str_replace($search, $replace, $string);
5911 } else {
5912 $string = str_replace('{$a}', (string)$a, $string);
5916 return $string;
5920 * Returns information about the string_manager performance
5921 * @return array
5923 public function get_performance_summary() {
5924 return array(array(
5925 'langcountgetstring' => $this->countgetstring,
5926 'langcountmemcache' => $this->countmemcache,
5927 'langcountdiskcache' => $this->countdiskcache,
5928 ), array(
5929 'langcountgetstring' => 'get_string calls',
5930 'langcountmemcache' => 'strings mem cache hits',
5931 'langcountdiskcache' => 'strings disk cache hits',
5936 * Returns a localised list of all country names, sorted by localised name.
5938 * @param bool $returnall return all or just enabled
5939 * @param string $lang moodle translation language, NULL means use current
5940 * @return array two-letter country code => translated name.
5942 public function get_list_of_countries($returnall = false, $lang = NULL) {
5943 global $CFG;
5945 if ($lang === NULL) {
5946 $lang = current_language();
5949 $countries = $this->load_component_strings('core_countries', $lang);
5950 textlib_get_instance()->asort($countries);
5951 if (!$returnall and !empty($CFG->allcountrycodes)) {
5952 $enabled = explode(',', $CFG->allcountrycodes);
5953 $return = array();
5954 foreach ($enabled as $c) {
5955 if (isset($countries[$c])) {
5956 $return[$c] = $countries[$c];
5959 return $return;
5962 return $countries;
5966 * Returns a localised list of languages, sorted by code keys.
5968 * @param string $lang moodle translation language, NULL means use current
5969 * @param string $standard language list standard
5970 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
5971 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
5972 * @return array language code => translated name
5974 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
5975 if ($lang === NULL) {
5976 $lang = current_language();
5979 if ($standard === 'iso6392') {
5980 $langs = $this->load_component_strings('core_iso6392', $lang);
5981 ksort($langs);
5982 return $langs;
5984 } else if ($standard === 'iso6391') {
5985 $langs2 = $this->load_component_strings('core_iso6392', $lang);
5986 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
5987 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
5988 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
5989 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
5990 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
5991 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
5992 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
5993 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
5994 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
5995 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
5996 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
5997 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
5998 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
5999 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6000 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6001 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6002 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6003 $langs1 = array();
6004 foreach ($mapping as $c2=>$c1) {
6005 $langs1[$c1] = $langs2[$c2];
6007 ksort($langs1);
6008 return $langs1;
6010 } else {
6011 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6014 return array();
6018 * Does the translation exist?
6020 * @param string $lang moodle translation language code
6021 * @param bool include also disabled translations?
6022 * @return boot true if exists
6024 public function translation_exists($lang, $includeall = true) {
6026 if (strpos($lang, '_local') !== false) {
6027 // _local packs are not real translations
6028 return false;
6030 if (!$includeall and !empty($this->translist)) {
6031 if (!in_array($lang, $this->translist)) {
6032 return false;
6035 if ($lang === 'en') {
6036 // part of distribution
6037 return true;
6039 return file_exists("$this->otherroot/$lang/langconfig.php");
6043 * Returns localised list of installed translations
6044 * @param bool $returnall return all or just enabled
6045 * @return array moodle translation code => localised translation name
6047 public function get_list_of_translations($returnall = false) {
6048 global $CFG;
6050 $languages = array();
6052 if (!empty($CFG->langcache) and is_readable($this->menucache)) {
6053 // try to re-use the cached list of all available languages
6054 $cachedlist = json_decode(file_get_contents($this->menucache), true);
6056 if (is_array($cachedlist) and !empty($cachedlist)) {
6057 // the cache file is restored correctly
6059 if (!$returnall and !empty($this->translist)) {
6060 // return just enabled translations
6061 foreach ($cachedlist as $langcode => $langname) {
6062 if (in_array($langcode, $this->translist)) {
6063 $languages[$langcode] = $langname;
6066 return $languages;
6068 } else {
6069 // return all translations
6070 return $cachedlist;
6075 // the cached list of languages is not available, let us populate the list
6077 if (!$returnall and !empty($this->translist)) {
6078 // return only some translations
6079 foreach ($this->translist as $lang) {
6080 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6081 if (strstr($lang, '_local') !== false) {
6082 continue;
6084 if (strstr($lang, '_utf8') !== false) {
6085 continue;
6087 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6088 // some broken or missing lang - can not switch to it anyway
6089 continue;
6091 $string = $this->load_component_strings('langconfig', $lang);
6092 if (!empty($string['thislanguage'])) {
6093 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6095 unset($string);
6098 } else {
6099 // return all languages available in system
6100 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6102 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6103 // Sort all
6105 // Loop through all langs and get info
6106 foreach ($langdirs as $lang) {
6107 if (strstr($lang, '_local') !== false) {
6108 continue;
6110 if (strstr($lang, '_utf8') !== false) {
6111 continue;
6113 $string = $this->load_component_strings('langconfig', $lang);
6114 if (!empty($string['thislanguage'])) {
6115 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6117 unset($string);
6120 if (!empty($CFG->langcache) and !empty($this->menucache)) {
6121 // cache the list so that it can be used next time
6122 textlib_get_instance()->asort($languages);
6123 file_put_contents($this->menucache, json_encode($languages));
6127 textlib_get_instance()->asort($languages);
6129 return $languages;
6133 * Returns localised list of currencies.
6135 * @param string $lang moodle translation language, NULL means use current
6136 * @return array currency code => localised currency name
6138 public function get_list_of_currencies($lang = NULL) {
6139 if ($lang === NULL) {
6140 $lang = current_language();
6143 $currencies = $this->load_component_strings('core_currencies', $lang);
6144 asort($currencies);
6146 return $currencies;
6150 * Clears both in-memory and on-disk caches
6152 public function reset_caches() {
6153 global $CFG;
6154 require_once("$CFG->libdir/filelib.php");
6156 // clear the on-disk disk with aggregated string files
6157 fulldelete($this->cacheroot);
6159 // clear the in-memory cache of loaded strings
6160 $this->cache = array();
6162 // clear the cache containing the list of available translations
6163 // and re-populate it again
6164 fulldelete($this->menucache);
6165 $this->get_list_of_translations(true);
6171 * Minimalistic string fetching implementation
6172 * that is used in installer before we fetch the wanted
6173 * language pack from moodle.org lang download site.
6175 * @package moodlecore
6176 * @copyright 2010 Petr Skoda (http://skodak.org)
6177 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6179 class install_string_manager implements string_manager {
6180 /** @var string location of pre-install packs for all langs */
6181 protected $installroot;
6184 * Crate new instance of install string manager
6186 public function __construct() {
6187 global $CFG;
6188 $this->installroot = "$CFG->dirroot/install/lang";
6192 * Load all strings for one component
6193 * @param string $component The module the string is associated with
6194 * @param string $lang
6195 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6196 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6197 * @return array of all string for given component and lang
6199 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6200 // not needed in installer
6201 return array();
6205 * Does the string actually exist?
6207 * get_string() is throwing debug warnings, sometimes we do not want them
6208 * or we want to display better explanation of the problem.
6210 * Use with care!
6212 * @param string $identifier The identifier of the string to search for
6213 * @param string $component The module the string is associated with
6214 * @return boot true if exists
6216 public function string_exists($identifier, $component) {
6217 $identifier = clean_param($identifier, PARAM_STRINGID);
6218 if (empty($identifier)) {
6219 return false;
6221 // simple old style hack ;)
6222 $str = get_string($identifier, $component);
6223 return (strpos($str, '[[') === false);
6227 * Get String returns a requested string
6229 * @param string $identifier The identifier of the string to search for
6230 * @param string $component The module the string is associated with
6231 * @param string|object|array $a An object, string or number that can be used
6232 * within translation strings
6233 * @param string $lang moodle translation language, NULL means use current
6234 * @return string The String !
6236 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6237 if (!$component) {
6238 $component = 'moodle';
6241 if ($lang === NULL) {
6242 $lang = current_language();
6245 //get parent lang
6246 $parent = '';
6247 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
6248 if (file_exists("$this->installroot/$lang/langconfig.php")) {
6249 $string = array();
6250 include("$this->installroot/$lang/langconfig.php");
6251 if (isset($string['parentlanguage'])) {
6252 $parent = $string['parentlanguage'];
6254 unset($string);
6258 // include en string first
6259 if (!file_exists("$this->installroot/en/$component.php")) {
6260 return "[[$identifier]]";
6262 $string = array();
6263 include("$this->installroot/en/$component.php");
6265 // now override en with parent if defined
6266 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
6267 include("$this->installroot/$parent/$component.php");
6270 // finally override with requested language
6271 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
6272 include("$this->installroot/$lang/$component.php");
6275 if (!isset($string[$identifier])) {
6276 return "[[$identifier]]";
6279 $string = $string[$identifier];
6281 if ($a !== NULL) {
6282 if (is_object($a) or is_array($a)) {
6283 $a = (array)$a;
6284 $search = array();
6285 $replace = array();
6286 foreach ($a as $key=>$value) {
6287 if (is_int($key)) {
6288 // we do not support numeric keys - sorry!
6289 continue;
6291 $search[] = '{$a->'.$key.'}';
6292 $replace[] = (string)$value;
6294 if ($search) {
6295 $string = str_replace($search, $replace, $string);
6297 } else {
6298 $string = str_replace('{$a}', (string)$a, $string);
6302 return $string;
6306 * Returns a localised list of all country names, sorted by country keys.
6308 * @param bool $returnall return all or just enabled
6309 * @param string $lang moodle translation language, NULL means use current
6310 * @return array two-letter country code => translated name.
6312 public function get_list_of_countries($returnall = false, $lang = NULL) {
6313 //not used in installer
6314 return array();
6318 * Returns a localised list of languages, sorted by code keys.
6320 * @param string $lang moodle translation language, NULL means use current
6321 * @param string $standard language list standard
6322 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6323 * @return array language code => translated name
6325 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
6326 //not used in installer
6327 return array();
6331 * Does the translation exist?
6333 * @param string $lang moodle translation language code
6334 * @param bool include also disabled translations?
6335 * @return boot true if exists
6337 public function translation_exists($lang, $includeall = true) {
6338 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
6342 * Returns localised list of installed translations
6343 * @param bool $returnall return all or just enabled
6344 * @return array moodle translation code => localised translation name
6346 public function get_list_of_translations($returnall = false) {
6347 // return all is ignored here - we need to know all langs in installer
6348 $languages = array();
6349 // Get raw list of lang directories
6350 $langdirs = get_list_of_plugins('install/lang');
6351 asort($langdirs);
6352 // Get some info from each lang
6353 foreach ($langdirs as $lang) {
6354 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
6355 $string = array();
6356 include($this->installroot.'/'.$lang.'/langconfig.php');
6357 if (!empty($string['thislanguage'])) {
6358 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
6362 // Return array
6363 return $languages;
6367 * Returns localised list of currencies.
6369 * @param string $lang moodle translation language, NULL means use current
6370 * @return array currency code => localised currency name
6372 public function get_list_of_currencies($lang = NULL) {
6373 // not used in installer
6374 return array();
6378 * This implementation does not use any caches
6380 public function reset_caches() {}
6385 * Returns a localized string.
6387 * Returns the translated string specified by $identifier as
6388 * for $module. Uses the same format files as STphp.
6389 * $a is an object, string or number that can be used
6390 * within translation strings
6392 * eg 'hello {$a->firstname} {$a->lastname}'
6393 * or 'hello {$a}'
6395 * If you would like to directly echo the localized string use
6396 * the function {@link print_string()}
6398 * Example usage of this function involves finding the string you would
6399 * like a local equivalent of and using its identifier and module information
6400 * to retrieve it.<br/>
6401 * If you open moodle/lang/en/moodle.php and look near line 278
6402 * you will find a string to prompt a user for their word for 'course'
6403 * <code>
6404 * $string['course'] = 'Course';
6405 * </code>
6406 * So if you want to display the string 'Course'
6407 * in any language that supports it on your site
6408 * you just need to use the identifier 'course'
6409 * <code>
6410 * $mystring = '<strong>'. get_string('course') .'</strong>';
6411 * or
6412 * </code>
6413 * If the string you want is in another file you'd take a slightly
6414 * different approach. Looking in moodle/lang/en/calendar.php you find
6415 * around line 75:
6416 * <code>
6417 * $string['typecourse'] = 'Course event';
6418 * </code>
6419 * If you want to display the string "Course event" in any language
6420 * supported you would use the identifier 'typecourse' and the module 'calendar'
6421 * (because it is in the file calendar.php):
6422 * <code>
6423 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
6424 * </code>
6426 * As a last resort, should the identifier fail to map to a string
6427 * the returned string will be [[ $identifier ]]
6429 * @param string $identifier The key identifier for the localized string
6430 * @param string $component The module where the key identifier is stored,
6431 * usually expressed as the filename in the language pack without the
6432 * .php on the end but can also be written as mod/forum or grade/export/xls.
6433 * If none is specified then moodle.php is used.
6434 * @param string|object|array $a An object, string or number that can be used
6435 * within translation strings
6436 * @return string The localized string.
6438 function get_string($identifier, $component = '', $a = NULL) {
6440 $identifier = clean_param($identifier, PARAM_STRINGID);
6441 if (empty($identifier)) {
6442 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');
6445 if (func_num_args() > 3) {
6446 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
6449 if (strpos($component, '/') !== false) {
6450 debugging('The module name you passed to get_string is the deprecated format ' .
6451 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
6452 $componentpath = explode('/', $component);
6454 switch ($componentpath[0]) {
6455 case 'mod':
6456 $component = $componentpath[1];
6457 break;
6458 case 'blocks':
6459 case 'block':
6460 $component = 'block_'.$componentpath[1];
6461 break;
6462 case 'enrol':
6463 $component = 'enrol_'.$componentpath[1];
6464 break;
6465 case 'format':
6466 $component = 'format_'.$componentpath[1];
6467 break;
6468 case 'grade':
6469 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
6470 break;
6474 return get_string_manager()->get_string($identifier, $component, $a);
6478 * Converts an array of strings to their localized value.
6480 * @param array $array An array of strings
6481 * @param string $module The language module that these strings can be found in.
6482 * @return array and array of translated strings.
6484 function get_strings($array, $component = '') {
6485 $string = new stdClass;
6486 foreach ($array as $item) {
6487 $string->$item = get_string($item, $component);
6489 return $string;
6493 * Prints out a translated string.
6495 * Prints out a translated string using the return value from the {@link get_string()} function.
6497 * Example usage of this function when the string is in the moodle.php file:<br/>
6498 * <code>
6499 * echo '<strong>';
6500 * print_string('course');
6501 * echo '</strong>';
6502 * </code>
6504 * Example usage of this function when the string is not in the moodle.php file:<br/>
6505 * <code>
6506 * echo '<h1>';
6507 * print_string('typecourse', 'calendar');
6508 * echo '</h1>';
6509 * </code>
6511 * @param string $identifier The key identifier for the localized string
6512 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
6513 * @param mixed $a An object, string or number that can be used within translation strings
6515 function print_string($identifier, $component = '', $a = NULL) {
6516 echo get_string($identifier, $component, $a);
6520 * Returns a list of charset codes
6522 * Returns a list of charset codes. It's hardcoded, so they should be added manually
6523 * (checking that such charset is supported by the texlib library!)
6525 * @return array And associative array with contents in the form of charset => charset
6527 function get_list_of_charsets() {
6529 $charsets = array(
6530 'EUC-JP' => 'EUC-JP',
6531 'ISO-2022-JP'=> 'ISO-2022-JP',
6532 'ISO-8859-1' => 'ISO-8859-1',
6533 'SHIFT-JIS' => 'SHIFT-JIS',
6534 'GB2312' => 'GB2312',
6535 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
6536 'UTF-8' => 'UTF-8');
6538 asort($charsets);
6540 return $charsets;
6544 * Returns a list of valid and compatible themes
6546 * @global object
6547 * @return array
6549 function get_list_of_themes() {
6550 global $CFG;
6552 $themes = array();
6554 if (!empty($CFG->themelist)) { // use admin's list of themes
6555 $themelist = explode(',', $CFG->themelist);
6556 } else {
6557 $themelist = array_keys(get_plugin_list("theme"));
6560 foreach ($themelist as $key => $themename) {
6561 $theme = theme_config::load($themename);
6562 $themes[$themename] = $theme;
6564 asort($themes);
6566 return $themes;
6570 * Returns a list of timezones in the current language
6572 * @global object
6573 * @global object
6574 * @return array
6576 function get_list_of_timezones() {
6577 global $CFG, $DB;
6579 static $timezones;
6581 if (!empty($timezones)) { // This function has been called recently
6582 return $timezones;
6585 $timezones = array();
6587 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
6588 foreach($rawtimezones as $timezone) {
6589 if (!empty($timezone->name)) {
6590 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
6591 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
6592 } else {
6593 $timezones[$timezone->name] = $timezone->name;
6595 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
6596 $timezones[$timezone->name] = $timezone->name;
6602 asort($timezones);
6604 for ($i = -13; $i <= 13; $i += .5) {
6605 $tzstring = 'UTC';
6606 if ($i < 0) {
6607 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
6608 } else if ($i > 0) {
6609 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
6610 } else {
6611 $timezones[sprintf("%.1f", $i)] = $tzstring;
6615 return $timezones;
6619 * Factory function for emoticon_manager
6621 * @return emoticon_manager singleton
6623 function get_emoticon_manager() {
6624 static $singleton = null;
6626 if (is_null($singleton)) {
6627 $singleton = new emoticon_manager();
6630 return $singleton;
6634 * Provides core support for plugins that have to deal with
6635 * emoticons (like HTML editor or emoticon filter).
6637 * Whenever this manager mentiones 'emoticon object', the following data
6638 * structure is expected: stdClass with properties text, imagename, imagecomponent,
6639 * altidentifier and altcomponent
6641 * @see admin_setting_emoticons
6643 class emoticon_manager {
6646 * Returns the currently enabled emoticons
6648 * @return array of emoticon objects
6650 public function get_emoticons() {
6651 global $CFG;
6653 if (empty($CFG->emoticons)) {
6654 return array();
6657 $emoticons = $this->decode_stored_config($CFG->emoticons);
6659 if (!is_array($emoticons)) {
6660 // something is wrong with the format of stored setting
6661 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
6662 return array();
6665 return $emoticons;
6669 * Converts emoticon object into renderable pix_emoticon object
6671 * @param stdClass $emoticon emoticon object
6672 * @param array $attributes explicit HTML attributes to set
6673 * @return pix_emoticon
6675 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
6676 $stringmanager = get_string_manager();
6677 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
6678 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
6679 } else {
6680 $alt = s($emoticon->text);
6682 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
6686 * Encodes the array of emoticon objects into a string storable in config table
6688 * @see self::decode_stored_config()
6689 * @param array $emoticons array of emtocion objects
6690 * @return string
6692 public function encode_stored_config(array $emoticons) {
6693 return json_encode($emoticons);
6697 * Decodes the string into an array of emoticon objects
6699 * @see self::encode_stored_config()
6700 * @param string $encoded
6701 * @return string|null
6703 public function decode_stored_config($encoded) {
6704 $decoded = json_decode($encoded);
6705 if (!is_array($decoded)) {
6706 return null;
6708 return $decoded;
6712 * Returns default set of emoticons supported by Moodle
6714 * @return array of sdtClasses
6716 public function default_emoticons() {
6717 return array(
6718 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
6719 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
6720 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
6721 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
6722 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
6723 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
6724 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
6725 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
6726 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
6727 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
6728 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
6729 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
6730 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
6731 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
6732 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
6733 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
6734 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
6735 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
6736 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
6737 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
6738 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
6739 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
6740 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
6741 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
6742 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
6743 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
6744 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
6745 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
6746 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
6747 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
6752 * Helper method preparing the stdClass with the emoticon properties
6754 * @param string|array $text or array of strings
6755 * @param string $imagename to be used by {@see pix_emoticon}
6756 * @param string $altidentifier alternative string identifier, null for no alt
6757 * @param array $altcomponent where the alternative string is defined
6758 * @param string $imagecomponent to be used by {@see pix_emoticon}
6759 * @return stdClass
6761 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
6762 return (object)array(
6763 'text' => $text,
6764 'imagename' => $imagename,
6765 'imagecomponent' => $imagecomponent,
6766 'altidentifier' => $altidentifier,
6767 'altcomponent' => $altcomponent,
6772 /// ENCRYPTION ////////////////////////////////////////////////
6775 * rc4encrypt
6777 * @todo Finish documenting this function
6779 * @param string $data Data to encrypt
6780 * @return string The now encrypted data
6782 function rc4encrypt($data) {
6783 $password = 'nfgjeingjk';
6784 return endecrypt($password, $data, '');
6788 * rc4decrypt
6790 * @todo Finish documenting this function
6792 * @param string $data Data to decrypt
6793 * @return string The now decrypted data
6795 function rc4decrypt($data) {
6796 $password = 'nfgjeingjk';
6797 return endecrypt($password, $data, 'de');
6801 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6803 * @todo Finish documenting this function
6805 * @param string $pwd The password to use when encrypting or decrypting
6806 * @param string $data The data to be decrypted/encrypted
6807 * @param string $case Either 'de' for decrypt or '' for encrypt
6808 * @return string
6810 function endecrypt ($pwd, $data, $case) {
6812 if ($case == 'de') {
6813 $data = urldecode($data);
6816 $key[] = '';
6817 $box[] = '';
6818 $temp_swap = '';
6819 $pwd_length = 0;
6821 $pwd_length = strlen($pwd);
6823 for ($i = 0; $i <= 255; $i++) {
6824 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
6825 $box[$i] = $i;
6828 $x = 0;
6830 for ($i = 0; $i <= 255; $i++) {
6831 $x = ($x + $box[$i] + $key[$i]) % 256;
6832 $temp_swap = $box[$i];
6833 $box[$i] = $box[$x];
6834 $box[$x] = $temp_swap;
6837 $temp = '';
6838 $k = '';
6840 $cipherby = '';
6841 $cipher = '';
6843 $a = 0;
6844 $j = 0;
6846 for ($i = 0; $i < strlen($data); $i++) {
6847 $a = ($a + 1) % 256;
6848 $j = ($j + $box[$a]) % 256;
6849 $temp = $box[$a];
6850 $box[$a] = $box[$j];
6851 $box[$j] = $temp;
6852 $k = $box[(($box[$a] + $box[$j]) % 256)];
6853 $cipherby = ord(substr($data, $i, 1)) ^ $k;
6854 $cipher .= chr($cipherby);
6857 if ($case == 'de') {
6858 $cipher = urldecode(urlencode($cipher));
6859 } else {
6860 $cipher = urlencode($cipher);
6863 return $cipher;
6866 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6869 * Returns the exact absolute path to plugin directory.
6871 * @param string $plugintype type of plugin
6872 * @param string $name name of the plugin
6873 * @return string full path to plugin directory; NULL if not found
6875 function get_plugin_directory($plugintype, $name) {
6876 global $CFG;
6878 if ($plugintype === '') {
6879 $plugintype = 'mod';
6882 $types = get_plugin_types(true);
6883 if (!array_key_exists($plugintype, $types)) {
6884 return NULL;
6886 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
6888 if (!empty($CFG->themedir) and $plugintype === 'theme') {
6889 if (!is_dir($types['theme'] . '/' . $name)) {
6890 // ok, so the theme is supposed to be in the $CFG->themedir
6891 return $CFG->themedir . '/' . $name;
6895 return $types[$plugintype].'/'.$name;
6899 * Return exact absolute path to a plugin directory,
6900 * this method support "simpletest_" prefix designed for unit testing.
6902 * @param string $component name such as 'moodle', 'mod_forum' or special simpletest value
6903 * @return string full path to component directory; NULL if not found
6905 function get_component_directory($component) {
6906 global $CFG;
6908 $simpletest = false;
6909 if (strpos($component, 'simpletest_') === 0) {
6910 $subdir = substr($component, strlen('simpletest_'));
6911 //TODO: this looks borked, where is it used actually?
6912 return $subdir;
6915 list($type, $plugin) = normalize_component($component);
6917 if ($type === 'core') {
6918 if ($plugin === NULL ) {
6919 $path = $CFG->libdir;
6920 } else {
6921 $subsystems = get_core_subsystems();
6922 if (isset($subsystems[$plugin])) {
6923 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
6924 } else {
6925 $path = NULL;
6929 } else {
6930 $path = get_plugin_directory($type, $plugin);
6933 return $path;
6937 * Normalize the component name using the "frankenstyle" names.
6938 * @param string $component
6939 * @return array $type+$plugin elements
6941 function normalize_component($component) {
6942 if ($component === 'moodle' or $component === 'core') {
6943 $type = 'core';
6944 $plugin = NULL;
6946 } else if (strpos($component, '_') === false) {
6947 $subsystems = get_core_subsystems();
6948 if (array_key_exists($component, $subsystems)) {
6949 $type = 'core';
6950 $plugin = $component;
6951 } else {
6952 // everything else is a module
6953 $type = 'mod';
6954 $plugin = $component;
6957 } else {
6958 list($type, $plugin) = explode('_', $component, 2);
6959 $plugintypes = get_plugin_types(false);
6960 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
6961 $type = 'mod';
6962 $plugin = $component;
6966 return array($type, $plugin);
6970 * List all core subsystems and their location
6972 * This is a whitelist of components that are part of the core and their
6973 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
6974 * plugin is not listed here and it does not have proper plugintype prefix,
6975 * then it is considered as course activity module.
6977 * The location is dirroot relative path. NULL means there is no special
6978 * directory for this subsystem. If the location is set, the subsystem's
6979 * renderer.php is expected to be there.
6981 * @return array of (string)name => (string|null)location
6983 function get_core_subsystems() {
6984 global $CFG;
6986 static $info = null;
6988 if (!$info) {
6989 $info = array(
6990 'access' => NULL,
6991 'admin' => $CFG->admin,
6992 'auth' => 'auth',
6993 'backup' => 'backup/util/ui',
6994 'block' => 'blocks',
6995 'blog' => 'blog',
6996 'bulkusers' => NULL,
6997 'calendar' => 'calendar',
6998 'cohort' => 'cohort',
6999 'condition' => NULL,
7000 'completion' => NULL,
7001 'countries' => NULL,
7002 'course' => 'course',
7003 'currencies' => NULL,
7004 'dbtransfer' => NULL,
7005 'debug' => NULL,
7006 'dock' => NULL,
7007 'editor' => 'lib/editor',
7008 'edufields' => NULL,
7009 'enrol' => 'enrol',
7010 'error' => NULL,
7011 'filepicker' => NULL,
7012 'files' => 'files',
7013 'filters' => NULL,
7014 'flashdetect' => NULL,
7015 'fonts' => NULL,
7016 'form' => 'lib/form',
7017 'grades' => 'grade',
7018 'group' => 'group',
7019 'help' => NULL,
7020 'hub' => NULL,
7021 'imscc' => NULL,
7022 'install' => NULL,
7023 'iso6392' => NULL,
7024 'langconfig' => NULL,
7025 'license' => NULL,
7026 'message' => 'message',
7027 'mimetypes' => NULL,
7028 'mnet' => 'mnet',
7029 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7030 'my' => 'my',
7031 'notes' => 'notes',
7032 'pagetype' => NULL,
7033 'pix' => NULL,
7034 'plagiarism' => 'plagiarism',
7035 'portfolio' => 'portfolio',
7036 'publish' => 'course/publish',
7037 'question' => 'question',
7038 'rating' => 'rating',
7039 'register' => 'admin/registration',
7040 'repository' => 'repository',
7041 'rss' => 'rss',
7042 'role' => $CFG->admin.'/role',
7043 'simpletest' => NULL,
7044 'search' => 'search',
7045 'table' => NULL,
7046 'tag' => 'tag',
7047 'timezones' => NULL,
7048 'user' => 'user',
7049 'userkey' => NULL,
7050 'webservice' => 'webservice',
7051 'xmldb' => NULL,
7055 return $info;
7059 * Lists all plugin types
7060 * @param bool $fullpaths false means relative paths from dirroot
7061 * @return array Array of strings - name=>location
7063 function get_plugin_types($fullpaths=true) {
7064 global $CFG;
7066 static $info = null;
7067 static $fullinfo = null;
7069 if (!$info) {
7070 $info = array('mod' => 'mod',
7071 'auth' => 'auth',
7072 'enrol' => 'enrol',
7073 'message' => 'message/output',
7074 'block' => 'blocks',
7075 'filter' => 'filter',
7076 'editor' => 'lib/editor',
7077 'format' => 'course/format',
7078 'profilefield' => 'user/profile/field',
7079 'report' => $CFG->admin.'/report',
7080 'coursereport' => 'course/report', // must be after system reports
7081 'gradeexport' => 'grade/export',
7082 'gradeimport' => 'grade/import',
7083 'gradereport' => 'grade/report',
7084 'mnetservice' => 'mnet/service',
7085 'webservice' => 'webservice',
7086 'repository' => 'repository',
7087 'portfolio' => 'portfolio',
7088 'qtype' => 'question/type',
7089 'qformat' => 'question/format',
7090 'plagiarism' => 'plagiarism',
7091 'theme' => 'theme'); // this is a bit hacky, themes may be in $CFG->themedir too
7093 $mods = get_plugin_list('mod');
7094 foreach ($mods as $mod => $moddir) {
7095 if (file_exists("$moddir/db/subplugins.php")) {
7096 $subplugins = array();
7097 include("$moddir/db/subplugins.php");
7098 foreach ($subplugins as $subtype=>$dir) {
7099 $info[$subtype] = $dir;
7104 // local is always last!
7105 $info['local'] = 'local';
7107 $fullinfo = array();
7108 foreach ($info as $type => $dir) {
7109 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
7113 return ($fullpaths ? $fullinfo : $info);
7117 * Simplified version of get_list_of_plugins()
7118 * @param string $plugintype type of plugin
7119 * @return array name=>fulllocation pairs of plugins of given type
7121 function get_plugin_list($plugintype) {
7122 global $CFG;
7124 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
7125 if ($plugintype == 'auth') {
7126 // Historically we have had an auth plugin called 'db', so allow a special case.
7127 $key = array_search('db', $ignored);
7128 if ($key !== false) {
7129 unset($ignored[$key]);
7133 if ($plugintype === '') {
7134 $plugintype = 'mod';
7137 $fulldirs = array();
7139 if ($plugintype === 'mod') {
7140 // mod is an exception because we have to call this function from get_plugin_types()
7141 $fulldirs[] = $CFG->dirroot.'/mod';
7143 } else if ($plugintype === 'theme') {
7144 $fulldirs[] = $CFG->dirroot.'/theme';
7145 // themes are special because they may be stored also in separate directory
7146 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
7147 $fulldirs[] = $CFG->themedir;
7150 } else {
7151 $types = get_plugin_types(true);
7152 if (!array_key_exists($plugintype, $types)) {
7153 return array();
7155 $fulldir = $types[$plugintype];
7156 if (!file_exists($fulldir)) {
7157 return array();
7159 $fulldirs[] = $fulldir;
7162 $result = array();
7164 foreach ($fulldirs as $fulldir) {
7165 if (!is_dir($fulldir)) {
7166 continue;
7168 $items = new DirectoryIterator($fulldir);
7169 foreach ($items as $item) {
7170 if ($item->isDot() or !$item->isDir()) {
7171 continue;
7173 $pluginname = $item->getFilename();
7174 if (in_array($pluginname, $ignored)) {
7175 continue;
7177 if ($pluginname !== clean_param($pluginname, PARAM_SAFEDIR)) {
7178 // better ignore plugins with problematic names here
7179 continue;
7181 $result[$pluginname] = $fulldir.'/'.$pluginname;
7182 unset($item);
7184 unset($items);
7187 //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!
7188 ksort($result);
7189 return $result;
7193 * Gets a list of all plugin API functions for given plugin type, function
7194 * name, and filename.
7195 * @param string $plugintype Plugin type, e.g. 'mod' or 'report'
7196 * @param string $function Name of function after the frankenstyle prefix;
7197 * e.g. if the function is called report_courselist_hook then this value
7198 * would be 'hook'
7199 * @param string $file Name of file that includes function within plugin,
7200 * default 'lib.php'
7201 * @return Array of plugin frankenstyle (e.g. 'report_courselist', 'mod_forum')
7202 * to valid, existing plugin function name (e.g. 'report_courselist_hook',
7203 * 'forum_hook')
7205 function get_plugin_list_with_function($plugintype, $function, $file='lib.php') {
7206 global $CFG; // mandatory in case it is referenced by include()d PHP script
7208 $result = array();
7209 // Loop through list of plugins with given type
7210 $list = get_plugin_list($plugintype);
7211 foreach($list as $plugin => $dir) {
7212 $path = $dir . '/' . $file;
7213 // If file exists, require it and look for function
7214 if (file_exists($path)) {
7215 include_once($path);
7216 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
7217 if (function_exists($fullfunction)) {
7218 // Function exists with standard name. Store, indexed by
7219 // frankenstyle name of plugin
7220 $result[$plugintype . '_' . $plugin] = $fullfunction;
7221 } else if ($plugintype === 'mod') {
7222 // For modules, we also allow plugin without full frankenstyle
7223 // but just starting with the module name
7224 $shortfunction = $plugin . '_' . $function;
7225 if (function_exists($shortfunction)) {
7226 $result[$plugintype . '_' . $plugin] = $shortfunction;
7231 return $result;
7235 * Lists plugin-like directories within specified directory
7237 * This function was originally used for standard Moodle plugins, please use
7238 * new get_plugin_list() now.
7240 * This function is used for general directory listing and backwards compatility.
7242 * @param string $directory relative directory from root
7243 * @param string $exclude dir name to exclude from the list (defaults to none)
7244 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
7245 * @return array Sorted array of directory names found under the requested parameters
7247 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
7248 global $CFG;
7250 $plugins = array();
7252 if (empty($basedir)) {
7253 $basedir = $CFG->dirroot .'/'. $directory;
7255 } else {
7256 $basedir = $basedir .'/'. $directory;
7259 if (file_exists($basedir) && filetype($basedir) == 'dir') {
7260 $dirhandle = opendir($basedir);
7261 while (false !== ($dir = readdir($dirhandle))) {
7262 $firstchar = substr($dir, 0, 1);
7263 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
7264 continue;
7266 if (filetype($basedir .'/'. $dir) != 'dir') {
7267 continue;
7269 $plugins[] = $dir;
7271 closedir($dirhandle);
7273 if ($plugins) {
7274 asort($plugins);
7276 return $plugins;
7281 * invoke plugin's callback functions
7283 * @param string $type Plugin type e.g. 'mod'
7284 * @param string $name Plugin name
7285 * @param string $feature Feature code (FEATURE_xx constant)
7286 * @param string $action Feature's action
7287 * @param string $options parameters of callback function, should be an array
7288 * @param mixed $default default value if callback function hasn't been defined
7289 * @return mixed
7291 function plugin_callback($type, $name, $feature, $action, $options = null, $default=null) {
7292 global $CFG;
7294 $name = clean_param($name, PARAM_SAFEDIR);
7295 $function = $name.'_'.$feature.'_'.$action;
7296 $file = get_plugin_directory($type, $name) . '/lib.php';
7298 // Load library and look for function
7299 if (file_exists($file)) {
7300 require_once($file);
7302 if (function_exists($function)) {
7303 // Function exists, so just return function result
7304 $ret = call_user_func_array($function, (array)$options);
7305 if (is_null($ret)) {
7306 return $default;
7307 } else {
7308 return $ret;
7311 return $default;
7315 * Checks whether a plugin supports a specified feature.
7317 * @param string $type Plugin type e.g. 'mod'
7318 * @param string $name Plugin name e.g. 'forum'
7319 * @param string $feature Feature code (FEATURE_xx constant)
7320 * @param mixed $default default value if feature support unknown
7321 * @return mixed Feature result (false if not supported, null if feature is unknown,
7322 * otherwise usually true but may have other feature-specific value such as array)
7324 function plugin_supports($type, $name, $feature, $default = NULL) {
7325 global $CFG;
7327 $name = clean_param($name, PARAM_SAFEDIR); //bit of extra security
7329 $function = null;
7331 if ($type === 'mod') {
7332 // we need this special case because we support subplugins in modules,
7333 // otherwise it would end up in infinite loop
7334 if ($name === 'NEWMODULE') {
7335 //somebody forgot to rename the module template
7336 return false;
7338 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
7339 include_once("$CFG->dirroot/mod/$name/lib.php");
7340 $function = $type.'_'.$name.'_supports';
7341 if (!function_exists($function)) {
7342 // legacy non-frankenstyle function name
7343 $function = $name.'_supports';
7347 } else {
7348 if (!$path = get_plugin_directory($type, $name)) {
7349 // non existent plugin type
7350 return false;
7352 if (file_exists("$path/lib.php")) {
7353 include_once("$path/lib.php");
7354 $function = $type.'_'.$name.'_supports';
7358 if ($function and function_exists($function)) {
7359 $supports = $function($feature);
7360 if (is_null($supports)) {
7361 // plugin does not know - use default
7362 return $default;
7363 } else {
7364 return $supports;
7368 //plugin does not care, so use default
7369 return $default;
7373 * Returns true if the current version of PHP is greater that the specified one.
7375 * @todo Check PHP version being required here is it too low?
7377 * @param string $version The version of php being tested.
7378 * @return bool
7380 function check_php_version($version='5.2.4') {
7381 return (version_compare(phpversion(), $version) >= 0);
7385 * Checks to see if is the browser operating system matches the specified
7386 * brand.
7388 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
7390 * @uses $_SERVER
7391 * @param string $brand The operating system identifier being tested
7392 * @return bool true if the given brand below to the detected operating system
7394 function check_browser_operating_system($brand) {
7395 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7396 return false;
7399 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
7400 return true;
7403 return false;
7407 * Checks to see if is a browser matches the specified
7408 * brand and is equal or better version.
7410 * @uses $_SERVER
7411 * @param string $brand The browser identifier being tested
7412 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
7413 * @return bool true if the given version is below that of the detected browser
7415 function check_browser_version($brand, $version = null) {
7416 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7417 return false;
7420 $agent = $_SERVER['HTTP_USER_AGENT'];
7422 switch ($brand) {
7424 case 'Camino': /// OSX browser using Gecke engine
7425 if (strpos($agent, 'Camino') === false) {
7426 return false;
7428 if (empty($version)) {
7429 return true; // no version specified
7431 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
7432 if (version_compare($match[1], $version) >= 0) {
7433 return true;
7436 break;
7439 case 'Firefox': /// Mozilla Firefox browsers
7440 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
7441 return false;
7443 if (empty($version)) {
7444 return true; // no version specified
7446 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
7447 if (version_compare($match[2], $version) >= 0) {
7448 return true;
7451 break;
7454 case 'Gecko': /// Gecko based browsers
7455 if (empty($version) and substr_count($agent, 'Camino')) {
7456 // MacOS X Camino support
7457 $version = 20041110;
7460 // the proper string - Gecko/CCYYMMDD Vendor/Version
7461 // Faster version and work-a-round No IDN problem.
7462 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
7463 if ($match[1] > $version) {
7464 return true;
7467 break;
7470 case 'MSIE': /// Internet Explorer
7471 if (strpos($agent, 'Opera') !== false) { // Reject Opera
7472 return false;
7474 // in case of IE we have to deal with BC of the version parameter
7475 if (is_null($version)) {
7476 $version = 5.5; // anything older is not considered a browser at all!
7479 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
7480 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
7481 if (version_compare($match[1], $version) >= 0) {
7482 return true;
7485 break;
7488 case 'Opera': /// Opera
7489 if (strpos($agent, 'Opera') === false) {
7490 return false;
7492 if (empty($version)) {
7493 return true; // no version specified
7495 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
7496 if (version_compare($match[1], $version) >= 0) {
7497 return true;
7500 break;
7503 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
7504 if (strpos($agent, 'AppleWebKit') === false) {
7505 return false;
7507 if (empty($version)) {
7508 return true; // no version specified
7510 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7511 if (version_compare($match[1], $version) >= 0) {
7512 return true;
7515 break;
7518 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
7519 if (strpos($agent, 'AppleWebKit') === false) {
7520 return false;
7522 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
7523 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
7524 return false;
7526 if (strpos($agent, 'Shiira')) { // Reject Shiira
7527 return false;
7529 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
7530 return false;
7532 if (strpos($agent, 'Android')) { // Reject Androids too
7533 return false;
7535 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
7536 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
7537 return false;
7539 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
7540 return false;
7543 if (empty($version)) {
7544 return true; // no version specified
7546 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7547 if (version_compare($match[1], $version) >= 0) {
7548 return true;
7551 break;
7554 case 'Chrome':
7555 if (strpos($agent, 'Chrome') === false) {
7556 return false;
7558 if (empty($version)) {
7559 return true; // no version specified
7561 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
7562 if (version_compare($match[1], $version) >= 0) {
7563 return true;
7566 break;
7569 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
7570 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
7571 return false;
7573 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
7574 return false;
7576 if (empty($version)) {
7577 return true; // no version specified
7579 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7580 if (version_compare($match[1], $version) >= 0) {
7581 return true;
7584 break;
7587 case 'WebKit Android': /// WebKit browser on Android
7588 if (strpos($agent, 'Linux; U; Android') === false) {
7589 return false;
7591 if (empty($version)) {
7592 return true; // no version specified
7594 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7595 if (version_compare($match[1], $version) >= 0) {
7596 return true;
7599 break;
7603 return false;
7607 * Returns one or several CSS class names that match the user's browser. These can be put
7608 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
7610 * @return array An array of browser version classes
7612 function get_browser_version_classes() {
7613 $classes = array();
7615 if (check_browser_version("MSIE", "0")) {
7616 $classes[] = 'ie';
7617 if (check_browser_version("MSIE", 9)) {
7618 $classes[] = 'ie9';
7619 } else if (check_browser_version("MSIE", 8)) {
7620 $classes[] = 'ie8';
7621 } elseif (check_browser_version("MSIE", 7)) {
7622 $classes[] = 'ie7';
7623 } elseif (check_browser_version("MSIE", 6)) {
7624 $classes[] = 'ie6';
7627 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
7628 $classes[] = 'gecko';
7629 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
7630 $classes[] = "gecko{$matches[1]}{$matches[2]}";
7633 } else if (check_browser_version("WebKit")) {
7634 $classes[] = 'safari';
7635 if (check_browser_version("Safari iOS")) {
7636 $classes[] = 'ios';
7638 } else if (check_browser_version("WebKit Android")) {
7639 $classes[] = 'android';
7642 } else if (check_browser_version("Opera")) {
7643 $classes[] = 'opera';
7647 return $classes;
7651 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
7653 * @return bool True for yes, false for no
7655 function can_use_rotated_text() {
7656 global $USER;
7657 return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader;;
7661 * Hack to find out the GD version by parsing phpinfo output
7663 * @return int GD version (1, 2, or 0)
7665 function check_gd_version() {
7666 $gdversion = 0;
7668 if (function_exists('gd_info')){
7669 $gd_info = gd_info();
7670 if (substr_count($gd_info['GD Version'], '2.')) {
7671 $gdversion = 2;
7672 } else if (substr_count($gd_info['GD Version'], '1.')) {
7673 $gdversion = 1;
7676 } else {
7677 ob_start();
7678 phpinfo(INFO_MODULES);
7679 $phpinfo = ob_get_contents();
7680 ob_end_clean();
7682 $phpinfo = explode("\n", $phpinfo);
7685 foreach ($phpinfo as $text) {
7686 $parts = explode('</td>', $text);
7687 foreach ($parts as $key => $val) {
7688 $parts[$key] = trim(strip_tags($val));
7690 if ($parts[0] == 'GD Version') {
7691 if (substr_count($parts[1], '2.0')) {
7692 $parts[1] = '2.0';
7694 $gdversion = intval($parts[1]);
7699 return $gdversion; // 1, 2 or 0
7703 * Determine if moodle installation requires update
7705 * Checks version numbers of main code and all modules to see
7706 * if there are any mismatches
7708 * @global object
7709 * @global object
7710 * @return bool
7712 function moodle_needs_upgrading() {
7713 global $CFG, $DB, $OUTPUT;
7715 if (empty($CFG->version)) {
7716 return true;
7719 // main versio nfirst
7720 $version = null;
7721 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
7722 if ($version > $CFG->version) {
7723 return true;
7726 // modules
7727 $mods = get_plugin_list('mod');
7728 $installed = $DB->get_records('modules', array(), '', 'name, version');
7729 foreach ($mods as $mod => $fullmod) {
7730 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
7731 continue;
7733 $module = new stdClass();
7734 if (!is_readable($fullmod.'/version.php')) {
7735 continue;
7737 include($fullmod.'/version.php'); // defines $module with version etc
7738 if (empty($installed[$mod])) {
7739 return true;
7740 } else if ($module->version > $installed[$mod]->version) {
7741 return true;
7744 unset($installed);
7746 // blocks
7747 $blocks = get_plugin_list('block');
7748 $installed = $DB->get_records('block', array(), '', 'name, version');
7749 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
7750 foreach ($blocks as $blockname=>$fullblock) {
7751 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
7752 continue;
7754 if (!is_readable($fullblock.'/version.php')) {
7755 continue;
7757 $plugin = new stdClass();
7758 $plugin->version = NULL;
7759 include($fullblock.'/version.php');
7760 if (empty($installed[$blockname])) {
7761 return true;
7762 } else if ($plugin->version > $installed[$blockname]->version) {
7763 return true;
7766 unset($installed);
7768 // now the rest of plugins
7769 $plugintypes = get_plugin_types();
7770 unset($plugintypes['mod']);
7771 unset($plugintypes['block']);
7772 foreach ($plugintypes as $type=>$unused) {
7773 $plugs = get_plugin_list($type);
7774 foreach ($plugs as $plug=>$fullplug) {
7775 $component = $type.'_'.$plug;
7776 if (!is_readable($fullplug.'/version.php')) {
7777 continue;
7779 $plugin = new stdClass();
7780 include($fullplug.'/version.php'); // defines $plugin with version etc
7781 $installedversion = get_config($component, 'version');
7782 if (empty($installedversion)) { // new installation
7783 return true;
7784 } else if ($installedversion < $plugin->version) { // upgrade
7785 return true;
7790 return false;
7794 * Sets maximum expected time needed for upgrade task.
7795 * Please always make sure that upgrade will not run longer!
7797 * The script may be automatically aborted if upgrade times out.
7799 * @global object
7800 * @param int $max_execution_time in seconds (can not be less than 60 s)
7802 function upgrade_set_timeout($max_execution_time=300) {
7803 global $CFG;
7805 if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
7806 $upgraderunning = get_config(null, 'upgraderunning');
7807 } else {
7808 $upgraderunning = $CFG->upgraderunning;
7811 if (!$upgraderunning) {
7812 // upgrade not running or aborted
7813 print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
7814 die;
7817 if ($max_execution_time < 60) {
7818 // protection against 0 here
7819 $max_execution_time = 60;
7822 $expected_end = time() + $max_execution_time;
7824 if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
7825 // no need to store new end, it is nearly the same ;-)
7826 return;
7829 set_time_limit($max_execution_time);
7830 set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
7833 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
7836 * Notify admin users or admin user of any failed logins (since last notification).
7838 * Note that this function must be only executed from the cron script
7839 * It uses the cache_flags system to store temporary records, deleting them
7840 * by name before finishing
7842 * @global object
7843 * @global object
7844 * @uses HOURSECS
7846 function notify_login_failures() {
7847 global $CFG, $DB, $OUTPUT;
7849 $recip = get_users_from_config($CFG->notifyloginfailures, 'moodle/site:config');
7851 if (empty($CFG->lastnotifyfailure)) {
7852 $CFG->lastnotifyfailure=0;
7855 // we need to deal with the threshold stuff first.
7856 if (empty($CFG->notifyloginthreshold)) {
7857 $CFG->notifyloginthreshold = 10; // default to something sensible.
7860 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
7861 /// and insert them into the cache_flags temp table
7862 $sql = "SELECT ip, COUNT(*)
7863 FROM {log}
7864 WHERE module = 'login' AND action = 'error'
7865 AND time > ?
7866 GROUP BY ip
7867 HAVING COUNT(*) >= ?";
7868 $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
7869 $rs = $DB->get_recordset_sql($sql, $params);
7870 foreach ($rs as $iprec) {
7871 if (!empty($iprec->ip)) {
7872 set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
7875 $rs->close();
7877 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
7878 /// and insert them into the cache_flags temp table
7879 $sql = "SELECT info, count(*)
7880 FROM {log}
7881 WHERE module = 'login' AND action = 'error'
7882 AND time > ?
7883 GROUP BY info
7884 HAVING count(*) >= ?";
7885 $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
7886 $rs = $DB->get_recordset_sql($sql, $params);
7887 foreach ($rs as $inforec) {
7888 if (!empty($inforec->info)) {
7889 set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
7892 $rs->close();
7894 /// Now, select all the login error logged records belonging to the ips and infos
7895 /// since lastnotifyfailure, that we have stored in the cache_flags table
7896 $sql = "SELECT l.*, u.firstname, u.lastname
7897 FROM {log} l
7898 JOIN {cache_flags} cf ON l.ip = cf.name
7899 LEFT JOIN {user} u ON l.userid = u.id
7900 WHERE l.module = 'login' AND l.action = 'error'
7901 AND l.time > ?
7902 AND cf.flagtype = 'login_failure_by_ip'
7903 UNION ALL
7904 SELECT l.*, u.firstname, u.lastname
7905 FROM {log} l
7906 JOIN {cache_flags} cf ON l.info = cf.name
7907 LEFT JOIN {user} u ON l.userid = u.id
7908 WHERE l.module = 'login' AND l.action = 'error'
7909 AND l.time > ?
7910 AND cf.flagtype = 'login_failure_by_info'
7911 ORDER BY time DESC";
7912 $params = array($CFG->lastnotifyfailure, $CFG->lastnotifyfailure);
7914 /// Init some variables
7915 $count = 0;
7916 $messages = '';
7917 /// Iterate over the logs recordset
7918 $rs = $DB->get_recordset_sql($sql, $params);
7919 foreach ($rs as $log) {
7920 $log->time = userdate($log->time);
7921 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
7922 $count++;
7924 $rs->close();
7926 /// If we haven't run in the last hour and
7927 /// we have something useful to report and we
7928 /// are actually supposed to be reporting to somebody
7929 if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
7930 $site = get_site();
7931 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
7932 /// Calculate the complete body of notification (start + messages + end)
7933 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
7934 (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
7935 $messages .
7936 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
7938 /// For each destination, send mail
7939 mtrace('Emailing admins about '. $count .' failed login attempts');
7940 foreach ($recip as $admin) {
7941 //emailing the admins directly rather than putting these through the messaging system
7942 email_to_user($admin,get_admin(), $subject, $body);
7945 /// Update lastnotifyfailure with current time
7946 set_config('lastnotifyfailure', time());
7949 /// Finally, delete all the temp records we have created in cache_flags
7950 $DB->delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
7954 * Sets the system locale
7956 * @todo Finish documenting this function
7958 * @global object
7959 * @param string $locale Can be used to force a locale
7961 function moodle_setlocale($locale='') {
7962 global $CFG;
7964 static $currentlocale = ''; // last locale caching
7966 $oldlocale = $currentlocale;
7968 /// Fetch the correct locale based on ostype
7969 if ($CFG->ostype == 'WINDOWS') {
7970 $stringtofetch = 'localewin';
7971 } else {
7972 $stringtofetch = 'locale';
7975 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
7976 if (!empty($locale)) {
7977 $currentlocale = $locale;
7978 } else if (!empty($CFG->locale)) { // override locale for all language packs
7979 $currentlocale = $CFG->locale;
7980 } else {
7981 $currentlocale = get_string($stringtofetch, 'langconfig');
7984 /// do nothing if locale already set up
7985 if ($oldlocale == $currentlocale) {
7986 return;
7989 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
7990 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
7991 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
7993 /// Get current values
7994 $monetary= setlocale (LC_MONETARY, 0);
7995 $numeric = setlocale (LC_NUMERIC, 0);
7996 $ctype = setlocale (LC_CTYPE, 0);
7997 if ($CFG->ostype != 'WINDOWS') {
7998 $messages= setlocale (LC_MESSAGES, 0);
8000 /// Set locale to all
8001 setlocale (LC_ALL, $currentlocale);
8002 /// Set old values
8003 setlocale (LC_MONETARY, $monetary);
8004 setlocale (LC_NUMERIC, $numeric);
8005 if ($CFG->ostype != 'WINDOWS') {
8006 setlocale (LC_MESSAGES, $messages);
8008 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
8009 setlocale (LC_CTYPE, $ctype);
8014 * Converts string to lowercase using most compatible function available.
8016 * @todo Remove this function when no longer in use
8017 * @deprecated Use textlib->strtolower($text) instead.
8019 * @param string $string The string to convert to all lowercase characters.
8020 * @param string $encoding The encoding on the string.
8021 * @return string
8023 function moodle_strtolower ($string, $encoding='') {
8025 //If not specified use utf8
8026 if (empty($encoding)) {
8027 $encoding = 'UTF-8';
8029 //Use text services
8030 $textlib = textlib_get_instance();
8032 return $textlib->strtolower($string, $encoding);
8036 * Count words in a string.
8038 * Words are defined as things between whitespace.
8040 * @param string $string The text to be searched for words.
8041 * @return int The count of words in the specified string
8043 function count_words($string) {
8044 $string = strip_tags($string);
8045 return count(preg_split("/\w\b/", $string)) - 1;
8048 /** Count letters in a string.
8050 * Letters are defined as chars not in tags and different from whitespace.
8052 * @param string $string The text to be searched for letters.
8053 * @return int The count of letters in the specified text.
8055 function count_letters($string) {
8056 /// Loading the textlib singleton instance. We are going to need it.
8057 $textlib = textlib_get_instance();
8059 $string = strip_tags($string); // Tags are out now
8060 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
8062 return $textlib->strlen($string);
8066 * Generate and return a random string of the specified length.
8068 * @param int $length The length of the string to be created.
8069 * @return string
8071 function random_string ($length=15) {
8072 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8073 $pool .= 'abcdefghijklmnopqrstuvwxyz';
8074 $pool .= '0123456789';
8075 $poollen = strlen($pool);
8076 mt_srand ((double) microtime() * 1000000);
8077 $string = '';
8078 for ($i = 0; $i < $length; $i++) {
8079 $string .= substr($pool, (mt_rand()%($poollen)), 1);
8081 return $string;
8085 * Generate a complex random string (useful for md5 salts)
8087 * This function is based on the above {@link random_string()} however it uses a
8088 * larger pool of characters and generates a string between 24 and 32 characters
8090 * @param int $length Optional if set generates a string to exactly this length
8091 * @return string
8093 function complex_random_string($length=null) {
8094 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
8095 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
8096 $poollen = strlen($pool);
8097 mt_srand ((double) microtime() * 1000000);
8098 if ($length===null) {
8099 $length = floor(rand(24,32));
8101 $string = '';
8102 for ($i = 0; $i < $length; $i++) {
8103 $string .= $pool[(mt_rand()%$poollen)];
8105 return $string;
8109 * Given some text (which may contain HTML) and an ideal length,
8110 * this function truncates the text neatly on a word boundary if possible
8112 * @global object
8113 * @param string $text - text to be shortened
8114 * @param int $ideal - ideal string length
8115 * @param boolean $exact if false, $text will not be cut mid-word
8116 * @param string $ending The string to append if the passed string is truncated
8117 * @return string $truncate - shortened string
8119 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
8121 global $CFG;
8123 // if the plain text is shorter than the maximum length, return the whole text
8124 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
8125 return $text;
8128 // Splits on HTML tags. Each open/close/empty tag will be the first thing
8129 // and only tag in its 'line'
8130 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
8132 $total_length = strlen($ending);
8133 $truncate = '';
8135 // This array stores information about open and close tags and their position
8136 // in the truncated string. Each item in the array is an object with fields
8137 // ->open (true if open), ->tag (tag name in lower case), and ->pos
8138 // (byte position in truncated text)
8139 $tagdetails = array();
8141 foreach ($lines as $line_matchings) {
8142 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
8143 if (!empty($line_matchings[1])) {
8144 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
8145 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
8146 // do nothing
8147 // if tag is a closing tag (f.e. </b>)
8148 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
8149 // record closing tag
8150 $tagdetails[] = (object)array('open'=>false,
8151 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8152 // if tag is an opening tag (f.e. <b>)
8153 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
8154 // record opening tag
8155 $tagdetails[] = (object)array('open'=>true,
8156 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8158 // add html-tag to $truncate'd text
8159 $truncate .= $line_matchings[1];
8162 // calculate the length of the plain text part of the line; handle entities as one character
8163 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
8164 if ($total_length+$content_length > $ideal) {
8165 // the number of characters which are left
8166 $left = $ideal - $total_length;
8167 $entities_length = 0;
8168 // search for html entities
8169 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)) {
8170 // calculate the real length of all entities in the legal range
8171 foreach ($entities[0] as $entity) {
8172 if ($entity[1]+1-$entities_length <= $left) {
8173 $left--;
8174 $entities_length += strlen($entity[0]);
8175 } else {
8176 // no more characters left
8177 break;
8181 $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
8182 // maximum length is reached, so get off the loop
8183 break;
8184 } else {
8185 $truncate .= $line_matchings[2];
8186 $total_length += $content_length;
8189 // if the maximum length is reached, get off the loop
8190 if($total_length >= $ideal) {
8191 break;
8195 // if the words shouldn't be cut in the middle...
8196 if (!$exact) {
8197 // ...search the last occurence of a space...
8198 for ($k=strlen($truncate);$k>0;$k--) {
8199 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
8200 if ($char == '.' or $char == ' ') {
8201 $breakpos = $k+1;
8202 break;
8203 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
8204 $breakpos = $k; // can be truncated at any UTF-8
8205 break; // character boundary.
8210 if (isset($breakpos)) {
8211 // ...and cut the text in this position
8212 $truncate = substr($truncate, 0, $breakpos);
8216 // add the defined ending to the text
8217 $truncate .= $ending;
8219 // Now calculate the list of open html tags based on the truncate position
8220 $open_tags = array();
8221 foreach ($tagdetails as $taginfo) {
8222 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
8223 // Don't include tags after we made the break!
8224 break;
8226 if($taginfo->open) {
8227 // add tag to the beginning of $open_tags list
8228 array_unshift($open_tags, $taginfo->tag);
8229 } else {
8230 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
8231 if ($pos !== false) {
8232 unset($open_tags[$pos]);
8237 // close all unclosed html-tags
8238 foreach ($open_tags as $tag) {
8239 $truncate .= '</' . $tag . '>';
8242 return $truncate;
8247 * Given dates in seconds, how many weeks is the date from startdate
8248 * The first week is 1, the second 2 etc ...
8250 * @todo Finish documenting this function
8252 * @uses WEEKSECS
8253 * @param int $startdate Timestamp for the start date
8254 * @param int $thedate Timestamp for the end date
8255 * @return string
8257 function getweek ($startdate, $thedate) {
8258 if ($thedate < $startdate) { // error
8259 return 0;
8262 return floor(($thedate - $startdate) / WEEKSECS) + 1;
8266 * returns a randomly generated password of length $maxlen. inspired by
8268 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
8269 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
8271 * @global object
8272 * @param int $maxlen The maximum size of the password being generated.
8273 * @return string
8275 function generate_password($maxlen=10) {
8276 global $CFG;
8278 if (empty($CFG->passwordpolicy)) {
8279 $fillers = PASSWORD_DIGITS;
8280 $wordlist = file($CFG->wordlist);
8281 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8282 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8283 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
8284 $password = $word1 . $filler1 . $word2;
8285 } else {
8286 $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
8287 $digits = $CFG->minpassworddigits;
8288 $lower = $CFG->minpasswordlower;
8289 $upper = $CFG->minpasswordupper;
8290 $nonalphanum = $CFG->minpasswordnonalphanum;
8291 $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
8293 // Make sure we have enough characters to fulfill
8294 // complexity requirements
8295 $passworddigits = PASSWORD_DIGITS;
8296 while ($digits > strlen($passworddigits)) {
8297 $passworddigits .= PASSWORD_DIGITS;
8299 $passwordlower = PASSWORD_LOWER;
8300 while ($lower > strlen($passwordlower)) {
8301 $passwordlower .= PASSWORD_LOWER;
8303 $passwordupper = PASSWORD_UPPER;
8304 while ($upper > strlen($passwordupper)) {
8305 $passwordupper .= PASSWORD_UPPER;
8307 $passwordnonalphanum = PASSWORD_NONALPHANUM;
8308 while ($nonalphanum > strlen($passwordnonalphanum)) {
8309 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
8312 // Now mix and shuffle it all
8313 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
8314 substr(str_shuffle ($passwordupper), 0, $upper) .
8315 substr(str_shuffle ($passworddigits), 0, $digits) .
8316 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
8317 substr(str_shuffle ($passwordlower .
8318 $passwordupper .
8319 $passworddigits .
8320 $passwordnonalphanum), 0 , $additional));
8323 return substr ($password, 0, $maxlen);
8327 * Given a float, prints it nicely.
8328 * Localized floats must not be used in calculations!
8330 * @param float $float The float to print
8331 * @param int $places The number of decimal places to print.
8332 * @param bool $localized use localized decimal separator
8333 * @return string locale float
8335 function format_float($float, $decimalpoints=1, $localized=true) {
8336 if (is_null($float)) {
8337 return '';
8339 if ($localized) {
8340 return number_format($float, $decimalpoints, get_string('decsep', 'langconfig'), '');
8341 } else {
8342 return number_format($float, $decimalpoints, '.', '');
8347 * Converts locale specific floating point/comma number back to standard PHP float value
8348 * Do NOT try to do any math operations before this conversion on any user submitted floats!
8350 * @param string $locale_float locale aware float representation
8351 * @return float
8353 function unformat_float($locale_float) {
8354 $locale_float = trim($locale_float);
8356 if ($locale_float == '') {
8357 return null;
8360 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
8362 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
8366 * Given a simple array, this shuffles it up just like shuffle()
8367 * Unlike PHP's shuffle() this function works on any machine.
8369 * @param array $array The array to be rearranged
8370 * @return array
8372 function swapshuffle($array) {
8374 srand ((double) microtime() * 10000000);
8375 $last = count($array) - 1;
8376 for ($i=0;$i<=$last;$i++) {
8377 $from = rand(0,$last);
8378 $curr = $array[$i];
8379 $array[$i] = $array[$from];
8380 $array[$from] = $curr;
8382 return $array;
8386 * Like {@link swapshuffle()}, but works on associative arrays
8388 * @param array $array The associative array to be rearranged
8389 * @return array
8391 function swapshuffle_assoc($array) {
8393 $newarray = array();
8394 $newkeys = swapshuffle(array_keys($array));
8396 foreach ($newkeys as $newkey) {
8397 $newarray[$newkey] = $array[$newkey];
8399 return $newarray;
8403 * Given an arbitrary array, and a number of draws,
8404 * this function returns an array with that amount
8405 * of items. The indexes are retained.
8407 * @todo Finish documenting this function
8409 * @param array $array
8410 * @param int $draws
8411 * @return array
8413 function draw_rand_array($array, $draws) {
8414 srand ((double) microtime() * 10000000);
8416 $return = array();
8418 $last = count($array);
8420 if ($draws > $last) {
8421 $draws = $last;
8424 while ($draws > 0) {
8425 $last--;
8427 $keys = array_keys($array);
8428 $rand = rand(0, $last);
8430 $return[$keys[$rand]] = $array[$keys[$rand]];
8431 unset($array[$keys[$rand]]);
8433 $draws--;
8436 return $return;
8440 * Calculate the difference between two microtimes
8442 * @param string $a The first Microtime
8443 * @param string $b The second Microtime
8444 * @return string
8446 function microtime_diff($a, $b) {
8447 list($a_dec, $a_sec) = explode(' ', $a);
8448 list($b_dec, $b_sec) = explode(' ', $b);
8449 return $b_sec - $a_sec + $b_dec - $a_dec;
8453 * Given a list (eg a,b,c,d,e) this function returns
8454 * an array of 1->a, 2->b, 3->c etc
8456 * @param string $list The string to explode into array bits
8457 * @param string $separator The separator used within the list string
8458 * @return array The now assembled array
8460 function make_menu_from_list($list, $separator=',') {
8462 $array = array_reverse(explode($separator, $list), true);
8463 foreach ($array as $key => $item) {
8464 $outarray[$key+1] = trim($item);
8466 return $outarray;
8470 * Creates an array that represents all the current grades that
8471 * can be chosen using the given grading type.
8473 * Negative numbers
8474 * are scales, zero is no grade, and positive numbers are maximum
8475 * grades.
8477 * @todo Finish documenting this function or better deprecated this completely!
8479 * @param int $gradingtype
8480 * @return array
8482 function make_grades_menu($gradingtype) {
8483 global $DB;
8485 $grades = array();
8486 if ($gradingtype < 0) {
8487 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
8488 return make_menu_from_list($scale->scale);
8490 } else if ($gradingtype > 0) {
8491 for ($i=$gradingtype; $i>=0; $i--) {
8492 $grades[$i] = $i .' / '. $gradingtype;
8494 return $grades;
8496 return $grades;
8500 * This function returns the number of activities
8501 * using scaleid in a courseid
8503 * @todo Finish documenting this function
8505 * @global object
8506 * @global object
8507 * @param int $courseid ?
8508 * @param int $scaleid ?
8509 * @return int
8511 function course_scale_used($courseid, $scaleid) {
8512 global $CFG, $DB;
8514 $return = 0;
8516 if (!empty($scaleid)) {
8517 if ($cms = get_course_mods($courseid)) {
8518 foreach ($cms as $cm) {
8519 //Check cm->name/lib.php exists
8520 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
8521 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
8522 $function_name = $cm->modname.'_scale_used';
8523 if (function_exists($function_name)) {
8524 if ($function_name($cm->instance,$scaleid)) {
8525 $return++;
8532 // check if any course grade item makes use of the scale
8533 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
8535 // check if any outcome in the course makes use of the scale
8536 $return += $DB->count_records_sql("SELECT COUNT('x')
8537 FROM {grade_outcomes_courses} goc,
8538 {grade_outcomes} go
8539 WHERE go.id = goc.outcomeid
8540 AND go.scaleid = ? AND goc.courseid = ?",
8541 array($scaleid, $courseid));
8543 return $return;
8547 * This function returns the number of activities
8548 * using scaleid in the entire site
8550 * @param int $scaleid
8551 * @param array $courses
8552 * @return int
8554 function site_scale_used($scaleid, &$courses) {
8555 $return = 0;
8557 if (!is_array($courses) || count($courses) == 0) {
8558 $courses = get_courses("all",false,"c.id,c.shortname");
8561 if (!empty($scaleid)) {
8562 if (is_array($courses) && count($courses) > 0) {
8563 foreach ($courses as $course) {
8564 $return += course_scale_used($course->id,$scaleid);
8568 return $return;
8572 * make_unique_id_code
8574 * @todo Finish documenting this function
8576 * @uses $_SERVER
8577 * @param string $extra Extra string to append to the end of the code
8578 * @return string
8580 function make_unique_id_code($extra='') {
8582 $hostname = 'unknownhost';
8583 if (!empty($_SERVER['HTTP_HOST'])) {
8584 $hostname = $_SERVER['HTTP_HOST'];
8585 } else if (!empty($_ENV['HTTP_HOST'])) {
8586 $hostname = $_ENV['HTTP_HOST'];
8587 } else if (!empty($_SERVER['SERVER_NAME'])) {
8588 $hostname = $_SERVER['SERVER_NAME'];
8589 } else if (!empty($_ENV['SERVER_NAME'])) {
8590 $hostname = $_ENV['SERVER_NAME'];
8593 $date = gmdate("ymdHis");
8595 $random = random_string(6);
8597 if ($extra) {
8598 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
8599 } else {
8600 return $hostname .'+'. $date .'+'. $random;
8606 * Function to check the passed address is within the passed subnet
8608 * The parameter is a comma separated string of subnet definitions.
8609 * Subnet strings can be in one of three formats:
8610 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
8611 * 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)
8612 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
8613 * Code for type 1 modified from user posted comments by mediator at
8614 * {@link http://au.php.net/manual/en/function.ip2long.php}
8616 * @param string $addr The address you are checking
8617 * @param string $subnetstr The string of subnet addresses
8618 * @return bool
8620 function address_in_subnet($addr, $subnetstr) {
8622 if ($addr == '0.0.0.0') {
8623 return false;
8625 $subnets = explode(',', $subnetstr);
8626 $found = false;
8627 $addr = trim($addr);
8628 $addr = cleanremoteaddr($addr, false); // normalise
8629 if ($addr === null) {
8630 return false;
8632 $addrparts = explode(':', $addr);
8634 $ipv6 = strpos($addr, ':');
8636 foreach ($subnets as $subnet) {
8637 $subnet = trim($subnet);
8638 if ($subnet === '') {
8639 continue;
8642 if (strpos($subnet, '/') !== false) {
8643 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
8644 list($ip, $mask) = explode('/', $subnet);
8645 $mask = trim($mask);
8646 if (!is_number($mask)) {
8647 continue; // incorect mask number, eh?
8649 $ip = cleanremoteaddr($ip, false); // normalise
8650 if ($ip === null) {
8651 continue;
8653 if (strpos($ip, ':') !== false) {
8654 // IPv6
8655 if (!$ipv6) {
8656 continue;
8658 if ($mask > 128 or $mask < 0) {
8659 continue; // nonsense
8661 if ($mask == 0) {
8662 return true; // any address
8664 if ($mask == 128) {
8665 if ($ip === $addr) {
8666 return true;
8668 continue;
8670 $ipparts = explode(':', $ip);
8671 $modulo = $mask % 16;
8672 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
8673 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
8674 if (implode(':', $ipnet) === implode(':', $addrnet)) {
8675 if ($modulo == 0) {
8676 return true;
8678 $pos = ($mask-$modulo)/16;
8679 $ipnet = hexdec($ipparts[$pos]);
8680 $addrnet = hexdec($addrparts[$pos]);
8681 $mask = 0xffff << (16 - $modulo);
8682 if (($addrnet & $mask) == ($ipnet & $mask)) {
8683 return true;
8687 } else {
8688 // IPv4
8689 if ($ipv6) {
8690 continue;
8692 if ($mask > 32 or $mask < 0) {
8693 continue; // nonsense
8695 if ($mask == 0) {
8696 return true;
8698 if ($mask == 32) {
8699 if ($ip === $addr) {
8700 return true;
8702 continue;
8704 $mask = 0xffffffff << (32 - $mask);
8705 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
8706 return true;
8710 } else if (strpos($subnet, '-') !== false) {
8711 /// 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.
8712 $parts = explode('-', $subnet);
8713 if (count($parts) != 2) {
8714 continue;
8717 if (strpos($subnet, ':') !== false) {
8718 // IPv6
8719 if (!$ipv6) {
8720 continue;
8722 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
8723 if ($ipstart === null) {
8724 continue;
8726 $ipparts = explode(':', $ipstart);
8727 $start = hexdec(array_pop($ipparts));
8728 $ipparts[] = trim($parts[1]);
8729 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
8730 if ($ipend === null) {
8731 continue;
8733 $ipparts[7] = '';
8734 $ipnet = implode(':', $ipparts);
8735 if (strpos($addr, $ipnet) !== 0) {
8736 continue;
8738 $ipparts = explode(':', $ipend);
8739 $end = hexdec($ipparts[7]);
8741 $addrend = hexdec($addrparts[7]);
8743 if (($addrend >= $start) and ($addrend <= $end)) {
8744 return true;
8747 } else {
8748 // IPv4
8749 if ($ipv6) {
8750 continue;
8752 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
8753 if ($ipstart === null) {
8754 continue;
8756 $ipparts = explode('.', $ipstart);
8757 $ipparts[3] = trim($parts[1]);
8758 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
8759 if ($ipend === null) {
8760 continue;
8763 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
8764 return true;
8768 } else {
8769 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
8770 if (strpos($subnet, ':') !== false) {
8771 // IPv6
8772 if (!$ipv6) {
8773 continue;
8775 $parts = explode(':', $subnet);
8776 $count = count($parts);
8777 if ($parts[$count-1] === '') {
8778 unset($parts[$count-1]); // trim trailing :
8779 $count--;
8780 $subnet = implode('.', $parts);
8782 $isip = cleanremoteaddr($subnet, false); // normalise
8783 if ($isip !== null) {
8784 if ($isip === $addr) {
8785 return true;
8787 continue;
8788 } else if ($count > 8) {
8789 continue;
8791 $zeros = array_fill(0, 8-$count, '0');
8792 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
8793 if (address_in_subnet($addr, $subnet)) {
8794 return true;
8797 } else {
8798 // IPv4
8799 if ($ipv6) {
8800 continue;
8802 $parts = explode('.', $subnet);
8803 $count = count($parts);
8804 if ($parts[$count-1] === '') {
8805 unset($parts[$count-1]); // trim trailing .
8806 $count--;
8807 $subnet = implode('.', $parts);
8809 if ($count == 4) {
8810 $subnet = cleanremoteaddr($subnet, false); // normalise
8811 if ($subnet === $addr) {
8812 return true;
8814 continue;
8815 } else if ($count > 4) {
8816 continue;
8818 $zeros = array_fill(0, 4-$count, '0');
8819 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
8820 if (address_in_subnet($addr, $subnet)) {
8821 return true;
8827 return false;
8831 * For outputting debugging info
8833 * @uses STDOUT
8834 * @param string $string The string to write
8835 * @param string $eol The end of line char(s) to use
8836 * @param string $sleep Period to make the application sleep
8837 * This ensures any messages have time to display before redirect
8839 function mtrace($string, $eol="\n", $sleep=0) {
8841 if (defined('STDOUT')) {
8842 fwrite(STDOUT, $string.$eol);
8843 } else {
8844 echo $string . $eol;
8847 flush();
8849 //delay to keep message on user's screen in case of subsequent redirect
8850 if ($sleep) {
8851 sleep($sleep);
8856 * Replace 1 or more slashes or backslashes to 1 slash
8858 * @param string $path The path to strip
8859 * @return string the path with double slashes removed
8861 function cleardoubleslashes ($path) {
8862 return preg_replace('/(\/|\\\){1,}/','/',$path);
8866 * Is current ip in give list?
8868 * @param string $list
8869 * @return bool
8871 function remoteip_in_list($list){
8872 $inlist = false;
8873 $client_ip = getremoteaddr(null);
8875 if(!$client_ip){
8876 // ensure access on cli
8877 return true;
8880 $list = explode("\n", $list);
8881 foreach($list as $subnet) {
8882 $subnet = trim($subnet);
8883 if (address_in_subnet($client_ip, $subnet)) {
8884 $inlist = true;
8885 break;
8888 return $inlist;
8892 * Returns most reliable client address
8894 * @global object
8895 * @param string $default If an address can't be determined, then return this
8896 * @return string The remote IP address
8898 function getremoteaddr($default='0.0.0.0') {
8899 global $CFG;
8901 if (empty($CFG->getremoteaddrconf)) {
8902 // This will happen, for example, before just after the upgrade, as the
8903 // user is redirected to the admin screen.
8904 $variablestoskip = 0;
8905 } else {
8906 $variablestoskip = $CFG->getremoteaddrconf;
8908 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
8909 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
8910 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
8911 return $address ? $address : $default;
8914 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
8915 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
8916 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
8917 return $address ? $address : $default;
8920 if (!empty($_SERVER['REMOTE_ADDR'])) {
8921 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
8922 return $address ? $address : $default;
8923 } else {
8924 return $default;
8929 * Cleans an ip address. Internal addresses are now allowed.
8930 * (Originally local addresses were not allowed.)
8932 * @param string $addr IPv4 or IPv6 address
8933 * @param bool $compress use IPv6 address compression
8934 * @return string normalised ip address string, null if error
8936 function cleanremoteaddr($addr, $compress=false) {
8937 $addr = trim($addr);
8939 //TODO: maybe add a separate function is_addr_public() or something like this
8941 if (strpos($addr, ':') !== false) {
8942 // can be only IPv6
8943 $parts = explode(':', $addr);
8944 $count = count($parts);
8946 if (strpos($parts[$count-1], '.') !== false) {
8947 //legacy ipv4 notation
8948 $last = array_pop($parts);
8949 $ipv4 = cleanremoteaddr($last, true);
8950 if ($ipv4 === null) {
8951 return null;
8953 $bits = explode('.', $ipv4);
8954 $parts[] = dechex($bits[0]).dechex($bits[1]);
8955 $parts[] = dechex($bits[2]).dechex($bits[3]);
8956 $count = count($parts);
8957 $addr = implode(':', $parts);
8960 if ($count < 3 or $count > 8) {
8961 return null; // severly malformed
8964 if ($count != 8) {
8965 if (strpos($addr, '::') === false) {
8966 return null; // malformed
8968 // uncompress ::
8969 $insertat = array_search('', $parts, true);
8970 $missing = array_fill(0, 1 + 8 - $count, '0');
8971 array_splice($parts, $insertat, 1, $missing);
8972 foreach ($parts as $key=>$part) {
8973 if ($part === '') {
8974 $parts[$key] = '0';
8979 $adr = implode(':', $parts);
8980 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
8981 return null; // incorrect format - sorry
8984 // normalise 0s and case
8985 $parts = array_map('hexdec', $parts);
8986 $parts = array_map('dechex', $parts);
8988 $result = implode(':', $parts);
8990 if (!$compress) {
8991 return $result;
8994 if ($result === '0:0:0:0:0:0:0:0') {
8995 return '::'; // all addresses
8998 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
8999 if ($compressed !== $result) {
9000 return $compressed;
9003 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
9004 if ($compressed !== $result) {
9005 return $compressed;
9008 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
9009 if ($compressed !== $result) {
9010 return $compressed;
9013 return $result;
9016 // first get all things that look like IPv4 addresses
9017 $parts = array();
9018 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
9019 return null;
9021 unset($parts[0]);
9023 foreach ($parts as $key=>$match) {
9024 if ($match > 255) {
9025 return null;
9027 $parts[$key] = (int)$match; // normalise 0s
9030 return implode('.', $parts);
9034 * This function will make a complete copy of anything it's given,
9035 * regardless of whether it's an object or not.
9037 * @param mixed $thing Something you want cloned
9038 * @return mixed What ever it is you passed it
9040 function fullclone($thing) {
9041 return unserialize(serialize($thing));
9046 * This function expects to called during shutdown
9047 * should be set via register_shutdown_function()
9048 * in lib/setup.php .
9050 * @return void
9052 function moodle_request_shutdown() {
9053 global $CFG;
9055 // help apache server if possible
9056 $apachereleasemem = false;
9057 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
9058 && ini_get_bool('child_terminate')) {
9060 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
9061 if (memory_get_usage() > get_real_size($limit)) {
9062 $apachereleasemem = $limit;
9063 @apache_child_terminate();
9067 // deal with perf logging
9068 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
9069 if ($apachereleasemem) {
9070 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
9072 if (defined('MDL_PERFTOLOG')) {
9073 $perf = get_performance_info();
9074 error_log("PERF: " . $perf['txt']);
9076 if (defined('MDL_PERFINC')) {
9077 $inc = get_included_files();
9078 $ts = 0;
9079 foreach($inc as $f) {
9080 if (preg_match(':^/:', $f)) {
9081 $fs = filesize($f);
9082 $ts += $fs;
9083 $hfs = display_size($fs);
9084 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
9085 , NULL, NULL, 0);
9086 } else {
9087 error_log($f , NULL, NULL, 0);
9090 if ($ts > 0 ) {
9091 $hts = display_size($ts);
9092 error_log("Total size of files included: $ts ($hts)");
9099 * If new messages are waiting for the current user, then insert
9100 * JavaScript to pop up the messaging window into the page
9102 * @global moodle_page $PAGE
9103 * @return void
9105 function message_popup_window() {
9106 global $USER, $DB, $PAGE, $CFG, $SITE;
9108 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
9109 return;
9112 if (!isloggedin() || isguestuser()) {
9113 return;
9116 if (!isset($USER->message_lastpopup)) {
9117 $USER->message_lastpopup = 0;
9118 } else if ($USER->message_lastpopup > (time()-120)) {
9119 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
9120 return;
9123 //a quick query to check whether the user has new messages
9124 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
9125 if ($messagecount<1) {
9126 return;
9129 //got unread messages so now do another query that joins with the user table
9130 $messagesql = "SELECT m.id, m.smallmessage, m.notification, u.firstname, u.lastname FROM {message} m
9131 JOIN {message_working} mw ON m.id=mw.unreadmessageid
9132 JOIN {message_processors} p ON mw.processorid=p.id
9133 JOIN {user} u ON m.useridfrom=u.id
9134 WHERE m.useridto = :userid AND p.name='popup'";
9136 //if the user was last notified over an hour ago we can renotify them of old messages
9137 //so don't worry about when the new message was sent
9138 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
9139 if (!$lastnotifiedlongago) {
9140 $messagesql .= 'AND m.timecreated > :lastpopuptime';
9143 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
9145 //if we have new messages to notify the user about
9146 if (!empty($message_users)) {
9148 $strmessages = '';
9149 if (count($message_users)>1) {
9150 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
9151 } else {
9152 $message_users = reset($message_users);
9154 //show who the message is from if its not a notification
9155 if (!$message_users->notification) {
9156 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
9159 //try to display the small version of the message
9160 $smallmessage = null;
9161 if (!empty($message_users->smallmessage)) {
9162 //display the first 200 chars of the message in the popup
9163 $smallmessage = null;
9164 if (strlen($message_users->smallmessage>200)) {
9165 $smallmessage = substr($message_users->smallmessage,0,200).'...';
9166 } else {
9167 $smallmessage = $message_users->smallmessage;
9169 } else if ($message_users->notification) {
9170 //its a notification with no smallmessage so just say they have a notification
9171 $smallmessage = get_string('unreadnewnotification', 'message');
9173 if (!empty($smallmessage)) {
9174 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
9178 $strgomessage = get_string('gotomessages', 'message');
9179 $strstaymessage = get_string('ignore','admin');
9181 $url = $CFG->wwwroot.'/message/index.php';
9182 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
9183 html_writer::start_tag('div', array('id'=>'newmessagetext')).
9184 $strmessages.
9185 html_writer::end_tag('div').
9187 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
9188 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
9189 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
9190 html_writer::end_tag('div');
9191 html_writer::end_tag('div');
9193 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
9195 $USER->message_lastpopup = time();
9200 * Used to make sure that $min <= $value <= $max
9202 * Make sure that value is between min, and max
9204 * @param int $min The minimum value
9205 * @param int $value The value to check
9206 * @param int $max The maximum value
9208 function bounded_number($min, $value, $max) {
9209 if($value < $min) {
9210 return $min;
9212 if($value > $max) {
9213 return $max;
9215 return $value;
9219 * Check if there is a nested array within the passed array
9221 * @param array $array
9222 * @return bool true if there is a nested array false otherwise
9224 function array_is_nested($array) {
9225 foreach ($array as $value) {
9226 if (is_array($value)) {
9227 return true;
9230 return false;
9234 * get_performance_info() pairs up with init_performance_info()
9235 * loaded in setup.php. Returns an array with 'html' and 'txt'
9236 * values ready for use, and each of the individual stats provided
9237 * separately as well.
9239 * @global object
9240 * @global object
9241 * @global object
9242 * @return array
9244 function get_performance_info() {
9245 global $CFG, $PERF, $DB, $PAGE;
9247 $info = array();
9248 $info['html'] = ''; // holds userfriendly HTML representation
9249 $info['txt'] = me() . ' '; // holds log-friendly representation
9251 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
9253 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
9254 $info['txt'] .= 'time: '.$info['realtime'].'s ';
9256 if (function_exists('memory_get_usage')) {
9257 $info['memory_total'] = memory_get_usage();
9258 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
9259 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
9260 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
9263 if (function_exists('memory_get_peak_usage')) {
9264 $info['memory_peak'] = memory_get_peak_usage();
9265 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
9266 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
9269 $inc = get_included_files();
9270 //error_log(print_r($inc,1));
9271 $info['includecount'] = count($inc);
9272 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
9273 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
9275 $filtermanager = filter_manager::instance();
9276 if (method_exists($filtermanager, 'get_performance_summary')) {
9277 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
9278 $info = array_merge($filterinfo, $info);
9279 foreach ($filterinfo as $key => $value) {
9280 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9281 $info['txt'] .= "$key: $value ";
9285 $stringmanager = get_string_manager();
9286 if (method_exists($stringmanager, 'get_performance_summary')) {
9287 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
9288 $info = array_merge($filterinfo, $info);
9289 foreach ($filterinfo as $key => $value) {
9290 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9291 $info['txt'] .= "$key: $value ";
9295 $jsmodules = $PAGE->requires->get_loaded_modules();
9296 if ($jsmodules) {
9297 $yuicount = 0;
9298 $othercount = 0;
9299 $details = '';
9300 foreach ($jsmodules as $module => $backtraces) {
9301 if (strpos($module, 'yui') === 0) {
9302 $yuicount += 1;
9303 } else {
9304 $othercount += 1;
9306 $details .= "<div class='yui-module'><p>$module</p>";
9307 foreach ($backtraces as $backtrace) {
9308 $details .= "<div class='backtrace'>$backtrace</div>";
9310 $details .= '</div>';
9312 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
9313 $info['txt'] .= "includedyuimodules: $yuicount ";
9314 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
9315 $info['txt'] .= "includedjsmodules: $othercount ";
9316 // Slightly odd to output the details in a display: none div. The point
9317 // Is that it takes a lot of space, and if you care you can reveal it
9318 // using firebug.
9319 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
9322 if (!empty($PERF->logwrites)) {
9323 $info['logwrites'] = $PERF->logwrites;
9324 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
9325 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
9328 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
9329 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
9330 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
9332 if (!empty($PERF->profiling) && $PERF->profiling) {
9333 require_once($CFG->dirroot .'/lib/profilerlib.php');
9334 $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
9337 if (function_exists('posix_times')) {
9338 $ptimes = posix_times();
9339 if (is_array($ptimes)) {
9340 foreach ($ptimes as $key => $val) {
9341 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
9343 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
9344 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
9348 // Grab the load average for the last minute
9349 // /proc will only work under some linux configurations
9350 // while uptime is there under MacOSX/Darwin and other unices
9351 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
9352 list($server_load) = explode(' ', $loadavg[0]);
9353 unset($loadavg);
9354 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
9355 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
9356 $server_load = $matches[1];
9357 } else {
9358 trigger_error('Could not parse uptime output!');
9361 if (!empty($server_load)) {
9362 $info['serverload'] = $server_load;
9363 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
9364 $info['txt'] .= "serverload: {$info['serverload']} ";
9367 // Display size of session if session started
9368 if (session_id()) {
9369 $info['sessionsize'] = display_size(strlen(session_encode()));
9370 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
9371 $info['txt'] .= "Session: {$info['sessionsize']} ";
9374 /* if (isset($rcache->hits) && isset($rcache->misses)) {
9375 $info['rcachehits'] = $rcache->hits;
9376 $info['rcachemisses'] = $rcache->misses;
9377 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
9378 "{$rcache->hits}/{$rcache->misses}</span> ";
9379 $info['txt'] .= 'rcache: '.
9380 "{$rcache->hits}/{$rcache->misses} ";
9382 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
9383 return $info;
9387 * @todo Document this function linux people
9389 function apd_get_profiling() {
9390 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
9394 * Delete directory or only it's content
9396 * @param string $dir directory path
9397 * @param bool $content_only
9398 * @return bool success, true also if dir does not exist
9400 function remove_dir($dir, $content_only=false) {
9401 if (!file_exists($dir)) {
9402 // nothing to do
9403 return true;
9405 $handle = opendir($dir);
9406 $result = true;
9407 while (false!==($item = readdir($handle))) {
9408 if($item != '.' && $item != '..') {
9409 if(is_dir($dir.'/'.$item)) {
9410 $result = remove_dir($dir.'/'.$item) && $result;
9411 }else{
9412 $result = unlink($dir.'/'.$item) && $result;
9416 closedir($handle);
9417 if ($content_only) {
9418 return $result;
9420 return rmdir($dir); // if anything left the result will be false, no need for && $result
9424 * Detect if an object or a class contains a given property
9425 * will take an actual object or the name of a class
9427 * @param mix $obj Name of class or real object to test
9428 * @param string $property name of property to find
9429 * @return bool true if property exists
9431 function object_property_exists( $obj, $property ) {
9432 if (is_string( $obj )) {
9433 $properties = get_class_vars( $obj );
9435 else {
9436 $properties = get_object_vars( $obj );
9438 return array_key_exists( $property, $properties );
9443 * Detect a custom script replacement in the data directory that will
9444 * replace an existing moodle script
9446 * @param string $urlpath path to the original script
9447 * @return string|bool full path name if a custom script exists, false if no custom script exists
9449 function custom_script_path($urlpath='') {
9450 global $CFG;
9452 // set default $urlpath, if necessary
9453 if (empty($urlpath)) {
9454 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
9457 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
9458 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
9459 return false;
9462 // replace wwwroot with the path to the customscripts folder and clean path
9463 $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
9465 // remove the query string, if any
9466 if (($strpos = strpos($scriptpath, '?')) !== false) {
9467 $scriptpath = substr($scriptpath, 0, $strpos);
9470 // remove trailing slashes, if any
9471 $scriptpath = rtrim($scriptpath, '/\\');
9473 // append index.php, if necessary
9474 if (is_dir($scriptpath)) {
9475 $scriptpath .= '/index.php';
9478 // check the custom script exists
9479 if (file_exists($scriptpath)) {
9480 return $scriptpath;
9481 } else {
9482 return false;
9487 * Returns whether or not the user object is a remote MNET user. This function
9488 * is in moodlelib because it does not rely on loading any of the MNET code.
9490 * @global object
9491 * @param object $user A valid user object
9492 * @return bool True if the user is from a remote Moodle.
9494 function is_mnet_remote_user($user) {
9495 global $CFG;
9497 if (!isset($CFG->mnet_localhost_id)) {
9498 include_once $CFG->dirroot . '/mnet/lib.php';
9499 $env = new mnet_environment();
9500 $env->init();
9501 unset($env);
9504 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
9508 * This function will search for browser prefereed languages, setting Moodle
9509 * to use the best one available if $SESSION->lang is undefined
9511 * @global object
9512 * @global object
9513 * @global object
9515 function setup_lang_from_browser() {
9517 global $CFG, $SESSION, $USER;
9519 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
9520 // Lang is defined in session or user profile, nothing to do
9521 return;
9524 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
9525 return;
9528 /// Extract and clean langs from headers
9529 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
9530 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
9531 $rawlangs = explode(',', $rawlangs); // Convert to array
9532 $langs = array();
9534 $order = 1.0;
9535 foreach ($rawlangs as $lang) {
9536 if (strpos($lang, ';') === false) {
9537 $langs[(string)$order] = $lang;
9538 $order = $order-0.01;
9539 } else {
9540 $parts = explode(';', $lang);
9541 $pos = strpos($parts[1], '=');
9542 $langs[substr($parts[1], $pos+1)] = $parts[0];
9545 krsort($langs, SORT_NUMERIC);
9547 /// Look for such langs under standard locations
9548 foreach ($langs as $lang) {
9549 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
9550 if (get_string_manager()->translation_exists($lang, false)) {
9551 $SESSION->lang = $lang; /// Lang exists, set it in session
9552 break; /// We have finished. Go out
9555 return;
9559 * check if $url matches anything in proxybypass list
9561 * any errors just result in the proxy being used (least bad)
9563 * @global object
9564 * @param string $url url to check
9565 * @return boolean true if we should bypass the proxy
9567 function is_proxybypass( $url ) {
9568 global $CFG;
9570 // sanity check
9571 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
9572 return false;
9575 // get the host part out of the url
9576 if (!$host = parse_url( $url, PHP_URL_HOST )) {
9577 return false;
9580 // get the possible bypass hosts into an array
9581 $matches = explode( ',', $CFG->proxybypass );
9583 // check for a match
9584 // (IPs need to match the left hand side and hosts the right of the url,
9585 // but we can recklessly check both as there can't be a false +ve)
9586 $bypass = false;
9587 foreach ($matches as $match) {
9588 $match = trim($match);
9590 // try for IP match (Left side)
9591 $lhs = substr($host,0,strlen($match));
9592 if (strcasecmp($match,$lhs)==0) {
9593 return true;
9596 // try for host match (Right side)
9597 $rhs = substr($host,-strlen($match));
9598 if (strcasecmp($match,$rhs)==0) {
9599 return true;
9603 // nothing matched.
9604 return false;
9608 ////////////////////////////////////////////////////////////////////////////////
9611 * Check if the passed navigation is of the new style
9613 * @param mixed $navigation
9614 * @return bool true for yes false for no
9616 function is_newnav($navigation) {
9617 if (is_array($navigation) && !empty($navigation['newnav'])) {
9618 return true;
9619 } else {
9620 return false;
9625 * Checks whether the given variable name is defined as a variable within the given object.
9627 * This will NOT work with stdClass objects, which have no class variables.
9629 * @param string $var The variable name
9630 * @param object $object The object to check
9631 * @return boolean
9633 function in_object_vars($var, $object) {
9634 $class_vars = get_class_vars(get_class($object));
9635 $class_vars = array_keys($class_vars);
9636 return in_array($var, $class_vars);
9640 * Returns an array without repeated objects.
9641 * This function is similar to array_unique, but for arrays that have objects as values
9643 * @param array $array
9644 * @param bool $keep_key_assoc
9645 * @return array
9647 function object_array_unique($array, $keep_key_assoc = true) {
9648 $duplicate_keys = array();
9649 $tmp = array();
9651 foreach ($array as $key=>$val) {
9652 // convert objects to arrays, in_array() does not support objects
9653 if (is_object($val)) {
9654 $val = (array)$val;
9657 if (!in_array($val, $tmp)) {
9658 $tmp[] = $val;
9659 } else {
9660 $duplicate_keys[] = $key;
9664 foreach ($duplicate_keys as $key) {
9665 unset($array[$key]);
9668 return $keep_key_assoc ? $array : array_values($array);
9672 * Returns the language string for the given plugin.
9674 * @param string $plugin the plugin code name
9675 * @param string $type the type of plugin (mod, block, filter)
9676 * @return string The plugin language string
9678 function get_plugin_name($plugin, $type='mod') {
9679 $plugin_name = '';
9681 switch ($type) {
9682 case 'mod':
9683 $plugin_name = get_string('modulename', $plugin);
9684 break;
9685 case 'blocks':
9686 $plugin_name = get_string('pluginname', "block_$plugin");
9687 if (empty($plugin_name) || $plugin_name == '[[pluginname]]') {
9688 if (($block = block_instance($plugin)) !== false) {
9689 $plugin_name = $block->get_title();
9690 } else {
9691 $plugin_name = "[[$plugin]]";
9694 break;
9695 case 'filter':
9696 $plugin_name = filter_get_name('filter/' . $plugin);
9697 break;
9698 default:
9699 $plugin_name = $plugin;
9700 break;
9703 return $plugin_name;
9707 * Is a userid the primary administrator?
9709 * @param int $userid int id of user to check
9710 * @return boolean
9712 function is_primary_admin($userid){
9713 $primaryadmin = get_admin();
9715 if($userid == $primaryadmin->id){
9716 return true;
9717 }else{
9718 return false;
9723 * Returns the site identifier
9725 * @global object
9726 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
9728 function get_site_identifier() {
9729 global $CFG;
9730 // Check to see if it is missing. If so, initialise it.
9731 if (empty($CFG->siteidentifier)) {
9732 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
9734 // Return it.
9735 return $CFG->siteidentifier;
9739 * Check whether the given password has no more than the specified
9740 * number of consecutive identical characters.
9742 * @param string $password password to be checked against the password policy
9743 * @param integer $maxchars maximum number of consecutive identical characters
9745 function check_consecutive_identical_characters($password, $maxchars) {
9747 if ($maxchars < 1) {
9748 return true; // 0 is to disable this check
9750 if (strlen($password) <= $maxchars) {
9751 return true; // too short to fail this test
9754 $previouschar = '';
9755 $consecutivecount = 1;
9756 foreach (str_split($password) as $char) {
9757 if ($char != $previouschar) {
9758 $consecutivecount = 1;
9760 else {
9761 $consecutivecount++;
9762 if ($consecutivecount > $maxchars) {
9763 return false; // check failed already
9767 $previouschar = $char;
9770 return true;
9774 * helper function to do partial function binding
9775 * so we can use it for preg_replace_callback, for example
9776 * this works with php functions, user functions, static methods and class methods
9777 * it returns you a callback that you can pass on like so:
9779 * $callback = partial('somefunction', $arg1, $arg2);
9780 * or
9781 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
9782 * or even
9783 * $obj = new someclass();
9784 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
9786 * and then the arguments that are passed through at calltime are appended to the argument list.
9788 * @param mixed $function a php callback
9789 * $param mixed $arg1.. $argv arguments to partially bind with
9791 * @return callback
9793 function partial() {
9794 if (!class_exists('partial')) {
9795 class partial{
9796 var $values = array();
9797 var $func;
9799 function __construct($func, $args) {
9800 $this->values = $args;
9801 $this->func = $func;
9804 function method() {
9805 $args = func_get_args();
9806 return call_user_func_array($this->func, array_merge($this->values, $args));
9810 $args = func_get_args();
9811 $func = array_shift($args);
9812 $p = new partial($func, $args);
9813 return array($p, 'method');
9817 * helper function to load up and initialise the mnet environment
9818 * this must be called before you use mnet functions.
9820 * @return mnet_environment the equivalent of old $MNET global
9822 function get_mnet_environment() {
9823 global $CFG;
9824 require_once($CFG->dirroot . '/mnet/lib.php');
9825 static $instance = null;
9826 if (empty($instance)) {
9827 $instance = new mnet_environment();
9828 $instance->init();
9830 return $instance;
9834 * during xmlrpc server code execution, any code wishing to access
9835 * information about the remote peer must use this to get it.
9837 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
9839 function get_mnet_remote_client() {
9840 if (!defined('MNET_SERVER')) {
9841 debugging(get_string('notinxmlrpcserver', 'mnet'));
9842 return false;
9844 global $MNET_REMOTE_CLIENT;
9845 if (isset($MNET_REMOTE_CLIENT)) {
9846 return $MNET_REMOTE_CLIENT;
9848 return false;
9852 * during the xmlrpc server code execution, this will be called
9853 * to setup the object returned by {@see get_mnet_remote_client}
9855 * @param mnet_remote_client $client the client to set up
9857 function set_mnet_remote_client($client) {
9858 if (!defined('MNET_SERVER')) {
9859 throw new moodle_exception('notinxmlrpcserver', 'mnet');
9861 global $MNET_REMOTE_CLIENT;
9862 $MNET_REMOTE_CLIENT = $client;
9866 * return the jump url for a given remote user
9867 * this is used for rewriting forum post links in emails, etc
9869 * @param stdclass $user the user to get the idp url for
9871 function mnet_get_idp_jump_url($user) {
9872 global $CFG;
9874 static $mnetjumps = array();
9875 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
9876 $idp = mnet_get_peer_host($user->mnethostid);
9877 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
9878 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
9880 return $mnetjumps[$user->mnethostid];
9884 * Gets the homepage to use for the current user
9886 * @return int One of HOMEPAGE_*
9888 function get_home_page() {
9889 global $CFG;
9891 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
9892 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
9893 return HOMEPAGE_MY;
9894 } else {
9895 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
9898 return HOMEPAGE_SITE;