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