3 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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
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 ///
38 * Time constant - the number of seconds in a year
40 define('YEARSECS', 31536000);
43 * Time constant - the number of seconds in a week
45 define('WEEKSECS', 604800);
48 * Time constant - the number of seconds in a day
50 define('DAYSECS', 86400);
53 * Time constant - the number of seconds in an hour
55 define('HOURSECS', 3600);
58 * Time constant - the number of seconds in a minute
60 define('MINSECS', 60);
63 * Time constant - the number of minutes in a day
65 define('DAYMINS', 1440);
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. //////////////
78 * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
80 define('PARAM_ALPHA', 'alpha');
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');
89 * PARAM_ALPHANUM - expected numbers and letters only.
91 define('PARAM_ALPHANUM', 'alphanum');
94 * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
96 define('PARAM_ALPHANUMEXT', 'alphanumext');
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');
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);
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);
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
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
447 function required_param($parname, $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];
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
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
480 function optional_param($parname, $default, $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)) {
489 if (isset($_POST[$parname])) { // POST has precedence
490 $param = $_POST[$parname];
491 } else if (isset($_GET[$parname])) {
492 $param = $_GET[$parname];
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
) {
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);
535 * Used by {@link optional_param()} and {@link required_param()} to
536 * clean the variables and/or cast to specific types, based on
539 * $course->format = clean_param($course->format, PARAM_ALPHA);
540 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
543 * @param mixed $param the variable we are cleaning
544 * @param int $type expected format of param after cleaning.
547 function clean_param($param, $type) {
551 if (is_array($param)) { // Let's loop
553 foreach ($param as $key => $value) {
554 $newparam[$key] = clean_param($value, $type);
560 case PARAM_RAW
: // no cleaning at all
563 case PARAM_RAW_TRIMMED
: // no cleaning, but strip leading and trailing whitespace.
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)) {
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
578 return (int)$param; // Convert to integer
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') {
603 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
606 $param = empty($param) ?
0 : 1;
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
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)) {
625 foreach ($matches[0] as $match) {
626 if ($match === '</lang>') {
634 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
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)) {
652 foreach ($matches[0] as $match) {
653 if ($match === '</span>') {
661 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
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 === '.') {
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
705 ||
$match[4] > 255 ) {
706 // hmmm, what kind of dotted quad is this?
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
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
725 $param =''; // not really ok
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
737 // relative - let's make sure there are no tricks
738 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
748 $param = trim($param);
749 // PEM formatted strings may contain letters/numbers and the symbols
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
);
759 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
767 if (!empty($param)) {
768 // PEM formatted strings may contain letters/numbers and the symbols
772 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
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++
) {
781 if (64 < strlen($lines[$i])) {
787 if (64 != strlen($lines[$i])) {
791 return implode("\n",$lines);
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
);
808 $tags = explode(',', $param);
810 foreach ($tags as $tag) {
811 $res = clean_param($tag, PARAM_TAG
);
817 return implode(',', $result);
822 case PARAM_CAPABILITY
:
823 if (get_capability_info($param)) {
829 case PARAM_PERMISSION
:
830 $param = (int)$param;
831 if (in_array($param, array(CAP_INHERIT
, CAP_ALLOW
, CAP_PREVENT
, CAP_PROHIBIT
))) {
838 $param = clean_param($param, PARAM_SAFEDIR
);
839 if (exists_auth_plugin($param)) {
846 $param = clean_param($param, PARAM_SAFEDIR
);
847 if (get_string_manager()->translation_exists($param)) {
850 return ''; // Specified language is not installed or param malformed
854 $param = clean_param($param, PARAM_SAFEDIR
);
855 if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
857 } else if (!empty($CFG->themedir
) and file_exists("$CFG->themedir/$param/config.php")) {
860 return ''; // Specified theme is not installed
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);
874 if (validate_email($param)) {
881 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
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)) {
901 } else if (is_string($value)) {
902 return ((string)(int)$value) === $value;
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);
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.
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) {
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)) {
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));
971 $DB->set_field('config', 'value', $value, array('name'=>$name));
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))) {
985 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
987 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
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);
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) {
1020 // normalise component name
1021 if ($plugin === 'moodle' or $plugin === 'core') {
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];
1031 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1034 if (array_key_exists($name, $CFG->config_php_settings
)) {
1035 // setting force in config file
1036 return $CFG->config_php_settings
[$name];
1038 return $DB->get_field('config', 'value', array('name'=>$name));
1043 // the user is after a recordset
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]);
1052 //convert to string as if it went through the DB
1053 $localcfg[$n] = (string)$v;
1058 return (object)$localcfg;
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]);
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
1085 * @return boolean whether the operation succeeded.
1087 function unset_config($name, $plugin=NULL) {
1090 if (empty($plugin)) {
1092 $DB->delete_records('config', array('name'=>$name));
1094 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
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) {
1108 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1109 $DB->delete_records_select('config', 'name LIKE ?', array($plugin . '_%'));
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) {
1126 if (empty($value) or $value === '$@NONE@$') {
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@$') {
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;
1159 * Invalidates browser caches and cached data in temp
1162 function purge_all_caches() {
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');
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) {
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";
1197 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1198 foreach ($flags as $flag) {
1199 $cf[$flag->name
] = $flag->value
;
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) {
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) {
1239 $timemodified = time();
1240 if ($expiry===NULL ||
$expiry < $timemodified) {
1241 $expiry = $timemodified +
24 * 60 * 60;
1243 $expiry = (int)$expiry;
1246 if ($value === NULL) {
1247 unset_cache_flag($type,$name);
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
1256 $f->expiry
= $expiry;
1257 $f->timemodified
= $timemodified;
1258 $DB->update_record('cache_flags', $f);
1260 $f = new stdClass();
1261 $f->flagtype
= $type;
1264 $f->expiry
= $expiry;
1265 $f->timemodified
= $timemodified;
1266 $DB->insert_record('cache_flags', $f);
1272 * Removes a single volatile flag
1275 * @param string $type the "type" namespace for the key
1276 * @param string $name the key to set
1279 function unset_cache_flag($type, $name) {
1281 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1286 * Garbage-collect volatile flags
1288 * @return bool Always returns true
1290 function gc_cache_flags() {
1292 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
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)
1308 function check_user_preferences_loaded(stdClass
$user, $cachelifetime = 120) {
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();
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
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;
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.
1354 function mark_user_preferences_changed($userid) {
1357 if (empty($userid) or isguestuser($userid)) {
1358 // no cache flags for guest and not-logged-in users
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) {
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)) {
1395 } else if (isset($user->id
)) {
1396 // $user is valid object
1397 } else if (is_numeric($user)) {
1398 $user = (object)array('id'=>(int)$user);
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;
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
1416 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id
));
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
);
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);
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) {
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)) {
1469 } else if (isset($user->id
)) {
1470 // $user is valid object
1471 } else if (is_numeric($user)) {
1472 $user = (object)array('id'=>(int)$user);
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]);
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
);
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,
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) {
1518 if (is_null($name)) {
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)) {
1526 } else if (isset($user->id
)) {
1527 // $user is valid object
1528 } else if (is_numeric($user)) {
1529 $user = (object)array('id'=>(int)$user);
1531 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1534 check_user_preferences_loaded($user);
1537 return $user->preference
; // All values
1538 } else if (isset($user->preference
[$name])) {
1539 return $user->preference
[$name]; // The single string value
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);
1573 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1574 $time = usertime($time, $timezone);
1576 $time -= dst_offset_on($time, $strtimezone);
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
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
;
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) {
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.
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
1698 $datestring = strftime($formatnoday, $date);
1699 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
1700 $datestring = str_replace('DD', $daystring, $datestring);
1702 $datestring = strftime($format, $date);
1705 $date +
= (int)($timezone * 3600);
1707 $datestring = gmstrftime($formatnoday, $date);
1708 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
1709 $datestring = str_replace('DD', $daystring, $datestring);
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');
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
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
1760 $getdate['weekday'],
1767 $getdate['minutes'],
1769 ) = explode('_', $datestring);
1775 * Given a GMT timestamp (seconds since epoch), offsets it by
1776 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1779 * @param int $date Timestamp in GMT
1780 * @param float $timezone
1783 function usertime($date, $timezone=99) {
1785 $timezone = get_user_timezone_offset($timezone);
1787 if (abs($timezone) > 13) {
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
1816 function usertimezone($timezone=99) {
1818 $tz = get_user_timezone($timezone);
1820 if (!is_float($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
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
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
1854 function get_user_timezone_offset($tz = 99) {
1858 $tz = get_user_timezone($tz);
1860 if (is_float($tz)) {
1863 $tzrecord = get_timezone_record($tz);
1864 if (empty($tzrecord)) {
1867 return (float)$tzrecord->gmtoff
/ HOURMINS
;
1872 * Returns an int which represents the systems's timezone difference from GMT in seconds
1875 * @param mixed $tz timezone
1876 * @return int if found, false is timezone 99 or error
1878 function get_timezone_offset($tz) {
1885 if (is_numeric($tz)) {
1886 return intval($tz * 60*60);
1889 if (!$tzrecord = get_timezone_record($tz)) {
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
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
1906 function get_user_timezone($tz = 99) {
1911 isset($CFG->forcetimezone
) ?
$CFG->forcetimezone
: 99,
1912 isset($USER->timezone
) ?
$USER->timezone
: 99,
1913 isset($CFG->timezone
) ?
$CFG->timezone
: 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
1930 * @param string $timezonename
1931 * @return mixed timezonerecord object or false
1933 function get_timezone_record($timezonename) {
1935 static $cache = NULL;
1937 if ($cache === NULL) {
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
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
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
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
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);
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
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!
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])) {
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) {
2059 $changes = dst_changes_for_year($y, $preset);
2061 if($changes === NULL) {
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
2077 krsort($SESSION->dst_offsets
);
2083 * Calculates the required DST change and returns a Timestamp Array
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) {
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)
2120 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2123 function dst_offset_on($time, $strtimezone = NULL) {
2126 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) ||
empty($SESSION->dst_offsets
)) {
2130 reset($SESSION->dst_offsets
);
2131 while(list($from, $offset) = each($SESSION->dst_offsets
)) {
2132 if($from <= $time) {
2137 // This is the normal return path
2138 if($offset !== NULL) {
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.
2147 // We need a year smaller than $SESSION->dst_range[0]
2148 if($SESSION->dst_range
[0] == 1971) {
2151 calculate_user_dst_table($SESSION->dst_range
[0] - 5, NULL, $strtimezone);
2152 return dst_offset_on($time, $strtimezone);
2155 // We need a year larger than $SESSION->dst_range[1]
2156 if($SESSION->dst_range
[1] == 2035) {
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
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
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) {
2205 // Find the first such weekday <= $startday
2206 while($lastinmonth > $startday) {
2210 return $lastinmonth;
2215 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2217 $diff = $weekday - $indexweekday;
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
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
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() {
2265 $url = "$CFG->wwwroot/login/index.php";
2267 if (!empty($CFG->loginhttps
)) {
2268 $url = str_replace('http:', 'https:', $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
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);
2311 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST
);
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');
2328 $PAGE->set_course($course); // set's up global $COURSE
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!!
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;
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);
2388 //use moodle internal method
2389 if (empty($CFG->loginhttps
)) {
2390 redirect($CFG->wwwroot
.'/login/change_password.php');
2392 $wwwroot = str_replace('http:','https:', $CFG->wwwroot
);
2393 redirect($wwwroot .'/login/change_password.php');
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
.'&course='. SITEID
);
2410 // Make sure the USER has a sesskey set up. Used for CSRF protection.
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
);
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
);
2441 $cmcontext = get_context_instance(CONTEXT_MODULE
, $cm->id
, MUST_EXIST
);
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
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
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
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();
2500 if (is_viewing($coursecontext, $USER)) {
2501 // ok, no need to mess with enrol
2505 if (isset($USER->enrol
['enrolled'][$course->id
])) {
2506 if ($USER->enrol
['enrolled'][$course->id
] == 0) {
2508 } else if ($USER->enrol
['enrolled'][$course->id
] > time()) {
2512 unset($USER->enrol
['enrolled'][$course->id
]);
2515 if (isset($USER->enrol
['tempguest'][$course->id
])) {
2516 if ($USER->enrol
['tempguest'][$course->id
] == 0) {
2518 } else if ($USER->enrol
['tempguest'][$course->id
] > time()) {
2522 unset($USER->enrol
['tempguest'][$course->id
]);
2523 $USER->access
= remove_temp_roles($coursecontext, $USER->access
);
2529 } else if (is_enrolled($coursecontext, $USER, '', true)) {
2530 // active participants may always access
2531 // TODO: refactor this into some new function
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;
2549 // remove traces of previous temp guest access
2550 $USER->access
= remove_temp_roles($coursecontext, $USER->access
);
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
])) {
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
);
2569 // if not enrolled yet try to gain temporary guest access
2571 foreach($instances as $instance) {
2572 if (!isset($enrols[$instance->enrol
])) {
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;
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.
2615 function require_logout() {
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();
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()
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
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;
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);
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;
2692 $course = clone($SITE);
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');
2701 $PAGE->set_course($course);
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
);
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.
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');
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.
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) {
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
))) {
2800 $key->value
= md5($userid.'_'.time().random_string(40));
2802 $DB->insert_record('user_private_key', $key);
2807 * Delete the user's new private user access keys for a particular script.
2810 * @param string $script unique target identifier
2811 * @param int $userid
2814 function delete_user_key($script,$userid) {
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).
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) {
2833 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
2834 'instance'=>$instance, 'iprestriction'=>$iprestriction,
2835 'validuntil'=>$validuntil))) {
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.
2849 * @return bool Always returns true
2851 function update_user_login_times() {
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);
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
2870 function user_not_fully_set_up($user) {
2871 if (isguestuser($user)) {
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
2882 * @param user $user A {@link $USER} object
2883 * @return bool true=>User has exceeded bounce threshold
2885 function over_bounce_threshold($user) {
2888 if (empty($CFG->handlebounces
)) {
2892 if (empty($user->id
)) { /// No real (DB) user, nothing to do here.
2896 // set sensible defaults
2897 if (empty($CFG->minbounces
)) {
2898 $CFG->minbounces
= 10;
2900 if (empty($CFG->bounceratio
)) {
2901 $CFG->bounceratio
= .20;
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
2918 * @param user $user object containing an id
2919 * @param bool $reset will reset the count to 0
2922 function set_send_count($user,$reset=false) {
2925 if (empty($user->id
)) { /// No real (DB) user, nothing to do here.
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.
2935 $pref = new stdClass();
2936 $pref->name
= 'email_send_count';
2938 $pref->userid
= $user->id
;
2939 $DB->insert_record('user_preferences', $pref, false);
2944 * Increment or reset user's email bounce count
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) {
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.
2959 $pref = new stdClass();
2960 $pref->name
= 'email_bounce_count';
2962 $pref->userid
= $user->id
;
2963 $DB->insert_record('user_preferences', $pref, false);
2968 * Keeps track of login attempts
2972 function update_login_count() {
2977 if (empty($SESSION->logincount
)) {
2978 $SESSION->logincount
= 1;
2980 $SESSION->logincount++
;
2983 if ($SESSION->logincount
> $max_logins) {
2984 unset($SESSION->wantsurl
);
2985 print_error('errortoomanylogins');
2990 * Resets login attempts
2994 function reset_login_count() {
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
3008 * @uses DEBUG_DEVELOPER
3011 function isediting() {
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
3021 * @param int $courseid The id of the course being tested
3024 function ismoving($courseid) {
3027 if (!empty($USER->activitycopy
)) {
3028 return ($USER->activitycopycourse
== $courseid);
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.
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.
3049 function fullname($user, $override=false) {
3050 global $CFG, $SESSION;
3052 if (!isset($user->firstname
) and !isset($user->lastname
)) {
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') {
3077 return get_string('fullnamedisplay', '', $user);
3079 return $user->firstname
;
3083 return get_string('fullnamedisplay', '', $user);
3087 * Returns whether a given authentication plugin exists.
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) {
3097 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3098 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
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) {
3114 $enabled = get_enabled_auth_plugins();
3116 return in_array($auth, $enabled);
3120 * Returns an authentication plugin instance.
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) {
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";
3141 * Returns array of active auth plugins.
3143 * @param bool $fix fix $CFG->auth if needed
3146 function get_enabled_auth_plugins($fix=false) {
3149 $default = array('manual', 'nologin');
3151 if (empty($CFG->auth
)) {
3154 $auths = explode(',', $CFG->auth
);
3158 $auths = array_unique($auths);
3159 foreach($auths as $k=>$authname) {
3160 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
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
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
3193 * @param string $username username to be checked
3196 function is_restored_user($username) {
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() {
3210 $fieldarray = $DB->get_columns('user');
3211 unset($fieldarray['id']);
3212 $fieldarray = array_keys($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') {
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;
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
)));
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) {
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
);
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
3307 $confval = $userauth->config
->{'field_updatelocal_' . $key};
3308 $lockval = $userauth->config
->{'field_lock_' . $key};
3309 if (empty($confval) ||
empty($lockval)) {
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;
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
3358 'institution' => 40,
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]));
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) {
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
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
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);
3461 * Retrieve the guest user object
3465 * @return user A {@link $USER} object
3467 function guest_user() {
3470 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest
))) {
3471 $newuser->confirmed
= 1;
3472 $newuser->lang
= $CFG->lang
;
3473 $newuser->lastip
= getremoteaddr();
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
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) {
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']);
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']);
3516 $auths = array($auth);
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']);
3525 // User does not exist
3526 $auths = $authsenabled;
3527 $user = new stdClass();
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)) {
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);
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
)) {
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']);
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']);
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
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) {
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
3627 if (empty($CFG->nolastloggedin
)) {
3628 set_moodle_cookie($USER->username
);
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);
3646 redirect($CFG->httpswwwroot
.'/login/change_password.php');
3649 print_error('nopasswordchangeforced', 'auth');
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) {
3666 if (!isset($CFG->passwordsaltmain
)) {
3667 $CFG->passwordsaltmain
= '';
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
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)) {
3696 // force update of password hash using latest main password salt and encoding if needed
3697 update_internal_user_password($user, $password);
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) {
3712 if (isset($CFG->passwordsaltmain
)) {
3713 return md5($password.$CFG->passwordsaltmain
);
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) {
3729 $authplugin = get_auth_plugin($user->auth
);
3730 if ($authplugin->prevent_local_passwords()) {
3731 $hashedpassword = 'not cached';
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;
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) {
3758 if (!$field ||
!$value) {
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)) {
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
= ' ';
3837 * Validate a password against the configured password policy
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) {
3847 if (empty($CFG->passwordpolicy
)) {
3851 $textlib = textlib_get_instance();
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 == '') {
3885 * When logging in, this function is run to set certain preferences
3886 * for the current SESSION
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.
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) {
3921 if (is_object($courseorid)) {
3922 $courseid = $courseorid->id
;
3923 $course = $courseorid;
3925 $courseid = $courseorid;
3926 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
3930 $context = get_context_instance(CONTEXT_COURSE
, $courseid);
3932 // frontpage course can not be deleted!!
3933 if ($courseid == SITEID
) {
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));
3945 $course->context
= $context; // you can not fetch context in the event because it was already deleted
3946 events_trigger('course_deleted', $course);
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=?)',
4015 $DB->delete_records_select('course_modules_availability',
4016 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
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)
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
)) {
4043 echo $OUTPUT->notification('Could not delete '. $modname .' instance '. $instance->id
.' ('. format_string($instance->name
) .')');
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
));
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);
4113 $course->context
= $context; // you can not access context in cron event later after course is deleted
4114 events_trigger('course_content_removed', $course);
4120 * Change dates in module - used from course reset.
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) {
4132 include_once($CFG->dirroot
.'/mod/'.$modname.'/lib.php');
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);
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
);
4171 $data->timeshift
= 0;
4174 // result array: component, item, error
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]);
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
])) {
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);
4328 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
4331 $unsupported_mods[] = $mod;
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');
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);
4360 if (!empty($data->reset_comments
)) {
4361 require_once($CFG->dirroot
.'/comment/lib.php');
4362 comment
::reset_course_page_comments($context);
4369 * Generate an email processing address
4372 * @param string $modargs
4373 * @return string Returns email processing address
4375 function generate_email_processing_address($modid,$modargs) {
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
4388 * @param string $modargs
4389 * @param string $body Currently unused
4391 function moodle_process_email($modargs,$body) {
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?
4408 // maybe more later?
4412 /// CORRESPONDENCE ////////////////////////////////////////////////
4415 * Get mailer instance, enable buffering, flush buffer or disable buffering.
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') {
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()) {
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
= "";
4447 $mailer->AltBody
= "";
4448 $mailer->ConfirmReadingTo
= "";
4450 $mailer->ClearAllRecipients();
4451 $mailer->ClearReplyTos();
4452 $mailer->ClearAttachments();
4453 $mailer->ClearCustomHeaders();
4457 $prevkeepalive = $mailer->SMTPKeepAlive
;
4458 get_mailer('flush');
4461 include_once($CFG->libdir
.'/phpmailer/moodle_phpmailer.php');
4462 $mailer = new moodle_phpmailer();
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";
4477 if ($CFG->smtphosts
== 'qmail') {
4478 $mailer->IsQmail(); // use Qmail system
4480 } else if (empty($CFG->smtphosts
)) {
4481 $mailer->IsMail(); // use PHP mail() = sendmail
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
;
4503 // keep smtp session open after sending
4504 if ($action == 'buffer') {
4505 if (!empty($CFG->smtpmaxbulk
)) {
4506 get_mailer('flush');
4508 if ($m->Mailer
== 'smtp') {
4509 $m->SMTPKeepAlive
= true;
4515 // close smtp session, but continue buffering
4516 if ($action == 'flush') {
4517 if (isset($mailer) and $mailer->Mailer
== 'smtp') {
4518 if (!empty($mailer->SMTPDebug
)) {
4521 $mailer->SmtpClose();
4522 if (!empty($mailer->SMTPDebug
)) {
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
4541 * Send an email to a specified user
4545 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
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');
4570 if (!empty($user->deleted
)) {
4571 // do not mail delted users
4572 mtrace('Error: lib/moodlelib.php email_to_user(): User is deleted');
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');
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') {
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);
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:]]*)%",
4612 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
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);
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);
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);
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";
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');
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
)) {
4740 mtrace('ERROR: '. $mail->ErrorInfo
);
4741 add_to_log(SITEID
, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo
);
4742 if (!empty($mail->SMTPDebug
)) {
4750 * Generate a signoff for emails based on support settings
4755 function generate_email_signoff() {
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";
4772 * Generate a fake user for emails based on support settings
4774 * @return object user info
4776 function generate_email_supportuser() {
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.
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) {
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) {
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.");
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.
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) {
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.
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) {
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.
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) {
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();
4968 //no way to change password, sorry
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
));
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
4990 * @param string $email Content of email
4991 * @return string|false
4993 function email_is_not_allowed($email) {
4996 if (!empty($CFG->allowemailaddresses
)) {
4997 $allowed = explode(' ', $CFG->allowemailaddresses
);
4998 foreach ($allowed as $allowedpattern) {
4999 $allowedpattern = trim($allowedpattern);
5000 if (!$allowedpattern) {
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"
5009 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
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) {
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
);
5037 /// FILE HANDLING /////////////////////////////////////////////
5040 * Returns local file storage instance
5042 * @return file_storage
5044 function get_file_storage() {
5053 require_once("$CFG->libdir/filelib.php");
5055 if (isset($CFG->filedir
)) {
5056 $filedir = $CFG->filedir
;
5058 $filedir = $CFG->dataroot
.'/filedir';
5061 if (isset($CFG->trashdir
)) {
5062 $trashdirdir = $CFG->trashdir
;
5064 $trashdirdir = $CFG->dataroot
.'/trashdir';
5067 $fs = new file_storage($filedir, $trashdirdir, "$CFG->dataroot/temp/filestorage", $CFG->directorypermissions
, $CFG->filepermissions
);
5073 * Returns local file storage instance
5075 * @return file_browser
5077 function get_file_browser() {
5086 require_once("$CFG->libdir/filelib.php");
5088 $fb = new file_browser();
5094 * Returns file packer
5096 * @param string $mimetype default application/zip
5097 * @return file_packer
5099 function get_file_packer($mimetype='application/zip') {
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';
5113 case 'application/x-tar':
5114 // $classname = 'tar_packer';
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)) {
5136 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5137 return $newfile['tmp_name'];
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')) {
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
5202 * @todo Finish documenting this function
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)
5211 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5214 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
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
);
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) {
5262 if (!$getdirs and !$getfiles) { // Nothing to show
5266 if (!is_dir($rootdir)) { // Must be a directory
5270 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
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)) {
5283 $fullfile = $rootdir .'/'. $file;
5284 if (filetype($fullfile) == 'dir') {
5289 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5290 foreach ($subdirs as $subdir) {
5291 $dirs[] = $file .'/'. $subdir;
5294 } else if ($getfiles) {
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='') {
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);
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
5333 if (!$dir = @opendir
($rootdir)) { // Can't open it for some reason
5339 while (false !== ($file = readdir($dir))) {
5340 $firstchar = substr($file, 0, 1);
5341 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5344 $fullfile = $rootdir .'/'. $file;
5345 if (filetype($fullfile) == 'dir') {
5346 $size +
= get_directory_size($fullfile, $excludefile);
5348 $size +
= filesize($fullfile);
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
5368 function display_size($size) {
5370 static $gb, $mb, $kb, $b;
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;
5386 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
5392 * Cleans a given filename by removing suspicious or troublesome characters
5393 * @see clean_param()
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
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
;
5430 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
5436 * Returns parent language of current active language if defined
5440 * @param string $lang null means current language
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
;
5451 $SESSION->lang
= $lang;
5454 $parentlang = get_string('parentlanguage', 'langconfig');
5455 if ($parentlang === 'en') {
5459 //let's hack around the current language
5460 if (!empty($lang)) {
5461 $COURSE->lang
= $old_course_lang;
5462 $SESSION->lang
= $old_session_lang;
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) {
5480 static $singleton = null;
5485 if ($singleton === null) {
5486 if (empty($CFG->early_install_lang
)) {
5488 if (empty($CFG->langcacheroot
)) {
5489 $langcacheroot = $CFG->dataroot
. '/cache/lang';
5491 $langcacheroot = $CFG->langcacheroot
;
5494 if (empty($CFG->langlist
)) {
5495 $translist = array();
5497 $translist = explode(',', $CFG->langlist
);
5500 if (empty($CFG->langmenucachefile
)) {
5501 $langmenucache = $CFG->dataroot
. '/cache/languages';
5503 $langmenucache = $CFG->langmenucachefile
;
5506 $singleton = new core_string_manager($CFG->langotherroot
, $CFG->langlocalroot
, $langcacheroot,
5507 !empty($CFG->langstringcache
), $translist, $langmenucache);
5510 $singleton = new install_string_manager();
5519 * Interface describing class which is responsible for getting
5520 * of localised strings from language packs.
5522 * @package moodlecore
5523 * @copyright 2010 Petr Skoda (http://skodak.org)
5524 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5526 interface string_manager
{
5528 * Get String returns a requested string
5530 * @param string $identifier The identifier of the string to search for
5531 * @param string $component The module the string is associated with
5532 * @param string|object|array $a An object, string or number that can be used
5533 * within translation strings
5534 * @param string $lang moodle translation language, NULL means use current
5535 * @return string The String !
5537 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
5540 * Does the string actually exist?
5542 * get_string() is throwing debug warnings, sometimes we do not want them
5543 * or we want to display better explanation of the problem.
5547 * @param string $identifier The identifier of the string to search for
5548 * @param string $component The module the string is associated with
5549 * @return boot true if exists
5551 public function string_exists($identifier, $component);
5554 * Returns a localised list of all country names, sorted by country keys.
5555 * @param bool $returnall return all or just enabled
5556 * @param string $lang moodle translation language, NULL means use current
5557 * @return array two-letter country code => translated name.
5559 public function get_list_of_countries($returnall = false, $lang = NULL);
5562 * Returns a localised list of languages, sorted by code keys.
5564 * @param string $lang moodle translation language, NULL means use current
5565 * @param string $standard language list standard
5566 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
5567 * @return array language code => translated name
5569 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
5572 * Does the translation exist?
5574 * @param string $lang moodle translation language code
5575 * @param bool include also disabled translations?
5576 * @return boot true if exists
5578 public function translation_exists($lang, $includeall = true);
5581 * Returns localised list of installed translations
5582 * @param bool $returnall return all or just enabled
5583 * @return array moodle translation code => localised translation name
5585 public function get_list_of_translations($returnall = false);
5588 * Returns localised list of currencies.
5590 * @param string $lang moodle translation language, NULL means use current
5591 * @return array currency code => localised currency name
5593 public function get_list_of_currencies($lang = NULL);
5596 * Load all strings for one component
5597 * @param string $component The module the string is associated with
5598 * @param string $lang
5599 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5600 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5601 * @return array of all string for given component and lang
5603 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
5606 * Invalidates all caches, should the implementation use any
5608 public function reset_caches();
5613 * Standard string_manager implementation
5615 * @package moodlecore
5616 * @copyright 2010 Petr Skoda (http://skodak.org)
5617 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5619 class core_string_manager
implements string_manager
{
5620 /** @var string location of all packs except 'en' */
5621 protected $otherroot;
5622 /** @var string location of all lang pack local modifications */
5623 protected $localroot;
5624 /** @var string location of on-disk cache of merged strings */
5625 protected $cacheroot;
5626 /** @var array lang string cache - it will be optimised more later */
5627 protected $cache = array();
5628 /** @var int get_string() counter */
5629 protected $countgetstring = 0;
5630 /** @var int in-memory cache hits counter */
5631 protected $countmemcache = 0;
5632 /** @var int on-disk cache hits counter */
5633 protected $countdiskcache = 0;
5634 /** @var bool use disk cache */
5635 protected $usediskcache;
5636 /* @var array limit list of translations */
5637 protected $translist;
5638 /** @var string location of a file that caches the list of available translations */
5639 protected $menucache;
5642 * Create new instance of string manager
5644 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
5645 * @param string $localroot usually the same as $otherroot
5646 * @param string $cacheroot usually lang dir in cache folder
5647 * @param bool $usediskcache use disk cache
5648 * @param array $translist limit list of visible translations
5649 * @param string $menucache the location of a file that caches the list of available translations
5651 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
5652 $this->otherroot
= $otherroot;
5653 $this->localroot
= $localroot;
5654 $this->cacheroot
= $cacheroot;
5655 $this->usediskcache
= $usediskcache;
5656 $this->translist
= $translist;
5657 $this->menucache
= $menucache;
5661 * Returns dependencies of current language, en is not included.
5662 * @param string $lang
5663 * @return array all parents, the lang itself is last
5665 public function get_language_dependencies($lang) {
5666 if ($lang === 'en') {
5669 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
5673 include("$this->otherroot/$lang/langconfig.php");
5675 if (empty($string['parentlanguage'])) {
5676 return array($lang);
5678 $parentlang = $string['parentlanguage'];
5680 return array_merge($this->get_language_dependencies($parentlang), array($lang));
5685 * Load all strings for one component
5686 * @param string $component The module the string is associated with
5687 * @param string $lang
5688 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5689 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5690 * @return array of all string for given component and lang
5692 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
5695 list($plugintype, $pluginname) = normalize_component($component);
5696 if ($plugintype == 'core' and is_null($pluginname)) {
5697 $component = 'core';
5699 $component = $plugintype . '_' . $pluginname;
5702 if (!$disablecache) {
5703 // try in-memory cache first
5704 if (isset($this->cache
[$lang][$component])) {
5705 $this->countmemcache++
;
5706 return $this->cache
[$lang][$component];
5709 // try on-disk cache then
5710 if ($this->usediskcache
and file_exists($this->cacheroot
. "/$lang/$component.php")) {
5711 $this->countdiskcache++
;
5712 include($this->cacheroot
. "/$lang/$component.php");
5713 return $this->cache
[$lang][$component];
5717 // no cache found - let us merge all possible sources of the strings
5718 if ($plugintype === 'core') {
5719 $file = $pluginname;
5720 if ($file === null) {
5724 // first load english pack
5725 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
5728 include("$CFG->dirroot/lang/en/$file.php");
5729 $originalkeys = array_keys($string);
5730 $originalkeys = array_flip($originalkeys);
5732 // and then corresponding local if present and allowed
5733 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5734 include("$this->localroot/en_local/$file.php");
5736 // now loop through all langs in correct order
5737 $deps = $this->get_language_dependencies($lang);
5738 foreach ($deps as $dep) {
5739 // the main lang string location
5740 if (file_exists("$this->otherroot/$dep/$file.php")) {
5741 include("$this->otherroot/$dep/$file.php");
5743 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5744 include("$this->localroot/{$dep}_local/$file.php");
5749 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
5752 if ($plugintype === 'mod') {
5754 $file = $pluginname;
5756 $file = $plugintype . '_' . $pluginname;
5759 // first load English pack
5760 if (!file_exists("$location/lang/en/$file.php")) {
5761 //English pack does not exist, so do not try to load anything else
5764 include("$location/lang/en/$file.php");
5765 $originalkeys = array_keys($string);
5766 $originalkeys = array_flip($originalkeys);
5767 // and then corresponding local english if present
5768 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5769 include("$this->localroot/en_local/$file.php");
5772 // now loop through all langs in correct order
5773 $deps = $this->get_language_dependencies($lang);
5774 foreach ($deps as $dep) {
5775 // legacy location - used by contrib only
5776 if (file_exists("$location/lang/$dep/$file.php")) {
5777 include("$location/lang/$dep/$file.php");
5779 // the main lang string location
5780 if (file_exists("$this->otherroot/$dep/$file.php")) {
5781 include("$this->otherroot/$dep/$file.php");
5783 // local customisations
5784 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5785 include("$this->localroot/{$dep}_local/$file.php");
5790 // we do not want any extra strings from other languages - everything must be in en lang pack
5791 $string = array_intersect_key($string, $originalkeys);
5793 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
5794 // caches so we do not need to do all this merging and dependencies resolving again
5795 $this->cache
[$lang][$component] = $string;
5796 if ($this->usediskcache
) {
5797 check_dir_exists("$this->cacheroot/$lang");
5798 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
5804 * Does the string actually exist?
5806 * get_string() is throwing debug warnings, sometimes we do not want them
5807 * or we want to display better explanation of the problem.
5811 * @param string $identifier The identifier of the string to search for
5812 * @param string $component The module the string is associated with
5813 * @return boot true if exists
5815 public function string_exists($identifier, $component) {
5816 $identifier = clean_param($identifier, PARAM_STRINGID
);
5817 if (empty($identifier)) {
5820 $lang = current_language();
5821 $string = $this->load_component_strings($component, $lang);
5822 return isset($string[$identifier]);
5826 * Get String returns a requested string
5828 * @param string $identifier The identifier of the string to search for
5829 * @param string $component The module the string is associated with
5830 * @param string|object|array $a An object, string or number that can be used
5831 * within translation strings
5832 * @param string $lang moodle translation language, NULL means use current
5833 * @return string The String !
5835 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
5836 $this->countgetstring++
;
5837 // there are very many uses of these time formating strings without the 'langconfig' component,
5838 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
5839 static $langconfigstrs = array(
5840 'strftimedate' => 1,
5841 'strftimedatefullshort' => 1,
5842 'strftimedateshort' => 1,
5843 'strftimedatetime' => 1,
5844 'strftimedatetimeshort' => 1,
5845 'strftimedaydate' => 1,
5846 'strftimedaydatetime' => 1,
5847 'strftimedayshort' => 1,
5848 'strftimedaytime' => 1,
5849 'strftimemonthyear' => 1,
5850 'strftimerecent' => 1,
5851 'strftimerecentfull' => 1,
5852 'strftimetime' => 1);
5854 if (empty($component)) {
5855 if (isset($langconfigstrs[$identifier])) {
5856 $component = 'langconfig';
5858 $component = 'moodle';
5862 if ($lang === NULL) {
5863 $lang = current_language();
5866 $string = $this->load_component_strings($component, $lang);
5868 if (!isset($string[$identifier])) {
5869 if ($component === 'pix' or $component === 'core_pix') {
5870 // this component contains only alt tags for emoticons,
5871 // not all of them are supposed to be defined
5874 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
5875 // parentlanguage is a special string, undefined means use English if not defined
5878 if ($this->usediskcache
) {
5879 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources
5880 $string = $this->load_component_strings($component, $lang, true);
5882 if (!isset($string[$identifier])) {
5883 // the string is still missing - should be fixed by developer
5884 debugging("Invalid get_string() identifier: '$identifier' or component '$component'", DEBUG_DEVELOPER
);
5885 return "[[$identifier]]";
5889 $string = $string[$identifier];
5892 if (is_object($a) or is_array($a)) {
5896 foreach ($a as $key=>$value) {
5898 // we do not support numeric keys - sorry!
5901 if (is_object($value) or is_array($value)) {
5902 // we support just string as value
5905 $search[] = '{$a->'.$key.'}';
5906 $replace[] = (string)$value;
5909 $string = str_replace($search, $replace, $string);
5912 $string = str_replace('{$a}', (string)$a, $string);
5920 * Returns information about the string_manager performance
5923 public function get_performance_summary() {
5925 'langcountgetstring' => $this->countgetstring
,
5926 'langcountmemcache' => $this->countmemcache
,
5927 'langcountdiskcache' => $this->countdiskcache
,
5929 'langcountgetstring' => 'get_string calls',
5930 'langcountmemcache' => 'strings mem cache hits',
5931 'langcountdiskcache' => 'strings disk cache hits',
5936 * Returns a localised list of all country names, sorted by localised name.
5938 * @param bool $returnall return all or just enabled
5939 * @param string $lang moodle translation language, NULL means use current
5940 * @return array two-letter country code => translated name.
5942 public function get_list_of_countries($returnall = false, $lang = NULL) {
5945 if ($lang === NULL) {
5946 $lang = current_language();
5949 $countries = $this->load_component_strings('core_countries', $lang);
5950 textlib_get_instance()->asort($countries);
5951 if (!$returnall and !empty($CFG->allcountrycodes
)) {
5952 $enabled = explode(',', $CFG->allcountrycodes
);
5954 foreach ($enabled as $c) {
5955 if (isset($countries[$c])) {
5956 $return[$c] = $countries[$c];
5966 * Returns a localised list of languages, sorted by code keys.
5968 * @param string $lang moodle translation language, NULL means use current
5969 * @param string $standard language list standard
5970 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
5971 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
5972 * @return array language code => translated name
5974 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
5975 if ($lang === NULL) {
5976 $lang = current_language();
5979 if ($standard === 'iso6392') {
5980 $langs = $this->load_component_strings('core_iso6392', $lang);
5984 } else if ($standard === 'iso6391') {
5985 $langs2 = $this->load_component_strings('core_iso6392', $lang);
5986 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
5987 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
5988 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
5989 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
5990 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
5991 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
5992 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
5993 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
5994 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
5995 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
5996 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
5997 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
5998 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
5999 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6000 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6001 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6002 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6004 foreach ($mapping as $c2=>$c1) {
6005 $langs1[$c1] = $langs2[$c2];
6011 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6018 * Does the translation exist?
6020 * @param string $lang moodle translation language code
6021 * @param bool include also disabled translations?
6022 * @return boot true if exists
6024 public function translation_exists($lang, $includeall = true) {
6026 if (strpos($lang, '_local') !== false) {
6027 // _local packs are not real translations
6030 if (!$includeall and !empty($this->translist
)) {
6031 if (!in_array($lang, $this->translist
)) {
6035 if ($lang === 'en') {
6036 // part of distribution
6039 return file_exists("$this->otherroot/$lang/langconfig.php");
6043 * Returns localised list of installed translations
6044 * @param bool $returnall return all or just enabled
6045 * @return array moodle translation code => localised translation name
6047 public function get_list_of_translations($returnall = false) {
6050 $languages = array();
6052 if (!empty($CFG->langcache
) and is_readable($this->menucache
)) {
6053 // try to re-use the cached list of all available languages
6054 $cachedlist = json_decode(file_get_contents($this->menucache
), true);
6056 if (is_array($cachedlist) and !empty($cachedlist)) {
6057 // the cache file is restored correctly
6059 if (!$returnall and !empty($this->translist
)) {
6060 // return just enabled translations
6061 foreach ($cachedlist as $langcode => $langname) {
6062 if (in_array($langcode, $this->translist
)) {
6063 $languages[$langcode] = $langname;
6069 // return all translations
6075 // the cached list of languages is not available, let us populate the list
6077 if (!$returnall and !empty($this->translist
)) {
6078 // return only some translations
6079 foreach ($this->translist
as $lang) {
6080 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6081 if (strstr($lang, '_local') !== false) {
6084 if (strstr($lang, '_utf8') !== false) {
6087 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6088 // some broken or missing lang - can not switch to it anyway
6091 $string = $this->load_component_strings('langconfig', $lang);
6092 if (!empty($string['thislanguage'])) {
6093 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6099 // return all languages available in system
6100 $langdirs = get_list_of_plugins('', '', $this->otherroot
);
6102 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6105 // Loop through all langs and get info
6106 foreach ($langdirs as $lang) {
6107 if (strstr($lang, '_local') !== false) {
6110 if (strstr($lang, '_utf8') !== false) {
6113 $string = $this->load_component_strings('langconfig', $lang);
6114 if (!empty($string['thislanguage'])) {
6115 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6120 if (!empty($CFG->langcache
) and !empty($this->menucache
)) {
6121 // cache the list so that it can be used next time
6122 textlib_get_instance()->asort($languages);
6123 file_put_contents($this->menucache
, json_encode($languages));
6127 textlib_get_instance()->asort($languages);
6133 * Returns localised list of currencies.
6135 * @param string $lang moodle translation language, NULL means use current
6136 * @return array currency code => localised currency name
6138 public function get_list_of_currencies($lang = NULL) {
6139 if ($lang === NULL) {
6140 $lang = current_language();
6143 $currencies = $this->load_component_strings('core_currencies', $lang);
6150 * Clears both in-memory and on-disk caches
6152 public function reset_caches() {
6154 require_once("$CFG->libdir/filelib.php");
6156 // clear the on-disk disk with aggregated string files
6157 fulldelete($this->cacheroot
);
6159 // clear the in-memory cache of loaded strings
6160 $this->cache
= array();
6162 // clear the cache containing the list of available translations
6163 // and re-populate it again
6164 fulldelete($this->menucache
);
6165 $this->get_list_of_translations(true);
6171 * Minimalistic string fetching implementation
6172 * that is used in installer before we fetch the wanted
6173 * language pack from moodle.org lang download site.
6175 * @package moodlecore
6176 * @copyright 2010 Petr Skoda (http://skodak.org)
6177 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6179 class install_string_manager
implements string_manager
{
6180 /** @var string location of pre-install packs for all langs */
6181 protected $installroot;
6184 * Crate new instance of install string manager
6186 public function __construct() {
6188 $this->installroot
= "$CFG->dirroot/install/lang";
6192 * Load all strings for one component
6193 * @param string $component The module the string is associated with
6194 * @param string $lang
6195 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6196 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6197 * @return array of all string for given component and lang
6199 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6200 // not needed in installer
6205 * Does the string actually exist?
6207 * get_string() is throwing debug warnings, sometimes we do not want them
6208 * or we want to display better explanation of the problem.
6212 * @param string $identifier The identifier of the string to search for
6213 * @param string $component The module the string is associated with
6214 * @return boot true if exists
6216 public function string_exists($identifier, $component) {
6217 $identifier = clean_param($identifier, PARAM_STRINGID
);
6218 if (empty($identifier)) {
6221 // simple old style hack ;)
6222 $str = get_string($identifier, $component);
6223 return (strpos($str, '[[') === false);
6227 * Get String returns a requested string
6229 * @param string $identifier The identifier of the string to search for
6230 * @param string $component The module the string is associated with
6231 * @param string|object|array $a An object, string or number that can be used
6232 * within translation strings
6233 * @param string $lang moodle translation language, NULL means use current
6234 * @return string The String !
6236 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6238 $component = 'moodle';
6241 if ($lang === NULL) {
6242 $lang = current_language();
6247 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
6248 if (file_exists("$this->installroot/$lang/langconfig.php")) {
6250 include("$this->installroot/$lang/langconfig.php");
6251 if (isset($string['parentlanguage'])) {
6252 $parent = $string['parentlanguage'];
6258 // include en string first
6259 if (!file_exists("$this->installroot/en/$component.php")) {
6260 return "[[$identifier]]";
6263 include("$this->installroot/en/$component.php");
6265 // now override en with parent if defined
6266 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
6267 include("$this->installroot/$parent/$component.php");
6270 // finally override with requested language
6271 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
6272 include("$this->installroot/$lang/$component.php");
6275 if (!isset($string[$identifier])) {
6276 return "[[$identifier]]";
6279 $string = $string[$identifier];
6282 if (is_object($a) or is_array($a)) {
6286 foreach ($a as $key=>$value) {
6288 // we do not support numeric keys - sorry!
6291 $search[] = '{$a->'.$key.'}';
6292 $replace[] = (string)$value;
6295 $string = str_replace($search, $replace, $string);
6298 $string = str_replace('{$a}', (string)$a, $string);
6306 * Returns a localised list of all country names, sorted by country keys.
6308 * @param bool $returnall return all or just enabled
6309 * @param string $lang moodle translation language, NULL means use current
6310 * @return array two-letter country code => translated name.
6312 public function get_list_of_countries($returnall = false, $lang = NULL) {
6313 //not used in installer
6318 * Returns a localised list of languages, sorted by code keys.
6320 * @param string $lang moodle translation language, NULL means use current
6321 * @param string $standard language list standard
6322 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6323 * @return array language code => translated name
6325 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
6326 //not used in installer
6331 * Does the translation exist?
6333 * @param string $lang moodle translation language code
6334 * @param bool include also disabled translations?
6335 * @return boot true if exists
6337 public function translation_exists($lang, $includeall = true) {
6338 return file_exists($this->installroot
.'/'.$lang.'/langconfig.php');
6342 * Returns localised list of installed translations
6343 * @param bool $returnall return all or just enabled
6344 * @return array moodle translation code => localised translation name
6346 public function get_list_of_translations($returnall = false) {
6347 // return all is ignored here - we need to know all langs in installer
6348 $languages = array();
6349 // Get raw list of lang directories
6350 $langdirs = get_list_of_plugins('install/lang');
6352 // Get some info from each lang
6353 foreach ($langdirs as $lang) {
6354 if (file_exists($this->installroot
.'/'.$lang.'/langconfig.php')) {
6356 include($this->installroot
.'/'.$lang.'/langconfig.php');
6357 if (!empty($string['thislanguage'])) {
6358 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
6367 * Returns localised list of currencies.
6369 * @param string $lang moodle translation language, NULL means use current
6370 * @return array currency code => localised currency name
6372 public function get_list_of_currencies($lang = NULL) {
6373 // not used in installer
6378 * This implementation does not use any caches
6380 public function reset_caches() {}
6385 * Returns a localized string.
6387 * Returns the translated string specified by $identifier as
6388 * for $module. Uses the same format files as STphp.
6389 * $a is an object, string or number that can be used
6390 * within translation strings
6392 * eg 'hello {$a->firstname} {$a->lastname}'
6395 * If you would like to directly echo the localized string use
6396 * the function {@link print_string()}
6398 * Example usage of this function involves finding the string you would
6399 * like a local equivalent of and using its identifier and module information
6400 * to retrieve it.<br/>
6401 * If you open moodle/lang/en/moodle.php and look near line 278
6402 * you will find a string to prompt a user for their word for 'course'
6404 * $string['course'] = 'Course';
6406 * So if you want to display the string 'Course'
6407 * in any language that supports it on your site
6408 * you just need to use the identifier 'course'
6410 * $mystring = '<strong>'. get_string('course') .'</strong>';
6413 * If the string you want is in another file you'd take a slightly
6414 * different approach. Looking in moodle/lang/en/calendar.php you find
6417 * $string['typecourse'] = 'Course event';
6419 * If you want to display the string "Course event" in any language
6420 * supported you would use the identifier 'typecourse' and the module 'calendar'
6421 * (because it is in the file calendar.php):
6423 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
6426 * As a last resort, should the identifier fail to map to a string
6427 * the returned string will be [[ $identifier ]]
6429 * @param string $identifier The key identifier for the localized string
6430 * @param string $component The module where the key identifier is stored,
6431 * usually expressed as the filename in the language pack without the
6432 * .php on the end but can also be written as mod/forum or grade/export/xls.
6433 * If none is specified then moodle.php is used.
6434 * @param string|object|array $a An object, string or number that can be used
6435 * within translation strings
6436 * @return string The localized string.
6438 function get_string($identifier, $component = '', $a = NULL) {
6440 $identifier = clean_param($identifier, PARAM_STRINGID
);
6441 if (empty($identifier)) {
6442 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please fix your get_string() call and string definition');
6445 if (func_num_args() > 3) {
6446 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
6449 if (strpos($component, '/') !== false) {
6450 debugging('The module name you passed to get_string is the deprecated format ' .
6451 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER
);
6452 $componentpath = explode('/', $component);
6454 switch ($componentpath[0]) {
6456 $component = $componentpath[1];
6460 $component = 'block_'.$componentpath[1];
6463 $component = 'enrol_'.$componentpath[1];
6466 $component = 'format_'.$componentpath[1];
6469 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
6474 return get_string_manager()->get_string($identifier, $component, $a);
6478 * Converts an array of strings to their localized value.
6480 * @param array $array An array of strings
6481 * @param string $module The language module that these strings can be found in.
6482 * @return array and array of translated strings.
6484 function get_strings($array, $component = '') {
6485 $string = new stdClass
;
6486 foreach ($array as $item) {
6487 $string->$item = get_string($item, $component);
6493 * Prints out a translated string.
6495 * Prints out a translated string using the return value from the {@link get_string()} function.
6497 * Example usage of this function when the string is in the moodle.php file:<br/>
6500 * print_string('course');
6504 * Example usage of this function when the string is not in the moodle.php file:<br/>
6507 * print_string('typecourse', 'calendar');
6511 * @param string $identifier The key identifier for the localized string
6512 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
6513 * @param mixed $a An object, string or number that can be used within translation strings
6515 function print_string($identifier, $component = '', $a = NULL) {
6516 echo get_string($identifier, $component, $a);
6520 * Returns a list of charset codes
6522 * Returns a list of charset codes. It's hardcoded, so they should be added manually
6523 * (checking that such charset is supported by the texlib library!)
6525 * @return array And associative array with contents in the form of charset => charset
6527 function get_list_of_charsets() {
6530 'EUC-JP' => 'EUC-JP',
6531 'ISO-2022-JP'=> 'ISO-2022-JP',
6532 'ISO-8859-1' => 'ISO-8859-1',
6533 'SHIFT-JIS' => 'SHIFT-JIS',
6534 'GB2312' => 'GB2312',
6535 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
6536 'UTF-8' => 'UTF-8');
6544 * Returns a list of valid and compatible themes
6549 function get_list_of_themes() {
6554 if (!empty($CFG->themelist
)) { // use admin's list of themes
6555 $themelist = explode(',', $CFG->themelist
);
6557 $themelist = array_keys(get_plugin_list("theme"));
6560 foreach ($themelist as $key => $themename) {
6561 $theme = theme_config
::load($themename);
6562 $themes[$themename] = $theme;
6570 * Returns a list of timezones in the current language
6576 function get_list_of_timezones() {
6581 if (!empty($timezones)) { // This function has been called recently
6585 $timezones = array();
6587 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
6588 foreach($rawtimezones as $timezone) {
6589 if (!empty($timezone->name
)) {
6590 if (get_string_manager()->string_exists(strtolower($timezone->name
), 'timezones')) {
6591 $timezones[$timezone->name
] = get_string(strtolower($timezone->name
), 'timezones');
6593 $timezones[$timezone->name
] = $timezone->name
;
6595 if (substr($timezones[$timezone->name
], 0, 1) == '[') { // No translation found
6596 $timezones[$timezone->name
] = $timezone->name
;
6604 for ($i = -13; $i <= 13; $i +
= .5) {
6607 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
6608 } else if ($i > 0) {
6609 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
6611 $timezones[sprintf("%.1f", $i)] = $tzstring;
6619 * Factory function for emoticon_manager
6621 * @return emoticon_manager singleton
6623 function get_emoticon_manager() {
6624 static $singleton = null;
6626 if (is_null($singleton)) {
6627 $singleton = new emoticon_manager();
6634 * Provides core support for plugins that have to deal with
6635 * emoticons (like HTML editor or emoticon filter).
6637 * Whenever this manager mentiones 'emoticon object', the following data
6638 * structure is expected: stdClass with properties text, imagename, imagecomponent,
6639 * altidentifier and altcomponent
6641 * @see admin_setting_emoticons
6643 class emoticon_manager
{
6646 * Returns the currently enabled emoticons
6648 * @return array of emoticon objects
6650 public function get_emoticons() {
6653 if (empty($CFG->emoticons
)) {
6657 $emoticons = $this->decode_stored_config($CFG->emoticons
);
6659 if (!is_array($emoticons)) {
6660 // something is wrong with the format of stored setting
6661 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL
);
6669 * Converts emoticon object into renderable pix_emoticon object
6671 * @param stdClass $emoticon emoticon object
6672 * @param array $attributes explicit HTML attributes to set
6673 * @return pix_emoticon
6675 public function prepare_renderable_emoticon(stdClass
$emoticon, array $attributes = array()) {
6676 $stringmanager = get_string_manager();
6677 if ($stringmanager->string_exists($emoticon->altidentifier
, $emoticon->altcomponent
)) {
6678 $alt = get_string($emoticon->altidentifier
, $emoticon->altcomponent
);
6680 $alt = s($emoticon->text
);
6682 return new pix_emoticon($emoticon->imagename
, $alt, $emoticon->imagecomponent
, $attributes);
6686 * Encodes the array of emoticon objects into a string storable in config table
6688 * @see self::decode_stored_config()
6689 * @param array $emoticons array of emtocion objects
6692 public function encode_stored_config(array $emoticons) {
6693 return json_encode($emoticons);
6697 * Decodes the string into an array of emoticon objects
6699 * @see self::encode_stored_config()
6700 * @param string $encoded
6701 * @return string|null
6703 public function decode_stored_config($encoded) {
6704 $decoded = json_decode($encoded);
6705 if (!is_array($decoded)) {
6712 * Returns default set of emoticons supported by Moodle
6714 * @return array of sdtClasses
6716 public function default_emoticons() {
6718 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
6719 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
6720 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
6721 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
6722 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
6723 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
6724 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
6725 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
6726 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
6727 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
6728 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
6729 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
6730 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
6731 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
6732 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
6733 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
6734 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
6735 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
6736 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
6737 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
6738 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
6739 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
6740 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
6741 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
6742 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
6743 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
6744 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
6745 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
6746 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
6747 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
6752 * Helper method preparing the stdClass with the emoticon properties
6754 * @param string|array $text or array of strings
6755 * @param string $imagename to be used by {@see pix_emoticon}
6756 * @param string $altidentifier alternative string identifier, null for no alt
6757 * @param array $altcomponent where the alternative string is defined
6758 * @param string $imagecomponent to be used by {@see pix_emoticon}
6761 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
6762 return (object)array(
6764 'imagename' => $imagename,
6765 'imagecomponent' => $imagecomponent,
6766 'altidentifier' => $altidentifier,
6767 'altcomponent' => $altcomponent,
6772 /// ENCRYPTION ////////////////////////////////////////////////
6777 * @todo Finish documenting this function
6779 * @param string $data Data to encrypt
6780 * @return string The now encrypted data
6782 function rc4encrypt($data) {
6783 $password = 'nfgjeingjk';
6784 return endecrypt($password, $data, '');
6790 * @todo Finish documenting this function
6792 * @param string $data Data to decrypt
6793 * @return string The now decrypted data
6795 function rc4decrypt($data) {
6796 $password = 'nfgjeingjk';
6797 return endecrypt($password, $data, 'de');
6801 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6803 * @todo Finish documenting this function
6805 * @param string $pwd The password to use when encrypting or decrypting
6806 * @param string $data The data to be decrypted/encrypted
6807 * @param string $case Either 'de' for decrypt or '' for encrypt
6810 function endecrypt ($pwd, $data, $case) {
6812 if ($case == 'de') {
6813 $data = urldecode($data);
6821 $pwd_length = strlen($pwd);
6823 for ($i = 0; $i <= 255; $i++
) {
6824 $key[$i] = ord(substr($pwd, ($i %
$pwd_length), 1));
6830 for ($i = 0; $i <= 255; $i++
) {
6831 $x = ($x +
$box[$i] +
$key[$i]) %
256;
6832 $temp_swap = $box[$i];
6833 $box[$i] = $box[$x];
6834 $box[$x] = $temp_swap;
6846 for ($i = 0; $i < strlen($data); $i++
) {
6847 $a = ($a +
1) %
256;
6848 $j = ($j +
$box[$a]) %
256;
6850 $box[$a] = $box[$j];
6852 $k = $box[(($box[$a] +
$box[$j]) %
256)];
6853 $cipherby = ord(substr($data, $i, 1)) ^
$k;
6854 $cipher .= chr($cipherby);
6857 if ($case == 'de') {
6858 $cipher = urldecode(urlencode($cipher));
6860 $cipher = urlencode($cipher);
6866 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6869 * Returns the exact absolute path to plugin directory.
6871 * @param string $plugintype type of plugin
6872 * @param string $name name of the plugin
6873 * @return string full path to plugin directory; NULL if not found
6875 function get_plugin_directory($plugintype, $name) {
6878 if ($plugintype === '') {
6879 $plugintype = 'mod';
6882 $types = get_plugin_types(true);
6883 if (!array_key_exists($plugintype, $types)) {
6886 $name = clean_param($name, PARAM_SAFEDIR
); // just in case ;-)
6888 if (!empty($CFG->themedir
) and $plugintype === 'theme') {
6889 if (!is_dir($types['theme'] . '/' . $name)) {
6890 // ok, so the theme is supposed to be in the $CFG->themedir
6891 return $CFG->themedir
. '/' . $name;
6895 return $types[$plugintype].'/'.$name;
6899 * Return exact absolute path to a plugin directory,
6900 * this method support "simpletest_" prefix designed for unit testing.
6902 * @param string $component name such as 'moodle', 'mod_forum' or special simpletest value
6903 * @return string full path to component directory; NULL if not found
6905 function get_component_directory($component) {
6908 $simpletest = false;
6909 if (strpos($component, 'simpletest_') === 0) {
6910 $subdir = substr($component, strlen('simpletest_'));
6911 //TODO: this looks borked, where is it used actually?
6915 list($type, $plugin) = normalize_component($component);
6917 if ($type === 'core') {
6918 if ($plugin === NULL ) {
6919 $path = $CFG->libdir
;
6921 $subsystems = get_core_subsystems();
6922 if (isset($subsystems[$plugin])) {
6923 $path = $CFG->dirroot
.'/'.$subsystems[$plugin];
6930 $path = get_plugin_directory($type, $plugin);
6937 * Normalize the component name using the "frankenstyle" names.
6938 * @param string $component
6939 * @return array $type+$plugin elements
6941 function normalize_component($component) {
6942 if ($component === 'moodle' or $component === 'core') {
6946 } else if (strpos($component, '_') === false) {
6947 $subsystems = get_core_subsystems();
6948 if (array_key_exists($component, $subsystems)) {
6950 $plugin = $component;
6952 // everything else is a module
6954 $plugin = $component;
6958 list($type, $plugin) = explode('_', $component, 2);
6959 $plugintypes = get_plugin_types(false);
6960 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
6962 $plugin = $component;
6966 return array($type, $plugin);
6970 * List all core subsystems and their location
6972 * This is a whitelist of components that are part of the core and their
6973 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
6974 * plugin is not listed here and it does not have proper plugintype prefix,
6975 * then it is considered as course activity module.
6977 * The location is dirroot relative path. NULL means there is no special
6978 * directory for this subsystem. If the location is set, the subsystem's
6979 * renderer.php is expected to be there.
6981 * @return array of (string)name => (string|null)location
6983 function get_core_subsystems() {
6986 static $info = null;
6991 'admin' => $CFG->admin
,
6993 'backup' => 'backup/util/ui',
6994 'block' => 'blocks',
6996 'bulkusers' => NULL,
6997 'calendar' => 'calendar',
6998 'cohort' => 'cohort',
6999 'condition' => NULL,
7000 'completion' => NULL,
7001 'countries' => NULL,
7002 'course' => 'course',
7003 'currencies' => NULL,
7004 'dbtransfer' => NULL,
7007 'editor' => 'lib/editor',
7008 'edufields' => NULL,
7011 'filepicker' => NULL,
7014 'flashdetect' => NULL,
7016 'form' => 'lib/form',
7017 'grades' => 'grade',
7024 'langconfig' => NULL,
7026 'message' => 'message',
7027 'mimetypes' => NULL,
7029 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7034 'plagiarism' => 'plagiarism',
7035 'portfolio' => 'portfolio',
7036 'publish' => 'course/publish',
7037 'question' => 'question',
7038 'rating' => 'rating',
7039 'register' => 'admin/registration',
7040 'repository' => 'repository',
7042 'role' => $CFG->admin
.'/role',
7043 'simpletest' => NULL,
7044 'search' => 'search',
7047 'timezones' => NULL,
7050 'webservice' => 'webservice',
7059 * Lists all plugin types
7060 * @param bool $fullpaths false means relative paths from dirroot
7061 * @return array Array of strings - name=>location
7063 function get_plugin_types($fullpaths=true) {
7066 static $info = null;
7067 static $fullinfo = null;
7070 $info = array('mod' => 'mod',
7073 'message' => 'message/output',
7074 'block' => 'blocks',
7075 'filter' => 'filter',
7076 'editor' => 'lib/editor',
7077 'format' => 'course/format',
7078 'profilefield' => 'user/profile/field',
7079 'report' => $CFG->admin
.'/report',
7080 'coursereport' => 'course/report', // must be after system reports
7081 'gradeexport' => 'grade/export',
7082 'gradeimport' => 'grade/import',
7083 'gradereport' => 'grade/report',
7084 'mnetservice' => 'mnet/service',
7085 'webservice' => 'webservice',
7086 'repository' => 'repository',
7087 'portfolio' => 'portfolio',
7088 'qtype' => 'question/type',
7089 'qformat' => 'question/format',
7090 'plagiarism' => 'plagiarism',
7091 'theme' => 'theme'); // this is a bit hacky, themes may be in $CFG->themedir too
7093 $mods = get_plugin_list('mod');
7094 foreach ($mods as $mod => $moddir) {
7095 if (file_exists("$moddir/db/subplugins.php")) {
7096 $subplugins = array();
7097 include("$moddir/db/subplugins.php");
7098 foreach ($subplugins as $subtype=>$dir) {
7099 $info[$subtype] = $dir;
7104 // local is always last!
7105 $info['local'] = 'local';
7107 $fullinfo = array();
7108 foreach ($info as $type => $dir) {
7109 $fullinfo[$type] = $CFG->dirroot
.'/'.$dir;
7113 return ($fullpaths ?
$fullinfo : $info);
7117 * Simplified version of get_list_of_plugins()
7118 * @param string $plugintype type of plugin
7119 * @return array name=>fulllocation pairs of plugins of given type
7121 function get_plugin_list($plugintype) {
7124 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
7125 if ($plugintype == 'auth') {
7126 // Historically we have had an auth plugin called 'db', so allow a special case.
7127 $key = array_search('db', $ignored);
7128 if ($key !== false) {
7129 unset($ignored[$key]);
7133 if ($plugintype === '') {
7134 $plugintype = 'mod';
7137 $fulldirs = array();
7139 if ($plugintype === 'mod') {
7140 // mod is an exception because we have to call this function from get_plugin_types()
7141 $fulldirs[] = $CFG->dirroot
.'/mod';
7143 } else if ($plugintype === 'theme') {
7144 $fulldirs[] = $CFG->dirroot
.'/theme';
7145 // themes are special because they may be stored also in separate directory
7146 if (!empty($CFG->themedir
) and file_exists($CFG->themedir
) and is_dir($CFG->themedir
) ) {
7147 $fulldirs[] = $CFG->themedir
;
7151 $types = get_plugin_types(true);
7152 if (!array_key_exists($plugintype, $types)) {
7155 $fulldir = $types[$plugintype];
7156 if (!file_exists($fulldir)) {
7159 $fulldirs[] = $fulldir;
7164 foreach ($fulldirs as $fulldir) {
7165 if (!is_dir($fulldir)) {
7168 $items = new DirectoryIterator($fulldir);
7169 foreach ($items as $item) {
7170 if ($item->isDot() or !$item->isDir()) {
7173 $pluginname = $item->getFilename();
7174 if (in_array($pluginname, $ignored)) {
7177 if ($pluginname !== clean_param($pluginname, PARAM_SAFEDIR
)) {
7178 // better ignore plugins with problematic names here
7181 $result[$pluginname] = $fulldir.'/'.$pluginname;
7187 //TODO: implement better sorting once we migrated all plugin names to 'pluginname', ksort does not work for unicode, that is why we have to sort by the dir name, not the strings!
7193 * Gets a list of all plugin API functions for given plugin type, function
7194 * name, and filename.
7195 * @param string $plugintype Plugin type, e.g. 'mod' or 'report'
7196 * @param string $function Name of function after the frankenstyle prefix;
7197 * e.g. if the function is called report_courselist_hook then this value
7199 * @param string $file Name of file that includes function within plugin,
7201 * @return Array of plugin frankenstyle (e.g. 'report_courselist', 'mod_forum')
7202 * to valid, existing plugin function name (e.g. 'report_courselist_hook',
7205 function get_plugin_list_with_function($plugintype, $function, $file='lib.php') {
7206 global $CFG; // mandatory in case it is referenced by include()d PHP script
7209 // Loop through list of plugins with given type
7210 $list = get_plugin_list($plugintype);
7211 foreach($list as $plugin => $dir) {
7212 $path = $dir . '/' . $file;
7213 // If file exists, require it and look for function
7214 if (file_exists($path)) {
7215 include_once($path);
7216 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
7217 if (function_exists($fullfunction)) {
7218 // Function exists with standard name. Store, indexed by
7219 // frankenstyle name of plugin
7220 $result[$plugintype . '_' . $plugin] = $fullfunction;
7221 } else if ($plugintype === 'mod') {
7222 // For modules, we also allow plugin without full frankenstyle
7223 // but just starting with the module name
7224 $shortfunction = $plugin . '_' . $function;
7225 if (function_exists($shortfunction)) {
7226 $result[$plugintype . '_' . $plugin] = $shortfunction;
7235 * Lists plugin-like directories within specified directory
7237 * This function was originally used for standard Moodle plugins, please use
7238 * new get_plugin_list() now.
7240 * This function is used for general directory listing and backwards compatility.
7242 * @param string $directory relative directory from root
7243 * @param string $exclude dir name to exclude from the list (defaults to none)
7244 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
7245 * @return array Sorted array of directory names found under the requested parameters
7247 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
7252 if (empty($basedir)) {
7253 $basedir = $CFG->dirroot
.'/'. $directory;
7256 $basedir = $basedir .'/'. $directory;
7259 if (file_exists($basedir) && filetype($basedir) == 'dir') {
7260 $dirhandle = opendir($basedir);
7261 while (false !== ($dir = readdir($dirhandle))) {
7262 $firstchar = substr($dir, 0, 1);
7263 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
7266 if (filetype($basedir .'/'. $dir) != 'dir') {
7271 closedir($dirhandle);
7281 * invoke plugin's callback functions
7283 * @param string $type Plugin type e.g. 'mod'
7284 * @param string $name Plugin name
7285 * @param string $feature Feature code (FEATURE_xx constant)
7286 * @param string $action Feature's action
7287 * @param string $options parameters of callback function, should be an array
7288 * @param mixed $default default value if callback function hasn't been defined
7291 function plugin_callback($type, $name, $feature, $action, $options = null, $default=null) {
7294 $name = clean_param($name, PARAM_SAFEDIR
);
7295 $function = $name.'_'.$feature.'_'.$action;
7296 $file = get_plugin_directory($type, $name) . '/lib.php';
7298 // Load library and look for function
7299 if (file_exists($file)) {
7300 require_once($file);
7302 if (function_exists($function)) {
7303 // Function exists, so just return function result
7304 $ret = call_user_func_array($function, (array)$options);
7305 if (is_null($ret)) {
7315 * Checks whether a plugin supports a specified feature.
7317 * @param string $type Plugin type e.g. 'mod'
7318 * @param string $name Plugin name e.g. 'forum'
7319 * @param string $feature Feature code (FEATURE_xx constant)
7320 * @param mixed $default default value if feature support unknown
7321 * @return mixed Feature result (false if not supported, null if feature is unknown,
7322 * otherwise usually true but may have other feature-specific value such as array)
7324 function plugin_supports($type, $name, $feature, $default = NULL) {
7327 $name = clean_param($name, PARAM_SAFEDIR
); //bit of extra security
7331 if ($type === 'mod') {
7332 // we need this special case because we support subplugins in modules,
7333 // otherwise it would end up in infinite loop
7334 if ($name === 'NEWMODULE') {
7335 //somebody forgot to rename the module template
7338 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
7339 include_once("$CFG->dirroot/mod/$name/lib.php");
7340 $function = $type.'_'.$name.'_supports';
7341 if (!function_exists($function)) {
7342 // legacy non-frankenstyle function name
7343 $function = $name.'_supports';
7348 if (!$path = get_plugin_directory($type, $name)) {
7349 // non existent plugin type
7352 if (file_exists("$path/lib.php")) {
7353 include_once("$path/lib.php");
7354 $function = $type.'_'.$name.'_supports';
7358 if ($function and function_exists($function)) {
7359 $supports = $function($feature);
7360 if (is_null($supports)) {
7361 // plugin does not know - use default
7368 //plugin does not care, so use default
7373 * Returns true if the current version of PHP is greater that the specified one.
7375 * @todo Check PHP version being required here is it too low?
7377 * @param string $version The version of php being tested.
7380 function check_php_version($version='5.2.4') {
7381 return (version_compare(phpversion(), $version) >= 0);
7385 * Checks to see if is the browser operating system matches the specified
7388 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
7391 * @param string $brand The operating system identifier being tested
7392 * @return bool true if the given brand below to the detected operating system
7394 function check_browser_operating_system($brand) {
7395 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7399 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
7407 * Checks to see if is a browser matches the specified
7408 * brand and is equal or better version.
7411 * @param string $brand The browser identifier being tested
7412 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
7413 * @return bool true if the given version is below that of the detected browser
7415 function check_browser_version($brand, $version = null) {
7416 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7420 $agent = $_SERVER['HTTP_USER_AGENT'];
7424 case 'Camino': /// OSX browser using Gecke engine
7425 if (strpos($agent, 'Camino') === false) {
7428 if (empty($version)) {
7429 return true; // no version specified
7431 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
7432 if (version_compare($match[1], $version) >= 0) {
7439 case 'Firefox': /// Mozilla Firefox browsers
7440 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
7443 if (empty($version)) {
7444 return true; // no version specified
7446 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
7447 if (version_compare($match[2], $version) >= 0) {
7454 case 'Gecko': /// Gecko based browsers
7455 if (empty($version) and substr_count($agent, 'Camino')) {
7456 // MacOS X Camino support
7457 $version = 20041110;
7460 // the proper string - Gecko/CCYYMMDD Vendor/Version
7461 // Faster version and work-a-round No IDN problem.
7462 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
7463 if ($match[1] > $version) {
7470 case 'MSIE': /// Internet Explorer
7471 if (strpos($agent, 'Opera') !== false) { // Reject Opera
7474 // in case of IE we have to deal with BC of the version parameter
7475 if (is_null($version)) {
7476 $version = 5.5; // anything older is not considered a browser at all!
7479 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
7480 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
7481 if (version_compare($match[1], $version) >= 0) {
7488 case 'Opera': /// Opera
7489 if (strpos($agent, 'Opera') === false) {
7492 if (empty($version)) {
7493 return true; // no version specified
7495 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
7496 if (version_compare($match[1], $version) >= 0) {
7503 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
7504 if (strpos($agent, 'AppleWebKit') === false) {
7507 if (empty($version)) {
7508 return true; // no version specified
7510 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7511 if (version_compare($match[1], $version) >= 0) {
7518 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
7519 if (strpos($agent, 'AppleWebKit') === false) {
7522 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
7523 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
7526 if (strpos($agent, 'Shiira')) { // Reject Shiira
7529 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
7532 if (strpos($agent, 'Android')) { // Reject Androids too
7535 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
7536 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
7539 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
7543 if (empty($version)) {
7544 return true; // no version specified
7546 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7547 if (version_compare($match[1], $version) >= 0) {
7555 if (strpos($agent, 'Chrome') === false) {
7558 if (empty($version)) {
7559 return true; // no version specified
7561 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
7562 if (version_compare($match[1], $version) >= 0) {
7569 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
7570 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
7573 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
7576 if (empty($version)) {
7577 return true; // no version specified
7579 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7580 if (version_compare($match[1], $version) >= 0) {
7587 case 'WebKit Android': /// WebKit browser on Android
7588 if (strpos($agent, 'Linux; U; Android') === false) {
7591 if (empty($version)) {
7592 return true; // no version specified
7594 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7595 if (version_compare($match[1], $version) >= 0) {
7607 * Returns one or several CSS class names that match the user's browser. These can be put
7608 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
7610 * @return array An array of browser version classes
7612 function get_browser_version_classes() {
7615 if (check_browser_version("MSIE", "0")) {
7617 if (check_browser_version("MSIE", 9)) {
7619 } else if (check_browser_version("MSIE", 8)) {
7621 } elseif (check_browser_version("MSIE", 7)) {
7623 } elseif (check_browser_version("MSIE", 6)) {
7627 } else if (check_browser_version("Firefox") ||
check_browser_version("Gecko") ||
check_browser_version("Camino")) {
7628 $classes[] = 'gecko';
7629 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
7630 $classes[] = "gecko{$matches[1]}{$matches[2]}";
7633 } else if (check_browser_version("WebKit")) {
7634 $classes[] = 'safari';
7635 if (check_browser_version("Safari iOS")) {
7638 } else if (check_browser_version("WebKit Android")) {
7639 $classes[] = 'android';
7642 } else if (check_browser_version("Opera")) {
7643 $classes[] = 'opera';
7651 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
7653 * @return bool True for yes, false for no
7655 function can_use_rotated_text() {
7657 return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader
;;
7661 * Hack to find out the GD version by parsing phpinfo output
7663 * @return int GD version (1, 2, or 0)
7665 function check_gd_version() {
7668 if (function_exists('gd_info')){
7669 $gd_info = gd_info();
7670 if (substr_count($gd_info['GD Version'], '2.')) {
7672 } else if (substr_count($gd_info['GD Version'], '1.')) {
7678 phpinfo(INFO_MODULES
);
7679 $phpinfo = ob_get_contents();
7682 $phpinfo = explode("\n", $phpinfo);
7685 foreach ($phpinfo as $text) {
7686 $parts = explode('</td>', $text);
7687 foreach ($parts as $key => $val) {
7688 $parts[$key] = trim(strip_tags($val));
7690 if ($parts[0] == 'GD Version') {
7691 if (substr_count($parts[1], '2.0')) {
7694 $gdversion = intval($parts[1]);
7699 return $gdversion; // 1, 2 or 0
7703 * Determine if moodle installation requires update
7705 * Checks version numbers of main code and all modules to see
7706 * if there are any mismatches
7712 function moodle_needs_upgrading() {
7713 global $CFG, $DB, $OUTPUT;
7715 if (empty($CFG->version
)) {
7719 // main versio nfirst
7721 include($CFG->dirroot
.'/version.php'); // defines $version and upgrades
7722 if ($version > $CFG->version
) {
7727 $mods = get_plugin_list('mod');
7728 $installed = $DB->get_records('modules', array(), '', 'name, version');
7729 foreach ($mods as $mod => $fullmod) {
7730 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
7733 $module = new stdClass();
7734 if (!is_readable($fullmod.'/version.php')) {
7737 include($fullmod.'/version.php'); // defines $module with version etc
7738 if (empty($installed[$mod])) {
7740 } else if ($module->version
> $installed[$mod]->version
) {
7747 $blocks = get_plugin_list('block');
7748 $installed = $DB->get_records('block', array(), '', 'name, version');
7749 require_once($CFG->dirroot
.'/blocks/moodleblock.class.php');
7750 foreach ($blocks as $blockname=>$fullblock) {
7751 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
7754 if (!is_readable($fullblock.'/version.php')) {
7757 $plugin = new stdClass();
7758 $plugin->version
= NULL;
7759 include($fullblock.'/version.php');
7760 if (empty($installed[$blockname])) {
7762 } else if ($plugin->version
> $installed[$blockname]->version
) {
7768 // now the rest of plugins
7769 $plugintypes = get_plugin_types();
7770 unset($plugintypes['mod']);
7771 unset($plugintypes['block']);
7772 foreach ($plugintypes as $type=>$unused) {
7773 $plugs = get_plugin_list($type);
7774 foreach ($plugs as $plug=>$fullplug) {
7775 $component = $type.'_'.$plug;
7776 if (!is_readable($fullplug.'/version.php')) {
7779 $plugin = new stdClass();
7780 include($fullplug.'/version.php'); // defines $plugin with version etc
7781 $installedversion = get_config($component, 'version');
7782 if (empty($installedversion)) { // new installation
7784 } else if ($installedversion < $plugin->version
) { // upgrade
7794 * Sets maximum expected time needed for upgrade task.
7795 * Please always make sure that upgrade will not run longer!
7797 * The script may be automatically aborted if upgrade times out.
7800 * @param int $max_execution_time in seconds (can not be less than 60 s)
7802 function upgrade_set_timeout($max_execution_time=300) {
7805 if (!isset($CFG->upgraderunning
) or $CFG->upgraderunning
< time()) {
7806 $upgraderunning = get_config(null, 'upgraderunning');
7808 $upgraderunning = $CFG->upgraderunning
;
7811 if (!$upgraderunning) {
7812 // upgrade not running or aborted
7813 print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
7817 if ($max_execution_time < 60) {
7818 // protection against 0 here
7819 $max_execution_time = 60;
7822 $expected_end = time() +
$max_execution_time;
7824 if ($expected_end < $upgraderunning +
10 and $expected_end > $upgraderunning - 10) {
7825 // no need to store new end, it is nearly the same ;-)
7829 set_time_limit($max_execution_time);
7830 set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
7833 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
7836 * Notify admin users or admin user of any failed logins (since last notification).
7838 * Note that this function must be only executed from the cron script
7839 * It uses the cache_flags system to store temporary records, deleting them
7840 * by name before finishing
7846 function notify_login_failures() {
7847 global $CFG, $DB, $OUTPUT;
7849 $recip = get_users_from_config($CFG->notifyloginfailures
, 'moodle/site:config');
7851 if (empty($CFG->lastnotifyfailure
)) {
7852 $CFG->lastnotifyfailure
=0;
7855 // we need to deal with the threshold stuff first.
7856 if (empty($CFG->notifyloginthreshold
)) {
7857 $CFG->notifyloginthreshold
= 10; // default to something sensible.
7860 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
7861 /// and insert them into the cache_flags temp table
7862 $sql = "SELECT ip, COUNT(*)
7864 WHERE module = 'login' AND action = 'error'
7867 HAVING COUNT(*) >= ?";
7868 $params = array($CFG->lastnotifyfailure
, $CFG->notifyloginthreshold
);
7869 $rs = $DB->get_recordset_sql($sql, $params);
7870 foreach ($rs as $iprec) {
7871 if (!empty($iprec->ip
)) {
7872 set_cache_flag('login_failure_by_ip', $iprec->ip
, '1', 0);
7877 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
7878 /// and insert them into the cache_flags temp table
7879 $sql = "SELECT info, count(*)
7881 WHERE module = 'login' AND action = 'error'
7884 HAVING count(*) >= ?";
7885 $params = array($CFG->lastnotifyfailure
, $CFG->notifyloginthreshold
);
7886 $rs = $DB->get_recordset_sql($sql, $params);
7887 foreach ($rs as $inforec) {
7888 if (!empty($inforec->info
)) {
7889 set_cache_flag('login_failure_by_info', $inforec->info
, '1', 0);
7894 /// Now, select all the login error logged records belonging to the ips and infos
7895 /// since lastnotifyfailure, that we have stored in the cache_flags table
7896 $sql = "SELECT l.*, u.firstname, u.lastname
7898 JOIN {cache_flags} cf ON l.ip = cf.name
7899 LEFT JOIN {user} u ON l.userid = u.id
7900 WHERE l.module = 'login' AND l.action = 'error'
7902 AND cf.flagtype = 'login_failure_by_ip'
7904 SELECT l.*, u.firstname, u.lastname
7906 JOIN {cache_flags} cf ON l.info = cf.name
7907 LEFT JOIN {user} u ON l.userid = u.id
7908 WHERE l.module = 'login' AND l.action = 'error'
7910 AND cf.flagtype = 'login_failure_by_info'
7911 ORDER BY time DESC";
7912 $params = array($CFG->lastnotifyfailure
, $CFG->lastnotifyfailure
);
7914 /// Init some variables
7917 /// Iterate over the logs recordset
7918 $rs = $DB->get_recordset_sql($sql, $params);
7919 foreach ($rs as $log) {
7920 $log->time
= userdate($log->time
);
7921 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
7926 /// If we haven't run in the last hour and
7927 /// we have something useful to report and we
7928 /// are actually supposed to be reporting to somebody
7929 if ((time() - HOURSECS
) > $CFG->lastnotifyfailure
&& $count > 0 && is_array($recip) && count($recip) > 0) {
7931 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname
));
7932 /// Calculate the complete body of notification (start + messages + end)
7933 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot
) .
7934 (($CFG->lastnotifyfailure
!= 0) ?
'('.userdate($CFG->lastnotifyfailure
).')' : '')."\n\n" .
7936 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot
)."\n\n";
7938 /// For each destination, send mail
7939 mtrace('Emailing admins about '. $count .' failed login attempts');
7940 foreach ($recip as $admin) {
7941 //emailing the admins directly rather than putting these through the messaging system
7942 email_to_user($admin,get_admin(), $subject, $body);
7945 /// Update lastnotifyfailure with current time
7946 set_config('lastnotifyfailure', time());
7949 /// Finally, delete all the temp records we have created in cache_flags
7950 $DB->delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
7954 * Sets the system locale
7956 * @todo Finish documenting this function
7959 * @param string $locale Can be used to force a locale
7961 function moodle_setlocale($locale='') {
7964 static $currentlocale = ''; // last locale caching
7966 $oldlocale = $currentlocale;
7968 /// Fetch the correct locale based on ostype
7969 if ($CFG->ostype
== 'WINDOWS') {
7970 $stringtofetch = 'localewin';
7972 $stringtofetch = 'locale';
7975 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
7976 if (!empty($locale)) {
7977 $currentlocale = $locale;
7978 } else if (!empty($CFG->locale
)) { // override locale for all language packs
7979 $currentlocale = $CFG->locale
;
7981 $currentlocale = get_string($stringtofetch, 'langconfig');
7984 /// do nothing if locale already set up
7985 if ($oldlocale == $currentlocale) {
7989 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
7990 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
7991 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
7993 /// Get current values
7994 $monetary= setlocale (LC_MONETARY
, 0);
7995 $numeric = setlocale (LC_NUMERIC
, 0);
7996 $ctype = setlocale (LC_CTYPE
, 0);
7997 if ($CFG->ostype
!= 'WINDOWS') {
7998 $messages= setlocale (LC_MESSAGES
, 0);
8000 /// Set locale to all
8001 setlocale (LC_ALL
, $currentlocale);
8003 setlocale (LC_MONETARY
, $monetary);
8004 setlocale (LC_NUMERIC
, $numeric);
8005 if ($CFG->ostype
!= 'WINDOWS') {
8006 setlocale (LC_MESSAGES
, $messages);
8008 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
8009 setlocale (LC_CTYPE
, $ctype);
8014 * Converts string to lowercase using most compatible function available.
8016 * @todo Remove this function when no longer in use
8017 * @deprecated Use textlib->strtolower($text) instead.
8019 * @param string $string The string to convert to all lowercase characters.
8020 * @param string $encoding The encoding on the string.
8023 function moodle_strtolower ($string, $encoding='') {
8025 //If not specified use utf8
8026 if (empty($encoding)) {
8027 $encoding = 'UTF-8';
8030 $textlib = textlib_get_instance();
8032 return $textlib->strtolower($string, $encoding);
8036 * Count words in a string.
8038 * Words are defined as things between whitespace.
8040 * @param string $string The text to be searched for words.
8041 * @return int The count of words in the specified string
8043 function count_words($string) {
8044 $string = strip_tags($string);
8045 return count(preg_split("/\w\b/", $string)) - 1;
8048 /** Count letters in a string.
8050 * Letters are defined as chars not in tags and different from whitespace.
8052 * @param string $string The text to be searched for letters.
8053 * @return int The count of letters in the specified text.
8055 function count_letters($string) {
8056 /// Loading the textlib singleton instance. We are going to need it.
8057 $textlib = textlib_get_instance();
8059 $string = strip_tags($string); // Tags are out now
8060 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
8062 return $textlib->strlen($string);
8066 * Generate and return a random string of the specified length.
8068 * @param int $length The length of the string to be created.
8071 function random_string ($length=15) {
8072 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8073 $pool .= 'abcdefghijklmnopqrstuvwxyz';
8074 $pool .= '0123456789';
8075 $poollen = strlen($pool);
8076 mt_srand ((double) microtime() * 1000000);
8078 for ($i = 0; $i < $length; $i++
) {
8079 $string .= substr($pool, (mt_rand()%
($poollen)), 1);
8085 * Generate a complex random string (useful for md5 salts)
8087 * This function is based on the above {@link random_string()} however it uses a
8088 * larger pool of characters and generates a string between 24 and 32 characters
8090 * @param int $length Optional if set generates a string to exactly this length
8093 function complex_random_string($length=null) {
8094 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
8095 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
8096 $poollen = strlen($pool);
8097 mt_srand ((double) microtime() * 1000000);
8098 if ($length===null) {
8099 $length = floor(rand(24,32));
8102 for ($i = 0; $i < $length; $i++
) {
8103 $string .= $pool[(mt_rand()%
$poollen)];
8109 * Given some text (which may contain HTML) and an ideal length,
8110 * this function truncates the text neatly on a word boundary if possible
8113 * @param string $text - text to be shortened
8114 * @param int $ideal - ideal string length
8115 * @param boolean $exact if false, $text will not be cut mid-word
8116 * @param string $ending The string to append if the passed string is truncated
8117 * @return string $truncate - shortened string
8119 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
8123 // if the plain text is shorter than the maximum length, return the whole text
8124 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
8128 // Splits on HTML tags. Each open/close/empty tag will be the first thing
8129 // and only tag in its 'line'
8130 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER
);
8132 $total_length = strlen($ending);
8135 // This array stores information about open and close tags and their position
8136 // in the truncated string. Each item in the array is an object with fields
8137 // ->open (true if open), ->tag (tag name in lower case), and ->pos
8138 // (byte position in truncated text)
8139 $tagdetails = array();
8141 foreach ($lines as $line_matchings) {
8142 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
8143 if (!empty($line_matchings[1])) {
8144 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
8145 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
8147 // if tag is a closing tag (f.e. </b>)
8148 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
8149 // record closing tag
8150 $tagdetails[] = (object)array('open'=>false,
8151 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8152 // if tag is an opening tag (f.e. <b>)
8153 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
8154 // record opening tag
8155 $tagdetails[] = (object)array('open'=>true,
8156 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8158 // add html-tag to $truncate'd text
8159 $truncate .= $line_matchings[1];
8162 // calculate the length of the plain text part of the line; handle entities as one character
8163 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
8164 if ($total_length+
$content_length > $ideal) {
8165 // the number of characters which are left
8166 $left = $ideal - $total_length;
8167 $entities_length = 0;
8168 // search for html entities
8169 if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE
)) {
8170 // calculate the real length of all entities in the legal range
8171 foreach ($entities[0] as $entity) {
8172 if ($entity[1]+
1-$entities_length <= $left) {
8174 $entities_length +
= strlen($entity[0]);
8176 // no more characters left
8181 $truncate .= substr($line_matchings[2], 0, $left+
$entities_length);
8182 // maximum length is reached, so get off the loop
8185 $truncate .= $line_matchings[2];
8186 $total_length +
= $content_length;
8189 // if the maximum length is reached, get off the loop
8190 if($total_length >= $ideal) {
8195 // if the words shouldn't be cut in the middle...
8197 // ...search the last occurence of a space...
8198 for ($k=strlen($truncate);$k>0;$k--) {
8199 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
8200 if ($char == '.' or $char == ' ') {
8203 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
8204 $breakpos = $k; // can be truncated at any UTF-8
8205 break; // character boundary.
8210 if (isset($breakpos)) {
8211 // ...and cut the text in this position
8212 $truncate = substr($truncate, 0, $breakpos);
8216 // add the defined ending to the text
8217 $truncate .= $ending;
8219 // Now calculate the list of open html tags based on the truncate position
8220 $open_tags = array();
8221 foreach ($tagdetails as $taginfo) {
8222 if(isset($breakpos) && $taginfo->pos
>= $breakpos) {
8223 // Don't include tags after we made the break!
8226 if($taginfo->open
) {
8227 // add tag to the beginning of $open_tags list
8228 array_unshift($open_tags, $taginfo->tag
);
8230 $pos = array_search($taginfo->tag
, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
8231 if ($pos !== false) {
8232 unset($open_tags[$pos]);
8237 // close all unclosed html-tags
8238 foreach ($open_tags as $tag) {
8239 $truncate .= '</' . $tag . '>';
8247 * Given dates in seconds, how many weeks is the date from startdate
8248 * The first week is 1, the second 2 etc ...
8250 * @todo Finish documenting this function
8253 * @param int $startdate Timestamp for the start date
8254 * @param int $thedate Timestamp for the end date
8257 function getweek ($startdate, $thedate) {
8258 if ($thedate < $startdate) { // error
8262 return floor(($thedate - $startdate) / WEEKSECS
) +
1;
8266 * returns a randomly generated password of length $maxlen. inspired by
8268 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
8269 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
8272 * @param int $maxlen The maximum size of the password being generated.
8275 function generate_password($maxlen=10) {
8278 if (empty($CFG->passwordpolicy
)) {
8279 $fillers = PASSWORD_DIGITS
;
8280 $wordlist = file($CFG->wordlist
);
8281 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8282 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8283 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
8284 $password = $word1 . $filler1 . $word2;
8286 $maxlen = !empty($CFG->minpasswordlength
) ?
$CFG->minpasswordlength
: 0;
8287 $digits = $CFG->minpassworddigits
;
8288 $lower = $CFG->minpasswordlower
;
8289 $upper = $CFG->minpasswordupper
;
8290 $nonalphanum = $CFG->minpasswordnonalphanum
;
8291 $additional = $maxlen - ($lower +
$upper +
$digits +
$nonalphanum);
8293 // Make sure we have enough characters to fulfill
8294 // complexity requirements
8295 $passworddigits = PASSWORD_DIGITS
;
8296 while ($digits > strlen($passworddigits)) {
8297 $passworddigits .= PASSWORD_DIGITS
;
8299 $passwordlower = PASSWORD_LOWER
;
8300 while ($lower > strlen($passwordlower)) {
8301 $passwordlower .= PASSWORD_LOWER
;
8303 $passwordupper = PASSWORD_UPPER
;
8304 while ($upper > strlen($passwordupper)) {
8305 $passwordupper .= PASSWORD_UPPER
;
8307 $passwordnonalphanum = PASSWORD_NONALPHANUM
;
8308 while ($nonalphanum > strlen($passwordnonalphanum)) {
8309 $passwordnonalphanum .= PASSWORD_NONALPHANUM
;
8312 // Now mix and shuffle it all
8313 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
8314 substr(str_shuffle ($passwordupper), 0, $upper) .
8315 substr(str_shuffle ($passworddigits), 0, $digits) .
8316 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
8317 substr(str_shuffle ($passwordlower .
8320 $passwordnonalphanum), 0 , $additional));
8323 return substr ($password, 0, $maxlen);
8327 * Given a float, prints it nicely.
8328 * Localized floats must not be used in calculations!
8330 * @param float $float The float to print
8331 * @param int $places The number of decimal places to print.
8332 * @param bool $localized use localized decimal separator
8333 * @return string locale float
8335 function format_float($float, $decimalpoints=1, $localized=true) {
8336 if (is_null($float)) {
8340 return number_format($float, $decimalpoints, get_string('decsep', 'langconfig'), '');
8342 return number_format($float, $decimalpoints, '.', '');
8347 * Converts locale specific floating point/comma number back to standard PHP float value
8348 * Do NOT try to do any math operations before this conversion on any user submitted floats!
8350 * @param string $locale_float locale aware float representation
8353 function unformat_float($locale_float) {
8354 $locale_float = trim($locale_float);
8356 if ($locale_float == '') {
8360 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
8362 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
8366 * Given a simple array, this shuffles it up just like shuffle()
8367 * Unlike PHP's shuffle() this function works on any machine.
8369 * @param array $array The array to be rearranged
8372 function swapshuffle($array) {
8374 srand ((double) microtime() * 10000000);
8375 $last = count($array) - 1;
8376 for ($i=0;$i<=$last;$i++
) {
8377 $from = rand(0,$last);
8379 $array[$i] = $array[$from];
8380 $array[$from] = $curr;
8386 * Like {@link swapshuffle()}, but works on associative arrays
8388 * @param array $array The associative array to be rearranged
8391 function swapshuffle_assoc($array) {
8393 $newarray = array();
8394 $newkeys = swapshuffle(array_keys($array));
8396 foreach ($newkeys as $newkey) {
8397 $newarray[$newkey] = $array[$newkey];
8403 * Given an arbitrary array, and a number of draws,
8404 * this function returns an array with that amount
8405 * of items. The indexes are retained.
8407 * @todo Finish documenting this function
8409 * @param array $array
8413 function draw_rand_array($array, $draws) {
8414 srand ((double) microtime() * 10000000);
8418 $last = count($array);
8420 if ($draws > $last) {
8424 while ($draws > 0) {
8427 $keys = array_keys($array);
8428 $rand = rand(0, $last);
8430 $return[$keys[$rand]] = $array[$keys[$rand]];
8431 unset($array[$keys[$rand]]);
8440 * Calculate the difference between two microtimes
8442 * @param string $a The first Microtime
8443 * @param string $b The second Microtime
8446 function microtime_diff($a, $b) {
8447 list($a_dec, $a_sec) = explode(' ', $a);
8448 list($b_dec, $b_sec) = explode(' ', $b);
8449 return $b_sec - $a_sec +
$b_dec - $a_dec;
8453 * Given a list (eg a,b,c,d,e) this function returns
8454 * an array of 1->a, 2->b, 3->c etc
8456 * @param string $list The string to explode into array bits
8457 * @param string $separator The separator used within the list string
8458 * @return array The now assembled array
8460 function make_menu_from_list($list, $separator=',') {
8462 $array = array_reverse(explode($separator, $list), true);
8463 foreach ($array as $key => $item) {
8464 $outarray[$key+
1] = trim($item);
8470 * Creates an array that represents all the current grades that
8471 * can be chosen using the given grading type.
8474 * are scales, zero is no grade, and positive numbers are maximum
8477 * @todo Finish documenting this function or better deprecated this completely!
8479 * @param int $gradingtype
8482 function make_grades_menu($gradingtype) {
8486 if ($gradingtype < 0) {
8487 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
8488 return make_menu_from_list($scale->scale
);
8490 } else if ($gradingtype > 0) {
8491 for ($i=$gradingtype; $i>=0; $i--) {
8492 $grades[$i] = $i .' / '. $gradingtype;
8500 * This function returns the number of activities
8501 * using scaleid in a courseid
8503 * @todo Finish documenting this function
8507 * @param int $courseid ?
8508 * @param int $scaleid ?
8511 function course_scale_used($courseid, $scaleid) {
8516 if (!empty($scaleid)) {
8517 if ($cms = get_course_mods($courseid)) {
8518 foreach ($cms as $cm) {
8519 //Check cm->name/lib.php exists
8520 if (file_exists($CFG->dirroot
.'/mod/'.$cm->modname
.'/lib.php')) {
8521 include_once($CFG->dirroot
.'/mod/'.$cm->modname
.'/lib.php');
8522 $function_name = $cm->modname
.'_scale_used';
8523 if (function_exists($function_name)) {
8524 if ($function_name($cm->instance
,$scaleid)) {
8532 // check if any course grade item makes use of the scale
8533 $return +
= $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
8535 // check if any outcome in the course makes use of the scale
8536 $return +
= $DB->count_records_sql("SELECT COUNT('x')
8537 FROM {grade_outcomes_courses} goc,
8539 WHERE go.id = goc.outcomeid
8540 AND go.scaleid = ? AND goc.courseid = ?",
8541 array($scaleid, $courseid));
8547 * This function returns the number of activities
8548 * using scaleid in the entire site
8550 * @param int $scaleid
8551 * @param array $courses
8554 function site_scale_used($scaleid, &$courses) {
8557 if (!is_array($courses) ||
count($courses) == 0) {
8558 $courses = get_courses("all",false,"c.id,c.shortname");
8561 if (!empty($scaleid)) {
8562 if (is_array($courses) && count($courses) > 0) {
8563 foreach ($courses as $course) {
8564 $return +
= course_scale_used($course->id
,$scaleid);
8572 * make_unique_id_code
8574 * @todo Finish documenting this function
8577 * @param string $extra Extra string to append to the end of the code
8580 function make_unique_id_code($extra='') {
8582 $hostname = 'unknownhost';
8583 if (!empty($_SERVER['HTTP_HOST'])) {
8584 $hostname = $_SERVER['HTTP_HOST'];
8585 } else if (!empty($_ENV['HTTP_HOST'])) {
8586 $hostname = $_ENV['HTTP_HOST'];
8587 } else if (!empty($_SERVER['SERVER_NAME'])) {
8588 $hostname = $_SERVER['SERVER_NAME'];
8589 } else if (!empty($_ENV['SERVER_NAME'])) {
8590 $hostname = $_ENV['SERVER_NAME'];
8593 $date = gmdate("ymdHis");
8595 $random = random_string(6);
8598 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
8600 return $hostname .'+'. $date .'+'. $random;
8606 * Function to check the passed address is within the passed subnet
8608 * The parameter is a comma separated string of subnet definitions.
8609 * Subnet strings can be in one of three formats:
8610 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
8611 * 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group)
8612 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
8613 * Code for type 1 modified from user posted comments by mediator at
8614 * {@link http://au.php.net/manual/en/function.ip2long.php}
8616 * @param string $addr The address you are checking
8617 * @param string $subnetstr The string of subnet addresses
8620 function address_in_subnet($addr, $subnetstr) {
8622 if ($addr == '0.0.0.0') {
8625 $subnets = explode(',', $subnetstr);
8627 $addr = trim($addr);
8628 $addr = cleanremoteaddr($addr, false); // normalise
8629 if ($addr === null) {
8632 $addrparts = explode(':', $addr);
8634 $ipv6 = strpos($addr, ':');
8636 foreach ($subnets as $subnet) {
8637 $subnet = trim($subnet);
8638 if ($subnet === '') {
8642 if (strpos($subnet, '/') !== false) {
8643 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
8644 list($ip, $mask) = explode('/', $subnet);
8645 $mask = trim($mask);
8646 if (!is_number($mask)) {
8647 continue; // incorect mask number, eh?
8649 $ip = cleanremoteaddr($ip, false); // normalise
8653 if (strpos($ip, ':') !== false) {
8658 if ($mask > 128 or $mask < 0) {
8659 continue; // nonsense
8662 return true; // any address
8665 if ($ip === $addr) {
8670 $ipparts = explode(':', $ip);
8671 $modulo = $mask %
16;
8672 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
8673 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
8674 if (implode(':', $ipnet) === implode(':', $addrnet)) {
8678 $pos = ($mask-$modulo)/16;
8679 $ipnet = hexdec($ipparts[$pos]);
8680 $addrnet = hexdec($addrparts[$pos]);
8681 $mask = 0xffff << (16 - $modulo);
8682 if (($addrnet & $mask) == ($ipnet & $mask)) {
8692 if ($mask > 32 or $mask < 0) {
8693 continue; // nonsense
8699 if ($ip === $addr) {
8704 $mask = 0xffffffff << (32 - $mask);
8705 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
8710 } else if (strpos($subnet, '-') !== false) {
8711 /// 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy ...a range of IP addresses in the last group.
8712 $parts = explode('-', $subnet);
8713 if (count($parts) != 2) {
8717 if (strpos($subnet, ':') !== false) {
8722 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
8723 if ($ipstart === null) {
8726 $ipparts = explode(':', $ipstart);
8727 $start = hexdec(array_pop($ipparts));
8728 $ipparts[] = trim($parts[1]);
8729 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
8730 if ($ipend === null) {
8734 $ipnet = implode(':', $ipparts);
8735 if (strpos($addr, $ipnet) !== 0) {
8738 $ipparts = explode(':', $ipend);
8739 $end = hexdec($ipparts[7]);
8741 $addrend = hexdec($addrparts[7]);
8743 if (($addrend >= $start) and ($addrend <= $end)) {
8752 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
8753 if ($ipstart === null) {
8756 $ipparts = explode('.', $ipstart);
8757 $ipparts[3] = trim($parts[1]);
8758 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
8759 if ($ipend === null) {
8763 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
8769 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
8770 if (strpos($subnet, ':') !== false) {
8775 $parts = explode(':', $subnet);
8776 $count = count($parts);
8777 if ($parts[$count-1] === '') {
8778 unset($parts[$count-1]); // trim trailing :
8780 $subnet = implode('.', $parts);
8782 $isip = cleanremoteaddr($subnet, false); // normalise
8783 if ($isip !== null) {
8784 if ($isip === $addr) {
8788 } else if ($count > 8) {
8791 $zeros = array_fill(0, 8-$count, '0');
8792 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
8793 if (address_in_subnet($addr, $subnet)) {
8802 $parts = explode('.', $subnet);
8803 $count = count($parts);
8804 if ($parts[$count-1] === '') {
8805 unset($parts[$count-1]); // trim trailing .
8807 $subnet = implode('.', $parts);
8810 $subnet = cleanremoteaddr($subnet, false); // normalise
8811 if ($subnet === $addr) {
8815 } else if ($count > 4) {
8818 $zeros = array_fill(0, 4-$count, '0');
8819 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
8820 if (address_in_subnet($addr, $subnet)) {
8831 * For outputting debugging info
8834 * @param string $string The string to write
8835 * @param string $eol The end of line char(s) to use
8836 * @param string $sleep Period to make the application sleep
8837 * This ensures any messages have time to display before redirect
8839 function mtrace($string, $eol="\n", $sleep=0) {
8841 if (defined('STDOUT')) {
8842 fwrite(STDOUT
, $string.$eol);
8844 echo $string . $eol;
8849 //delay to keep message on user's screen in case of subsequent redirect
8856 * Replace 1 or more slashes or backslashes to 1 slash
8858 * @param string $path The path to strip
8859 * @return string the path with double slashes removed
8861 function cleardoubleslashes ($path) {
8862 return preg_replace('/(\/|\\\){1,}/','/',$path);
8866 * Is current ip in give list?
8868 * @param string $list
8871 function remoteip_in_list($list){
8873 $client_ip = getremoteaddr(null);
8876 // ensure access on cli
8880 $list = explode("\n", $list);
8881 foreach($list as $subnet) {
8882 $subnet = trim($subnet);
8883 if (address_in_subnet($client_ip, $subnet)) {
8892 * Returns most reliable client address
8895 * @param string $default If an address can't be determined, then return this
8896 * @return string The remote IP address
8898 function getremoteaddr($default='0.0.0.0') {
8901 if (empty($CFG->getremoteaddrconf
)) {
8902 // This will happen, for example, before just after the upgrade, as the
8903 // user is redirected to the admin screen.
8904 $variablestoskip = 0;
8906 $variablestoskip = $CFG->getremoteaddrconf
;
8908 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP
)) {
8909 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
8910 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
8911 return $address ?
$address : $default;
8914 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR
)) {
8915 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
8916 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
8917 return $address ?
$address : $default;
8920 if (!empty($_SERVER['REMOTE_ADDR'])) {
8921 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
8922 return $address ?
$address : $default;
8929 * Cleans an ip address. Internal addresses are now allowed.
8930 * (Originally local addresses were not allowed.)
8932 * @param string $addr IPv4 or IPv6 address
8933 * @param bool $compress use IPv6 address compression
8934 * @return string normalised ip address string, null if error
8936 function cleanremoteaddr($addr, $compress=false) {
8937 $addr = trim($addr);
8939 //TODO: maybe add a separate function is_addr_public() or something like this
8941 if (strpos($addr, ':') !== false) {
8943 $parts = explode(':', $addr);
8944 $count = count($parts);
8946 if (strpos($parts[$count-1], '.') !== false) {
8947 //legacy ipv4 notation
8948 $last = array_pop($parts);
8949 $ipv4 = cleanremoteaddr($last, true);
8950 if ($ipv4 === null) {
8953 $bits = explode('.', $ipv4);
8954 $parts[] = dechex($bits[0]).dechex($bits[1]);
8955 $parts[] = dechex($bits[2]).dechex($bits[3]);
8956 $count = count($parts);
8957 $addr = implode(':', $parts);
8960 if ($count < 3 or $count > 8) {
8961 return null; // severly malformed
8965 if (strpos($addr, '::') === false) {
8966 return null; // malformed
8969 $insertat = array_search('', $parts, true);
8970 $missing = array_fill(0, 1 +
8 - $count, '0');
8971 array_splice($parts, $insertat, 1, $missing);
8972 foreach ($parts as $key=>$part) {
8979 $adr = implode(':', $parts);
8980 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
8981 return null; // incorrect format - sorry
8984 // normalise 0s and case
8985 $parts = array_map('hexdec', $parts);
8986 $parts = array_map('dechex', $parts);
8988 $result = implode(':', $parts);
8994 if ($result === '0:0:0:0:0:0:0:0') {
8995 return '::'; // all addresses
8998 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
8999 if ($compressed !== $result) {
9003 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
9004 if ($compressed !== $result) {
9008 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
9009 if ($compressed !== $result) {
9016 // first get all things that look like IPv4 addresses
9018 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
9023 foreach ($parts as $key=>$match) {
9027 $parts[$key] = (int)$match; // normalise 0s
9030 return implode('.', $parts);
9034 * This function will make a complete copy of anything it's given,
9035 * regardless of whether it's an object or not.
9037 * @param mixed $thing Something you want cloned
9038 * @return mixed What ever it is you passed it
9040 function fullclone($thing) {
9041 return unserialize(serialize($thing));
9046 * This function expects to called during shutdown
9047 * should be set via register_shutdown_function()
9048 * in lib/setup.php .
9052 function moodle_request_shutdown() {
9055 // help apache server if possible
9056 $apachereleasemem = false;
9057 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
9058 && ini_get_bool('child_terminate')) {
9060 $limit = (empty($CFG->apachemaxmem
) ?
64*1024*1024 : $CFG->apachemaxmem
); //64MB default
9061 if (memory_get_usage() > get_real_size($limit)) {
9062 $apachereleasemem = $limit;
9063 @apache_child_terminate
();
9067 // deal with perf logging
9068 if (defined('MDL_PERF') ||
(!empty($CFG->perfdebug
) and $CFG->perfdebug
> 7)) {
9069 if ($apachereleasemem) {
9070 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
9072 if (defined('MDL_PERFTOLOG')) {
9073 $perf = get_performance_info();
9074 error_log("PERF: " . $perf['txt']);
9076 if (defined('MDL_PERFINC')) {
9077 $inc = get_included_files();
9079 foreach($inc as $f) {
9080 if (preg_match(':^/:', $f)) {
9083 $hfs = display_size($fs);
9084 error_log(substr($f,strlen($CFG->dirroot
)) . " size: $fs ($hfs)"
9087 error_log($f , NULL, NULL, 0);
9091 $hts = display_size($ts);
9092 error_log("Total size of files included: $ts ($hts)");
9099 * If new messages are waiting for the current user, then insert
9100 * JavaScript to pop up the messaging window into the page
9102 * @global moodle_page $PAGE
9105 function message_popup_window() {
9106 global $USER, $DB, $PAGE, $CFG, $SITE;
9108 if (!$PAGE->get_popup_notification_allowed() ||
empty($CFG->messaging
)) {
9112 if (!isloggedin() ||
isguestuser()) {
9116 if (!isset($USER->message_lastpopup
)) {
9117 $USER->message_lastpopup
= 0;
9118 } else if ($USER->message_lastpopup
> (time()-120)) {
9119 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
9123 //a quick query to check whether the user has new messages
9124 $messagecount = $DB->count_records('message', array('useridto' => $USER->id
));
9125 if ($messagecount<1) {
9129 //got unread messages so now do another query that joins with the user table
9130 $messagesql = "SELECT m.id, m.smallmessage, m.notification, u.firstname, u.lastname FROM {message} m
9131 JOIN {message_working} mw ON m.id=mw.unreadmessageid
9132 JOIN {message_processors} p ON mw.processorid=p.id
9133 JOIN {user} u ON m.useridfrom=u.id
9134 WHERE m.useridto = :userid AND p.name='popup'";
9136 //if the user was last notified over an hour ago we can renotify them of old messages
9137 //so don't worry about when the new message was sent
9138 $lastnotifiedlongago = $USER->message_lastpopup
< (time()-3600);
9139 if (!$lastnotifiedlongago) {
9140 $messagesql .= 'AND m.timecreated > :lastpopuptime';
9143 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id
, 'lastpopuptime'=>$USER->message_lastpopup
));
9145 //if we have new messages to notify the user about
9146 if (!empty($message_users)) {
9149 if (count($message_users)>1) {
9150 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
9152 $message_users = reset($message_users);
9154 //show who the message is from if its not a notification
9155 if (!$message_users->notification
) {
9156 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
9159 //try to display the small version of the message
9160 $smallmessage = null;
9161 if (!empty($message_users->smallmessage
)) {
9162 //display the first 200 chars of the message in the popup
9163 $smallmessage = null;
9164 if (strlen($message_users->smallmessage
>200)) {
9165 $smallmessage = substr($message_users->smallmessage
,0,200).'...';
9167 $smallmessage = $message_users->smallmessage
;
9169 } else if ($message_users->notification
) {
9170 //its a notification with no smallmessage so just say they have a notification
9171 $smallmessage = get_string('unreadnewnotification', 'message');
9173 if (!empty($smallmessage)) {
9174 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
9178 $strgomessage = get_string('gotomessages', 'message');
9179 $strstaymessage = get_string('ignore','admin');
9181 $url = $CFG->wwwroot
.'/message/index.php';
9182 $content = html_writer
::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
9183 html_writer
::start_tag('div', array('id'=>'newmessagetext')).
9185 html_writer
::end_tag('div').
9187 html_writer
::start_tag('div', array('id'=>'newmessagelinks')).
9188 html_writer
::link($url, $strgomessage, array('id'=>'notificationyes')).' '.
9189 html_writer
::link('', $strstaymessage, array('id'=>'notificationno')).
9190 html_writer
::end_tag('div');
9191 html_writer
::end_tag('div');
9193 $PAGE->requires
->js_init_call('M.core_message.init_notification', array('', $content, $url));
9195 $USER->message_lastpopup
= time();
9200 * Used to make sure that $min <= $value <= $max
9202 * Make sure that value is between min, and max
9204 * @param int $min The minimum value
9205 * @param int $value The value to check
9206 * @param int $max The maximum value
9208 function bounded_number($min, $value, $max) {
9219 * Check if there is a nested array within the passed array
9221 * @param array $array
9222 * @return bool true if there is a nested array false otherwise
9224 function array_is_nested($array) {
9225 foreach ($array as $value) {
9226 if (is_array($value)) {
9234 * get_performance_info() pairs up with init_performance_info()
9235 * loaded in setup.php. Returns an array with 'html' and 'txt'
9236 * values ready for use, and each of the individual stats provided
9237 * separately as well.
9244 function get_performance_info() {
9245 global $CFG, $PERF, $DB, $PAGE;
9248 $info['html'] = ''; // holds userfriendly HTML representation
9249 $info['txt'] = me() . ' '; // holds log-friendly representation
9251 $info['realtime'] = microtime_diff($PERF->starttime
, microtime());
9253 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
9254 $info['txt'] .= 'time: '.$info['realtime'].'s ';
9256 if (function_exists('memory_get_usage')) {
9257 $info['memory_total'] = memory_get_usage();
9258 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory
;
9259 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
9260 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
9263 if (function_exists('memory_get_peak_usage')) {
9264 $info['memory_peak'] = memory_get_peak_usage();
9265 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
9266 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
9269 $inc = get_included_files();
9270 //error_log(print_r($inc,1));
9271 $info['includecount'] = count($inc);
9272 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
9273 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
9275 $filtermanager = filter_manager
::instance();
9276 if (method_exists($filtermanager, 'get_performance_summary')) {
9277 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
9278 $info = array_merge($filterinfo, $info);
9279 foreach ($filterinfo as $key => $value) {
9280 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9281 $info['txt'] .= "$key: $value ";
9285 $stringmanager = get_string_manager();
9286 if (method_exists($stringmanager, 'get_performance_summary')) {
9287 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
9288 $info = array_merge($filterinfo, $info);
9289 foreach ($filterinfo as $key => $value) {
9290 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9291 $info['txt'] .= "$key: $value ";
9295 $jsmodules = $PAGE->requires
->get_loaded_modules();
9300 foreach ($jsmodules as $module => $backtraces) {
9301 if (strpos($module, 'yui') === 0) {
9306 $details .= "<div class='yui-module'><p>$module</p>";
9307 foreach ($backtraces as $backtrace) {
9308 $details .= "<div class='backtrace'>$backtrace</div>";
9310 $details .= '</div>';
9312 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
9313 $info['txt'] .= "includedyuimodules: $yuicount ";
9314 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
9315 $info['txt'] .= "includedjsmodules: $othercount ";
9316 // Slightly odd to output the details in a display: none div. The point
9317 // Is that it takes a lot of space, and if you care you can reveal it
9319 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
9322 if (!empty($PERF->logwrites
)) {
9323 $info['logwrites'] = $PERF->logwrites
;
9324 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
9325 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
9328 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites
);
9329 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
9330 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
9332 if (!empty($PERF->profiling
) && $PERF->profiling
) {
9333 require_once($CFG->dirroot
.'/lib/profilerlib.php');
9334 $info['html'] .= '<span class="profilinginfo">'.Profiler
::get_profiling(array('-R')).'</span>';
9337 if (function_exists('posix_times')) {
9338 $ptimes = posix_times();
9339 if (is_array($ptimes)) {
9340 foreach ($ptimes as $key => $val) {
9341 $info[$key] = $ptimes[$key] - $PERF->startposixtimes
[$key];
9343 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
9344 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
9348 // Grab the load average for the last minute
9349 // /proc will only work under some linux configurations
9350 // while uptime is there under MacOSX/Darwin and other unices
9351 if (is_readable('/proc/loadavg') && $loadavg = @file
('/proc/loadavg')) {
9352 list($server_load) = explode(' ', $loadavg[0]);
9354 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `
/usr
/bin
/uptime`
) {
9355 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
9356 $server_load = $matches[1];
9358 trigger_error('Could not parse uptime output!');
9361 if (!empty($server_load)) {
9362 $info['serverload'] = $server_load;
9363 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
9364 $info['txt'] .= "serverload: {$info['serverload']} ";
9367 // Display size of session if session started
9369 $info['sessionsize'] = display_size(strlen(session_encode()));
9370 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
9371 $info['txt'] .= "Session: {$info['sessionsize']} ";
9374 /* if (isset($rcache->hits) && isset($rcache->misses)) {
9375 $info['rcachehits'] = $rcache->hits;
9376 $info['rcachemisses'] = $rcache->misses;
9377 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
9378 "{$rcache->hits}/{$rcache->misses}</span> ";
9379 $info['txt'] .= 'rcache: '.
9380 "{$rcache->hits}/{$rcache->misses} ";
9382 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
9387 * @todo Document this function linux people
9389 function apd_get_profiling() {
9390 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
9394 * Delete directory or only it's content
9396 * @param string $dir directory path
9397 * @param bool $content_only
9398 * @return bool success, true also if dir does not exist
9400 function remove_dir($dir, $content_only=false) {
9401 if (!file_exists($dir)) {
9405 $handle = opendir($dir);
9407 while (false!==($item = readdir($handle))) {
9408 if($item != '.' && $item != '..') {
9409 if(is_dir($dir.'/'.$item)) {
9410 $result = remove_dir($dir.'/'.$item) && $result;
9412 $result = unlink($dir.'/'.$item) && $result;
9417 if ($content_only) {
9420 return rmdir($dir); // if anything left the result will be false, no need for && $result
9424 * Detect if an object or a class contains a given property
9425 * will take an actual object or the name of a class
9427 * @param mix $obj Name of class or real object to test
9428 * @param string $property name of property to find
9429 * @return bool true if property exists
9431 function object_property_exists( $obj, $property ) {
9432 if (is_string( $obj )) {
9433 $properties = get_class_vars( $obj );
9436 $properties = get_object_vars( $obj );
9438 return array_key_exists( $property, $properties );
9443 * Detect a custom script replacement in the data directory that will
9444 * replace an existing moodle script
9446 * @param string $urlpath path to the original script
9447 * @return string|bool full path name if a custom script exists, false if no custom script exists
9449 function custom_script_path($urlpath='') {
9452 // set default $urlpath, if necessary
9453 if (empty($urlpath)) {
9454 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
9457 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
9458 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot
) === false )) {
9462 // replace wwwroot with the path to the customscripts folder and clean path
9463 $scriptpath = $CFG->customscripts
. clean_param(substr($urlpath, strlen($CFG->wwwroot
)), PARAM_PATH
);
9465 // remove the query string, if any
9466 if (($strpos = strpos($scriptpath, '?')) !== false) {
9467 $scriptpath = substr($scriptpath, 0, $strpos);
9470 // remove trailing slashes, if any
9471 $scriptpath = rtrim($scriptpath, '/\\');
9473 // append index.php, if necessary
9474 if (is_dir($scriptpath)) {
9475 $scriptpath .= '/index.php';
9478 // check the custom script exists
9479 if (file_exists($scriptpath)) {
9487 * Returns whether or not the user object is a remote MNET user. This function
9488 * is in moodlelib because it does not rely on loading any of the MNET code.
9491 * @param object $user A valid user object
9492 * @return bool True if the user is from a remote Moodle.
9494 function is_mnet_remote_user($user) {
9497 if (!isset($CFG->mnet_localhost_id
)) {
9498 include_once $CFG->dirroot
. '/mnet/lib.php';
9499 $env = new mnet_environment();
9504 return (!empty($user->mnethostid
) && $user->mnethostid
!= $CFG->mnet_localhost_id
);
9508 * This function will search for browser prefereed languages, setting Moodle
9509 * to use the best one available if $SESSION->lang is undefined
9515 function setup_lang_from_browser() {
9517 global $CFG, $SESSION, $USER;
9519 if (!empty($SESSION->lang
) or !empty($USER->lang
) or empty($CFG->autolang
)) {
9520 // Lang is defined in session or user profile, nothing to do
9524 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
9528 /// Extract and clean langs from headers
9529 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
9530 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
9531 $rawlangs = explode(',', $rawlangs); // Convert to array
9535 foreach ($rawlangs as $lang) {
9536 if (strpos($lang, ';') === false) {
9537 $langs[(string)$order] = $lang;
9538 $order = $order-0.01;
9540 $parts = explode(';', $lang);
9541 $pos = strpos($parts[1], '=');
9542 $langs[substr($parts[1], $pos+
1)] = $parts[0];
9545 krsort($langs, SORT_NUMERIC
);
9547 /// Look for such langs under standard locations
9548 foreach ($langs as $lang) {
9549 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR
)); // clean it properly for include
9550 if (get_string_manager()->translation_exists($lang, false)) {
9551 $SESSION->lang
= $lang; /// Lang exists, set it in session
9552 break; /// We have finished. Go out
9559 * check if $url matches anything in proxybypass list
9561 * any errors just result in the proxy being used (least bad)
9564 * @param string $url url to check
9565 * @return boolean true if we should bypass the proxy
9567 function is_proxybypass( $url ) {
9571 if (empty($CFG->proxyhost
) or empty($CFG->proxybypass
)) {
9575 // get the host part out of the url
9576 if (!$host = parse_url( $url, PHP_URL_HOST
)) {
9580 // get the possible bypass hosts into an array
9581 $matches = explode( ',', $CFG->proxybypass
);
9583 // check for a match
9584 // (IPs need to match the left hand side and hosts the right of the url,
9585 // but we can recklessly check both as there can't be a false +ve)
9587 foreach ($matches as $match) {
9588 $match = trim($match);
9590 // try for IP match (Left side)
9591 $lhs = substr($host,0,strlen($match));
9592 if (strcasecmp($match,$lhs)==0) {
9596 // try for host match (Right side)
9597 $rhs = substr($host,-strlen($match));
9598 if (strcasecmp($match,$rhs)==0) {
9608 ////////////////////////////////////////////////////////////////////////////////
9611 * Check if the passed navigation is of the new style
9613 * @param mixed $navigation
9614 * @return bool true for yes false for no
9616 function is_newnav($navigation) {
9617 if (is_array($navigation) && !empty($navigation['newnav'])) {
9625 * Checks whether the given variable name is defined as a variable within the given object.
9627 * This will NOT work with stdClass objects, which have no class variables.
9629 * @param string $var The variable name
9630 * @param object $object The object to check
9633 function in_object_vars($var, $object) {
9634 $class_vars = get_class_vars(get_class($object));
9635 $class_vars = array_keys($class_vars);
9636 return in_array($var, $class_vars);
9640 * Returns an array without repeated objects.
9641 * This function is similar to array_unique, but for arrays that have objects as values
9643 * @param array $array
9644 * @param bool $keep_key_assoc
9647 function object_array_unique($array, $keep_key_assoc = true) {
9648 $duplicate_keys = array();
9651 foreach ($array as $key=>$val) {
9652 // convert objects to arrays, in_array() does not support objects
9653 if (is_object($val)) {
9657 if (!in_array($val, $tmp)) {
9660 $duplicate_keys[] = $key;
9664 foreach ($duplicate_keys as $key) {
9665 unset($array[$key]);
9668 return $keep_key_assoc ?
$array : array_values($array);
9672 * Returns the language string for the given plugin.
9674 * @param string $plugin the plugin code name
9675 * @param string $type the type of plugin (mod, block, filter)
9676 * @return string The plugin language string
9678 function get_plugin_name($plugin, $type='mod') {
9683 $plugin_name = get_string('modulename', $plugin);
9686 $plugin_name = get_string('pluginname', "block_$plugin");
9687 if (empty($plugin_name) ||
$plugin_name == '[[pluginname]]') {
9688 if (($block = block_instance($plugin)) !== false) {
9689 $plugin_name = $block->get_title();
9691 $plugin_name = "[[$plugin]]";
9696 $plugin_name = filter_get_name('filter/' . $plugin);
9699 $plugin_name = $plugin;
9703 return $plugin_name;
9707 * Is a userid the primary administrator?
9709 * @param int $userid int id of user to check
9712 function is_primary_admin($userid){
9713 $primaryadmin = get_admin();
9715 if($userid == $primaryadmin->id
){
9723 * Returns the site identifier
9726 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
9728 function get_site_identifier() {
9730 // Check to see if it is missing. If so, initialise it.
9731 if (empty($CFG->siteidentifier
)) {
9732 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
9735 return $CFG->siteidentifier
;
9739 * Check whether the given password has no more than the specified
9740 * number of consecutive identical characters.
9742 * @param string $password password to be checked against the password policy
9743 * @param integer $maxchars maximum number of consecutive identical characters
9745 function check_consecutive_identical_characters($password, $maxchars) {
9747 if ($maxchars < 1) {
9748 return true; // 0 is to disable this check
9750 if (strlen($password) <= $maxchars) {
9751 return true; // too short to fail this test
9755 $consecutivecount = 1;
9756 foreach (str_split($password) as $char) {
9757 if ($char != $previouschar) {
9758 $consecutivecount = 1;
9761 $consecutivecount++
;
9762 if ($consecutivecount > $maxchars) {
9763 return false; // check failed already
9767 $previouschar = $char;
9774 * helper function to do partial function binding
9775 * so we can use it for preg_replace_callback, for example
9776 * this works with php functions, user functions, static methods and class methods
9777 * it returns you a callback that you can pass on like so:
9779 * $callback = partial('somefunction', $arg1, $arg2);
9781 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
9783 * $obj = new someclass();
9784 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
9786 * and then the arguments that are passed through at calltime are appended to the argument list.
9788 * @param mixed $function a php callback
9789 * $param mixed $arg1.. $argv arguments to partially bind with
9793 function partial() {
9794 if (!class_exists('partial')) {
9796 var $values = array();
9799 function __construct($func, $args) {
9800 $this->values
= $args;
9801 $this->func
= $func;
9805 $args = func_get_args();
9806 return call_user_func_array($this->func
, array_merge($this->values
, $args));
9810 $args = func_get_args();
9811 $func = array_shift($args);
9812 $p = new partial($func, $args);
9813 return array($p, 'method');
9817 * helper function to load up and initialise the mnet environment
9818 * this must be called before you use mnet functions.
9820 * @return mnet_environment the equivalent of old $MNET global
9822 function get_mnet_environment() {
9824 require_once($CFG->dirroot
. '/mnet/lib.php');
9825 static $instance = null;
9826 if (empty($instance)) {
9827 $instance = new mnet_environment();
9834 * during xmlrpc server code execution, any code wishing to access
9835 * information about the remote peer must use this to get it.
9837 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
9839 function get_mnet_remote_client() {
9840 if (!defined('MNET_SERVER')) {
9841 debugging(get_string('notinxmlrpcserver', 'mnet'));
9844 global $MNET_REMOTE_CLIENT;
9845 if (isset($MNET_REMOTE_CLIENT)) {
9846 return $MNET_REMOTE_CLIENT;
9852 * during the xmlrpc server code execution, this will be called
9853 * to setup the object returned by {@see get_mnet_remote_client}
9855 * @param mnet_remote_client $client the client to set up
9857 function set_mnet_remote_client($client) {
9858 if (!defined('MNET_SERVER')) {
9859 throw new moodle_exception('notinxmlrpcserver', 'mnet');
9861 global $MNET_REMOTE_CLIENT;
9862 $MNET_REMOTE_CLIENT = $client;
9866 * return the jump url for a given remote user
9867 * this is used for rewriting forum post links in emails, etc
9869 * @param stdclass $user the user to get the idp url for
9871 function mnet_get_idp_jump_url($user) {
9874 static $mnetjumps = array();
9875 if (!array_key_exists($user->mnethostid
, $mnetjumps)) {
9876 $idp = mnet_get_peer_host($user->mnethostid
);
9877 $idpjumppath = mnet_get_app_jumppath($idp->applicationid
);
9878 $mnetjumps[$user->mnethostid
] = $idp->wwwroot
. $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot
. '&wantsurl=';
9880 return $mnetjumps[$user->mnethostid
];
9884 * Gets the homepage to use for the current user
9886 * @return int One of HOMEPAGE_*
9888 function get_home_page() {
9891 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage
)) {
9892 if ($CFG->defaulthomepage
== HOMEPAGE_MY
) {
9895 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY
);
9898 return HOMEPAGE_SITE
;