3 ///////////////////////////////////////////////////////////////////////////
5 // NOTICE OF COPYRIGHT //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment //
8 // http://moodle.org //
10 // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
12 // This program is free software; you can redistribute it and/or modify //
13 // it under the terms of the GNU General Public License as published by //
14 // the Free Software Foundation; either version 2 of the License, or //
15 // (at your option) any later version. //
17 // This program is distributed in the hope that it will be useful, //
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
20 // GNU General Public License for more details: //
22 // http://www.gnu.org/copyleft/gpl.html //
24 ///////////////////////////////////////////////////////////////////////////
27 * moodlelib.php - Moodle main library
29 * Main library file of miscellaneous general-purpose Moodle functions.
30 * Other main libraries:
31 * - weblib.php - functions that produce web output
32 * - datalib.php - functions that access the database
33 * @author Martin Dougiamas
35 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
39 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
42 * Used by some scripts to check they are being called by Moodle
44 define('MOODLE_INTERNAL', true);
46 /// Date and time constants ///
48 * Time constant - the number of seconds in a year
51 define('YEARSECS', 31536000);
54 * Time constant - the number of seconds in a week
56 define('WEEKSECS', 604800);
59 * Time constant - the number of seconds in a day
61 define('DAYSECS', 86400);
64 * Time constant - the number of seconds in an hour
66 define('HOURSECS', 3600);
69 * Time constant - the number of seconds in a minute
71 define('MINSECS', 60);
74 * Time constant - the number of minutes in a day
76 define('DAYMINS', 1440);
79 * Time constant - the number of minutes in an hour
81 define('HOURMINS', 60);
83 /// Parameter constants - every call to optional_param(), required_param() ///
84 /// or clean_param() should have a specified type of parameter. //////////////
87 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
88 * originally was 0, but changed because we need to detect unknown
89 * parameter types and swiched order in clean_param().
91 define('PARAM_RAW', 666);
94 * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
95 * It was one of the first types, that is why it is abused so much ;-)
97 define('PARAM_CLEAN', 0x0001);
100 * PARAM_INT - integers only, use when expecting only numbers.
102 define('PARAM_INT', 0x0002);
105 * PARAM_INTEGER - an alias for PARAM_INT
107 define('PARAM_INTEGER', 0x0002);
110 * PARAM_NUMBER - a real/floating point number.
112 define('PARAM_NUMBER', 0x000a);
115 * PARAM_ALPHA - contains only english letters.
117 define('PARAM_ALPHA', 0x0004);
120 * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
121 * @TODO: should we alias it to PARAM_ALPHANUM ?
123 define('PARAM_ACTION', 0x0004);
126 * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
127 * @TODO: should we alias it to PARAM_ALPHANUM ?
129 define('PARAM_FORMAT', 0x0004);
132 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
134 define('PARAM_NOTAGS', 0x0008);
137 * PARAM_MULTILANG - alias of PARAM_TEXT.
139 define('PARAM_MULTILANG', 0x0009);
142 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
144 define('PARAM_TEXT', 0x0009);
147 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
149 define('PARAM_FILE', 0x0010);
152 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
154 define('PARAM_TAG', 0x0011);
157 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
159 define('PARAM_TAGLIST', 0x0012);
162 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
163 * note: the leading slash is not removed, window drive letter is not allowed
165 define('PARAM_PATH', 0x0020);
168 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
170 define('PARAM_HOST', 0x0040);
173 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
175 define('PARAM_URL', 0x0080);
178 * 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!)
180 define('PARAM_LOCALURL', 0x0180);
183 * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
184 * use when you want to store a new file submitted by students
186 define('PARAM_CLEANFILE',0x0200);
189 * PARAM_ALPHANUM - expected numbers and letters only.
191 define('PARAM_ALPHANUM', 0x0400);
194 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
196 define('PARAM_BOOL', 0x0800);
199 * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
200 * note: do not forget to addslashes() before storing into database!
202 define('PARAM_CLEANHTML',0x1000);
205 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
206 * suitable for include() and require()
207 * @TODO: should we rename this function to PARAM_SAFEDIRS??
209 define('PARAM_ALPHAEXT', 0x2000);
212 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
214 define('PARAM_SAFEDIR', 0x4000);
217 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
219 define('PARAM_SEQUENCE', 0x8000);
222 * PARAM_PEM - Privacy Enhanced Mail format
224 define('PARAM_PEM', 0x10000);
227 * PARAM_BASE64 - Base 64 encoded format
229 define('PARAM_BASE64', 0x20000);
234 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
236 define('PAGE_COURSE_VIEW', 'course-view');
239 /** no warnings at all */
240 define ('DEBUG_NONE', 0);
241 /** E_ERROR | E_PARSE */
242 define ('DEBUG_MINIMAL', 5);
243 /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
244 define ('DEBUG_NORMAL', 15);
245 /** E_ALL without E_STRICT for now, do show recoverable fatal errors */
246 define ('DEBUG_ALL', 6143);
247 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
248 define ('DEBUG_DEVELOPER', 38911);
251 * Blog access level constant declaration
253 define ('BLOG_USER_LEVEL', 1);
254 define ('BLOG_GROUP_LEVEL', 2);
255 define ('BLOG_COURSE_LEVEL', 3);
256 define ('BLOG_SITE_LEVEL', 4);
257 define ('BLOG_GLOBAL_LEVEL', 5);
262 //To prevent problems with multibytes strings, this should not exceed the
263 //length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
264 define('TAG_MAX_LENGTH', 50);
267 * Password policy constants
269 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
270 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
271 define ('PASSWORD_DIGITS', '0123456789');
272 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
274 if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
275 define('SORT_LOCALE_STRING', SORT_STRING
);
279 /// PARAMETER HANDLING ////////////////////////////////////////////////////
282 * Returns a particular value for the named variable, taken from
283 * POST or GET. If the parameter doesn't exist then an error is
284 * thrown because we require this variable.
286 * This function should be used to initialise all required values
287 * in a script that are based on parameters. Usually it will be
289 * $id = required_param('id');
291 * @param string $parname the name of the page parameter we want
292 * @param int $type expected type of parameter
295 function required_param($parname, $type=PARAM_CLEAN
) {
297 // detect_unchecked_vars addition
299 if (!empty($CFG->detect_unchecked_vars
)) {
300 global $UNCHECKED_VARS;
301 unset ($UNCHECKED_VARS->vars
[$parname]);
304 if (isset($_POST[$parname])) { // POST has precedence
305 $param = $_POST[$parname];
306 } else if (isset($_GET[$parname])) {
307 $param = $_GET[$parname];
309 error('A required parameter ('.$parname.') was missing');
312 return clean_param($param, $type);
316 * Returns a particular value for the named variable, taken from
317 * POST or GET, otherwise returning a given default.
319 * This function should be used to initialise all optional values
320 * in a script that are based on parameters. Usually it will be
322 * $name = optional_param('name', 'Fred');
324 * @param string $parname the name of the page parameter we want
325 * @param mixed $default the default value to return if nothing is found
326 * @param int $type expected type of parameter
329 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN
) {
331 // detect_unchecked_vars addition
333 if (!empty($CFG->detect_unchecked_vars
)) {
334 global $UNCHECKED_VARS;
335 unset ($UNCHECKED_VARS->vars
[$parname]);
338 if (isset($_POST[$parname])) { // POST has precedence
339 $param = $_POST[$parname];
340 } else if (isset($_GET[$parname])) {
341 $param = $_GET[$parname];
346 return clean_param($param, $type);
350 * Used by {@link optional_param()} and {@link required_param()} to
351 * clean the variables and/or cast to specific types, based on
354 * $course->format = clean_param($course->format, PARAM_ALPHA);
355 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
361 * @uses PARAM_CLEANHTML
365 * @uses PARAM_ALPHANUM
366 * @uses PARAM_ALPHAEXT
367 * @uses PARAM_SEQUENCE
371 * @uses PARAM_SAFEDIR
372 * @uses PARAM_CLEANFILE
377 * @uses PARAM_LOCALURL
381 * @uses PARAM_SEQUENCE
382 * @param mixed $param the variable we are cleaning
383 * @param int $type expected format of param after cleaning.
386 function clean_param($param, $type) {
390 if (is_array($param)) { // Let's loop
392 foreach ($param as $key => $value) {
393 $newparam[$key] = clean_param($value, $type);
399 case PARAM_RAW
: // no cleaning at all
402 case PARAM_CLEAN
: // General HTML cleaning, try to use more specific type if possible
403 if (is_numeric($param)) {
406 $param = stripslashes($param); // Needed for kses to work fine
407 $param = clean_text($param); // Sweep for scripts, etc
408 return addslashes($param); // Restore original request parameter slashes
410 case PARAM_CLEANHTML
: // prepare html fragment for display, do not store it into db!!
411 $param = stripslashes($param); // Remove any slashes
412 $param = clean_text($param); // Sweep for scripts, etc
416 return (int)$param; // Convert to integer
419 return (float)$param; // Convert to integer
421 case PARAM_ALPHA
: // Remove everything not a-z
422 return eregi_replace('[^a-zA-Z]', '', $param);
424 case PARAM_ALPHANUM
: // Remove everything not a-zA-Z0-9
425 return eregi_replace('[^A-Za-z0-9]', '', $param);
427 case PARAM_ALPHAEXT
: // Remove everything not a-zA-Z/_-
428 return eregi_replace('[^a-zA-Z/_-]', '', $param);
430 case PARAM_SEQUENCE
: // Remove everything not 0-9,
431 return eregi_replace('[^0-9,]', '', $param);
433 case PARAM_BOOL
: // Convert to 1 or 0
434 $tempstr = strtolower($param);
435 if ($tempstr == 'on' or $tempstr == 'yes' ) {
437 } else if ($tempstr == 'off' or $tempstr == 'no') {
440 $param = empty($param) ?
0 : 1;
444 case PARAM_NOTAGS
: // Strip all tags
445 return strip_tags($param);
447 case PARAM_TEXT
: // leave only tags needed for multilang
448 return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN
);
450 case PARAM_SAFEDIR
: // Remove everything not a-zA-Z0-9_-
451 return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
453 case PARAM_CLEANFILE
: // allow only safe characters
454 return clean_filename($param);
456 case PARAM_FILE
: // Strip all suspicious characters from filename
457 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
458 $param = ereg_replace('\.\.+', '', $param);
464 case PARAM_PATH
: // Strip all suspicious characters from file path
465 $param = str_replace('\\\'', '\'', $param);
466 $param = str_replace('\\"', '"', $param);
467 $param = str_replace('\\', '/', $param);
468 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
469 $param = ereg_replace('\.\.+', '', $param);
470 $param = ereg_replace('//+', '/', $param);
471 return ereg_replace('/(\./)+', '/', $param);
473 case PARAM_HOST
: // allow FQDN or IPv4 dotted quad
474 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
475 // match ipv4 dotted quad
476 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
477 // confirm values are ok
481 ||
$match[4] > 255 ) {
482 // hmmm, what kind of dotted quad is this?
485 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
486 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
487 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
489 // all is ok - $param is respected
496 case PARAM_URL
: // allow safe ftp, http, mailto urls
497 include_once($CFG->dirroot
. '/lib/validateurlsyntax.php');
498 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
499 // all is ok, param is respected
501 $param =''; // not really ok
505 case PARAM_LOCALURL
: // allow http absolute, root relative and relative URLs within wwwroot
506 $param = clean_param($param, PARAM_URL
);
507 if (!empty($param)) {
508 if (preg_match(':^/:', $param)) {
509 // root-relative, ok!
510 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot
, '/').'/i',$param)) {
511 // absolute, and matches our wwwroot
513 // relative - let's make sure there are no tricks
514 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
524 $param = trim($param);
525 // PEM formatted strings may contain letters/numbers and the symbols
529 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
530 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
531 list($wholething, $body) = $matches;
532 unset($wholething, $matches);
533 $b64 = clean_param($body, PARAM_BASE64
);
535 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
543 if (!empty($param)) {
544 // PEM formatted strings may contain letters/numbers and the symbols
548 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
551 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY
);
552 // Each line of base64 encoded data must be 64 characters in
553 // length, except for the last line which may be less than (or
554 // equal to) 64 characters long.
555 for ($i=0, $j=count($lines); $i < $j; $i++
) {
557 if (64 < strlen($lines[$i])) {
563 if (64 != strlen($lines[$i])) {
567 return implode("\n",$lines);
573 //as long as magic_quotes_gpc is used, a backslash will be a
574 //problem, so remove *all* backslash.
575 $param = str_replace('\\', '', $param);
576 //convert many whitespace chars into one
577 $param = preg_replace('/\s+/', ' ', $param);
578 $textlib = textlib_get_instance();
579 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH
);
584 $tags = explode(',', $param);
586 foreach ($tags as $tag) {
587 $res = clean_param($tag, PARAM_TAG
);
593 return implode(',', $result);
598 default: // throw error, switched parameters in optional_param or another serious problem
599 error("Unknown parameter type: $type");
606 * Set a key in global configuration
608 * Set a key/value pair in both this session's {@link $CFG} global variable
609 * and in the 'config' database table for future sessions.
611 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
612 * In that case it doesn't affect $CFG.
614 * A NULL value will delete the entry.
616 * @param string $name the key to set
617 * @param string $value the value to set (without magic quotes)
618 * @param string $plugin (optional) the plugin scope
622 function set_config($name, $value, $plugin=NULL) {
623 /// No need for get_config because they are usually always available in $CFG
627 if (empty($plugin)) {
628 if (!array_key_exists($name, $CFG->config_php_settings
)) {
629 // So it's defined for this invocation at least
630 if (is_null($value)) {
633 $CFG->$name = (string)$value; // settings from db are always strings
637 if (get_field('config', 'name', 'name', $name)) {
639 return delete_records('config', 'name', $name);
641 return set_field('config', 'value', addslashes($value), 'name', $name);
647 $config = new object();
648 $config->name
= $name;
649 $config->value
= addslashes($value);
650 return insert_record('config', $config);
652 } else { // plugin scope
653 if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
655 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
657 return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
663 $config = new object();
664 $config->plugin
= addslashes($plugin);
665 $config->name
= $name;
666 $config->value
= addslashes($value);
667 return insert_record('config_plugins', $config);
673 * Get configuration values from the global config table
674 * or the config_plugins table.
676 * If called with no parameters it will do the right thing
677 * generating $CFG safely from the database without overwriting
680 * If called with 2 parameters it will return a $string single
681 * value or false of the value is not found.
683 * @param string $plugin
684 * @param string $name
686 * @return hash-like object or single value
689 function get_config($plugin=NULL, $name=NULL) {
693 if (!empty($name)) { // the user is asking for a specific value
694 if (!empty($plugin)) {
695 return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
697 return get_field('config', 'value', 'name', $name);
701 // the user is after a recordset
702 if (!empty($plugin)) {
703 if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
704 $configs = (array)$configs;
706 foreach ($configs as $config) {
707 $localcfg[$config->name
] = $config->value
;
709 return (object)$localcfg;
714 // this was originally in setup.php
715 if ($configs = get_records('config')) {
716 $localcfg = (array)$CFG;
717 foreach ($configs as $config) {
718 if (!isset($localcfg[$config->name
])) {
719 $localcfg[$config->name
] = $config->value
;
721 // do not complain anymore if config.php overrides settings from db
724 $localcfg = (object)$localcfg;
727 // preserve $CFG if DB returns nothing or error
735 * Removes a key from global configuration
737 * @param string $name the key to set
738 * @param string $plugin (optional) the plugin scope
742 function unset_config($name, $plugin=NULL) {
748 if (empty($plugin)) {
749 return delete_records('config', 'name', $name);
751 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
758 * @param string $type
759 * @param int $changedsince
760 * @return records array
763 function get_cache_flags($type, $changedsince=NULL) {
765 $type = addslashes($type);
767 $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
768 if ($changedsince !== NULL) {
769 $changedsince = (int)$changedsince;
770 $sqlwhere .= ' AND timemodified > ' . $changedsince;
773 if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
774 foreach ($flags as $flag) {
775 $cf[$flag->name
] = $flag->value
;
784 * @param string $type
785 * @param string $name
786 * @param int $changedsince
787 * @return records array
790 function get_cache_flag($type, $name, $changedsince=NULL) {
792 $type = addslashes($type);
793 $name = addslashes($name);
795 $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
796 if ($changedsince !== NULL) {
797 $changedsince = (int)$changedsince;
798 $sqlwhere .= ' AND timemodified > ' . $changedsince;
800 return get_field_select('cache_flags', 'value', $sqlwhere);
804 * Set a volatile flag
806 * @param string $type the "type" namespace for the key
807 * @param string $name the key to set
808 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
809 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
812 function set_cache_flag($type, $name, $value, $expiry=NULL) {
815 $timemodified = time();
816 if ($expiry===NULL ||
$expiry < $timemodified) {
817 $expiry = $timemodified +
24 * 60 * 60;
819 $expiry = (int)$expiry;
822 if ($value === NULL) {
823 return unset_cache_flag($type,$name);
826 $type = addslashes($type);
827 $name = addslashes($name);
828 if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
829 if ($f->value
== $value and $f->expiry
== $expiry and $f->timemodified
== $timemodified) {
830 return true; //no need to update; helps rcache too
832 $f->value
= addslashes($value);
833 $f->expiry
= $expiry;
834 $f->timemodified
= $timemodified;
835 return update_record('cache_flags', $f);
838 $f->flagtype
= $type;
840 $f->value
= addslashes($value);
841 $f->expiry
= $expiry;
842 $f->timemodified
= $timemodified;
843 return (bool)insert_record('cache_flags', $f);
848 * Removes a single volatile flag
850 * @param string $type the "type" namespace for the key
851 * @param string $name the key to set
855 function unset_cache_flag($type, $name) {
857 return delete_records('cache_flags',
858 'name', addslashes($name),
859 'flagtype', addslashes($type));
863 * Garbage-collect volatile flags
866 function gc_cache_flags() {
867 return delete_records_select('cache_flags', 'expiry < ' . time());
871 * Refresh current $USER session global variable with all their current preferences.
874 function reload_user_preferences() {
879 $USER->preference
= array();
881 if (!isloggedin() or isguestuser()) {
882 // no permanent storage for not-logged-in user and guest
884 } else if ($preferences = get_records('user_preferences', 'userid', $USER->id
)) {
885 foreach ($preferences as $preference) {
886 $USER->preference
[$preference->name
] = $preference->value
;
894 * Sets a preference for the current user
895 * Optionally, can set a preference for a different user object
897 * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
899 * @param string $name The key to set as preference for the specified user
900 * @param string $value The value to set forthe $name key in the specified user's record
901 * @param int $otheruserid A moodle user ID
904 function set_user_preference($name, $value, $otheruserid=NULL) {
908 if (!isset($USER->preference
)) {
909 reload_user_preferences();
918 if (empty($otheruserid)){
919 if (!isloggedin() or isguestuser()) {
924 if (isguestuser($otheruserid)) {
927 $userid = $otheruserid;
932 // no permanent storage for not-logged-in user and guest
934 } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
935 if ($preference->value
=== $value) {
938 if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id
)) {
943 $preference = new object();
944 $preference->userid
= $userid;
945 $preference->name
= addslashes($name);
946 $preference->value
= addslashes((string)$value);
947 if (!insert_record('user_preferences', $preference)) {
952 // update value in USER session if needed
953 if ($userid == $USER->id
) {
954 $USER->preference
[$name] = (string)$value;
961 * Unsets a preference completely by deleting it from the database
962 * Optionally, can set a preference for a different user id
964 * @param string $name The key to unset as preference for the specified user
965 * @param int $otheruserid A moodle user ID
967 function unset_user_preference($name, $otheruserid=NULL) {
971 if (!isset($USER->preference
)) {
972 reload_user_preferences();
975 if (empty($otheruserid)){
978 $userid = $otheruserid;
981 //Delete the preference from $USER if needed
982 if ($userid == $USER->id
) {
983 unset($USER->preference
[$name]);
987 return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
992 * Sets a whole array of preferences for the current user
993 * @param array $prefarray An array of key/value pairs to be set
994 * @param int $otheruserid A moodle user ID
997 function set_user_preferences($prefarray, $otheruserid=NULL) {
999 if (!is_array($prefarray) or empty($prefarray)) {
1004 foreach ($prefarray as $name => $value) {
1005 // The order is important; test for return is done first
1006 $return = (set_user_preference($name, $value, $otheruserid) && $return);
1012 * If no arguments are supplied this function will return
1013 * all of the current user preferences as an array.
1014 * If a name is specified then this function
1015 * attempts to return that particular preference value. If
1016 * none is found, then the optional value $default is returned,
1018 * @param string $name Name of the key to use in finding a preference value
1019 * @param string $default Value to be returned if the $name key is not set in the user preferences
1020 * @param int $otheruserid A moodle user ID
1024 function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
1027 if (!isset($USER->preference
)) {
1028 reload_user_preferences();
1031 if (empty($otheruserid)){
1032 $userid = $USER->id
;
1034 $userid = $otheruserid;
1037 if ($userid == $USER->id
) {
1038 $preference = $USER->preference
;
1041 $preference = array();
1042 if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
1043 foreach ($prefdata as $pref) {
1044 $preference[$pref->name
] = $pref->value
;
1050 return $preference; // All values
1052 } else if (array_key_exists($name, $preference)) {
1053 return $preference[$name]; // The single value
1056 return $default; // Default value (or NULL)
1061 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1064 * Given date parts in user time produce a GMT timestamp.
1066 * @param int $year The year part to create timestamp of
1067 * @param int $month The month part to create timestamp of
1068 * @param int $day The day part to create timestamp of
1069 * @param int $hour The hour part to create timestamp of
1070 * @param int $minute The minute part to create timestamp of
1071 * @param int $second The second part to create timestamp of
1072 * @param float $timezone ?
1073 * @param bool $applydst ?
1074 * @return int timestamp
1075 * @todo Finish documenting this function
1077 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1079 $strtimezone = NULL;
1080 if (!is_numeric($timezone)) {
1081 $strtimezone = $timezone;
1084 $timezone = get_user_timezone_offset($timezone);
1086 if (abs($timezone) > 13) {
1087 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1089 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1090 $time = usertime($time, $timezone);
1092 $time -= dst_offset_on($time, $strtimezone);
1101 * Given an amount of time in seconds, returns string
1102 * formatted nicely as weeks, days, hours etc as needed
1108 * @param int $totalsecs ?
1109 * @param array $str ?
1112 function format_time($totalsecs, $str=NULL) {
1114 $totalsecs = abs($totalsecs);
1116 if (!$str) { // Create the str structure the slow way
1117 $str->day
= get_string('day');
1118 $str->days
= get_string('days');
1119 $str->hour
= get_string('hour');
1120 $str->hours
= get_string('hours');
1121 $str->min
= get_string('min');
1122 $str->mins
= get_string('mins');
1123 $str->sec
= get_string('sec');
1124 $str->secs
= get_string('secs');
1125 $str->year
= get_string('year');
1126 $str->years
= get_string('years');
1130 $years = floor($totalsecs/YEARSECS
);
1131 $remainder = $totalsecs - ($years*YEARSECS
);
1132 $days = floor($remainder/DAYSECS
);
1133 $remainder = $totalsecs - ($days*DAYSECS
);
1134 $hours = floor($remainder/HOURSECS
);
1135 $remainder = $remainder - ($hours*HOURSECS
);
1136 $mins = floor($remainder/MINSECS
);
1137 $secs = $remainder - ($mins*MINSECS
);
1139 $ss = ($secs == 1) ?
$str->sec
: $str->secs
;
1140 $sm = ($mins == 1) ?
$str->min
: $str->mins
;
1141 $sh = ($hours == 1) ?
$str->hour
: $str->hours
;
1142 $sd = ($days == 1) ?
$str->day
: $str->days
;
1143 $sy = ($years == 1) ?
$str->year
: $str->years
;
1151 if ($years) $oyears = $years .' '. $sy;
1152 if ($days) $odays = $days .' '. $sd;
1153 if ($hours) $ohours = $hours .' '. $sh;
1154 if ($mins) $omins = $mins .' '. $sm;
1155 if ($secs) $osecs = $secs .' '. $ss;
1157 if ($years) return trim($oyears .' '. $odays);
1158 if ($days) return trim($odays .' '. $ohours);
1159 if ($hours) return trim($ohours .' '. $omins);
1160 if ($mins) return trim($omins .' '. $osecs);
1161 if ($secs) return $osecs;
1162 return get_string('now');
1166 * Returns a formatted string that represents a date in user time
1167 * <b>WARNING: note that the format is for strftime(), not date().</b>
1168 * Because of a bug in most Windows time libraries, we can't use
1169 * the nicer %e, so we have to use %d which has leading zeroes.
1170 * A lot of the fuss in the function is just getting rid of these leading
1171 * zeroes as efficiently as possible.
1173 * If parameter fixday = true (default), then take off leading
1174 * zero from %d, else mantain it.
1177 * @param int $date timestamp in GMT
1178 * @param string $format strftime format
1179 * @param float $timezone
1180 * @param bool $fixday If true (default) then the leading
1181 * zero from %d is removed. If false then the leading zero is mantained.
1184 function userdate($date, $format='', $timezone=99, $fixday = true) {
1188 $strtimezone = NULL;
1189 if (!is_numeric($timezone)) {
1190 $strtimezone = $timezone;
1193 if (empty($format)) {
1194 $format = get_string('strftimedaydatetime');
1197 if (!empty($CFG->nofixday
)) { // Config.php can force %d not to be fixed.
1199 } else if ($fixday) {
1200 $formatnoday = str_replace('%d', 'DD', $format);
1201 $fixday = ($formatnoday != $format);
1204 $date +
= dst_offset_on($date, $strtimezone);
1206 $timezone = get_user_timezone_offset($timezone);
1208 if (abs($timezone) > 13) { /// Server time
1210 $datestring = strftime($formatnoday, $date);
1211 $daystring = str_replace(' 0', '', strftime(' %d', $date));
1212 $datestring = str_replace('DD', $daystring, $datestring);
1214 $datestring = strftime($format, $date);
1217 $date +
= (int)($timezone * 3600);
1219 $datestring = gmstrftime($formatnoday, $date);
1220 $daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
1221 $datestring = str_replace('DD', $daystring, $datestring);
1223 $datestring = gmstrftime($format, $date);
1227 /// If we are running under Windows convert from windows encoding to UTF-8
1228 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1230 if ($CFG->ostype
== 'WINDOWS') {
1231 if ($localewincharset = get_string('localewincharset')) {
1232 $textlib = textlib_get_instance();
1233 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1241 * Given a $time timestamp in GMT (seconds since epoch),
1242 * returns an array that represents the date in user time
1245 * @param int $time Timestamp in GMT
1246 * @param float $timezone ?
1247 * @return array An array that represents the date in user time
1248 * @todo Finish documenting this function
1250 function usergetdate($time, $timezone=99) {
1252 $strtimezone = NULL;
1253 if (!is_numeric($timezone)) {
1254 $strtimezone = $timezone;
1257 $timezone = get_user_timezone_offset($timezone);
1259 if (abs($timezone) > 13) { // Server time
1260 return getdate($time);
1263 // There is no gmgetdate so we use gmdate instead
1264 $time +
= dst_offset_on($time, $strtimezone);
1265 $time +
= intval((float)$timezone * HOURSECS
);
1267 $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
1270 $getdate['seconds'],
1271 $getdate['minutes'],
1278 $getdate['weekday'],
1280 ) = explode('_', $datestring);
1286 * Given a GMT timestamp (seconds since epoch), offsets it by
1287 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1290 * @param int $date Timestamp in GMT
1291 * @param float $timezone
1294 function usertime($date, $timezone=99) {
1296 $timezone = get_user_timezone_offset($timezone);
1298 if (abs($timezone) > 13) {
1301 return $date - (int)($timezone * HOURSECS
);
1305 * Given a time, return the GMT timestamp of the most recent midnight
1306 * for the current user.
1308 * @param int $date Timestamp in GMT
1309 * @param float $timezone ?
1312 function usergetmidnight($date, $timezone=99) {
1314 $userdate = usergetdate($date, $timezone);
1316 // Time of midnight of this user's day, in GMT
1317 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1322 * Returns a string that prints the user's timezone
1324 * @param float $timezone The user's timezone
1327 function usertimezone($timezone=99) {
1329 $tz = get_user_timezone($timezone);
1331 if (!is_float($tz)) {
1335 if(abs($tz) > 13) { // Server time
1336 return get_string('serverlocaltime');
1339 if($tz == intval($tz)) {
1340 // Don't show .0 for whole hours
1357 * Returns a float which represents the user's timezone difference from GMT in hours
1358 * Checks various settings and picks the most dominant of those which have a value
1362 * @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
1365 function get_user_timezone_offset($tz = 99) {
1369 $tz = get_user_timezone($tz);
1371 if (is_float($tz)) {
1374 $tzrecord = get_timezone_record($tz);
1375 if (empty($tzrecord)) {
1378 return (float)$tzrecord->gmtoff
/ HOURMINS
;
1383 * Returns an int which represents the systems's timezone difference from GMT in seconds
1384 * @param mixed $tz timezone
1385 * @return int if found, false is timezone 99 or error
1387 function get_timezone_offset($tz) {
1394 if (is_numeric($tz)) {
1395 return intval($tz * 60*60);
1398 if (!$tzrecord = get_timezone_record($tz)) {
1401 return intval($tzrecord->gmtoff
* 60);
1405 * Returns a float or a string which denotes the user's timezone
1406 * 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)
1407 * means that for this timezone there are also DST rules to be taken into account
1408 * Checks various settings and picks the most dominant of those which have a value
1412 * @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
1415 function get_user_timezone($tz = 99) {
1420 isset($CFG->forcetimezone
) ?
$CFG->forcetimezone
: 99,
1421 isset($USER->timezone
) ?
$USER->timezone
: 99,
1422 isset($CFG->timezone
) ?
$CFG->timezone
: 99,
1427 while(($tz == '' ||
$tz == 99 ||
$tz == NULL) && $next = each($timezones)) {
1428 $tz = $next['value'];
1431 return is_numeric($tz) ?
(float) $tz : $tz;
1439 * @param string $timezonename ?
1442 function get_timezone_record($timezonename) {
1444 static $cache = NULL;
1446 if ($cache === NULL) {
1450 if (isset($cache[$timezonename])) {
1451 return $cache[$timezonename];
1454 return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix
.'timezone
1455 WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1463 * @param ? $fromyear ?
1464 * @param ? $to_year ?
1467 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1468 global $CFG, $SESSION;
1470 $usertz = get_user_timezone($strtimezone);
1472 if (is_float($usertz)) {
1473 // Trivial timezone, no DST
1477 if (!empty($SESSION->dst_offsettz
) && $SESSION->dst_offsettz
!= $usertz) {
1478 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1479 unset($SESSION->dst_offsets
);
1480 unset($SESSION->dst_range
);
1483 if (!empty($SESSION->dst_offsets
) && empty($from_year) && empty($to_year)) {
1484 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1485 // This will be the return path most of the time, pretty light computationally
1489 // Reaching here means we either need to extend our table or create it from scratch
1491 // Remember which TZ we calculated these changes for
1492 $SESSION->dst_offsettz
= $usertz;
1494 if(empty($SESSION->dst_offsets
)) {
1495 // If we 're creating from scratch, put the two guard elements in there
1496 $SESSION->dst_offsets
= array(1 => NULL, 0 => NULL);
1498 if(empty($SESSION->dst_range
)) {
1499 // If creating from scratch
1500 $from = max((empty($from_year) ?
intval(date('Y')) - 3 : $from_year), 1971);
1501 $to = min((empty($to_year) ?
intval(date('Y')) +
3 : $to_year), 2035);
1503 // Fill in the array with the extra years we need to process
1504 $yearstoprocess = array();
1505 for($i = $from; $i <= $to; ++
$i) {
1506 $yearstoprocess[] = $i;
1509 // Take note of which years we have processed for future calls
1510 $SESSION->dst_range
= array($from, $to);
1513 // If needing to extend the table, do the same
1514 $yearstoprocess = array();
1516 $from = max((empty($from_year) ?
$SESSION->dst_range
[0] : $from_year), 1971);
1517 $to = min((empty($to_year) ?
$SESSION->dst_range
[1] : $to_year), 2035);
1519 if($from < $SESSION->dst_range
[0]) {
1520 // Take note of which years we need to process and then note that we have processed them for future calls
1521 for($i = $from; $i < $SESSION->dst_range
[0]; ++
$i) {
1522 $yearstoprocess[] = $i;
1524 $SESSION->dst_range
[0] = $from;
1526 if($to > $SESSION->dst_range
[1]) {
1527 // Take note of which years we need to process and then note that we have processed them for future calls
1528 for($i = $SESSION->dst_range
[1] +
1; $i <= $to; ++
$i) {
1529 $yearstoprocess[] = $i;
1531 $SESSION->dst_range
[1] = $to;
1535 if(empty($yearstoprocess)) {
1536 // This means that there was a call requesting a SMALLER range than we have already calculated
1540 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1541 // Also, the array is sorted in descending timestamp order!
1545 static $presets_cache = array();
1546 if (!isset($presets_cache[$usertz])) {
1547 $presets_cache[$usertz] = get_records('timezone', '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');
1549 if(empty($presets_cache[$usertz])) {
1553 // Remove ending guard (first element of the array)
1554 reset($SESSION->dst_offsets
);
1555 unset($SESSION->dst_offsets
[key($SESSION->dst_offsets
)]);
1557 // Add all required change timestamps
1558 foreach($yearstoprocess as $y) {
1559 // Find the record which is in effect for the year $y
1560 foreach($presets_cache[$usertz] as $year => $preset) {
1566 $changes = dst_changes_for_year($y, $preset);
1568 if($changes === NULL) {
1571 if($changes['dst'] != 0) {
1572 $SESSION->dst_offsets
[$changes['dst']] = $preset->dstoff
* MINSECS
;
1574 if($changes['std'] != 0) {
1575 $SESSION->dst_offsets
[$changes['std']] = 0;
1579 // Put in a guard element at the top
1580 $maxtimestamp = max(array_keys($SESSION->dst_offsets
));
1581 $SESSION->dst_offsets
[($maxtimestamp + DAYSECS
)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1584 krsort($SESSION->dst_offsets
);
1589 function dst_changes_for_year($year, $timezone) {
1591 if($timezone->dst_startday
== 0 && $timezone->dst_weekday
== 0 && $timezone->std_startday
== 0 && $timezone->std_weekday
== 0) {
1595 $monthdaydst = find_day_in_month($timezone->dst_startday
, $timezone->dst_weekday
, $timezone->dst_month
, $year);
1596 $monthdaystd = find_day_in_month($timezone->std_startday
, $timezone->std_weekday
, $timezone->std_month
, $year);
1598 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time
);
1599 list($std_hour, $std_min) = explode(':', $timezone->std_time
);
1601 $timedst = make_timestamp($year, $timezone->dst_month
, $monthdaydst, 0, 0, 0, 99, false);
1602 $timestd = make_timestamp($year, $timezone->std_month
, $monthdaystd, 0, 0, 0, 99, false);
1604 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1605 // This has the advantage of being able to have negative values for hour, i.e. for timezones
1606 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1608 $timedst +
= $dst_hour * HOURSECS +
$dst_min * MINSECS
;
1609 $timestd +
= $std_hour * HOURSECS +
$std_min * MINSECS
;
1611 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1614 // $time must NOT be compensated at all, it has to be a pure timestamp
1615 function dst_offset_on($time, $strtimezone = NULL) {
1618 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) ||
empty($SESSION->dst_offsets
)) {
1622 reset($SESSION->dst_offsets
);
1623 while(list($from, $offset) = each($SESSION->dst_offsets
)) {
1624 if($from <= $time) {
1629 // This is the normal return path
1630 if($offset !== NULL) {
1634 // Reaching this point means we haven't calculated far enough, do it now:
1635 // Calculate extra DST changes if needed and recurse. The recursion always
1636 // moves toward the stopping condition, so will always end.
1639 // We need a year smaller than $SESSION->dst_range[0]
1640 if($SESSION->dst_range
[0] == 1971) {
1643 calculate_user_dst_table($SESSION->dst_range
[0] - 5, NULL, $strtimezone);
1644 return dst_offset_on($time, $strtimezone);
1647 // We need a year larger than $SESSION->dst_range[1]
1648 if($SESSION->dst_range
[1] == 2035) {
1651 calculate_user_dst_table(NULL, $SESSION->dst_range
[1] +
5, $strtimezone);
1652 return dst_offset_on($time, $strtimezone);
1656 function find_day_in_month($startday, $weekday, $month, $year) {
1658 $daysinmonth = days_in_month($month, $year);
1660 if($weekday == -1) {
1661 // Don't care about weekday, so return:
1662 // abs($startday) if $startday != -1
1663 // $daysinmonth otherwise
1664 return ($startday == -1) ?
$daysinmonth : abs($startday);
1667 // From now on we 're looking for a specific weekday
1669 // Give "end of month" its actual value, since we know it
1670 if($startday == -1) {
1671 $startday = -1 * $daysinmonth;
1674 // Starting from day $startday, the sign is the direction
1678 $startday = abs($startday);
1679 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1681 // This is the last such weekday of the month
1682 $lastinmonth = $daysinmonth +
$weekday - $lastmonthweekday;
1683 if($lastinmonth > $daysinmonth) {
1687 // Find the first such weekday <= $startday
1688 while($lastinmonth > $startday) {
1692 return $lastinmonth;
1697 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1699 $diff = $weekday - $indexweekday;
1704 // This is the first such weekday of the month equal to or after $startday
1705 $firstfromindex = $startday +
$diff;
1707 return $firstfromindex;
1713 * Calculate the number of days in a given month
1715 * @param int $month The month whose day count is sought
1716 * @param int $year The year of the month whose day count is sought
1719 function days_in_month($month, $year) {
1720 return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1724 * Calculate the position in the week of a specific calendar day
1726 * @param int $day The day of the date whose position in the week is sought
1727 * @param int $month The month of the date whose position in the week is sought
1728 * @param int $year The year of the date whose position in the week is sought
1731 function dayofweek($day, $month, $year) {
1732 // I wonder if this is any different from
1733 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1734 return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1737 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1740 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1741 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1742 * sesskey string if $USER exists, or boolean false if not.
1747 function sesskey() {
1754 if (empty($USER->sesskey
)) {
1755 $USER->sesskey
= random_string(10);
1758 return $USER->sesskey
;
1763 * For security purposes, this function will check that the currently
1764 * given sesskey (passed as a parameter to the script or this function)
1765 * matches that of the current user.
1767 * @param string $sesskey optionally provided sesskey
1770 function confirm_sesskey($sesskey=NULL) {
1773 if (!empty($USER->ignoresesskey
) ||
!empty($CFG->ignoresesskey
)) {
1777 if (empty($sesskey)) {
1778 $sesskey = required_param('sesskey', PARAM_RAW
); // Check script parameters
1781 if (!isset($USER->sesskey
)) {
1785 return ($USER->sesskey
=== $sesskey);
1789 * Setup all global $CFG course variables, set locale and also themes
1790 * This function can be used on pages that do not require login instead of require_login()
1792 * @param mixed $courseorid id of the course or course object
1794 function course_setup($courseorid=0) {
1795 global $COURSE, $CFG, $SITE;
1797 /// Redefine global $COURSE if needed
1798 if (empty($courseorid)) {
1799 // no change in global $COURSE - for backwards compatibiltiy
1800 // if require_rogin() used after require_login($courseid);
1801 } else if (is_object($courseorid)) {
1802 $COURSE = clone($courseorid);
1804 global $course; // used here only to prevent repeated fetching from DB - may be removed later
1805 if ($courseorid == SITEID
) {
1806 $COURSE = clone($SITE);
1807 } else if (!empty($course->id
) and $course->id
== $courseorid) {
1808 $COURSE = clone($course);
1810 if (!$COURSE = get_record('course', 'id', $courseorid)) {
1811 error('Invalid course ID');
1816 /// set locale and themes
1823 * This function checks that the current user is logged in and has the
1824 * required privileges
1826 * This function checks that the current user is logged in, and optionally
1827 * whether they are allowed to be in a particular course and view a particular
1829 * If they are not logged in, then it redirects them to the site login unless
1830 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1831 * case they are automatically logged in as guests.
1832 * If $courseid is given and the user is not enrolled in that course then the
1833 * user is redirected to the course enrolment page.
1834 * If $cm is given and the coursemodule is hidden and the user is not a teacher
1835 * in the course then the user is redirected to the course home page.
1843 * @param mixed $courseorid id of the course or course object
1844 * @param bool $autologinguest
1845 * @param object $cm course module object
1846 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
1847 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
1848 * in order to keep redirects working properly. MDL-14495
1850 function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
1852 global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1854 /// setup global $COURSE, themes, language and locale
1855 course_setup($courseorid);
1857 /// If the user is not even logged in yet then make sure they are
1858 if (!isloggedin()) {
1859 //NOTE: $USER->site check was obsoleted by session test cookie,
1860 // $USER->confirmed test is in login/index.php
1861 if ($setwantsurltome) {
1862 $SESSION->wantsurl
= $FULLME;
1864 if (!empty($_SERVER['HTTP_REFERER'])) {
1865 $SESSION->fromurl
= $_SERVER['HTTP_REFERER'];
1867 if ($autologinguest and !empty($CFG->guestloginbutton
) and !empty($CFG->autologinguests
) and ($COURSE->id
== SITEID
or $COURSE->guest
) ) {
1868 $loginguest = '?loginguest=true';
1872 if (empty($CFG->loginhttps
) or $loginguest) { //do not require https for guest logins
1873 redirect($CFG->wwwroot
.'/login/index.php'. $loginguest);
1875 $wwwroot = str_replace('http:','https:', $CFG->wwwroot
);
1876 redirect($wwwroot .'/login/index.php');
1881 /// loginas as redirection if needed
1882 if ($COURSE->id
!= SITEID
and !empty($USER->realuser
)) {
1883 if ($USER->loginascontext
->contextlevel
== CONTEXT_COURSE
) {
1884 if ($USER->loginascontext
->instanceid
!= $COURSE->id
) {
1885 print_error('loginasonecourse', '', $CFG->wwwroot
.'/course/view.php?id='.$USER->loginascontext
->instanceid
);
1890 /// check whether the user should be changing password (but only if it is REALLY them)
1891 if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser
)) {
1892 $userauth = get_auth_plugin($USER->auth
);
1893 if ($userauth->can_change_password()) {
1894 $SESSION->wantsurl
= $FULLME;
1895 if ($changeurl = $userauth->change_password_url()) {
1896 //use plugin custom url
1897 redirect($changeurl);
1899 //use moodle internal method
1900 if (empty($CFG->loginhttps
)) {
1901 redirect($CFG->wwwroot
.'/login/change_password.php');
1903 $wwwroot = str_replace('http:','https:', $CFG->wwwroot
);
1904 redirect($wwwroot .'/login/change_password.php');
1908 print_error('nopasswordchangeforced', 'auth');
1912 /// Check that the user account is properly set up
1913 if (user_not_fully_set_up($USER)) {
1914 $SESSION->wantsurl
= $FULLME;
1915 redirect($CFG->wwwroot
.'/user/edit.php?id='. $USER->id
.'&course='. SITEID
);
1918 /// Make sure current IP matches the one for this session (if required)
1919 if (!empty($CFG->tracksessionip
)) {
1920 if ($USER->sessionIP
!= md5(getremoteaddr())) {
1921 print_error('sessionipnomatch', 'error');
1925 /// Make sure the USER has a sesskey set up. Used for checking script parameters.
1928 // Check that the user has agreed to a site policy if there is one
1929 if (!empty($CFG->sitepolicy
)) {
1930 if (!$USER->policyagreed
) {
1931 $SESSION->wantsurl
= $FULLME;
1932 redirect($CFG->wwwroot
.'/user/policy.php');
1936 // Fetch the system context, we are going to use it a lot.
1937 $sysctx = get_context_instance(CONTEXT_SYSTEM
);
1939 /// If the site is currently under maintenance, then print a message
1940 if (!has_capability('moodle/site:config', $sysctx)) {
1941 if (file_exists($CFG->dataroot
.'/'.SITEID
.'/maintenance.html')) {
1942 print_maintenance_message();
1947 /// groupmembersonly access control
1948 if (!empty($CFG->enablegroupings
) and $cm and $cm->groupmembersonly
and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE
, $cm->id
))) {
1949 if (isguestuser() or !groups_has_membership($cm)) {
1950 print_error('groupmembersonlyerror', 'group', $CFG->wwwroot
.'/course/view.php?id='.$cm->course
);
1954 // Fetch the course context, and prefetch its child contexts
1955 if (!isset($COURSE->context
)) {
1956 if ( ! $COURSE->context
= get_context_instance(CONTEXT_COURSE
, $COURSE->id
) ) {
1957 print_error('nocontext');
1960 if ($COURSE->id
== SITEID
) {
1961 /// Eliminate hidden site activities straight away
1962 if (!empty($cm) && !$cm->visible
1963 && !has_capability('moodle/course:viewhiddenactivities', $COURSE->context
)) {
1964 redirect($CFG->wwwroot
, get_string('activityiscurrentlyhidden'));
1966 user_accesstime_log($COURSE->id
); /// Access granted, update lastaccess times
1971 /// Check if the user can be in a particular course
1972 if (empty($USER->access
['rsw'][$COURSE->context
->path
])) {
1974 // MDL-13900 - If the course or the parent category are hidden
1975 // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
1977 if ( !($COURSE->visible
&& course_parent_visible($COURSE)) &&
1978 !has_capability('moodle/course:viewhiddencourses', $COURSE->context
)) {
1979 print_header_simple();
1980 notice(get_string('coursehidden'), $CFG->wwwroot
.'/');
1984 /// Non-guests who don't currently have access, check if they can be allowed in as a guest
1986 if ($USER->username
!= 'guest' and !has_capability('moodle/course:view', $COURSE->context
)) {
1987 if ($COURSE->guest
== 1) {
1988 // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
1989 $USER->access
= load_temp_role($COURSE->context
, $CFG->guestroleid
, $USER->access
);
1993 /// If the user is a guest then treat them according to the course policy about guests
1995 if (has_capability('moodle/legacy:guest', $COURSE->context
, NULL, false)) {
1996 if (has_capability('moodle/site:doanything', $sysctx)) {
1997 // administrators must be able to access any course - even if somebody gives them guest access
1998 user_accesstime_log($COURSE->id
); /// Access granted, update lastaccess times
2002 switch ($COURSE->guest
) { /// Check course policy about guest access
2004 case 1: /// Guests always allowed
2005 if (!has_capability('moodle/course:view', $COURSE->context
)) { // Prohibited by capability
2006 print_header_simple();
2007 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname
)), "$CFG->wwwroot/login/index.php");
2009 if (!empty($cm) and !$cm->visible
) { // Not allowed to see module, send to course page
2010 redirect($CFG->wwwroot
.'/course/view.php?id='.$cm->course
,
2011 get_string('activityiscurrentlyhidden'));
2014 user_accesstime_log($COURSE->id
); /// Access granted, update lastaccess times
2015 return; // User is allowed to see this course
2019 case 2: /// Guests allowed with key
2020 if (!empty($USER->enrolkey
[$COURSE->id
])) { // Set by enrol/manual/enrol.php
2021 user_accesstime_log($COURSE->id
); /// Access granted, update lastaccess times
2024 // otherwise drop through to logic below (--> enrol.php)
2027 default: /// Guests not allowed
2028 $strloggedinasguest = get_string('loggedinasguest');
2029 print_header_simple('', '',
2030 build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
2031 if (empty($USER->access
['rsw'][$COURSE->context
->path
])) { // Normal guest
2032 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname
)), "$CFG->wwwroot/login/index.php");
2034 notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname
)));
2035 echo '<div class="notifyproblem">'.switchroles_form($COURSE->id
).'</div>';
2036 print_footer($COURSE);
2042 /// For non-guests, check if they have course view access
2044 } else if (has_capability('moodle/course:view', $COURSE->context
)) {
2045 if (!empty($USER->realuser
)) { // Make sure the REAL person can also access this course
2046 if (!has_capability('moodle/course:view', $COURSE->context
, $USER->realuser
)) {
2047 print_header_simple();
2048 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot
.'/');
2052 /// Make sure they can read this activity too, if specified
2054 if (!empty($cm) and !$cm->visible
and !has_capability('moodle/course:viewhiddenactivities', $COURSE->context
)) {
2055 redirect($CFG->wwwroot
.'/course/view.php?id='.$cm->course
, get_string('activityiscurrentlyhidden'));
2057 user_accesstime_log($COURSE->id
); /// Access granted, update lastaccess times
2058 return; // User is allowed to see this course
2063 /// Currently not enrolled in the course, so see if they want to enrol
2064 $SESSION->wantsurl
= $FULLME;
2065 redirect($CFG->wwwroot
.'/course/enrol.php?id='. $COURSE->id
);
2073 * This function just makes sure a user is logged out.
2078 function require_logout() {
2080 global $USER, $CFG, $SESSION;
2083 add_to_log(SITEID
, "user", "logout", "view.php?id=$USER->id&course=".SITEID
, $USER->id
, 0, $USER->id
);
2085 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2086 foreach($authsequence as $authname) {
2087 $authplugin = get_auth_plugin($authname);
2088 $authplugin->prelogout_hook();
2092 if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
2093 // This method is just to try to avoid silly warnings from PHP 4.3.0
2094 session_unregister("USER");
2095 session_unregister("SESSION");
2098 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
2099 $file = $line = null;
2100 if (headers_sent($file, $line)) {
2101 error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__
);
2102 error_log('Headers were already sent in file: '.$file.' on line '.$line);
2104 if (check_php_version('5.2.0')) {
2105 setcookie('MoodleSessionTest'.$CFG->sessioncookie
, '', time() - 3600, $CFG->sessioncookiepath
, '', $CFG->cookiesecure
, $CFG->cookiehttponly
);
2107 setcookie('MoodleSessionTest'.$CFG->sessioncookie
, '', time() - 3600, $CFG->sessioncookiepath
, '', $CFG->cookiesecure
);
2111 unset($_SESSION['USER']);
2112 unset($_SESSION['SESSION']);
2120 * This is a weaker version of {@link require_login()} which only requires login
2121 * when called from within a course rather than the site page, unless
2122 * the forcelogin option is turned on.
2125 * @param mixed $courseorid The course object or id in question
2126 * @param bool $autologinguest Allow autologin guests if that is wanted
2127 * @param object $cm Course activity module if known
2128 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2129 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2130 * in order to keep redirects working properly. MDL-14495
2132 function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
2134 if (!empty($CFG->forcelogin
)) {
2135 // login required for both SITE and courses
2136 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2138 } else if (!empty($cm) and !$cm->visible
) {
2139 // always login for hidden activities
2140 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2142 } else if ((is_object($courseorid) and $courseorid->id
== SITEID
)
2143 or (!is_object($courseorid) and $courseorid == SITEID
)) {
2144 //login for SITE not required
2145 user_accesstime_log(SITEID
);
2149 // course login always required
2150 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2155 * Require key login. Function terminates with error if key not found or incorrect.
2156 * @param string $script unique script identifier
2157 * @param int $instance optional instance id
2159 function require_user_key_login($script, $instance=null) {
2160 global $nomoodlecookie, $USER, $SESSION, $CFG;
2162 if (empty($nomoodlecookie)) {
2163 error('Incorrect use of require_key_login() - session cookies must be disabled!');
2167 @session_write_close
();
2169 $keyvalue = required_param('key', PARAM_ALPHANUM
);
2171 if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
2172 error('Incorrect key');
2175 if (!empty($key->validuntil
) and $key->validuntil
< time()) {
2176 error('Expired key');
2179 if ($key->iprestriction
) {
2180 $remoteaddr = getremoteaddr();
2181 if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction
)) {
2182 error('Client IP address mismatch');
2186 if (!$user = get_record('user', 'id', $key->userid
)) {
2187 error('Incorrect user record');
2190 /// emulate normal session
2191 $SESSION = new object();
2194 /// note we are not using normal login
2195 if (!defined('USER_KEY_LOGIN')) {
2196 define('USER_KEY_LOGIN', true);
2199 load_all_capabilities();
2201 /// return isntance id - it might be empty
2202 return $key->instance
;
2206 * Creates a new private user access key.
2207 * @param string $script unique target identifier
2208 * @param int $userid
2209 * @param instance $int optional instance id
2210 * @param string $iprestriction optional ip restricted access
2211 * @param timestamp $validuntil key valid only until given data
2212 * @return string access key value
2214 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2215 $key = new object();
2216 $key->script
= $script;
2217 $key->userid
= $userid;
2218 $key->instance
= $instance;
2219 $key->iprestriction
= $iprestriction;
2220 $key->validuntil
= $validuntil;
2221 $key->timecreated
= time();
2223 $key->value
= md5($userid.'_'.time().random_string(40)); // something long and unique
2224 while (record_exists('user_private_key', 'value', $key->value
)) {
2226 $key->value
= md5($userid.'_'.time().random_string(40));
2229 if (!insert_record('user_private_key', $key)) {
2230 error('Can not insert new key');
2237 * Modify the user table by setting the currently logged in user's
2238 * last login to now.
2243 function update_user_login_times() {
2246 $user = new object();
2247 $USER->lastlogin
= $user->lastlogin
= $USER->currentlogin
;
2248 $USER->currentlogin
= $user->lastaccess
= $user->currentlogin
= time();
2250 $user->id
= $USER->id
;
2252 return update_record('user', $user);
2256 * Determines if a user has completed setting up their account.
2258 * @param user $user A {@link $USER} object to test for the existance of a valid name and email
2261 function user_not_fully_set_up($user) {
2262 return ($user->username
!= 'guest' and (empty($user->firstname
) or empty($user->lastname
) or empty($user->email
) or over_bounce_threshold($user)));
2265 function over_bounce_threshold($user) {
2269 if (empty($CFG->handlebounces
)) {
2272 // set sensible defaults
2273 if (empty($CFG->minbounces
)) {
2274 $CFG->minbounces
= 10;
2276 if (empty($CFG->bounceratio
)) {
2277 $CFG->bounceratio
= .20;
2281 if ($bounce = get_record('user_preferences','userid',$user->id
,'name','email_bounce_count')) {
2282 $bouncecount = $bounce->value
;
2284 if ($send = get_record('user_preferences','userid',$user->id
,'name','email_send_count')) {
2285 $sendcount = $send->value
;
2287 return ($bouncecount >= $CFG->minbounces
&& $bouncecount/$sendcount >= $CFG->bounceratio
);
2291 * @param $user - object containing an id
2292 * @param $reset - will reset the count to 0
2294 function set_send_count($user,$reset=false) {
2295 if ($pref = get_record('user_preferences','userid',$user->id
,'name','email_send_count')) {
2296 $pref->value
= (!empty($reset)) ?
0 : $pref->value+
1;
2297 update_record('user_preferences',$pref);
2299 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2301 $pref->name
= 'email_send_count';
2303 $pref->userid
= $user->id
;
2304 insert_record('user_preferences',$pref, false);
2309 * @param $user - object containing an id
2310 * @param $reset - will reset the count to 0
2312 function set_bounce_count($user,$reset=false) {
2313 if ($pref = get_record('user_preferences','userid',$user->id
,'name','email_bounce_count')) {
2314 $pref->value
= (!empty($reset)) ?
0 : $pref->value+
1;
2315 update_record('user_preferences',$pref);
2317 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2319 $pref->name
= 'email_bounce_count';
2321 $pref->userid
= $user->id
;
2322 insert_record('user_preferences',$pref, false);
2327 * Keeps track of login attempts
2331 function update_login_count() {
2337 if (empty($SESSION->logincount
)) {
2338 $SESSION->logincount
= 1;
2340 $SESSION->logincount++
;
2343 if ($SESSION->logincount
> $max_logins) {
2344 unset($SESSION->wantsurl
);
2345 print_error('errortoomanylogins');
2350 * Resets login attempts
2354 function reset_login_count() {
2357 $SESSION->logincount
= 0;
2360 function sync_metacourses() {
2364 if (!$courses = get_records('course', 'metacourse', 1)) {
2368 foreach ($courses as $course) {
2369 sync_metacourse($course);
2374 * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2376 * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2378 function sync_metacourse($course) {
2381 // Check the course is valid.
2382 if (!is_object($course)) {
2383 if (!$course = get_record('course', 'id', $course)) {
2384 return false; // invalid course id
2388 // Check that we actually have a metacourse.
2389 if (empty($course->metacourse
)) {
2393 // Get a list of roles that should not be synced.
2394 if (!empty($CFG->nonmetacoursesyncroleids
)) {
2395 $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids
. ') AND';
2397 $roleexclusions = '';
2400 // Get the context of the metacourse.
2401 $context = get_context_instance(CONTEXT_COURSE
, $course->id
); // SITEID can not be a metacourse
2403 // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2404 if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2405 $managers = array_keys($users);
2407 $managers = array();
2410 // Get assignments of a user to a role that exist in a child course, but
2411 // not in the meta coure. That is, get a list of the assignments that need to be made.
2412 if (!$assignments = get_records_sql("
2414 ra.id, ra.roleid, ra.userid
2416 {$CFG->prefix}role_assignments ra,
2417 {$CFG->prefix}context con,
2418 {$CFG->prefix}course_meta cm
2420 ra.contextid = con.id AND
2421 con.contextlevel = " . CONTEXT_COURSE
. " AND
2422 con.instanceid = cm.child_course AND
2423 cm.parent_course = {$course->id} AND
2427 {$CFG->prefix}role_assignments ra2
2429 ra2.userid = ra.userid AND
2430 ra2.roleid = ra.roleid AND
2431 ra2.contextid = {$context->id}
2434 $assignments = array();
2437 // Get assignments of a user to a role that exist in the meta course, but
2438 // not in any child courses. That is, get a list of the unassignments that need to be made.
2439 if (!$unassignments = get_records_sql("
2441 ra.id, ra.roleid, ra.userid
2443 {$CFG->prefix}role_assignments ra
2445 ra.contextid = {$context->id} AND
2449 {$CFG->prefix}role_assignments ra2,
2450 {$CFG->prefix}context con2,
2451 {$CFG->prefix}course_meta cm
2453 ra2.userid = ra.userid AND
2454 ra2.roleid = ra.roleid AND
2455 ra2.contextid = con2.id AND
2456 con2.contextlevel = " . CONTEXT_COURSE
. " AND
2457 con2.instanceid = cm.child_course AND
2458 cm.parent_course = {$course->id}
2461 $unassignments = array();
2466 // Make the unassignments, if they are not managers.
2467 foreach ($unassignments as $unassignment) {
2468 if (!in_array($unassignment->userid
, $managers)) {
2469 $success = role_unassign($unassignment->roleid
, $unassignment->userid
, 0, $context->id
) && $success;
2473 // Make the assignments.
2474 foreach ($assignments as $assignment) {
2475 $success = role_assign($assignment->roleid
, $assignment->userid
, 0, $context->id
) && $success;
2480 // TODO: finish timeend and timestart
2481 // maybe we could rely on cron job to do the cleaning from time to time
2485 * Adds a record to the metacourse table and calls sync_metacoures
2487 function add_to_metacourse ($metacourseid, $courseid) {
2489 if (!$metacourse = get_record("course","id",$metacourseid)) {
2493 if (!$course = get_record("course","id",$courseid)) {
2497 if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2498 $rec = new object();
2499 $rec->parent_course
= $metacourseid;
2500 $rec->child_course
= $courseid;
2501 if (!insert_record('course_meta',$rec)) {
2504 return sync_metacourse($metacourseid);
2511 * Removes the record from the metacourse table and calls sync_metacourse
2513 function remove_from_metacourse($metacourseid, $courseid) {
2515 if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2516 return sync_metacourse($metacourseid);
2523 * Determines if a user is currently logged in
2528 function isloggedin() {
2531 return (!empty($USER->id
));
2535 * Determines if a user is logged in as real guest user with username 'guest'.
2536 * This function is similar to original isguest() in 1.6 and earlier.
2537 * Current isguest() is deprecated - do not use it anymore.
2539 * @param $user mixed user object or id, $USER if not specified
2540 * @return bool true if user is the real guest user, false if not logged in or other user
2542 function isguestuser($user=NULL) {
2544 if ($user === NULL) {
2546 } else if (is_numeric($user)) {
2547 $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2550 if (empty($user->id
)) {
2551 return false; // not logged in, can not be guest
2554 return ($user->username
== 'guest');
2558 * Determines if the currently logged in user is in editing mode.
2559 * Note: originally this function had $userid parameter - it was not usable anyway
2561 * @uses $USER, $PAGE
2564 function isediting() {
2565 global $USER, $PAGE;
2567 if (empty($USER->editing
)) {
2569 } elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
2570 return $PAGE->user_allowed_editing();
2572 return true;//false;
2576 * Determines if the logged in user is currently moving an activity
2579 * @param int $courseid The id of the course being tested
2582 function ismoving($courseid) {
2585 if (!empty($USER->activitycopy
)) {
2586 return ($USER->activitycopycourse
== $courseid);
2592 * Given an object containing firstname and lastname
2593 * values, this function returns a string with the
2594 * full name of the person.
2595 * The result may depend on system settings
2596 * or language. 'override' will force both names
2597 * to be used even if system settings specify one.
2601 * @param object $user A {@link $USER} object to get full name of
2602 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2604 function fullname($user, $override=false) {
2606 global $CFG, $SESSION;
2608 if (!isset($user->firstname
) and !isset($user->lastname
)) {
2613 if (!empty($CFG->forcefirstname
)) {
2614 $user->firstname
= $CFG->forcefirstname
;
2616 if (!empty($CFG->forcelastname
)) {
2617 $user->lastname
= $CFG->forcelastname
;
2621 if (!empty($SESSION->fullnamedisplay
)) {
2622 $CFG->fullnamedisplay
= $SESSION->fullnamedisplay
;
2625 if ($CFG->fullnamedisplay
== 'firstname lastname') {
2626 return $user->firstname
.' '. $user->lastname
;
2628 } else if ($CFG->fullnamedisplay
== 'lastname firstname') {
2629 return $user->lastname
.' '. $user->firstname
;
2631 } else if ($CFG->fullnamedisplay
== 'firstname') {
2633 return get_string('fullnamedisplay', '', $user);
2635 return $user->firstname
;
2639 return get_string('fullnamedisplay', '', $user);
2643 * Sets a moodle cookie with an encrypted string
2648 * @param string $thing The string to encrypt and place in a cookie
2650 function set_moodle_cookie($thing) {
2653 if ($thing == 'guest') { // Ignore guest account
2657 $cookiename = 'MOODLEID_'.$CFG->sessioncookie
;
2660 $seconds = DAYSECS
*$days;
2662 // no need to set secure or http cookie only here - it is not secret
2663 setCookie($cookiename, '', time() - HOURSECS
, $CFG->sessioncookiepath
);
2664 setCookie($cookiename, rc4encrypt($thing), time()+
$seconds, $CFG->sessioncookiepath
);
2668 * Gets a moodle cookie with an encrypted string
2673 function get_moodle_cookie() {
2676 $cookiename = 'MOODLEID_'.$CFG->sessioncookie
;
2678 if (empty($_COOKIE[$cookiename])) {
2681 $thing = rc4decrypt($_COOKIE[$cookiename]);
2682 return ($thing == 'guest') ?
'': $thing; // Ignore guest account
2687 * Returns whether a given authentication plugin exists.
2690 * @param string $auth Form of authentication to check for. Defaults to the
2691 * global setting in {@link $CFG}.
2692 * @return boolean Whether the plugin is available.
2694 function exists_auth_plugin($auth) {
2697 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2698 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2704 * Checks if a given plugin is in the list of enabled authentication plugins.
2706 * @param string $auth Authentication plugin.
2707 * @return boolean Whether the plugin is enabled.
2709 function is_enabled_auth($auth) {
2714 $enabled = get_enabled_auth_plugins();
2716 return in_array($auth, $enabled);
2720 * Returns an authentication plugin instance.
2723 * @param string $auth name of authentication plugin
2724 * @return object An instance of the required authentication plugin.
2726 function get_auth_plugin($auth) {
2729 // check the plugin exists first
2730 if (! exists_auth_plugin($auth)) {
2731 error("Authentication plugin '$auth' not found.");
2734 // return auth plugin instance
2735 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2736 $class = "auth_plugin_$auth";
2741 * Returns array of active auth plugins.
2743 * @param bool $fix fix $CFG->auth if needed
2746 function get_enabled_auth_plugins($fix=false) {
2749 $default = array('manual', 'nologin');
2751 if (empty($CFG->auth
)) {
2754 $auths = explode(',', $CFG->auth
);
2758 $auths = array_unique($auths);
2759 foreach($auths as $k=>$authname) {
2760 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
2764 $newconfig = implode(',', $auths);
2765 if (!isset($CFG->auth
) or $newconfig != $CFG->auth
) {
2766 set_config('auth', $newconfig);
2770 return (array_merge($default, $auths));
2774 * Returns true if an internal authentication method is being used.
2775 * if method not specified then, global default is assumed
2778 * @param string $auth Form of authentication required
2781 function is_internal_auth($auth) {
2782 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2783 return $authplugin->is_internal();
2787 * Returns an array of user fields
2791 * @return array User field/column names
2793 function get_user_fieldnames() {
2797 $fieldarray = $db->MetaColumnNames($CFG->prefix
.'user');
2798 unset($fieldarray['ID']);
2804 * Creates the default "guest" user. Used both from
2805 * admin/index.php and login/index.php
2806 * @return mixed user object created or boolean false if the creation has failed
2808 function create_guest_record() {
2812 $guest = new stdClass();
2813 $guest->auth
= 'manual';
2814 $guest->username
= 'guest';
2815 $guest->password
= hash_internal_user_password('guest');
2816 $guest->firstname
= addslashes(get_string('guestuser'));
2817 $guest->lastname
= ' ';
2818 $guest->email
= 'root@localhost';
2819 $guest->description
= addslashes(get_string('guestuserinfo'));
2820 $guest->mnethostid
= $CFG->mnet_localhost_id
;
2821 $guest->confirmed
= 1;
2822 $guest->lang
= $CFG->lang
;
2823 $guest->timemodified
= time();
2825 if (! $guest->id
= insert_record("user", $guest)) {
2833 * Creates a bare-bones user record
2836 * @param string $username New user's username to add to record
2837 * @param string $password New user's password to add to record
2838 * @param string $auth Form of authentication required
2839 * @return object A {@link $USER} object
2840 * @todo Outline auth types and provide code example
2842 function create_user_record($username, $password, $auth='manual') {
2845 //just in case check text case
2846 $username = trim(moodle_strtolower($username));
2848 $authplugin = get_auth_plugin($auth);
2850 if ($newinfo = $authplugin->get_userinfo($username)) {
2851 $newinfo = truncate_userinfo($newinfo);
2852 foreach ($newinfo as $key => $value){
2853 $newuser->$key = addslashes($value);
2857 if (!empty($newuser->email
)) {
2858 if (email_is_not_allowed($newuser->email
)) {
2859 unset($newuser->email
);
2863 $newuser->auth
= $auth;
2864 $newuser->username
= $username;
2867 // user CFG lang for user if $newuser->lang is empty
2868 // or $user->lang is not an installed language
2869 $sitelangs = array_keys(get_list_of_languages());
2870 if (empty($newuser->lang
) ||
!in_array($newuser->lang
, $sitelangs)) {
2871 $newuser -> lang
= $CFG->lang
;
2873 $newuser->confirmed
= 1;
2874 $newuser->lastip
= getremoteaddr();
2875 $newuser->timemodified
= time();
2876 $newuser->mnethostid
= $CFG->mnet_localhost_id
;
2878 if (insert_record('user', $newuser)) {
2879 $user = get_complete_user_data('username', $newuser->username
);
2880 if(!empty($CFG->{'auth_'.$newuser->auth
.'_forcechangepassword'})){
2881 set_user_preference('auth_forcepasswordchange', 1, $user->id
);
2883 update_internal_user_password($user, $password);
2890 * Will update a local user record from an external source
2893 * @param string $username New user's username to add to record
2894 * @return user A {@link $USER} object
2896 function update_user_record($username, $authplugin) {
2897 $username = trim(moodle_strtolower($username)); /// just in case check text case
2899 $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2900 $userauth = get_auth_plugin($oldinfo->auth
);
2902 if ($newinfo = $userauth->get_userinfo($username)) {
2903 $newinfo = truncate_userinfo($newinfo);
2904 foreach ($newinfo as $key => $value){
2905 if ($key === 'username') {
2906 // 'username' is not a mapped updateable/lockable field, so skip it.
2909 $confval = $userauth->config
->{'field_updatelocal_' . $key};
2910 $lockval = $userauth->config
->{'field_lock_' . $key};
2911 if (empty($confval) ||
empty($lockval)) {
2914 if ($confval === 'onlogin') {
2915 $value = addslashes(stripslashes($value)); // Just in case
2916 // MDL-4207 Don't overwrite modified user profile values with
2917 // empty LDAP values when 'unlocked if empty' is set. The purpose
2918 // of the setting 'unlocked if empty' is to allow the user to fill
2919 // in a value for the selected field _if LDAP is giving
2920 // nothing_ for this field. Thus it makes sense to let this value
2921 // stand in until LDAP is giving a value for this field.
2922 if (!(empty($value) && $lockval === 'unlockedifempty')) {
2923 set_field('user', $key, $value, 'username', $username)
2924 ||
error_log("Error updating $key for $username");
2930 return get_complete_user_data('username', $username);
2933 function truncate_userinfo($info) {
2934 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
2935 /// which may have large fields
2937 // define the limits
2947 'institution' => 40,
2955 // apply where needed
2956 foreach (array_keys($info) as $key) {
2957 if (!empty($limit[$key])) {
2958 $info[$key] = trim(substr($info[$key],0, $limit[$key]));
2966 * Marks user deleted in internal user database and notifies the auth plugin.
2967 * Also unenrols user from all roles and does other cleanup.
2968 * @param object $user Userobject before delete (without system magic quotes)
2969 * @return boolean success
2971 function delete_user($user) {
2973 require_once($CFG->libdir
.'/grouplib.php');
2974 require_once($CFG->libdir
.'/gradelib.php');
2978 // delete all grades - backup is kept in grade_grades_history table
2979 if ($grades = grade_grade
::fetch_all(array('userid'=>$user->id
))) {
2980 foreach ($grades as $grade) {
2981 $grade->delete('userdelete');
2985 // remove from all groups
2986 delete_records('groups_members', 'userid', $user->id
);
2988 // unenrol from all roles in all contexts
2989 role_unassign(0, $user->id
); // this might be slow but it is really needed - modules might do some extra cleanup!
2991 // now do a final accesslib cleanup - removes all role assingments in user context and context itself
2992 delete_context(CONTEXT_USER
, $user->id
);
2994 require_once($CFG->dirroot
.'/tag/lib.php');
2995 tag_set('user', $user->id
, array());
2997 // workaround for bulk deletes of users with the same email address
2998 $delname = addslashes("$user->email.".time());
2999 while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
3003 // mark internal user record as "deleted"
3004 $updateuser = new object();
3005 $updateuser->id
= $user->id
;
3006 $updateuser->deleted
= 1;
3007 $updateuser->username
= $delname; // Remember it just in case
3008 $updateuser->email
= ''; // Clear this field to free it up
3009 $updateuser->idnumber
= ''; // Clear this field to free it up
3010 $updateuser->timemodified
= time();
3012 if (update_record('user', $updateuser)) {
3014 // notify auth plugin - do not block the delete even when plugin fails
3015 $authplugin = get_auth_plugin($user->auth
);
3016 $authplugin->user_delete($user);
3018 events_trigger('user_deleted', $user);
3028 * Retrieve the guest user object
3031 * @return user A {@link $USER} object
3033 function guest_user() {
3036 if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id
)) {
3037 $newuser->confirmed
= 1;
3038 $newuser->lang
= $CFG->lang
;
3039 $newuser->lastip
= getremoteaddr();
3046 * Given a username and password, this function looks them
3047 * up using the currently selected authentication mechanism,
3048 * and if the authentication is successful, it returns a
3049 * valid $user object from the 'user' table.
3051 * Uses auth_ functions from the currently active auth module
3053 * After authenticate_user_login() returns success, you will need to
3054 * log that the user has logged in, and call complete_user_login() to set
3058 * @param string $username User's username (with system magic quotes)
3059 * @param string $password User's password (with system magic quotes)
3060 * @return user|flase A {@link $USER} object or false if error
3062 function authenticate_user_login($username, $password) {
3066 $authsenabled = get_enabled_auth_plugins();
3068 if ($user = get_complete_user_data('username', $username)) {
3069 $auth = empty($user->auth
) ?
'manual' : $user->auth
; // use manual if auth not set
3070 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3071 add_to_log(0, 'login', 'error', 'index.php', $username);
3072 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3075 if (!empty($user->deleted
)) {
3076 add_to_log(0, 'login', 'error', 'index.php', $username);
3077 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3080 $auths = array($auth);
3083 $auths = $authsenabled;
3084 $user = new object();
3085 $user->id
= 0; // User does not exist
3088 foreach ($auths as $auth) {
3089 $authplugin = get_auth_plugin($auth);
3091 // on auth fail fall through to the next plugin
3092 if (!$authplugin->user_login($username, $password)) {
3096 // successful authentication
3097 if ($user->id
) { // User already exists in database
3098 if (empty($user->auth
)) { // For some reason auth isn't set yet
3099 set_field('user', 'auth', $auth, 'username', $username);
3100 $user->auth
= $auth;
3103 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3105 if (!$authplugin->is_internal()) { // update user record from external DB
3106 $user = update_user_record($username, get_auth_plugin($user->auth
));
3109 // if user not found, create him
3110 $user = create_user_record($username, $password, $auth);
3113 $authplugin->sync_roles($user);
3115 foreach ($authsenabled as $hau) {
3116 $hauth = get_auth_plugin($hau);
3117 $hauth->user_authenticated_hook($user, $username, $password);
3120 /// Log in to a second system if necessary
3121 /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
3122 if (!empty($CFG->sso
)) {
3123 include_once($CFG->dirroot
.'/sso/'. $CFG->sso
.'/lib.php');
3124 if (function_exists('sso_user_login')) {
3125 if (!sso_user_login($username, $password)) { // Perform the signon process
3126 notify('Second sign-on failed');
3131 if ($user->id
===0) {
3137 // failed if all the plugins have failed
3138 add_to_log(0, 'login', 'error', 'index.php', $username);
3139 if (debugging('', DEBUG_ALL
)) {
3140 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3146 * Call to complete the user login process after authenticate_user_login()
3147 * has succeeded. It will setup the $USER variable and other required bits
3151 * - It will NOT log anything -- up to the caller to decide what to log.
3156 * @param string $user obj
3157 * @return user|flase A {@link $USER} object or false if error
3159 function complete_user_login($user) {
3162 $USER = $user; // this is required because we need to access preferences here!
3164 reload_user_preferences();
3166 update_user_login_times();
3167 if (empty($CFG->nolastloggedin
)) {
3168 set_moodle_cookie($USER->username
);
3170 // do not store last logged in user in cookie
3171 // auth plugins can temporarily override this from loginpage_hook()
3172 // do not save $CFG->nolastloggedin in database!
3173 set_moodle_cookie('nobody');
3175 set_login_session_preferences();
3177 // Call enrolment plugins
3178 check_enrolment_plugins($user);
3180 /// This is what lets the user do anything on the site :-)
3181 load_all_capabilities();
3183 /// Select password change url
3184 $userauth = get_auth_plugin($USER->auth
);
3186 /// check whether the user should be changing password
3187 if (get_user_preferences('auth_forcepasswordchange', false)){
3188 if ($userauth->can_change_password()) {
3189 if ($changeurl = $userauth->change_password_url()) {
3190 redirect($changeurl);
3192 redirect($CFG->httpswwwroot
.'/login/change_password.php');
3195 print_error('nopasswordchangeforced', 'auth');
3202 * Compare password against hash stored in internal user table.
3203 * If necessary it also updates the stored hash to new format.
3205 * @param object user
3206 * @param string plain text password
3207 * @return bool is password valid?
3209 function validate_internal_user_password(&$user, $password) {
3212 if (!isset($CFG->passwordsaltmain
)) {
3213 $CFG->passwordsaltmain
= '';
3218 // get password original encoding in case it was not updated to unicode yet
3219 $textlib = textlib_get_instance();
3220 $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
3222 if ($user->password
== md5($password.$CFG->passwordsaltmain
) or $user->password
== md5($password)
3223 or $user->password
== md5($convpassword.$CFG->passwordsaltmain
) or $user->password
== md5($convpassword)) {
3226 for ($i=1; $i<=20; $i++
) { //20 alternative salts should be enough, right?
3227 $alt = 'passwordsaltalt'.$i;
3228 if (!empty($CFG->$alt)) {
3229 if ($user->password
== md5($password.$CFG->$alt) or $user->password
== md5($convpassword.$CFG->$alt)) {
3238 // force update of password hash using latest main password salt and encoding if needed
3239 update_internal_user_password($user, $password);
3246 * Calculate hashed value from password using current hash mechanism.
3248 * @param string password
3249 * @return string password hash
3251 function hash_internal_user_password($password) {
3254 if (isset($CFG->passwordsaltmain
)) {
3255 return md5($password.$CFG->passwordsaltmain
);
3257 return md5($password);
3262 * Update pssword hash in user object.
3264 * @param object user
3265 * @param string plain text password
3266 * @param bool store changes also in db, default true
3267 * @return true if hash changed
3269 function update_internal_user_password(&$user, $password) {
3272 $authplugin = get_auth_plugin($user->auth
);
3273 if (!empty($authplugin->config
->preventpassindb
)) {
3274 $hashedpassword = 'not cached';
3276 $hashedpassword = hash_internal_user_password($password);
3279 return set_field('user', 'password', $hashedpassword, 'id', $user->id
);
3283 * Get a complete user record, which includes all the info
3284 * in the user record
3285 * Intended for setting as $USER session variable
3289 * @param string $field The user field to be checked for a given value.
3290 * @param string $value The value to match for $field.
3291 * @return user A {@link $USER} object.
3293 function get_complete_user_data($field, $value, $mnethostid=null) {
3297 if (!$field ||
!$value) {
3301 /// Build the WHERE clause for an SQL query
3303 $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
3305 // If we are loading user data based on anything other than id,
3306 // we must also restrict our search based on mnet host.
3307 if ($field != 'id') {
3308 if (empty($mnethostid)) {
3309 // if empty, we restrict to local users
3310 $mnethostid = $CFG->mnet_localhost_id
;
3313 if (!empty($mnethostid)) {
3314 $mnethostid = (int)$mnethostid;
3315 $constraints .= ' AND mnethostid = ' . $mnethostid;
3318 /// Get all the basic user data
3320 if (! $user = get_record_select('user', $constraints)) {
3324 /// Get various settings and preferences
3326 if ($displays = get_records('course_display', 'userid', $user->id
)) {
3327 foreach ($displays as $display) {
3328 $user->display
[$display->course
] = $display->display
;
3332 $user->preference
= get_user_preferences(null, null, $user->id
);
3334 $user->lastcourseaccess
= array(); // during last session
3335 $user->currentcourseaccess
= array(); // during current session
3336 if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id
)) {
3337 foreach ($lastaccesses as $lastaccess) {
3338 $user->lastcourseaccess
[$lastaccess->courseid
] = $lastaccess->timeaccess
;
3342 $sql = "SELECT g.id, g.courseid
3343 FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
3344 WHERE gm.groupid=g.id AND gm.userid={$user->id}";
3346 // this is a special hack to speedup calendar display
3347 $user->groupmember
= array();
3348 if ($groups = get_records_sql($sql)) {
3349 foreach ($groups as $group) {
3350 if (!array_key_exists($group->courseid
, $user->groupmember
)) {
3351 $user->groupmember
[$group->courseid
] = array();
3353 $user->groupmember
[$group->courseid
][$group->id
] = $group->id
;
3357 /// Add the custom profile fields to the user record
3358 include_once($CFG->dirroot
.'/user/profile/lib.php');
3359 $customfields = (array)profile_user_record($user->id
);
3360 foreach ($customfields as $cname=>$cvalue) {
3361 if (!isset($user->$cname)) { // Don't overwrite any standard fields
3362 $user->$cname = $cvalue;
3366 /// Rewrite some variables if necessary
3367 if (!empty($user->description
)) {
3368 $user->description
= true; // No need to cart all of it around
3370 if ($user->username
== 'guest') {
3371 $user->lang
= $CFG->lang
; // Guest language always same as site
3372 $user->firstname
= get_string('guestuser'); // Name always in current language
3373 $user->lastname
= ' ';
3376 $user->sesskey
= random_string(10);
3377 $user->sessionIP
= md5(getremoteaddr()); // Store the current IP in the session
3384 * @param string $password the password to be checked agains the password policy
3385 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3386 * @return bool true if the password is valid according to the policy. false otherwise.
3388 function check_password_policy($password, &$errmsg) {
3391 if (empty($CFG->passwordpolicy
)) {
3395 $textlib = textlib_get_instance();
3397 if ($textlib->strlen($password) < $CFG->minpasswordlength
) {
3398 $errmsg = get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength
);
3400 } else if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits
) {
3401 $errmsg = get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits
);
3403 } else if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower
) {
3404 $errmsg = get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower
);
3406 } else if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper
) {
3407 $errmsg = get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper
);
3409 } else if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum
) {
3410 $errmsg = get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum
);
3412 } else if ($password == 'admin' or $password == 'password') {
3413 $errmsg = get_string('unsafepassword');
3416 if ($errmsg == '') {
3425 * When logging in, this function is run to set certain preferences
3426 * for the current SESSION
3428 function set_login_session_preferences() {
3429 global $SESSION, $CFG;
3431 $SESSION->justloggedin
= true;
3433 unset($SESSION->lang
);
3435 // Restore the calendar filters, if saved
3436 if (intval(get_user_preferences('calendar_persistflt', 0))) {
3437 include_once($CFG->dirroot
.'/calendar/lib.php');
3438 calendar_set_filters_status(get_user_preferences('calendav_savedflt', 0xff));
3444 * Delete a course, including all related data from the database,
3445 * and any associated files from the moodledata folder.
3447 * @param mixed $courseorid The id of the course or course object to delete.
3448 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3449 * @return bool true if all the removals succeeded. false if there were any failures. If this
3450 * method returns false, some of the removals will probably have succeeded, and others
3451 * failed, but you have no way of knowing which.
3453 function delete_course($courseorid, $showfeedback = true) {
3457 if (is_object($courseorid)) {
3458 $courseid = $courseorid->id
;
3459 $course = $courseorid;
3461 $courseid = $courseorid;
3462 if (!$course = get_record('course', 'id', $courseid)) {
3467 // frontpage course can not be deleted!!
3468 if ($courseid == SITEID
) {
3472 if (!remove_course_contents($courseid, $showfeedback)) {
3473 if ($showfeedback) {
3474 notify("An error occurred while deleting some of the course contents.");
3479 if (!delete_records("course", "id", $courseid)) {
3480 if ($showfeedback) {
3481 notify("An error occurred while deleting the main course record.");
3486 /// Delete all roles and overiddes in the course context
3487 if (!delete_context(CONTEXT_COURSE
, $courseid)) {
3488 if ($showfeedback) {
3489 notify("An error occurred while deleting the main course context.");
3494 if (!fulldelete($CFG->dataroot
.'/'.$courseid)) {
3495 if ($showfeedback) {
3496 notify("An error occurred while deleting the course files.");
3503 events_trigger('course_deleted', $course);
3510 * Clear a course out completely, deleting all content
3511 * but don't delete the course itself
3514 * @param int $courseid The id of the course that is being deleted
3515 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3516 * @return bool true if all the removals succeeded. false if there were any failures. If this
3517 * method returns false, some of the removals will probably have succeeded, and others
3518 * failed, but you have no way of knowing which.
3520 function remove_course_contents($courseid, $showfeedback=true) {
3523 require_once($CFG->libdir
.'/questionlib.php');
3524 require_once($CFG->libdir
.'/gradelib.php');
3528 if (! $course = get_record('course', 'id', $courseid)) {
3529 error('Course ID was incorrect (can\'t find it)');
3532 $strdeleted = get_string('deleted');
3534 /// First delete every instance of every module
3536 if ($allmods = get_records('modules') ) {
3537 foreach ($allmods as $mod) {
3538 $modname = $mod->name
;
3539 $modfile = $CFG->dirroot
.'/mod/'. $modname .'/lib.php';
3540 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
3541 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
3543 if (file_exists($modfile)) {
3544 include_once($modfile);
3545 if (function_exists($moddelete)) {
3546 if ($instances = get_records($modname, 'course', $course->id
)) {
3547 foreach ($instances as $instance) {
3548 if ($cm = get_coursemodule_from_instance($modname, $instance->id
, $course->id
)) {
3549 /// Delete activity context questions and question categories
3550 question_delete_activity($cm, $showfeedback);
3552 if ($moddelete($instance->id
)) {
3556 notify('Could not delete '. $modname .' instance '. $instance->id
.' ('. format_string($instance->name
) .')');
3560 // delete cm and its context in correct order
3561 delete_records('course_modules', 'id', $cm->id
);
3562 delete_context(CONTEXT_MODULE
, $cm->id
);
3567 notify('Function '.$moddelete.'() doesn\'t exist!');
3571 if (function_exists($moddeletecourse)) {
3572 $moddeletecourse($course, $showfeedback);
3575 if ($showfeedback) {
3576 notify($strdeleted .' '. $count .' x '. $modname);
3580 error('No modules are installed!');
3583 /// Give local code a chance to delete its references to this course.
3584 require_once('locallib.php');
3585 notify_local_delete_course($courseid, $showfeedback);
3587 /// Delete course blocks
3589 if ($blocks = get_records_sql("SELECT *
3590 FROM {$CFG->prefix}block_instance
3591 WHERE pagetype = '".PAGE_COURSE_VIEW
."'
3592 AND pageid = $course->id")) {
3593 if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW
, 'pageid', $course->id
)) {
3594 if ($showfeedback) {
3595 notify($strdeleted .' block_instance');
3598 require_once($CFG->libdir
.'/blocklib.php');
3599 foreach ($blocks as $block) { /// Delete any associated contexts for this block
3601 delete_context(CONTEXT_BLOCK
, $block->id
);
3604 // Get the block object and call instance_delete()
3605 if (!$record = blocks_get_record($block->blockid
)) {
3609 if (!$obj = block_instance($record->name
, $block)) {
3613 // Return value ignored, in core mods this does not do anything, but just in case
3614 // third party blocks might have stuff to clean up
3615 // we execute this anyway
3616 $obj->instance_delete();
3624 /// Delete any groups, removing members and grouping/course links first.
3625 require_once($CFG->dirroot
.'/group/lib.php');
3626 groups_delete_groupings($courseid, $showfeedback);
3627 groups_delete_groups($courseid, $showfeedback);
3629 /// Delete all related records in other tables that may have a courseid
3630 /// This array stores the tables that need to be cleared, as
3631 /// table_name => column_name that contains the course id.
3633 $tablestoclear = array(
3634 'event' => 'courseid', // Delete events
3635 'log' => 'course', // Delete logs
3636 'course_sections' => 'course', // Delete any course stuff
3637 'course_modules' => 'course',
3638 'backup_courses' => 'courseid', // Delete scheduled backup stuff
3639 'user_lastaccess' => 'courseid',
3640 'backup_log' => 'courseid'
3642 foreach ($tablestoclear as $table => $col) {
3643 if (delete_records($table, $col, $course->id
)) {
3644 if ($showfeedback) {
3645 notify($strdeleted . ' ' . $table);
3653 /// Clean up metacourse stuff
3655 if ($course->metacourse
) {
3656 delete_records("course_meta","parent_course",$course->id
);
3657 sync_metacourse($course->id
); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3658 if ($showfeedback) {
3659 notify("$strdeleted course_meta");
3662 if ($parents = get_records("course_meta","child_course",$course->id
)) {
3663 foreach ($parents as $parent) {
3664 remove_from_metacourse($parent->parent_course
,$parent->child_course
); // this will do the unenrolments as well.
3666 if ($showfeedback) {
3667 notify("$strdeleted course_meta");
3672 /// Delete questions and question categories
3673 question_delete_course($course, $showfeedback);
3675 /// Remove all data from gradebook
3676 $context = get_context_instance(CONTEXT_COURSE
, $courseid);
3677 remove_course_grades($courseid, $showfeedback);
3678 remove_grade_letters($context, $showfeedback);
3684 * Change dates in module - used from course reset.
3685 * @param strin $modname forum, assignent, etc
3686 * @param array $fields array of date fields from mod table
3687 * @param int $timeshift time difference
3690 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
3692 include_once($CFG->dirroot
.'/mod/'.$modname.'/lib.php');
3695 foreach ($fields as $field) {
3696 $updatesql = "UPDATE {$CFG->prefix}$modname
3697 SET $field = $field + ($timeshift)
3698 WHERE course=$courseid AND $field<>0 AND $field<>0";
3699 $return = execute_sql($updatesql, false) && $return;
3702 $refreshfunction = $modname.'_refresh_events';
3703 if (function_exists($refreshfunction)) {
3704 $refreshfunction($courseid);
3711 * This function will empty a course of user data.
3712 * It will retain the activities and the structure of the course.
3713 * @param object $data an object containing all the settings including courseid (without magic quotes)
3714 * @return array status array of array component, item, error
3716 function reset_course_userdata($data) {
3718 require_once($CFG->libdir
.'/gradelib.php');
3719 require_once($CFG->dirroot
.'/group/lib.php');
3721 $data->courseid
= $data->id
;
3722 $context = get_context_instance(CONTEXT_COURSE
, $data->courseid
);
3724 // calculate the time shift of dates
3725 if (!empty($data->reset_start_date
)) {
3726 // time part of course startdate should be zero
3727 $data->timeshift
= $data->reset_start_date
- usergetmidnight($data->reset_start_date_old
);
3729 $data->timeshift
= 0;
3732 // result array: component, item, error
3735 // start the resetting
3736 $componentstr = get_string('general');
3738 // move the course start time
3739 if (!empty($data->reset_start_date
) and $data->timeshift
) {
3740 // change course start data
3741 set_field('course', 'startdate', $data->reset_start_date
, 'id', $data->courseid
);
3742 // update all course and group events - do not move activity events
3743 $updatesql = "UPDATE {$CFG->prefix}event
3744 SET timestart = timestart + ({$data->timeshift})
3745 WHERE courseid={$data->courseid} AND instance=0";
3746 execute_sql($updatesql, false);
3748 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3751 if (!empty($data->reset_logs
)) {
3752 delete_records('log', 'course', $data->courseid
);
3753 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
3756 if (!empty($data->reset_events
)) {
3757 delete_records('event', 'courseid', $data->courseid
);
3758 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
3761 if (!empty($data->reset_notes
)) {
3762 require_once($CFG->dirroot
.'/notes/lib.php');
3763 note_delete_all($data->courseid
);
3764 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
3767 $componentstr = get_string('roles');
3769 if (!empty($data->reset_roles_overrides
)) {
3770 $children = get_child_contexts($context);
3771 foreach ($children as $child) {
3772 delete_records('role_capabilities', 'contextid', $child->id
);
3774 delete_records('role_capabilities', 'contextid', $context->id
);
3775 //force refresh for logged in users
3776 mark_context_dirty($context->path
);
3777 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
3780 if (!empty($data->reset_roles_local
)) {
3781 $children = get_child_contexts($context);
3782 foreach ($children as $child) {
3783 role_unassign(0, 0, 0, $child->id
);
3785 //force refresh for logged in users
3786 mark_context_dirty($context->path
);
3787 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
3790 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
3791 $data->unenrolled
= array();
3792 if (!empty($data->reset_roles
)) {
3793 foreach($data->reset_roles
as $roleid) {
3794 if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) {
3795 foreach ($users as $user) {
3796 role_unassign($roleid, $user->id
, 0, $context->id
);
3797 if (!has_capability('moodle/course:view', $context, $user->id
)) {
3798 $data->unenrolled
[$user->id
] = $user->id
;
3804 if (!empty($data->unenrolled
)) {
3805 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol').' ('.count($data->unenrolled
).')', 'error'=>false);
3809 $componentstr = get_string('groups');
3811 // remove all group members
3812 if (!empty($data->reset_groups_members
)) {
3813 groups_delete_group_members($data->courseid
);
3814 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
3817 // remove all groups
3818 if (!empty($data->reset_groups_remove
)) {
3819 groups_delete_groups($data->courseid
, false);
3820 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
3823 // remove all grouping members
3824 if (!empty($data->reset_groupings_members
)) {
3825 groups_delete_groupings_groups($data->courseid
, false);
3826 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
3829 // remove all groupings
3830 if (!empty($data->reset_groupings_remove
)) {
3831 groups_delete_groupings($data->courseid
, false);
3832 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
3835 // Look in every instance of every module for data to delete
3836 $unsupported_mods = array();
3837 if ($allmods = get_records('modules') ) {
3838 foreach ($allmods as $mod) {
3839 $modname = $mod->name
;
3840 if (!count_records($modname, 'course', $data->courseid
)) {
3841 continue; // skip mods with no instances
3843 $modfile = $CFG->dirroot
.'/mod/'. $modname.'/lib.php';
3844 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
3845 if (file_exists($modfile)) {
3846 include_once($modfile);
3847 if (function_exists($moddeleteuserdata)) {
3848 $modstatus = $moddeleteuserdata($data);
3849 if (is_array($modstatus)) {
3850 $status = array_merge($status, $modstatus);
3852 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
3855 $unsupported_mods[] = $mod;
3858 debugging('Missing lib.php in '.$modname.' module!');
3863 // mention unsupported mods
3864 if (!empty($unsupported_mods)) {
3865 foreach($unsupported_mods as $mod) {
3866 $status[] = array('component'=>get_string('modulenameplural', $mod->name
), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
3871 $componentstr = get_string('gradebook', 'grades');
3873 if (!empty($data->reset_gradebook_items
)) {
3874 remove_course_grades($data->courseid
, false);
3875 grade_grab_course_grades($data->courseid
);
3876 grade_regrade_final_grades($data->courseid
);
3877 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
3879 } else if (!empty($data->reset_gradebook_grades
)) {
3880 grade_course_reset($data->courseid
);
3881 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
3887 function generate_email_processing_address($modid,$modargs) {
3890 if (empty($CFG->siteidentifier
)) { // Unique site identification code
3891 set_config('siteidentifier', random_string(32));
3894 $header = $CFG->mailprefix
. substr(base64_encode(pack('C',$modid)),0,2).$modargs;
3895 return $header . substr(md5($header.$CFG->siteidentifier
),0,16).'@'.$CFG->maildomain
;
3899 function moodle_process_email($modargs,$body) {
3900 // the first char should be an unencoded letter. We'll take this as an action
3901 switch ($modargs{0}) {
3902 case 'B': { // bounce
3903 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
3904 if ($user = get_record_select("user","id=$userid","id,email")) {
3905 // check the half md5 of their email
3906 $md5check = substr(md5($user->email
),0,16);
3907 if ($md5check == substr($modargs, -16)) {
3908 set_bounce_count($user);
3910 // else maybe they've already changed it?
3914 // maybe more later?
3918 /// CORRESPONDENCE ////////////////////////////////////////////////
3921 * Get mailer instance, enable buffering, flush buffer or disable buffering.
3922 * @param $action string 'get', 'buffer', 'close' or 'flush'
3923 * @return reference to mailer instance if 'get' used or nothing
3925 function &get_mailer($action='get') {
3928 static $mailer = null;
3929 static $counter = 0;
3931 if (!isset($CFG->smtpmaxbulk
)) {
3932 $CFG->smtpmaxbulk
= 1;
3935 if ($action == 'get') {
3936 $prevkeepalive = false;
3938 if (isset($mailer) and $mailer->Mailer
== 'smtp') {
3939 if ($counter < $CFG->smtpmaxbulk
and empty($mailer->error_count
)) {
3942 $mailer->Priority
= 3;
3943 $mailer->CharSet
= 'UTF-8'; // our default
3944 $mailer->ContentType
= "text/plain";
3945 $mailer->Encoding
= "8bit";
3946 $mailer->From
= "root@localhost";
3947 $mailer->FromName
= "Root User";
3948 $mailer->Sender
= "";
3949 $mailer->Subject
= "";
3951 $mailer->AltBody
= "";
3952 $mailer->ConfirmReadingTo
= "";
3954 $mailer->ClearAllRecipients();
3955 $mailer->ClearReplyTos();
3956 $mailer->ClearAttachments();
3957 $mailer->ClearCustomHeaders();
3961 $prevkeepalive = $mailer->SMTPKeepAlive
;
3962 get_mailer('flush');
3965 include_once($CFG->libdir
.'/phpmailer/class.phpmailer.php');
3966 $mailer = new phpmailer();
3970 $mailer->Version
= 'Moodle '.$CFG->version
; // mailer version
3971 $mailer->PluginDir
= $CFG->libdir
.'/phpmailer/'; // plugin directory (eg smtp plugin)
3972 $mailer->CharSet
= 'UTF-8';
3974 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
3975 // hmm, this is a bit hacky because LE should be private
3976 if (isset($CFG->mailnewline
) and $CFG->mailnewline
== 'CRLF') {
3977 $mailer->LE
= "\r\n";
3982 if ($CFG->smtphosts
== 'qmail') {
3983 $mailer->IsQmail(); // use Qmail system
3985 } else if (empty($CFG->smtphosts
)) {
3986 $mailer->IsMail(); // use PHP mail() = sendmail
3989 $mailer->IsSMTP(); // use SMTP directly
3990 if (!empty($CFG->debugsmtp
)) {
3991 $mailer->SMTPDebug
= true;
3993 $mailer->Host
= $CFG->smtphosts
; // specify main and backup servers
3994 $mailer->SMTPKeepAlive
= $prevkeepalive; // use previous keepalive
3996 if ($CFG->smtpuser
) { // Use SMTP authentication
3997 $mailer->SMTPAuth
= true;
3998 $mailer->Username
= $CFG->smtpuser
;
3999 $mailer->Password
= $CFG->smtppass
;
4008 // keep smtp session open after sending
4009 if ($action == 'buffer') {
4010 if (!empty($CFG->smtpmaxbulk
)) {
4011 get_mailer('flush');
4013 if ($m->Mailer
== 'smtp') {
4014 $m->SMTPKeepAlive
= true;
4020 // close smtp session, but continue buffering
4021 if ($action == 'flush') {
4022 if (isset($mailer) and $mailer->Mailer
== 'smtp') {
4023 if (!empty($mailer->SMTPDebug
)) {
4026 $mailer->SmtpClose();
4027 if (!empty($mailer->SMTPDebug
)) {
4034 // close smtp session, do not buffer anymore
4035 if ($action == 'close') {
4036 if (isset($mailer) and $mailer->Mailer
== 'smtp') {
4037 get_mailer('flush');
4038 $mailer->SMTPKeepAlive
= false;
4040 $mailer = null; // better force new instance
4046 * Send an email to a specified user
4050 * @uses $MNETIDPJUMPURL IdentityProvider(IDP) URL user hits to jump to mnet peer.
4052 * @param user $user A {@link $USER} object
4053 * @param user $from A {@link $USER} object
4054 * @param string $subject plain text subject line of the email
4055 * @param string $messagetext plain text version of the message
4056 * @param string $messagehtml complete html version of the message (optional)
4057 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4058 * @param string $attachname the name of the file (extension indicates MIME)
4059 * @param bool $usetrueaddress determines whether $from email address should
4060 * be sent out. Will be overruled by user profile setting for maildisplay
4061 * @param int $wordwrapwidth custom word wrap width
4062 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4063 * was blocked by user and "false" if there was another sort of error.
4065 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4067 global $CFG, $FULLME, $MNETIDPJUMPURL;
4068 static $mnetjumps = array();
4074 if (!empty($CFG->noemailever
)) {
4075 // hidden setting for development sites, set in config.php if needed
4079 // skip mail to suspended users
4080 if (isset($user->auth
) && $user->auth
=='nologin') {
4084 if (!empty($user->emailstop
)) {
4088 if (over_bounce_threshold($user)) {
4089 error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
4093 // If the user is a remote mnet user, parse the email text for URL to the
4094 // wwwroot and modify the url to direct the user's browser to login at their
4095 // home site (identity provider - idp) before hitting the link itself
4096 if ($user->mnethostid
> 1) {
4097 require_once($CFG->dirroot
.'/mnet/lib.php');
4098 // Form the request url to hit the idp's jump.php
4099 if (isset($mnetjumps[$user->mnethostid
])) {
4100 $MNETIDPJUMPURL = $mnetjumps[$user->mnethostid
];
4102 $idp = mnet_get_peer_host($user->mnethostid
);
4103 $idpjumppath = '/auth/mnet/jump.php';
4104 $MNETIDPJUMPURL = $idp->wwwroot
. $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot
. '&wantsurl=';
4105 $mnetjumps[$user->mnethostid
] = $MNETIDPJUMPURL;
4108 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4109 'mnet_sso_apply_indirection',
4111 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4112 'mnet_sso_apply_indirection',
4115 $mail =& get_mailer();
4117 if (!empty($mail->SMTPDebug
)) {
4118 echo '<pre>' . "\n";
4121 /// We are going to use textlib services here
4122 $textlib = textlib_get_instance();
4124 $supportuser = generate_email_supportuser();
4126 // make up an email address for handling bounces
4127 if (!empty($CFG->handlebounces
)) {
4128 $modargs = 'B'.base64_encode(pack('V',$user->id
)).substr(md5($user->email
),0,16);
4129 $mail->Sender
= generate_email_processing_address(0,$modargs);
4131 $mail->Sender
= $supportuser->email
;
4134 if (is_string($from)) { // So we can pass whatever we want if there is need
4135 $mail->From
= $CFG->noreplyaddress
;
4136 $mail->FromName
= $from;
4137 } else if ($usetrueaddress and $from->maildisplay
) {
4138 $mail->From
= $from->email
;
4139 $mail->FromName
= fullname($from);
4141 $mail->From
= $CFG->noreplyaddress
;
4142 $mail->FromName
= fullname($from);
4143 if (empty($replyto)) {
4144 $mail->AddReplyTo($CFG->noreplyaddress
,get_string('noreplyname'));
4148 if (!empty($replyto)) {
4149 $mail->AddReplyTo($replyto,$replytoname);
4152 $mail->Subject
= substr(stripslashes($subject), 0, 900);
4154 $mail->AddAddress($user->email
, fullname($user) );
4156 $mail->WordWrap
= $wordwrapwidth; // set word wrap
4158 if (!empty($from->customheaders
)) { // Add custom headers
4159 if (is_array($from->customheaders
)) {
4160 foreach ($from->customheaders
as $customheader) {
4161 $mail->AddCustomHeader($customheader);
4164 $mail->AddCustomHeader($from->customheaders
);
4168 if (!empty($from->priority
)) {
4169 $mail->Priority
= $from->priority
;
4172 if ($messagehtml && $user->mailformat
== 1) { // Don't ever send HTML to users who don't want it
4173 $mail->IsHTML(true);
4174 $mail->Encoding
= 'quoted-printable'; // Encoding to use
4175 $mail->Body
= $messagehtml;
4176 $mail->AltBody
= "\n$messagetext\n";
4178 $mail->IsHTML(false);
4179 $mail->Body
= "\n$messagetext\n";
4182 if ($attachment && $attachname) {
4183 if (ereg( "\\.\\." ,$attachment )) { // Security check for ".." in dir path
4184 $mail->AddAddress($supportuser->email
, fullname($supportuser, true) );
4185 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4187 require_once($CFG->libdir
.'/filelib.php');
4188 $mimetype = mimeinfo('type', $attachname);
4189 $mail->AddAttachment($CFG->dataroot
.'/'. $attachment, $attachname, 'base64', $mimetype);
4195 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
4196 /// encoding to the specified one
4197 if ((!empty($CFG->sitemailcharset
) ||
!empty($CFG->allowusermailcharset
))) {
4198 /// Set it to site mail charset
4199 $charset = $CFG->sitemailcharset
;
4200 /// Overwrite it with the user mail charset
4201 if (!empty($CFG->allowusermailcharset
)) {
4202 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id
)) {
4203 $charset = $useremailcharset;
4206 /// If it has changed, convert all the necessary strings
4207 $charsets = get_list_of_charsets();
4208 unset($charsets['UTF-8']);
4209 if (in_array($charset, $charsets)) {
4210 /// Save the new mail charset
4211 $mail->CharSet
= $charset;
4212 /// And convert some strings
4213 $mail->FromName
= $textlib->convert($mail->FromName
, 'utf-8', $mail->CharSet
); //From Name
4214 foreach ($mail->ReplyTo
as $key => $rt) { //ReplyTo Names
4215 $mail->ReplyTo
[$key][1] = $textlib->convert($rt, 'utf-8', $mail->CharSet
);
4217 $mail->Subject
= $textlib->convert($mail->Subject
, 'utf-8', $mail->CharSet
); //Subject
4218 foreach ($mail->to
as $key => $to) {
4219 $mail->to
[$key][1] = $textlib->convert($to, 'utf-8', $mail->CharSet
); //To Names
4221 $mail->Body
= $textlib->convert($mail->Body
, 'utf-8', $mail->CharSet
); //Body
4222 $mail->AltBody
= $textlib->convert($mail->AltBody
, 'utf-8', $mail->CharSet
); //Subject
4226 if ($mail->Send()) {
4227 set_send_count($user);
4228 $mail->IsSMTP(); // use SMTP directly
4229 if (!empty($mail->SMTPDebug
)) {
4234 mtrace('ERROR: '. $mail->ErrorInfo
);
4235 add_to_log(SITEID
, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo
);
4236 if (!empty($mail->SMTPDebug
)) {
4244 * Generate a signoff for emails based on support settings
4247 function generate_email_signoff() {
4251 if (!empty($CFG->supportname
)) {
4252 $signoff .= $CFG->supportname
."\n";
4254 if (!empty($CFG->supportemail
)) {
4255 $signoff .= $CFG->supportemail
."\n";
4257 if (!empty($CFG->supportpage
)) {
4258 $signoff .= $CFG->supportpage
."\n";
4264 * Generate a fake user for emails based on support settings
4267 function generate_email_supportuser() {
4271 static $supportuser;
4273 if (!empty($supportuser)) {
4274 return $supportuser;
4277 $supportuser = new object;
4278 $supportuser->email
= $CFG->supportemail ?
$CFG->supportemail
: $CFG->noreplyaddress
;
4279 $supportuser->firstname
= $CFG->supportname ?
$CFG->supportname
: get_string('noreplyname');
4280 $supportuser->lastname
= '';
4281 $supportuser->maildisplay
= true;
4283 return $supportuser;
4288 * Sets specified user's password and send the new password to the user via email.
4291 * @param user $user A {@link $USER} object
4292 * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
4293 * was blocked by user and "false" if there was another sort of error.
4295 function setnew_password_and_mail($user) {
4301 $supportuser = generate_email_supportuser();
4303 $newpassword = generate_password();
4305 if (! set_field('user', 'password', md5($newpassword), 'id', $user->id
) ) {
4306 trigger_error('Could not set user password!');
4311 $a->firstname
= fullname($user, true);
4312 $a->sitename
= format_string($site->fullname
);
4313 $a->username
= $user->username
;
4314 $a->newpassword
= $newpassword;
4315 $a->link
= $CFG->wwwroot
.'/login/';
4316 $a->signoff
= generate_email_signoff();
4318 $message = get_string('newusernewpasswordtext', '', $a);
4320 $subject = format_string($site->fullname
) .': '. get_string('newusernewpasswordsubj');
4322 return email_to_user($user, $supportuser, $subject, $message);
4327 * Resets specified user's password and send the new password to the user via email.
4330 * @param user $user A {@link $USER} object
4331 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4332 * was blocked by user and "false" if there was another sort of error.
4334 function reset_password_and_mail($user) {
4339 $supportuser = generate_email_supportuser();
4341 $userauth = get_auth_plugin($user->auth
);
4342 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth
)) {
4343 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4347 $newpassword = generate_password();
4349 if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
4350 error("Could not set user password!");
4354 $a->firstname
= $user->firstname
;
4355 $a->sitename
= format_string($site->fullname
);
4356 $a->username
= $user->username
;
4357 $a->newpassword
= $newpassword;
4358 $a->link
= $CFG->httpswwwroot
.'/login/change_password.php';
4359 $a->signoff
= generate_email_signoff();
4361 $message = get_string('newpasswordtext', '', $a);
4363 $subject = format_string($site->fullname
) .': '. get_string('changedpassword');
4365 return email_to_user($user, $supportuser, $subject, $message);
4370 * Send email to specified user with confirmation text and activation link.
4373 * @param user $user A {@link $USER} object
4374 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4375 * was blocked by user and "false" if there was another sort of error.
4377 function send_confirmation_email($user) {
4382 $supportuser = generate_email_supportuser();
4384 $data = new object();
4385 $data->firstname
= fullname($user);
4386 $data->sitename
= format_string($site->fullname
);
4387 $data->admin
= generate_email_signoff();
4389 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname
));
4391 $data->link
= $CFG->wwwroot
.'/login/confirm.php?data='. $user->secret
.'/'. urlencode($user->username
);
4392 $message = get_string('emailconfirmation', '', $data);
4393 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4395 $user->mailformat
= 1; // Always send HTML version as well
4397 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4402 * send_password_change_confirmation_email.
4405 * @param user $user A {@link $USER} object
4406 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4407 * was blocked by user and "false" if there was another sort of error.
4409 function send_password_change_confirmation_email($user) {
4414 $supportuser = generate_email_supportuser();
4416 $data = new object();
4417 $data->firstname
= $user->firstname
;
4418 $data->sitename
= format_string($site->fullname
);
4419 $data->link
= $CFG->httpswwwroot
.'/login/forgot_password.php?p='. $user->secret
.'&s='. urlencode($user->username
);
4420 $data->admin
= generate_email_signoff();
4422 $message = get_string('emailpasswordconfirmation', '', $data);
4423 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname
));
4425 return email_to_user($user, $supportuser, $subject, $message);
4430 * send_password_change_info.
4433 * @param user $user A {@link $USER} object
4434 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4435 * was blocked by user and "false" if there was another sort of error.
4437 function send_password_change_info($user) {
4442 $supportuser = generate_email_supportuser();
4443 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
4445 $data = new object();
4446 $data->firstname
= $user->firstname
;
4447 $data->sitename
= format_string($site->fullname
);
4448 $data->admin
= generate_email_signoff();
4450 $userauth = get_auth_plugin($user->auth
);
4452 if (!is_enabled_auth($user->auth
) or $user->auth
== 'nologin') {
4453 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4454 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname
));
4455 return email_to_user($user, $supportuser, $subject, $message);
4458 if ($userauth->can_change_password() and $userauth->change_password_url()) {
4459 // we have some external url for password changing
4460 $data->link
.= $userauth->change_password_url();
4463 //no way to change password, sorry
4467 if (!empty($data->link
) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id
)) {
4468 $message = get_string('emailpasswordchangeinfo', '', $data);
4469 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname
));
4471 $message = get_string('emailpasswordchangeinfofail', '', $data);
4472 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname
));
4475 return email_to_user($user, $supportuser, $subject, $message);
4480 * Check that an email is allowed. It returns an error message if there
4484 * @param string $email Content of email
4485 * @return string|false
4487 function email_is_not_allowed($email) {
4491 if (!empty($CFG->allowemailaddresses
)) {
4492 $allowed = explode(' ', $CFG->allowemailaddresses
);
4493 foreach ($allowed as $allowedpattern) {
4494 $allowedpattern = trim($allowedpattern);
4495 if (!$allowedpattern) {
4498 if (strpos($allowedpattern, '.') === 0) {
4499 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
4500 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4504 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
4508 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses
);
4510 } else if (!empty($CFG->denyemailaddresses
)) {
4511 $denied = explode(' ', $CFG->denyemailaddresses
);
4512 foreach ($denied as $deniedpattern) {
4513 $deniedpattern = trim($deniedpattern);
4514 if (!$deniedpattern) {
4517 if (strpos($deniedpattern, '.') === 0) {
4518 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
4519 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4520 return get_string('emailnotallowed', '', $CFG->denyemailaddresses
);
4523 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
4524 return get_string('emailnotallowed', '', $CFG->denyemailaddresses
);
4532 function email_welcome_message_to_user($course, $user=NULL) {
4535 if (isset($CFG->sendcoursewelcomemessage
) and !$CFG->sendcoursewelcomemessage
) {
4540 if (!isloggedin()) {
4546 if (!empty($course->welcomemessage
)) {
4547 $message = $course->welcomemessage
;
4550 $a->coursename
= $course->fullname
;
4551 $a->profileurl
= "$CFG->wwwroot/user/view.php?id=$USER->id&course=$course->id";
4552 $message = get_string("welcometocoursetext", "", $a);
4555 /// If you don't want a welcome message sent, then make the message string blank.
4556 if (!empty($message)) {
4557 $subject = get_string('welcometocourse', '', format_string($course->fullname
));
4559 if (! $teacher = get_teacher($course->id
)) {
4560 $teacher = get_admin();
4562 email_to_user($user, $teacher, $subject, $message);
4566 /// FILE HANDLING /////////////////////////////////////////////
4570 * Makes an upload directory for a particular module.
4573 * @param int $courseid The id of the course in question - maps to id field of 'course' table.
4574 * @return string|false Returns full path to directory if successful, false if not
4576 function make_mod_upload_directory($courseid) {
4579 if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata
)) {
4583 $strreadme = get_string('readme');
4585 if (file_exists($CFG->dirroot
.'/lang/'. $CFG->lang
.'/docs/module_files.txt')) {
4586 copy($CFG->dirroot
.'/lang/'. $CFG->lang
.'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4588 copy($CFG->dirroot
.'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4594 * Makes a directory for a particular user.
4597 * @param int $userid The id of the user in question - maps to id field of 'user' table.
4598 * @param bool $test Whether we are only testing the return value (do not create the directory)
4599 * @return string|false Returns full path to directory if successful, false if not
4601 function make_user_directory($userid, $test=false) {
4604 if (is_bool($userid) ||
$userid < 0 ||
!ereg('^[0-9]{1,10}$', $userid) ||
$userid > 2147483647) {
4606 notify("Given userid was not a valid integer! (" . gettype($userid) . " $userid)");
4611 // Generate a two-level path for the userid. First level groups them by slices of 1000 users, second level is userid
4612 $level1 = floor($userid / 1000) * 1000;
4614 $userdir = "user/$level1/$userid";
4616 return $CFG->dataroot
. '/' . $userdir;
4618 return make_upload_directory($userdir);
4623 * Returns an array of full paths to user directories, indexed by their userids.
4625 * @param bool $only_non_empty Only return directories that contain files
4626 * @param bool $legacy Search for user directories in legacy location (dataroot/users/userid) instead of (dataroot/user/section/userid)
4627 * @return array An associative array: userid=>array(basedir => $basedir, userfolder => $userfolder)
4629 function get_user_directories($only_non_empty=true, $legacy=false) {
4632 $rootdir = $CFG->dataroot
."/user";
4635 $rootdir = $CFG->dataroot
."/users";
4639 //Check if directory exists
4640 if (check_dir_exists($rootdir, true)) {
4642 if ($userlist = get_directory_list($rootdir, '', true, true, false)) {
4643 foreach ($userlist as $userid) {
4644 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $userid);
4647 notify("no directories found under $rootdir");
4650 if ($grouplist =get_directory_list($rootdir, '', true, true, false)) { // directories will be in the form 0, 1000, 2000 etc...
4651 foreach ($grouplist as $group) {
4652 if ($userlist = get_directory_list("$rootdir/$group", '', true, true, false)) {
4653 foreach ($userlist as $userid) {
4654 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $group . '/' . $userid);
4661 notify("$rootdir does not exist!");
4668 * Returns current name of file on disk if it exists.
4670 * @param string $newfile File to be verified
4671 * @return string Current name of file on disk if true
4673 function valid_uploaded_file($newfile) {
4674 if (empty($newfile)) {
4677 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
4678 return $newfile['tmp_name'];
4685 * Returns the maximum size for uploading files.
4687 * There are seven possible upload limits:
4688 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
4689 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
4690 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
4691 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
4692 * 5. by the Moodle admin in $CFG->maxbytes
4693 * 6. by the teacher in the current course $course->maxbytes
4694 * 7. by the teacher for the current module, eg $assignment->maxbytes
4696 * These last two are passed to this function as arguments (in bytes).
4697 * Anything defined as 0 is ignored.
4698 * The smallest of all the non-zero numbers is returned.
4700 * @param int $sizebytes ?
4701 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4702 * @param int $modulebytes Current module ->maxbytes (in bytes)
4703 * @return int The maximum size for uploading files.
4704 * @todo Finish documenting this function
4706 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4708 if (! $filesize = ini_get('upload_max_filesize')) {
4711 $minimumsize = get_real_size($filesize);
4713 if ($postsize = ini_get('post_max_size')) {
4714 $postsize = get_real_size($postsize);
4715 if ($postsize < $minimumsize) {
4716 $minimumsize = $postsize;
4720 if ($sitebytes and $sitebytes < $minimumsize) {
4721 $minimumsize = $sitebytes;
4724 if ($coursebytes and $coursebytes < $minimumsize) {
4725 $minimumsize = $coursebytes;
4728 if ($modulebytes and $modulebytes < $minimumsize) {
4729 $minimumsize = $modulebytes;
4732 return $minimumsize;
4736 * Related to {@link get_max_upload_file_size()} - this function returns an
4737 * array of possible sizes in an array, translated to the
4740 * @uses SORT_NUMERIC
4741 * @param int $sizebytes ?
4742 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4743 * @param int $modulebytes Current module ->maxbytes (in bytes)
4745 * @todo Finish documenting this function
4747 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4750 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
4754 $filesize[$maxsize] = display_size($maxsize);
4756 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
4757 5242880, 10485760, 20971520, 52428800, 104857600);
4759 // Allow maxbytes to be selected if it falls outside the above boundaries
4760 if( isset($CFG->maxbytes
) && !in_array($CFG->maxbytes
, $sizelist) ){
4761 $sizelist[] = $CFG->maxbytes
;
4764 foreach ($sizelist as $sizebytes) {
4765 if ($sizebytes < $maxsize) {
4766 $filesize[$sizebytes] = display_size($sizebytes);
4770 krsort($filesize, SORT_NUMERIC
);
4776 * If there has been an error uploading a file, print the appropriate error message
4777 * Numerical constants used as constant definitions not added until PHP version 4.2.0
4779 * $filearray is a 1-dimensional sub-array of the $_FILES array
4780 * eg $filearray = $_FILES['userfile1']
4781 * If left empty then the first element of the $_FILES array will be used
4784 * @param array $filearray A 1-dimensional sub-array of the $_FILES array
4785 * @param bool $returnerror If true then a string error message will be returned. Otherwise the user will be notified of the error in a notify() call.
4786 * @return bool|string
4788 function print_file_upload_error($filearray = '', $returnerror = false) {
4790 if ($filearray == '' or !isset($filearray['error'])) {
4792 if (empty($_FILES)) return false;
4794 $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
4795 $filearray = array_shift($files); /// use first element of array
4798 switch ($filearray['error']) {
4800 case 0: // UPLOAD_ERR_OK
4801 if ($filearray['size'] > 0) {
4802 $errmessage = get_string('uploadproblem', $filearray['name']);
4804 $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
4808 case 1: // UPLOAD_ERR_INI_SIZE
4809 $errmessage = get_string('uploadserverlimit');
4812 case 2: // UPLOAD_ERR_FORM_SIZE
4813 $errmessage = get_string('uploadformlimit');
4816 case 3: // UPLOAD_ERR_PARTIAL
4817 $errmessage = get_string('uploadpartialfile');
4820 case 4: // UPLOAD_ERR_NO_FILE
4821 $errmessage = get_string('uploadnofilefound');
4825 $errmessage = get_string('uploadproblem', $filearray['name']);
4831 notify($errmessage);
4838 * handy function to loop through an array of files and resolve any filename conflicts
4839 * both in the array of filenames and for what is already on disk.
4840 * not really compatible with the similar function in uploadlib.php
4841 * but this could be used for files/index.php for moving files around.
4844 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
4845 foreach ($files as $k => $f) {
4846 if (check_potential_filename($destination,$f,$files)) {
4847 $bits = explode('.', $f);
4848 for ($i = 1; true; $i++
) {
4849 $try = sprintf($format, $bits[0], $i, $bits[1]);
4850 if (!check_potential_filename($destination,$try,$files)) {
4861 * @used by resolve_filename_collisions
4863 function check_potential_filename($destination,$filename,$files) {
4864 if (file_exists($destination.'/'.$filename)) {
4867 if (count(array_keys($files,$filename)) > 1) {
4875 * Returns an array with all the filenames in
4876 * all subdirectories, relative to the given rootdir.
4877 * If excludefile is defined, then that file/directory is ignored
4878 * If getdirs is true, then (sub)directories are included in the output
4879 * If getfiles is true, then files are included in the output
4880 * (at least one of these must be true!)
4882 * @param string $rootdir ?
4883 * @param string $excludefile If defined then the specified file/directory is ignored
4884 * @param bool $descend ?
4885 * @param bool $getdirs If true then (sub)directories are included in the output
4886 * @param bool $getfiles If true then files are included in the output
4887 * @return array An array with all the filenames in
4888 * all subdirectories, relative to the given rootdir
4889 * @todo Finish documenting this function. Add examples of $excludefile usage.
4891 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
4895 if (!$getdirs and !$getfiles) { // Nothing to show
4899 if (!is_dir($rootdir)) { // Must be a directory
4903 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
4907 if (!is_array($excludefiles)) {
4908 $excludefiles = array($excludefiles);
4911 while (false !== ($file = readdir($dir))) {
4912 $firstchar = substr($file, 0, 1);
4913 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
4916 $fullfile = $rootdir .'/'. $file;
4917 if (filetype($fullfile) == 'dir') {
4922 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
4923 foreach ($subdirs as $subdir) {
4924 $dirs[] = $file .'/'. $subdir;
4927 } else if ($getfiles) {
4940 * Adds up all the files in a directory and works out the size.
4942 * @param string $rootdir ?
4943 * @param string $excludefile ?
4945 * @todo Finish documenting this function
4947 function get_directory_size($rootdir, $excludefile='') {
4951 // do it this way if we can, it's much faster
4952 if (!empty($CFG->pathtodu
) && is_executable(trim($CFG->pathtodu
))) {
4953 $command = trim($CFG->pathtodu
).' -sk '.escapeshellarg($rootdir);
4956 exec($command,$output,$return);
4957 if (is_array($output)) {
4958 return get_real_size(intval($output[0]).'k'); // we told it to return k.
4962 if (!is_dir($rootdir)) { // Must be a directory
4966 if (!$dir = @opendir
($rootdir)) { // Can't open it for some reason
4972 while (false !== ($file = readdir($dir))) {
4973 $firstchar = substr($file, 0, 1);
4974 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
4977 $fullfile = $rootdir .'/'. $file;
4978 if (filetype($fullfile) == 'dir') {
4979 $size +
= get_directory_size($fullfile, $excludefile);
4981 $size +
= filesize($fullfile);
4990 * Converts bytes into display form
4992 * @param string $size ?
4994 * @staticvar string $gb Localized string for size in gigabytes
4995 * @staticvar string $mb Localized string for size in megabytes
4996 * @staticvar string $kb Localized string for size in kilobytes
4997 * @staticvar string $b Localized string for size in bytes
4998 * @todo Finish documenting this function. Verify return type.
5000 function display_size($size) {
5002 static $gb, $mb, $kb, $b;
5005 $gb = get_string('sizegb');
5006 $mb = get_string('sizemb');
5007 $kb = get_string('sizekb');
5008 $b = get_string('sizeb');
5011 if ($size >= 1073741824) {
5012 $size = round($size / 1073741824 * 10) / 10 . $gb;
5013 } else if ($size >= 1048576) {
5014 $size = round($size / 1048576 * 10) / 10 . $mb;
5015 } else if ($size >= 1024) {
5016 $size = round($size / 1024 * 10) / 10 . $kb;
5018 $size = $size .' '. $b;
5024 * Cleans a given filename by removing suspicious or troublesome characters
5025 * Only these are allowed: alphanumeric _ - .
5026 * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
5028 * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
5029 * because native zip binaries do weird character conversions. Use PHP zipping instead.
5031 * @param string $string file name
5032 * @return string cleaned file name
5034 function clean_filename($string) {
5036 if (empty($CFG->unicodecleanfilename
)) {
5037 $textlib = textlib_get_instance();
5038 $string = $textlib->specialtoascii($string);
5039 $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
5041 //clean only ascii range
5042 $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
5044 $string = preg_replace("/_+/", '_', $string);
5045 $string = preg_replace("/\.\.+/", '.', $string);
5050 /// STRING TRANSLATION ////////////////////////////////////////
5053 * Returns the code for the current language
5060 function current_language() {
5061 global $CFG, $USER, $SESSION, $COURSE;
5063 if (!empty($COURSE->id
) and $COURSE->id
!= SITEID
and !empty($COURSE->lang
)) { // Course language can override all other settings for this page
5064 $return = $COURSE->lang
;
5066 } else if (!empty($SESSION->lang
)) { // Session language can override other settings
5067 $return = $SESSION->lang
;
5069 } else if (!empty($USER->lang
)) {
5070 $return = $USER->lang
;
5073 $return = $CFG->lang
;
5076 if ($return == 'en') {
5077 $return = 'en_utf8';
5084 * Prints out a translated string.
5086 * Prints out a translated string using the return value from the {@link get_string()} function.
5088 * Example usage of this function when the string is in the moodle.php file:<br/>
5091 * print_string('wordforstudent');
5095 * Example usage of this function when the string is not in the moodle.php file:<br/>
5098 * print_string('typecourse', 'calendar');
5102 * @param string $identifier The key identifier for the localized string
5103 * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
5104 * @param mixed $a An object, string or number that can be used
5105 * within translation strings
5107 function print_string($identifier, $module='', $a=NULL) {
5108 echo get_string($identifier, $module, $a);
5112 * fix up the optional data in get_string()/print_string() etc
5113 * ensure possible sprintf() format characters are escaped correctly
5114 * needs to handle arbitrary strings and objects
5115 * @param mixed $a An object, string or number that can be used
5116 * @return mixed the supplied parameter 'cleaned'
5118 function clean_getstring_data( $a ) {
5119 if (is_string($a)) {
5120 return str_replace( '%','%%',$a );
5122 elseif (is_object($a)) {
5123 $a_vars = get_object_vars( $a );
5124 $new_a_vars = array();
5125 foreach ($a_vars as $fname => $a_var) {
5126 $new_a_vars[$fname] = clean_getstring_data( $a_var );
5128 return (object)$new_a_vars;
5136 * @return array places to look for lang strings based on the prefix to the
5137 * module name. For example qtype_ in question/type. Used by get_string and
5140 function places_to_search_for_lang_strings() {
5144 '__exceptions' => array('moodle', 'langconfig'),
5145 'assignment_' => array('mod/assignment/type'),
5146 'auth_' => array('auth'),
5147 'block_' => array('blocks'),
5148 'datafield_' => array('mod/data/field'),
5149 'datapreset_' => array('mod/data/preset'),
5150 'enrol_' => array('enrol'),
5151 'filter_' => array('filter'),
5152 'format_' => array('course/format'),
5153 'qtype_' => array('question/type'),
5154 'report_' => array($CFG->admin
.'/report', 'course/report', 'mod/quiz/report'),
5155 'resource_' => array('mod/resource/type'),
5156 'gradereport_' => array('grade/report'),
5157 'gradeimport_' => array('grade/import'),
5158 'gradeexport_' => array('grade/export'),
5159 'profilefield_' => array('user/profile/field'),
5165 * Returns a localized string.
5167 * Returns the translated string specified by $identifier as
5168 * for $module. Uses the same format files as STphp.
5169 * $a is an object, string or number that can be used
5170 * within translation strings
5172 * eg "hello \$a->firstname \$a->lastname"
5175 * If you would like to directly echo the localized string use
5176 * the function {@link print_string()}
5178 * Example usage of this function involves finding the string you would
5179 * like a local equivalent of and using its identifier and module information
5180 * to retrive it.<br/>
5181 * If you open moodle/lang/en/moodle.php and look near line 1031
5182 * you will find a string to prompt a user for their word for student
5184 * $string['wordforstudent'] = 'Your word for Student';
5186 * So if you want to display the string 'Your word for student'
5187 * in any language that supports it on your site
5188 * you just need to use the identifier 'wordforstudent'
5190 * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
5193 * If the string you want is in another file you'd take a slightly
5194 * different approach. Looking in moodle/lang/en/calendar.php you find
5197 * $string['typecourse'] = 'Course event';
5199 * If you want to display the string "Course event" in any language
5200 * supported you would use the identifier 'typecourse' and the module 'calendar'
5201 * (because it is in the file calendar.php):
5203 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
5206 * As a last resort, should the identifier fail to map to a string
5207 * the returned string will be [[ $identifier ]]
5210 * @param string $identifier The key identifier for the localized string
5211 * @param string $module The module where the key identifier is stored, usually expressed as the filename in the language pack without the .php on the end but can also be written as mod/forum or grade/export/xls. If none is specified then moodle.php is used.
5212 * @param mixed $a An object, string or number that can be used
5213 * within translation strings
5214 * @param array $extralocations An array of strings with other locations to look for string files
5215 * @return string The localized string.
5217 function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
5221 /// originally these special strings were stored in moodle.php now we are only in langconfig.php
5222 $langconfigstrs = array('alphabet', 'backupnameformat', 'decsep', 'firstdayofweek', 'listsep', 'locale',
5223 'localewin', 'localewincharset', 'oldcharset',
5224 'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
5225 'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
5226 'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
5227 'thischarset', 'thisdirection', 'thislanguage', 'strftimedatetimeshort');
5229 $filetocheck = 'langconfig.php';
5230 $defaultlang = 'en_utf8';
5231 if (in_array($identifier, $langconfigstrs)) {
5232 $module = 'langconfig'; //This strings are under langconfig.php for 1.6 lang packs
5235 $lang = current_language();
5237 if ($module == '') {
5241 /// If the "module" is actually a pathname, then automatically derive the proper module name
5242 if (strpos($module, '/') !== false) {
5243 $modulepath = split('/', $module);
5245 switch ($modulepath[0]) {
5248 $module = $modulepath[1];
5253 $module = 'block_'.$modulepath[1];
5257 $module = 'enrol_'.$modulepath[1];
5261 $module = 'format_'.$modulepath[1];
5265 $module = 'grade'.$modulepath[1].'_'.$modulepath[2];
5270 /// if $a happens to have % in it, double it so sprintf() doesn't break
5272 $a = clean_getstring_data( $a );
5275 /// Define the two or three major locations of language strings for this module
5276 $locations = array();
5278 if (!empty($extralocations)) { // Calling code has a good idea where to look
5279 if (is_array($extralocations)) {
5280 $locations +
= $extralocations;
5281 } else if (is_string($extralocations)) {
5282 $locations[] = $extralocations;
5284 debugging('Bad lang path provided');
5288 if (isset($CFG->running_installer
)) {
5289 $module = 'installer';
5290 $filetocheck = 'installer.php';
5291 $locations[] = $CFG->dirroot
.'/install/lang/';
5292 $locations[] = $CFG->dataroot
.'/lang/';
5293 $locations[] = $CFG->dirroot
.'/lang/';
5294 $defaultlang = 'en_utf8';
5296 $locations[] = $CFG->dataroot
.'/lang/';
5297 $locations[] = $CFG->dirroot
.'/lang/';
5298 $locations[] = $CFG->dirroot
.'/local/lang/';
5301 /// Add extra places to look for strings for particular plugin types.
5302 $rules = places_to_search_for_lang_strings();
5303 $exceptions = $rules['__exceptions'];
5304 unset($rules['__exceptions']);
5306 if (!in_array($module, $exceptions)) {
5307 $dividerpos = strpos($module, '_');
5308 if ($dividerpos === false) {
5312 $type = substr($module, 0, $dividerpos +
1);
5313 $plugin = substr($module, $dividerpos +
1);
5315 if (!empty($rules[$type])) {
5316 foreach ($rules[$type] as $location) {
5317 $locations[] = $CFG->dirroot
. "/$location/$plugin/lang/";
5322 /// First check all the normal locations for the string in the current language
5324 foreach ($locations as $location) {
5325 $locallangfile = $location.$lang.'_local'.'/'.$module.'.php'; //first, see if there's a local file
5326 if (file_exists($locallangfile)) {
5327 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5328 if (eval($result) === FALSE) {
5329 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE
);
5331 return $resultstring;
5334 //if local directory not found, or particular string does not exist in local direcotry
5335 $langfile = $location.$lang.'/'.$module.'.php';
5336 if (file_exists($langfile)) {
5337 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5338 if (eval($result) === FALSE) {
5339 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE
);
5341 return $resultstring;
5346 /// If the preferred language was English (utf8) we can abort now
5347 /// saving some checks beacuse it's the only "root" lang
5348 if ($lang == 'en_utf8') {
5349 return '[['. $identifier .']]';
5352 /// Is a parent language defined? If so, try to find this string in a parent language file
5354 foreach ($locations as $location) {
5355 $langfile = $location.$lang.'/'.$filetocheck;
5356 if (file_exists($langfile)) {
5357 if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
5359 if (!empty($parentlang)) { // found it!
5361 //first, see if there's a local file for parent
5362 $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
5363 if (file_exists($locallangfile)) {
5364 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5366 return $resultstring;
5370 //if local directory not found, or particular string does not exist in local direcotry
5371 $langfile = $location.$parentlang.'/'.$module.'.php';
5372 if (file_exists($langfile)) {
5373 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5375 return $resultstring;
5383 /// Our only remaining option is to try English
5385 foreach ($locations as $location) {
5386 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5387 if (file_exists($locallangfile)) {
5388 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5390 return $resultstring;
5394 //if local_en not found, or string not found in local_en
5395 $langfile = $location.$defaultlang.'/'.$module.'.php';
5397 if (file_exists($langfile)) {
5398 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5400 return $resultstring;
5405 /// And, because under 1.6 en is defined as en_utf8 child, me must try
5406 /// if it hasn't been queried before.
5407 if ($defaultlang == 'en') {
5408 $defaultlang = 'en_utf8';
5409 foreach ($locations as $location) {
5410 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5411 if (file_exists($locallangfile)) {
5412 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5414 return $resultstring;
5418 //if local_en not found, or string not found in local_en
5419 $langfile = $location.$defaultlang.'/'.$module.'.php';
5421 if (file_exists($langfile)) {
5422 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5424 return $resultstring;
5430 return '[['.$identifier.']]'; // Last resort
5434 * This function is only used from {@link get_string()}.
5436 * @internal Only used from get_string, not meant to be public API
5437 * @param string $identifier ?
5438 * @param string $langfile ?
5439 * @param string $destination ?
5440 * @return string|false ?
5441 * @staticvar array $strings Localized strings
5443 * @todo Finish documenting this function.
5445 function get_string_from_file($identifier, $langfile, $destination) {
5447 static $strings; // Keep the strings cached in memory.
5449 if (empty($strings[$langfile])) {
5451 include ($langfile);
5452 $strings[$langfile] = $string;
5454 $string = &$strings[$langfile];
5457 if (!isset ($string[$identifier])) {
5461 return $destination .'= sprintf("'. $string[$identifier] .'");';
5465 * Converts an array of strings to their localized value.
5467 * @param array $array An array of strings
5468 * @param string $module The language module that these strings can be found in.
5471 function get_strings($array, $module='') {
5474 foreach ($array as $item) {
5475 $string->$item = get_string($item, $module);
5481 * Returns a list of language codes and their full names
5482 * hides the _local files from everyone.
5483 * @param bool refreshcache force refreshing of lang cache
5484 * @param bool returnall ignore langlist, return all languages available
5485 * @return array An associative array with contents in the form of LanguageCode => LanguageName
5487 function get_list_of_languages($refreshcache=false, $returnall=false) {
5491 $languages = array();
5493 $filetocheck = 'langconfig.php';
5495 if (!$refreshcache && !$returnall && !empty($CFG->langcache
) && file_exists($CFG->dataroot
.'/cache/languages')) {
5496 /// read available langs from cache
5498 $lines = file($CFG->dataroot
.'/cache/languages');
5499 foreach ($lines as $line) {
5500 $line = trim($line);
5501 if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
5502 $languages[$matches[1]] = $matches[2];
5505 unset($lines); unset($line); unset($matches);
5509 if (!$returnall && !empty($CFG->langlist
)) {
5510 /// return only languages allowed in langlist admin setting
5512 $langlist = explode(',', $CFG->langlist
);
5513 // fix short lang names first - non existing langs are skipped anyway...
5514 foreach ($langlist as $lang) {
5515 if (strpos($lang, '_utf8') === false) {
5516 $langlist[] = $lang.'_utf8';
5519 // find existing langs from langlist
5520 foreach ($langlist as $lang) {
5521 $lang = trim($lang); //Just trim spaces to be a bit more permissive
5522 if (strstr($lang, '_local')!==false) {
5525 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5526 $shortlang = substr($lang, 0, -5);
5530 /// Search under dirroot/lang
5531 if (file_exists($CFG->dirroot
.'/lang/'. $lang .'/'. $filetocheck)) {
5532 include($CFG->dirroot
.'/lang/'. $lang .'/'. $filetocheck);
5533 if (!empty($string['thislanguage'])) {
5534 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5538 /// And moodledata/lang
5539 if (file_exists($CFG->dataroot
.'/lang/'. $lang .'/'. $filetocheck)) {
5540 include($CFG->dataroot
.'/lang/'. $lang .'/'. $filetocheck);
5541 if (!empty($string['thislanguage'])) {
5542 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5549 /// return all languages available in system
5550 /// Fetch langs from moodle/lang directory
5551 $langdirs = get_list_of_plugins('lang');
5552 /// Fetch langs from moodledata/lang directory
5553 $langdirs2 = get_list_of_plugins('lang', '', $CFG->dataroot
);
5554 /// Merge both lists of langs
5555 $langdirs = array_merge($langdirs, $langdirs2);
5558 /// Get some info from each lang (first from moodledata, then from moodle)
5559 foreach ($langdirs as $lang) {
5560 if (strstr($lang, '_local')!==false) {
5563 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5564 $shortlang = substr($lang, 0, -5);
5568 /// Search under moodledata/lang
5569 if (file_exists($CFG->dataroot
.'/lang/'. $lang .'/'. $filetocheck)) {
5570 include($CFG->dataroot
.'/lang/'. $lang .'/'. $filetocheck);
5571 if (!empty($string['thislanguage'])) {
5572 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5576 /// And dirroot/lang
5577 if (file_exists($CFG->dirroot
.'/lang/'. $lang .'/'. $filetocheck)) {
5578 include($CFG->dirroot
.'/lang/'. $lang .'/'. $filetocheck);
5579 if (!empty($string['thislanguage'])) {
5580 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5587 if ($refreshcache && !empty($CFG->langcache
)) {
5589 // we have a list of all langs only, just delete old cache
5590 @unlink
($CFG->dataroot
.'/cache/languages');
5593 // store the list of allowed languages
5594 if ($file = fopen($CFG->dataroot
.'/cache/languages', 'w')) {
5595 foreach ($languages as $key => $value) {
5596 fwrite($file, "$key $value\n");
5607 * Returns a list of charset codes. It's hardcoded, so they should be added manually
5608 * (cheking that such charset is supported by the texlib library!)
5610 * @return array And associative array with contents in the form of charset => charset
5612 function get_list_of_charsets() {
5615 'EUC-JP' => 'EUC-JP',
5616 'ISO-2022-JP'=> 'ISO-2022-JP',
5617 'ISO-8859-1' => 'ISO-8859-1',
5618 'SHIFT-JIS' => 'SHIFT-JIS',
5619 'GB2312' => 'GB2312',
5620 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
5621 'UTF-8' => 'UTF-8');
5629 * Returns a list of country names in the current language
5635 function get_list_of_countries() {
5638 $lang = current_language();
5640 if (!file_exists($CFG->dirroot
.'/lang/'. $lang .'/countries.php') &&
5641 !file_exists($CFG->dataroot
.'/lang/'. $lang .'/countries.php')) {
5642 if ($parentlang = get_string('parentlanguage')) {
5643 if (file_exists($CFG->dirroot
.'/lang/'. $parentlang .'/countries.php') ||
5644 file_exists($CFG->dataroot
.'/lang/'. $parentlang .'/countries.php')) {
5645 $lang = $parentlang;
5647 $lang = 'en_utf8'; // countries.php must exist in this pack
5650 $lang = 'en_utf8'; // countries.php must exist in this pack
5654 if (file_exists($CFG->dataroot
.'/lang/'. $lang .'/countries.php')) {
5655 include($CFG->dataroot
.'/lang/'. $lang .'/countries.php');
5656 } else if (file_exists($CFG->dirroot
.'/lang/'. $lang .'/countries.php')) {
5657 include($CFG->dirroot
.'/lang/'. $lang .'/countries.php');
5660 if (!empty($string)) {
5661 uasort($string, 'strcoll');
5668 * Returns a list of valid and compatible themes
5673 function get_list_of_themes() {
5679 if (!empty($CFG->themelist
)) { // use admin's list of themes
5680 $themelist = explode(',', $CFG->themelist
);
5682 $themelist = get_list_of_plugins("theme");
5685 foreach ($themelist as $key => $theme) {
5686 if (!file_exists("$CFG->themedir/$theme/config.php")) { // bad folder
5689 $THEME = new object(); // Note this is not the global one!! :-)
5690 include("$CFG->themedir/$theme/config.php");
5691 if (!isset($THEME->sheets
)) { // Not a valid 1.5 theme
5694 $themes[$theme] = $theme;
5703 * Returns a list of picture names in the current or specified language
5708 function get_list_of_pixnames($lang = '') {
5712 $lang = current_language();
5717 $path = $CFG->dirroot
.'/lang/en_utf8/pix.php'; // always exists
5719 if (file_exists($CFG->dataroot
.'/lang/'. $lang .'_local/pix.php')) {
5720 $path = $CFG->dataroot
.'/lang/'. $lang .'_local/pix.php';
5722 } else if (file_exists($CFG->dirroot
.'/lang/'. $lang .'/pix.php')) {
5723 $path = $CFG->dirroot
.'/lang/'. $lang .'/pix.php';
5725 } else if (file_exists($CFG->dataroot
.'/lang/'. $lang .'/pix.php')) {
5726 $path = $CFG->dataroot
.'/lang/'. $lang .'/pix.php';
5728 } else if ($parentlang = get_string('parentlanguage') and $parentlang != '[[parentlanguage]]') {
5729 return get_list_of_pixnames($parentlang); //return pixnames from parent language instead
5738 * Returns a list of timezones in the current language
5743 function get_list_of_timezones() {
5748 if (!empty($timezones)) { // This function has been called recently
5752 $timezones = array();
5754 if ($rawtimezones = get_records_sql('SELECT MAX(id), name FROM '.$CFG->prefix
.'timezone GROUP BY name')) {
5755 foreach($rawtimezones as $timezone) {
5756 if (!empty($timezone->name
)) {
5757 $timezones[$timezone->name
] = get_string(strtolower($timezone->name
), 'timezones');
5758 if (substr($timezones[$timezone->name
], 0, 1) == '[') { // No translation found
5759 $timezones[$timezone->name
] = $timezone->name
;
5767 for ($i = -13; $i <= 13; $i +
= .5) {
5770 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
5771 } else if ($i > 0) {
5772 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
5774 $timezones[sprintf("%.1f", $i)] = $tzstring;
5782 * Returns a list of currencies in the current language
5788 function get_list_of_currencies() {
5791 $lang = current_language();
5793 if (!file_exists($CFG->dataroot
.'/lang/'. $lang .'/currencies.php')) {
5794 if ($parentlang = get_string('parentlanguage')) {
5795 if (file_exists($CFG->dataroot
.'/lang/'. $parentlang .'/currencies.php')) {
5796 $lang = $parentlang;
5798 $lang = 'en_utf8'; // currencies.php must exist in this pack
5801 $lang = 'en_utf8'; // currencies.php must exist in this pack
5805 if (file_exists($CFG->dataroot
.'/lang/'. $lang .'/currencies.php')) {
5806 include_once($CFG->dataroot
.'/lang/'. $lang .'/currencies.php');
5807 } else { //if en_utf8 is not installed in dataroot
5808 include_once($CFG->dirroot
.'/lang/'. $lang .'/currencies.php');
5811 if (!empty($string)) {
5819 /// ENCRYPTION ////////////////////////////////////////////////
5824 * @param string $data ?
5826 * @todo Finish documenting this function
5828 function rc4encrypt($data) {
5829 $password = 'nfgjeingjk';
5830 return endecrypt($password, $data, '');
5836 * @param string $data ?
5838 * @todo Finish documenting this function
5840 function rc4decrypt($data) {
5841 $password = 'nfgjeingjk';
5842 return endecrypt($password, $data, 'de');
5846 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
5848 * @param string $pwd ?
5849 * @param string $data ?
5850 * @param string $case ?
5852 * @todo Finish documenting this function
5854 function endecrypt ($pwd, $data, $case) {
5856 if ($case == 'de') {
5857 $data = urldecode($data);
5865 $pwd_length = strlen($pwd);
5867 for ($i = 0; $i <= 255; $i++
) {
5868 $key[$i] = ord(substr($pwd, ($i %
$pwd_length), 1));
5874 for ($i = 0; $i <= 255; $i++
) {
5875 $x = ($x +
$box[$i] +
$key[$i]) %
256;
5876 $temp_swap = $box[$i];
5877 $box[$i] = $box[$x];
5878 $box[$x] = $temp_swap;
5890 for ($i = 0; $i < strlen($data); $i++
) {
5891 $a = ($a +
1) %
256;
5892 $j = ($j +
$box[$a]) %
256;
5894 $box[$a] = $box[$j];
5896 $k = $box[(($box[$a] +
$box[$j]) %
256)];
5897 $cipherby = ord(substr($data, $i, 1)) ^
$k;
5898 $cipher .= chr($cipherby);
5901 if ($case == 'de') {
5902 $cipher = urldecode(urlencode($cipher));
5904 $cipher = urlencode($cipher);
5911 /// CALENDAR MANAGEMENT ////////////////////////////////////////////////////////////////
5915 * Call this function to add an event to the calendar table
5916 * and to call any calendar plugins
5919 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field. The object event should include the following:
5921 * <li><b>$event->name</b> - Name for the event
5922 * <li><b>$event->description</b> - Description of the event (defaults to '')
5923 * <li><b>$event->format</b> - Format for the description (using formatting types defined at the top of weblib.php)
5924 * <li><b>$event->courseid</b> - The id of the course this event belongs to (0 = all courses)
5925 * <li><b>$event->groupid</b> - The id of the group this event belongs to (0 = no group)
5926 * <li><b>$event->userid</b> - The id of the user this event belongs to (0 = no user)
5927 * <li><b>$event->modulename</b> - Name of the module that creates this event
5928 * <li><b>$event->instance</b> - Instance of the module that owns this event
5929 * <li><b>$event->eventtype</b> - The type info together with the module info could
5930 * be used by calendar plugins to decide how to display event
5931 * <li><b>$event->timestart</b>- Timestamp for start of event
5932 * <li><b>$event->timeduration</b> - Duration (defaults to zero)
5933 * <li><b>$event->visible</b> - 0 if the event should be hidden (e.g. because the activity that created it is hidden)
5935 * @return int The id number of the resulting record
5937 function add_event($event) {
5941 $event->timemodified
= time();
5943 if (!$event->id
= insert_record('event', $event)) {
5947 if (!empty($CFG->calendar
)) { // call the add_event function of the selected calendar
5948 if (file_exists($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php')) {
5949 include_once($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php');
5950 $calendar_add_event = $CFG->calendar
.'_add_event';
5951 if (function_exists($calendar_add_event)) {
5952 $calendar_add_event($event);
5961 * Call this function to update an event in the calendar table
5962 * the event will be identified by the id field of the $event object.
5965 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
5968 function update_event($event) {
5972 $event->timemodified
= time();
5974 if (!empty($CFG->calendar
)) { // call the update_event function of the selected calendar
5975 if (file_exists($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php')) {
5976 include_once($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php');
5977 $calendar_update_event = $CFG->calendar
.'_update_event';
5978 if (function_exists($calendar_update_event)) {
5979 $calendar_update_event($event);
5983 return update_record('event', $event);
5987 * Call this function to delete the event with id $id from calendar table.
5990 * @param int $id The id of an event from the 'calendar' table.
5991 * @return array An associative array with the results from the SQL call.
5992 * @todo Verify return type
5994 function delete_event($id) {
5998 if (!empty($CFG->calendar
)) { // call the delete_event function of the selected calendar
5999 if (file_exists($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php')) {
6000 include_once($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php');
6001 $calendar_delete_event = $CFG->calendar
.'_delete_event';
6002 if (function_exists($calendar_delete_event)) {
6003 $calendar_delete_event($id);
6007 return delete_records('event', 'id', $id);
6011 * Call this function to hide an event in the calendar table
6012 * the event will be identified by the id field of the $event object.
6015 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6016 * @return array An associative array with the results from the SQL call.
6017 * @todo Verify return type
6019 function hide_event($event) {
6022 if (!empty($CFG->calendar
)) { // call the update_event function of the selected calendar
6023 if (file_exists($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php')) {
6024 include_once($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php');
6025 $calendar_hide_event = $CFG->calendar
.'_hide_event';
6026 if (function_exists($calendar_hide_event)) {
6027 $calendar_hide_event($event);
6031 return set_field('event', 'visible', 0, 'id', $event->id
);
6035 * Call this function to unhide an event in the calendar table
6036 * the event will be identified by the id field of the $event object.
6039 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6040 * @return array An associative array with the results from the SQL call.
6041 * @todo Verify return type
6043 function show_event($event) {
6046 if (!empty($CFG->calendar
)) { // call the update_event function of the selected calendar
6047 if (file_exists($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php')) {
6048 include_once($CFG->dirroot
.'/calendar/'. $CFG->calendar
.'/lib.php');
6049 $calendar_show_event = $CFG->calendar
.'_show_event';
6050 if (function_exists($calendar_show_event)) {
6051 $calendar_show_event($event);
6055 return set_field('event', 'visible', 1, 'id', $event->id
);
6059 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6062 * Lists plugin directories within some directory
6065 * @param string $plugin dir under we'll look for plugins (defaults to 'mod')
6066 * @param string $exclude dir name to exclude from the list (defaults to none)
6067 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
6068 * @return array of plugins found under the requested parameters
6070 function get_list_of_plugins($plugin='mod', $exclude='', $basedir='') {
6076 if (empty($basedir)) {
6078 # This switch allows us to use the appropiate theme directory - and potentialy alternatives for other plugins
6081 $basedir = $CFG->themedir
;
6085 $basedir = $CFG->dirroot
.'/'. $plugin;
6089 $basedir = $basedir .'/'. $plugin;
6092 if (file_exists($basedir) && filetype($basedir) == 'dir') {
6093 $dirhandle = opendir($basedir);
6094 while (false !== ($dir = readdir($dirhandle))) {
6095 $firstchar = substr($dir, 0, 1);
6096 if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == $exclude) {
6099 if (filetype($basedir .'/'. $dir) != 'dir') {
6104 closedir($dirhandle);
6113 * Returns true if the current version of PHP is greater that the specified one.
6115 * @param string $version The version of php being tested.
6118 function check_php_version($version='4.1.0') {
6119 return (version_compare(phpversion(), $version) >= 0);
6123 * Checks to see if is the browser operating system matches the specified
6126 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
6129 * @param string $brand The operating system identifier being tested
6130 * @return bool true if the given brand below to the detected operating system
6132 function check_browser_operating_system($brand) {
6133 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6137 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
6145 * Checks to see if is a browser matches the specified
6146 * brand and is equal or better version.
6149 * @param string $brand The browser identifier being tested
6150 * @param int $version The version of the browser
6151 * @return bool true if the given version is below that of the detected browser
6153 function check_browser_version($brand='MSIE', $version=5.5) {
6154 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6158 $agent = $_SERVER['HTTP_USER_AGENT'];
6162 case 'Camino': /// Mozilla Firefox browsers
6164 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
6165 if (version_compare($match[1], $version) >= 0) {
6172 case 'Firefox': /// Mozilla Firefox browsers
6174 if (preg_match("/Firefox\/([0-9\.]+)/i", $agent, $match)) {
6175 if (version_compare($match[1], $version) >= 0) {
6182 case 'Gecko': /// Gecko based browsers
6184 if (substr_count($agent, 'Camino')) {
6185 // MacOS X Camino support
6186 $version = 20041110;
6189 // the proper string - Gecko/CCYYMMDD Vendor/Version
6190 // Faster version and work-a-round No IDN problem.
6191 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
6192 if ($match[1] > $version) {
6199 case 'MSIE': /// Internet Explorer
6201 if (strpos($agent, 'Opera')) { // Reject Opera
6204 $string = explode(';', $agent);
6205 if (!isset($string[1])) {
6208 $string = explode(' ', trim($string[1]));
6209 if (!isset($string[0]) and !isset($string[1])) {
6212 if ($string[0] == $brand and (float)$string[1] >= $version ) {
6217 case 'Opera': /// Opera
6219 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
6220 if (version_compare($match[1], $version) >= 0) {
6226 case 'Safari': /// Safari
6227 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SimbianOS
6228 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
6230 } elseif (strpos($agent, 'Shiira')) { // Reject Shiira
6232 } elseif (strpos($agent, 'SimbianOS')) { // Reject SimbianOS
6236 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
6237 if (version_compare($match[1], $version) >= 0) {
6250 * This function makes the return value of ini_get consistent if you are
6251 * setting server directives through the .htaccess file in apache.
6252 * Current behavior for value set from php.ini On = 1, Off = [blank]
6253 * Current behavior for value set from .htaccess On = On, Off = Off
6254 * Contributed by jdell @ unr.edu
6256 * @param string $ini_get_arg ?
6258 * @todo Finish documenting this function
6260 function ini_get_bool($ini_get_arg) {
6261 $temp = ini_get($ini_get_arg);
6263 if ($temp == '1' or strtolower($temp) == 'on') {
6270 * Compatibility stub to provide backward compatibility
6272 * Determines if the HTML editor is enabled.
6273 * @deprecated Use {@link can_use_html_editor()} instead.
6275 function can_use_richtext_editor() {
6276 return can_use_html_editor();
6280 * Determines if the HTML editor is enabled.
6282 * This depends on site and user
6283 * settings, as well as the current browser being used.
6285 * @return string|false Returns false if editor is not being used, otherwise
6286 * returns 'MSIE' or 'Gecko'.
6288 function can_use_html_editor() {
6291 if (!empty($USER->htmleditor
) and !empty($CFG->htmleditor
)) {
6292 if (check_browser_version('MSIE', 5.5)) {
6294 } else if (check_browser_version('Gecko', 20030516)) {
6302 * Hack to find out the GD version by parsing phpinfo output
6304 * @return int GD version (1, 2, or 0)
6306 function check_gd_version() {
6309 if (function_exists('gd_info')){
6310 $gd_info = gd_info();
6311 if (substr_count($gd_info['GD Version'], '2.')) {
6313 } else if (substr_count($gd_info['GD Version'], '1.')) {
6319 phpinfo(INFO_MODULES
);
6320 $phpinfo = ob_get_contents();
6323 $phpinfo = explode("\n", $phpinfo);
6326 foreach ($phpinfo as $text) {
6327 $parts = explode('</td>', $text);
6328 foreach ($parts as $key => $val) {
6329 $parts[$key] = trim(strip_tags($val));
6331 if ($parts[0] == 'GD Version') {
6332 if (substr_count($parts[1], '2.0')) {
6335 $gdversion = intval($parts[1]);
6340 return $gdversion; // 1, 2 or 0
6344 * Determine if moodle installation requires update
6346 * Checks version numbers of main code and all modules to see
6347 * if there are any mismatches
6352 function moodle_needs_upgrading() {
6356 include_once($CFG->dirroot
.'/version.php'); # defines $version and upgrades
6357 if ($CFG->version
) {
6358 if ($version > $CFG->version
) {
6361 if ($mods = get_list_of_plugins('mod')) {
6362 foreach ($mods as $mod) {
6363 $fullmod = $CFG->dirroot
.'/mod/'. $mod;
6364 $module = new object();
6365 if (!is_readable($fullmod .'/version.php')) {
6366 notify('Module "'. $mod .'" is not readable - check permissions');
6369 include_once($fullmod .'/version.php'); # defines $module with version etc
6370 if ($currmodule = get_record('modules', 'name', $mod)) {
6371 if ($module->version
> $currmodule->version
) {
6384 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
6387 * Notify admin users or admin user of any failed logins (since last notification).
6389 * Note that this function must be only executed from the cron script
6390 * It uses the cache_flags system to store temporary records, deleting them
6391 * by name before finishing
6397 function notify_login_failures() {
6400 switch ($CFG->notifyloginfailures
) {
6402 $recip = array(get_admin());
6405 $recip = get_admins();
6409 if (empty($CFG->lastnotifyfailure
)) {
6410 $CFG->lastnotifyfailure
=0;
6413 // we need to deal with the threshold stuff first.
6414 if (empty($CFG->notifyloginthreshold
)) {
6415 $CFG->notifyloginthreshold
= 10; // default to something sensible.
6418 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
6419 /// and insert them into the cache_flags temp table
6420 $iprs = get_recordset_sql("SELECT ip, count(*)
6421 FROM {$CFG->prefix}log
6422 WHERE module = 'login'
6423 AND action = 'error'
6424 AND time > $CFG->lastnotifyfailure
6426 HAVING count(*) >= $CFG->notifyloginthreshold");
6427 while ($iprec = rs_fetch_next_record($iprs)) {
6428 if (!empty($iprec->ip
)) {
6429 set_cache_flag('login_failure_by_ip', $iprec->ip
, '1', 0);
6434 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
6435 /// and insert them into the cache_flags temp table
6436 $infors = get_recordset_sql("SELECT info, count(*)
6437 FROM {$CFG->prefix}log
6438 WHERE module = 'login'
6439 AND action = 'error'
6440 AND time > $CFG->lastnotifyfailure
6442 HAVING count(*) >= $CFG->notifyloginthreshold");
6443 while ($inforec = rs_fetch_next_record($infors)) {
6444 if (!empty($inforec->info
)) {
6445 set_cache_flag('login_failure_by_info', $inforec->info
, '1', 0);
6450 /// Now, select all the login error logged records belonging to the ips and infos
6451 /// since lastnotifyfailure, that we have stored in the cache_flags table
6452 $logsrs = get_recordset_sql("SELECT l.*, u.firstname, u.lastname
6453 FROM {$CFG->prefix}log l
6454 JOIN {$CFG->prefix}cache_flags cf ON (l.ip = cf.name)
6455 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6456 WHERE l.module = 'login'
6457 AND l.action = 'error'
6458 AND l.time > $CFG->lastnotifyfailure
6459 AND cf.flagtype = 'login_failure_by_ip'
6461 SELECT l.*, u.firstname, u.lastname
6462 FROM {$CFG->prefix}log l
6463 JOIN {$CFG->prefix}cache_flags cf ON (l.info = cf.name)
6464 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6465 WHERE l.module = 'login'
6466 AND l.action = 'error'
6467 AND l.time > $CFG->lastnotifyfailure
6468 AND cf.flagtype = 'login_failure_by_info'
6469 ORDER BY time DESC");
6471 /// Init some variables
6474 /// Iterate over the logs recordset
6475 while ($log = rs_fetch_next_record($logsrs)) {
6476 $log->time
= userdate($log->time
);
6477 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
6482 /// If we haven't run in the last hour and
6483 /// we have something useful to report and we
6484 /// are actually supposed to be reporting to somebody
6485 if ((time() - HOURSECS
) > $CFG->lastnotifyfailure
&& $count > 0 && is_array($recip) && count($recip) > 0) {
6487 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname
));
6488 /// Calculate the complete body of notification (start + messages + end)
6489 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot
) .
6490 (($CFG->lastnotifyfailure
!= 0) ?
'('.userdate($CFG->lastnotifyfailure
).')' : '')."\n\n" .
6492 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot
)."\n\n";
6494 /// For each destination, send mail
6495 foreach ($recip as $admin) {
6496 mtrace('Emailing '. $admin->username
.' about '. $count .' failed login attempts');
6497 email_to_user($admin,get_admin(), $subject, $body);
6500 /// Update lastnotifyfailure with current time
6501 set_config('lastnotifyfailure', time());
6504 /// Finally, delete all the temp records we have created in cache_flags
6505 delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
6512 * @param string $locale ?
6513 * @todo Finish documenting this function
6515 function moodle_setlocale($locale='') {
6519 static $currentlocale = ''; // last locale caching
6521 $oldlocale = $currentlocale;
6523 /// Fetch the correct locale based on ostype
6524 if($CFG->ostype
== 'WINDOWS') {
6525 $stringtofetch = 'localewin';
6527 $stringtofetch = 'locale';
6530 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
6531 if (!empty($locale)) {
6532 $currentlocale = $locale;
6533 } else if (!empty($CFG->locale
)) { // override locale for all language packs
6534 $currentlocale = $CFG->locale
;
6536 $currentlocale = get_string($stringtofetch);
6539 /// do nothing if locale already set up
6540 if ($oldlocale == $currentlocale) {
6544 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
6545 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
6546 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
6548 /// Get current values
6549 $monetary= setlocale (LC_MONETARY
, 0);
6550 $numeric = setlocale (LC_NUMERIC
, 0);
6551 $ctype = setlocale (LC_CTYPE
, 0);
6552 if ($CFG->ostype
!= 'WINDOWS') {
6553 $messages= setlocale (LC_MESSAGES
, 0);
6555 /// Set locale to all
6556 setlocale (LC_ALL
, $currentlocale);
6558 setlocale (LC_MONETARY
, $monetary);
6559 setlocale (LC_NUMERIC
, $numeric);
6560 if ($CFG->ostype
!= 'WINDOWS') {
6561 setlocale (LC_MESSAGES
, $messages);
6563 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
6564 setlocale (LC_CTYPE
, $ctype);
6569 * Converts string to lowercase using most compatible function available.
6571 * @param string $string The string to convert to all lowercase characters.
6572 * @param string $encoding The encoding on the string.
6574 * @todo Add examples of calling this function with/without encoding types
6575 * @deprecated Use textlib->strtolower($text) instead.
6577 function moodle_strtolower ($string, $encoding='') {
6579 //If not specified use utf8
6580 if (empty($encoding)) {
6581 $encoding = 'UTF-8';
6584 $textlib = textlib_get_instance();
6586 return $textlib->strtolower($string, $encoding);
6590 * Count words in a string.
6592 * Words are defined as things between whitespace.
6594 * @param string $string The text to be searched for words.
6595 * @return int The count of words in the specified string
6597 function count_words($string) {
6598 $string = strip_tags($string);
6599 return count(preg_split("/\w\b/", $string)) - 1;
6602 /** Count letters in a string.
6604 * Letters are defined as chars not in tags and different from whitespace.
6606 * @param string $string The text to be searched for letters.
6607 * @return int The count of letters in the specified text.
6609 function count_letters($string) {
6610 /// Loading the textlib singleton instance. We are going to need it.
6611 $textlib = textlib_get_instance();
6613 $string = strip_tags($string); // Tags are out now
6614 $string = ereg_replace('[[:space:]]*','',$string); //Whitespace are out now
6616 return $textlib->strlen($string);
6620 * Generate and return a random string of the specified length.
6622 * @param int $length The length of the string to be created.
6625 function random_string ($length=15) {
6626 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
6627 $pool .= 'abcdefghijklmnopqrstuvwxyz';
6628 $pool .= '0123456789';
6629 $poollen = strlen($pool);
6630 mt_srand ((double) microtime() * 1000000);
6632 for ($i = 0; $i < $length; $i++
) {
6633 $string .= substr($pool, (mt_rand()%
($poollen)), 1);
6639 * Given some text (which may contain HTML) and an ideal length,
6640 * this function truncates the text neatly on a word boundary if possible
6641 * @param string $text - text to be shortened
6642 * @param int $ideal - ideal string length
6643 * @param boolean $exact if false, $text will not be cut mid-word
6644 * @return string $truncate - shortened string
6647 function shorten_text($text, $ideal=30, $exact = false) {
6652 // if the plain text is shorter than the maximum length, return the whole text
6653 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
6657 // splits all html-tags to scanable lines
6658 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER
);
6660 $total_length = strlen($ending);
6661 $open_tags = array();
6664 foreach ($lines as $line_matchings) {
6665 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
6666 if (!empty($line_matchings[1])) {
6667 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
6668 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
6670 // if tag is a closing tag (f.e. </b>)
6671 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
6672 // delete tag from $open_tags list
6673 $pos = array_search($tag_matchings[1], array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
6674 if ($pos !== false) {
6675 unset($open_tags[$pos]);
6677 // if tag is an opening tag (f.e. <b>)
6678 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
6679 // add tag to the beginning of $open_tags list
6680 array_unshift($open_tags, strtolower($tag_matchings[1]));
6682 // add html-tag to $truncate'd text
6683 $truncate .= $line_matchings[1];
6686 // calculate the length of the plain text part of the line; handle entities as one character
6687 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
6688 if ($total_length+
$content_length > $ideal) {
6689 // the number of characters which are left
6690 $left = $ideal - $total_length;
6691 $entities_length = 0;
6692 // search for html entities
6693 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
)) {
6694 // calculate the real length of all entities in the legal range
6695 foreach ($entities[0] as $entity) {
6696 if ($entity[1]+
1-$entities_length <= $left) {
6698 $entities_length +
= strlen($entity[0]);
6700 // no more characters left
6705 $truncate .= substr($line_matchings[2], 0, $left+
$entities_length);
6706 // maximum lenght is reached, so get off the loop
6709 $truncate .= $line_matchings[2];
6710 $total_length +
= $content_length;
6713 // if the maximum length is reached, get off the loop
6714 if($total_length >= $ideal) {
6719 // if the words shouldn't be cut in the middle...
6721 // ...search the last occurance of a space...
6722 for ($k=strlen($truncate);$k>0;$k--) {
6723 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
6724 if ($char == '.' or $char == ' ') {
6727 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
6728 $breakpos = $k; // can be truncated at any UTF-8
6729 break; // character boundary.
6734 if (isset($breakpos)) {
6735 // ...and cut the text in this position
6736 $truncate = substr($truncate, 0, $breakpos);
6740 // add the defined ending to the text
6741 $truncate .= $ending;
6743 // close all unclosed html-tags
6744 foreach ($open_tags as $tag) {
6745 $truncate .= '</' . $tag . '>';
6753 * Given dates in seconds, how many weeks is the date from startdate
6754 * The first week is 1, the second 2 etc ...
6757 * @param ? $startdate ?
6758 * @param ? $thedate ?
6760 * @todo Finish documenting this function
6762 function getweek ($startdate, $thedate) {
6763 if ($thedate < $startdate) { // error
6767 return floor(($thedate - $startdate) / WEEKSECS
) +
1;
6771 * returns a randomly generated password of length $maxlen. inspired by
6772 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
6773 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
6775 * @param int $maxlen The maximum size of the password being generated.
6778 function generate_password($maxlen=10) {
6781 if (empty($CFG->passwordpolicy
)) {
6782 $fillers = PASSWORD_DIGITS
;
6783 $wordlist = file($CFG->wordlist
);
6784 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
6785 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
6786 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
6787 $password = $word1 . $filler1 . $word2;
6789 $maxlen = !empty($CFG->minpasswordlength
) ?
$CFG->minpasswordlength
: 0;
6790 $digits = $CFG->minpassworddigits
;
6791 $lower = $CFG->minpasswordlower
;
6792 $upper = $CFG->minpasswordupper
;
6793 $nonalphanum = $CFG->minpasswordnonalphanum
;
6794 $additional = $maxlen - ($lower +
$upper +
$digits +
$nonalphanum);
6796 // Make sure we have enough characters to fulfill
6797 // complexity requirements
6798 $passworddigits = PASSWORD_DIGITS
;
6799 while ($digits > strlen($passworddigits)) {
6800 $passworddigits .= PASSWORD_DIGITS
;
6802 $passwordlower = PASSWORD_LOWER
;
6803 while ($lower > strlen($passwordlower)) {
6804 $passwordlower .= PASSWORD_LOWER
;
6806 $passwordupper = PASSWORD_UPPER
;
6807 while ($upper > strlen($passwordupper)) {
6808 $passwordupper .= PASSWORD_UPPER
;
6810 $passwordnonalphanum = PASSWORD_NONALPHANUM
;
6811 while ($nonalphanum > strlen($passwordnonalphanum)) {
6812 $passwordnonalphanum .= PASSWORD_NONALPHANUM
;
6815 // Now mix and shuffle it all
6816 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
6817 substr(str_shuffle ($passwordupper), 0, $upper) .
6818 substr(str_shuffle ($passworddigits), 0, $digits) .
6819 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
6820 substr(str_shuffle ($passwordlower .
6823 $passwordnonalphanum), 0 , $additional));
6826 return substr ($password, 0, $maxlen);
6830 * Given a float, prints it nicely.
6831 * Localized floats must not be used in calculations!
6833 * @param float $flaot The float to print
6834 * @param int $places The number of decimal places to print.
6835 * @param bool $localized use localized decimal separator
6836 * @return string locale float
6838 function format_float($float, $decimalpoints=1, $localized=true) {
6839 if (is_null($float)) {
6843 return number_format($float, $decimalpoints, get_string('decsep'), '');
6845 return number_format($float, $decimalpoints, '.', '');
6850 * Converts locale specific floating point/comma number back to standard PHP float value
6851 * Do NOT try to do any math operations before this conversion on any user submitted floats!
6853 * @param string $locale_float locale aware float representation
6855 function unformat_float($locale_float) {
6856 $locale_float = trim($locale_float);
6858 if ($locale_float == '') {
6862 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
6864 return (float)str_replace(get_string('decsep'), '.', $locale_float);
6868 * Given a simple array, this shuffles it up just like shuffle()
6869 * Unlike PHP's shuffle() this function works on any machine.
6871 * @param array $array The array to be rearranged
6874 function swapshuffle($array) {
6876 srand ((double) microtime() * 10000000);
6877 $last = count($array) - 1;
6878 for ($i=0;$i<=$last;$i++
) {
6879 $from = rand(0,$last);
6881 $array[$i] = $array[$from];
6882 $array[$from] = $curr;
6888 * Like {@link swapshuffle()}, but works on associative arrays
6890 * @param array $array The associative array to be rearranged
6893 function swapshuffle_assoc($array) {
6895 $newarray = array();
6896 $newkeys = swapshuffle(array_keys($array));
6898 foreach ($newkeys as $newkey) {
6899 $newarray[$newkey] = $array[$newkey];
6905 * Given an arbitrary array, and a number of draws,
6906 * this function returns an array with that amount
6907 * of items. The indexes are retained.
6909 * @param array $array ?
6912 * @todo Finish documenting this function
6914 function draw_rand_array($array, $draws) {
6915 srand ((double) microtime() * 10000000);
6919 $last = count($array);
6921 if ($draws > $last) {
6925 while ($draws > 0) {
6928 $keys = array_keys($array);
6929 $rand = rand(0, $last);
6931 $return[$keys[$rand]] = $array[$keys[$rand]];
6932 unset($array[$keys[$rand]]);
6943 * @param string $a ?
6944 * @param string $b ?
6946 * @todo Finish documenting this function
6948 function microtime_diff($a, $b) {
6949 list($a_dec, $a_sec) = explode(' ', $a);
6950 list($b_dec, $b_sec) = explode(' ', $b);
6951 return $b_sec - $a_sec +
$b_dec - $a_dec;
6955 * Given a list (eg a,b,c,d,e) this function returns
6956 * an array of 1->a, 2->b, 3->c etc
6958 * @param array $list ?
6959 * @param string $separator ?
6960 * @todo Finish documenting this function
6962 function make_menu_from_list($list, $separator=',') {
6964 $array = array_reverse(explode($separator, $list), true);
6965 foreach ($array as $key => $item) {
6966 $outarray[$key+
1] = trim($item);
6972 * Creates an array that represents all the current grades that
6973 * can be chosen using the given grading type. Negative numbers
6974 * are scales, zero is no grade, and positive numbers are maximum
6977 * @param int $gradingtype ?
6979 * @todo Finish documenting this function
6981 function make_grades_menu($gradingtype) {
6983 if ($gradingtype < 0) {
6984 if ($scale = get_record('scale', 'id', - $gradingtype)) {
6985 return make_menu_from_list($scale->scale
);
6987 } else if ($gradingtype > 0) {
6988 for ($i=$gradingtype; $i>=0; $i--) {
6989 $grades[$i] = $i .' / '. $gradingtype;
6997 * This function returns the nummber of activities
6998 * using scaleid in a courseid
7000 * @param int $courseid ?
7001 * @param int $scaleid ?
7003 * @todo Finish documenting this function
7005 function course_scale_used($courseid, $scaleid) {
7011 if (!empty($scaleid)) {
7012 if ($cms = get_course_mods($courseid)) {
7013 foreach ($cms as $cm) {
7014 //Check cm->name/lib.php exists
7015 if (file_exists($CFG->dirroot
.'/mod/'.$cm->modname
.'/lib.php')) {
7016 include_once($CFG->dirroot
.'/mod/'.$cm->modname
.'/lib.php');
7017 $function_name = $cm->modname
.'_scale_used';
7018 if (function_exists($function_name)) {
7019 if ($function_name($cm->instance
,$scaleid)) {
7027 // check if any course grade item makes use of the scale
7028 $return +
= count_records('grade_items', 'courseid', $courseid, 'scaleid', $scaleid);
7030 // check if any outcome in the course makes use of the scale
7031 $return +
= count_records_sql("SELECT COUNT(*)
7032 FROM {$CFG->prefix}grade_outcomes_courses goc,
7033 {$CFG->prefix}grade_outcomes go
7034 WHERE go.id = goc.outcomeid
7035 AND go.scaleid = $scaleid
7036 AND goc.courseid = $courseid");
7042 * This function returns the nummber of activities
7043 * using scaleid in the entire site
7045 * @param int $scaleid ?
7047 * @todo Finish documenting this function. Is return type correct?
7049 function site_scale_used($scaleid,&$courses) {
7055 if (!is_array($courses) ||
count($courses) == 0) {
7056 $courses = get_courses("all",false,"c.id,c.shortname");
7059 if (!empty($scaleid)) {
7060 if (is_array($courses) && count($courses) > 0) {
7061 foreach ($courses as $course) {
7062 $return +
= course_scale_used($course->id
,$scaleid);
7070 * make_unique_id_code
7072 * @param string $extra ?
7074 * @todo Finish documenting this function
7076 function make_unique_id_code($extra='') {
7078 $hostname = 'unknownhost';
7079 if (!empty($_SERVER['HTTP_HOST'])) {
7080 $hostname = $_SERVER['HTTP_HOST'];
7081 } else if (!empty($_ENV['HTTP_HOST'])) {
7082 $hostname = $_ENV['HTTP_HOST'];
7083 } else if (!empty($_SERVER['SERVER_NAME'])) {
7084 $hostname = $_SERVER['SERVER_NAME'];
7085 } else if (!empty($_ENV['SERVER_NAME'])) {
7086 $hostname = $_ENV['SERVER_NAME'];
7089 $date = gmdate("ymdHis");
7091 $random = random_string(6);
7094 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
7096 return $hostname .'+'. $date .'+'. $random;
7102 * Function to check the passed address is within the passed subnet
7104 * The parameter is a comma separated string of subnet definitions.
7105 * Subnet strings can be in one of three formats:
7106 * 1: xxx.xxx.xxx.xxx/xx
7108 * 3: xxx.xxx.xxx.xxx-xxx //a range of IP addresses in the last group.
7109 * Code for type 1 modified from user posted comments by mediator at
7110 * {@link http://au.php.net/manual/en/function.ip2long.php}
7112 * @param string $addr The address you are checking
7113 * @param string $subnetstr The string of subnet addresses
7116 function address_in_subnet($addr, $subnetstr) {
7118 $subnets = explode(',', $subnetstr);
7120 $addr = trim($addr);
7122 foreach ($subnets as $subnet) {
7123 $subnet = trim($subnet);
7124 if (strpos($subnet, '/') !== false) { /// type 1
7125 list($ip, $mask) = explode('/', $subnet);
7126 $mask = 0xffffffff << (32 - $mask);
7127 $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
7128 } else if (strpos($subnet, '-') !== false) {/// type 3
7129 $subnetparts = explode('.', $subnet);
7130 $addrparts = explode('.', $addr);
7131 $subnetrange = explode('-', array_pop($subnetparts));
7132 if (count($subnetrange) == 2) {
7133 $lastaddrpart = array_pop($addrparts);
7134 $found = ($subnetparts == $addrparts &&
7135 $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
7138 $found = (strpos($addr, $subnet) === 0);
7149 * This function sets the $HTTPSPAGEREQUIRED global
7150 * (used in some parts of moodle to change some links)
7151 * and calculate the proper wwwroot to be used
7153 * By using this function properly, we can ensure 100% https-ized pages
7154 * at our entire discretion (login, forgot_password, change_password)
7156 function httpsrequired() {
7158 global $CFG, $HTTPSPAGEREQUIRED;
7160 if (!empty($CFG->loginhttps
)) {
7161 $HTTPSPAGEREQUIRED = true;
7162 $CFG->httpswwwroot
= str_replace('http:', 'https:', $CFG->wwwroot
);
7163 $CFG->httpsthemewww
= str_replace('http:', 'https:', $CFG->themewww
);
7165 // change theme URLs to https
7169 $CFG->httpswwwroot
= $CFG->wwwroot
;
7170 $CFG->httpsthemewww
= $CFG->themewww
;
7175 * For outputting debugging info
7178 * @param string $string ?
7179 * @param string $eol ?
7180 * @todo Finish documenting this function
7182 function mtrace($string, $eol="\n", $sleep=0) {
7184 if (defined('STDOUT')) {
7185 fwrite(STDOUT
, $string.$eol);
7187 echo $string . $eol;
7192 //delay to keep message on user's screen in case of subsequent redirect
7198 //Replace 1 or more slashes or backslashes to 1 slash
7199 function cleardoubleslashes ($path) {
7200 return preg_replace('/(\/|\\\){1,}/','/',$path);
7203 function zip_files ($originalfiles, $destination) {
7204 //Zip an array of files/dirs to a destination zip file
7205 //Both parameters must be FULL paths to the files/dirs
7209 //Extract everything from destination
7210 $path_parts = pathinfo(cleardoubleslashes($destination));
7211 $destpath = $path_parts["dirname"]; //The path of the zip file
7212 $destfilename = $path_parts["basename"]; //The name of the zip file
7213 $extension = $path_parts["extension"]; //The extension of the file
7216 if (empty($destfilename)) {
7220 //If no extension, add it
7221 if (empty($extension)) {
7223 $destfilename = $destfilename.'.'.$extension;
7226 //Check destination path exists
7227 if (!is_dir($destpath)) {
7231 //Check destination path is writable. TODO!!
7233 //Clean destination filename
7234 $destfilename = clean_filename($destfilename);
7236 //Now check and prepare every file
7240 foreach ($originalfiles as $file) { //Iterate over each file
7241 //Check for every file
7242 $tempfile = cleardoubleslashes($file); // no doubleslashes!
7243 //Calculate the base path for all files if it isn't set
7244 if ($origpath === NULL) {
7245 $origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
7247 //See if the file is readable
7248 if (!is_readable($tempfile)) { //Is readable
7251 //See if the file/dir is in the same directory than the rest
7252 if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
7255 //Add the file to the array
7256 $files[] = $tempfile;
7259 //Everything is ready:
7260 // -$origpath is the path where ALL the files to be compressed reside (dir).
7261 // -$destpath is the destination path where the zip file will go (dir).
7262 // -$files is an array of files/dirs to compress (fullpath)
7263 // -$destfilename is the name of the zip file (without path)
7265 //print_object($files); //Debug
7267 if (empty($CFG->zip
)) { // Use built-in php-based zip function
7269 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7270 //rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
7271 $zipfiles = array();
7272 $start = strlen($origpath)+
1;
7273 foreach($files as $file) {
7275 $tf[PCLZIP_ATT_FILE_NAME
] = $file;
7276 $tf[PCLZIP_ATT_FILE_NEW_FULL_NAME
] = substr($file, $start);
7279 //create the archive
7280 $archive = new PclZip(cleardoubleslashes("$destpath/$destfilename"));
7281 if (($list = $archive->create($zipfiles) == 0)) {
7282 notice($archive->errorInfo(true));
7286 } else { // Use external zip program
7289 foreach ($files as $filetozip) {
7290 $filestozip .= escapeshellarg(basename($filetozip));
7293 //Construct the command
7294 $separator = strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' ?
' &' : ' ;';
7295 $command = 'cd '.escapeshellarg($origpath).$separator.
7296 escapeshellarg($CFG->zip
).' -r '.
7297 escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
7298 //All converted to backslashes in WIN
7299 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
7300 $command = str_replace('/','\\',$command);
7307 function unzip_file ($zipfile, $destination = '', $showstatus = true) {
7308 //Unzip one zip file to a destination dir
7309 //Both parameters must be FULL paths
7310 //If destination isn't specified, it will be the
7311 //SAME directory where the zip file resides.
7315 //Extract everything from zipfile
7316 $path_parts = pathinfo(cleardoubleslashes($zipfile));
7317 $zippath = $path_parts["dirname"]; //The path of the zip file
7318 $zipfilename = $path_parts["basename"]; //The name of the zip file
7319 $extension = $path_parts["extension"]; //The extension of the file
7322 if (empty($zipfilename)) {
7326 //If no extension, error
7327 if (empty($extension)) {
7332 $zipfile = cleardoubleslashes($zipfile);
7334 //Check zipfile exists
7335 if (!file_exists($zipfile)) {
7339 //If no destination, passed let's go with the same directory
7340 if (empty($destination)) {
7341 $destination = $zippath;
7344 //Clear $destination
7345 $destpath = rtrim(cleardoubleslashes($destination), "/");
7347 //Check destination path exists
7348 if (!is_dir($destpath)) {
7352 //Check destination path is writable. TODO!!
7354 //Everything is ready:
7355 // -$zippath is the path where the zip file resides (dir)
7356 // -$zipfilename is the name of the zip file (without path)
7357 // -$destpath is the destination path where the zip file will uncompressed (dir)
7361 if (empty($CFG->unzip
)) { // Use built-in php-based unzip function
7363 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7364 $archive = new PclZip(cleardoubleslashes("$zippath/$zipfilename"));
7365 if (!$list = $archive->extract(PCLZIP_OPT_PATH
, $destpath,
7366 PCLZIP_CB_PRE_EXTRACT
, 'unzip_cleanfilename',
7367 PCLZIP_OPT_EXTRACT_DIR_RESTRICTION
, $destpath)) {
7368 if (!empty($showstatus)) {
7369 notice($archive->errorInfo(true));
7374 } else { // Use external unzip program
7376 $separator = strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' ?
' &' : ' ;';
7377 $redirection = strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' ?
'' : ' 2>&1';
7379 $command = 'cd '.escapeshellarg($zippath).$separator.
7380 escapeshellarg($CFG->unzip
).' -o '.
7381 escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
7382 escapeshellarg($destpath).$redirection;
7383 //All converted to backslashes in WIN
7384 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
7385 $command = str_replace('/','\\',$command);
7387 Exec($command,$list);
7390 //Display some info about the unzip execution
7392 unzip_show_status($list,$destpath);
7398 function unzip_cleanfilename ($p_event, &$p_header) {
7399 //This function is used as callback in unzip_file() function
7400 //to clean illegal characters for given platform and to prevent directory traversal.
7401 //Produces the same result as info-zip unzip.
7402 $p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
7403 $p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
7404 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
7405 $p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
7406 $p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
7408 //Add filtering for other systems here
7409 // BSD: none (tested)
7413 $p_header['filename'] = cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
7417 function unzip_show_status ($list,$removepath) {
7418 //This function shows the results of the unzip execution
7419 //depending of the value of the $CFG->zip, results will be
7420 //text or an array of files.
7424 if (empty($CFG->unzip
)) { // Use built-in php-based zip function
7425 $strname = get_string("name");
7426 $strsize = get_string("size");
7427 $strmodified = get_string("modified");
7428 $strstatus = get_string("status");
7429 echo "<table width=\"640\">";
7430 echo "<tr><th class=\"header\" scope=\"col\">$strname</th>";
7431 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strsize</th>";
7432 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strmodified</th>";
7433 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strstatus</th></tr>";
7434 foreach ($list as $item) {
7436 $item['filename'] = str_replace(cleardoubleslashes($removepath).'/', "", $item['filename']);
7437 print_cell("left", s($item['filename']));
7438 if (! $item['folder']) {
7439 print_cell("right", display_size($item['size']));
7441 echo "<td> </td>";
7443 $filedate = userdate($item['mtime'], get_string("strftimedatetime"));
7444 print_cell("right", $filedate);
7445 print_cell("right", $item['status']);
7450 } else { // Use external zip program
7451 print_simple_box_start("center");
7453 foreach ($list as $item) {
7454 echo s(str_replace(cleardoubleslashes($removepath.'/'), '', $item)).'<br />';
7457 print_simple_box_end();
7462 * Returns most reliable client address
7464 * @return string The remote IP address
7466 function getremoteaddr() {
7467 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
7468 return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
7470 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
7471 return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
7473 if (!empty($_SERVER['REMOTE_ADDR'])) {
7474 return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
7480 * Cleans a remote address ready to put into the log table
7482 function cleanremoteaddr($addr) {
7483 $originaladdr = $addr;
7485 // first get all things that look like IP addresses.
7486 if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER
)) {
7489 $goodmatches = array();
7490 $lanmatches = array();
7491 foreach ($matches as $match) {
7493 // check to make sure it's not an internal address.
7494 // the following are reserved for private lans...
7495 // 10.0.0.0 - 10.255.255.255
7496 // 172.16.0.0 - 172.31.255.255
7497 // 192.168.0.0 - 192.168.255.255
7498 // 169.254.0.0 -169.254.255.255
7499 $bits = explode('.',$match[0]);
7500 if (count($bits) != 4) {
7501 // weird, preg match shouldn't give us it.
7504 if (($bits[0] == 10)
7505 ||
($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
7506 ||
($bits[0] == 192 && $bits[1] == 168)
7507 ||
($bits[0] == 169 && $bits[1] == 254)) {
7508 $lanmatches[] = $match[0];
7512 $goodmatches[] = $match[0];
7514 if (!count($goodmatches)) {
7515 // perhaps we have a lan match, it's probably better to return that.
7516 if (!count($lanmatches)) {
7519 return array_pop($lanmatches);
7522 if (count($goodmatches) == 1) {
7523 return $goodmatches[0];
7525 //Commented out following because there are so many, and it clogs the logs MDL-13544
7526 //error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
7528 // We need to return something, so return the first
7529 return array_pop($goodmatches);
7533 * file_put_contents is only supported by php 5.0 and higher
7534 * so if it is not predefined, define it here
7536 * @param $file full path of the file to write
7537 * @param $contents contents to be sent
7538 * @return number of bytes written (false on error)
7540 if(!function_exists('file_put_contents')) {
7541 function file_put_contents($file, $contents) {
7543 if ($f = fopen($file, 'w')) {
7544 $result = fwrite($f, $contents);
7552 * The clone keyword is only supported from PHP 5 onwards.
7553 * The behaviour of $obj2 = $obj1 differs fundamentally
7554 * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
7555 * created, in PHP 5 $obj1 is referenced. To create a copy
7556 * in PHP 5 the clone keyword was introduced. This function
7557 * simulates this behaviour for PHP < 5.0.0.
7558 * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
7560 * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
7561 * Found a better implementation (more checks and possibilities) from PEAR:
7562 * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
7564 * @param object $obj
7567 if(!check_php_version('5.0.0')) {
7568 // the eval is needed to prevent PHP 5 from getting a parse error!
7570 function clone($obj) {
7572 if (!is_object($obj)) {
7573 user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
7577 /// Use serialize/unserialize trick to deep copy the object
7578 $obj = unserialize(serialize($obj));
7580 /// If there is a __clone method call it on the "new" class
7581 if (method_exists($obj, \'__clone\')) {
7588 // Supply the PHP5 function scandir() to older versions.
7589 function scandir($directory) {
7591 if ($dh = opendir($directory)) {
7592 while (($file = readdir($dh)) !== false) {
7600 // Supply the PHP5 function array_combine() to older versions.
7601 function array_combine($keys, $values) {
7602 if (!is_array($keys) || !is_array($values) || count($keys) != count($values)) {
7607 foreach ($keys as $key) {
7608 $result[$key] = current($values);
7617 * This function will make a complete copy of anything it's given,
7618 * regardless of whether it's an object or not.
7619 * @param mixed $thing
7622 function fullclone($thing) {
7623 return unserialize(serialize($thing));
7628 * This function expects to called during shutdown
7629 * should be set via register_shutdown_function()
7630 * in lib/setup.php .
7632 * Right now we do it only if we are under apache, to
7633 * make sure apache children that hog too much mem are
7637 function moodle_request_shutdown() {
7641 // initially, we are only ever called under apache
7642 // but check just in case
7643 if (function_exists('apache_child_terminate')
7644 && function_exists('memory_get_usage')
7645 && ini_get_bool('child_terminate')) {
7646 if (empty($CFG->apachemaxmem
)) {
7647 $CFG->apachemaxmem
= 25000000; // default 25MiB
7649 if (memory_get_usage() > (int)$CFG->apachemaxmem
) {
7650 trigger_error('Mem usage over $CFG->apachemaxmem: marking child for reaping.');
7651 @apache_child_terminate
();
7654 if (defined('MDL_PERF') ||
(!empty($CFG->perfdebug
) and $CFG->perfdebug
> 7)) {
7655 if (defined('MDL_PERFTOLOG')) {
7656 $perf = get_performance_info();
7657 error_log("PERF: " . $perf['txt']);
7659 if (defined('MDL_PERFINC')) {
7660 $inc = get_included_files();
7662 foreach($inc as $f) {
7663 if (preg_match(':^/:', $f)) {
7666 $hfs = display_size($fs);
7667 error_log(substr($f,strlen($CFG->dirroot
)) . " size: $fs ($hfs)"
7670 error_log($f , NULL, NULL, 0);
7674 $hts = display_size($ts);
7675 error_log("Total size of files included: $ts ($hts)");
7682 * If new messages are waiting for the current user, then return
7683 * Javascript code to create a popup window
7685 * @return string Javascript code
7687 function message_popup_window() {
7690 $popuplimit = 30; // Minimum seconds between popups
7692 if (!defined('MESSAGE_WINDOW')) {
7693 if (isset($USER->id
) and !isguestuser()) {
7694 if (!isset($USER->message_lastpopup
)) {
7695 $USER->message_lastpopup
= 0;
7697 if ((time() - $USER->message_lastpopup
) > $popuplimit) { /// It's been long enough
7698 if (get_user_preferences('message_showmessagewindow', 1) == 1) {
7699 if (count_records_select('message', 'useridto = \''.$USER->id
.'\' AND timecreated > \''.$USER->message_lastpopup
.'\'')) {
7700 $USER->message_lastpopup
= time();
7701 return '<script type="text/javascript">'."\n//<![CDATA[\n openpopup('/message/index.php', 'message',
7702 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0);\n//]]>\n</script>";
7712 // Used to make sure that $min <= $value <= $max
7713 function bounded_number($min, $value, $max) {
7723 function array_is_nested($array) {
7724 foreach ($array as $value) {
7725 if (is_array($value)) {
7733 *** get_performance_info() pairs up with init_performance_info()
7734 *** loaded in setup.php. Returns an array with 'html' and 'txt'
7735 *** values ready for use, and each of the individual stats provided
7736 *** separately as well.
7739 function get_performance_info() {
7740 global $CFG, $PERF, $rcache;
7743 $info['html'] = ''; // holds userfriendly HTML representation
7744 $info['txt'] = me() . ' '; // holds log-friendly representation
7746 $info['realtime'] = microtime_diff($PERF->starttime
, microtime());
7748 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
7749 $info['txt'] .= 'time: '.$info['realtime'].'s ';
7751 if (function_exists('memory_get_usage')) {
7752 $info['memory_total'] = memory_get_usage();
7753 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory
;
7754 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
7755 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
7758 if (function_exists('memory_get_peak_usage')) {
7759 $info['memory_peak'] = memory_get_peak_usage();
7760 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
7761 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
7764 $inc = get_included_files();
7765 //error_log(print_r($inc,1));
7766 $info['includecount'] = count($inc);
7767 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
7768 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
7770 if (!empty($PERF->dbqueries
)) {
7771 $info['dbqueries'] = $PERF->dbqueries
;
7772 $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
7773 $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
7776 if (!empty($PERF->logwrites
)) {
7777 $info['logwrites'] = $PERF->logwrites
;
7778 $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
7779 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
7782 if (!empty($PERF->profiling
) && $PERF->profiling
) {
7783 require_once($CFG->dirroot
.'/lib/profilerlib.php');
7784 $info['html'] .= '<span class="profilinginfo">'.Profiler
::get_profiling(array('-R')).'</span>';
7787 if (function_exists('posix_times')) {
7788 $ptimes = posix_times();
7789 if (is_array($ptimes)) {
7790 foreach ($ptimes as $key => $val) {
7791 $info[$key] = $ptimes[$key] - $PERF->startposixtimes
[$key];
7793 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
7794 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
7798 // Grab the load average for the last minute
7799 // /proc will only work under some linux configurations
7800 // while uptime is there under MacOSX/Darwin and other unices
7801 if (is_readable('/proc/loadavg') && $loadavg = @file
('/proc/loadavg')) {
7802 list($server_load) = explode(' ', $loadavg[0]);
7804 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `
/usr
/bin
/uptime`
) {
7805 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
7806 $server_load = $matches[1];
7808 trigger_error('Could not parse uptime output!');
7811 if (!empty($server_load)) {
7812 $info['serverload'] = $server_load;
7813 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
7814 $info['txt'] .= "serverload: {$info['serverload']} ";
7817 if (isset($rcache->hits
) && isset($rcache->misses
)) {
7818 $info['rcachehits'] = $rcache->hits
;
7819 $info['rcachemisses'] = $rcache->misses
;
7820 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
7821 "{$rcache->hits}/{$rcache->misses}</span> ";
7822 $info['txt'] .= 'rcache: '.
7823 "{$rcache->hits}/{$rcache->misses} ";
7825 $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
7829 function apd_get_profiling() {
7830 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
7834 * Delete directory or only it's content
7835 * @param string $dir directory path
7836 * @param bool $content_only
7837 * @return bool success, true also if dir does not exist
7839 function remove_dir($dir, $content_only=false) {
7840 if (!file_exists($dir)) {
7844 $handle = opendir($dir);
7846 while (false!==($item = readdir($handle))) {
7847 if($item != '.' && $item != '..') {
7848 if(is_dir($dir.'/'.$item)) {
7849 $result = remove_dir($dir.'/'.$item) && $result;
7851 $result = unlink($dir.'/'.$item) && $result;
7856 if ($content_only) {
7859 return rmdir($dir); // if anything left the result will be false, noo need for && $result
7863 * Function to check if a directory exists and optionally create it.
7865 * @param string absolute directory path (must be under $CFG->dataroot)
7866 * @param boolean create directory if does not exist
7867 * @param boolean create directory recursively
7869 * @return boolean true if directory exists or created
7871 function check_dir_exists($dir, $create=false, $recursive=false) {
7875 if (strstr($dir, $CFG->dataroot
.'/') === false) {
7876 debugging('Warning. Wrong call to check_dir_exists(). $dir must be an absolute path under $CFG->dataroot ("' . $dir . '" is incorrect)', DEBUG_DEVELOPER
);
7887 /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-(
7888 $dir = str_replace('\\', '/', $dir); //windows compatibility
7889 /// We are going to make it recursive under $CFG->dataroot only
7890 /// (will help sites running open_basedir security and others)
7891 $dir = str_replace($CFG->dataroot
. '/', '', $dir);
7892 $dirs = explode('/', $dir); /// Extract path parts
7893 /// Iterate over each part with start point $CFG->dataroot
7894 $dir = $CFG->dataroot
. '/';
7895 foreach ($dirs as $part) {
7900 if (!is_dir($dir)) {
7901 if (!mkdir($dir, $CFG->directorypermissions
)) {
7908 $status = mkdir($dir, $CFG->directorypermissions
);
7915 function report_session_error() {
7916 global $CFG, $FULLME;
7918 if (empty($CFG->lang
)) {
7921 // Set up default theme and locale
7925 //clear session cookies
7926 if (check_php_version('5.2.0')) {
7928 setcookie('MoodleSession'.$CFG->sessioncookie
, '', time() - 3600, $CFG->sessioncookiepath
, '', $CFG->cookiesecure
, $CFG->cookiehttponly
);
7929 setcookie('MoodleSessionTest'.$CFG->sessioncookie
, '', time() - 3600, $CFG->sessioncookiepath
, '', $CFG->cookiesecure
, $CFG->cookiehttponly
);
7931 setcookie('MoodleSession'.$CFG->sessioncookie
, '', time() - 3600, $CFG->sessioncookiepath
, '', $CFG->cookiesecure
);
7932 setcookie('MoodleSessionTest'.$CFG->sessioncookie
, '', time() - 3600, $CFG->sessioncookiepath
, '', $CFG->cookiesecure
);
7934 //increment database error counters
7935 if (isset($CFG->session_error_counter
)) {
7936 set_config('session_error_counter', 1 +
$CFG->session_error_counter
);
7938 set_config('session_error_counter', 1);
7940 redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
7945 * Detect if an object or a class contains a given property
7946 * will take an actual object or the name of a class
7947 * @param mix $obj Name of class or real object to test
7948 * @param string $property name of property to find
7949 * @return bool true if property exists
7951 function object_property_exists( $obj, $property ) {
7952 if (is_string( $obj )) {
7953 $properties = get_class_vars( $obj );
7956 $properties = get_object_vars( $obj );
7958 return array_key_exists( $property, $properties );
7963 * Detect a custom script replacement in the data directory that will
7964 * replace an existing moodle script
7965 * @param string $urlpath path to the original script
7966 * @return string full path name if a custom script exists
7967 * @return bool false if no custom script exists
7969 function custom_script_path($urlpath='') {
7972 // set default $urlpath, if necessary
7973 if (empty($urlpath)) {
7974 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
7977 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
7978 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot
) === false )) {
7982 // replace wwwroot with the path to the customscripts folder and clean path
7983 $scriptpath = $CFG->customscripts
. clean_param(substr($urlpath, strlen($CFG->wwwroot
)), PARAM_PATH
);
7985 // remove the query string, if any
7986 if (($strpos = strpos($scriptpath, '?')) !== false) {
7987 $scriptpath = substr($scriptpath, 0, $strpos);
7990 // remove trailing slashes, if any
7991 $scriptpath = rtrim($scriptpath, '/\\');
7993 // append index.php, if necessary
7994 if (is_dir($scriptpath)) {
7995 $scriptpath .= '/index.php';
7998 // check the custom script exists
7999 if (file_exists($scriptpath)) {
8007 * Wrapper function to load necessary editor scripts
8008 * to $CFG->editorsrc array. Params can be coursei id
8009 * or associative array('courseid' => value, 'name' => 'editorname').
8011 * @param mixed $args Courseid or associative array.
8013 function loadeditor($args) {
8015 include($CFG->libdir
.'/editorlib.php');
8016 return editorObject
::loadeditor($args);
8020 * Returns whether or not the user object is a remote MNET user. This function
8021 * is in moodlelib because it does not rely on loading any of the MNET code.
8023 * @param object $user A valid user object
8024 * @return bool True if the user is from a remote Moodle.
8026 function is_mnet_remote_user($user) {
8029 if (!isset($CFG->mnet_localhost_id
)) {
8030 include_once $CFG->dirroot
. '/mnet/lib.php';
8031 $env = new mnet_environment();
8036 return (!empty($user->mnethostid
) && $user->mnethostid
!= $CFG->mnet_localhost_id
);
8040 * Checks if a given plugin is in the list of enabled enrolment plugins.
8042 * @param string $auth Enrolment plugin.
8043 * @return boolean Whether the plugin is enabled.
8045 function is_enabled_enrol($enrol='') {
8048 // use the global default if not specified
8050 $enrol = $CFG->enrol
;
8052 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled
));
8056 * This function will search for browser prefereed languages, setting Moodle
8057 * to use the best one available if $SESSION->lang is undefined
8059 function setup_lang_from_browser() {
8061 global $CFG, $SESSION, $USER;
8063 if (!empty($SESSION->lang
) or !empty($USER->lang
) or empty($CFG->autolang
)) {
8064 // Lang is defined in session or user profile, nothing to do
8068 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
8072 /// Extract and clean langs from headers
8073 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
8074 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
8075 $rawlangs = explode(',', $rawlangs); // Convert to array
8079 foreach ($rawlangs as $lang) {
8080 if (strpos($lang, ';') === false) {
8081 $langs[(string)$order] = $lang;
8082 $order = $order-0.01;
8084 $parts = explode(';', $lang);
8085 $pos = strpos($parts[1], '=');
8086 $langs[substr($parts[1], $pos+
1)] = $parts[0];
8089 krsort($langs, SORT_NUMERIC
);
8091 $langlist = get_list_of_languages();
8093 /// Look for such langs under standard locations
8094 foreach ($langs as $lang) {
8095 $lang = strtolower(clean_param($lang.'_utf8', PARAM_SAFEDIR
)); // clean it properly for include
8096 if (!array_key_exists($lang, $langlist)) {
8097 continue; // language not allowed, try next one
8099 if (file_exists($CFG->dataroot
.'/lang/'. $lang) or file_exists($CFG->dirroot
.'/lang/'. $lang)) {
8100 $SESSION->lang
= $lang; /// Lang exists, set it in session
8101 break; /// We have finished. Go out
8108 ////////////////////////////////////////////////////////////////////////////////
8110 function is_newnav($navigation) {
8111 if (is_array($navigation) && !empty($navigation['newnav'])) {
8119 * Checks whether the given variable name is defined as a variable within the given object.
8120 * @note This will NOT work with stdClass objects, which have no class variables.
8121 * @param string $var The variable name
8122 * @param object $object The object to check
8125 function in_object_vars($var, $object) {
8126 $class_vars = get_class_vars(get_class($object));
8127 $class_vars = array_keys($class_vars);
8128 return in_array($var, $class_vars);
8132 * Returns an array without repeated objects.
8133 * This function is similar to array_unique, but for arrays that have objects as values
8135 * @param unknown_type $array
8136 * @param unknown_type $keep_key_assoc
8139 function object_array_unique($array, $keep_key_assoc = true) {
8140 $duplicate_keys = array();
8143 foreach ($array as $key=>$val) {
8144 // convert objects to arrays, in_array() does not support objects
8145 if (is_object($val)) {
8149 if (!in_array($val, $tmp)) {
8152 $duplicate_keys[] = $key;
8156 foreach ($duplicate_keys as $key) {
8157 unset($array[$key]);
8160 return $keep_key_assoc ?
$array : array_values($array);
8164 * Returns the language string for the given plugin.
8166 * @param string $plugin the plugin code name
8167 * @param string $type the type of plugin (mod, block, filter)
8168 * @return string The plugin language string
8170 function get_plugin_name($plugin, $type='mod') {
8175 $plugin_name = get_string('modulename', $plugin);
8178 $plugin_name = get_string('blockname', "block_$plugin");
8179 if (empty($plugin_name) ||
$plugin_name == '[[blockname]]') {
8180 if (($block = block_instance($plugin)) !== false) {
8181 $plugin_name = $block->get_title();
8183 $plugin_name = "[[$plugin]]";
8188 $plugin_name = trim(get_string('filtername', $plugin));
8189 if (empty($plugin_name) or ($plugin_name == '[[filtername]]')) {
8190 $textlib = textlib_get_instance();
8191 $plugin_name = $textlib->strtotitle($plugin);
8195 $plugin_name = $plugin;
8199 return $plugin_name;
8203 * Is a userid the primary administrator?
8205 * @param $userid int id of user to check
8208 function is_primary_admin($userid){
8209 $primaryadmin = get_admin();
8211 if($userid == $primaryadmin->id
){
8218 // vim:autoindent:expandtab:shiftwidth=4:tabstop=4:tw=140: