MDL-23939 do not load groups and profile for guest user because they can not have...
[moodle.git] / lib / moodlelib.php
blobc9542bdd672a62daa07726656099b533c3c65321
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 * @global object
4835 * @param user $user A {@link $USER} object
4836 * @return bool Returns true if mail was sent OK and false if there was an error.
4838 function reset_password_and_mail($user) {
4839 global $CFG;
4841 $site = get_site();
4842 $supportuser = generate_email_supportuser();
4844 $userauth = get_auth_plugin($user->auth);
4845 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
4846 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4847 return false;
4850 $newpassword = generate_password();
4852 if (!$userauth->user_update_password($user, $newpassword)) {
4853 print_error("cannotsetpassword");
4856 $a = new stdClass();
4857 $a->firstname = $user->firstname;
4858 $a->lastname = $user->lastname;
4859 $a->sitename = format_string($site->fullname);
4860 $a->username = $user->username;
4861 $a->newpassword = $newpassword;
4862 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
4863 $a->signoff = generate_email_signoff();
4865 $message = get_string('newpasswordtext', '', $a);
4867 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
4869 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4870 return email_to_user($user, $supportuser, $subject, $message);
4875 * Send email to specified user with confirmation text and activation link.
4877 * @global object
4878 * @param user $user A {@link $USER} object
4879 * @return bool Returns true if mail was sent OK and false if there was an error.
4881 function send_confirmation_email($user) {
4882 global $CFG;
4884 $site = get_site();
4885 $supportuser = generate_email_supportuser();
4887 $data = new stdClass();
4888 $data->firstname = fullname($user);
4889 $data->sitename = format_string($site->fullname);
4890 $data->admin = generate_email_signoff();
4892 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
4894 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
4895 $message = get_string('emailconfirmation', '', $data);
4896 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4898 $user->mailformat = 1; // Always send HTML version as well
4900 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4901 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4906 * send_password_change_confirmation_email.
4908 * @global object
4909 * @param user $user A {@link $USER} object
4910 * @return bool Returns true if mail was sent OK and false if there was an error.
4912 function send_password_change_confirmation_email($user) {
4913 global $CFG;
4915 $site = get_site();
4916 $supportuser = generate_email_supportuser();
4918 $data = new stdClass();
4919 $data->firstname = $user->firstname;
4920 $data->lastname = $user->lastname;
4921 $data->sitename = format_string($site->fullname);
4922 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
4923 $data->admin = generate_email_signoff();
4925 $message = get_string('emailpasswordconfirmation', '', $data);
4926 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
4928 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4929 return email_to_user($user, $supportuser, $subject, $message);
4934 * send_password_change_info.
4936 * @global object
4937 * @param user $user A {@link $USER} object
4938 * @return bool Returns true if mail was sent OK and false if there was an error.
4940 function send_password_change_info($user) {
4941 global $CFG;
4943 $site = get_site();
4944 $supportuser = generate_email_supportuser();
4945 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4947 $data = new stdClass();
4948 $data->firstname = $user->firstname;
4949 $data->lastname = $user->lastname;
4950 $data->sitename = format_string($site->fullname);
4951 $data->admin = generate_email_signoff();
4953 $userauth = get_auth_plugin($user->auth);
4955 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
4956 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4957 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4958 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4959 return email_to_user($user, $supportuser, $subject, $message);
4962 if ($userauth->can_change_password() and $userauth->change_password_url()) {
4963 // we have some external url for password changing
4964 $data->link .= $userauth->change_password_url();
4966 } else {
4967 //no way to change password, sorry
4968 $data->link = '';
4971 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
4972 $message = get_string('emailpasswordchangeinfo', '', $data);
4973 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4974 } else {
4975 $message = get_string('emailpasswordchangeinfofail', '', $data);
4976 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4979 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4980 return email_to_user($user, $supportuser, $subject, $message);
4985 * Check that an email is allowed. It returns an error message if there
4986 * was a problem.
4988 * @global object
4989 * @param string $email Content of email
4990 * @return string|false
4992 function email_is_not_allowed($email) {
4993 global $CFG;
4995 if (!empty($CFG->allowemailaddresses)) {
4996 $allowed = explode(' ', $CFG->allowemailaddresses);
4997 foreach ($allowed as $allowedpattern) {
4998 $allowedpattern = trim($allowedpattern);
4999 if (!$allowedpattern) {
5000 continue;
5002 if (strpos($allowedpattern, '.') === 0) {
5003 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5004 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5005 return false;
5008 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5009 return false;
5012 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5014 } else if (!empty($CFG->denyemailaddresses)) {
5015 $denied = explode(' ', $CFG->denyemailaddresses);
5016 foreach ($denied as $deniedpattern) {
5017 $deniedpattern = trim($deniedpattern);
5018 if (!$deniedpattern) {
5019 continue;
5021 if (strpos($deniedpattern, '.') === 0) {
5022 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5023 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5024 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5027 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5028 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5033 return false;
5036 /// FILE HANDLING /////////////////////////////////////////////
5039 * Returns local file storage instance
5041 * @return file_storage
5043 function get_file_storage() {
5044 global $CFG;
5046 static $fs = null;
5048 if ($fs) {
5049 return $fs;
5052 require_once("$CFG->libdir/filelib.php");
5054 if (isset($CFG->filedir)) {
5055 $filedir = $CFG->filedir;
5056 } else {
5057 $filedir = $CFG->dataroot.'/filedir';
5060 if (isset($CFG->trashdir)) {
5061 $trashdirdir = $CFG->trashdir;
5062 } else {
5063 $trashdirdir = $CFG->dataroot.'/trashdir';
5066 $fs = new file_storage($filedir, $trashdirdir, "$CFG->dataroot/temp/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5068 return $fs;
5072 * Returns local file storage instance
5074 * @return file_browser
5076 function get_file_browser() {
5077 global $CFG;
5079 static $fb = null;
5081 if ($fb) {
5082 return $fb;
5085 require_once("$CFG->libdir/filelib.php");
5087 $fb = new file_browser();
5089 return $fb;
5093 * Returns file packer
5095 * @param string $mimetype default application/zip
5096 * @return file_packer
5098 function get_file_packer($mimetype='application/zip') {
5099 global $CFG;
5101 static $fp = array();;
5103 if (isset($fp[$mimetype])) {
5104 return $fp[$mimetype];
5107 switch ($mimetype) {
5108 case 'application/zip':
5109 case 'application/vnd.moodle.backup':
5110 $classname = 'zip_packer';
5111 break;
5112 case 'application/x-tar':
5113 // $classname = 'tar_packer';
5114 // break;
5115 default:
5116 return false;
5119 require_once("$CFG->libdir/filestorage/$classname.php");
5120 $fp[$mimetype] = new $classname();
5122 return $fp[$mimetype];
5126 * Returns current name of file on disk if it exists.
5128 * @param string $newfile File to be verified
5129 * @return string Current name of file on disk if true
5131 function valid_uploaded_file($newfile) {
5132 if (empty($newfile)) {
5133 return '';
5135 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5136 return $newfile['tmp_name'];
5137 } else {
5138 return '';
5143 * Returns the maximum size for uploading files.
5145 * There are seven possible upload limits:
5146 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5147 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5148 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5149 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5150 * 5. by the Moodle admin in $CFG->maxbytes
5151 * 6. by the teacher in the current course $course->maxbytes
5152 * 7. by the teacher for the current module, eg $assignment->maxbytes
5154 * These last two are passed to this function as arguments (in bytes).
5155 * Anything defined as 0 is ignored.
5156 * The smallest of all the non-zero numbers is returned.
5158 * @todo Finish documenting this function
5160 * @param int $sizebytes Set maximum size
5161 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5162 * @param int $modulebytes Current module ->maxbytes (in bytes)
5163 * @return int The maximum size for uploading files.
5165 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5167 if (! $filesize = ini_get('upload_max_filesize')) {
5168 $filesize = '5M';
5170 $minimumsize = get_real_size($filesize);
5172 if ($postsize = ini_get('post_max_size')) {
5173 $postsize = get_real_size($postsize);
5174 if ($postsize < $minimumsize) {
5175 $minimumsize = $postsize;
5179 if ($sitebytes and $sitebytes < $minimumsize) {
5180 $minimumsize = $sitebytes;
5183 if ($coursebytes and $coursebytes < $minimumsize) {
5184 $minimumsize = $coursebytes;
5187 if ($modulebytes and $modulebytes < $minimumsize) {
5188 $minimumsize = $modulebytes;
5191 return $minimumsize;
5195 * Returns an array of possible sizes in local language
5197 * Related to {@link get_max_upload_file_size()} - this function returns an
5198 * array of possible sizes in an array, translated to the
5199 * local language.
5201 * @todo Finish documenting this function
5203 * @global object
5204 * @uses SORT_NUMERIC
5205 * @param int $sizebytes Set maximum size
5206 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5207 * @param int $modulebytes Current module ->maxbytes (in bytes)
5208 * @return array
5210 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5211 global $CFG;
5213 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5214 return array();
5217 $filesize[intval($maxsize)] = display_size($maxsize);
5219 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5220 5242880, 10485760, 20971520, 52428800, 104857600);
5222 // Allow maxbytes to be selected if it falls outside the above boundaries
5223 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
5224 // note: get_real_size() is used in order to prevent problems with invalid values
5225 $sizelist[] = get_real_size($CFG->maxbytes);
5228 foreach ($sizelist as $sizebytes) {
5229 if ($sizebytes < $maxsize) {
5230 $filesize[intval($sizebytes)] = display_size($sizebytes);
5234 krsort($filesize, SORT_NUMERIC);
5236 return $filesize;
5240 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
5242 * If excludefiles is defined, then that file/directory is ignored
5243 * If getdirs is true, then (sub)directories are included in the output
5244 * If getfiles is true, then files are included in the output
5245 * (at least one of these must be true!)
5247 * @todo Finish documenting this function. Add examples of $excludefile usage.
5249 * @param string $rootdir A given root directory to start from
5250 * @param string|array $excludefile If defined then the specified file/directory is ignored
5251 * @param bool $descend If true then subdirectories are recursed as well
5252 * @param bool $getdirs If true then (sub)directories are included in the output
5253 * @param bool $getfiles If true then files are included in the output
5254 * @return array An array with all the filenames in
5255 * all subdirectories, relative to the given rootdir
5257 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5259 $dirs = array();
5261 if (!$getdirs and !$getfiles) { // Nothing to show
5262 return $dirs;
5265 if (!is_dir($rootdir)) { // Must be a directory
5266 return $dirs;
5269 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5270 return $dirs;
5273 if (!is_array($excludefiles)) {
5274 $excludefiles = array($excludefiles);
5277 while (false !== ($file = readdir($dir))) {
5278 $firstchar = substr($file, 0, 1);
5279 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5280 continue;
5282 $fullfile = $rootdir .'/'. $file;
5283 if (filetype($fullfile) == 'dir') {
5284 if ($getdirs) {
5285 $dirs[] = $file;
5287 if ($descend) {
5288 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5289 foreach ($subdirs as $subdir) {
5290 $dirs[] = $file .'/'. $subdir;
5293 } else if ($getfiles) {
5294 $dirs[] = $file;
5297 closedir($dir);
5299 asort($dirs);
5301 return $dirs;
5306 * Adds up all the files in a directory and works out the size.
5308 * @todo Finish documenting this function
5310 * @param string $rootdir The directory to start from
5311 * @param string $excludefile A file to exclude when summing directory size
5312 * @return int The summed size of all files and subfiles within the root directory
5314 function get_directory_size($rootdir, $excludefile='') {
5315 global $CFG;
5317 // do it this way if we can, it's much faster
5318 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5319 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5320 $output = null;
5321 $return = null;
5322 exec($command,$output,$return);
5323 if (is_array($output)) {
5324 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5328 if (!is_dir($rootdir)) { // Must be a directory
5329 return 0;
5332 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
5333 return 0;
5336 $size = 0;
5338 while (false !== ($file = readdir($dir))) {
5339 $firstchar = substr($file, 0, 1);
5340 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5341 continue;
5343 $fullfile = $rootdir .'/'. $file;
5344 if (filetype($fullfile) == 'dir') {
5345 $size += get_directory_size($fullfile, $excludefile);
5346 } else {
5347 $size += filesize($fullfile);
5350 closedir($dir);
5352 return $size;
5356 * Converts bytes into display form
5358 * @todo Finish documenting this function. Verify return type.
5360 * @staticvar string $gb Localized string for size in gigabytes
5361 * @staticvar string $mb Localized string for size in megabytes
5362 * @staticvar string $kb Localized string for size in kilobytes
5363 * @staticvar string $b Localized string for size in bytes
5364 * @param int $size The size to convert to human readable form
5365 * @return string
5367 function display_size($size) {
5369 static $gb, $mb, $kb, $b;
5371 if (empty($gb)) {
5372 $gb = get_string('sizegb');
5373 $mb = get_string('sizemb');
5374 $kb = get_string('sizekb');
5375 $b = get_string('sizeb');
5378 if ($size >= 1073741824) {
5379 $size = round($size / 1073741824 * 10) / 10 . $gb;
5380 } else if ($size >= 1048576) {
5381 $size = round($size / 1048576 * 10) / 10 . $mb;
5382 } else if ($size >= 1024) {
5383 $size = round($size / 1024 * 10) / 10 . $kb;
5384 } else {
5385 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
5387 return $size;
5391 * Cleans a given filename by removing suspicious or troublesome characters
5392 * @see clean_param()
5394 * @uses PARAM_FILE
5395 * @param string $string file name
5396 * @return string cleaned file name
5398 function clean_filename($string) {
5399 return clean_param($string, PARAM_FILE);
5403 /// STRING TRANSLATION ////////////////////////////////////////
5406 * Returns the code for the current language
5408 * @return string
5410 function current_language() {
5411 global $CFG, $USER, $SESSION, $COURSE;
5413 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
5414 $return = $COURSE->lang;
5416 } else if (!empty($SESSION->lang)) { // Session language can override other settings
5417 $return = $SESSION->lang;
5419 } else if (!empty($USER->lang)) {
5420 $return = $USER->lang;
5422 } else if (isset($CFG->lang)) {
5423 $return = $CFG->lang;
5425 } else {
5426 $return = 'en';
5429 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
5431 return $return;
5435 * Returns parent language of current active language if defined
5437 * @uses COURSE
5438 * @uses SESSION
5439 * @param string $lang null means current language
5440 * @return string
5442 function get_parent_language($lang=null) {
5443 global $COURSE, $SESSION;
5445 //let's hack around the current language
5446 if (!empty($lang)) {
5447 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
5448 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
5449 $COURSE->lang = '';
5450 $SESSION->lang = $lang;
5453 $parentlang = get_string('parentlanguage', 'langconfig');
5454 if ($parentlang === 'en') {
5455 $parentlang = '';
5458 //let's hack around the current language
5459 if (!empty($lang)) {
5460 $COURSE->lang = $old_course_lang;
5461 $SESSION->lang = $old_session_lang;
5464 return $parentlang;
5468 * Returns current string_manager instance.
5470 * The param $forcereload is needed for CLI installer only where the string_manager instance
5471 * must be replaced during the install.php script life time.
5473 * @param bool $forcereload shall the singleton be released and new instance created instead?
5474 * @return string_manager
5476 function get_string_manager($forcereload=false) {
5477 global $CFG;
5479 static $singleton = null;
5481 if ($forcereload) {
5482 $singleton = null;
5484 if ($singleton === null) {
5485 if (empty($CFG->early_install_lang)) {
5486 if (empty($CFG->langlist)) {
5487 $translist = array();
5488 } else {
5489 $translist = explode(',', $CFG->langlist);
5491 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, "$CFG->dataroot/cache/lang", !empty($CFG->langstringcache), $translist);
5492 } else {
5493 $singleton = new install_string_manager();
5497 return $singleton;
5502 * Interface describing class which is responsible for getting
5503 * of localised strings from language packs.
5505 * @package moodlecore
5506 * @copyright 2010 Petr Skoda (http://skodak.org)
5507 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5509 interface string_manager {
5511 * Get String returns a requested string
5513 * @param string $identifier The identifier of the string to search for
5514 * @param string $component The module the string is associated with
5515 * @param string|object|array $a An object, string or number that can be used
5516 * within translation strings
5517 * @param string $lang moodle translation language, NULL means use current
5518 * @return string The String !
5520 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
5523 * Does the string actually exist?
5525 * get_string() is throwing debug warnings, sometimes we do not want them
5526 * or we want to display better explanation of the problem.
5528 * Use with care!
5530 * @param string $identifier The identifier of the string to search for
5531 * @param string $component The module the string is associated with
5532 * @return boot true if exists
5534 public function string_exists($identifier, $component);
5537 * Returns a localised list of all country names, sorted by country keys.
5538 * @param bool $returnall return all or just enabled
5539 * @param string $lang moodle translation language, NULL means use current
5540 * @return array two-letter country code => translated name.
5542 public function get_list_of_countries($returnall = false, $lang = NULL);
5545 * Returns a localised list of languages, sorted by code keys.
5547 * @param string $lang moodle translation language, NULL means use current
5548 * @param string $standard language list standard
5549 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
5550 * @return array language code => translated name
5552 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
5555 * Does the translation exist?
5557 * @param string $lang moodle translation language code
5558 * @param bool include also disabled translations?
5559 * @return boot true if exists
5561 public function translation_exists($lang, $includeall = true);
5564 * Returns localised list of installed translations
5565 * @param bool $returnall return all or just enabled
5566 * @return array moodle translation code => localised translation name
5568 public function get_list_of_translations($returnall = false);
5571 * Returns localised list of currencies.
5573 * @param string $lang moodle translation language, NULL means use current
5574 * @return array currency code => localised currency name
5576 public function get_list_of_currencies($lang = NULL);
5579 * Load all strings for one component
5580 * @param string $component The module the string is associated with
5581 * @param string $lang
5582 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5583 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5584 * @return array of all string for given component and lang
5586 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
5589 * Invalidates all caches, should the implementation use any
5591 public function reset_caches();
5596 * Standard string_manager implementation
5598 * @package moodlecore
5599 * @copyright 2010 Petr Skoda (http://skodak.org)
5600 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5602 class core_string_manager implements string_manager {
5603 /** @var string location of all packs except 'en' */
5604 protected $otherroot;
5605 /** @var string location of all lang pack local modifications */
5606 protected $localroot;
5607 /** @var string location of on-disk cache of merged strings */
5608 protected $cacheroot;
5609 /** @var array lang string cache - it will be optimised more later */
5610 protected $cache = array();
5611 /** @var int get_string() counter */
5612 protected $countgetstring = 0;
5613 /** @var int in-memory cache hits counter */
5614 protected $countmemcache = 0;
5615 /** @var int on-disk cache hits counter */
5616 protected $countdiskcache = 0;
5617 /** @var bool use disk cache */
5618 protected $usediskcache;
5619 /* @var array limit list of translations */
5620 protected $translist;
5623 * Crate new instance of string manager
5625 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
5626 * @param string $localroot usually the same as $otherroot
5627 * @param string $cacheroot usually lang dir in cache folder
5628 * @param bool $usediskcache use disk cache
5629 * @param array $translist limit list of visible translations
5631 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist) {
5632 $this->otherroot = $otherroot;
5633 $this->localroot = $localroot;
5634 $this->cacheroot = $cacheroot;
5635 $this->usediskcache = $usediskcache;
5636 $this->translist = $translist;
5640 * Returns dependencies of current language, en is not included.
5641 * @param string $lang
5642 * @return array all parents, the lang itself is last
5644 public function get_language_dependencies($lang) {
5645 if ($lang === 'en') {
5646 return array();
5648 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
5649 return array();
5651 $string = array();
5652 include("$this->otherroot/$lang/langconfig.php");
5654 if (empty($string['parentlanguage'])) {
5655 return array($lang);
5656 } else {
5657 $parentlang = $string['parentlanguage'];
5658 unset($string);
5659 return array_merge($this->get_language_dependencies($parentlang), array($lang));
5664 * Load all strings for one component
5665 * @param string $component The module the string is associated with
5666 * @param string $lang
5667 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5668 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5669 * @return array of all string for given component and lang
5671 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
5672 global $CFG;
5674 list($plugintype, $pluginname) = normalize_component($component);
5675 if ($plugintype == 'core' and is_null($pluginname)) {
5676 $component = 'core';
5677 } else {
5678 $component = $plugintype . '_' . $pluginname;
5681 if (!$disablecache) {
5682 // try in-memory cache first
5683 if (isset($this->cache[$lang][$component])) {
5684 $this->countmemcache++;
5685 return $this->cache[$lang][$component];
5688 // try on-disk cache then
5689 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
5690 $this->countdiskcache++;
5691 include($this->cacheroot . "/$lang/$component.php");
5692 return $this->cache[$lang][$component];
5696 // no cache found - let us merge all possible sources of the strings
5697 if ($plugintype === 'core') {
5698 $file = $pluginname;
5699 if ($file === null) {
5700 $file = 'moodle';
5702 $string = array();
5703 // first load english pack
5704 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
5705 return array();
5707 include("$CFG->dirroot/lang/en/$file.php");
5708 $originalkeys = array_keys($string);
5709 $originalkeys = array_flip($originalkeys);
5711 // and then corresponding local if present and allowed
5712 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5713 include("$this->localroot/en_local/$file.php");
5715 // now loop through all langs in correct order
5716 $deps = $this->get_language_dependencies($lang);
5717 foreach ($deps as $dep) {
5718 // the main lang string location
5719 if (file_exists("$this->otherroot/$dep/$file.php")) {
5720 include("$this->otherroot/$dep/$file.php");
5722 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5723 include("$this->localroot/{$dep}_local/$file.php");
5727 } else {
5728 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
5729 return array();
5731 if ($plugintype === 'mod') {
5732 // bloody mod hack
5733 $file = $pluginname;
5734 } else {
5735 $file = $plugintype . '_' . $pluginname;
5737 $string = array();
5738 // first load English pack
5739 if (!file_exists("$location/lang/en/$file.php")) {
5740 //English pack does not exist, so do not try to load anything else
5741 return array();
5743 include("$location/lang/en/$file.php");
5744 $originalkeys = array_keys($string);
5745 $originalkeys = array_flip($originalkeys);
5746 // and then corresponding local english if present
5747 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5748 include("$this->localroot/en_local/$file.php");
5751 // now loop through all langs in correct order
5752 $deps = $this->get_language_dependencies($lang);
5753 foreach ($deps as $dep) {
5754 // legacy location - used by contrib only
5755 if (file_exists("$location/lang/$dep/$file.php")) {
5756 include("$location/lang/$dep/$file.php");
5758 // the main lang string location
5759 if (file_exists("$this->otherroot/$dep/$file.php")) {
5760 include("$this->otherroot/$dep/$file.php");
5762 // local customisations
5763 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5764 include("$this->localroot/{$dep}_local/$file.php");
5769 // we do not want any extra strings from other languages - everything must be in en lang pack
5770 $string = array_intersect_key($string, $originalkeys);
5772 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
5773 // caches so we do not need to do all this merging and dependencies resolving again
5774 $this->cache[$lang][$component] = $string;
5775 if ($this->usediskcache) {
5776 check_dir_exists("$this->cacheroot/$lang");
5777 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
5779 return $string;
5783 * Does the string actually exist?
5785 * get_string() is throwing debug warnings, sometimes we do not want them
5786 * or we want to display better explanation of the problem.
5788 * Use with care!
5790 * @param string $identifier The identifier of the string to search for
5791 * @param string $component The module the string is associated with
5792 * @return boot true if exists
5794 public function string_exists($identifier, $component) {
5795 $identifier = clean_param($identifier, PARAM_STRINGID);
5796 if (empty($identifier)) {
5797 return false;
5799 $lang = current_language();
5800 $string = $this->load_component_strings($component, $lang);
5801 return isset($string[$identifier]);
5805 * Get String returns a requested string
5807 * @param string $identifier The identifier of the string to search for
5808 * @param string $component The module the string is associated with
5809 * @param string|object|array $a An object, string or number that can be used
5810 * within translation strings
5811 * @param string $lang moodle translation language, NULL means use current
5812 * @return string The String !
5814 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
5815 $this->countgetstring++;
5816 // there are very many uses of these time formating strings without the 'langconfig' component,
5817 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
5818 static $langconfigstrs = array(
5819 'strftimedate' => 1,
5820 'strftimedatefullshort' => 1,
5821 'strftimedateshort' => 1,
5822 'strftimedatetime' => 1,
5823 'strftimedatetimeshort' => 1,
5824 'strftimedaydate' => 1,
5825 'strftimedaydatetime' => 1,
5826 'strftimedayshort' => 1,
5827 'strftimedaytime' => 1,
5828 'strftimemonthyear' => 1,
5829 'strftimerecent' => 1,
5830 'strftimerecentfull' => 1,
5831 'strftimetime' => 1);
5833 if (empty($component)) {
5834 if (isset($langconfigstrs[$identifier])) {
5835 $component = 'langconfig';
5836 } else {
5837 $component = 'moodle';
5841 if ($lang === NULL) {
5842 $lang = current_language();
5845 $string = $this->load_component_strings($component, $lang);
5847 if (!isset($string[$identifier])) {
5848 if ($component === 'pix' or $component === 'core_pix') {
5849 // this component contains only alt tags for emoticons,
5850 // not all of them are supposed to be defined
5851 return '';
5853 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
5854 // parentlanguage is a special string, undefined means use English if not defined
5855 return 'en';
5857 if ($this->usediskcache) {
5858 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources
5859 $string = $this->load_component_strings($component, $lang, true);
5861 if (!isset($string[$identifier])) {
5862 // the string is still missing - should be fixed by developer
5863 debugging("Invalid get_string() identifier: '$identifier' or component '$component'", DEBUG_DEVELOPER);
5864 return "[[$identifier]]";
5868 $string = $string[$identifier];
5870 if ($a !== NULL) {
5871 if (is_object($a) or is_array($a)) {
5872 $a = (array)$a;
5873 $search = array();
5874 $replace = array();
5875 foreach ($a as $key=>$value) {
5876 if (is_int($key)) {
5877 // we do not support numeric keys - sorry!
5878 continue;
5880 if (is_object($value) or is_array($value)) {
5881 // we support just string as value
5882 continue;
5884 $search[] = '{$a->'.$key.'}';
5885 $replace[] = (string)$value;
5887 if ($search) {
5888 $string = str_replace($search, $replace, $string);
5890 } else {
5891 $string = str_replace('{$a}', (string)$a, $string);
5895 return $string;
5899 * Returns information about the string_manager performance
5900 * @return array
5902 public function get_performance_summary() {
5903 return array(array(
5904 'langcountgetstring' => $this->countgetstring,
5905 'langcountmemcache' => $this->countmemcache,
5906 'langcountdiskcache' => $this->countdiskcache,
5907 ), array(
5908 'langcountgetstring' => 'get_string calls',
5909 'langcountmemcache' => 'strings mem cache hits',
5910 'langcountdiskcache' => 'strings disk cache hits',
5915 * Returns a localised list of all country names, sorted by localised name.
5917 * @param bool $returnall return all or just enabled
5918 * @param string $lang moodle translation language, NULL means use current
5919 * @return array two-letter country code => translated name.
5921 public function get_list_of_countries($returnall = false, $lang = NULL) {
5922 global $CFG;
5924 if ($lang === NULL) {
5925 $lang = current_language();
5928 $countries = $this->load_component_strings('core_countries', $lang);
5929 textlib_get_instance()->asort($countries);
5930 if (!$returnall and !empty($CFG->allcountrycodes)) {
5931 $enabled = explode(',', $CFG->allcountrycodes);
5932 $return = array();
5933 foreach ($enabled as $c) {
5934 if (isset($countries[$c])) {
5935 $return[$c] = $countries[$c];
5938 return $return;
5941 return $countries;
5945 * Returns a localised list of languages, sorted by code keys.
5947 * @param string $lang moodle translation language, NULL means use current
5948 * @param string $standard language list standard
5949 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
5950 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
5951 * @return array language code => translated name
5953 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
5954 if ($lang === NULL) {
5955 $lang = current_language();
5958 if ($standard === 'iso6392') {
5959 $langs = $this->load_component_strings('core_iso6392', $lang);
5960 ksort($langs);
5961 return $langs;
5963 } else if ($standard === 'iso6391') {
5964 $langs2 = $this->load_component_strings('core_iso6392', $lang);
5965 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
5966 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
5967 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
5968 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
5969 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
5970 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
5971 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
5972 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
5973 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
5974 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
5975 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
5976 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
5977 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
5978 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
5979 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
5980 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
5981 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
5982 $langs1 = array();
5983 foreach ($mapping as $c2=>$c1) {
5984 $langs1[$c1] = $langs2[$c2];
5986 ksort($langs1);
5987 return $langs1;
5989 } else {
5990 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
5993 return array();
5997 * Does the translation exist?
5999 * @param string $lang moodle translation language code
6000 * @param bool include also disabled translations?
6001 * @return boot true if exists
6003 public function translation_exists($lang, $includeall = true) {
6004 global $CFG;
6006 if (strpos($lang, '_local') !== false) {
6007 // _local packs are not real translations
6008 return false;
6010 if (!$includeall and !empty($CFG->langlist)) {
6011 $enabled = explode(',', $CFG->langlist);
6012 if (!in_array($lang, $enabled)) {
6013 return false;
6016 if ($lang === 'en') {
6017 // part of distribution
6018 return true;
6020 return file_exists("$this->otherroot/$lang/langconfig.php");
6024 * Returns localised list of installed translations
6025 * @param bool $returnall return all or just enabled
6026 * @return array moodle translation code => localised translation name
6028 public function get_list_of_translations($returnall = false) {
6029 global $CFG;
6031 $languages = array();
6033 //TODO: add some translist cache stored in normal cache dir
6035 if (!$returnall and !empty($this->translist)) {
6036 // return only some translations
6037 foreach ($this->translist as $lang) {
6038 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6039 if (strstr($lang, '_local') !== false) {
6040 continue;
6042 if (strstr($lang, '_utf8') !== false) {
6043 continue;
6045 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6046 // some broken or missing lang - can not switch to it anyway
6047 continue;
6049 $string = $this->load_component_strings('langconfig', $lang);
6050 if (!empty($string['thislanguage'])) {
6051 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6053 unset($string);
6056 } else {
6057 // return all languages available in system
6058 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6060 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6061 // Sort all
6063 // Loop through all langs and get info
6064 foreach ($langdirs as $lang) {
6065 if (strstr($lang, '_local') !== false) {
6066 continue;
6068 if (strstr($lang, '_utf8') !== false) {
6069 continue;
6071 $string = $this->load_component_strings('langconfig', $lang);
6072 if (!empty($string['thislanguage'])) {
6073 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6075 unset($string);
6079 textlib_get_instance()->asort($languages);
6081 return $languages;
6085 * Returns localised list of currencies.
6087 * @param string $lang moodle translation language, NULL means use current
6088 * @return array currency code => localised currency name
6090 public function get_list_of_currencies($lang = NULL) {
6091 if ($lang === NULL) {
6092 $lang = current_language();
6095 $currencies = $this->load_component_strings('core_currencies', $lang);
6096 asort($currencies);
6098 return $currencies;
6102 * Clears both in-memory and on-disk caches
6104 public function reset_caches() {
6105 global $CFG;
6106 require_once("$CFG->libdir/filelib.php");
6108 fulldelete($this->cacheroot);
6109 $this->cache = array();
6115 * Minimalistic string fetching implementation
6116 * that is used in installer before we fetch the wanted
6117 * language pack from moodle.org lang download site.
6119 * @package moodlecore
6120 * @copyright 2010 Petr Skoda (http://skodak.org)
6121 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6123 class install_string_manager implements string_manager {
6124 /** @var string location of pre-install packs for all langs */
6125 protected $installroot;
6128 * Crate new instance of install string manager
6130 public function __construct() {
6131 global $CFG;
6132 $this->installroot = "$CFG->dirroot/install/lang";
6136 * Load all strings for one component
6137 * @param string $component The module the string is associated with
6138 * @param string $lang
6139 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6140 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6141 * @return array of all string for given component and lang
6143 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6144 // not needed in installer
6145 return array();
6149 * Does the string actually exist?
6151 * get_string() is throwing debug warnings, sometimes we do not want them
6152 * or we want to display better explanation of the problem.
6154 * Use with care!
6156 * @param string $identifier The identifier of the string to search for
6157 * @param string $component The module the string is associated with
6158 * @return boot true if exists
6160 public function string_exists($identifier, $component) {
6161 $identifier = clean_param($identifier, PARAM_STRINGID);
6162 if (empty($identifier)) {
6163 return false;
6165 // simple old style hack ;)
6166 $str = get_string($identifier, $component);
6167 return (strpos($str, '[[') === false);
6171 * Get String returns a requested string
6173 * @param string $identifier The identifier of the string to search for
6174 * @param string $component The module the string is associated with
6175 * @param string|object|array $a An object, string or number that can be used
6176 * within translation strings
6177 * @param string $lang moodle translation language, NULL means use current
6178 * @return string The String !
6180 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6181 if (!$component) {
6182 $component = 'moodle';
6185 if ($lang === NULL) {
6186 $lang = current_language();
6189 //get parent lang
6190 $parent = '';
6191 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
6192 if (file_exists("$this->installroot/$lang/langconfig.php")) {
6193 $string = array();
6194 include("$this->installroot/$lang/langconfig.php");
6195 if (isset($string['parentlanguage'])) {
6196 $parent = $string['parentlanguage'];
6198 unset($string);
6202 // include en string first
6203 if (!file_exists("$this->installroot/en/$component.php")) {
6204 return "[[$identifier]]";
6206 $string = array();
6207 include("$this->installroot/en/$component.php");
6209 // now override en with parent if defined
6210 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
6211 include("$this->installroot/$parent/$component.php");
6214 // finally override with requested language
6215 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
6216 include("$this->installroot/$lang/$component.php");
6219 if (!isset($string[$identifier])) {
6220 return "[[$identifier]]";
6223 $string = $string[$identifier];
6225 if ($a !== NULL) {
6226 if (is_object($a) or is_array($a)) {
6227 $a = (array)$a;
6228 $search = array();
6229 $replace = array();
6230 foreach ($a as $key=>$value) {
6231 if (is_int($key)) {
6232 // we do not support numeric keys - sorry!
6233 continue;
6235 $search[] = '{$a->'.$key.'}';
6236 $replace[] = (string)$value;
6238 if ($search) {
6239 $string = str_replace($search, $replace, $string);
6241 } else {
6242 $string = str_replace('{$a}', (string)$a, $string);
6246 return $string;
6250 * Returns a localised list of all country names, sorted by country keys.
6252 * @param bool $returnall return all or just enabled
6253 * @param string $lang moodle translation language, NULL means use current
6254 * @return array two-letter country code => translated name.
6256 public function get_list_of_countries($returnall = false, $lang = NULL) {
6257 //not used in installer
6258 return array();
6262 * Returns a localised list of languages, sorted by code keys.
6264 * @param string $lang moodle translation language, NULL means use current
6265 * @param string $standard language list standard
6266 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6267 * @return array language code => translated name
6269 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
6270 //not used in installer
6271 return array();
6275 * Does the translation exist?
6277 * @param string $lang moodle translation language code
6278 * @param bool include also disabled translations?
6279 * @return boot true if exists
6281 public function translation_exists($lang, $includeall = true) {
6282 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
6286 * Returns localised list of installed translations
6287 * @param bool $returnall return all or just enabled
6288 * @return array moodle translation code => localised translation name
6290 public function get_list_of_translations($returnall = false) {
6291 // return all is ignored here - we need to know all langs in installer
6292 $languages = array();
6293 // Get raw list of lang directories
6294 $langdirs = get_list_of_plugins('install/lang');
6295 asort($langdirs);
6296 // Get some info from each lang
6297 foreach ($langdirs as $lang) {
6298 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
6299 $string = array();
6300 include($this->installroot.'/'.$lang.'/langconfig.php');
6301 if (!empty($string['thislanguage'])) {
6302 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
6306 // Return array
6307 return $languages;
6311 * Returns localised list of currencies.
6313 * @param string $lang moodle translation language, NULL means use current
6314 * @return array currency code => localised currency name
6316 public function get_list_of_currencies($lang = NULL) {
6317 // not used in installer
6318 return array();
6322 * This implementation does not use any caches
6324 public function reset_caches() {}
6329 * Returns a localized string.
6331 * Returns the translated string specified by $identifier as
6332 * for $module. Uses the same format files as STphp.
6333 * $a is an object, string or number that can be used
6334 * within translation strings
6336 * eg 'hello {$a->firstname} {$a->lastname}'
6337 * or 'hello {$a}'
6339 * If you would like to directly echo the localized string use
6340 * the function {@link print_string()}
6342 * Example usage of this function involves finding the string you would
6343 * like a local equivalent of and using its identifier and module information
6344 * to retrieve it.<br/>
6345 * If you open moodle/lang/en/moodle.php and look near line 278
6346 * you will find a string to prompt a user for their word for 'course'
6347 * <code>
6348 * $string['course'] = 'Course';
6349 * </code>
6350 * So if you want to display the string 'Course'
6351 * in any language that supports it on your site
6352 * you just need to use the identifier 'course'
6353 * <code>
6354 * $mystring = '<strong>'. get_string('course') .'</strong>';
6355 * or
6356 * </code>
6357 * If the string you want is in another file you'd take a slightly
6358 * different approach. Looking in moodle/lang/en/calendar.php you find
6359 * around line 75:
6360 * <code>
6361 * $string['typecourse'] = 'Course event';
6362 * </code>
6363 * If you want to display the string "Course event" in any language
6364 * supported you would use the identifier 'typecourse' and the module 'calendar'
6365 * (because it is in the file calendar.php):
6366 * <code>
6367 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
6368 * </code>
6370 * As a last resort, should the identifier fail to map to a string
6371 * the returned string will be [[ $identifier ]]
6373 * @param string $identifier The key identifier for the localized string
6374 * @param string $component The module where the key identifier is stored,
6375 * usually expressed as the filename in the language pack without the
6376 * .php on the end but can also be written as mod/forum or grade/export/xls.
6377 * If none is specified then moodle.php is used.
6378 * @param string|object|array $a An object, string or number that can be used
6379 * within translation strings
6380 * @return string The localized string.
6382 function get_string($identifier, $component = '', $a = NULL) {
6384 $identifier = clean_param($identifier, PARAM_STRINGID);
6385 if (empty($identifier)) {
6386 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');
6389 if (func_num_args() > 3) {
6390 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
6393 if (strpos($component, '/') !== false) {
6394 debugging('The module name you passed to get_string is the deprecated format ' .
6395 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
6396 $componentpath = explode('/', $component);
6398 switch ($componentpath[0]) {
6399 case 'mod':
6400 $component = $componentpath[1];
6401 break;
6402 case 'blocks':
6403 case 'block':
6404 $component = 'block_'.$componentpath[1];
6405 break;
6406 case 'enrol':
6407 $component = 'enrol_'.$componentpath[1];
6408 break;
6409 case 'format':
6410 $component = 'format_'.$componentpath[1];
6411 break;
6412 case 'grade':
6413 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
6414 break;
6418 return get_string_manager()->get_string($identifier, $component, $a);
6422 * Converts an array of strings to their localized value.
6424 * @param array $array An array of strings
6425 * @param string $module The language module that these strings can be found in.
6426 * @return array and array of translated strings.
6428 function get_strings($array, $component = '') {
6429 $string = new stdClass;
6430 foreach ($array as $item) {
6431 $string->$item = get_string($item, $component);
6433 return $string;
6437 * Prints out a translated string.
6439 * Prints out a translated string using the return value from the {@link get_string()} function.
6441 * Example usage of this function when the string is in the moodle.php file:<br/>
6442 * <code>
6443 * echo '<strong>';
6444 * print_string('course');
6445 * echo '</strong>';
6446 * </code>
6448 * Example usage of this function when the string is not in the moodle.php file:<br/>
6449 * <code>
6450 * echo '<h1>';
6451 * print_string('typecourse', 'calendar');
6452 * echo '</h1>';
6453 * </code>
6455 * @param string $identifier The key identifier for the localized string
6456 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
6457 * @param mixed $a An object, string or number that can be used within translation strings
6459 function print_string($identifier, $component = '', $a = NULL) {
6460 echo get_string($identifier, $component, $a);
6464 * Returns a list of charset codes
6466 * Returns a list of charset codes. It's hardcoded, so they should be added manually
6467 * (checking that such charset is supported by the texlib library!)
6469 * @return array And associative array with contents in the form of charset => charset
6471 function get_list_of_charsets() {
6473 $charsets = array(
6474 'EUC-JP' => 'EUC-JP',
6475 'ISO-2022-JP'=> 'ISO-2022-JP',
6476 'ISO-8859-1' => 'ISO-8859-1',
6477 'SHIFT-JIS' => 'SHIFT-JIS',
6478 'GB2312' => 'GB2312',
6479 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
6480 'UTF-8' => 'UTF-8');
6482 asort($charsets);
6484 return $charsets;
6488 * Returns a list of valid and compatible themes
6490 * @global object
6491 * @return array
6493 function get_list_of_themes() {
6494 global $CFG;
6496 $themes = array();
6498 if (!empty($CFG->themelist)) { // use admin's list of themes
6499 $themelist = explode(',', $CFG->themelist);
6500 } else {
6501 $themelist = array_keys(get_plugin_list("theme"));
6504 foreach ($themelist as $key => $themename) {
6505 $theme = theme_config::load($themename);
6506 $themes[$themename] = $theme;
6508 asort($themes);
6510 return $themes;
6514 * Returns a list of timezones in the current language
6516 * @global object
6517 * @global object
6518 * @return array
6520 function get_list_of_timezones() {
6521 global $CFG, $DB;
6523 static $timezones;
6525 if (!empty($timezones)) { // This function has been called recently
6526 return $timezones;
6529 $timezones = array();
6531 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
6532 foreach($rawtimezones as $timezone) {
6533 if (!empty($timezone->name)) {
6534 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
6535 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
6536 } else {
6537 $timezones[$timezone->name] = $timezone->name;
6539 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
6540 $timezones[$timezone->name] = $timezone->name;
6546 asort($timezones);
6548 for ($i = -13; $i <= 13; $i += .5) {
6549 $tzstring = 'UTC';
6550 if ($i < 0) {
6551 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
6552 } else if ($i > 0) {
6553 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
6554 } else {
6555 $timezones[sprintf("%.1f", $i)] = $tzstring;
6559 return $timezones;
6563 * Factory function for emoticon_manager
6565 * @return emoticon_manager singleton
6567 function get_emoticon_manager() {
6568 static $singleton = null;
6570 if (is_null($singleton)) {
6571 $singleton = new emoticon_manager();
6574 return $singleton;
6578 * Provides core support for plugins that have to deal with
6579 * emoticons (like HTML editor or emoticon filter).
6581 * Whenever this manager mentiones 'emoticon object', the following data
6582 * structure is expected: stdClass with properties text, imagename, imagecomponent,
6583 * altidentifier and altcomponent
6585 * @see admin_setting_emoticons
6587 class emoticon_manager {
6590 * Returns the currently enabled emoticons
6592 * @return array of emoticon objects
6594 public function get_emoticons() {
6595 global $CFG;
6597 if (empty($CFG->emoticons)) {
6598 return array();
6601 $emoticons = $this->decode_stored_config($CFG->emoticons);
6603 if (!is_array($emoticons)) {
6604 // something is wrong with the format of stored setting
6605 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
6606 return array();
6609 return $emoticons;
6613 * Converts emoticon object into renderable pix_emoticon object
6615 * @param stdClass $emoticon emoticon object
6616 * @param array $attributes explicit HTML attributes to set
6617 * @return pix_emoticon
6619 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
6620 $stringmanager = get_string_manager();
6621 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
6622 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
6623 } else {
6624 $alt = s($emoticon->text);
6626 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
6630 * Encodes the array of emoticon objects into a string storable in config table
6632 * @see self::decode_stored_config()
6633 * @param array $emoticons array of emtocion objects
6634 * @return string
6636 public function encode_stored_config(array $emoticons) {
6637 return json_encode($emoticons);
6641 * Decodes the string into an array of emoticon objects
6643 * @see self::encode_stored_config()
6644 * @param string $encoded
6645 * @return string|null
6647 public function decode_stored_config($encoded) {
6648 $decoded = json_decode($encoded);
6649 if (!is_array($decoded)) {
6650 return null;
6652 return $decoded;
6656 * Returns default set of emoticons supported by Moodle
6658 * @return array of sdtClasses
6660 public function default_emoticons() {
6661 return array(
6662 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
6663 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
6664 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
6665 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
6666 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
6667 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
6668 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
6669 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
6670 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
6671 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
6672 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
6673 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
6674 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
6675 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
6676 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
6677 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
6678 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
6679 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
6680 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
6681 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
6682 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
6683 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
6684 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
6685 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
6686 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
6687 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
6688 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
6689 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
6690 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
6691 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
6696 * Helper method preparing the stdClass with the emoticon properties
6698 * @param string|array $text or array of strings
6699 * @param string $imagename to be used by {@see pix_emoticon}
6700 * @param string $altidentifier alternative string identifier, null for no alt
6701 * @param array $altcomponent where the alternative string is defined
6702 * @param string $imagecomponent to be used by {@see pix_emoticon}
6703 * @return stdClass
6705 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
6706 return (object)array(
6707 'text' => $text,
6708 'imagename' => $imagename,
6709 'imagecomponent' => $imagecomponent,
6710 'altidentifier' => $altidentifier,
6711 'altcomponent' => $altcomponent,
6716 /// ENCRYPTION ////////////////////////////////////////////////
6719 * rc4encrypt
6721 * @todo Finish documenting this function
6723 * @param string $data Data to encrypt
6724 * @return string The now encrypted data
6726 function rc4encrypt($data) {
6727 $password = 'nfgjeingjk';
6728 return endecrypt($password, $data, '');
6732 * rc4decrypt
6734 * @todo Finish documenting this function
6736 * @param string $data Data to decrypt
6737 * @return string The now decrypted data
6739 function rc4decrypt($data) {
6740 $password = 'nfgjeingjk';
6741 return endecrypt($password, $data, 'de');
6745 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6747 * @todo Finish documenting this function
6749 * @param string $pwd The password to use when encrypting or decrypting
6750 * @param string $data The data to be decrypted/encrypted
6751 * @param string $case Either 'de' for decrypt or '' for encrypt
6752 * @return string
6754 function endecrypt ($pwd, $data, $case) {
6756 if ($case == 'de') {
6757 $data = urldecode($data);
6760 $key[] = '';
6761 $box[] = '';
6762 $temp_swap = '';
6763 $pwd_length = 0;
6765 $pwd_length = strlen($pwd);
6767 for ($i = 0; $i <= 255; $i++) {
6768 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
6769 $box[$i] = $i;
6772 $x = 0;
6774 for ($i = 0; $i <= 255; $i++) {
6775 $x = ($x + $box[$i] + $key[$i]) % 256;
6776 $temp_swap = $box[$i];
6777 $box[$i] = $box[$x];
6778 $box[$x] = $temp_swap;
6781 $temp = '';
6782 $k = '';
6784 $cipherby = '';
6785 $cipher = '';
6787 $a = 0;
6788 $j = 0;
6790 for ($i = 0; $i < strlen($data); $i++) {
6791 $a = ($a + 1) % 256;
6792 $j = ($j + $box[$a]) % 256;
6793 $temp = $box[$a];
6794 $box[$a] = $box[$j];
6795 $box[$j] = $temp;
6796 $k = $box[(($box[$a] + $box[$j]) % 256)];
6797 $cipherby = ord(substr($data, $i, 1)) ^ $k;
6798 $cipher .= chr($cipherby);
6801 if ($case == 'de') {
6802 $cipher = urldecode(urlencode($cipher));
6803 } else {
6804 $cipher = urlencode($cipher);
6807 return $cipher;
6810 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6813 * Returns the exact absolute path to plugin directory.
6815 * @param string $plugintype type of plugin
6816 * @param string $name name of the plugin
6817 * @return string full path to plugin directory; NULL if not found
6819 function get_plugin_directory($plugintype, $name) {
6820 if ($plugintype === '') {
6821 $plugintype = 'mod';
6824 $types = get_plugin_types(true);
6825 if (!array_key_exists($plugintype, $types)) {
6826 return NULL;
6828 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
6830 return $types[$plugintype].'/'.$name;
6834 * Return exact absolute path to a plugin directory,
6835 * this method support "simpletest_" prefix designed for unit testing.
6837 * @param string $component name such as 'moodle', 'mod_forum' or special simpletest value
6838 * @return string full path to component directory; NULL if not found
6840 function get_component_directory($component) {
6841 global $CFG;
6843 $simpletest = false;
6844 if (strpos($component, 'simpletest_') === 0) {
6845 $subdir = substr($component, strlen('simpletest_'));
6846 //TODO: this looks borked, where is it used actually?
6847 return $subdir;
6850 list($type, $plugin) = normalize_component($component);
6852 if ($type === 'core') {
6853 if ($plugin === NULL ) {
6854 $path = $CFG->libdir;
6855 } else {
6856 $subsystems = get_core_subsystems();
6857 if (isset($subsystems[$plugin])) {
6858 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
6859 } else {
6860 $path = NULL;
6864 } else {
6865 $path = get_plugin_directory($type, $plugin);
6868 return $path;
6872 * Normalize the component name using the "frankenstyle" names.
6873 * @param string $component
6874 * @return array $type+$plugin elements
6876 function normalize_component($component) {
6877 if ($component === 'moodle' or $component === 'core') {
6878 $type = 'core';
6879 $plugin = NULL;
6881 } else if (strpos($component, '_') === false) {
6882 $subsystems = get_core_subsystems();
6883 if (array_key_exists($component, $subsystems)) {
6884 $type = 'core';
6885 $plugin = $component;
6886 } else {
6887 // everything else is a module
6888 $type = 'mod';
6889 $plugin = $component;
6892 } else {
6893 list($type, $plugin) = explode('_', $component, 2);
6894 $plugintypes = get_plugin_types(false);
6895 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
6896 $type = 'mod';
6897 $plugin = $component;
6901 return array($type, $plugin);
6905 * List all core subsystems and their location
6907 * This is a whitelist of components that are part of the core and their
6908 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
6909 * plugin is not listed here and it does not have proper plugintype prefix,
6910 * then it is considered as course activity module.
6912 * The location is dirroot relative path. NULL means there is no special
6913 * directory for this subsystem. If the location is set, the subsystem's
6914 * renderer.php is expected to be there.
6916 * @return array of (string)name => (string|null)location
6918 function get_core_subsystems() {
6919 global $CFG;
6921 static $info = null;
6923 if (!$info) {
6924 $info = array(
6925 'access' => NULL,
6926 'admin' => $CFG->admin,
6927 'auth' => 'auth',
6928 'backup' => 'backup/util/ui',
6929 'block' => 'blocks',
6930 'blog' => 'blog',
6931 'bulkusers' => NULL,
6932 'calendar' => 'calendar',
6933 'cohort' => 'cohort',
6934 'condition' => NULL,
6935 'completion' => NULL,
6936 'countries' => NULL,
6937 'course' => 'course',
6938 'currencies' => NULL,
6939 'dbtransfer' => NULL,
6940 'debug' => NULL,
6941 'dock' => NULL,
6942 'editor' => 'lib/editor',
6943 'edufields' => NULL,
6944 'enrol' => 'enrol',
6945 'error' => NULL,
6946 'filepicker' => NULL,
6947 'files' => 'files',
6948 'filters' => NULL,
6949 'flashdetect' => NULL,
6950 'fonts' => NULL,
6951 'form' => 'lib/form',
6952 'grades' => 'grade',
6953 'group' => 'group',
6954 'help' => NULL,
6955 'hub' => NULL,
6956 'imscc' => NULL,
6957 'install' => NULL,
6958 'iso6392' => NULL,
6959 'langconfig' => NULL,
6960 'license' => NULL,
6961 'message' => 'message',
6962 'mimetypes' => NULL,
6963 'mnet' => 'mnet',
6964 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
6965 'my' => 'my',
6966 'notes' => 'notes',
6967 'pagetype' => NULL,
6968 'pix' => NULL,
6969 'plagiarism' => 'plagiarism',
6970 'portfolio' => 'portfolio',
6971 'publish' => 'course/publish',
6972 'question' => 'question',
6973 'rating' => 'rating',
6974 'register' => 'admin/registration',
6975 'repository' => 'repository',
6976 'rss' => 'rss',
6977 'role' => $CFG->admin.'/role',
6978 'simpletest' => NULL,
6979 'search' => 'search',
6980 'table' => NULL,
6981 'tag' => 'tag',
6982 'timezones' => NULL,
6983 'user' => 'user',
6984 'userkey' => NULL,
6985 'webservice' => 'webservice',
6986 'xmldb' => NULL,
6990 return $info;
6994 * Lists all plugin types
6995 * @param bool $fullpaths false means relative paths from dirroot
6996 * @return array Array of strings - name=>location
6998 function get_plugin_types($fullpaths=true) {
6999 global $CFG;
7001 static $info = null;
7002 static $fullinfo = null;
7004 if (!$info) {
7005 $info = array('mod' => 'mod',
7006 'auth' => 'auth',
7007 'enrol' => 'enrol',
7008 'message' => 'message/output',
7009 'block' => 'blocks',
7010 'filter' => 'filter',
7011 'editor' => 'lib/editor',
7012 'format' => 'course/format',
7013 'profilefield' => 'user/profile/field',
7014 'report' => $CFG->admin.'/report',
7015 'coursereport' => 'course/report', // must be after system reports
7016 'gradeexport' => 'grade/export',
7017 'gradeimport' => 'grade/import',
7018 'gradereport' => 'grade/report',
7019 'mnetservice' => 'mnet/service',
7020 'webservice' => 'webservice',
7021 'repository' => 'repository',
7022 'portfolio' => 'portfolio',
7023 'qtype' => 'question/type',
7024 'qformat' => 'question/format',
7025 'plagiarism' => 'plagiarism',
7026 'theme' => 'theme'); // this is a bit hacky, themes may be in dataroot too
7028 $mods = get_plugin_list('mod');
7029 foreach ($mods as $mod => $moddir) {
7030 if (file_exists("$moddir/db/subplugins.php")) {
7031 $subplugins = array();
7032 include("$moddir/db/subplugins.php");
7033 foreach ($subplugins as $subtype=>$dir) {
7034 $info[$subtype] = $dir;
7039 // local is always last!
7040 $info['local'] = 'local';
7042 $fullinfo = array();
7043 foreach ($info as $type => $dir) {
7044 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
7048 return ($fullpaths ? $fullinfo : $info);
7052 * Simplified version of get_list_of_plugins()
7053 * @param string $plugintype type of plugin
7054 * @return array name=>fulllocation pairs of plugins of given type
7056 function get_plugin_list($plugintype) {
7057 global $CFG;
7059 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
7060 if ($plugintype == 'auth') {
7061 // Historically we have had an auth plugin called 'db', so allow a special case.
7062 $key = array_search('db', $ignored);
7063 if ($key !== false) {
7064 unset($ignored[$key]);
7068 if ($plugintype === '') {
7069 $plugintype = 'mod';
7072 $fulldirs = array();
7074 if ($plugintype === 'mod') {
7075 // mod is an exception because we have to call this function from get_plugin_types()
7076 $fulldirs[] = $CFG->dirroot.'/mod';
7078 } else if ($plugintype === 'theme') {
7079 $fulldirs[] = $CFG->dirroot.'/theme';
7080 // themes are special because they may be stored also in separate directory
7081 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
7082 $fulldirs[] = $CFG->themedir;
7085 } else {
7086 $types = get_plugin_types(true);
7087 if (!array_key_exists($plugintype, $types)) {
7088 return array();
7090 $fulldir = $types[$plugintype];
7091 if (!file_exists($fulldir)) {
7092 return array();
7094 $fulldirs[] = $fulldir;
7097 $result = array();
7099 foreach ($fulldirs as $fulldir) {
7100 if (!is_dir($fulldir)) {
7101 continue;
7103 $items = new DirectoryIterator($fulldir);
7104 foreach ($items as $item) {
7105 if ($item->isDot() or !$item->isDir()) {
7106 continue;
7108 $pluginname = $item->getFilename();
7109 if (in_array($pluginname, $ignored)) {
7110 continue;
7112 if ($pluginname !== clean_param($pluginname, PARAM_SAFEDIR)) {
7113 // better ignore plugins with problematic names here
7114 continue;
7116 $result[$pluginname] = $fulldir.'/'.$pluginname;
7117 unset($item);
7119 unset($items);
7122 //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!
7123 ksort($result);
7124 return $result;
7128 * Gets a list of all plugin API functions for given plugin type, function
7129 * name, and filename.
7130 * @param string $plugintype Plugin type, e.g. 'mod' or 'report'
7131 * @param string $function Name of function after the frankenstyle prefix;
7132 * e.g. if the function is called report_courselist_hook then this value
7133 * would be 'hook'
7134 * @param string $file Name of file that includes function within plugin,
7135 * default 'lib.php'
7136 * @return Array of plugin frankenstyle (e.g. 'report_courselist', 'mod_forum')
7137 * to valid, existing plugin function name (e.g. 'report_courselist_hook',
7138 * 'forum_hook')
7140 function get_plugin_list_with_function($plugintype, $function, $file='lib.php') {
7141 global $CFG; // mandatory in case it is referenced by include()d PHP script
7143 $result = array();
7144 // Loop through list of plugins with given type
7145 $list = get_plugin_list($plugintype);
7146 foreach($list as $plugin => $dir) {
7147 $path = $dir . '/' . $file;
7148 // If file exists, require it and look for function
7149 if (file_exists($path)) {
7150 include_once($path);
7151 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
7152 if (function_exists($fullfunction)) {
7153 // Function exists with standard name. Store, indexed by
7154 // frankenstyle name of plugin
7155 $result[$plugintype . '_' . $plugin] = $fullfunction;
7156 } else if ($plugintype === 'mod') {
7157 // For modules, we also allow plugin without full frankenstyle
7158 // but just starting with the module name
7159 $shortfunction = $plugin . '_' . $function;
7160 if (function_exists($shortfunction)) {
7161 $result[$plugintype . '_' . $plugin] = $shortfunction;
7166 return $result;
7170 * Lists plugin-like directories within specified directory
7172 * This function was originally used for standard Moodle plugins, please use
7173 * new get_plugin_list() now.
7175 * This function is used for general directory listing and backwards compatility.
7177 * @param string $directory relative directory from root
7178 * @param string $exclude dir name to exclude from the list (defaults to none)
7179 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
7180 * @return array Sorted array of directory names found under the requested parameters
7182 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
7183 global $CFG;
7185 $plugins = array();
7187 if (empty($basedir)) {
7188 $basedir = $CFG->dirroot .'/'. $directory;
7190 } else {
7191 $basedir = $basedir .'/'. $directory;
7194 if (file_exists($basedir) && filetype($basedir) == 'dir') {
7195 $dirhandle = opendir($basedir);
7196 while (false !== ($dir = readdir($dirhandle))) {
7197 $firstchar = substr($dir, 0, 1);
7198 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
7199 continue;
7201 if (filetype($basedir .'/'. $dir) != 'dir') {
7202 continue;
7204 $plugins[] = $dir;
7206 closedir($dirhandle);
7208 if ($plugins) {
7209 asort($plugins);
7211 return $plugins;
7216 * invoke plugin's callback functions
7218 * @param string $type Plugin type e.g. 'mod'
7219 * @param string $name Plugin name
7220 * @param string $feature Feature code (FEATURE_xx constant)
7221 * @param string $action Feature's action
7222 * @param string $options parameters of callback function, should be an array
7223 * @param mixed $default default value if callback function hasn't been defined
7224 * @return mixed
7226 function plugin_callback($type, $name, $feature, $action, $options = null, $default=null) {
7227 global $CFG;
7229 $name = clean_param($name, PARAM_SAFEDIR);
7230 $function = $name.'_'.$feature.'_'.$action;
7231 $file = get_plugin_directory($type, $name) . '/lib.php';
7233 // Load library and look for function
7234 if (file_exists($file)) {
7235 require_once($file);
7237 if (function_exists($function)) {
7238 // Function exists, so just return function result
7239 $ret = call_user_func_array($function, (array)$options);
7240 if (is_null($ret)) {
7241 return $default;
7242 } else {
7243 return $ret;
7246 return $default;
7250 * Checks whether a plugin supports a specified feature.
7252 * @param string $type Plugin type e.g. 'mod'
7253 * @param string $name Plugin name e.g. 'forum'
7254 * @param string $feature Feature code (FEATURE_xx constant)
7255 * @param mixed $default default value if feature support unknown
7256 * @return mixed Feature result (false if not supported, null if feature is unknown,
7257 * otherwise usually true but may have other feature-specific value such as array)
7259 function plugin_supports($type, $name, $feature, $default = NULL) {
7260 global $CFG;
7262 $name = clean_param($name, PARAM_SAFEDIR); //bit of extra security
7264 $function = null;
7266 if ($type === 'mod') {
7267 // we need this special case because we support subplugins in modules,
7268 // otherwise it would end up in infinite loop
7269 if ($name === 'NEWMODULE') {
7270 //somebody forgot to rename the module template
7271 return false;
7273 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
7274 include_once("$CFG->dirroot/mod/$name/lib.php");
7275 $function = $type.'_'.$name.'_supports';
7276 if (!function_exists($function)) {
7277 // legacy non-frankenstyle function name
7278 $function = $name.'_supports';
7282 } else {
7283 if (!$path = get_plugin_directory($type, $name)) {
7284 // non existent plugin type
7285 return false;
7287 if (file_exists("$path/lib.php")) {
7288 include_once("$path/lib.php");
7289 $function = $type.'_'.$name.'_supports';
7293 if ($function and function_exists($function)) {
7294 $supports = $function($feature);
7295 if (is_null($supports)) {
7296 // plugin does not know - use default
7297 return $default;
7298 } else {
7299 return $supports;
7303 //plugin does not care, so use default
7304 return $default;
7308 * Returns true if the current version of PHP is greater that the specified one.
7310 * @todo Check PHP version being required here is it too low?
7312 * @param string $version The version of php being tested.
7313 * @return bool
7315 function check_php_version($version='5.2.4') {
7316 return (version_compare(phpversion(), $version) >= 0);
7320 * Checks to see if is the browser operating system matches the specified
7321 * brand.
7323 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
7325 * @uses $_SERVER
7326 * @param string $brand The operating system identifier being tested
7327 * @return bool true if the given brand below to the detected operating system
7329 function check_browser_operating_system($brand) {
7330 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7331 return false;
7334 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
7335 return true;
7338 return false;
7342 * Checks to see if is a browser matches the specified
7343 * brand and is equal or better version.
7345 * @uses $_SERVER
7346 * @param string $brand The browser identifier being tested
7347 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
7348 * @return bool true if the given version is below that of the detected browser
7350 function check_browser_version($brand, $version = null) {
7351 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7352 return false;
7355 $agent = $_SERVER['HTTP_USER_AGENT'];
7357 switch ($brand) {
7359 case 'Camino': /// OSX browser using Gecke engine
7360 if (strpos($agent, 'Camino') === false) {
7361 return false;
7363 if (empty($version)) {
7364 return true; // no version specified
7366 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
7367 if (version_compare($match[1], $version) >= 0) {
7368 return true;
7371 break;
7374 case 'Firefox': /// Mozilla Firefox browsers
7375 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
7376 return false;
7378 if (empty($version)) {
7379 return true; // no version specified
7381 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
7382 if (version_compare($match[2], $version) >= 0) {
7383 return true;
7386 break;
7389 case 'Gecko': /// Gecko based browsers
7390 if (empty($version) and substr_count($agent, 'Camino')) {
7391 // MacOS X Camino support
7392 $version = 20041110;
7395 // the proper string - Gecko/CCYYMMDD Vendor/Version
7396 // Faster version and work-a-round No IDN problem.
7397 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
7398 if ($match[1] > $version) {
7399 return true;
7402 break;
7405 case 'MSIE': /// Internet Explorer
7406 if (strpos($agent, 'Opera') !== false) { // Reject Opera
7407 return false;
7409 // in case of IE we have to deal with BC of the version parameter
7410 if (is_null($version)) {
7411 $version = 5.5; // anything older is not considered a browser at all!
7414 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
7415 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
7416 if (version_compare($match[1], $version) >= 0) {
7417 return true;
7420 break;
7423 case 'Opera': /// Opera
7424 if (strpos($agent, 'Opera') === false) {
7425 return false;
7427 if (empty($version)) {
7428 return true; // no version specified
7430 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
7431 if (version_compare($match[1], $version) >= 0) {
7432 return true;
7435 break;
7438 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
7439 if (strpos($agent, 'AppleWebKit') === false) {
7440 return false;
7442 if (empty($version)) {
7443 return true; // no version specified
7445 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7446 if (version_compare($match[1], $version) >= 0) {
7447 return true;
7450 break;
7453 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
7454 if (strpos($agent, 'AppleWebKit') === false) {
7455 return false;
7457 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SimbianOS and any other mobile devices
7458 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
7459 return false;
7461 if (strpos($agent, 'Shiira')) { // Reject Shiira
7462 return false;
7464 if (strpos($agent, 'SimbianOS')) { // Reject SimbianOS
7465 return false;
7467 if (strpos($agent, 'Android')) { // Reject Androids too
7468 return false;
7470 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
7471 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
7472 return false;
7474 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
7475 return false;
7478 if (empty($version)) {
7479 return true; // no version specified
7481 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7482 if (version_compare($match[1], $version) >= 0) {
7483 return true;
7486 break;
7489 case 'Chrome':
7490 if (strpos($agent, 'Chrome') === false) {
7491 return false;
7493 if (empty($version)) {
7494 return true; // no version specified
7496 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
7497 if (version_compare($match[1], $version) >= 0) {
7498 return true;
7501 break;
7504 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
7505 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
7506 return false;
7508 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
7509 return false;
7511 if (empty($version)) {
7512 return true; // no version specified
7514 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7515 if (version_compare($match[1], $version) >= 0) {
7516 return true;
7519 break;
7522 case 'WebKit Android': /// WebKit browser on Android
7523 if (strpos($agent, 'Linux; U; Android') === false) {
7524 return false;
7526 if (empty($version)) {
7527 return true; // no version specified
7529 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7530 if (version_compare($match[1], $version) >= 0) {
7531 return true;
7534 break;
7538 return false;
7542 * Returns one or several CSS class names that match the user's browser. These can be put
7543 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
7545 * @return array An array of browser version classes
7547 function get_browser_version_classes() {
7548 $classes = array();
7550 if (check_browser_version("MSIE", "0")) {
7551 $classes[] = 'ie';
7552 if (check_browser_version("MSIE", 9)) {
7553 $classes[] = 'ie9';
7554 } else if (check_browser_version("MSIE", 8)) {
7555 $classes[] = 'ie8';
7556 } elseif (check_browser_version("MSIE", 7)) {
7557 $classes[] = 'ie7';
7558 } elseif (check_browser_version("MSIE", 6)) {
7559 $classes[] = 'ie6';
7562 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
7563 $classes[] = 'gecko';
7564 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
7565 $classes[] = "gecko{$matches[1]}{$matches[2]}";
7568 } else if (check_browser_version("WebKit")) {
7569 $classes[] = 'safari';
7570 if (check_browser_version("Safari iOS")) {
7571 $classes[] = 'ios';
7573 } else if (check_browser_version("WebKit Android")) {
7574 $classes[] = 'android';
7577 } else if (check_browser_version("Opera")) {
7578 $classes[] = 'opera';
7582 return $classes;
7586 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
7588 * @return bool True for yes, false for no
7590 function can_use_rotated_text() {
7591 global $USER;
7592 return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader;;
7596 * Hack to find out the GD version by parsing phpinfo output
7598 * @return int GD version (1, 2, or 0)
7600 function check_gd_version() {
7601 $gdversion = 0;
7603 if (function_exists('gd_info')){
7604 $gd_info = gd_info();
7605 if (substr_count($gd_info['GD Version'], '2.')) {
7606 $gdversion = 2;
7607 } else if (substr_count($gd_info['GD Version'], '1.')) {
7608 $gdversion = 1;
7611 } else {
7612 ob_start();
7613 phpinfo(INFO_MODULES);
7614 $phpinfo = ob_get_contents();
7615 ob_end_clean();
7617 $phpinfo = explode("\n", $phpinfo);
7620 foreach ($phpinfo as $text) {
7621 $parts = explode('</td>', $text);
7622 foreach ($parts as $key => $val) {
7623 $parts[$key] = trim(strip_tags($val));
7625 if ($parts[0] == 'GD Version') {
7626 if (substr_count($parts[1], '2.0')) {
7627 $parts[1] = '2.0';
7629 $gdversion = intval($parts[1]);
7634 return $gdversion; // 1, 2 or 0
7638 * Determine if moodle installation requires update
7640 * Checks version numbers of main code and all modules to see
7641 * if there are any mismatches
7643 * @global object
7644 * @global object
7645 * @return bool
7647 function moodle_needs_upgrading() {
7648 global $CFG, $DB, $OUTPUT;
7650 if (empty($CFG->version)) {
7651 return true;
7654 // main versio nfirst
7655 $version = null;
7656 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
7657 if ($version > $CFG->version) {
7658 return true;
7661 // modules
7662 $mods = get_plugin_list('mod');
7663 $installed = $DB->get_records('modules', array(), '', 'name, version');
7664 foreach ($mods as $mod => $fullmod) {
7665 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
7666 continue;
7668 $module = new stdClass();
7669 if (!is_readable($fullmod.'/version.php')) {
7670 continue;
7672 include($fullmod.'/version.php'); // defines $module with version etc
7673 if (empty($installed[$mod])) {
7674 return true;
7675 } else if ($module->version > $installed[$mod]->version) {
7676 return true;
7679 unset($installed);
7681 // blocks
7682 $blocks = get_plugin_list('block');
7683 $installed = $DB->get_records('block', array(), '', 'name, version');
7684 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
7685 foreach ($blocks as $blockname=>$fullblock) {
7686 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
7687 continue;
7689 if (!is_readable($fullblock.'/version.php')) {
7690 continue;
7692 $plugin = new stdClass();
7693 $plugin->version = NULL;
7694 include($fullblock.'/version.php');
7695 if (empty($installed[$blockname])) {
7696 return true;
7697 } else if ($plugin->version > $installed[$blockname]->version) {
7698 return true;
7701 unset($installed);
7703 // now the rest of plugins
7704 $plugintypes = get_plugin_types();
7705 unset($plugintypes['mod']);
7706 unset($plugintypes['block']);
7707 foreach ($plugintypes as $type=>$unused) {
7708 $plugs = get_plugin_list($type);
7709 foreach ($plugs as $plug=>$fullplug) {
7710 $component = $type.'_'.$plug;
7711 if (!is_readable($fullplug.'/version.php')) {
7712 continue;
7714 $plugin = new stdClass();
7715 include($fullplug.'/version.php'); // defines $plugin with version etc
7716 $installedversion = get_config($component, 'version');
7717 if (empty($installedversion)) { // new installation
7718 return true;
7719 } else if ($installedversion < $plugin->version) { // upgrade
7720 return true;
7725 return false;
7729 * Sets maximum expected time needed for upgrade task.
7730 * Please always make sure that upgrade will not run longer!
7732 * The script may be automatically aborted if upgrade times out.
7734 * @global object
7735 * @param int $max_execution_time in seconds (can not be less than 60 s)
7737 function upgrade_set_timeout($max_execution_time=300) {
7738 global $CFG;
7740 if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
7741 $upgraderunning = get_config(null, 'upgraderunning');
7742 } else {
7743 $upgraderunning = $CFG->upgraderunning;
7746 if (!$upgraderunning) {
7747 // upgrade not running or aborted
7748 print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
7749 die;
7752 if ($max_execution_time < 60) {
7753 // protection against 0 here
7754 $max_execution_time = 60;
7757 $expected_end = time() + $max_execution_time;
7759 if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
7760 // no need to store new end, it is nearly the same ;-)
7761 return;
7764 set_time_limit($max_execution_time);
7765 set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
7768 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
7771 * Notify admin users or admin user of any failed logins (since last notification).
7773 * Note that this function must be only executed from the cron script
7774 * It uses the cache_flags system to store temporary records, deleting them
7775 * by name before finishing
7777 * @global object
7778 * @global object
7779 * @uses HOURSECS
7781 function notify_login_failures() {
7782 global $CFG, $DB, $OUTPUT;
7784 $recip = get_users_from_config($CFG->notifyloginfailures, 'moodle/site:config');
7786 if (empty($CFG->lastnotifyfailure)) {
7787 $CFG->lastnotifyfailure=0;
7790 // we need to deal with the threshold stuff first.
7791 if (empty($CFG->notifyloginthreshold)) {
7792 $CFG->notifyloginthreshold = 10; // default to something sensible.
7795 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
7796 /// and insert them into the cache_flags temp table
7797 $sql = "SELECT ip, COUNT(*)
7798 FROM {log}
7799 WHERE module = 'login' AND action = 'error'
7800 AND time > ?
7801 GROUP BY ip
7802 HAVING COUNT(*) >= ?";
7803 $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
7804 $rs = $DB->get_recordset_sql($sql, $params);
7805 foreach ($rs as $iprec) {
7806 if (!empty($iprec->ip)) {
7807 set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
7810 $rs->close();
7812 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
7813 /// and insert them into the cache_flags temp table
7814 $sql = "SELECT info, count(*)
7815 FROM {log}
7816 WHERE module = 'login' AND action = 'error'
7817 AND time > ?
7818 GROUP BY info
7819 HAVING count(*) >= ?";
7820 $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
7821 $rs = $DB->get_recordset_sql($sql, $params);
7822 foreach ($rs as $inforec) {
7823 if (!empty($inforec->info)) {
7824 set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
7827 $rs->close();
7829 /// Now, select all the login error logged records belonging to the ips and infos
7830 /// since lastnotifyfailure, that we have stored in the cache_flags table
7831 $sql = "SELECT l.*, u.firstname, u.lastname
7832 FROM {log} l
7833 JOIN {cache_flags} cf ON l.ip = cf.name
7834 LEFT JOIN {user} u ON l.userid = u.id
7835 WHERE l.module = 'login' AND l.action = 'error'
7836 AND l.time > ?
7837 AND cf.flagtype = 'login_failure_by_ip'
7838 UNION ALL
7839 SELECT l.*, u.firstname, u.lastname
7840 FROM {log} l
7841 JOIN {cache_flags} cf ON l.info = cf.name
7842 LEFT JOIN {user} u ON l.userid = u.id
7843 WHERE l.module = 'login' AND l.action = 'error'
7844 AND l.time > ?
7845 AND cf.flagtype = 'login_failure_by_info'
7846 ORDER BY time DESC";
7847 $params = array($CFG->lastnotifyfailure, $CFG->lastnotifyfailure);
7849 /// Init some variables
7850 $count = 0;
7851 $messages = '';
7852 /// Iterate over the logs recordset
7853 $rs = $DB->get_recordset_sql($sql, $params);
7854 foreach ($rs as $log) {
7855 $log->time = userdate($log->time);
7856 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
7857 $count++;
7859 $rs->close();
7861 /// If we haven't run in the last hour and
7862 /// we have something useful to report and we
7863 /// are actually supposed to be reporting to somebody
7864 if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
7865 $site = get_site();
7866 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
7867 /// Calculate the complete body of notification (start + messages + end)
7868 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
7869 (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
7870 $messages .
7871 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
7873 /// For each destination, send mail
7874 mtrace('Emailing admins about '. $count .' failed login attempts');
7875 foreach ($recip as $admin) {
7876 //emailing the admins directly rather than putting these through the messaging system
7877 email_to_user($admin,get_admin(), $subject, $body);
7880 /// Update lastnotifyfailure with current time
7881 set_config('lastnotifyfailure', time());
7884 /// Finally, delete all the temp records we have created in cache_flags
7885 $DB->delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
7889 * Sets the system locale
7891 * @todo Finish documenting this function
7893 * @global object
7894 * @param string $locale Can be used to force a locale
7896 function moodle_setlocale($locale='') {
7897 global $CFG;
7899 static $currentlocale = ''; // last locale caching
7901 $oldlocale = $currentlocale;
7903 /// Fetch the correct locale based on ostype
7904 if ($CFG->ostype == 'WINDOWS') {
7905 $stringtofetch = 'localewin';
7906 } else {
7907 $stringtofetch = 'locale';
7910 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
7911 if (!empty($locale)) {
7912 $currentlocale = $locale;
7913 } else if (!empty($CFG->locale)) { // override locale for all language packs
7914 $currentlocale = $CFG->locale;
7915 } else {
7916 $currentlocale = get_string($stringtofetch, 'langconfig');
7919 /// do nothing if locale already set up
7920 if ($oldlocale == $currentlocale) {
7921 return;
7924 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
7925 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
7926 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
7928 /// Get current values
7929 $monetary= setlocale (LC_MONETARY, 0);
7930 $numeric = setlocale (LC_NUMERIC, 0);
7931 $ctype = setlocale (LC_CTYPE, 0);
7932 if ($CFG->ostype != 'WINDOWS') {
7933 $messages= setlocale (LC_MESSAGES, 0);
7935 /// Set locale to all
7936 setlocale (LC_ALL, $currentlocale);
7937 /// Set old values
7938 setlocale (LC_MONETARY, $monetary);
7939 setlocale (LC_NUMERIC, $numeric);
7940 if ($CFG->ostype != 'WINDOWS') {
7941 setlocale (LC_MESSAGES, $messages);
7943 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
7944 setlocale (LC_CTYPE, $ctype);
7949 * Converts string to lowercase using most compatible function available.
7951 * @todo Remove this function when no longer in use
7952 * @deprecated Use textlib->strtolower($text) instead.
7954 * @param string $string The string to convert to all lowercase characters.
7955 * @param string $encoding The encoding on the string.
7956 * @return string
7958 function moodle_strtolower ($string, $encoding='') {
7960 //If not specified use utf8
7961 if (empty($encoding)) {
7962 $encoding = 'UTF-8';
7964 //Use text services
7965 $textlib = textlib_get_instance();
7967 return $textlib->strtolower($string, $encoding);
7971 * Count words in a string.
7973 * Words are defined as things between whitespace.
7975 * @param string $string The text to be searched for words.
7976 * @return int The count of words in the specified string
7978 function count_words($string) {
7979 $string = strip_tags($string);
7980 return count(preg_split("/\w\b/", $string)) - 1;
7983 /** Count letters in a string.
7985 * Letters are defined as chars not in tags and different from whitespace.
7987 * @param string $string The text to be searched for letters.
7988 * @return int The count of letters in the specified text.
7990 function count_letters($string) {
7991 /// Loading the textlib singleton instance. We are going to need it.
7992 $textlib = textlib_get_instance();
7994 $string = strip_tags($string); // Tags are out now
7995 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
7997 return $textlib->strlen($string);
8001 * Generate and return a random string of the specified length.
8003 * @param int $length The length of the string to be created.
8004 * @return string
8006 function random_string ($length=15) {
8007 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8008 $pool .= 'abcdefghijklmnopqrstuvwxyz';
8009 $pool .= '0123456789';
8010 $poollen = strlen($pool);
8011 mt_srand ((double) microtime() * 1000000);
8012 $string = '';
8013 for ($i = 0; $i < $length; $i++) {
8014 $string .= substr($pool, (mt_rand()%($poollen)), 1);
8016 return $string;
8020 * Generate a complex random string (useful for md5 salts)
8022 * This function is based on the above {@link random_string()} however it uses a
8023 * larger pool of characters and generates a string between 24 and 32 characters
8025 * @param int $length Optional if set generates a string to exactly this length
8026 * @return string
8028 function complex_random_string($length=null) {
8029 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
8030 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
8031 $poollen = strlen($pool);
8032 mt_srand ((double) microtime() * 1000000);
8033 if ($length===null) {
8034 $length = floor(rand(24,32));
8036 $string = '';
8037 for ($i = 0; $i < $length; $i++) {
8038 $string .= $pool[(mt_rand()%$poollen)];
8040 return $string;
8044 * Given some text (which may contain HTML) and an ideal length,
8045 * this function truncates the text neatly on a word boundary if possible
8047 * @global object
8048 * @param string $text - text to be shortened
8049 * @param int $ideal - ideal string length
8050 * @param boolean $exact if false, $text will not be cut mid-word
8051 * @param string $ending The string to append if the passed string is truncated
8052 * @return string $truncate - shortened string
8054 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
8056 global $CFG;
8058 // if the plain text is shorter than the maximum length, return the whole text
8059 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
8060 return $text;
8063 // Splits on HTML tags. Each open/close/empty tag will be the first thing
8064 // and only tag in its 'line'
8065 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
8067 $total_length = strlen($ending);
8068 $truncate = '';
8070 // This array stores information about open and close tags and their position
8071 // in the truncated string. Each item in the array is an object with fields
8072 // ->open (true if open), ->tag (tag name in lower case), and ->pos
8073 // (byte position in truncated text)
8074 $tagdetails = array();
8076 foreach ($lines as $line_matchings) {
8077 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
8078 if (!empty($line_matchings[1])) {
8079 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
8080 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
8081 // do nothing
8082 // if tag is a closing tag (f.e. </b>)
8083 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
8084 // record closing tag
8085 $tagdetails[] = (object)array('open'=>false,
8086 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8087 // if tag is an opening tag (f.e. <b>)
8088 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
8089 // record opening tag
8090 $tagdetails[] = (object)array('open'=>true,
8091 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8093 // add html-tag to $truncate'd text
8094 $truncate .= $line_matchings[1];
8097 // calculate the length of the plain text part of the line; handle entities as one character
8098 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
8099 if ($total_length+$content_length > $ideal) {
8100 // the number of characters which are left
8101 $left = $ideal - $total_length;
8102 $entities_length = 0;
8103 // search for html entities
8104 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)) {
8105 // calculate the real length of all entities in the legal range
8106 foreach ($entities[0] as $entity) {
8107 if ($entity[1]+1-$entities_length <= $left) {
8108 $left--;
8109 $entities_length += strlen($entity[0]);
8110 } else {
8111 // no more characters left
8112 break;
8116 $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
8117 // maximum length is reached, so get off the loop
8118 break;
8119 } else {
8120 $truncate .= $line_matchings[2];
8121 $total_length += $content_length;
8124 // if the maximum length is reached, get off the loop
8125 if($total_length >= $ideal) {
8126 break;
8130 // if the words shouldn't be cut in the middle...
8131 if (!$exact) {
8132 // ...search the last occurence of a space...
8133 for ($k=strlen($truncate);$k>0;$k--) {
8134 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
8135 if ($char == '.' or $char == ' ') {
8136 $breakpos = $k+1;
8137 break;
8138 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
8139 $breakpos = $k; // can be truncated at any UTF-8
8140 break; // character boundary.
8145 if (isset($breakpos)) {
8146 // ...and cut the text in this position
8147 $truncate = substr($truncate, 0, $breakpos);
8151 // add the defined ending to the text
8152 $truncate .= $ending;
8154 // Now calculate the list of open html tags based on the truncate position
8155 $open_tags = array();
8156 foreach ($tagdetails as $taginfo) {
8157 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
8158 // Don't include tags after we made the break!
8159 break;
8161 if($taginfo->open) {
8162 // add tag to the beginning of $open_tags list
8163 array_unshift($open_tags, $taginfo->tag);
8164 } else {
8165 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
8166 if ($pos !== false) {
8167 unset($open_tags[$pos]);
8172 // close all unclosed html-tags
8173 foreach ($open_tags as $tag) {
8174 $truncate .= '</' . $tag . '>';
8177 return $truncate;
8182 * Given dates in seconds, how many weeks is the date from startdate
8183 * The first week is 1, the second 2 etc ...
8185 * @todo Finish documenting this function
8187 * @uses WEEKSECS
8188 * @param int $startdate Timestamp for the start date
8189 * @param int $thedate Timestamp for the end date
8190 * @return string
8192 function getweek ($startdate, $thedate) {
8193 if ($thedate < $startdate) { // error
8194 return 0;
8197 return floor(($thedate - $startdate) / WEEKSECS) + 1;
8201 * returns a randomly generated password of length $maxlen. inspired by
8203 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
8204 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
8206 * @global object
8207 * @param int $maxlen The maximum size of the password being generated.
8208 * @return string
8210 function generate_password($maxlen=10) {
8211 global $CFG;
8213 if (empty($CFG->passwordpolicy)) {
8214 $fillers = PASSWORD_DIGITS;
8215 $wordlist = file($CFG->wordlist);
8216 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8217 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8218 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
8219 $password = $word1 . $filler1 . $word2;
8220 } else {
8221 $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
8222 $digits = $CFG->minpassworddigits;
8223 $lower = $CFG->minpasswordlower;
8224 $upper = $CFG->minpasswordupper;
8225 $nonalphanum = $CFG->minpasswordnonalphanum;
8226 $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
8228 // Make sure we have enough characters to fulfill
8229 // complexity requirements
8230 $passworddigits = PASSWORD_DIGITS;
8231 while ($digits > strlen($passworddigits)) {
8232 $passworddigits .= PASSWORD_DIGITS;
8234 $passwordlower = PASSWORD_LOWER;
8235 while ($lower > strlen($passwordlower)) {
8236 $passwordlower .= PASSWORD_LOWER;
8238 $passwordupper = PASSWORD_UPPER;
8239 while ($upper > strlen($passwordupper)) {
8240 $passwordupper .= PASSWORD_UPPER;
8242 $passwordnonalphanum = PASSWORD_NONALPHANUM;
8243 while ($nonalphanum > strlen($passwordnonalphanum)) {
8244 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
8247 // Now mix and shuffle it all
8248 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
8249 substr(str_shuffle ($passwordupper), 0, $upper) .
8250 substr(str_shuffle ($passworddigits), 0, $digits) .
8251 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
8252 substr(str_shuffle ($passwordlower .
8253 $passwordupper .
8254 $passworddigits .
8255 $passwordnonalphanum), 0 , $additional));
8258 return substr ($password, 0, $maxlen);
8262 * Given a float, prints it nicely.
8263 * Localized floats must not be used in calculations!
8265 * @param float $float The float to print
8266 * @param int $places The number of decimal places to print.
8267 * @param bool $localized use localized decimal separator
8268 * @return string locale float
8270 function format_float($float, $decimalpoints=1, $localized=true) {
8271 if (is_null($float)) {
8272 return '';
8274 if ($localized) {
8275 return number_format($float, $decimalpoints, get_string('decsep', 'langconfig'), '');
8276 } else {
8277 return number_format($float, $decimalpoints, '.', '');
8282 * Converts locale specific floating point/comma number back to standard PHP float value
8283 * Do NOT try to do any math operations before this conversion on any user submitted floats!
8285 * @param string $locale_float locale aware float representation
8286 * @return float
8288 function unformat_float($locale_float) {
8289 $locale_float = trim($locale_float);
8291 if ($locale_float == '') {
8292 return null;
8295 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
8297 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
8301 * Given a simple array, this shuffles it up just like shuffle()
8302 * Unlike PHP's shuffle() this function works on any machine.
8304 * @param array $array The array to be rearranged
8305 * @return array
8307 function swapshuffle($array) {
8309 srand ((double) microtime() * 10000000);
8310 $last = count($array) - 1;
8311 for ($i=0;$i<=$last;$i++) {
8312 $from = rand(0,$last);
8313 $curr = $array[$i];
8314 $array[$i] = $array[$from];
8315 $array[$from] = $curr;
8317 return $array;
8321 * Like {@link swapshuffle()}, but works on associative arrays
8323 * @param array $array The associative array to be rearranged
8324 * @return array
8326 function swapshuffle_assoc($array) {
8328 $newarray = array();
8329 $newkeys = swapshuffle(array_keys($array));
8331 foreach ($newkeys as $newkey) {
8332 $newarray[$newkey] = $array[$newkey];
8334 return $newarray;
8338 * Given an arbitrary array, and a number of draws,
8339 * this function returns an array with that amount
8340 * of items. The indexes are retained.
8342 * @todo Finish documenting this function
8344 * @param array $array
8345 * @param int $draws
8346 * @return array
8348 function draw_rand_array($array, $draws) {
8349 srand ((double) microtime() * 10000000);
8351 $return = array();
8353 $last = count($array);
8355 if ($draws > $last) {
8356 $draws = $last;
8359 while ($draws > 0) {
8360 $last--;
8362 $keys = array_keys($array);
8363 $rand = rand(0, $last);
8365 $return[$keys[$rand]] = $array[$keys[$rand]];
8366 unset($array[$keys[$rand]]);
8368 $draws--;
8371 return $return;
8375 * Calculate the difference between two microtimes
8377 * @param string $a The first Microtime
8378 * @param string $b The second Microtime
8379 * @return string
8381 function microtime_diff($a, $b) {
8382 list($a_dec, $a_sec) = explode(' ', $a);
8383 list($b_dec, $b_sec) = explode(' ', $b);
8384 return $b_sec - $a_sec + $b_dec - $a_dec;
8388 * Given a list (eg a,b,c,d,e) this function returns
8389 * an array of 1->a, 2->b, 3->c etc
8391 * @param string $list The string to explode into array bits
8392 * @param string $separator The separator used within the list string
8393 * @return array The now assembled array
8395 function make_menu_from_list($list, $separator=',') {
8397 $array = array_reverse(explode($separator, $list), true);
8398 foreach ($array as $key => $item) {
8399 $outarray[$key+1] = trim($item);
8401 return $outarray;
8405 * Creates an array that represents all the current grades that
8406 * can be chosen using the given grading type.
8408 * Negative numbers
8409 * are scales, zero is no grade, and positive numbers are maximum
8410 * grades.
8412 * @todo Finish documenting this function or better deprecated this completely!
8414 * @param int $gradingtype
8415 * @return array
8417 function make_grades_menu($gradingtype) {
8418 global $DB;
8420 $grades = array();
8421 if ($gradingtype < 0) {
8422 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
8423 return make_menu_from_list($scale->scale);
8425 } else if ($gradingtype > 0) {
8426 for ($i=$gradingtype; $i>=0; $i--) {
8427 $grades[$i] = $i .' / '. $gradingtype;
8429 return $grades;
8431 return $grades;
8435 * This function returns the number of activities
8436 * using scaleid in a courseid
8438 * @todo Finish documenting this function
8440 * @global object
8441 * @global object
8442 * @param int $courseid ?
8443 * @param int $scaleid ?
8444 * @return int
8446 function course_scale_used($courseid, $scaleid) {
8447 global $CFG, $DB;
8449 $return = 0;
8451 if (!empty($scaleid)) {
8452 if ($cms = get_course_mods($courseid)) {
8453 foreach ($cms as $cm) {
8454 //Check cm->name/lib.php exists
8455 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
8456 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
8457 $function_name = $cm->modname.'_scale_used';
8458 if (function_exists($function_name)) {
8459 if ($function_name($cm->instance,$scaleid)) {
8460 $return++;
8467 // check if any course grade item makes use of the scale
8468 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
8470 // check if any outcome in the course makes use of the scale
8471 $return += $DB->count_records_sql("SELECT COUNT('x')
8472 FROM {grade_outcomes_courses} goc,
8473 {grade_outcomes} go
8474 WHERE go.id = goc.outcomeid
8475 AND go.scaleid = ? AND goc.courseid = ?",
8476 array($scaleid, $courseid));
8478 return $return;
8482 * This function returns the number of activities
8483 * using scaleid in the entire site
8485 * @param int $scaleid
8486 * @param array $courses
8487 * @return int
8489 function site_scale_used($scaleid, &$courses) {
8490 $return = 0;
8492 if (!is_array($courses) || count($courses) == 0) {
8493 $courses = get_courses("all",false,"c.id,c.shortname");
8496 if (!empty($scaleid)) {
8497 if (is_array($courses) && count($courses) > 0) {
8498 foreach ($courses as $course) {
8499 $return += course_scale_used($course->id,$scaleid);
8503 return $return;
8507 * make_unique_id_code
8509 * @todo Finish documenting this function
8511 * @uses $_SERVER
8512 * @param string $extra Extra string to append to the end of the code
8513 * @return string
8515 function make_unique_id_code($extra='') {
8517 $hostname = 'unknownhost';
8518 if (!empty($_SERVER['HTTP_HOST'])) {
8519 $hostname = $_SERVER['HTTP_HOST'];
8520 } else if (!empty($_ENV['HTTP_HOST'])) {
8521 $hostname = $_ENV['HTTP_HOST'];
8522 } else if (!empty($_SERVER['SERVER_NAME'])) {
8523 $hostname = $_SERVER['SERVER_NAME'];
8524 } else if (!empty($_ENV['SERVER_NAME'])) {
8525 $hostname = $_ENV['SERVER_NAME'];
8528 $date = gmdate("ymdHis");
8530 $random = random_string(6);
8532 if ($extra) {
8533 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
8534 } else {
8535 return $hostname .'+'. $date .'+'. $random;
8541 * Function to check the passed address is within the passed subnet
8543 * The parameter is a comma separated string of subnet definitions.
8544 * Subnet strings can be in one of three formats:
8545 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
8546 * 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)
8547 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
8548 * Code for type 1 modified from user posted comments by mediator at
8549 * {@link http://au.php.net/manual/en/function.ip2long.php}
8551 * @param string $addr The address you are checking
8552 * @param string $subnetstr The string of subnet addresses
8553 * @return bool
8555 function address_in_subnet($addr, $subnetstr) {
8557 if ($addr == '0.0.0.0') {
8558 return false;
8560 $subnets = explode(',', $subnetstr);
8561 $found = false;
8562 $addr = trim($addr);
8563 $addr = cleanremoteaddr($addr, false); // normalise
8564 if ($addr === null) {
8565 return false;
8567 $addrparts = explode(':', $addr);
8569 $ipv6 = strpos($addr, ':');
8571 foreach ($subnets as $subnet) {
8572 $subnet = trim($subnet);
8573 if ($subnet === '') {
8574 continue;
8577 if (strpos($subnet, '/') !== false) {
8578 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
8579 list($ip, $mask) = explode('/', $subnet);
8580 $mask = trim($mask);
8581 if (!is_number($mask)) {
8582 continue; // incorect mask number, eh?
8584 $ip = cleanremoteaddr($ip, false); // normalise
8585 if ($ip === null) {
8586 continue;
8588 if (strpos($ip, ':') !== false) {
8589 // IPv6
8590 if (!$ipv6) {
8591 continue;
8593 if ($mask > 128 or $mask < 0) {
8594 continue; // nonsense
8596 if ($mask == 0) {
8597 return true; // any address
8599 if ($mask == 128) {
8600 if ($ip === $addr) {
8601 return true;
8603 continue;
8605 $ipparts = explode(':', $ip);
8606 $modulo = $mask % 16;
8607 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
8608 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
8609 if (implode(':', $ipnet) === implode(':', $addrnet)) {
8610 if ($modulo == 0) {
8611 return true;
8613 $pos = ($mask-$modulo)/16;
8614 $ipnet = hexdec($ipparts[$pos]);
8615 $addrnet = hexdec($addrparts[$pos]);
8616 $mask = 0xffff << (16 - $modulo);
8617 if (($addrnet & $mask) == ($ipnet & $mask)) {
8618 return true;
8622 } else {
8623 // IPv4
8624 if ($ipv6) {
8625 continue;
8627 if ($mask > 32 or $mask < 0) {
8628 continue; // nonsense
8630 if ($mask == 0) {
8631 return true;
8633 if ($mask == 32) {
8634 if ($ip === $addr) {
8635 return true;
8637 continue;
8639 $mask = 0xffffffff << (32 - $mask);
8640 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
8641 return true;
8645 } else if (strpos($subnet, '-') !== false) {
8646 /// 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.
8647 $parts = explode('-', $subnet);
8648 if (count($parts) != 2) {
8649 continue;
8652 if (strpos($subnet, ':') !== false) {
8653 // IPv6
8654 if (!$ipv6) {
8655 continue;
8657 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
8658 if ($ipstart === null) {
8659 continue;
8661 $ipparts = explode(':', $ipstart);
8662 $start = hexdec(array_pop($ipparts));
8663 $ipparts[] = trim($parts[1]);
8664 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
8665 if ($ipend === null) {
8666 continue;
8668 $ipparts[7] = '';
8669 $ipnet = implode(':', $ipparts);
8670 if (strpos($addr, $ipnet) !== 0) {
8671 continue;
8673 $ipparts = explode(':', $ipend);
8674 $end = hexdec($ipparts[7]);
8676 $addrend = hexdec($addrparts[7]);
8678 if (($addrend >= $start) and ($addrend <= $end)) {
8679 return true;
8682 } else {
8683 // IPv4
8684 if ($ipv6) {
8685 continue;
8687 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
8688 if ($ipstart === null) {
8689 continue;
8691 $ipparts = explode('.', $ipstart);
8692 $ipparts[3] = trim($parts[1]);
8693 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
8694 if ($ipend === null) {
8695 continue;
8698 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
8699 return true;
8703 } else {
8704 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
8705 if (strpos($subnet, ':') !== false) {
8706 // IPv6
8707 if (!$ipv6) {
8708 continue;
8710 $parts = explode(':', $subnet);
8711 $count = count($parts);
8712 if ($parts[$count-1] === '') {
8713 unset($parts[$count-1]); // trim trailing :
8714 $count--;
8715 $subnet = implode('.', $parts);
8717 $isip = cleanremoteaddr($subnet, false); // normalise
8718 if ($isip !== null) {
8719 if ($isip === $addr) {
8720 return true;
8722 continue;
8723 } else if ($count > 8) {
8724 continue;
8726 $zeros = array_fill(0, 8-$count, '0');
8727 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
8728 if (address_in_subnet($addr, $subnet)) {
8729 return true;
8732 } else {
8733 // IPv4
8734 if ($ipv6) {
8735 continue;
8737 $parts = explode('.', $subnet);
8738 $count = count($parts);
8739 if ($parts[$count-1] === '') {
8740 unset($parts[$count-1]); // trim trailing .
8741 $count--;
8742 $subnet = implode('.', $parts);
8744 if ($count == 4) {
8745 $subnet = cleanremoteaddr($subnet, false); // normalise
8746 if ($subnet === $addr) {
8747 return true;
8749 continue;
8750 } else if ($count > 4) {
8751 continue;
8753 $zeros = array_fill(0, 4-$count, '0');
8754 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
8755 if (address_in_subnet($addr, $subnet)) {
8756 return true;
8762 return false;
8766 * For outputting debugging info
8768 * @uses STDOUT
8769 * @param string $string The string to write
8770 * @param string $eol The end of line char(s) to use
8771 * @param string $sleep Period to make the application sleep
8772 * This ensures any messages have time to display before redirect
8774 function mtrace($string, $eol="\n", $sleep=0) {
8776 if (defined('STDOUT')) {
8777 fwrite(STDOUT, $string.$eol);
8778 } else {
8779 echo $string . $eol;
8782 flush();
8784 //delay to keep message on user's screen in case of subsequent redirect
8785 if ($sleep) {
8786 sleep($sleep);
8791 * Replace 1 or more slashes or backslashes to 1 slash
8793 * @param string $path The path to strip
8794 * @return string the path with double slashes removed
8796 function cleardoubleslashes ($path) {
8797 return preg_replace('/(\/|\\\){1,}/','/',$path);
8801 * Is current ip in give list?
8803 * @param string $list
8804 * @return bool
8806 function remoteip_in_list($list){
8807 $inlist = false;
8808 $client_ip = getremoteaddr(null);
8810 if(!$client_ip){
8811 // ensure access on cli
8812 return true;
8815 $list = explode("\n", $list);
8816 foreach($list as $subnet) {
8817 $subnet = trim($subnet);
8818 if (address_in_subnet($client_ip, $subnet)) {
8819 $inlist = true;
8820 break;
8823 return $inlist;
8827 * Returns most reliable client address
8829 * @global object
8830 * @param string $default If an address can't be determined, then return this
8831 * @return string The remote IP address
8833 function getremoteaddr($default='0.0.0.0') {
8834 global $CFG;
8836 if (empty($CFG->getremoteaddrconf)) {
8837 // This will happen, for example, before just after the upgrade, as the
8838 // user is redirected to the admin screen.
8839 $variablestoskip = 0;
8840 } else {
8841 $variablestoskip = $CFG->getremoteaddrconf;
8843 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
8844 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
8845 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
8846 return $address ? $address : $default;
8849 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
8850 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
8851 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
8852 return $address ? $address : $default;
8855 if (!empty($_SERVER['REMOTE_ADDR'])) {
8856 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
8857 return $address ? $address : $default;
8858 } else {
8859 return $default;
8864 * Cleans an ip address. Internal addresses are now allowed.
8865 * (Originally local addresses were not allowed.)
8867 * @param string $addr IPv4 or IPv6 address
8868 * @param bool $compress use IPv6 address compression
8869 * @return string normalised ip address string, null if error
8871 function cleanremoteaddr($addr, $compress=false) {
8872 $addr = trim($addr);
8874 //TODO: maybe add a separate function is_addr_public() or something like this
8876 if (strpos($addr, ':') !== false) {
8877 // can be only IPv6
8878 $parts = explode(':', $addr);
8879 $count = count($parts);
8881 if (strpos($parts[$count-1], '.') !== false) {
8882 //legacy ipv4 notation
8883 $last = array_pop($parts);
8884 $ipv4 = cleanremoteaddr($last, true);
8885 if ($ipv4 === null) {
8886 return null;
8888 $bits = explode('.', $ipv4);
8889 $parts[] = dechex($bits[0]).dechex($bits[1]);
8890 $parts[] = dechex($bits[2]).dechex($bits[3]);
8891 $count = count($parts);
8892 $addr = implode(':', $parts);
8895 if ($count < 3 or $count > 8) {
8896 return null; // severly malformed
8899 if ($count != 8) {
8900 if (strpos($addr, '::') === false) {
8901 return null; // malformed
8903 // uncompress ::
8904 $insertat = array_search('', $parts, true);
8905 $missing = array_fill(0, 1 + 8 - $count, '0');
8906 array_splice($parts, $insertat, 1, $missing);
8907 foreach ($parts as $key=>$part) {
8908 if ($part === '') {
8909 $parts[$key] = '0';
8914 $adr = implode(':', $parts);
8915 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
8916 return null; // incorrect format - sorry
8919 // normalise 0s and case
8920 $parts = array_map('hexdec', $parts);
8921 $parts = array_map('dechex', $parts);
8923 $result = implode(':', $parts);
8925 if (!$compress) {
8926 return $result;
8929 if ($result === '0:0:0:0:0:0:0:0') {
8930 return '::'; // all addresses
8933 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
8934 if ($compressed !== $result) {
8935 return $compressed;
8938 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
8939 if ($compressed !== $result) {
8940 return $compressed;
8943 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
8944 if ($compressed !== $result) {
8945 return $compressed;
8948 return $result;
8951 // first get all things that look like IPv4 addresses
8952 $parts = array();
8953 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
8954 return null;
8956 unset($parts[0]);
8958 foreach ($parts as $key=>$match) {
8959 if ($match > 255) {
8960 return null;
8962 $parts[$key] = (int)$match; // normalise 0s
8965 return implode('.', $parts);
8969 * This function will make a complete copy of anything it's given,
8970 * regardless of whether it's an object or not.
8972 * @param mixed $thing Something you want cloned
8973 * @return mixed What ever it is you passed it
8975 function fullclone($thing) {
8976 return unserialize(serialize($thing));
8981 * This function expects to called during shutdown
8982 * should be set via register_shutdown_function()
8983 * in lib/setup.php .
8985 * @return void
8987 function moodle_request_shutdown() {
8988 global $CFG;
8990 // help apache server if possible
8991 $apachereleasemem = false;
8992 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
8993 && ini_get_bool('child_terminate')) {
8995 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
8996 if (memory_get_usage() > get_real_size($limit)) {
8997 $apachereleasemem = $limit;
8998 @apache_child_terminate();
9002 // deal with perf logging
9003 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
9004 if ($apachereleasemem) {
9005 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
9007 if (defined('MDL_PERFTOLOG')) {
9008 $perf = get_performance_info();
9009 error_log("PERF: " . $perf['txt']);
9011 if (defined('MDL_PERFINC')) {
9012 $inc = get_included_files();
9013 $ts = 0;
9014 foreach($inc as $f) {
9015 if (preg_match(':^/:', $f)) {
9016 $fs = filesize($f);
9017 $ts += $fs;
9018 $hfs = display_size($fs);
9019 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
9020 , NULL, NULL, 0);
9021 } else {
9022 error_log($f , NULL, NULL, 0);
9025 if ($ts > 0 ) {
9026 $hts = display_size($ts);
9027 error_log("Total size of files included: $ts ($hts)");
9034 * If new messages are waiting for the current user, then insert
9035 * JavaScript to pop up the messaging window into the page
9037 * @global moodle_page $PAGE
9038 * @return void
9040 function message_popup_window() {
9041 global $USER, $DB, $PAGE, $CFG, $SITE;
9043 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
9044 return;
9047 if (!isloggedin() || isguestuser()) {
9048 return;
9051 if (!isset($USER->message_lastpopup)) {
9052 $USER->message_lastpopup = 0;
9053 } else if ($USER->message_lastpopup > (time()-120)) {
9054 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
9055 return;
9058 //a quick query to check whether the user has new messages
9059 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
9060 if ($messagecount<1) {
9061 return;
9064 //got unread messages so now do another query that joins with the user table
9065 $messagesql = "SELECT m.id, m.smallmessage, m.notification, u.firstname, u.lastname FROM {message} m
9066 JOIN {message_working} mw ON m.id=mw.unreadmessageid
9067 JOIN {message_processors} p ON mw.processorid=p.id
9068 JOIN {user} u ON m.useridfrom=u.id
9069 WHERE m.useridto = :userid AND p.name='popup'";
9071 //if the user was last notified over an hour ago we can renotify them of old messages
9072 //so don't worry about when the new message was sent
9073 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
9074 if (!$lastnotifiedlongago) {
9075 $messagesql .= 'AND m.timecreated > :lastpopuptime';
9078 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
9080 //if we have new messages to notify the user about
9081 if (!empty($message_users)) {
9083 $strmessages = '';
9084 if (count($message_users)>1) {
9085 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
9086 } else {
9087 $message_users = reset($message_users);
9089 //show who the message is from if its not a notification
9090 if (!$message_users->notification) {
9091 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
9094 //try to display the small version of the message
9095 $smallmessage = null;
9096 if (!empty($message_users->smallmessage)) {
9097 //display the first 200 chars of the message in the popup
9098 $smallmessage = null;
9099 if (strlen($message_users->smallmessage>200)) {
9100 $smallmessage = substr($message_users->smallmessage,0,200).'...';
9101 } else {
9102 $smallmessage = $message_users->smallmessage;
9104 } else if ($message_users->notification) {
9105 //its a notification with no smallmessage so just say they have a notification
9106 $smallmessage = get_string('unreadnewnotification', 'message');
9108 if (!empty($smallmessage)) {
9109 $strmessages .= '<div id="usermessage">'.$smallmessage.'</div>';
9113 $strgomessage = get_string('gotomessages', 'message');
9114 $strstaymessage = get_string('ignore','admin');
9116 $url = $CFG->wwwroot.'/message/index.php';
9117 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
9118 html_writer::start_tag('div', array('id'=>'newmessagetext')).
9119 $strmessages.
9120 html_writer::end_tag('div').
9122 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
9123 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
9124 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
9125 html_writer::end_tag('div');
9126 html_writer::end_tag('div');
9128 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
9130 $USER->message_lastpopup = time();
9135 * Used to make sure that $min <= $value <= $max
9137 * Make sure that value is between min, and max
9139 * @param int $min The minimum value
9140 * @param int $value The value to check
9141 * @param int $max The maximum value
9143 function bounded_number($min, $value, $max) {
9144 if($value < $min) {
9145 return $min;
9147 if($value > $max) {
9148 return $max;
9150 return $value;
9154 * Check if there is a nested array within the passed array
9156 * @param array $array
9157 * @return bool true if there is a nested array false otherwise
9159 function array_is_nested($array) {
9160 foreach ($array as $value) {
9161 if (is_array($value)) {
9162 return true;
9165 return false;
9169 * get_performance_info() pairs up with init_performance_info()
9170 * loaded in setup.php. Returns an array with 'html' and 'txt'
9171 * values ready for use, and each of the individual stats provided
9172 * separately as well.
9174 * @global object
9175 * @global object
9176 * @global object
9177 * @return array
9179 function get_performance_info() {
9180 global $CFG, $PERF, $DB, $PAGE;
9182 $info = array();
9183 $info['html'] = ''; // holds userfriendly HTML representation
9184 $info['txt'] = me() . ' '; // holds log-friendly representation
9186 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
9188 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
9189 $info['txt'] .= 'time: '.$info['realtime'].'s ';
9191 if (function_exists('memory_get_usage')) {
9192 $info['memory_total'] = memory_get_usage();
9193 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
9194 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
9195 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
9198 if (function_exists('memory_get_peak_usage')) {
9199 $info['memory_peak'] = memory_get_peak_usage();
9200 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
9201 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
9204 $inc = get_included_files();
9205 //error_log(print_r($inc,1));
9206 $info['includecount'] = count($inc);
9207 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
9208 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
9210 $filtermanager = filter_manager::instance();
9211 if (method_exists($filtermanager, 'get_performance_summary')) {
9212 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
9213 $info = array_merge($filterinfo, $info);
9214 foreach ($filterinfo as $key => $value) {
9215 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9216 $info['txt'] .= "$key: $value ";
9220 $stringmanager = get_string_manager();
9221 if (method_exists($stringmanager, 'get_performance_summary')) {
9222 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
9223 $info = array_merge($filterinfo, $info);
9224 foreach ($filterinfo as $key => $value) {
9225 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9226 $info['txt'] .= "$key: $value ";
9230 $jsmodules = $PAGE->requires->get_loaded_modules();
9231 if ($jsmodules) {
9232 $yuicount = 0;
9233 $othercount = 0;
9234 $details = '';
9235 foreach ($jsmodules as $module => $backtraces) {
9236 if (strpos($module, 'yui') === 0) {
9237 $yuicount += 1;
9238 } else {
9239 $othercount += 1;
9241 $details .= "<div class='yui-module'><p>$module</p>";
9242 foreach ($backtraces as $backtrace) {
9243 $details .= "<div class='backtrace'>$backtrace</div>";
9245 $details .= '</div>';
9247 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
9248 $info['txt'] .= "includedyuimodules: $yuicount ";
9249 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
9250 $info['txt'] .= "includedjsmodules: $othercount ";
9251 // Slightly odd to output the details in a display: none div. The point
9252 // Is that it takes a lot of space, and if you care you can reveal it
9253 // using firebug.
9254 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
9257 if (!empty($PERF->logwrites)) {
9258 $info['logwrites'] = $PERF->logwrites;
9259 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
9260 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
9263 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
9264 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
9265 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
9267 if (!empty($PERF->profiling) && $PERF->profiling) {
9268 require_once($CFG->dirroot .'/lib/profilerlib.php');
9269 $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
9272 if (function_exists('posix_times')) {
9273 $ptimes = posix_times();
9274 if (is_array($ptimes)) {
9275 foreach ($ptimes as $key => $val) {
9276 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
9278 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
9279 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
9283 // Grab the load average for the last minute
9284 // /proc will only work under some linux configurations
9285 // while uptime is there under MacOSX/Darwin and other unices
9286 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
9287 list($server_load) = explode(' ', $loadavg[0]);
9288 unset($loadavg);
9289 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
9290 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
9291 $server_load = $matches[1];
9292 } else {
9293 trigger_error('Could not parse uptime output!');
9296 if (!empty($server_load)) {
9297 $info['serverload'] = $server_load;
9298 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
9299 $info['txt'] .= "serverload: {$info['serverload']} ";
9302 /* if (isset($rcache->hits) && isset($rcache->misses)) {
9303 $info['rcachehits'] = $rcache->hits;
9304 $info['rcachemisses'] = $rcache->misses;
9305 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
9306 "{$rcache->hits}/{$rcache->misses}</span> ";
9307 $info['txt'] .= 'rcache: '.
9308 "{$rcache->hits}/{$rcache->misses} ";
9310 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
9311 return $info;
9315 * @todo Document this function linux people
9317 function apd_get_profiling() {
9318 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
9322 * Delete directory or only it's content
9324 * @param string $dir directory path
9325 * @param bool $content_only
9326 * @return bool success, true also if dir does not exist
9328 function remove_dir($dir, $content_only=false) {
9329 if (!file_exists($dir)) {
9330 // nothing to do
9331 return true;
9333 $handle = opendir($dir);
9334 $result = true;
9335 while (false!==($item = readdir($handle))) {
9336 if($item != '.' && $item != '..') {
9337 if(is_dir($dir.'/'.$item)) {
9338 $result = remove_dir($dir.'/'.$item) && $result;
9339 }else{
9340 $result = unlink($dir.'/'.$item) && $result;
9344 closedir($handle);
9345 if ($content_only) {
9346 return $result;
9348 return rmdir($dir); // if anything left the result will be false, no need for && $result
9352 * Detect if an object or a class contains a given property
9353 * will take an actual object or the name of a class
9355 * @param mix $obj Name of class or real object to test
9356 * @param string $property name of property to find
9357 * @return bool true if property exists
9359 function object_property_exists( $obj, $property ) {
9360 if (is_string( $obj )) {
9361 $properties = get_class_vars( $obj );
9363 else {
9364 $properties = get_object_vars( $obj );
9366 return array_key_exists( $property, $properties );
9371 * Detect a custom script replacement in the data directory that will
9372 * replace an existing moodle script
9374 * @param string $urlpath path to the original script
9375 * @return string|bool full path name if a custom script exists, false if no custom script exists
9377 function custom_script_path($urlpath='') {
9378 global $CFG;
9380 // set default $urlpath, if necessary
9381 if (empty($urlpath)) {
9382 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
9385 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
9386 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
9387 return false;
9390 // replace wwwroot with the path to the customscripts folder and clean path
9391 $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
9393 // remove the query string, if any
9394 if (($strpos = strpos($scriptpath, '?')) !== false) {
9395 $scriptpath = substr($scriptpath, 0, $strpos);
9398 // remove trailing slashes, if any
9399 $scriptpath = rtrim($scriptpath, '/\\');
9401 // append index.php, if necessary
9402 if (is_dir($scriptpath)) {
9403 $scriptpath .= '/index.php';
9406 // check the custom script exists
9407 if (file_exists($scriptpath)) {
9408 return $scriptpath;
9409 } else {
9410 return false;
9415 * Returns whether or not the user object is a remote MNET user. This function
9416 * is in moodlelib because it does not rely on loading any of the MNET code.
9418 * @global object
9419 * @param object $user A valid user object
9420 * @return bool True if the user is from a remote Moodle.
9422 function is_mnet_remote_user($user) {
9423 global $CFG;
9425 if (!isset($CFG->mnet_localhost_id)) {
9426 include_once $CFG->dirroot . '/mnet/lib.php';
9427 $env = new mnet_environment();
9428 $env->init();
9429 unset($env);
9432 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
9436 * This function will search for browser prefereed languages, setting Moodle
9437 * to use the best one available if $SESSION->lang is undefined
9439 * @global object
9440 * @global object
9441 * @global object
9443 function setup_lang_from_browser() {
9445 global $CFG, $SESSION, $USER;
9447 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
9448 // Lang is defined in session or user profile, nothing to do
9449 return;
9452 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
9453 return;
9456 /// Extract and clean langs from headers
9457 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
9458 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
9459 $rawlangs = explode(',', $rawlangs); // Convert to array
9460 $langs = array();
9462 $order = 1.0;
9463 foreach ($rawlangs as $lang) {
9464 if (strpos($lang, ';') === false) {
9465 $langs[(string)$order] = $lang;
9466 $order = $order-0.01;
9467 } else {
9468 $parts = explode(';', $lang);
9469 $pos = strpos($parts[1], '=');
9470 $langs[substr($parts[1], $pos+1)] = $parts[0];
9473 krsort($langs, SORT_NUMERIC);
9475 /// Look for such langs under standard locations
9476 foreach ($langs as $lang) {
9477 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
9478 if (get_string_manager()->translation_exists($lang, false)) {
9479 $SESSION->lang = $lang; /// Lang exists, set it in session
9480 break; /// We have finished. Go out
9483 return;
9487 * check if $url matches anything in proxybypass list
9489 * any errors just result in the proxy being used (least bad)
9491 * @global object
9492 * @param string $url url to check
9493 * @return boolean true if we should bypass the proxy
9495 function is_proxybypass( $url ) {
9496 global $CFG;
9498 // sanity check
9499 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
9500 return false;
9503 // get the host part out of the url
9504 if (!$host = parse_url( $url, PHP_URL_HOST )) {
9505 return false;
9508 // get the possible bypass hosts into an array
9509 $matches = explode( ',', $CFG->proxybypass );
9511 // check for a match
9512 // (IPs need to match the left hand side and hosts the right of the url,
9513 // but we can recklessly check both as there can't be a false +ve)
9514 $bypass = false;
9515 foreach ($matches as $match) {
9516 $match = trim($match);
9518 // try for IP match (Left side)
9519 $lhs = substr($host,0,strlen($match));
9520 if (strcasecmp($match,$lhs)==0) {
9521 return true;
9524 // try for host match (Right side)
9525 $rhs = substr($host,-strlen($match));
9526 if (strcasecmp($match,$rhs)==0) {
9527 return true;
9531 // nothing matched.
9532 return false;
9536 ////////////////////////////////////////////////////////////////////////////////
9539 * Check if the passed navigation is of the new style
9541 * @param mixed $navigation
9542 * @return bool true for yes false for no
9544 function is_newnav($navigation) {
9545 if (is_array($navigation) && !empty($navigation['newnav'])) {
9546 return true;
9547 } else {
9548 return false;
9553 * Checks whether the given variable name is defined as a variable within the given object.
9555 * This will NOT work with stdClass objects, which have no class variables.
9557 * @param string $var The variable name
9558 * @param object $object The object to check
9559 * @return boolean
9561 function in_object_vars($var, $object) {
9562 $class_vars = get_class_vars(get_class($object));
9563 $class_vars = array_keys($class_vars);
9564 return in_array($var, $class_vars);
9568 * Returns an array without repeated objects.
9569 * This function is similar to array_unique, but for arrays that have objects as values
9571 * @param array $array
9572 * @param bool $keep_key_assoc
9573 * @return array
9575 function object_array_unique($array, $keep_key_assoc = true) {
9576 $duplicate_keys = array();
9577 $tmp = array();
9579 foreach ($array as $key=>$val) {
9580 // convert objects to arrays, in_array() does not support objects
9581 if (is_object($val)) {
9582 $val = (array)$val;
9585 if (!in_array($val, $tmp)) {
9586 $tmp[] = $val;
9587 } else {
9588 $duplicate_keys[] = $key;
9592 foreach ($duplicate_keys as $key) {
9593 unset($array[$key]);
9596 return $keep_key_assoc ? $array : array_values($array);
9600 * Returns the language string for the given plugin.
9602 * @param string $plugin the plugin code name
9603 * @param string $type the type of plugin (mod, block, filter)
9604 * @return string The plugin language string
9606 function get_plugin_name($plugin, $type='mod') {
9607 $plugin_name = '';
9609 switch ($type) {
9610 case 'mod':
9611 $plugin_name = get_string('modulename', $plugin);
9612 break;
9613 case 'blocks':
9614 $plugin_name = get_string('pluginname', "block_$plugin");
9615 if (empty($plugin_name) || $plugin_name == '[[pluginname]]') {
9616 if (($block = block_instance($plugin)) !== false) {
9617 $plugin_name = $block->get_title();
9618 } else {
9619 $plugin_name = "[[$plugin]]";
9622 break;
9623 case 'filter':
9624 $plugin_name = filter_get_name('filter/' . $plugin);
9625 break;
9626 default:
9627 $plugin_name = $plugin;
9628 break;
9631 return $plugin_name;
9635 * Is a userid the primary administrator?
9637 * @param int $userid int id of user to check
9638 * @return boolean
9640 function is_primary_admin($userid){
9641 $primaryadmin = get_admin();
9643 if($userid == $primaryadmin->id){
9644 return true;
9645 }else{
9646 return false;
9651 * Returns the site identifier
9653 * @global object
9654 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
9656 function get_site_identifier() {
9657 global $CFG;
9658 // Check to see if it is missing. If so, initialise it.
9659 if (empty($CFG->siteidentifier)) {
9660 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
9662 // Return it.
9663 return $CFG->siteidentifier;
9667 * Check whether the given password has no more than the specified
9668 * number of consecutive identical characters.
9670 * @param string $password password to be checked against the password policy
9671 * @param integer $maxchars maximum number of consecutive identical characters
9673 function check_consecutive_identical_characters($password, $maxchars) {
9675 if ($maxchars < 1) {
9676 return true; // 0 is to disable this check
9678 if (strlen($password) <= $maxchars) {
9679 return true; // too short to fail this test
9682 $previouschar = '';
9683 $consecutivecount = 1;
9684 foreach (str_split($password) as $char) {
9685 if ($char != $previouschar) {
9686 $consecutivecount = 1;
9688 else {
9689 $consecutivecount++;
9690 if ($consecutivecount > $maxchars) {
9691 return false; // check failed already
9695 $previouschar = $char;
9698 return true;
9702 * helper function to do partial function binding
9703 * so we can use it for preg_replace_callback, for example
9704 * this works with php functions, user functions, static methods and class methods
9705 * it returns you a callback that you can pass on like so:
9707 * $callback = partial('somefunction', $arg1, $arg2);
9708 * or
9709 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
9710 * or even
9711 * $obj = new someclass();
9712 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
9714 * and then the arguments that are passed through at calltime are appended to the argument list.
9716 * @param mixed $function a php callback
9717 * $param mixed $arg1.. $argv arguments to partially bind with
9719 * @return callback
9721 function partial() {
9722 if (!class_exists('partial')) {
9723 class partial{
9724 var $values = array();
9725 var $func;
9727 function __construct($func, $args) {
9728 $this->values = $args;
9729 $this->func = $func;
9732 function method() {
9733 $args = func_get_args();
9734 return call_user_func_array($this->func, array_merge($this->values, $args));
9738 $args = func_get_args();
9739 $func = array_shift($args);
9740 $p = new partial($func, $args);
9741 return array($p, 'method');
9745 * helper function to load up and initialise the mnet environment
9746 * this must be called before you use mnet functions.
9748 * @return mnet_environment the equivalent of old $MNET global
9750 function get_mnet_environment() {
9751 global $CFG;
9752 require_once($CFG->dirroot . '/mnet/lib.php');
9753 static $instance = null;
9754 if (empty($instance)) {
9755 $instance = new mnet_environment();
9756 $instance->init();
9758 return $instance;
9762 * during xmlrpc server code execution, any code wishing to access
9763 * information about the remote peer must use this to get it.
9765 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
9767 function get_mnet_remote_client() {
9768 if (!defined('MNET_SERVER')) {
9769 debugging(get_string('notinxmlrpcserver', 'mnet'));
9770 return false;
9772 global $MNET_REMOTE_CLIENT;
9773 if (isset($MNET_REMOTE_CLIENT)) {
9774 return $MNET_REMOTE_CLIENT;
9776 return false;
9780 * during the xmlrpc server code execution, this will be called
9781 * to setup the object returned by {@see get_mnet_remote_client}
9783 * @param mnet_remote_client $client the client to set up
9785 function set_mnet_remote_client($client) {
9786 if (!defined('MNET_SERVER')) {
9787 throw new moodle_exception('notinxmlrpcserver', 'mnet');
9789 global $MNET_REMOTE_CLIENT;
9790 $MNET_REMOTE_CLIENT = $client;
9794 * return the jump url for a given remote user
9795 * this is used for rewriting forum post links in emails, etc
9797 * @param stdclass $user the user to get the idp url for
9799 function mnet_get_idp_jump_url($user) {
9800 global $CFG;
9802 static $mnetjumps = array();
9803 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
9804 $idp = mnet_get_peer_host($user->mnethostid);
9805 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
9806 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
9808 return $mnetjumps[$user->mnethostid];
9812 * Gets the homepage to use for the current user
9814 * @return int One of HOMEPAGE_*
9816 function get_home_page() {
9817 global $CFG;
9819 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
9820 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
9821 return HOMEPAGE_MY;
9822 } else {
9823 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
9826 return HOMEPAGE_SITE;