3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * moodlelib.php - Moodle main library
21 * Main library file of miscellaneous general-purpose Moodle functions.
22 * Other main libraries:
23 * - weblib.php - functions that produce web output
24 * - datalib.php - functions that access the database
28 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32 defined('MOODLE_INTERNAL') ||
die();
34 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
36 /// Date and time constants ///
38 * Time constant - the number of seconds in a year
40 define('YEARSECS', 31536000);
43 * Time constant - the number of seconds in a week
45 define('WEEKSECS', 604800);
48 * Time constant - the number of seconds in a day
50 define('DAYSECS', 86400);
53 * Time constant - the number of seconds in an hour
55 define('HOURSECS', 3600);
58 * Time constant - the number of seconds in a minute
60 define('MINSECS', 60);
63 * Time constant - the number of minutes in a day
65 define('DAYMINS', 1440);
68 * Time constant - the number of minutes in an hour
70 define('HOURMINS', 60);
72 /// Parameter constants - every call to optional_param(), required_param() ///
73 /// or clean_param() should have a specified type of parameter. //////////////
78 * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
80 define('PARAM_ALPHA', 'alpha');
83 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "_-" allowed
84 * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
86 define('PARAM_ALPHAEXT', 'alphaext');
89 * PARAM_ALPHANUM - expected numbers and letters only.
91 define('PARAM_ALPHANUM', 'alphanum');
94 * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
96 define('PARAM_ALPHANUMEXT', 'alphanumext');
99 * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
101 define('PARAM_AUTH', 'auth');
104 * PARAM_BASE64 - Base 64 encoded format
106 define('PARAM_BASE64', 'base64');
109 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
111 define('PARAM_BOOL', 'bool');
114 * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
115 * checked against the list of capabilities in the database.
117 define('PARAM_CAPABILITY', 'capability');
120 * PARAM_CLEANHTML - cleans submitted HTML code. use only for text in HTML format. This cleaning may fix xhtml strictness too.
122 define('PARAM_CLEANHTML', 'cleanhtml');
125 * PARAM_EMAIL - an email address following the RFC
127 define('PARAM_EMAIL', 'email');
130 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
132 define('PARAM_FILE', 'file');
135 * PARAM_FLOAT - a real/floating point number.
137 define('PARAM_FLOAT', 'float');
140 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
142 define('PARAM_HOST', 'host');
145 * PARAM_INT - integers only, use when expecting only numbers.
147 define('PARAM_INT', 'int');
150 * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
152 define('PARAM_LANG', 'lang');
155 * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the others! Implies PARAM_URL!)
157 define('PARAM_LOCALURL', 'localurl');
160 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
162 define('PARAM_NOTAGS', 'notags');
165 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
166 * note: the leading slash is not removed, window drive letter is not allowed
168 define('PARAM_PATH', 'path');
171 * PARAM_PEM - Privacy Enhanced Mail format
173 define('PARAM_PEM', 'pem');
176 * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
178 define('PARAM_PERMISSION', 'permission');
181 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way
183 define('PARAM_RAW', 'raw');
186 * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
188 define('PARAM_RAW_TRIMMED', 'raw_trimmed');
191 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
193 define('PARAM_SAFEDIR', 'safedir');
196 * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
198 define('PARAM_SAFEPATH', 'safepath');
201 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
203 define('PARAM_SEQUENCE', 'sequence');
206 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
208 define('PARAM_TAG', 'tag');
211 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
213 define('PARAM_TAGLIST', 'taglist');
216 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
218 define('PARAM_TEXT', 'text');
221 * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
223 define('PARAM_THEME', 'theme');
226 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
228 define('PARAM_URL', 'url');
231 * PARAM_USERNAME - Clean username to only contains allowed characters. This is to be used ONLY when manually creating user accounts, do NOT use when syncing with external systems!!
233 define('PARAM_USERNAME', 'username');
236 * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
238 define('PARAM_STRINGID', 'stringid');
240 ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
242 * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
243 * It was one of the first types, that is why it is abused so much ;-)
244 * @deprecated since 2.0
246 define('PARAM_CLEAN', 'clean');
249 * PARAM_INTEGER - deprecated alias for PARAM_INT
251 define('PARAM_INTEGER', 'int');
254 * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
256 define('PARAM_NUMBER', 'float');
259 * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
260 * NOTE: originally alias for PARAM_APLHA
262 define('PARAM_ACTION', 'alphanumext');
265 * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
266 * NOTE: originally alias for PARAM_APLHA
268 define('PARAM_FORMAT', 'alphanumext');
271 * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
273 define('PARAM_MULTILANG', 'text');
276 * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
277 * string seperated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
278 * America/Port-au-Prince)
280 define('PARAM_TIMEZONE', 'timezone');
283 * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
285 define('PARAM_CLEANFILE', 'file');
290 * VALUE_REQUIRED - if the parameter is not supplied, there is an error
292 define('VALUE_REQUIRED', 1);
295 * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
297 define('VALUE_OPTIONAL', 2);
300 * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
302 define('VALUE_DEFAULT', 0);
305 * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
307 define('NULL_NOT_ALLOWED', false);
310 * NULL_ALLOWED - the parameter can be set to null in the database
312 define('NULL_ALLOWED', true);
316 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
318 define('PAGE_COURSE_VIEW', 'course-view');
320 /** Get remote addr constant */
321 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
322 /** Get remote addr constant */
323 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
325 /// Blog access level constant declaration ///
326 define ('BLOG_USER_LEVEL', 1);
327 define ('BLOG_GROUP_LEVEL', 2);
328 define ('BLOG_COURSE_LEVEL', 3);
329 define ('BLOG_SITE_LEVEL', 4);
330 define ('BLOG_GLOBAL_LEVEL', 5);
335 * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
336 * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
337 * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
339 * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
341 define('TAG_MAX_LENGTH', 50);
343 /// Password policy constants ///
344 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
345 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
346 define ('PASSWORD_DIGITS', '0123456789');
347 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
349 /// Feature constants ///
350 // Used for plugin_supports() to report features that are, or are not, supported by a module.
352 /** True if module can provide a grade */
353 define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
354 /** True if module supports outcomes */
355 define('FEATURE_GRADE_OUTCOMES', 'outcomes');
357 /** True if module has code to track whether somebody viewed it */
358 define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
359 /** True if module has custom completion rules */
360 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
362 /** True if module has no 'view' page (like label) */
363 define('FEATURE_NO_VIEW_LINK', 'viewlink');
364 /** True if module supports outcomes */
365 define('FEATURE_IDNUMBER', 'idnumber');
366 /** True if module supports groups */
367 define('FEATURE_GROUPS', 'groups');
368 /** True if module supports groupings */
369 define('FEATURE_GROUPINGS', 'groupings');
370 /** True if module supports groupmembersonly */
371 define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
373 /** Type of module */
374 define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
375 /** True if module supports intro editor */
376 define('FEATURE_MOD_INTRO', 'mod_intro');
377 /** True if module has default completion */
378 define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
380 define('FEATURE_COMMENT', 'comment');
382 define('FEATURE_RATE', 'rate');
383 /** True if module supports backup/restore of moodle2 format */
384 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
386 /** Unspecified module archetype */
387 define('MOD_ARCHETYPE_OTHER', 0);
388 /** Resource-like type module */
389 define('MOD_ARCHETYPE_RESOURCE', 1);
390 /** Assignment module archetype */
391 define('MOD_ARCHETYPE_ASSIGNMENT', 2);
394 * Security token used for allowing access
395 * from external application such as web services.
396 * Scripts do not use any session, performance is relatively
397 * low because we need to load access info in each request.
398 * Scripts are executed in parallel.
400 define('EXTERNAL_TOKEN_PERMANENT', 0);
403 * Security token used for allowing access
404 * of embedded applications, the code is executed in the
405 * active user session. Token is invalidated after user logs out.
406 * Scripts are executed serially - normal session locking is used.
408 define('EXTERNAL_TOKEN_EMBEDDED', 1);
411 * The home page should be the site home
413 define('HOMEPAGE_SITE', 0);
415 * The home page should be the users my page
417 define('HOMEPAGE_MY', 1);
419 * The home page can be chosen by the user
421 define('HOMEPAGE_USER', 2);
424 * Hub directory url (should be moodle.org)
426 define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
430 * Moodle.org url (should be moodle.org)
432 define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
435 * Moodle mobile app service name
437 define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
439 /// PARAMETER HANDLING ////////////////////////////////////////////////////
442 * Returns a particular value for the named variable, taken from
443 * POST or GET. If the parameter doesn't exist then an error is
444 * thrown because we require this variable.
446 * This function should be used to initialise all required values
447 * in a script that are based on parameters. Usually it will be
449 * $id = required_param('id', PARAM_INT);
451 * Please note the $type parameter is now required,
452 * for now PARAM_CLEAN is used for backwards compatibility only.
454 * @param string $parname the name of the page parameter we want
455 * @param string $type expected type of parameter
458 function required_param($parname, $type) {
460 debugging('required_param() requires $type to be specified.');
461 $type = PARAM_CLEAN
; // for now let's use this deprecated type
463 if (isset($_POST[$parname])) { // POST has precedence
464 $param = $_POST[$parname];
465 } else if (isset($_GET[$parname])) {
466 $param = $_GET[$parname];
468 print_error('missingparam', '', '', $parname);
471 return clean_param($param, $type);
475 * Returns a particular value for the named variable, taken from
476 * POST or GET, otherwise returning a given default.
478 * This function should be used to initialise all optional values
479 * in a script that are based on parameters. Usually it will be
481 * $name = optional_param('name', 'Fred', PARAM_TEXT);
483 * Please note $default and $type parameters are now required,
484 * for now PARAM_CLEAN is used for backwards compatibility only.
486 * @param string $parname the name of the page parameter we want
487 * @param mixed $default the default value to return if nothing is found
488 * @param string $type expected type of parameter
491 function optional_param($parname, $default, $type) {
493 debugging('optional_param() requires $default and $type to be specified.');
494 $type = PARAM_CLEAN
; // for now let's use this deprecated type
496 if (!isset($default)) {
500 if (isset($_POST[$parname])) { // POST has precedence
501 $param = $_POST[$parname];
502 } else if (isset($_GET[$parname])) {
503 $param = $_GET[$parname];
508 return clean_param($param, $type);
512 * Strict validation of parameter values, the values are only converted
513 * to requested PHP type. Internally it is using clean_param, the values
514 * before and after cleaning must be equal - otherwise
515 * an invalid_parameter_exception is thrown.
516 * Objects and classes are not accepted.
518 * @param mixed $param
519 * @param int $type PARAM_ constant
520 * @param bool $allownull are nulls valid value?
521 * @param string $debuginfo optional debug information
522 * @return mixed the $param value converted to PHP type or invalid_parameter_exception
524 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED
, $debuginfo='') {
525 if (is_null($param)) {
526 if ($allownull == NULL_ALLOWED
) {
529 throw new invalid_parameter_exception($debuginfo);
532 if (is_array($param) or is_object($param)) {
533 throw new invalid_parameter_exception($debuginfo);
536 $cleaned = clean_param($param, $type);
537 if ((string)$param !== (string)$cleaned) {
538 // conversion to string is usually lossless
539 throw new invalid_parameter_exception($debuginfo);
546 * Used by {@link optional_param()} and {@link required_param()} to
547 * clean the variables and/or cast to specific types, based on
550 * $course->format = clean_param($course->format, PARAM_ALPHA);
551 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
554 * @param mixed $param the variable we are cleaning
555 * @param string $type expected format of param after cleaning.
558 function clean_param($param, $type) {
562 if (is_array($param)) { // Let's loop
564 foreach ($param as $key => $value) {
565 $newparam[$key] = clean_param($value, $type);
571 case PARAM_RAW
: // no cleaning at all
574 case PARAM_RAW_TRIMMED
: // no cleaning, but strip leading and trailing whitespace.
577 case PARAM_CLEAN
: // General HTML cleaning, try to use more specific type if possible
578 // this is deprecated!, please use more specific type instead
579 if (is_numeric($param)) {
582 return clean_text($param); // Sweep for scripts, etc
584 case PARAM_CLEANHTML
: // clean html fragment
585 $param = clean_text($param, FORMAT_HTML
); // Sweep for scripts, etc
589 return (int)$param; // Convert to integer
593 return (float)$param; // Convert to float
595 case PARAM_ALPHA
: // Remove everything not a-z
596 return preg_replace('/[^a-zA-Z]/i', '', $param);
598 case PARAM_ALPHAEXT
: // Remove everything not a-zA-Z_- (originally allowed "/" too)
599 return preg_replace('/[^a-zA-Z_-]/i', '', $param);
601 case PARAM_ALPHANUM
: // Remove everything not a-zA-Z0-9
602 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
604 case PARAM_ALPHANUMEXT
: // Remove everything not a-zA-Z0-9_-
605 return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
607 case PARAM_SEQUENCE
: // Remove everything not 0-9,
608 return preg_replace('/[^0-9,]/i', '', $param);
610 case PARAM_BOOL
: // Convert to 1 or 0
611 $tempstr = strtolower($param);
612 if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
614 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
617 $param = empty($param) ?
0 : 1;
621 case PARAM_NOTAGS
: // Strip all tags
622 return strip_tags($param);
624 case PARAM_TEXT
: // leave only tags needed for multilang
625 // if the multilang syntax is not correct we strip all tags
626 // because it would break xhtml strict which is required for accessibility standards
627 // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
629 if (strpos($param, '</lang>') !== false) {
630 // old and future mutilang syntax
631 $param = strip_tags($param, '<lang>');
632 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
636 foreach ($matches[0] as $match) {
637 if ($match === '</lang>') {
645 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
656 } else if (strpos($param, '</span>') !== false) {
657 // current problematic multilang syntax
658 $param = strip_tags($param, '<span>');
659 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
663 foreach ($matches[0] as $match) {
664 if ($match === '</span>') {
672 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
684 // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
685 return strip_tags($param);
687 case PARAM_SAFEDIR
: // Remove everything not a-zA-Z0-9_-
688 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
690 case PARAM_SAFEPATH
: // Remove everything not a-zA-Z0-9/_-
691 return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
693 case PARAM_FILE
: // Strip all suspicious characters from filename
694 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
695 $param = preg_replace('~\.\.+~', '', $param);
696 if ($param === '.') {
701 case PARAM_PATH
: // Strip all suspicious characters from file path
702 $param = str_replace('\\', '/', $param);
703 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
704 $param = preg_replace('~\.\.+~', '', $param);
705 $param = preg_replace('~//+~', '/', $param);
706 return preg_replace('~/(\./)+~', '/', $param);
708 case PARAM_HOST
: // allow FQDN or IPv4 dotted quad
709 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
710 // match ipv4 dotted quad
711 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
712 // confirm values are ok
716 ||
$match[4] > 255 ) {
717 // hmmm, what kind of dotted quad is this?
720 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
721 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
722 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
724 // all is ok - $param is respected
731 case PARAM_URL
: // allow safe ftp, http, mailto urls
732 include_once($CFG->dirroot
. '/lib/validateurlsyntax.php');
733 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
734 // all is ok, param is respected
736 $param =''; // not really ok
740 case PARAM_LOCALURL
: // allow http absolute, root relative and relative URLs within wwwroot
741 $param = clean_param($param, PARAM_URL
);
742 if (!empty($param)) {
743 if (preg_match(':^/:', $param)) {
744 // root-relative, ok!
745 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot
, '/').'/i',$param)) {
746 // absolute, and matches our wwwroot
748 // relative - let's make sure there are no tricks
749 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
759 $param = trim($param);
760 // PEM formatted strings may contain letters/numbers and the symbols
764 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
765 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
766 list($wholething, $body) = $matches;
767 unset($wholething, $matches);
768 $b64 = clean_param($body, PARAM_BASE64
);
770 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
778 if (!empty($param)) {
779 // PEM formatted strings may contain letters/numbers and the symbols
783 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
786 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY
);
787 // Each line of base64 encoded data must be 64 characters in
788 // length, except for the last line which may be less than (or
789 // equal to) 64 characters long.
790 for ($i=0, $j=count($lines); $i < $j; $i++
) {
792 if (64 < strlen($lines[$i])) {
798 if (64 != strlen($lines[$i])) {
802 return implode("\n",$lines);
808 // Please note it is not safe to use the tag name directly anywhere,
809 // it must be processed with s(), urlencode() before embedding anywhere.
810 // remove some nasties
811 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
812 //convert many whitespace chars into one
813 $param = preg_replace('/\s+/', ' ', $param);
814 $textlib = textlib_get_instance();
815 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH
);
819 $tags = explode(',', $param);
821 foreach ($tags as $tag) {
822 $res = clean_param($tag, PARAM_TAG
);
828 return implode(',', $result);
833 case PARAM_CAPABILITY
:
834 if (get_capability_info($param)) {
840 case PARAM_PERMISSION
:
841 $param = (int)$param;
842 if (in_array($param, array(CAP_INHERIT
, CAP_ALLOW
, CAP_PREVENT
, CAP_PROHIBIT
))) {
849 $param = clean_param($param, PARAM_SAFEDIR
);
850 if (exists_auth_plugin($param)) {
857 $param = clean_param($param, PARAM_SAFEDIR
);
858 if (get_string_manager()->translation_exists($param)) {
861 return ''; // Specified language is not installed or param malformed
865 $param = clean_param($param, PARAM_SAFEDIR
);
866 if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
868 } else if (!empty($CFG->themedir
) and file_exists("$CFG->themedir/$param/config.php")) {
871 return ''; // Specified theme is not installed
875 $param = str_replace(" " , "", $param);
876 $param = moodle_strtolower($param); // Convert uppercase to lowercase MDL-16919
877 if (empty($CFG->extendedusernamechars
)) {
878 // regular expression, eliminate all chars EXCEPT:
879 // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
880 $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
885 if (validate_email($param)) {
892 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
898 case PARAM_TIMEZONE
: //can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'
899 $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3]|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
900 if (preg_match($timezonepattern, $param)) {
906 default: // throw error, switched parameters in optional_param or another serious problem
907 print_error("unknownparamtype", '', '', $type);
912 * Return true if given value is integer or string with integer value
914 * @param mixed $value String or Int
915 * @return bool true if number, false if not
917 function is_number($value) {
918 if (is_int($value)) {
920 } else if (is_string($value)) {
921 return ((string)(int)$value) === $value;
928 * Returns host part from url
929 * @param string $url full url
930 * @return string host, null if not found
932 function get_host_from_url($url) {
933 preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
941 * Tests whether anything was returned by text editor
943 * This function is useful for testing whether something you got back from
944 * the HTML editor actually contains anything. Sometimes the HTML editor
945 * appear to be empty, but actually you get back a <br> tag or something.
947 * @param string $string a string containing HTML.
948 * @return boolean does the string contain any actual content - that is text,
949 * images, objects, etc.
951 function html_is_blank($string) {
952 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
956 * Set a key in global configuration
958 * Set a key/value pair in both this session's {@link $CFG} global variable
959 * and in the 'config' database table for future sessions.
961 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
962 * In that case it doesn't affect $CFG.
964 * A NULL value will delete the entry.
968 * @param string $name the key to set
969 * @param string $value the value to set (without magic quotes)
970 * @param string $plugin (optional) the plugin scope, default NULL
971 * @return bool true or exception
973 function set_config($name, $value, $plugin=NULL) {
976 if (empty($plugin)) {
977 if (!array_key_exists($name, $CFG->config_php_settings
)) {
978 // So it's defined for this invocation at least
979 if (is_null($value)) {
982 $CFG->$name = (string)$value; // settings from db are always strings
986 if ($DB->get_field('config', 'name', array('name'=>$name))) {
987 if ($value === null) {
988 $DB->delete_records('config', array('name'=>$name));
990 $DB->set_field('config', 'value', $value, array('name'=>$name));
993 if ($value !== null) {
994 $config = new stdClass();
995 $config->name
= $name;
996 $config->value
= $value;
997 $DB->insert_record('config', $config, false);
1001 } else { // plugin scope
1002 if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
1003 if ($value===null) {
1004 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1006 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
1009 if ($value !== null) {
1010 $config = new stdClass();
1011 $config->plugin
= $plugin;
1012 $config->name
= $name;
1013 $config->value
= $value;
1014 $DB->insert_record('config_plugins', $config, false);
1023 * Get configuration values from the global config table
1024 * or the config_plugins table.
1026 * If called with one parameter, it will load all the config
1027 * variables for one plugin, and return them as an object.
1029 * If called with 2 parameters it will return a string single
1030 * value or false if the value is not found.
1032 * @param string $plugin full component name
1033 * @param string $name default NULL
1034 * @return mixed hash-like object or single value, return false no config found
1036 function get_config($plugin, $name = NULL) {
1039 // normalise component name
1040 if ($plugin === 'moodle' or $plugin === 'core') {
1044 if (!empty($name)) { // the user is asking for a specific value
1045 if (!empty($plugin)) {
1046 if (isset($CFG->forced_plugin_settings
[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings
[$plugin])) {
1047 // setting forced in config file
1048 return $CFG->forced_plugin_settings
[$plugin][$name];
1050 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1053 if (array_key_exists($name, $CFG->config_php_settings
)) {
1054 // setting force in config file
1055 return $CFG->config_php_settings
[$name];
1057 return $DB->get_field('config', 'value', array('name'=>$name));
1062 // the user is after a recordset
1064 $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
1065 if (isset($CFG->forced_plugin_settings
[$plugin])) {
1066 foreach($CFG->forced_plugin_settings
[$plugin] as $n=>$v) {
1067 if (is_null($v) or is_array($v) or is_object($v)) {
1068 // we do not want any extra mess here, just real settings that could be saved in db
1069 unset($localcfg[$n]);
1071 //convert to string as if it went through the DB
1072 $localcfg[$n] = (string)$v;
1077 return (object)$localcfg;
1079 return new stdClass();
1083 // this part is not really used any more, but anyway...
1084 $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
1085 foreach($CFG->config_php_settings
as $n=>$v) {
1086 if (is_null($v) or is_array($v) or is_object($v)) {
1087 // we do not want any extra mess here, just real settings that could be saved in db
1088 unset($localcfg[$n]);
1090 //convert to string as if it went through the DB
1091 $localcfg[$n] = (string)$v;
1094 return (object)$localcfg;
1099 * Removes a key from global configuration
1101 * @param string $name the key to set
1102 * @param string $plugin (optional) the plugin scope
1104 * @return boolean whether the operation succeeded.
1106 function unset_config($name, $plugin=NULL) {
1109 if (empty($plugin)) {
1111 $DB->delete_records('config', array('name'=>$name));
1113 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1120 * Remove all the config variables for a given plugin.
1122 * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1123 * @return boolean whether the operation succeeded.
1125 function unset_all_config_for_plugin($plugin) {
1127 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1128 $like = $DB->sql_like('name', '?', true, true, false, '|');
1129 $params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
1130 $DB->delete_records_select('config', $like, $params);
1135 * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1137 * All users are verified if they still have the necessary capability.
1139 * @param string $value the value of the config setting.
1140 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1141 * @param bool $include admins, include administrators
1142 * @return array of user objects.
1144 function get_users_from_config($value, $capability, $includeadmins = true) {
1147 if (empty($value) or $value === '$@NONE@$') {
1151 // we have to make sure that users still have the necessary capability,
1152 // it should be faster to fetch them all first and then test if they are present
1153 // instead of validating them one-by-one
1154 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM
), $capability);
1155 if ($includeadmins) {
1156 $admins = get_admins();
1157 foreach ($admins as $admin) {
1158 $users[$admin->id
] = $admin;
1162 if ($value === '$@ALL@$') {
1166 $result = array(); // result in correct order
1167 $allowed = explode(',', $value);
1168 foreach ($allowed as $uid) {
1169 if (isset($users[$uid])) {
1170 $user = $users[$uid];
1171 $result[$user->id
] = $user;
1180 * Invalidates browser caches and cached data in temp
1183 function purge_all_caches() {
1186 reset_text_filters_cache();
1187 js_reset_all_caches();
1188 theme_reset_all_caches();
1189 get_string_manager()->reset_caches();
1191 // purge all other caches: rss, simplepie, etc.
1192 remove_dir($CFG->dataroot
.'/cache', true);
1194 // make sure cache dir is writable, throws exception if not
1195 make_upload_directory('cache');
1197 // hack: this script may get called after the purifier was initialised,
1198 // but we do not want to verify repeatedly this exists in each call
1199 make_upload_directory('cache/htmlpurifier');
1205 * Get volatile flags
1207 * @param string $type
1208 * @param int $changedsince default null
1209 * @return records array
1211 function get_cache_flags($type, $changedsince=NULL) {
1214 $params = array('type'=>$type, 'expiry'=>time());
1215 $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1216 if ($changedsince !== NULL) {
1217 $params['changedsince'] = $changedsince;
1218 $sqlwhere .= " AND timemodified > :changedsince";
1222 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1223 foreach ($flags as $flag) {
1224 $cf[$flag->name
] = $flag->value
;
1231 * Get volatile flags
1233 * @param string $type
1234 * @param string $name
1235 * @param int $changedsince default null
1236 * @return records array
1238 function get_cache_flag($type, $name, $changedsince=NULL) {
1241 $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
1243 $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1244 if ($changedsince !== NULL) {
1245 $params['changedsince'] = $changedsince;
1246 $sqlwhere .= " AND timemodified > :changedsince";
1249 return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1253 * Set a volatile flag
1255 * @param string $type the "type" namespace for the key
1256 * @param string $name the key to set
1257 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
1258 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1259 * @return bool Always returns true
1261 function set_cache_flag($type, $name, $value, $expiry=NULL) {
1264 $timemodified = time();
1265 if ($expiry===NULL ||
$expiry < $timemodified) {
1266 $expiry = $timemodified +
24 * 60 * 60;
1268 $expiry = (int)$expiry;
1271 if ($value === NULL) {
1272 unset_cache_flag($type,$name);
1276 if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE
)) { // this is a potential problem in DEBUG_DEVELOPER
1277 if ($f->value
== $value and $f->expiry
== $expiry and $f->timemodified
== $timemodified) {
1278 return true; //no need to update; helps rcache too
1281 $f->expiry
= $expiry;
1282 $f->timemodified
= $timemodified;
1283 $DB->update_record('cache_flags', $f);
1285 $f = new stdClass();
1286 $f->flagtype
= $type;
1289 $f->expiry
= $expiry;
1290 $f->timemodified
= $timemodified;
1291 $DB->insert_record('cache_flags', $f);
1297 * Removes a single volatile flag
1300 * @param string $type the "type" namespace for the key
1301 * @param string $name the key to set
1304 function unset_cache_flag($type, $name) {
1306 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1311 * Garbage-collect volatile flags
1313 * @return bool Always returns true
1315 function gc_cache_flags() {
1317 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1321 /// FUNCTIONS FOR HANDLING USER PREFERENCES ////////////////////////////////////
1324 * Refresh user preference cache. This is used most often for $USER
1325 * object that is stored in session, but it also helps with performance in cron script.
1327 * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1329 * @param stdClass $user user object, preferences are preloaded into ->preference property
1330 * @param int $cachelifetime cache life time on the current page (ins seconds)
1333 function check_user_preferences_loaded(stdClass
$user, $cachelifetime = 120) {
1335 static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
1337 if (!isset($user->id
)) {
1338 throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1341 if (empty($user->id
) or isguestuser($user->id
)) {
1342 // No permanent storage for not-logged-in users and guest
1343 if (!isset($user->preference
)) {
1344 $user->preference
= array();
1351 if (isset($loadedusers[$user->id
]) and isset($user->preference
) and isset($user->preference
['_lastloaded'])) {
1352 // Already loaded at least once on this page. Are we up to date?
1353 if ($user->preference
['_lastloaded'] +
$cachelifetime > $timenow) {
1354 // no need to reload - we are on the same page and we loaded prefs just a moment ago
1357 } else if (!get_cache_flag('userpreferenceschanged', $user->id
, $user->preference
['_lastloaded'])) {
1358 // no change since the lastcheck on this page
1359 $user->preference
['_lastloaded'] = $timenow;
1364 // OK, so we have to reload all preferences
1365 $loadedusers[$user->id
] = true;
1366 $user->preference
= $DB->get_records_menu('user_preferences', array('userid'=>$user->id
), '', 'name,value'); // All values
1367 $user->preference
['_lastloaded'] = $timenow;
1371 * Called from set/delete_user_preferences, so that the prefs can
1372 * be correctly reloaded in different sessions.
1374 * NOTE: internal function, do not call from other code.
1376 * @param integer $userid the user whose prefs were changed.
1379 function mark_user_preferences_changed($userid) {
1382 if (empty($userid) or isguestuser($userid)) {
1383 // no cache flags for guest and not-logged-in users
1387 set_cache_flag('userpreferenceschanged', $userid, 1, time() +
$CFG->sessiontimeout
);
1391 * Sets a preference for the specified user.
1393 * If user object submitted, 'preference' property contains the preferences cache.
1395 * @param string $name The key to set as preference for the specified user
1396 * @param string $value The value to set for the $name key in the specified user's record,
1397 * null means delete current value
1398 * @param stdClass|int $user A moodle user object or id, null means current user
1399 * @return bool always true or exception
1401 function set_user_preference($name, $value, $user = null) {
1404 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1405 throw new coding_exception('Invalid preference name in set_user_preference() call');
1408 if (is_null($value)) {
1409 // null means delete current
1410 return unset_user_preference($name, $user);
1411 } else if (is_object($value)) {
1412 throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1413 } else if (is_array($value)) {
1414 throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1416 $value = (string)$value;
1418 if (is_null($user)) {
1420 } else if (isset($user->id
)) {
1421 // $user is valid object
1422 } else if (is_numeric($user)) {
1423 $user = (object)array('id'=>(int)$user);
1425 throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1428 check_user_preferences_loaded($user);
1430 if (empty($user->id
) or isguestuser($user->id
)) {
1431 // no permanent storage for not-logged-in users and guest
1432 $user->preference
[$name] = $value;
1436 if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id
, 'name'=>$name))) {
1437 if ($preference->value
=== $value and isset($user->preference
[$name]) and $user->preference
[$name] === $value) {
1438 // preference already set to this value
1441 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id
));
1444 $preference = new stdClass();
1445 $preference->userid
= $user->id
;
1446 $preference->name
= $name;
1447 $preference->value
= $value;
1448 $DB->insert_record('user_preferences', $preference);
1451 // update value in cache
1452 $user->preference
[$name] = $value;
1454 // set reload flag for other sessions
1455 mark_user_preferences_changed($user->id
);
1461 * Sets a whole array of preferences for the current user
1463 * If user object submitted, 'preference' property contains the preferences cache.
1465 * @param array $prefarray An array of key/value pairs to be set
1466 * @param stdClass|int $user A moodle user object or id, null means current user
1467 * @return bool always true or exception
1469 function set_user_preferences(array $prefarray, $user = null) {
1470 foreach ($prefarray as $name => $value) {
1471 set_user_preference($name, $value, $user);
1477 * Unsets a preference completely by deleting it from the database
1479 * If user object submitted, 'preference' property contains the preferences cache.
1481 * @param string $name The key to unset as preference for the specified user
1482 * @param stdClass|int $user A moodle user object or id, null means current user
1483 * @return bool always true or exception
1485 function unset_user_preference($name, $user = null) {
1488 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1489 throw new coding_exception('Invalid preference name in unset_user_preference() call');
1492 if (is_null($user)) {
1494 } else if (isset($user->id
)) {
1495 // $user is valid object
1496 } else if (is_numeric($user)) {
1497 $user = (object)array('id'=>(int)$user);
1499 throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1502 check_user_preferences_loaded($user);
1504 if (empty($user->id
) or isguestuser($user->id
)) {
1505 // no permanent storage for not-logged-in user and guest
1506 unset($user->preference
[$name]);
1511 $DB->delete_records('user_preferences', array('userid'=>$user->id
, 'name'=>$name));
1513 // delete the preference from cache
1514 unset($user->preference
[$name]);
1516 // set reload flag for other sessions
1517 mark_user_preferences_changed($user->id
);
1523 * Used to fetch user preference(s)
1525 * If no arguments are supplied this function will return
1526 * all of the current user preferences as an array.
1528 * If a name is specified then this function
1529 * attempts to return that particular preference value. If
1530 * none is found, then the optional value $default is returned,
1533 * If user object submitted, 'preference' property contains the preferences cache.
1535 * @param string $name Name of the key to use in finding a preference value
1536 * @param mixed $default Value to be returned if the $name key is not set in the user preferences
1537 * @param stdClass|int $user A moodle user object or id, null means current user
1538 * @return mixed string value or default
1540 function get_user_preferences($name = null, $default = null, $user = null) {
1543 if (is_null($name)) {
1545 } else if (is_numeric($name) or $name === '_lastloaded') {
1546 throw new coding_exception('Invalid preference name in get_user_preferences() call');
1549 if (is_null($user)) {
1551 } else if (isset($user->id
)) {
1552 // $user is valid object
1553 } else if (is_numeric($user)) {
1554 $user = (object)array('id'=>(int)$user);
1556 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1559 check_user_preferences_loaded($user);
1562 return $user->preference
; // All values
1563 } else if (isset($user->preference
[$name])) {
1564 return $user->preference
[$name]; // The single string value
1566 return $default; // Default value (null if not specified)
1570 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1573 * Given date parts in user time produce a GMT timestamp.
1575 * @todo Finish documenting this function
1576 * @param int $year The year part to create timestamp of
1577 * @param int $month The month part to create timestamp of
1578 * @param int $day The day part to create timestamp of
1579 * @param int $hour The hour part to create timestamp of
1580 * @param int $minute The minute part to create timestamp of
1581 * @param int $second The second part to create timestamp of
1582 * @param mixed $timezone Timezone modifier, if 99 then use default user's timezone
1583 * @param bool $applydst Toggle Daylight Saving Time, default true, will be
1584 * applied only if timezone is 99 or string.
1585 * @return int timestamp
1587 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1589 //save input timezone, required for dst offset check.
1590 $passedtimezone = $timezone;
1592 $timezone = get_user_timezone_offset($timezone);
1594 if (abs($timezone) > 13) { //server time
1595 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1597 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1598 $time = usertime($time, $timezone);
1600 //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
1601 if ($applydst && ((99 == $passedtimezone) ||
!is_numeric($passedtimezone))) {
1602 $time -= dst_offset_on($time, $passedtimezone);
1611 * Format a date/time (seconds) as weeks, days, hours etc as needed
1613 * Given an amount of time in seconds, returns string
1614 * formatted nicely as weeks, days, hours etc as needed
1620 * @param int $totalsecs Time in seconds
1621 * @param object $str Should be a time object
1622 * @return string A nicely formatted date/time string
1624 function format_time($totalsecs, $str=NULL) {
1626 $totalsecs = abs($totalsecs);
1628 if (!$str) { // Create the str structure the slow way
1629 $str = new stdClass();
1630 $str->day
= get_string('day');
1631 $str->days
= get_string('days');
1632 $str->hour
= get_string('hour');
1633 $str->hours
= get_string('hours');
1634 $str->min
= get_string('min');
1635 $str->mins
= get_string('mins');
1636 $str->sec
= get_string('sec');
1637 $str->secs
= get_string('secs');
1638 $str->year
= get_string('year');
1639 $str->years
= get_string('years');
1643 $years = floor($totalsecs/YEARSECS
);
1644 $remainder = $totalsecs - ($years*YEARSECS
);
1645 $days = floor($remainder/DAYSECS
);
1646 $remainder = $totalsecs - ($days*DAYSECS
);
1647 $hours = floor($remainder/HOURSECS
);
1648 $remainder = $remainder - ($hours*HOURSECS
);
1649 $mins = floor($remainder/MINSECS
);
1650 $secs = $remainder - ($mins*MINSECS
);
1652 $ss = ($secs == 1) ?
$str->sec
: $str->secs
;
1653 $sm = ($mins == 1) ?
$str->min
: $str->mins
;
1654 $sh = ($hours == 1) ?
$str->hour
: $str->hours
;
1655 $sd = ($days == 1) ?
$str->day
: $str->days
;
1656 $sy = ($years == 1) ?
$str->year
: $str->years
;
1664 if ($years) $oyears = $years .' '. $sy;
1665 if ($days) $odays = $days .' '. $sd;
1666 if ($hours) $ohours = $hours .' '. $sh;
1667 if ($mins) $omins = $mins .' '. $sm;
1668 if ($secs) $osecs = $secs .' '. $ss;
1670 if ($years) return trim($oyears .' '. $odays);
1671 if ($days) return trim($odays .' '. $ohours);
1672 if ($hours) return trim($ohours .' '. $omins);
1673 if ($mins) return trim($omins .' '. $osecs);
1674 if ($secs) return $osecs;
1675 return get_string('now');
1679 * Returns a formatted string that represents a date in user time
1681 * Returns a formatted string that represents a date in user time
1682 * <b>WARNING: note that the format is for strftime(), not date().</b>
1683 * Because of a bug in most Windows time libraries, we can't use
1684 * the nicer %e, so we have to use %d which has leading zeroes.
1685 * A lot of the fuss in the function is just getting rid of these leading
1686 * zeroes as efficiently as possible.
1688 * If parameter fixday = true (default), then take off leading
1689 * zero from %d, else maintain it.
1691 * @param int $date the timestamp in UTC, as obtained from the database.
1692 * @param string $format strftime format. You should probably get this using
1693 * get_string('strftime...', 'langconfig');
1694 * @param mixed $timezone by default, uses the user's time zone. if numeric and
1695 * not 99 then daylight saving will not be added.
1696 * @param bool $fixday If true (default) then the leading zero from %d is removed.
1697 * If false then the leading zero is maintained.
1698 * @return string the formatted date/time.
1700 function userdate($date, $format = '', $timezone = 99, $fixday = true) {
1704 if (empty($format)) {
1705 $format = get_string('strftimedaydatetime', 'langconfig');
1708 if (!empty($CFG->nofixday
)) { // Config.php can force %d not to be fixed.
1710 } else if ($fixday) {
1711 $formatnoday = str_replace('%d', 'DD', $format);
1712 $fixday = ($formatnoday != $format);
1715 //add daylight saving offset for string timezones only, as we can't get dst for
1716 //float values. if timezone is 99 (user default timezone), then try update dst.
1717 if ((99 == $timezone) ||
!is_numeric($timezone)) {
1718 $date +
= dst_offset_on($date, $timezone);
1721 $timezone = get_user_timezone_offset($timezone);
1723 if (abs($timezone) > 13) { /// Server time
1725 $datestring = strftime($formatnoday, $date);
1726 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
1727 $datestring = str_replace('DD', $daystring, $datestring);
1729 $datestring = strftime($format, $date);
1732 $date +
= (int)($timezone * 3600);
1734 $datestring = gmstrftime($formatnoday, $date);
1735 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
1736 $datestring = str_replace('DD', $daystring, $datestring);
1738 $datestring = gmstrftime($format, $date);
1742 /// If we are running under Windows convert from windows encoding to UTF-8
1743 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1745 if ($CFG->ostype
== 'WINDOWS') {
1746 if ($localewincharset = get_string('localewincharset', 'langconfig')) {
1747 $textlib = textlib_get_instance();
1748 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1756 * Given a $time timestamp in GMT (seconds since epoch),
1757 * returns an array that represents the date in user time
1759 * @todo Finish documenting this function
1761 * @param int $time Timestamp in GMT
1762 * @param mixed $timezone offset time with timezone, if float and not 99, then no
1763 * dst offset is applyed
1764 * @return array An array that represents the date in user time
1766 function usergetdate($time, $timezone=99) {
1768 //save input timezone, required for dst offset check.
1769 $passedtimezone = $timezone;
1771 $timezone = get_user_timezone_offset($timezone);
1773 if (abs($timezone) > 13) { // Server time
1774 return getdate($time);
1777 //add daylight saving offset for string timezones only, as we can't get dst for
1778 //float values. if timezone is 99 (user default timezone), then try update dst.
1779 if ($passedtimezone == 99 ||
!is_numeric($passedtimezone)) {
1780 $time +
= dst_offset_on($time, $passedtimezone);
1783 $time +
= intval((float)$timezone * HOURSECS
);
1785 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
1787 //be careful to ensure the returned array matches that produced by getdate() above
1790 $getdate['weekday'],
1797 $getdate['minutes'],
1799 ) = explode('_', $datestring);
1801 // set correct datatype to match with getdate()
1802 $getdate['seconds'] = (int)$getdate['seconds'];
1803 $getdate['yday'] = (int)$getdate['yday'] - 1; // gettime returns 0 through 365
1804 $getdate['year'] = (int)$getdate['year'];
1805 $getdate['mon'] = (int)$getdate['mon'];
1806 $getdate['wday'] = (int)$getdate['wday'];
1807 $getdate['mday'] = (int)$getdate['mday'];
1808 $getdate['hours'] = (int)$getdate['hours'];
1809 $getdate['minutes'] = (int)$getdate['minutes'];
1814 * Given a GMT timestamp (seconds since epoch), offsets it by
1815 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1818 * @param int $date Timestamp in GMT
1819 * @param float $timezone
1822 function usertime($date, $timezone=99) {
1824 $timezone = get_user_timezone_offset($timezone);
1826 if (abs($timezone) > 13) {
1829 return $date - (int)($timezone * HOURSECS
);
1833 * Given a time, return the GMT timestamp of the most recent midnight
1834 * for the current user.
1836 * @param int $date Timestamp in GMT
1837 * @param float $timezone Defaults to user's timezone
1838 * @return int Returns a GMT timestamp
1840 function usergetmidnight($date, $timezone=99) {
1842 $userdate = usergetdate($date, $timezone);
1844 // Time of midnight of this user's day, in GMT
1845 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1850 * Returns a string that prints the user's timezone
1852 * @param float $timezone The user's timezone
1855 function usertimezone($timezone=99) {
1857 $tz = get_user_timezone($timezone);
1859 if (!is_float($tz)) {
1863 if(abs($tz) > 13) { // Server time
1864 return get_string('serverlocaltime');
1867 if($tz == intval($tz)) {
1868 // Don't show .0 for whole hours
1885 * Returns a float which represents the user's timezone difference from GMT in hours
1886 * Checks various settings and picks the most dominant of those which have a value
1890 * @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
1893 function get_user_timezone_offset($tz = 99) {
1897 $tz = get_user_timezone($tz);
1899 if (is_float($tz)) {
1902 $tzrecord = get_timezone_record($tz);
1903 if (empty($tzrecord)) {
1906 return (float)$tzrecord->gmtoff
/ HOURMINS
;
1911 * Returns an int which represents the systems's timezone difference from GMT in seconds
1914 * @param mixed $tz timezone
1915 * @return int if found, false is timezone 99 or error
1917 function get_timezone_offset($tz) {
1924 if (is_numeric($tz)) {
1925 return intval($tz * 60*60);
1928 if (!$tzrecord = get_timezone_record($tz)) {
1931 return intval($tzrecord->gmtoff
* 60);
1935 * Returns a float or a string which denotes the user's timezone
1936 * 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)
1937 * means that for this timezone there are also DST rules to be taken into account
1938 * Checks various settings and picks the most dominant of those which have a value
1942 * @param mixed $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
1945 function get_user_timezone($tz = 99) {
1950 isset($CFG->forcetimezone
) ?
$CFG->forcetimezone
: 99,
1951 isset($USER->timezone
) ?
$USER->timezone
: 99,
1952 isset($CFG->timezone
) ?
$CFG->timezone
: 99,
1957 while(($tz == '' ||
$tz == 99 ||
$tz == NULL) && $next = each($timezones)) {
1958 $tz = $next['value'];
1961 return is_numeric($tz) ?
(float) $tz : $tz;
1965 * Returns cached timezone record for given $timezonename
1969 * @param string $timezonename
1970 * @return mixed timezonerecord object or false
1972 function get_timezone_record($timezonename) {
1974 static $cache = NULL;
1976 if ($cache === NULL) {
1980 if (isset($cache[$timezonename])) {
1981 return $cache[$timezonename];
1984 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
1985 WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE
);
1989 * Build and store the users Daylight Saving Time (DST) table
1994 * @param mixed $from_year Start year for the table, defaults to 1971
1995 * @param mixed $to_year End year for the table, defaults to 2035
1996 * @param mixed $strtimezone, if null or 99 then user's default timezone is used
1999 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
2000 global $CFG, $SESSION, $DB;
2002 $usertz = get_user_timezone($strtimezone);
2004 if (is_float($usertz)) {
2005 // Trivial timezone, no DST
2009 if (!empty($SESSION->dst_offsettz
) && $SESSION->dst_offsettz
!= $usertz) {
2010 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2011 unset($SESSION->dst_offsets
);
2012 unset($SESSION->dst_range
);
2015 if (!empty($SESSION->dst_offsets
) && empty($from_year) && empty($to_year)) {
2016 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
2017 // This will be the return path most of the time, pretty light computationally
2021 // Reaching here means we either need to extend our table or create it from scratch
2023 // Remember which TZ we calculated these changes for
2024 $SESSION->dst_offsettz
= $usertz;
2026 if(empty($SESSION->dst_offsets
)) {
2027 // If we 're creating from scratch, put the two guard elements in there
2028 $SESSION->dst_offsets
= array(1 => NULL, 0 => NULL);
2030 if(empty($SESSION->dst_range
)) {
2031 // If creating from scratch
2032 $from = max((empty($from_year) ?
intval(date('Y')) - 3 : $from_year), 1971);
2033 $to = min((empty($to_year) ?
intval(date('Y')) +
3 : $to_year), 2035);
2035 // Fill in the array with the extra years we need to process
2036 $yearstoprocess = array();
2037 for($i = $from; $i <= $to; ++
$i) {
2038 $yearstoprocess[] = $i;
2041 // Take note of which years we have processed for future calls
2042 $SESSION->dst_range
= array($from, $to);
2045 // If needing to extend the table, do the same
2046 $yearstoprocess = array();
2048 $from = max((empty($from_year) ?
$SESSION->dst_range
[0] : $from_year), 1971);
2049 $to = min((empty($to_year) ?
$SESSION->dst_range
[1] : $to_year), 2035);
2051 if($from < $SESSION->dst_range
[0]) {
2052 // Take note of which years we need to process and then note that we have processed them for future calls
2053 for($i = $from; $i < $SESSION->dst_range
[0]; ++
$i) {
2054 $yearstoprocess[] = $i;
2056 $SESSION->dst_range
[0] = $from;
2058 if($to > $SESSION->dst_range
[1]) {
2059 // Take note of which years we need to process and then note that we have processed them for future calls
2060 for($i = $SESSION->dst_range
[1] +
1; $i <= $to; ++
$i) {
2061 $yearstoprocess[] = $i;
2063 $SESSION->dst_range
[1] = $to;
2067 if(empty($yearstoprocess)) {
2068 // This means that there was a call requesting a SMALLER range than we have already calculated
2072 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2073 // Also, the array is sorted in descending timestamp order!
2077 static $presets_cache = array();
2078 if (!isset($presets_cache[$usertz])) {
2079 $presets_cache[$usertz] = $DB->get_records('timezone', array('name'=>$usertz), 'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, std_startday, std_weekday, std_skipweeks, std_time');
2081 if(empty($presets_cache[$usertz])) {
2085 // Remove ending guard (first element of the array)
2086 reset($SESSION->dst_offsets
);
2087 unset($SESSION->dst_offsets
[key($SESSION->dst_offsets
)]);
2089 // Add all required change timestamps
2090 foreach($yearstoprocess as $y) {
2091 // Find the record which is in effect for the year $y
2092 foreach($presets_cache[$usertz] as $year => $preset) {
2098 $changes = dst_changes_for_year($y, $preset);
2100 if($changes === NULL) {
2103 if($changes['dst'] != 0) {
2104 $SESSION->dst_offsets
[$changes['dst']] = $preset->dstoff
* MINSECS
;
2106 if($changes['std'] != 0) {
2107 $SESSION->dst_offsets
[$changes['std']] = 0;
2111 // Put in a guard element at the top
2112 $maxtimestamp = max(array_keys($SESSION->dst_offsets
));
2113 $SESSION->dst_offsets
[($maxtimestamp + DAYSECS
)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2116 krsort($SESSION->dst_offsets
);
2122 * Calculates the required DST change and returns a Timestamp Array
2126 * @param mixed $year Int or String Year to focus on
2127 * @param object $timezone Instatiated Timezone object
2128 * @return mixed Null, or Array dst=>xx, 0=>xx, std=>yy, 1=>yy
2130 function dst_changes_for_year($year, $timezone) {
2132 if($timezone->dst_startday
== 0 && $timezone->dst_weekday
== 0 && $timezone->std_startday
== 0 && $timezone->std_weekday
== 0) {
2136 $monthdaydst = find_day_in_month($timezone->dst_startday
, $timezone->dst_weekday
, $timezone->dst_month
, $year);
2137 $monthdaystd = find_day_in_month($timezone->std_startday
, $timezone->std_weekday
, $timezone->std_month
, $year);
2139 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time
);
2140 list($std_hour, $std_min) = explode(':', $timezone->std_time
);
2142 $timedst = make_timestamp($year, $timezone->dst_month
, $monthdaydst, 0, 0, 0, 99, false);
2143 $timestd = make_timestamp($year, $timezone->std_month
, $monthdaystd, 0, 0, 0, 99, false);
2145 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2146 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2147 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2149 $timedst +
= $dst_hour * HOURSECS +
$dst_min * MINSECS
;
2150 $timestd +
= $std_hour * HOURSECS +
$std_min * MINSECS
;
2152 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2156 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2157 * - Note: Daylight saving only works for string timezones and not for float.
2160 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2161 * @param mixed $strtimezone timezone for which offset is expected, if 99 or null
2162 * then user's default timezone is used.
2165 function dst_offset_on($time, $strtimezone = NULL) {
2168 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) ||
empty($SESSION->dst_offsets
)) {
2172 reset($SESSION->dst_offsets
);
2173 while(list($from, $offset) = each($SESSION->dst_offsets
)) {
2174 if($from <= $time) {
2179 // This is the normal return path
2180 if($offset !== NULL) {
2184 // Reaching this point means we haven't calculated far enough, do it now:
2185 // Calculate extra DST changes if needed and recurse. The recursion always
2186 // moves toward the stopping condition, so will always end.
2189 // We need a year smaller than $SESSION->dst_range[0]
2190 if($SESSION->dst_range
[0] == 1971) {
2193 calculate_user_dst_table($SESSION->dst_range
[0] - 5, NULL, $strtimezone);
2194 return dst_offset_on($time, $strtimezone);
2197 // We need a year larger than $SESSION->dst_range[1]
2198 if($SESSION->dst_range
[1] == 2035) {
2201 calculate_user_dst_table(NULL, $SESSION->dst_range
[1] +
5, $strtimezone);
2202 return dst_offset_on($time, $strtimezone);
2209 * @todo Document what this function does
2210 * @param int $startday
2211 * @param int $weekday
2216 function find_day_in_month($startday, $weekday, $month, $year) {
2218 $daysinmonth = days_in_month($month, $year);
2220 if($weekday == -1) {
2221 // Don't care about weekday, so return:
2222 // abs($startday) if $startday != -1
2223 // $daysinmonth otherwise
2224 return ($startday == -1) ?
$daysinmonth : abs($startday);
2227 // From now on we 're looking for a specific weekday
2229 // Give "end of month" its actual value, since we know it
2230 if($startday == -1) {
2231 $startday = -1 * $daysinmonth;
2234 // Starting from day $startday, the sign is the direction
2238 $startday = abs($startday);
2239 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2241 // This is the last such weekday of the month
2242 $lastinmonth = $daysinmonth +
$weekday - $lastmonthweekday;
2243 if($lastinmonth > $daysinmonth) {
2247 // Find the first such weekday <= $startday
2248 while($lastinmonth > $startday) {
2252 return $lastinmonth;
2257 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2259 $diff = $weekday - $indexweekday;
2264 // This is the first such weekday of the month equal to or after $startday
2265 $firstfromindex = $startday +
$diff;
2267 return $firstfromindex;
2273 * Calculate the number of days in a given month
2275 * @param int $month The month whose day count is sought
2276 * @param int $year The year of the month whose day count is sought
2279 function days_in_month($month, $year) {
2280 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2284 * Calculate the position in the week of a specific calendar day
2286 * @param int $day The day of the date whose position in the week is sought
2287 * @param int $month The month of the date whose position in the week is sought
2288 * @param int $year The year of the date whose position in the week is sought
2291 function dayofweek($day, $month, $year) {
2292 // I wonder if this is any different from
2293 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2294 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2297 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2300 * Returns full login url.
2302 * @return string login url
2304 function get_login_url() {
2307 $url = "$CFG->wwwroot/login/index.php";
2309 if (!empty($CFG->loginhttps
)) {
2310 $url = str_replace('http:', 'https:', $url);
2317 * This function checks that the current user is logged in and has the
2318 * required privileges
2320 * This function checks that the current user is logged in, and optionally
2321 * whether they are allowed to be in a particular course and view a particular
2323 * If they are not logged in, then it redirects them to the site login unless
2324 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2325 * case they are automatically logged in as guests.
2326 * If $courseid is given and the user is not enrolled in that course then the
2327 * user is redirected to the course enrolment page.
2328 * If $cm is given and the course module is hidden and the user is not a teacher
2329 * in the course then the user is redirected to the course home page.
2331 * When $cm parameter specified, this function sets page layout to 'module'.
2332 * You need to change it manually later if some other layout needed.
2334 * @param mixed $courseorid id of the course or course object
2335 * @param bool $autologinguest default true
2336 * @param object $cm course module object
2337 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2338 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2339 * in order to keep redirects working properly. MDL-14495
2340 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2341 * @return mixed Void, exit, and die depending on path
2343 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2344 global $CFG, $SESSION, $USER, $FULLME, $PAGE, $SITE, $DB, $OUTPUT;
2346 // setup global $COURSE, themes, language and locale
2347 if (!empty($courseorid)) {
2348 if (is_object($courseorid)) {
2349 $course = $courseorid;
2350 } else if ($courseorid == SITEID
) {
2351 $course = clone($SITE);
2353 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST
);
2356 if ($cm->course
!= $course->id
) {
2357 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2359 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2360 if (!($cm instanceof cm_info
)) {
2361 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2362 // db queries so this is not really a performance concern, however it is obviously
2363 // better if you use get_fast_modinfo to get the cm before calling this.
2364 $modinfo = get_fast_modinfo($course);
2365 $cm = $modinfo->get_cm($cm->id
);
2367 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2368 $PAGE->set_pagelayout('incourse');
2370 $PAGE->set_course($course); // set's up global $COURSE
2373 // do not touch global $COURSE via $PAGE->set_course(),
2374 // the reasons is we need to be able to call require_login() at any time!!
2377 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2381 // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2382 // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2383 // risk leading the user back to the AJAX request URL.
2384 if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT
) {
2385 $setwantsurltome = false;
2388 // If the user is not even logged in yet then make sure they are
2389 if (!isloggedin()) {
2390 if ($autologinguest and !empty($CFG->guestloginbutton
) and !empty($CFG->autologinguests
)) {
2391 if (!$guest = get_complete_user_data('id', $CFG->siteguest
)) {
2392 // misconfigured site guest, just redirect to login page
2393 redirect(get_login_url());
2394 exit; // never reached
2396 $lang = isset($SESSION->lang
) ?
$SESSION->lang
: $CFG->lang
;
2397 complete_user_login($guest, false);
2398 $USER->autologinguest
= true;
2399 $SESSION->lang
= $lang;
2401 //NOTE: $USER->site check was obsoleted by session test cookie,
2402 // $USER->confirmed test is in login/index.php
2403 if ($preventredirect) {
2404 throw new require_login_exception('You are not logged in');
2407 if ($setwantsurltome) {
2408 // TODO: switch to PAGE->url
2409 $SESSION->wantsurl
= $FULLME;
2411 if (!empty($_SERVER['HTTP_REFERER'])) {
2412 $SESSION->fromurl
= $_SERVER['HTTP_REFERER'];
2414 redirect(get_login_url());
2415 exit; // never reached
2419 // loginas as redirection if needed
2420 if ($course->id
!= SITEID
and session_is_loggedinas()) {
2421 if ($USER->loginascontext
->contextlevel
== CONTEXT_COURSE
) {
2422 if ($USER->loginascontext
->instanceid
!= $course->id
) {
2423 print_error('loginasonecourse', '', $CFG->wwwroot
.'/course/view.php?id='.$USER->loginascontext
->instanceid
);
2428 // check whether the user should be changing password (but only if it is REALLY them)
2429 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2430 $userauth = get_auth_plugin($USER->auth
);
2431 if ($userauth->can_change_password() and !$preventredirect) {
2432 if ($setwantsurltome) {
2433 $SESSION->wantsurl
= $FULLME;
2435 if ($changeurl = $userauth->change_password_url()) {
2436 //use plugin custom url
2437 redirect($changeurl);
2439 //use moodle internal method
2440 if (empty($CFG->loginhttps
)) {
2441 redirect($CFG->wwwroot
.'/login/change_password.php');
2443 $wwwroot = str_replace('http:','https:', $CFG->wwwroot
);
2444 redirect($wwwroot .'/login/change_password.php');
2448 print_error('nopasswordchangeforced', 'auth');
2452 // Check that the user account is properly set up
2453 if (user_not_fully_set_up($USER)) {
2454 if ($preventredirect) {
2455 throw new require_login_exception('User not fully set-up');
2457 if ($setwantsurltome) {
2458 $SESSION->wantsurl
= $FULLME;
2460 redirect($CFG->wwwroot
.'/user/edit.php?id='. $USER->id
.'&course='. SITEID
);
2463 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2466 // Do not bother admins with any formalities
2467 if (is_siteadmin()) {
2468 //set accesstime or the user will appear offline which messes up messaging
2469 user_accesstime_log($course->id
);
2473 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2474 if (!$USER->policyagreed
and !is_siteadmin()) {
2475 if (!empty($CFG->sitepolicy
) and !isguestuser()) {
2476 if ($preventredirect) {
2477 throw new require_login_exception('Policy not agreed');
2479 if ($setwantsurltome) {
2480 $SESSION->wantsurl
= $FULLME;
2482 redirect($CFG->wwwroot
.'/user/policy.php');
2483 } else if (!empty($CFG->sitepolicyguest
) and isguestuser()) {
2484 if ($preventredirect) {
2485 throw new require_login_exception('Policy not agreed');
2487 if ($setwantsurltome) {
2488 $SESSION->wantsurl
= $FULLME;
2490 redirect($CFG->wwwroot
.'/user/policy.php');
2494 // Fetch the system context, the course context, and prefetch its child contexts
2495 $sysctx = get_context_instance(CONTEXT_SYSTEM
);
2496 $coursecontext = get_context_instance(CONTEXT_COURSE
, $course->id
, MUST_EXIST
);
2498 $cmcontext = get_context_instance(CONTEXT_MODULE
, $cm->id
, MUST_EXIST
);
2503 // If the site is currently under maintenance, then print a message
2504 if (!empty($CFG->maintenance_enabled
) and !has_capability('moodle/site:config', $sysctx)) {
2505 if ($preventredirect) {
2506 throw new require_login_exception('Maintenance in progress');
2509 print_maintenance_message();
2512 // make sure the course itself is not hidden
2513 if ($course->id
== SITEID
) {
2514 // frontpage can not be hidden
2516 if (is_role_switched($course->id
)) {
2517 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2519 if (!$course->visible
and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2520 // originally there was also test of parent category visibility,
2521 // BUT is was very slow in complex queries involving "my courses"
2522 // now it is also possible to simply hide all courses user is not enrolled in :-)
2523 if ($preventredirect) {
2524 throw new require_login_exception('Course is hidden');
2526 // We need to override the navigation URL as the course won't have
2527 // been added to the navigation and thus the navigation will mess up
2528 // when trying to find it.
2529 navigation_node
::override_active_url(new moodle_url('/'));
2530 notice(get_string('coursehidden'), $CFG->wwwroot
.'/');
2535 // is the user enrolled?
2536 if ($course->id
== SITEID
) {
2537 // everybody is enrolled on the frontpage
2540 if (session_is_loggedinas()) {
2541 // Make sure the REAL person can access this course first
2542 $realuser = session_get_realuser();
2543 if (!is_enrolled($coursecontext, $realuser->id
, '', true) and !is_viewing($coursecontext, $realuser->id
) and !is_siteadmin($realuser->id
)) {
2544 if ($preventredirect) {
2545 throw new require_login_exception('Invalid course login-as access');
2547 echo $OUTPUT->header();
2548 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot
.'/');
2552 // very simple enrolment caching - changes in course setting are not reflected immediately
2553 if (!isset($USER->enrol
)) {
2554 $USER->enrol
= array();
2555 $USER->enrol
['enrolled'] = array();
2556 $USER->enrol
['tempguest'] = array();
2561 if (is_role_switched($course->id
)) {
2562 // ok, user had to be inside this course before the switch
2565 } else if (is_viewing($coursecontext, $USER)) {
2566 // ok, no need to mess with enrol
2570 if (isset($USER->enrol
['enrolled'][$course->id
])) {
2571 if ($USER->enrol
['enrolled'][$course->id
] == 0) {
2573 } else if ($USER->enrol
['enrolled'][$course->id
] > time()) {
2577 unset($USER->enrol
['enrolled'][$course->id
]);
2580 if (isset($USER->enrol
['tempguest'][$course->id
])) {
2581 if ($USER->enrol
['tempguest'][$course->id
] == 0) {
2583 } else if ($USER->enrol
['tempguest'][$course->id
] > time()) {
2587 unset($USER->enrol
['tempguest'][$course->id
]);
2588 $USER->access
= remove_temp_roles($coursecontext, $USER->access
);
2594 } else if (is_enrolled($coursecontext, $USER, '', true)) {
2595 // active participants may always access
2596 // TODO: refactor this into some new function
2598 $sql = "SELECT MAX(ue.timeend)
2599 FROM {user_enrolments} ue
2600 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2601 JOIN {user} u ON u.id = ue.userid
2602 WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0
2603 AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
2604 $params = array('enabled'=>ENROL_INSTANCE_ENABLED
, 'active'=>ENROL_USER_ACTIVE
,
2605 'userid'=>$USER->id
, 'courseid'=>$coursecontext->instanceid
, 'now1'=>$now, 'now2'=>$now);
2606 $until = $DB->get_field_sql($sql, $params);
2607 if (!$until or $until > time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD
) {
2608 $until = time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD
;
2611 $USER->enrol
['enrolled'][$course->id
] = $until;
2614 // remove traces of previous temp guest access
2615 $USER->access
= remove_temp_roles($coursecontext, $USER->access
);
2618 $instances = $DB->get_records('enrol', array('courseid'=>$course->id
, 'status'=>ENROL_INSTANCE_ENABLED
), 'sortorder, id ASC');
2619 $enrols = enrol_get_plugins(true);
2620 // first ask all enabled enrol instances in course if they want to auto enrol user
2621 foreach($instances as $instance) {
2622 if (!isset($enrols[$instance->enrol
])) {
2625 // Get a duration for the guestaccess, a timestamp in the future or false.
2626 $until = $enrols[$instance->enrol
]->try_autoenrol($instance);
2627 if ($until !== false) {
2628 $USER->enrol
['enrolled'][$course->id
] = $until;
2629 $USER->access
= remove_temp_roles($coursecontext, $USER->access
);
2634 // if not enrolled yet try to gain temporary guest access
2636 foreach($instances as $instance) {
2637 if (!isset($enrols[$instance->enrol
])) {
2640 // Get a duration for the guestaccess, a timestamp in the future or false.
2641 $until = $enrols[$instance->enrol
]->try_guestaccess($instance);
2642 if ($until !== false) {
2643 $USER->enrol
['tempguest'][$course->id
] = $until;
2653 if ($preventredirect) {
2654 throw new require_login_exception('Not enrolled');
2656 if ($setwantsurltome) {
2657 $SESSION->wantsurl
= $FULLME;
2659 redirect($CFG->wwwroot
.'/enrol/index.php?id='. $course->id
);
2663 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
2664 // conditional availability, etc
2665 if ($cm && !$cm->uservisible
) {
2666 if ($preventredirect) {
2667 throw new require_login_exception('Activity is hidden');
2669 redirect($CFG->wwwroot
, get_string('activityiscurrentlyhidden'));
2672 // Finally access granted, update lastaccess times
2673 user_accesstime_log($course->id
);
2678 * This function just makes sure a user is logged out.
2682 function require_logout() {
2688 add_to_log(SITEID
, "user", "logout", "view.php?id=$USER->id&course=".SITEID
, $USER->id
, 0, $USER->id
);
2690 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2691 foreach($authsequence as $authname) {
2692 $authplugin = get_auth_plugin($authname);
2693 $authplugin->prelogout_hook();
2697 events_trigger('user_logout', $params);
2698 session_get_instance()->terminate_current();
2703 * Weaker version of require_login()
2705 * This is a weaker version of {@link require_login()} which only requires login
2706 * when called from within a course rather than the site page, unless
2707 * the forcelogin option is turned on.
2708 * @see require_login()
2711 * @param mixed $courseorid The course object or id in question
2712 * @param bool $autologinguest Allow autologin guests if that is wanted
2713 * @param object $cm Course activity module if known
2714 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2715 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2716 * in order to keep redirects working properly. MDL-14495
2717 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2720 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2721 global $CFG, $PAGE, $SITE;
2722 $issite = (is_object($courseorid) and $courseorid->id
== SITEID
)
2723 or (!is_object($courseorid) and $courseorid == SITEID
);
2724 if ($issite && !empty($cm) && !($cm instanceof cm_info
)) {
2725 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2726 // db queries so this is not really a performance concern, however it is obviously
2727 // better if you use get_fast_modinfo to get the cm before calling this.
2728 if (is_object($courseorid)) {
2729 $course = $courseorid;
2731 $course = clone($SITE);
2733 $modinfo = get_fast_modinfo($course);
2734 $cm = $modinfo->get_cm($cm->id
);
2736 if (!empty($CFG->forcelogin
)) {
2737 // login required for both SITE and courses
2738 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2740 } else if ($issite && !empty($cm) and !$cm->uservisible
) {
2741 // always login for hidden activities
2742 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2744 } else if ($issite) {
2745 //login for SITE not required
2746 if ($cm and empty($cm->visible
)) {
2747 // hidden activities are not accessible without login
2748 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2749 } else if ($cm and !empty($CFG->enablegroupmembersonly
) and $cm->groupmembersonly
) {
2750 // not-logged-in users do not have any group membership
2751 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2753 // We still need to instatiate PAGE vars properly so that things
2754 // that rely on it like navigation function correctly.
2755 if (!empty($courseorid)) {
2756 if (is_object($courseorid)) {
2757 $course = $courseorid;
2759 $course = clone($SITE);
2762 if ($cm->course
!= $course->id
) {
2763 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
2765 $PAGE->set_cm($cm, $course);
2766 $PAGE->set_pagelayout('incourse');
2768 $PAGE->set_course($course);
2771 // If $PAGE->course, and hence $PAGE->context, have not already been set
2772 // up properly, set them up now.
2773 $PAGE->set_course($PAGE->course
);
2775 //TODO: verify conditional activities here
2776 user_accesstime_log(SITEID
);
2781 // course login always required
2782 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
2787 * Require key login. Function terminates with error if key not found or incorrect.
2793 * @uses NO_MOODLE_COOKIES
2794 * @uses PARAM_ALPHANUM
2795 * @param string $script unique script identifier
2796 * @param int $instance optional instance id
2797 * @return int Instance ID
2799 function require_user_key_login($script, $instance=null) {
2800 global $USER, $SESSION, $CFG, $DB;
2802 if (!NO_MOODLE_COOKIES
) {
2803 print_error('sessioncookiesdisable');
2807 @session_write_close
();
2809 $keyvalue = required_param('key', PARAM_ALPHANUM
);
2811 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
2812 print_error('invalidkey');
2815 if (!empty($key->validuntil
) and $key->validuntil
< time()) {
2816 print_error('expiredkey');
2819 if ($key->iprestriction
) {
2820 $remoteaddr = getremoteaddr(null);
2821 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction
)) {
2822 print_error('ipmismatch');
2826 if (!$user = $DB->get_record('user', array('id'=>$key->userid
))) {
2827 print_error('invaliduserid');
2830 /// emulate normal session
2831 session_set_user($user);
2833 /// note we are not using normal login
2834 if (!defined('USER_KEY_LOGIN')) {
2835 define('USER_KEY_LOGIN', true);
2838 /// return instance id - it might be empty
2839 return $key->instance
;
2843 * Creates a new private user access key.
2846 * @param string $script unique target identifier
2847 * @param int $userid
2848 * @param int $instance optional instance id
2849 * @param string $iprestriction optional ip restricted access
2850 * @param timestamp $validuntil key valid only until given data
2851 * @return string access key value
2853 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2856 $key = new stdClass();
2857 $key->script
= $script;
2858 $key->userid
= $userid;
2859 $key->instance
= $instance;
2860 $key->iprestriction
= $iprestriction;
2861 $key->validuntil
= $validuntil;
2862 $key->timecreated
= time();
2864 $key->value
= md5($userid.'_'.time().random_string(40)); // something long and unique
2865 while ($DB->record_exists('user_private_key', array('value'=>$key->value
))) {
2867 $key->value
= md5($userid.'_'.time().random_string(40));
2869 $DB->insert_record('user_private_key', $key);
2874 * Delete the user's new private user access keys for a particular script.
2877 * @param string $script unique target identifier
2878 * @param int $userid
2881 function delete_user_key($script,$userid) {
2883 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
2887 * Gets a private user access key (and creates one if one doesn't exist).
2890 * @param string $script unique target identifier
2891 * @param int $userid
2892 * @param int $instance optional instance id
2893 * @param string $iprestriction optional ip restricted access
2894 * @param timestamp $validuntil key valid only until given data
2895 * @return string access key value
2897 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2900 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
2901 'instance'=>$instance, 'iprestriction'=>$iprestriction,
2902 'validuntil'=>$validuntil))) {
2905 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
2911 * Modify the user table by setting the currently logged in user's
2912 * last login to now.
2916 * @return bool Always returns true
2918 function update_user_login_times() {
2921 $user = new stdClass();
2922 $USER->lastlogin
= $user->lastlogin
= $USER->currentlogin
;
2923 $USER->currentlogin
= $user->lastaccess
= $user->currentlogin
= time();
2925 $user->id
= $USER->id
;
2927 $DB->update_record('user', $user);
2932 * Determines if a user has completed setting up their account.
2934 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
2937 function user_not_fully_set_up($user) {
2938 if (isguestuser($user)) {
2941 return (empty($user->firstname
) or empty($user->lastname
) or empty($user->email
) or over_bounce_threshold($user));
2945 * Check whether the user has exceeded the bounce threshold
2949 * @param user $user A {@link $USER} object
2950 * @return bool true=>User has exceeded bounce threshold
2952 function over_bounce_threshold($user) {
2955 if (empty($CFG->handlebounces
)) {
2959 if (empty($user->id
)) { /// No real (DB) user, nothing to do here.
2963 // set sensible defaults
2964 if (empty($CFG->minbounces
)) {
2965 $CFG->minbounces
= 10;
2967 if (empty($CFG->bounceratio
)) {
2968 $CFG->bounceratio
= .20;
2972 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id
, 'name'=>'email_bounce_count'))) {
2973 $bouncecount = $bounce->value
;
2975 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id
, 'name'=>'email_send_count'))) {
2976 $sendcount = $send->value
;
2978 return ($bouncecount >= $CFG->minbounces
&& $bouncecount/$sendcount >= $CFG->bounceratio
);
2982 * Used to increment or reset email sent count
2985 * @param user $user object containing an id
2986 * @param bool $reset will reset the count to 0
2989 function set_send_count($user,$reset=false) {
2992 if (empty($user->id
)) { /// No real (DB) user, nothing to do here.
2996 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id
, 'name'=>'email_send_count'))) {
2997 $pref->value
= (!empty($reset)) ?
0 : $pref->value+
1;
2998 $DB->update_record('user_preferences', $pref);
3000 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3002 $pref = new stdClass();
3003 $pref->name
= 'email_send_count';
3005 $pref->userid
= $user->id
;
3006 $DB->insert_record('user_preferences', $pref, false);
3011 * Increment or reset user's email bounce count
3014 * @param user $user object containing an id
3015 * @param bool $reset will reset the count to 0
3017 function set_bounce_count($user,$reset=false) {
3020 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id
, 'name'=>'email_bounce_count'))) {
3021 $pref->value
= (!empty($reset)) ?
0 : $pref->value+
1;
3022 $DB->update_record('user_preferences', $pref);
3024 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3026 $pref = new stdClass();
3027 $pref->name
= 'email_bounce_count';
3029 $pref->userid
= $user->id
;
3030 $DB->insert_record('user_preferences', $pref, false);
3035 * Keeps track of login attempts
3039 function update_login_count() {
3044 if (empty($SESSION->logincount
)) {
3045 $SESSION->logincount
= 1;
3047 $SESSION->logincount++
;
3050 if ($SESSION->logincount
> $max_logins) {
3051 unset($SESSION->wantsurl
);
3052 print_error('errortoomanylogins');
3057 * Resets login attempts
3061 function reset_login_count() {
3064 $SESSION->logincount
= 0;
3068 * Determines if the currently logged in user is in editing mode.
3069 * Note: originally this function had $userid parameter - it was not usable anyway
3071 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3072 * @todo Deprecated function remove when ready
3075 * @uses DEBUG_DEVELOPER
3078 function isediting() {
3080 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER
);
3081 return $PAGE->user_is_editing();
3085 * Determines if the logged in user is currently moving an activity
3088 * @param int $courseid The id of the course being tested
3091 function ismoving($courseid) {
3094 if (!empty($USER->activitycopy
)) {
3095 return ($USER->activitycopycourse
== $courseid);
3101 * Returns a persons full name
3103 * Given an object containing firstname and lastname
3104 * values, this function returns a string with the
3105 * full name of the person.
3106 * The result may depend on system settings
3107 * or language. 'override' will force both names
3108 * to be used even if system settings specify one.
3112 * @param object $user A {@link $USER} object to get full name of
3113 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3116 function fullname($user, $override=false) {
3117 global $CFG, $SESSION;
3119 if (!isset($user->firstname
) and !isset($user->lastname
)) {
3124 if (!empty($CFG->forcefirstname
)) {
3125 $user->firstname
= $CFG->forcefirstname
;
3127 if (!empty($CFG->forcelastname
)) {
3128 $user->lastname
= $CFG->forcelastname
;
3132 if (!empty($SESSION->fullnamedisplay
)) {
3133 $CFG->fullnamedisplay
= $SESSION->fullnamedisplay
;
3136 if (!isset($CFG->fullnamedisplay
) or $CFG->fullnamedisplay
=== 'firstname lastname') {
3137 return $user->firstname
.' '. $user->lastname
;
3139 } else if ($CFG->fullnamedisplay
== 'lastname firstname') {
3140 return $user->lastname
.' '. $user->firstname
;
3142 } else if ($CFG->fullnamedisplay
== 'firstname') {
3144 return get_string('fullnamedisplay', '', $user);
3146 return $user->firstname
;
3150 return get_string('fullnamedisplay', '', $user);
3154 * Returns whether a given authentication plugin exists.
3157 * @param string $auth Form of authentication to check for. Defaults to the
3158 * global setting in {@link $CFG}.
3159 * @return boolean Whether the plugin is available.
3161 function exists_auth_plugin($auth) {
3164 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3165 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3171 * Checks if a given plugin is in the list of enabled authentication plugins.
3173 * @param string $auth Authentication plugin.
3174 * @return boolean Whether the plugin is enabled.
3176 function is_enabled_auth($auth) {
3181 $enabled = get_enabled_auth_plugins();
3183 return in_array($auth, $enabled);
3187 * Returns an authentication plugin instance.
3190 * @param string $auth name of authentication plugin
3191 * @return auth_plugin_base An instance of the required authentication plugin.
3193 function get_auth_plugin($auth) {
3196 // check the plugin exists first
3197 if (! exists_auth_plugin($auth)) {
3198 print_error('authpluginnotfound', 'debug', '', $auth);
3201 // return auth plugin instance
3202 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3203 $class = "auth_plugin_$auth";
3208 * Returns array of active auth plugins.
3210 * @param bool $fix fix $CFG->auth if needed
3213 function get_enabled_auth_plugins($fix=false) {
3216 $default = array('manual', 'nologin');
3218 if (empty($CFG->auth
)) {
3221 $auths = explode(',', $CFG->auth
);
3225 $auths = array_unique($auths);
3226 foreach($auths as $k=>$authname) {
3227 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3231 $newconfig = implode(',', $auths);
3232 if (!isset($CFG->auth
) or $newconfig != $CFG->auth
) {
3233 set_config('auth', $newconfig);
3237 return (array_merge($default, $auths));
3241 * Returns true if an internal authentication method is being used.
3242 * if method not specified then, global default is assumed
3244 * @param string $auth Form of authentication required
3247 function is_internal_auth($auth) {
3248 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3249 return $authplugin->is_internal();
3253 * Returns true if the user is a 'restored' one
3255 * Used in the login process to inform the user
3256 * and allow him/her to reset the password
3260 * @param string $username username to be checked
3263 function is_restored_user($username) {
3266 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id
, 'password'=>'restored'));
3270 * Returns an array of user fields
3272 * @return array User field/column names
3274 function get_user_fieldnames() {
3277 $fieldarray = $DB->get_columns('user');
3278 unset($fieldarray['id']);
3279 $fieldarray = array_keys($fieldarray);
3285 * Creates a bare-bones user record
3287 * @todo Outline auth types and provide code example
3289 * @param string $username New user's username to add to record
3290 * @param string $password New user's password to add to record
3291 * @param string $auth Form of authentication required
3292 * @return stdClass A complete user object
3294 function create_user_record($username, $password, $auth = 'manual') {
3297 //just in case check text case
3298 $username = trim(moodle_strtolower($username));
3300 $authplugin = get_auth_plugin($auth);
3302 $newuser = new stdClass();
3304 if ($newinfo = $authplugin->get_userinfo($username)) {
3305 $newinfo = truncate_userinfo($newinfo);
3306 foreach ($newinfo as $key => $value){
3307 $newuser->$key = $value;
3311 if (!empty($newuser->email
)) {
3312 if (email_is_not_allowed($newuser->email
)) {
3313 unset($newuser->email
);
3317 if (!isset($newuser->city
)) {
3318 $newuser->city
= '';
3321 $newuser->auth
= $auth;
3322 $newuser->username
= $username;
3325 // user CFG lang for user if $newuser->lang is empty
3326 // or $user->lang is not an installed language
3327 if (empty($newuser->lang
) ||
!get_string_manager()->translation_exists($newuser->lang
)) {
3328 $newuser->lang
= $CFG->lang
;
3330 $newuser->confirmed
= 1;
3331 $newuser->lastip
= getremoteaddr();
3332 $newuser->timecreated
= time();
3333 $newuser->timemodified
= $newuser->timecreated
;
3334 $newuser->mnethostid
= $CFG->mnet_localhost_id
;
3336 $newuser->id
= $DB->insert_record('user', $newuser);
3337 $user = get_complete_user_data('id', $newuser->id
);
3338 if (!empty($CFG->{'auth_'.$newuser->auth
.'_forcechangepassword'})){
3339 set_user_preference('auth_forcepasswordchange', 1, $user);
3341 update_internal_user_password($user, $password);
3343 // fetch full user record for the event, the complete user data contains too much info
3344 // and we want to be consistent with other places that trigger this event
3345 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id
)));
3351 * Will update a local user record from an external source.
3352 * (MNET users can not be updated using this method!)
3354 * @param string $username user's username to update the record
3355 * @return stdClass A complete user object
3357 function update_user_record($username) {
3360 $username = trim(moodle_strtolower($username)); /// just in case check text case
3362 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id
), '*', MUST_EXIST
);
3364 $userauth = get_auth_plugin($oldinfo->auth
);
3366 if ($newinfo = $userauth->get_userinfo($username)) {
3367 $newinfo = truncate_userinfo($newinfo);
3368 foreach ($newinfo as $key => $value){
3369 $key = strtolower($key);
3370 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3371 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3372 // unknown or must not be changed
3375 $confval = $userauth->config
->{'field_updatelocal_' . $key};
3376 $lockval = $userauth->config
->{'field_lock_' . $key};
3377 if (empty($confval) ||
empty($lockval)) {
3380 if ($confval === 'onlogin') {
3381 // MDL-4207 Don't overwrite modified user profile values with
3382 // empty LDAP values when 'unlocked if empty' is set. The purpose
3383 // of the setting 'unlocked if empty' is to allow the user to fill
3384 // in a value for the selected field _if LDAP is giving
3385 // nothing_ for this field. Thus it makes sense to let this value
3386 // stand in until LDAP is giving a value for this field.
3387 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3388 if ((string)$oldinfo->$key !== (string)$value) {
3389 $newuser[$key] = (string)$value;
3395 $newuser['id'] = $oldinfo->id
;
3396 $newuser['timemodified'] = time();
3397 $DB->update_record('user', $newuser);
3398 // fetch full user record for the event, the complete user data contains too much info
3399 // and we want to be consistent with other places that trigger this event
3400 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id
)));
3404 return get_complete_user_data('id', $oldinfo->id
);
3408 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3409 * which may have large fields
3411 * @todo Add vartype handling to ensure $info is an array
3413 * @param array $info Array of user properties to truncate if needed
3414 * @return array The now truncated information that was passed in
3416 function truncate_userinfo($info) {
3417 // define the limits
3427 'institution' => 40,
3435 $textlib = textlib_get_instance();
3436 // apply where needed
3437 foreach (array_keys($info) as $key) {
3438 if (!empty($limit[$key])) {
3439 $info[$key] = trim($textlib->substr($info[$key],0, $limit[$key]));
3447 * Marks user deleted in internal user database and notifies the auth plugin.
3448 * Also unenrols user from all roles and does other cleanup.
3450 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3452 * @param stdClass $user full user object before delete
3453 * @return boolean always true
3455 function delete_user($user) {
3457 require_once($CFG->libdir
.'/grouplib.php');
3458 require_once($CFG->libdir
.'/gradelib.php');
3459 require_once($CFG->dirroot
.'/message/lib.php');
3460 require_once($CFG->dirroot
.'/tag/lib.php');
3462 // delete all grades - backup is kept in grade_grades_history table
3463 grade_user_delete($user->id
);
3465 //move unread messages from this user to read
3466 message_move_userfrom_unread2read($user->id
);
3468 // TODO: remove from cohorts using standard API here
3471 tag_set('user', $user->id
, array());
3473 // unconditionally unenrol from all courses
3474 enrol_user_delete($user);
3476 // unenrol from all roles in all contexts
3477 role_unassign_all(array('userid'=>$user->id
)); // this might be slow but it is really needed - modules might do some extra cleanup!
3479 //now do a brute force cleanup
3481 // remove from all cohorts
3482 $DB->delete_records('cohort_members', array('userid'=>$user->id
));
3484 // remove from all groups
3485 $DB->delete_records('groups_members', array('userid'=>$user->id
));
3487 // brute force unenrol from all courses
3488 $DB->delete_records('user_enrolments', array('userid'=>$user->id
));
3490 // purge user preferences
3491 $DB->delete_records('user_preferences', array('userid'=>$user->id
));
3493 // purge user extra profile info
3494 $DB->delete_records('user_info_data', array('userid'=>$user->id
));
3496 // last course access not necessary either
3497 $DB->delete_records('user_lastaccess', array('userid'=>$user->id
));
3499 // remove all user tokens
3500 $DB->delete_records('external_tokens', array('userid'=>$user->id
));
3502 // unauthorise the user for all services
3503 $DB->delete_records('external_services_users', array('userid'=>$user->id
));
3505 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
3506 delete_context(CONTEXT_USER
, $user->id
);
3508 // workaround for bulk deletes of users with the same email address
3509 $delname = "$user->email.".time();
3510 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
3514 // mark internal user record as "deleted"
3515 $updateuser = new stdClass();
3516 $updateuser->id
= $user->id
;
3517 $updateuser->deleted
= 1;
3518 $updateuser->username
= $delname; // Remember it just in case
3519 $updateuser->email
= md5($user->username
);// Store hash of username, useful importing/restoring users
3520 $updateuser->idnumber
= ''; // Clear this field to free it up
3521 $updateuser->timemodified
= time();
3523 $DB->update_record('user', $updateuser);
3524 // Add this action to log
3525 add_to_log(SITEID
, 'user', 'delete', "view.php?id=$user->id", $user->firstname
.' '.$user->lastname
);
3528 // notify auth plugin - do not block the delete even when plugin fails
3529 $authplugin = get_auth_plugin($user->auth
);
3530 $authplugin->user_delete($user);
3532 // any plugin that needs to cleanup should register this event
3533 events_trigger('user_deleted', $user);
3539 * Retrieve the guest user object
3543 * @return user A {@link $USER} object
3545 function guest_user() {
3548 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest
))) {
3549 $newuser->confirmed
= 1;
3550 $newuser->lang
= $CFG->lang
;
3551 $newuser->lastip
= getremoteaddr();
3558 * Authenticates a user against the chosen authentication mechanism
3560 * Given a username and password, this function looks them
3561 * up using the currently selected authentication mechanism,
3562 * and if the authentication is successful, it returns a
3563 * valid $user object from the 'user' table.
3565 * Uses auth_ functions from the currently active auth module
3567 * After authenticate_user_login() returns success, you will need to
3568 * log that the user has logged in, and call complete_user_login() to set
3571 * Note: this function works only with non-mnet accounts!
3573 * @param string $username User's username
3574 * @param string $password User's password
3575 * @return user|flase A {@link $USER} object or false if error
3577 function authenticate_user_login($username, $password) {
3580 $authsenabled = get_enabled_auth_plugins();
3582 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id
)) {
3583 $auth = empty($user->auth
) ?
'manual' : $user->auth
; // use manual if auth not set
3584 if (!empty($user->suspended
)) {
3585 add_to_log(SITEID
, 'login', 'error', 'index.php', $username);
3586 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3589 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3590 add_to_log(SITEID
, 'login', 'error', 'index.php', $username);
3591 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3594 $auths = array($auth);
3597 // check if there's a deleted record (cheaply)
3598 if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
3599 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3603 // User does not exist
3604 $auths = $authsenabled;
3605 $user = new stdClass();
3609 foreach ($auths as $auth) {
3610 $authplugin = get_auth_plugin($auth);
3612 // on auth fail fall through to the next plugin
3613 if (!$authplugin->user_login($username, $password)) {
3617 // successful authentication
3618 if ($user->id
) { // User already exists in database
3619 if (empty($user->auth
)) { // For some reason auth isn't set yet
3620 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
3621 $user->auth
= $auth;
3623 if (empty($user->firstaccess
)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
3624 $DB->set_field('user','firstaccess', $user->timemodified
, array('id' => $user->id
));
3625 $user->firstaccess
= $user->timemodified
;
3628 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3630 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
3631 $user = update_user_record($username);
3634 // if user not found and user creation is not disabled, create it
3635 if (empty($CFG->authpreventaccountcreation
)) {
3636 $user = create_user_record($username, $password, $auth);
3642 $authplugin->sync_roles($user);
3644 foreach ($authsenabled as $hau) {
3645 $hauth = get_auth_plugin($hau);
3646 $hauth->user_authenticated_hook($user, $username, $password);
3649 if (empty($user->id
)) {
3653 if (!empty($user->suspended
)) {
3654 // just in case some auth plugin suspended account
3655 add_to_log(SITEID
, 'login', 'error', 'index.php', $username);
3656 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3663 // failed if all the plugins have failed
3664 add_to_log(SITEID
, 'login', 'error', 'index.php', $username);
3665 if (debugging('', DEBUG_ALL
)) {
3666 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3672 * Call to complete the user login process after authenticate_user_login()
3673 * has succeeded. It will setup the $USER variable and other required bits
3677 * - It will NOT log anything -- up to the caller to decide what to log.
3679 * @param object $user
3680 * @param bool $setcookie
3681 * @return object A {@link $USER} object - BC only, do not use
3683 function complete_user_login($user, $setcookie=true) {
3686 // regenerate session id and delete old session,
3687 // this helps prevent session fixation attacks from the same domain
3688 session_regenerate_id(true);
3690 // check enrolments, load caps and setup $USER object
3691 session_set_user($user);
3693 // reload preferences from DB
3694 unset($user->preference
);
3695 check_user_preferences_loaded($user);
3697 // update login times
3698 update_user_login_times();
3700 // extra session prefs init
3701 set_login_session_preferences();
3703 if (isguestuser()) {
3704 // no need to continue when user is THE guest
3709 if (empty($CFG->nolastloggedin
)) {
3710 set_moodle_cookie($USER->username
);
3712 // do not store last logged in user in cookie
3713 // auth plugins can temporarily override this from loginpage_hook()
3714 // do not save $CFG->nolastloggedin in database!
3715 set_moodle_cookie('');
3719 /// Select password change url
3720 $userauth = get_auth_plugin($USER->auth
);
3722 /// check whether the user should be changing password
3723 if (get_user_preferences('auth_forcepasswordchange', false)){
3724 if ($userauth->can_change_password()) {
3725 if ($changeurl = $userauth->change_password_url()) {
3726 redirect($changeurl);
3728 redirect($CFG->httpswwwroot
.'/login/change_password.php');
3731 print_error('nopasswordchangeforced', 'auth');
3738 * Compare password against hash stored in internal user table.
3739 * If necessary it also updates the stored hash to new format.
3741 * @param stdClass $user (password property may be updated)
3742 * @param string $password plain text password
3743 * @return bool is password valid?
3745 function validate_internal_user_password($user, $password) {
3748 if (!isset($CFG->passwordsaltmain
)) {
3749 $CFG->passwordsaltmain
= '';
3754 if ($user->password
=== 'not cached') {
3755 // internal password is not used at all, it can not validate
3757 } else if ($user->password
=== md5($password.$CFG->passwordsaltmain
)
3758 or $user->password
=== md5($password)
3759 or $user->password
=== md5(addslashes($password).$CFG->passwordsaltmain
)
3760 or $user->password
=== md5(addslashes($password))) {
3761 // note: we are intentionally using the addslashes() here because we
3762 // need to accept old password hashes of passwords with magic quotes
3766 for ($i=1; $i<=20; $i++
) { //20 alternative salts should be enough, right?
3767 $alt = 'passwordsaltalt'.$i;
3768 if (!empty($CFG->$alt)) {
3769 if ($user->password
=== md5($password.$CFG->$alt) or $user->password
=== md5(addslashes($password).$CFG->$alt)) {
3778 // force update of password hash using latest main password salt and encoding if needed
3779 update_internal_user_password($user, $password);
3786 * Calculate hashed value from password using current hash mechanism.
3788 * @param string $password
3789 * @return string password hash
3791 function hash_internal_user_password($password) {
3794 if (isset($CFG->passwordsaltmain
)) {
3795 return md5($password.$CFG->passwordsaltmain
);
3797 return md5($password);
3802 * Update password hash in user object.
3804 * @param stdClass $user (password property may be updated)
3805 * @param string $password plain text password
3806 * @return bool always returns true
3808 function update_internal_user_password($user, $password) {
3811 $authplugin = get_auth_plugin($user->auth
);
3812 if ($authplugin->prevent_local_passwords()) {
3813 $hashedpassword = 'not cached';
3815 $hashedpassword = hash_internal_user_password($password);
3818 if ($user->password
!== $hashedpassword) {
3819 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id
));
3820 $user->password
= $hashedpassword;
3827 * Get a complete user record, which includes all the info
3828 * in the user record.
3830 * Intended for setting as $USER session variable
3832 * @param string $field The user field to be checked for a given value.
3833 * @param string $value The value to match for $field.
3834 * @param int $mnethostid
3835 * @return mixed False, or A {@link $USER} object.
3837 function get_complete_user_data($field, $value, $mnethostid = null) {
3840 if (!$field ||
!$value) {
3844 /// Build the WHERE clause for an SQL query
3845 $params = array('fieldval'=>$value);
3846 $constraints = "$field = :fieldval AND deleted <> 1";
3848 // If we are loading user data based on anything other than id,
3849 // we must also restrict our search based on mnet host.
3850 if ($field != 'id') {
3851 if (empty($mnethostid)) {
3852 // if empty, we restrict to local users
3853 $mnethostid = $CFG->mnet_localhost_id
;
3856 if (!empty($mnethostid)) {
3857 $params['mnethostid'] = $mnethostid;
3858 $constraints .= " AND mnethostid = :mnethostid";
3861 /// Get all the basic user data
3863 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
3867 /// Get various settings and preferences
3869 // preload preference cache
3870 check_user_preferences_loaded($user);
3872 // load course enrolment related stuff
3873 $user->lastcourseaccess
= array(); // during last session
3874 $user->currentcourseaccess
= array(); // during current session
3875 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id
))) {
3876 foreach ($lastaccesses as $lastaccess) {
3877 $user->lastcourseaccess
[$lastaccess->courseid
] = $lastaccess->timeaccess
;
3881 $sql = "SELECT g.id, g.courseid
3882 FROM {groups} g, {groups_members} gm
3883 WHERE gm.groupid=g.id AND gm.userid=?";
3885 // this is a special hack to speedup calendar display
3886 $user->groupmember
= array();
3887 if (!isguestuser($user)) {
3888 if ($groups = $DB->get_records_sql($sql, array($user->id
))) {
3889 foreach ($groups as $group) {
3890 if (!array_key_exists($group->courseid
, $user->groupmember
)) {
3891 $user->groupmember
[$group->courseid
] = array();
3893 $user->groupmember
[$group->courseid
][$group->id
] = $group->id
;
3898 /// Add the custom profile fields to the user record
3899 $user->profile
= array();
3900 if (!isguestuser($user)) {
3901 require_once($CFG->dirroot
.'/user/profile/lib.php');
3902 profile_load_custom_fields($user);
3905 /// Rewrite some variables if necessary
3906 if (!empty($user->description
)) {
3907 $user->description
= true; // No need to cart all of it around
3909 if (isguestuser($user)) {
3910 $user->lang
= $CFG->lang
; // Guest language always same as site
3911 $user->firstname
= get_string('guestuser'); // Name always in current language
3912 $user->lastname
= ' ';
3919 * Validate a password against the configured password policy
3922 * @param string $password the password to be checked against the password policy
3923 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3924 * @return bool true if the password is valid according to the policy. false otherwise.
3926 function check_password_policy($password, &$errmsg) {
3929 if (empty($CFG->passwordpolicy
)) {
3933 $textlib = textlib_get_instance();
3935 if ($textlib->strlen($password) < $CFG->minpasswordlength
) {
3936 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength
) .'</div>';
3939 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits
) {
3940 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits
) .'</div>';
3943 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower
) {
3944 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower
) .'</div>';
3947 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper
) {
3948 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper
) .'</div>';
3951 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum
) {
3952 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum
) .'</div>';
3954 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars
)) {
3955 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars
) .'</div>';
3958 if ($errmsg == '') {
3967 * When logging in, this function is run to set certain preferences
3968 * for the current SESSION
3973 function set_login_session_preferences() {
3974 global $SESSION, $CFG;
3976 $SESSION->justloggedin
= true;
3978 unset($SESSION->lang
);
3983 * Delete a course, including all related data from the database,
3984 * and any associated files.
3988 * @param mixed $courseorid The id of the course or course object to delete.
3989 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3990 * @return bool true if all the removals succeeded. false if there were any failures. If this
3991 * method returns false, some of the removals will probably have succeeded, and others
3992 * failed, but you have no way of knowing which.
3994 function delete_course($courseorid, $showfeedback = true) {
3997 if (is_object($courseorid)) {
3998 $courseid = $courseorid->id
;
3999 $course = $courseorid;
4001 $courseid = $courseorid;
4002 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
4006 $context = get_context_instance(CONTEXT_COURSE
, $courseid);
4008 // frontpage course can not be deleted!!
4009 if ($courseid == SITEID
) {
4013 // make the course completely empty
4014 remove_course_contents($courseid, $showfeedback);
4016 // delete the course and related context instance
4017 delete_context(CONTEXT_COURSE
, $courseid);
4018 $DB->delete_records("course", array("id"=>$courseid));
4021 $course->context
= $context; // you can not fetch context in the event because it was already deleted
4022 events_trigger('course_deleted', $course);
4028 * Clear a course out completely, deleting all content
4029 * but don't delete the course itself.
4030 * This function does not verify any permissions.
4032 * Please note this function also deletes all user enrolments,
4033 * enrolment instances and role assignments.
4035 * @param int $courseid The id of the course that is being deleted
4036 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4037 * @return bool true if all the removals succeeded. false if there were any failures. If this
4038 * method returns false, some of the removals will probably have succeeded, and others
4039 * failed, but you have no way of knowing which.
4041 function remove_course_contents($courseid, $showfeedback = true) {
4042 global $CFG, $DB, $OUTPUT;
4043 require_once($CFG->libdir
.'/completionlib.php');
4044 require_once($CFG->libdir
.'/questionlib.php');
4045 require_once($CFG->libdir
.'/gradelib.php');
4046 require_once($CFG->dirroot
.'/group/lib.php');
4047 require_once($CFG->dirroot
.'/tag/coursetagslib.php');
4049 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST
);
4050 $context = get_context_instance(CONTEXT_COURSE
, $courseid, MUST_EXIST
);
4052 $strdeleted = get_string('deleted');
4054 // Delete course completion information,
4055 // this has to be done before grades and enrols
4056 $cc = new completion_info($course);
4057 $cc->clear_criteria();
4059 // remove roles and enrolments
4060 role_unassign_all(array('contextid'=>$context->id
), true);
4061 enrol_course_delete($course);
4063 // Clean up course formats (iterate through all formats in the even the course format was ever changed)
4064 $formats = get_plugin_list('format');
4065 foreach ($formats as $format=>$formatdir) {
4066 $formatdelete = 'format_'.$format.'_delete_course';
4067 $formatlib = "$formatdir/lib.php";
4068 if (file_exists($formatlib)) {
4069 include_once($formatlib);
4070 if (function_exists($formatdelete)) {
4071 if ($showfeedback) {
4072 echo $OUTPUT->notification($strdeleted.' '.$format);
4074 $formatdelete($course->id
);
4079 // Remove all data from gradebook - this needs to be done before course modules
4080 // because while deleting this information, the system may need to reference
4081 // the course modules that own the grades.
4082 remove_course_grades($courseid, $showfeedback);
4083 remove_grade_letters($context, $showfeedback);
4085 // Remove all data from availability and completion tables that is associated
4086 // with course-modules belonging to this course. Note this is done even if the
4087 // features are not enabled now, in case they were enabled previously
4088 $DB->delete_records_select('course_modules_completion',
4089 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4091 $DB->delete_records_select('course_modules_availability',
4092 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4095 // Delete course blocks - they may depend on modules so delete them first
4096 blocks_delete_all_for_context($context->id
);
4098 // Delete every instance of every module
4099 if ($allmods = $DB->get_records('modules') ) {
4100 foreach ($allmods as $mod) {
4101 $modname = $mod->name
;
4102 $modfile = $CFG->dirroot
.'/mod/'. $modname .'/lib.php';
4103 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4104 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4106 if (file_exists($modfile)) {
4107 include_once($modfile);
4108 if (function_exists($moddelete)) {
4109 if ($instances = $DB->get_records($modname, array('course'=>$course->id
))) {
4110 foreach ($instances as $instance) {
4111 if ($cm = get_coursemodule_from_instance($modname, $instance->id
, $course->id
)) {
4112 /// Delete activity context questions and question categories
4113 question_delete_activity($cm, $showfeedback);
4115 if ($moddelete($instance->id
)) {
4119 echo $OUTPUT->notification('Could not delete '. $modname .' instance '. $instance->id
.' ('. format_string($instance->name
) .')');
4122 // delete cm and its context in correct order
4123 delete_context(CONTEXT_MODULE
, $cm->id
); // some callbacks may try to fetch context, better delete first
4124 $DB->delete_records('course_modules', array('id'=>$cm->id
));
4129 //note: we should probably delete these anyway
4130 echo $OUTPUT->notification('Function '.$moddelete.'() doesn\'t exist!');
4133 if (function_exists($moddeletecourse)) {
4134 $moddeletecourse($course, $showfeedback);
4137 if ($showfeedback) {
4138 echo $OUTPUT->notification($strdeleted .' '. $count .' x '. $modname);
4143 // Delete any groups, removing members and grouping/course links first.
4144 groups_delete_groupings($course->id
, $showfeedback);
4145 groups_delete_groups($course->id
, $showfeedback);
4147 // Delete questions and question categories
4148 question_delete_course($course, $showfeedback);
4150 // Delete course tags
4151 coursetag_delete_course_tags($course->id
, $showfeedback);
4153 // Delete legacy files (just in case some files are still left there after conversion to new file api)
4154 fulldelete($CFG->dataroot
.'/'.$course->id
);
4156 // cleanup course record - remove links to delted stuff
4157 $oldcourse = new stdClass();
4158 $oldcourse->id
= $course->id
;
4159 $oldcourse->summary
= '';
4160 $oldcourse->modinfo
= NULL;
4161 $oldcourse->legacyfiles
= 0;
4162 $oldcourse->defaultgroupingid
= 0;
4163 $oldcourse->enablecompletion
= 0;
4164 $DB->update_record('course', $oldcourse);
4166 // Delete all related records in other tables that may have a courseid
4167 // This array stores the tables that need to be cleared, as
4168 // table_name => column_name that contains the course id.
4169 $tablestoclear = array(
4170 'event' => 'courseid', // Delete events
4171 'log' => 'course', // Delete logs
4172 'course_sections' => 'course', // Delete any course stuff
4173 'course_modules' => 'course',
4174 'course_display' => 'course',
4175 'backup_courses' => 'courseid', // Delete scheduled backup stuff
4176 'user_lastaccess' => 'courseid',
4177 'backup_log' => 'courseid'
4179 foreach ($tablestoclear as $table => $col) {
4180 $DB->delete_records($table, array($col=>$course->id
));
4183 // Delete all remaining stuff linked to context,
4184 // such as remaining roles, files, comments, etc.
4185 // Keep the context record for now.
4186 delete_context(CONTEXT_COURSE
, $course->id
, false);
4189 $course->context
= $context; // you can not access context in cron event later after course is deleted
4190 events_trigger('course_content_removed', $course);
4196 * Change dates in module - used from course reset.
4200 * @param string $modname forum, assignment, etc
4201 * @param array $fields array of date fields from mod table
4202 * @param int $timeshift time difference
4203 * @param int $courseid
4204 * @return bool success
4206 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4208 include_once($CFG->dirroot
.'/mod/'.$modname.'/lib.php');
4211 foreach ($fields as $field) {
4212 $updatesql = "UPDATE {".$modname."}
4213 SET $field = $field + ?
4214 WHERE course=? AND $field<>0 AND $field<>0";
4215 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4218 $refreshfunction = $modname.'_refresh_events';
4219 if (function_exists($refreshfunction)) {
4220 $refreshfunction($courseid);
4227 * This function will empty a course of user data.
4228 * It will retain the activities and the structure of the course.
4230 * @param object $data an object containing all the settings including courseid (without magic quotes)
4231 * @return array status array of array component, item, error
4233 function reset_course_userdata($data) {
4234 global $CFG, $USER, $DB;
4235 require_once($CFG->libdir
.'/gradelib.php');
4236 require_once($CFG->libdir
.'/completionlib.php');
4237 require_once($CFG->dirroot
.'/group/lib.php');
4239 $data->courseid
= $data->id
;
4240 $context = get_context_instance(CONTEXT_COURSE
, $data->courseid
);
4242 // calculate the time shift of dates
4243 if (!empty($data->reset_start_date
)) {
4244 // time part of course startdate should be zero
4245 $data->timeshift
= $data->reset_start_date
- usergetmidnight($data->reset_start_date_old
);
4247 $data->timeshift
= 0;
4250 // result array: component, item, error
4253 // start the resetting
4254 $componentstr = get_string('general');
4256 // move the course start time
4257 if (!empty($data->reset_start_date
) and $data->timeshift
) {
4258 // change course start data
4259 $DB->set_field('course', 'startdate', $data->reset_start_date
, array('id'=>$data->courseid
));
4260 // update all course and group events - do not move activity events
4261 $updatesql = "UPDATE {event}
4262 SET timestart = timestart + ?
4263 WHERE courseid=? AND instance=0";
4264 $DB->execute($updatesql, array($data->timeshift
, $data->courseid
));
4266 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4269 if (!empty($data->reset_logs
)) {
4270 $DB->delete_records('log', array('course'=>$data->courseid
));
4271 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4274 if (!empty($data->reset_events
)) {
4275 $DB->delete_records('event', array('courseid'=>$data->courseid
));
4276 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4279 if (!empty($data->reset_notes
)) {
4280 require_once($CFG->dirroot
.'/notes/lib.php');
4281 note_delete_all($data->courseid
);
4282 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4285 if (!empty($data->delete_blog_associations
)) {
4286 require_once($CFG->dirroot
.'/blog/lib.php');
4287 blog_remove_associations_for_course($data->courseid
);
4288 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4291 if (!empty($data->reset_course_completion
)) {
4292 // Delete course completion information
4293 $course = $DB->get_record('course', array('id'=>$data->courseid
));
4294 $cc = new completion_info($course);
4295 $cc->delete_course_completion_data();
4296 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecoursecompletiondata', 'completion'), 'error'=>false);
4299 $componentstr = get_string('roles');
4301 if (!empty($data->reset_roles_overrides
)) {
4302 $children = get_child_contexts($context);
4303 foreach ($children as $child) {
4304 $DB->delete_records('role_capabilities', array('contextid'=>$child->id
));
4306 $DB->delete_records('role_capabilities', array('contextid'=>$context->id
));
4307 //force refresh for logged in users
4308 mark_context_dirty($context->path
);
4309 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4312 if (!empty($data->reset_roles_local
)) {
4313 $children = get_child_contexts($context);
4314 foreach ($children as $child) {
4315 role_unassign_all(array('contextid'=>$child->id
));
4317 //force refresh for logged in users
4318 mark_context_dirty($context->path
);
4319 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4322 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4323 $data->unenrolled
= array();
4324 if (!empty($data->unenrol_users
)) {
4325 $plugins = enrol_get_plugins(true);
4326 $instances = enrol_get_instances($data->courseid
, true);
4327 foreach ($instances as $key=>$instance) {
4328 if (!isset($plugins[$instance->enrol
])) {
4329 unset($instances[$key]);
4332 if (!$plugins[$instance->enrol
]->allow_unenrol($instance)) {
4333 unset($instances[$key]);
4337 $sqlempty = $DB->sql_empty();
4338 foreach($data->unenrol_users
as $withroleid) {
4339 $sql = "SELECT DISTINCT ue.userid, ue.enrolid
4340 FROM {user_enrolments} ue
4341 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4342 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4343 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4344 $params = array('courseid'=>$data->courseid
, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE
);
4346 $rs = $DB->get_recordset_sql($sql, $params);
4347 foreach ($rs as $ue) {
4348 if (!isset($instances[$ue->enrolid
])) {
4351 $plugins[$instances[$ue->enrolid
]->enrol
]->unenrol_user($instances[$ue->enrolid
], $ue->userid
);
4352 $data->unenrolled
[$ue->userid
] = $ue->userid
;
4356 if (!empty($data->unenrolled
)) {
4357 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled
).')', 'error'=>false);
4361 $componentstr = get_string('groups');
4363 // remove all group members
4364 if (!empty($data->reset_groups_members
)) {
4365 groups_delete_group_members($data->courseid
);
4366 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
4369 // remove all groups
4370 if (!empty($data->reset_groups_remove
)) {
4371 groups_delete_groups($data->courseid
, false);
4372 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
4375 // remove all grouping members
4376 if (!empty($data->reset_groupings_members
)) {
4377 groups_delete_groupings_groups($data->courseid
, false);
4378 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
4381 // remove all groupings
4382 if (!empty($data->reset_groupings_remove
)) {
4383 groups_delete_groupings($data->courseid
, false);
4384 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
4387 // Look in every instance of every module for data to delete
4388 $unsupported_mods = array();
4389 if ($allmods = $DB->get_records('modules') ) {
4390 foreach ($allmods as $mod) {
4391 $modname = $mod->name
;
4392 if (!$DB->count_records($modname, array('course'=>$data->courseid
))) {
4393 continue; // skip mods with no instances
4395 $modfile = $CFG->dirroot
.'/mod/'. $modname.'/lib.php';
4396 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
4397 if (file_exists($modfile)) {
4398 include_once($modfile);
4399 if (function_exists($moddeleteuserdata)) {
4400 $modstatus = $moddeleteuserdata($data);
4401 if (is_array($modstatus)) {
4402 $status = array_merge($status, $modstatus);
4404 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
4407 $unsupported_mods[] = $mod;
4410 debugging('Missing lib.php in '.$modname.' module!');
4415 // mention unsupported mods
4416 if (!empty($unsupported_mods)) {
4417 foreach($unsupported_mods as $mod) {
4418 $status[] = array('component'=>get_string('modulenameplural', $mod->name
), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
4423 $componentstr = get_string('gradebook', 'grades');
4425 if (!empty($data->reset_gradebook_items
)) {
4426 remove_course_grades($data->courseid
, false);
4427 grade_grab_course_grades($data->courseid
);
4428 grade_regrade_final_grades($data->courseid
);
4429 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4431 } else if (!empty($data->reset_gradebook_grades
)) {
4432 grade_course_reset($data->courseid
);
4433 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4436 if (!empty($data->reset_comments
)) {
4437 require_once($CFG->dirroot
.'/comment/lib.php');
4438 comment
::reset_course_page_comments($context);
4445 * Generate an email processing address
4448 * @param string $modargs
4449 * @return string Returns email processing address
4451 function generate_email_processing_address($modid,$modargs) {
4454 $header = $CFG->mailprefix
. substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4455 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain
;
4461 * @todo Finish documenting this function
4464 * @param string $modargs
4465 * @param string $body Currently unused
4467 function moodle_process_email($modargs,$body) {
4470 // the first char should be an unencoded letter. We'll take this as an action
4471 switch ($modargs{0}) {
4472 case 'B': { // bounce
4473 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4474 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
4475 // check the half md5 of their email
4476 $md5check = substr(md5($user->email
),0,16);
4477 if ($md5check == substr($modargs, -16)) {
4478 set_bounce_count($user);
4480 // else maybe they've already changed it?
4484 // maybe more later?
4488 /// CORRESPONDENCE ////////////////////////////////////////////////
4491 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4494 * @param string $action 'get', 'buffer', 'close' or 'flush'
4495 * @return object|null mailer instance if 'get' used or nothing
4497 function get_mailer($action='get') {
4500 static $mailer = null;
4501 static $counter = 0;
4503 if (!isset($CFG->smtpmaxbulk
)) {
4504 $CFG->smtpmaxbulk
= 1;
4507 if ($action == 'get') {
4508 $prevkeepalive = false;
4510 if (isset($mailer) and $mailer->Mailer
== 'smtp') {
4511 if ($counter < $CFG->smtpmaxbulk
and !$mailer->IsError()) {
4514 $mailer->Priority
= 3;
4515 $mailer->CharSet
= 'UTF-8'; // our default
4516 $mailer->ContentType
= "text/plain";
4517 $mailer->Encoding
= "8bit";
4518 $mailer->From
= "root@localhost";
4519 $mailer->FromName
= "Root User";
4520 $mailer->Sender
= "";
4521 $mailer->Subject
= "";
4523 $mailer->AltBody
= "";
4524 $mailer->ConfirmReadingTo
= "";
4526 $mailer->ClearAllRecipients();
4527 $mailer->ClearReplyTos();
4528 $mailer->ClearAttachments();
4529 $mailer->ClearCustomHeaders();
4533 $prevkeepalive = $mailer->SMTPKeepAlive
;
4534 get_mailer('flush');
4537 include_once($CFG->libdir
.'/phpmailer/moodle_phpmailer.php');
4538 $mailer = new moodle_phpmailer();
4542 $mailer->Version
= 'Moodle '.$CFG->version
; // mailer version
4543 $mailer->PluginDir
= $CFG->libdir
.'/phpmailer/'; // plugin directory (eg smtp plugin)
4544 $mailer->CharSet
= 'UTF-8';
4546 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
4547 if (isset($CFG->mailnewline
) and $CFG->mailnewline
== 'CRLF') {
4548 $mailer->LE
= "\r\n";
4553 if ($CFG->smtphosts
== 'qmail') {
4554 $mailer->IsQmail(); // use Qmail system
4556 } else if (empty($CFG->smtphosts
)) {
4557 $mailer->IsMail(); // use PHP mail() = sendmail
4560 $mailer->IsSMTP(); // use SMTP directly
4561 if (!empty($CFG->debugsmtp
)) {
4562 $mailer->SMTPDebug
= true;
4564 $mailer->Host
= $CFG->smtphosts
; // specify main and backup servers
4565 $mailer->SMTPKeepAlive
= $prevkeepalive; // use previous keepalive
4567 if ($CFG->smtpuser
) { // Use SMTP authentication
4568 $mailer->SMTPAuth
= true;
4569 $mailer->Username
= $CFG->smtpuser
;
4570 $mailer->Password
= $CFG->smtppass
;
4579 // keep smtp session open after sending
4580 if ($action == 'buffer') {
4581 if (!empty($CFG->smtpmaxbulk
)) {
4582 get_mailer('flush');
4584 if ($m->Mailer
== 'smtp') {
4585 $m->SMTPKeepAlive
= true;
4591 // close smtp session, but continue buffering
4592 if ($action == 'flush') {
4593 if (isset($mailer) and $mailer->Mailer
== 'smtp') {
4594 if (!empty($mailer->SMTPDebug
)) {
4597 $mailer->SmtpClose();
4598 if (!empty($mailer->SMTPDebug
)) {
4605 // close smtp session, do not buffer anymore
4606 if ($action == 'close') {
4607 if (isset($mailer) and $mailer->Mailer
== 'smtp') {
4608 get_mailer('flush');
4609 $mailer->SMTPKeepAlive
= false;
4611 $mailer = null; // better force new instance
4617 * Send an email to a specified user
4621 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
4623 * @param stdClass $user A {@link $USER} object
4624 * @param stdClass $from A {@link $USER} object
4625 * @param string $subject plain text subject line of the email
4626 * @param string $messagetext plain text version of the message
4627 * @param string $messagehtml complete html version of the message (optional)
4628 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4629 * @param string $attachname the name of the file (extension indicates MIME)
4630 * @param bool $usetrueaddress determines whether $from email address should
4631 * be sent out. Will be overruled by user profile setting for maildisplay
4632 * @param string $replyto Email address to reply to
4633 * @param string $replytoname Name of reply to recipient
4634 * @param int $wordwrapwidth custom word wrap width, default 79
4635 * @return bool Returns true if mail was sent OK and false if there was an error.
4637 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4639 global $CFG, $FULLME;
4641 if (empty($user) ||
empty($user->email
)) {
4642 $nulluser = 'User is null or has no email';
4643 error_log($nulluser);
4645 mtrace('Error: lib/moodlelib.php email_to_user(): '.$nulluser);
4650 if (!empty($user->deleted
)) {
4651 // do not mail deleted users
4652 $userdeleted = 'User is deleted';
4653 error_log($userdeleted);
4655 mtrace('Error: lib/moodlelib.php email_to_user(): '.$userdeleted);
4660 if (!empty($CFG->noemailever
)) {
4661 // hidden setting for development sites, set in config.php if needed
4662 $noemail = 'Not sending email due to noemailever config setting';
4663 error_log($noemail);
4665 mtrace('Error: lib/moodlelib.php email_to_user(): '.$noemail);
4670 if (!empty($CFG->divertallemailsto
)) {
4671 $subject = "[DIVERTED {$user->email}] $subject";
4672 $user = clone($user);
4673 $user->email
= $CFG->divertallemailsto
;
4676 // skip mail to suspended users
4677 if ((isset($user->auth
) && $user->auth
=='nologin') or (isset($user->suspended
) && $user->suspended
)) {
4681 if (!validate_email($user->email
)) {
4682 // we can not send emails to invalid addresses - it might create security issue or confuse the mailer
4683 $invalidemail = "User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.";
4684 error_log($invalidemail);
4686 mtrace('Error: lib/moodlelib.php email_to_user(): '.$invalidemail);
4691 if (over_bounce_threshold($user)) {
4692 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
4693 error_log($bouncemsg);
4695 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
4700 // If the user is a remote mnet user, parse the email text for URL to the
4701 // wwwroot and modify the url to direct the user's browser to login at their
4702 // home site (identity provider - idp) before hitting the link itself
4703 if (is_mnet_remote_user($user)) {
4704 require_once($CFG->dirroot
.'/mnet/lib.php');
4706 $jumpurl = mnet_get_idp_jump_url($user);
4707 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
4709 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4712 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4716 $mail = get_mailer();
4718 if (!empty($mail->SMTPDebug
)) {
4719 echo '<pre>' . "\n";
4722 $temprecipients = array();
4723 $tempreplyto = array();
4725 $supportuser = generate_email_supportuser();
4727 // make up an email address for handling bounces
4728 if (!empty($CFG->handlebounces
)) {
4729 $modargs = 'B'.base64_encode(pack('V',$user->id
)).substr(md5($user->email
),0,16);
4730 $mail->Sender
= generate_email_processing_address(0,$modargs);
4732 $mail->Sender
= $supportuser->email
;
4735 if (is_string($from)) { // So we can pass whatever we want if there is need
4736 $mail->From
= $CFG->noreplyaddress
;
4737 $mail->FromName
= $from;
4738 } else if ($usetrueaddress and $from->maildisplay
) {
4739 $mail->From
= $from->email
;
4740 $mail->FromName
= fullname($from);
4742 $mail->From
= $CFG->noreplyaddress
;
4743 $mail->FromName
= fullname($from);
4744 if (empty($replyto)) {
4745 $tempreplyto[] = array($CFG->noreplyaddress
, get_string('noreplyname'));
4749 if (!empty($replyto)) {
4750 $tempreplyto[] = array($replyto, $replytoname);
4753 $mail->Subject
= substr($subject, 0, 900);
4755 $temprecipients[] = array($user->email
, fullname($user));
4757 $mail->WordWrap
= $wordwrapwidth; // set word wrap
4759 if (!empty($from->customheaders
)) { // Add custom headers
4760 if (is_array($from->customheaders
)) {
4761 foreach ($from->customheaders
as $customheader) {
4762 $mail->AddCustomHeader($customheader);
4765 $mail->AddCustomHeader($from->customheaders
);
4769 if (!empty($from->priority
)) {
4770 $mail->Priority
= $from->priority
;
4773 if ($messagehtml && !empty($user->mailformat
) && $user->mailformat
== 1) { // Don't ever send HTML to users who don't want it
4774 $mail->IsHTML(true);
4775 $mail->Encoding
= 'quoted-printable'; // Encoding to use
4776 $mail->Body
= $messagehtml;
4777 $mail->AltBody
= "\n$messagetext\n";
4779 $mail->IsHTML(false);
4780 $mail->Body
= "\n$messagetext\n";
4783 if ($attachment && $attachname) {
4784 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
4785 $temprecipients[] = array($supportuser->email
, fullname($supportuser, true));
4786 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4788 require_once($CFG->libdir
.'/filelib.php');
4789 $mimetype = mimeinfo('type', $attachname);
4790 $mail->AddAttachment($CFG->dataroot
.'/'. $attachment, $attachname, 'base64', $mimetype);
4794 // Check if the email should be sent in an other charset then the default UTF-8
4795 if ((!empty($CFG->sitemailcharset
) ||
!empty($CFG->allowusermailcharset
))) {
4797 // use the defined site mail charset or eventually the one preferred by the recipient
4798 $charset = $CFG->sitemailcharset
;
4799 if (!empty($CFG->allowusermailcharset
)) {
4800 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id
)) {
4801 $charset = $useremailcharset;
4805 // convert all the necessary strings if the charset is supported
4806 $charsets = get_list_of_charsets();
4807 unset($charsets['UTF-8']);
4808 if (in_array($charset, $charsets)) {
4809 $textlib = textlib_get_instance();
4810 $mail->CharSet
= $charset;
4811 $mail->FromName
= $textlib->convert($mail->FromName
, 'utf-8', strtolower($charset));
4812 $mail->Subject
= $textlib->convert($mail->Subject
, 'utf-8', strtolower($charset));
4813 $mail->Body
= $textlib->convert($mail->Body
, 'utf-8', strtolower($charset));
4814 $mail->AltBody
= $textlib->convert($mail->AltBody
, 'utf-8', strtolower($charset));
4816 foreach ($temprecipients as $key => $values) {
4817 $temprecipients[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
4819 foreach ($tempreplyto as $key => $values) {
4820 $tempreplyto[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
4825 foreach ($temprecipients as $values) {
4826 $mail->AddAddress($values[0], $values[1]);
4828 foreach ($tempreplyto as $values) {
4829 $mail->AddReplyTo($values[0], $values[1]);
4832 if ($mail->Send()) {
4833 set_send_count($user);
4834 $mail->IsSMTP(); // use SMTP directly
4835 if (!empty($mail->SMTPDebug
)) {
4840 add_to_log(SITEID
, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo
);
4842 mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo
);
4844 if (!empty($mail->SMTPDebug
)) {
4852 * Generate a signoff for emails based on support settings
4857 function generate_email_signoff() {
4861 if (!empty($CFG->supportname
)) {
4862 $signoff .= $CFG->supportname
."\n";
4864 if (!empty($CFG->supportemail
)) {
4865 $signoff .= $CFG->supportemail
."\n";
4867 if (!empty($CFG->supportpage
)) {
4868 $signoff .= $CFG->supportpage
."\n";
4874 * Generate a fake user for emails based on support settings
4876 * @return object user info
4878 function generate_email_supportuser() {
4881 static $supportuser;
4883 if (!empty($supportuser)) {
4884 return $supportuser;
4887 $supportuser = new stdClass();
4888 $supportuser->email
= $CFG->supportemail ?
$CFG->supportemail
: $CFG->noreplyaddress
;
4889 $supportuser->firstname
= $CFG->supportname ?
$CFG->supportname
: get_string('noreplyname');
4890 $supportuser->lastname
= '';
4891 $supportuser->maildisplay
= true;
4893 return $supportuser;
4898 * Sets specified user's password and send the new password to the user via email.
4902 * @param user $user A {@link $USER} object
4903 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
4905 function setnew_password_and_mail($user) {
4910 $supportuser = generate_email_supportuser();
4912 $newpassword = generate_password();
4914 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id
));
4916 $a = new stdClass();
4917 $a->firstname
= fullname($user, true);
4918 $a->sitename
= format_string($site->fullname
);
4919 $a->username
= $user->username
;
4920 $a->newpassword
= $newpassword;
4921 $a->link
= $CFG->wwwroot
.'/login/';
4922 $a->signoff
= generate_email_signoff();
4924 $message = get_string('newusernewpasswordtext', '', $a);
4926 $subject = format_string($site->fullname
) .': '. get_string('newusernewpasswordsubj');
4928 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4929 return email_to_user($user, $supportuser, $subject, $message);
4934 * Resets specified user's password and send the new password to the user via email.
4936 * @param stdClass $user A {@link $USER} object
4937 * @return bool Returns true if mail was sent OK and false if there was an error.
4939 function reset_password_and_mail($user) {
4943 $supportuser = generate_email_supportuser();
4945 $userauth = get_auth_plugin($user->auth
);
4946 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth
)) {
4947 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4951 $newpassword = generate_password();
4953 if (!$userauth->user_update_password($user, $newpassword)) {
4954 print_error("cannotsetpassword");
4957 $a = new stdClass();
4958 $a->firstname
= $user->firstname
;
4959 $a->lastname
= $user->lastname
;
4960 $a->sitename
= format_string($site->fullname
);
4961 $a->username
= $user->username
;
4962 $a->newpassword
= $newpassword;
4963 $a->link
= $CFG->httpswwwroot
.'/login/change_password.php';
4964 $a->signoff
= generate_email_signoff();
4966 $message = get_string('newpasswordtext', '', $a);
4968 $subject = format_string($site->fullname
) .': '. get_string('changedpassword');
4970 unset_user_preference('create_password', $user); // prevent cron from generating the password
4972 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
4973 return email_to_user($user, $supportuser, $subject, $message);
4978 * Send email to specified user with confirmation text and activation link.
4981 * @param user $user A {@link $USER} object
4982 * @return bool Returns true if mail was sent OK and false if there was an error.
4984 function send_confirmation_email($user) {
4988 $supportuser = generate_email_supportuser();
4990 $data = new stdClass();
4991 $data->firstname
= fullname($user);
4992 $data->sitename
= format_string($site->fullname
);
4993 $data->admin
= generate_email_signoff();
4995 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname
));
4997 $username = urlencode($user->username
);
4998 $username = str_replace('.', '%2E', $username); // prevent problems with trailing dots
4999 $data->link
= $CFG->wwwroot
.'/login/confirm.php?data='. $user->secret
.'/'. $username;
5000 $message = get_string('emailconfirmation', '', $data);
5001 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
5003 $user->mailformat
= 1; // Always send HTML version as well
5005 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5006 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
5011 * send_password_change_confirmation_email.
5014 * @param user $user A {@link $USER} object
5015 * @return bool Returns true if mail was sent OK and false if there was an error.
5017 function send_password_change_confirmation_email($user) {
5021 $supportuser = generate_email_supportuser();
5023 $data = new stdClass();
5024 $data->firstname
= $user->firstname
;
5025 $data->lastname
= $user->lastname
;
5026 $data->sitename
= format_string($site->fullname
);
5027 $data->link
= $CFG->httpswwwroot
.'/login/forgot_password.php?p='. $user->secret
.'&s='. urlencode($user->username
);
5028 $data->admin
= generate_email_signoff();
5030 $message = get_string('emailpasswordconfirmation', '', $data);
5031 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname
));
5033 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5034 return email_to_user($user, $supportuser, $subject, $message);
5039 * send_password_change_info.
5042 * @param user $user A {@link $USER} object
5043 * @return bool Returns true if mail was sent OK and false if there was an error.
5045 function send_password_change_info($user) {
5049 $supportuser = generate_email_supportuser();
5050 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
5052 $data = new stdClass();
5053 $data->firstname
= $user->firstname
;
5054 $data->lastname
= $user->lastname
;
5055 $data->sitename
= format_string($site->fullname
);
5056 $data->admin
= generate_email_signoff();
5058 $userauth = get_auth_plugin($user->auth
);
5060 if (!is_enabled_auth($user->auth
) or $user->auth
== 'nologin') {
5061 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
5062 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname
));
5063 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5064 return email_to_user($user, $supportuser, $subject, $message);
5067 if ($userauth->can_change_password() and $userauth->change_password_url()) {
5068 // we have some external url for password changing
5069 $data->link
.= $userauth->change_password_url();
5072 //no way to change password, sorry
5076 if (!empty($data->link
) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id
)) {
5077 $message = get_string('emailpasswordchangeinfo', '', $data);
5078 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname
));
5080 $message = get_string('emailpasswordchangeinfofail', '', $data);
5081 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname
));
5084 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5085 return email_to_user($user, $supportuser, $subject, $message);
5090 * Check that an email is allowed. It returns an error message if there
5094 * @param string $email Content of email
5095 * @return string|false
5097 function email_is_not_allowed($email) {
5100 if (!empty($CFG->allowemailaddresses
)) {
5101 $allowed = explode(' ', $CFG->allowemailaddresses
);
5102 foreach ($allowed as $allowedpattern) {
5103 $allowedpattern = trim($allowedpattern);
5104 if (!$allowedpattern) {
5107 if (strpos($allowedpattern, '.') === 0) {
5108 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5109 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5113 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5117 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses
);
5119 } else if (!empty($CFG->denyemailaddresses
)) {
5120 $denied = explode(' ', $CFG->denyemailaddresses
);
5121 foreach ($denied as $deniedpattern) {
5122 $deniedpattern = trim($deniedpattern);
5123 if (!$deniedpattern) {
5126 if (strpos($deniedpattern, '.') === 0) {
5127 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5128 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5129 return get_string('emailnotallowed', '', $CFG->denyemailaddresses
);
5132 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5133 return get_string('emailnotallowed', '', $CFG->denyemailaddresses
);
5141 /// FILE HANDLING /////////////////////////////////////////////
5144 * Returns local file storage instance
5146 * @return file_storage
5148 function get_file_storage() {
5157 require_once("$CFG->libdir/filelib.php");
5159 if (isset($CFG->filedir
)) {
5160 $filedir = $CFG->filedir
;
5162 $filedir = $CFG->dataroot
.'/filedir';
5165 if (isset($CFG->trashdir
)) {
5166 $trashdirdir = $CFG->trashdir
;
5168 $trashdirdir = $CFG->dataroot
.'/trashdir';
5171 $fs = new file_storage($filedir, $trashdirdir, "$CFG->dataroot/temp/filestorage", $CFG->directorypermissions
, $CFG->filepermissions
);
5177 * Returns local file storage instance
5179 * @return file_browser
5181 function get_file_browser() {
5190 require_once("$CFG->libdir/filelib.php");
5192 $fb = new file_browser();
5198 * Returns file packer
5200 * @param string $mimetype default application/zip
5201 * @return file_packer
5203 function get_file_packer($mimetype='application/zip') {
5206 static $fp = array();;
5208 if (isset($fp[$mimetype])) {
5209 return $fp[$mimetype];
5212 switch ($mimetype) {
5213 case 'application/zip':
5214 case 'application/vnd.moodle.backup':
5215 $classname = 'zip_packer';
5217 case 'application/x-tar':
5218 // $classname = 'tar_packer';
5224 require_once("$CFG->libdir/filestorage/$classname.php");
5225 $fp[$mimetype] = new $classname();
5227 return $fp[$mimetype];
5231 * Returns current name of file on disk if it exists.
5233 * @param string $newfile File to be verified
5234 * @return string Current name of file on disk if true
5236 function valid_uploaded_file($newfile) {
5237 if (empty($newfile)) {
5240 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5241 return $newfile['tmp_name'];
5248 * Returns the maximum size for uploading files.
5250 * There are seven possible upload limits:
5251 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5252 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5253 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5254 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5255 * 5. by the Moodle admin in $CFG->maxbytes
5256 * 6. by the teacher in the current course $course->maxbytes
5257 * 7. by the teacher for the current module, eg $assignment->maxbytes
5259 * These last two are passed to this function as arguments (in bytes).
5260 * Anything defined as 0 is ignored.
5261 * The smallest of all the non-zero numbers is returned.
5263 * @todo Finish documenting this function
5265 * @param int $sizebytes Set maximum size
5266 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5267 * @param int $modulebytes Current module ->maxbytes (in bytes)
5268 * @return int The maximum size for uploading files.
5270 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5272 if (! $filesize = ini_get('upload_max_filesize')) {
5275 $minimumsize = get_real_size($filesize);
5277 if ($postsize = ini_get('post_max_size')) {
5278 $postsize = get_real_size($postsize);
5279 if ($postsize < $minimumsize) {
5280 $minimumsize = $postsize;
5284 if ($sitebytes and $sitebytes < $minimumsize) {
5285 $minimumsize = $sitebytes;
5288 if ($coursebytes and $coursebytes < $minimumsize) {
5289 $minimumsize = $coursebytes;
5292 if ($modulebytes and $modulebytes < $minimumsize) {
5293 $minimumsize = $modulebytes;
5296 return $minimumsize;
5300 * Returns an array of possible sizes in local language
5302 * Related to {@link get_max_upload_file_size()} - this function returns an
5303 * array of possible sizes in an array, translated to the
5306 * @todo Finish documenting this function
5309 * @uses SORT_NUMERIC
5310 * @param int $sizebytes Set maximum size
5311 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5312 * @param int $modulebytes Current module ->maxbytes (in bytes)
5315 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5318 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5322 $filesize[intval($maxsize)] = display_size($maxsize);
5324 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5325 5242880, 10485760, 20971520, 52428800, 104857600);
5327 // Allow maxbytes to be selected if it falls outside the above boundaries
5328 if (isset($CFG->maxbytes
) && !in_array(get_real_size($CFG->maxbytes
), $sizelist)) {
5329 // note: get_real_size() is used in order to prevent problems with invalid values
5330 $sizelist[] = get_real_size($CFG->maxbytes
);
5333 foreach ($sizelist as $sizebytes) {
5334 if ($sizebytes < $maxsize) {
5335 $filesize[intval($sizebytes)] = display_size($sizebytes);
5339 krsort($filesize, SORT_NUMERIC
);
5345 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
5347 * If excludefiles is defined, then that file/directory is ignored
5348 * If getdirs is true, then (sub)directories are included in the output
5349 * If getfiles is true, then files are included in the output
5350 * (at least one of these must be true!)
5352 * @todo Finish documenting this function. Add examples of $excludefile usage.
5354 * @param string $rootdir A given root directory to start from
5355 * @param string|array $excludefile If defined then the specified file/directory is ignored
5356 * @param bool $descend If true then subdirectories are recursed as well
5357 * @param bool $getdirs If true then (sub)directories are included in the output
5358 * @param bool $getfiles If true then files are included in the output
5359 * @return array An array with all the filenames in
5360 * all subdirectories, relative to the given rootdir
5362 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5366 if (!$getdirs and !$getfiles) { // Nothing to show
5370 if (!is_dir($rootdir)) { // Must be a directory
5374 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5378 if (!is_array($excludefiles)) {
5379 $excludefiles = array($excludefiles);
5382 while (false !== ($file = readdir($dir))) {
5383 $firstchar = substr($file, 0, 1);
5384 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5387 $fullfile = $rootdir .'/'. $file;
5388 if (filetype($fullfile) == 'dir') {
5393 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5394 foreach ($subdirs as $subdir) {
5395 $dirs[] = $file .'/'. $subdir;
5398 } else if ($getfiles) {
5411 * Adds up all the files in a directory and works out the size.
5413 * @todo Finish documenting this function
5415 * @param string $rootdir The directory to start from
5416 * @param string $excludefile A file to exclude when summing directory size
5417 * @return int The summed size of all files and subfiles within the root directory
5419 function get_directory_size($rootdir, $excludefile='') {
5422 // do it this way if we can, it's much faster
5423 if (!empty($CFG->pathtodu
) && is_executable(trim($CFG->pathtodu
))) {
5424 $command = trim($CFG->pathtodu
).' -sk '.escapeshellarg($rootdir);
5427 exec($command,$output,$return);
5428 if (is_array($output)) {
5429 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5433 if (!is_dir($rootdir)) { // Must be a directory
5437 if (!$dir = @opendir
($rootdir)) { // Can't open it for some reason
5443 while (false !== ($file = readdir($dir))) {
5444 $firstchar = substr($file, 0, 1);
5445 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5448 $fullfile = $rootdir .'/'. $file;
5449 if (filetype($fullfile) == 'dir') {
5450 $size +
= get_directory_size($fullfile, $excludefile);
5452 $size +
= filesize($fullfile);
5461 * Converts bytes into display form
5463 * @todo Finish documenting this function. Verify return type.
5465 * @staticvar string $gb Localized string for size in gigabytes
5466 * @staticvar string $mb Localized string for size in megabytes
5467 * @staticvar string $kb Localized string for size in kilobytes
5468 * @staticvar string $b Localized string for size in bytes
5469 * @param int $size The size to convert to human readable form
5472 function display_size($size) {
5474 static $gb, $mb, $kb, $b;
5477 $gb = get_string('sizegb');
5478 $mb = get_string('sizemb');
5479 $kb = get_string('sizekb');
5480 $b = get_string('sizeb');
5483 if ($size >= 1073741824) {
5484 $size = round($size / 1073741824 * 10) / 10 . $gb;
5485 } else if ($size >= 1048576) {
5486 $size = round($size / 1048576 * 10) / 10 . $mb;
5487 } else if ($size >= 1024) {
5488 $size = round($size / 1024 * 10) / 10 . $kb;
5490 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
5496 * Cleans a given filename by removing suspicious or troublesome characters
5497 * @see clean_param()
5500 * @param string $string file name
5501 * @return string cleaned file name
5503 function clean_filename($string) {
5504 return clean_param($string, PARAM_FILE
);
5508 /// STRING TRANSLATION ////////////////////////////////////////
5511 * Returns the code for the current language
5515 function current_language() {
5516 global $CFG, $USER, $SESSION, $COURSE;
5518 if (!empty($COURSE->id
) and $COURSE->id
!= SITEID
and !empty($COURSE->lang
)) { // Course language can override all other settings for this page
5519 $return = $COURSE->lang
;
5521 } else if (!empty($SESSION->lang
)) { // Session language can override other settings
5522 $return = $SESSION->lang
;
5524 } else if (!empty($USER->lang
)) {
5525 $return = $USER->lang
;
5527 } else if (isset($CFG->lang
)) {
5528 $return = $CFG->lang
;
5534 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
5540 * Returns parent language of current active language if defined
5544 * @param string $lang null means current language
5547 function get_parent_language($lang=null) {
5548 global $COURSE, $SESSION;
5550 //let's hack around the current language
5551 if (!empty($lang)) {
5552 $old_course_lang = empty($COURSE->lang
) ?
'' : $COURSE->lang
;
5553 $old_session_lang = empty($SESSION->lang
) ?
'' : $SESSION->lang
;
5555 $SESSION->lang
= $lang;
5558 $parentlang = get_string('parentlanguage', 'langconfig');
5559 if ($parentlang === 'en') {
5563 //let's hack around the current language
5564 if (!empty($lang)) {
5565 $COURSE->lang
= $old_course_lang;
5566 $SESSION->lang
= $old_session_lang;
5573 * Returns current string_manager instance.
5575 * The param $forcereload is needed for CLI installer only where the string_manager instance
5576 * must be replaced during the install.php script life time.
5578 * @param bool $forcereload shall the singleton be released and new instance created instead?
5579 * @return string_manager
5581 function get_string_manager($forcereload=false) {
5584 static $singleton = null;
5589 if ($singleton === null) {
5590 if (empty($CFG->early_install_lang
)) {
5592 if (empty($CFG->langcacheroot
)) {
5593 $langcacheroot = $CFG->dataroot
. '/cache/lang';
5595 $langcacheroot = $CFG->langcacheroot
;
5598 if (empty($CFG->langlist
)) {
5599 $translist = array();
5601 $translist = explode(',', $CFG->langlist
);
5604 if (empty($CFG->langmenucachefile
)) {
5605 $langmenucache = $CFG->dataroot
. '/cache/languages';
5607 $langmenucache = $CFG->langmenucachefile
;
5610 $singleton = new core_string_manager($CFG->langotherroot
, $CFG->langlocalroot
, $langcacheroot,
5611 !empty($CFG->langstringcache
), $translist, $langmenucache);
5614 $singleton = new install_string_manager();
5623 * Interface describing class which is responsible for getting
5624 * of localised strings from language packs.
5626 * @package moodlecore
5627 * @copyright 2010 Petr Skoda (http://skodak.org)
5628 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5630 interface string_manager
{
5632 * Get String returns a requested string
5634 * @param string $identifier The identifier of the string to search for
5635 * @param string $component The module the string is associated with
5636 * @param string|object|array $a An object, string or number that can be used
5637 * within translation strings
5638 * @param string $lang moodle translation language, NULL means use current
5639 * @return string The String !
5641 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
5644 * Does the string actually exist?
5646 * get_string() is throwing debug warnings, sometimes we do not want them
5647 * or we want to display better explanation of the problem.
5651 * @param string $identifier The identifier of the string to search for
5652 * @param string $component The module the string is associated with
5653 * @return boot true if exists
5655 public function string_exists($identifier, $component);
5658 * Returns a localised list of all country names, sorted by country keys.
5659 * @param bool $returnall return all or just enabled
5660 * @param string $lang moodle translation language, NULL means use current
5661 * @return array two-letter country code => translated name.
5663 public function get_list_of_countries($returnall = false, $lang = NULL);
5666 * Returns a localised list of languages, sorted by code keys.
5668 * @param string $lang moodle translation language, NULL means use current
5669 * @param string $standard language list standard
5670 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
5671 * @return array language code => translated name
5673 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
5676 * Does the translation exist?
5678 * @param string $lang moodle translation language code
5679 * @param bool include also disabled translations?
5680 * @return boot true if exists
5682 public function translation_exists($lang, $includeall = true);
5685 * Returns localised list of installed translations
5686 * @param bool $returnall return all or just enabled
5687 * @return array moodle translation code => localised translation name
5689 public function get_list_of_translations($returnall = false);
5692 * Returns localised list of currencies.
5694 * @param string $lang moodle translation language, NULL means use current
5695 * @return array currency code => localised currency name
5697 public function get_list_of_currencies($lang = NULL);
5700 * Load all strings for one component
5701 * @param string $component The module the string is associated with
5702 * @param string $lang
5703 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5704 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5705 * @return array of all string for given component and lang
5707 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
5710 * Invalidates all caches, should the implementation use any
5712 public function reset_caches();
5717 * Standard string_manager implementation
5719 * @package moodlecore
5720 * @copyright 2010 Petr Skoda (http://skodak.org)
5721 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5723 class core_string_manager
implements string_manager
{
5724 /** @var string location of all packs except 'en' */
5725 protected $otherroot;
5726 /** @var string location of all lang pack local modifications */
5727 protected $localroot;
5728 /** @var string location of on-disk cache of merged strings */
5729 protected $cacheroot;
5730 /** @var array lang string cache - it will be optimised more later */
5731 protected $cache = array();
5732 /** @var int get_string() counter */
5733 protected $countgetstring = 0;
5734 /** @var int in-memory cache hits counter */
5735 protected $countmemcache = 0;
5736 /** @var int on-disk cache hits counter */
5737 protected $countdiskcache = 0;
5738 /** @var bool use disk cache */
5739 protected $usediskcache;
5740 /* @var array limit list of translations */
5741 protected $translist;
5742 /** @var string location of a file that caches the list of available translations */
5743 protected $menucache;
5746 * Create new instance of string manager
5748 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
5749 * @param string $localroot usually the same as $otherroot
5750 * @param string $cacheroot usually lang dir in cache folder
5751 * @param bool $usediskcache use disk cache
5752 * @param array $translist limit list of visible translations
5753 * @param string $menucache the location of a file that caches the list of available translations
5755 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
5756 $this->otherroot
= $otherroot;
5757 $this->localroot
= $localroot;
5758 $this->cacheroot
= $cacheroot;
5759 $this->usediskcache
= $usediskcache;
5760 $this->translist
= $translist;
5761 $this->menucache
= $menucache;
5765 * Returns dependencies of current language, en is not included.
5766 * @param string $lang
5767 * @return array all parents, the lang itself is last
5769 public function get_language_dependencies($lang) {
5770 if ($lang === 'en') {
5773 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
5777 include("$this->otherroot/$lang/langconfig.php");
5779 if (empty($string['parentlanguage'])) {
5780 return array($lang);
5782 $parentlang = $string['parentlanguage'];
5784 return array_merge($this->get_language_dependencies($parentlang), array($lang));
5789 * Load all strings for one component
5790 * @param string $component The module the string is associated with
5791 * @param string $lang
5792 * @param bool $disablecache Do not use caches, force fetching the strings from sources
5793 * @param bool $disablelocal Do not use customized strings in xx_local language packs
5794 * @return array of all string for given component and lang
5796 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
5799 list($plugintype, $pluginname) = normalize_component($component);
5800 if ($plugintype == 'core' and is_null($pluginname)) {
5801 $component = 'core';
5803 $component = $plugintype . '_' . $pluginname;
5806 if (!$disablecache) {
5807 // try in-memory cache first
5808 if (isset($this->cache
[$lang][$component])) {
5809 $this->countmemcache++
;
5810 return $this->cache
[$lang][$component];
5813 // try on-disk cache then
5814 if ($this->usediskcache
and file_exists($this->cacheroot
. "/$lang/$component.php")) {
5815 $this->countdiskcache++
;
5816 include($this->cacheroot
. "/$lang/$component.php");
5817 return $this->cache
[$lang][$component];
5821 // no cache found - let us merge all possible sources of the strings
5822 if ($plugintype === 'core') {
5823 $file = $pluginname;
5824 if ($file === null) {
5828 // first load english pack
5829 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
5832 include("$CFG->dirroot/lang/en/$file.php");
5833 $originalkeys = array_keys($string);
5834 $originalkeys = array_flip($originalkeys);
5836 // and then corresponding local if present and allowed
5837 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5838 include("$this->localroot/en_local/$file.php");
5840 // now loop through all langs in correct order
5841 $deps = $this->get_language_dependencies($lang);
5842 foreach ($deps as $dep) {
5843 // the main lang string location
5844 if (file_exists("$this->otherroot/$dep/$file.php")) {
5845 include("$this->otherroot/$dep/$file.php");
5847 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5848 include("$this->localroot/{$dep}_local/$file.php");
5853 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
5856 if ($plugintype === 'mod') {
5858 $file = $pluginname;
5860 $file = $plugintype . '_' . $pluginname;
5863 // first load English pack
5864 if (!file_exists("$location/lang/en/$file.php")) {
5865 //English pack does not exist, so do not try to load anything else
5868 include("$location/lang/en/$file.php");
5869 $originalkeys = array_keys($string);
5870 $originalkeys = array_flip($originalkeys);
5871 // and then corresponding local english if present
5872 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
5873 include("$this->localroot/en_local/$file.php");
5876 // now loop through all langs in correct order
5877 $deps = $this->get_language_dependencies($lang);
5878 foreach ($deps as $dep) {
5879 // legacy location - used by contrib only
5880 if (file_exists("$location/lang/$dep/$file.php")) {
5881 include("$location/lang/$dep/$file.php");
5883 // the main lang string location
5884 if (file_exists("$this->otherroot/$dep/$file.php")) {
5885 include("$this->otherroot/$dep/$file.php");
5887 // local customisations
5888 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
5889 include("$this->localroot/{$dep}_local/$file.php");
5894 // we do not want any extra strings from other languages - everything must be in en lang pack
5895 $string = array_intersect_key($string, $originalkeys);
5897 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
5898 // caches so we do not need to do all this merging and dependencies resolving again
5899 $this->cache
[$lang][$component] = $string;
5900 if ($this->usediskcache
) {
5901 check_dir_exists("$this->cacheroot/$lang");
5902 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
5908 * Does the string actually exist?
5910 * get_string() is throwing debug warnings, sometimes we do not want them
5911 * or we want to display better explanation of the problem.
5915 * @param string $identifier The identifier of the string to search for
5916 * @param string $component The module the string is associated with
5917 * @return boot true if exists
5919 public function string_exists($identifier, $component) {
5920 $identifier = clean_param($identifier, PARAM_STRINGID
);
5921 if (empty($identifier)) {
5924 $lang = current_language();
5925 $string = $this->load_component_strings($component, $lang);
5926 return isset($string[$identifier]);
5930 * Get String returns a requested string
5932 * @param string $identifier The identifier of the string to search for
5933 * @param string $component The module the string is associated with
5934 * @param string|object|array $a An object, string or number that can be used
5935 * within translation strings
5936 * @param string $lang moodle translation language, NULL means use current
5937 * @return string The String !
5939 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
5940 $this->countgetstring++
;
5941 // there are very many uses of these time formating strings without the 'langconfig' component,
5942 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
5943 static $langconfigstrs = array(
5944 'strftimedate' => 1,
5945 'strftimedatefullshort' => 1,
5946 'strftimedateshort' => 1,
5947 'strftimedatetime' => 1,
5948 'strftimedatetimeshort' => 1,
5949 'strftimedaydate' => 1,
5950 'strftimedaydatetime' => 1,
5951 'strftimedayshort' => 1,
5952 'strftimedaytime' => 1,
5953 'strftimemonthyear' => 1,
5954 'strftimerecent' => 1,
5955 'strftimerecentfull' => 1,
5956 'strftimetime' => 1);
5958 if (empty($component)) {
5959 if (isset($langconfigstrs[$identifier])) {
5960 $component = 'langconfig';
5962 $component = 'moodle';
5966 if ($lang === NULL) {
5967 $lang = current_language();
5970 $string = $this->load_component_strings($component, $lang);
5972 if (!isset($string[$identifier])) {
5973 if ($component === 'pix' or $component === 'core_pix') {
5974 // this component contains only alt tags for emoticons,
5975 // not all of them are supposed to be defined
5978 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
5979 // parentlanguage is a special string, undefined means use English if not defined
5982 if ($this->usediskcache
) {
5983 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources
5984 $string = $this->load_component_strings($component, $lang, true);
5986 if (!isset($string[$identifier])) {
5987 // the string is still missing - should be fixed by developer
5988 debugging("Invalid get_string() identifier: '$identifier' or component '$component'", DEBUG_DEVELOPER
);
5989 return "[[$identifier]]";
5993 $string = $string[$identifier];
5996 if (is_object($a) or is_array($a)) {
6000 foreach ($a as $key=>$value) {
6002 // we do not support numeric keys - sorry!
6005 if (is_object($value) or is_array($value)) {
6006 // we support just string as value
6009 $search[] = '{$a->'.$key.'}';
6010 $replace[] = (string)$value;
6013 $string = str_replace($search, $replace, $string);
6016 $string = str_replace('{$a}', (string)$a, $string);
6024 * Returns information about the string_manager performance
6027 public function get_performance_summary() {
6029 'langcountgetstring' => $this->countgetstring
,
6030 'langcountmemcache' => $this->countmemcache
,
6031 'langcountdiskcache' => $this->countdiskcache
,
6033 'langcountgetstring' => 'get_string calls',
6034 'langcountmemcache' => 'strings mem cache hits',
6035 'langcountdiskcache' => 'strings disk cache hits',
6040 * Returns a localised list of all country names, sorted by localised name.
6042 * @param bool $returnall return all or just enabled
6043 * @param string $lang moodle translation language, NULL means use current
6044 * @return array two-letter country code => translated name.
6046 public function get_list_of_countries($returnall = false, $lang = NULL) {
6049 if ($lang === NULL) {
6050 $lang = current_language();
6053 $countries = $this->load_component_strings('core_countries', $lang);
6054 textlib_get_instance()->asort($countries);
6055 if (!$returnall and !empty($CFG->allcountrycodes
)) {
6056 $enabled = explode(',', $CFG->allcountrycodes
);
6058 foreach ($enabled as $c) {
6059 if (isset($countries[$c])) {
6060 $return[$c] = $countries[$c];
6070 * Returns a localised list of languages, sorted by code keys.
6072 * @param string $lang moodle translation language, NULL means use current
6073 * @param string $standard language list standard
6074 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
6075 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
6076 * @return array language code => translated name
6078 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
6079 if ($lang === NULL) {
6080 $lang = current_language();
6083 if ($standard === 'iso6392') {
6084 $langs = $this->load_component_strings('core_iso6392', $lang);
6088 } else if ($standard === 'iso6391') {
6089 $langs2 = $this->load_component_strings('core_iso6392', $lang);
6090 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
6091 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
6092 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
6093 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
6094 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
6095 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
6096 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
6097 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
6098 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
6099 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
6100 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
6101 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
6102 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
6103 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6104 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6105 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6106 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6108 foreach ($mapping as $c2=>$c1) {
6109 $langs1[$c1] = $langs2[$c2];
6115 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6122 * Does the translation exist?
6124 * @param string $lang moodle translation language code
6125 * @param bool include also disabled translations?
6126 * @return boot true if exists
6128 public function translation_exists($lang, $includeall = true) {
6130 if (strpos($lang, '_local') !== false) {
6131 // _local packs are not real translations
6134 if (!$includeall and !empty($this->translist
)) {
6135 if (!in_array($lang, $this->translist
)) {
6139 if ($lang === 'en') {
6140 // part of distribution
6143 return file_exists("$this->otherroot/$lang/langconfig.php");
6147 * Returns localised list of installed translations
6148 * @param bool $returnall return all or just enabled
6149 * @return array moodle translation code => localised translation name
6151 public function get_list_of_translations($returnall = false) {
6154 $languages = array();
6156 if (!empty($CFG->langcache
) and is_readable($this->menucache
)) {
6157 // try to re-use the cached list of all available languages
6158 $cachedlist = json_decode(file_get_contents($this->menucache
), true);
6160 if (is_array($cachedlist) and !empty($cachedlist)) {
6161 // the cache file is restored correctly
6163 if (!$returnall and !empty($this->translist
)) {
6164 // return just enabled translations
6165 foreach ($cachedlist as $langcode => $langname) {
6166 if (in_array($langcode, $this->translist
)) {
6167 $languages[$langcode] = $langname;
6173 // return all translations
6179 // the cached list of languages is not available, let us populate the list
6181 if (!$returnall and !empty($this->translist
)) {
6182 // return only some translations
6183 foreach ($this->translist
as $lang) {
6184 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6185 if (strstr($lang, '_local') !== false) {
6188 if (strstr($lang, '_utf8') !== false) {
6191 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6192 // some broken or missing lang - can not switch to it anyway
6195 $string = $this->load_component_strings('langconfig', $lang);
6196 if (!empty($string['thislanguage'])) {
6197 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6203 // return all languages available in system
6204 $langdirs = get_list_of_plugins('', '', $this->otherroot
);
6206 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6209 // Loop through all langs and get info
6210 foreach ($langdirs as $lang) {
6211 if (strstr($lang, '_local') !== false) {
6214 if (strstr($lang, '_utf8') !== false) {
6217 $string = $this->load_component_strings('langconfig', $lang);
6218 if (!empty($string['thislanguage'])) {
6219 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6224 if (!empty($CFG->langcache
) and !empty($this->menucache
)) {
6225 // cache the list so that it can be used next time
6226 textlib_get_instance()->asort($languages);
6227 file_put_contents($this->menucache
, json_encode($languages));
6231 textlib_get_instance()->asort($languages);
6237 * Returns localised list of currencies.
6239 * @param string $lang moodle translation language, NULL means use current
6240 * @return array currency code => localised currency name
6242 public function get_list_of_currencies($lang = NULL) {
6243 if ($lang === NULL) {
6244 $lang = current_language();
6247 $currencies = $this->load_component_strings('core_currencies', $lang);
6254 * Clears both in-memory and on-disk caches
6256 public function reset_caches() {
6258 require_once("$CFG->libdir/filelib.php");
6260 // clear the on-disk disk with aggregated string files
6261 fulldelete($this->cacheroot
);
6263 // clear the in-memory cache of loaded strings
6264 $this->cache
= array();
6266 // clear the cache containing the list of available translations
6267 // and re-populate it again
6268 fulldelete($this->menucache
);
6269 $this->get_list_of_translations(true);
6275 * Minimalistic string fetching implementation
6276 * that is used in installer before we fetch the wanted
6277 * language pack from moodle.org lang download site.
6279 * @package moodlecore
6280 * @copyright 2010 Petr Skoda (http://skodak.org)
6281 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6283 class install_string_manager
implements string_manager
{
6284 /** @var string location of pre-install packs for all langs */
6285 protected $installroot;
6288 * Crate new instance of install string manager
6290 public function __construct() {
6292 $this->installroot
= "$CFG->dirroot/install/lang";
6296 * Load all strings for one component
6297 * @param string $component The module the string is associated with
6298 * @param string $lang
6299 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6300 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6301 * @return array of all string for given component and lang
6303 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6304 // not needed in installer
6309 * Does the string actually exist?
6311 * get_string() is throwing debug warnings, sometimes we do not want them
6312 * or we want to display better explanation of the problem.
6316 * @param string $identifier The identifier of the string to search for
6317 * @param string $component The module the string is associated with
6318 * @return boot true if exists
6320 public function string_exists($identifier, $component) {
6321 $identifier = clean_param($identifier, PARAM_STRINGID
);
6322 if (empty($identifier)) {
6325 // simple old style hack ;)
6326 $str = get_string($identifier, $component);
6327 return (strpos($str, '[[') === false);
6331 * Get String returns a requested string
6333 * @param string $identifier The identifier of the string to search for
6334 * @param string $component The module the string is associated with
6335 * @param string|object|array $a An object, string or number that can be used
6336 * within translation strings
6337 * @param string $lang moodle translation language, NULL means use current
6338 * @return string The String !
6340 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6342 $component = 'moodle';
6345 if ($lang === NULL) {
6346 $lang = current_language();
6351 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
6352 if (file_exists("$this->installroot/$lang/langconfig.php")) {
6354 include("$this->installroot/$lang/langconfig.php");
6355 if (isset($string['parentlanguage'])) {
6356 $parent = $string['parentlanguage'];
6362 // include en string first
6363 if (!file_exists("$this->installroot/en/$component.php")) {
6364 return "[[$identifier]]";
6367 include("$this->installroot/en/$component.php");
6369 // now override en with parent if defined
6370 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
6371 include("$this->installroot/$parent/$component.php");
6374 // finally override with requested language
6375 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
6376 include("$this->installroot/$lang/$component.php");
6379 if (!isset($string[$identifier])) {
6380 return "[[$identifier]]";
6383 $string = $string[$identifier];
6386 if (is_object($a) or is_array($a)) {
6390 foreach ($a as $key=>$value) {
6392 // we do not support numeric keys - sorry!
6395 $search[] = '{$a->'.$key.'}';
6396 $replace[] = (string)$value;
6399 $string = str_replace($search, $replace, $string);
6402 $string = str_replace('{$a}', (string)$a, $string);
6410 * Returns a localised list of all country names, sorted by country keys.
6412 * @param bool $returnall return all or just enabled
6413 * @param string $lang moodle translation language, NULL means use current
6414 * @return array two-letter country code => translated name.
6416 public function get_list_of_countries($returnall = false, $lang = NULL) {
6417 //not used in installer
6422 * Returns a localised list of languages, sorted by code keys.
6424 * @param string $lang moodle translation language, NULL means use current
6425 * @param string $standard language list standard
6426 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6427 * @return array language code => translated name
6429 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
6430 //not used in installer
6435 * Does the translation exist?
6437 * @param string $lang moodle translation language code
6438 * @param bool include also disabled translations?
6439 * @return boot true if exists
6441 public function translation_exists($lang, $includeall = true) {
6442 return file_exists($this->installroot
.'/'.$lang.'/langconfig.php');
6446 * Returns localised list of installed translations
6447 * @param bool $returnall return all or just enabled
6448 * @return array moodle translation code => localised translation name
6450 public function get_list_of_translations($returnall = false) {
6451 // return all is ignored here - we need to know all langs in installer
6452 $languages = array();
6453 // Get raw list of lang directories
6454 $langdirs = get_list_of_plugins('install/lang');
6456 // Get some info from each lang
6457 foreach ($langdirs as $lang) {
6458 if (file_exists($this->installroot
.'/'.$lang.'/langconfig.php')) {
6460 include($this->installroot
.'/'.$lang.'/langconfig.php');
6461 if (!empty($string['thislanguage'])) {
6462 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
6471 * Returns localised list of currencies.
6473 * @param string $lang moodle translation language, NULL means use current
6474 * @return array currency code => localised currency name
6476 public function get_list_of_currencies($lang = NULL) {
6477 // not used in installer
6482 * This implementation does not use any caches
6484 public function reset_caches() {}
6489 * Returns a localized string.
6491 * Returns the translated string specified by $identifier as
6492 * for $module. Uses the same format files as STphp.
6493 * $a is an object, string or number that can be used
6494 * within translation strings
6496 * eg 'hello {$a->firstname} {$a->lastname}'
6499 * If you would like to directly echo the localized string use
6500 * the function {@link print_string()}
6502 * Example usage of this function involves finding the string you would
6503 * like a local equivalent of and using its identifier and module information
6504 * to retrieve it.<br/>
6505 * If you open moodle/lang/en/moodle.php and look near line 278
6506 * you will find a string to prompt a user for their word for 'course'
6508 * $string['course'] = 'Course';
6510 * So if you want to display the string 'Course'
6511 * in any language that supports it on your site
6512 * you just need to use the identifier 'course'
6514 * $mystring = '<strong>'. get_string('course') .'</strong>';
6517 * If the string you want is in another file you'd take a slightly
6518 * different approach. Looking in moodle/lang/en/calendar.php you find
6521 * $string['typecourse'] = 'Course event';
6523 * If you want to display the string "Course event" in any language
6524 * supported you would use the identifier 'typecourse' and the module 'calendar'
6525 * (because it is in the file calendar.php):
6527 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
6530 * As a last resort, should the identifier fail to map to a string
6531 * the returned string will be [[ $identifier ]]
6533 * @param string $identifier The key identifier for the localized string
6534 * @param string $component The module where the key identifier is stored,
6535 * usually expressed as the filename in the language pack without the
6536 * .php on the end but can also be written as mod/forum or grade/export/xls.
6537 * If none is specified then moodle.php is used.
6538 * @param string|object|array $a An object, string or number that can be used
6539 * within translation strings
6540 * @return string The localized string.
6542 function get_string($identifier, $component = '', $a = NULL) {
6544 $identifier = clean_param($identifier, PARAM_STRINGID
);
6545 if (empty($identifier)) {
6546 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please fix your get_string() call and string definition');
6549 if (func_num_args() > 3) {
6550 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
6553 if (strpos($component, '/') !== false) {
6554 debugging('The module name you passed to get_string is the deprecated format ' .
6555 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER
);
6556 $componentpath = explode('/', $component);
6558 switch ($componentpath[0]) {
6560 $component = $componentpath[1];
6564 $component = 'block_'.$componentpath[1];
6567 $component = 'enrol_'.$componentpath[1];
6570 $component = 'format_'.$componentpath[1];
6573 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
6578 return get_string_manager()->get_string($identifier, $component, $a);
6582 * Converts an array of strings to their localized value.
6584 * @param array $array An array of strings
6585 * @param string $module The language module that these strings can be found in.
6586 * @return array and array of translated strings.
6588 function get_strings($array, $component = '') {
6589 $string = new stdClass
;
6590 foreach ($array as $item) {
6591 $string->$item = get_string($item, $component);
6597 * Prints out a translated string.
6599 * Prints out a translated string using the return value from the {@link get_string()} function.
6601 * Example usage of this function when the string is in the moodle.php file:<br/>
6604 * print_string('course');
6608 * Example usage of this function when the string is not in the moodle.php file:<br/>
6611 * print_string('typecourse', 'calendar');
6615 * @param string $identifier The key identifier for the localized string
6616 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
6617 * @param mixed $a An object, string or number that can be used within translation strings
6619 function print_string($identifier, $component = '', $a = NULL) {
6620 echo get_string($identifier, $component, $a);
6624 * Returns a list of charset codes
6626 * Returns a list of charset codes. It's hardcoded, so they should be added manually
6627 * (checking that such charset is supported by the texlib library!)
6629 * @return array And associative array with contents in the form of charset => charset
6631 function get_list_of_charsets() {
6634 'EUC-JP' => 'EUC-JP',
6635 'ISO-2022-JP'=> 'ISO-2022-JP',
6636 'ISO-8859-1' => 'ISO-8859-1',
6637 'SHIFT-JIS' => 'SHIFT-JIS',
6638 'GB2312' => 'GB2312',
6639 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
6640 'UTF-8' => 'UTF-8');
6648 * Returns a list of valid and compatible themes
6653 function get_list_of_themes() {
6658 if (!empty($CFG->themelist
)) { // use admin's list of themes
6659 $themelist = explode(',', $CFG->themelist
);
6661 $themelist = array_keys(get_plugin_list("theme"));
6664 foreach ($themelist as $key => $themename) {
6665 $theme = theme_config
::load($themename);
6666 $themes[$themename] = $theme;
6674 * Returns a list of timezones in the current language
6680 function get_list_of_timezones() {
6685 if (!empty($timezones)) { // This function has been called recently
6689 $timezones = array();
6691 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
6692 foreach($rawtimezones as $timezone) {
6693 if (!empty($timezone->name
)) {
6694 if (get_string_manager()->string_exists(strtolower($timezone->name
), 'timezones')) {
6695 $timezones[$timezone->name
] = get_string(strtolower($timezone->name
), 'timezones');
6697 $timezones[$timezone->name
] = $timezone->name
;
6699 if (substr($timezones[$timezone->name
], 0, 1) == '[') { // No translation found
6700 $timezones[$timezone->name
] = $timezone->name
;
6708 for ($i = -13; $i <= 13; $i +
= .5) {
6711 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
6712 } else if ($i > 0) {
6713 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
6715 $timezones[sprintf("%.1f", $i)] = $tzstring;
6723 * Factory function for emoticon_manager
6725 * @return emoticon_manager singleton
6727 function get_emoticon_manager() {
6728 static $singleton = null;
6730 if (is_null($singleton)) {
6731 $singleton = new emoticon_manager();
6738 * Provides core support for plugins that have to deal with
6739 * emoticons (like HTML editor or emoticon filter).
6741 * Whenever this manager mentiones 'emoticon object', the following data
6742 * structure is expected: stdClass with properties text, imagename, imagecomponent,
6743 * altidentifier and altcomponent
6745 * @see admin_setting_emoticons
6747 class emoticon_manager
{
6750 * Returns the currently enabled emoticons
6752 * @return array of emoticon objects
6754 public function get_emoticons() {
6757 if (empty($CFG->emoticons
)) {
6761 $emoticons = $this->decode_stored_config($CFG->emoticons
);
6763 if (!is_array($emoticons)) {
6764 // something is wrong with the format of stored setting
6765 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL
);
6773 * Converts emoticon object into renderable pix_emoticon object
6775 * @param stdClass $emoticon emoticon object
6776 * @param array $attributes explicit HTML attributes to set
6777 * @return pix_emoticon
6779 public function prepare_renderable_emoticon(stdClass
$emoticon, array $attributes = array()) {
6780 $stringmanager = get_string_manager();
6781 if ($stringmanager->string_exists($emoticon->altidentifier
, $emoticon->altcomponent
)) {
6782 $alt = get_string($emoticon->altidentifier
, $emoticon->altcomponent
);
6784 $alt = s($emoticon->text
);
6786 return new pix_emoticon($emoticon->imagename
, $alt, $emoticon->imagecomponent
, $attributes);
6790 * Encodes the array of emoticon objects into a string storable in config table
6792 * @see self::decode_stored_config()
6793 * @param array $emoticons array of emtocion objects
6796 public function encode_stored_config(array $emoticons) {
6797 return json_encode($emoticons);
6801 * Decodes the string into an array of emoticon objects
6803 * @see self::encode_stored_config()
6804 * @param string $encoded
6805 * @return string|null
6807 public function decode_stored_config($encoded) {
6808 $decoded = json_decode($encoded);
6809 if (!is_array($decoded)) {
6816 * Returns default set of emoticons supported by Moodle
6818 * @return array of sdtClasses
6820 public function default_emoticons() {
6822 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
6823 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
6824 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
6825 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
6826 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
6827 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
6828 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
6829 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
6830 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
6831 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
6832 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
6833 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
6834 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
6835 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
6836 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
6837 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
6838 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
6839 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
6840 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
6841 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
6842 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
6843 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
6844 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
6845 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
6846 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
6847 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
6848 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
6849 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
6850 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
6851 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
6856 * Helper method preparing the stdClass with the emoticon properties
6858 * @param string|array $text or array of strings
6859 * @param string $imagename to be used by {@see pix_emoticon}
6860 * @param string $altidentifier alternative string identifier, null for no alt
6861 * @param array $altcomponent where the alternative string is defined
6862 * @param string $imagecomponent to be used by {@see pix_emoticon}
6865 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
6866 return (object)array(
6868 'imagename' => $imagename,
6869 'imagecomponent' => $imagecomponent,
6870 'altidentifier' => $altidentifier,
6871 'altcomponent' => $altcomponent,
6876 /// ENCRYPTION ////////////////////////////////////////////////
6881 * @todo Finish documenting this function
6883 * @param string $data Data to encrypt.
6884 * @param bool $usesecurekey Lets us know if we are using the old or new password.
6885 * @return string The now encrypted data.
6887 function rc4encrypt($data, $usesecurekey = false) {
6888 if (!$usesecurekey) {
6889 $passwordkey = 'nfgjeingjk';
6891 $passwordkey = get_site_identifier();
6893 return endecrypt($passwordkey, $data, '');
6899 * @todo Finish documenting this function
6901 * @param string $data Data to decrypt.
6902 * @param bool $usesecurekey Lets us know if we are using the old or new password.
6903 * @return string The now decrypted data.
6905 function rc4decrypt($data, $usesecurekey = false) {
6906 if (!$usesecurekey) {
6907 $passwordkey = 'nfgjeingjk';
6909 $passwordkey = get_site_identifier();
6911 return endecrypt($passwordkey, $data, 'de');
6915 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6917 * @todo Finish documenting this function
6919 * @param string $pwd The password to use when encrypting or decrypting
6920 * @param string $data The data to be decrypted/encrypted
6921 * @param string $case Either 'de' for decrypt or '' for encrypt
6924 function endecrypt ($pwd, $data, $case) {
6926 if ($case == 'de') {
6927 $data = urldecode($data);
6935 $pwd_length = strlen($pwd);
6937 for ($i = 0; $i <= 255; $i++
) {
6938 $key[$i] = ord(substr($pwd, ($i %
$pwd_length), 1));
6944 for ($i = 0; $i <= 255; $i++
) {
6945 $x = ($x +
$box[$i] +
$key[$i]) %
256;
6946 $temp_swap = $box[$i];
6947 $box[$i] = $box[$x];
6948 $box[$x] = $temp_swap;
6960 for ($i = 0; $i < strlen($data); $i++
) {
6961 $a = ($a +
1) %
256;
6962 $j = ($j +
$box[$a]) %
256;
6964 $box[$a] = $box[$j];
6966 $k = $box[(($box[$a] +
$box[$j]) %
256)];
6967 $cipherby = ord(substr($data, $i, 1)) ^
$k;
6968 $cipher .= chr($cipherby);
6971 if ($case == 'de') {
6972 $cipher = urldecode(urlencode($cipher));
6974 $cipher = urlencode($cipher);
6980 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6983 * Returns the exact absolute path to plugin directory.
6985 * @param string $plugintype type of plugin
6986 * @param string $name name of the plugin
6987 * @return string full path to plugin directory; NULL if not found
6989 function get_plugin_directory($plugintype, $name) {
6992 if ($plugintype === '') {
6993 $plugintype = 'mod';
6996 $types = get_plugin_types(true);
6997 if (!array_key_exists($plugintype, $types)) {
7000 $name = clean_param($name, PARAM_SAFEDIR
); // just in case ;-)
7002 if (!empty($CFG->themedir
) and $plugintype === 'theme') {
7003 if (!is_dir($types['theme'] . '/' . $name)) {
7004 // ok, so the theme is supposed to be in the $CFG->themedir
7005 return $CFG->themedir
. '/' . $name;
7009 return $types[$plugintype].'/'.$name;
7013 * Return exact absolute path to a plugin directory,
7014 * this method support "simpletest_" prefix designed for unit testing.
7016 * @param string $component name such as 'moodle', 'mod_forum' or special simpletest value
7017 * @return string full path to component directory; NULL if not found
7019 function get_component_directory($component) {
7022 $simpletest = false;
7023 if (strpos($component, 'simpletest_') === 0) {
7024 $subdir = substr($component, strlen('simpletest_'));
7025 //TODO: this looks borked, where is it used actually?
7029 list($type, $plugin) = normalize_component($component);
7031 if ($type === 'core') {
7032 if ($plugin === NULL ) {
7033 $path = $CFG->libdir
;
7035 $subsystems = get_core_subsystems();
7036 if (isset($subsystems[$plugin])) {
7037 $path = $CFG->dirroot
.'/'.$subsystems[$plugin];
7044 $path = get_plugin_directory($type, $plugin);
7051 * Normalize the component name using the "frankenstyle" names.
7052 * @param string $component
7053 * @return array $type+$plugin elements
7055 function normalize_component($component) {
7056 if ($component === 'moodle' or $component === 'core') {
7060 } else if (strpos($component, '_') === false) {
7061 $subsystems = get_core_subsystems();
7062 if (array_key_exists($component, $subsystems)) {
7064 $plugin = $component;
7066 // everything else is a module
7068 $plugin = $component;
7072 list($type, $plugin) = explode('_', $component, 2);
7073 $plugintypes = get_plugin_types(false);
7074 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
7076 $plugin = $component;
7080 return array($type, $plugin);
7084 * List all core subsystems and their location
7086 * This is a whitelist of components that are part of the core and their
7087 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
7088 * plugin is not listed here and it does not have proper plugintype prefix,
7089 * then it is considered as course activity module.
7091 * The location is dirroot relative path. NULL means there is no special
7092 * directory for this subsystem. If the location is set, the subsystem's
7093 * renderer.php is expected to be there.
7095 * @return array of (string)name => (string|null)location
7097 function get_core_subsystems() {
7100 static $info = null;
7105 'admin' => $CFG->admin
,
7107 'backup' => 'backup/util/ui',
7108 'block' => 'blocks',
7110 'bulkusers' => NULL,
7111 'calendar' => 'calendar',
7112 'cohort' => 'cohort',
7113 'condition' => NULL,
7114 'completion' => NULL,
7115 'countries' => NULL,
7116 'course' => 'course',
7117 'currencies' => NULL,
7118 'dbtransfer' => NULL,
7121 'editor' => 'lib/editor',
7122 'edufields' => NULL,
7125 'filepicker' => NULL,
7128 'flashdetect' => NULL,
7130 'form' => 'lib/form',
7131 'grades' => 'grade',
7138 'langconfig' => NULL,
7141 'message' => 'message',
7142 'mimetypes' => NULL,
7144 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7149 'plagiarism' => 'plagiarism',
7151 'portfolio' => 'portfolio',
7152 'publish' => 'course/publish',
7153 'question' => 'question',
7154 'rating' => 'rating',
7155 'register' => 'admin/registration',
7156 'repository' => 'repository',
7158 'role' => $CFG->admin
.'/role',
7159 'simpletest' => NULL,
7160 'search' => 'search',
7163 'timezones' => NULL,
7166 'webservice' => 'webservice',
7175 * Lists all plugin types
7176 * @param bool $fullpaths false means relative paths from dirroot
7177 * @return array Array of strings - name=>location
7179 function get_plugin_types($fullpaths=true) {
7182 static $info = null;
7183 static $fullinfo = null;
7186 $info = array('qtype' => 'question/type',
7190 'message' => 'message/output',
7191 'block' => 'blocks',
7192 'filter' => 'filter',
7193 'editor' => 'lib/editor',
7194 'format' => 'course/format',
7195 'profilefield' => 'user/profile/field',
7196 'report' => $CFG->admin
.'/report',
7197 'coursereport' => 'course/report', // must be after system reports
7198 'gradeexport' => 'grade/export',
7199 'gradeimport' => 'grade/import',
7200 'gradereport' => 'grade/report',
7201 'mnetservice' => 'mnet/service',
7202 'webservice' => 'webservice',
7203 'repository' => 'repository',
7204 'portfolio' => 'portfolio',
7205 'qbehaviour' => 'question/behaviour',
7206 'qformat' => 'question/format',
7207 'plagiarism' => 'plagiarism',
7208 'theme' => 'theme'); // this is a bit hacky, themes may be in $CFG->themedir too
7210 $mods = get_plugin_list('mod');
7211 foreach ($mods as $mod => $moddir) {
7212 if (file_exists("$moddir/db/subplugins.php")) {
7213 $subplugins = array();
7214 include("$moddir/db/subplugins.php");
7215 foreach ($subplugins as $subtype=>$dir) {
7216 $info[$subtype] = $dir;
7221 // local is always last!
7222 $info['local'] = 'local';
7224 $fullinfo = array();
7225 foreach ($info as $type => $dir) {
7226 $fullinfo[$type] = $CFG->dirroot
.'/'.$dir;
7230 return ($fullpaths ?
$fullinfo : $info);
7234 * Simplified version of get_list_of_plugins()
7235 * @param string $plugintype type of plugin
7236 * @return array name=>fulllocation pairs of plugins of given type
7238 function get_plugin_list($plugintype) {
7241 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
7242 if ($plugintype == 'auth') {
7243 // Historically we have had an auth plugin called 'db', so allow a special case.
7244 $key = array_search('db', $ignored);
7245 if ($key !== false) {
7246 unset($ignored[$key]);
7250 if ($plugintype === '') {
7251 $plugintype = 'mod';
7254 $fulldirs = array();
7256 if ($plugintype === 'mod') {
7257 // mod is an exception because we have to call this function from get_plugin_types()
7258 $fulldirs[] = $CFG->dirroot
.'/mod';
7260 } else if ($plugintype === 'theme') {
7261 $fulldirs[] = $CFG->dirroot
.'/theme';
7262 // themes are special because they may be stored also in separate directory
7263 if (!empty($CFG->themedir
) and file_exists($CFG->themedir
) and is_dir($CFG->themedir
) ) {
7264 $fulldirs[] = $CFG->themedir
;
7268 $types = get_plugin_types(true);
7269 if (!array_key_exists($plugintype, $types)) {
7272 $fulldir = $types[$plugintype];
7273 if (!file_exists($fulldir)) {
7276 $fulldirs[] = $fulldir;
7281 foreach ($fulldirs as $fulldir) {
7282 if (!is_dir($fulldir)) {
7285 $items = new DirectoryIterator($fulldir);
7286 foreach ($items as $item) {
7287 if ($item->isDot() or !$item->isDir()) {
7290 $pluginname = $item->getFilename();
7291 if (in_array($pluginname, $ignored)) {
7294 if ($pluginname !== clean_param($pluginname, PARAM_SAFEDIR
)) {
7295 // better ignore plugins with problematic names here
7298 $result[$pluginname] = $fulldir.'/'.$pluginname;
7304 //TODO: implement better sorting once we migrated all plugin names to 'pluginname', ksort does not work for unicode, that is why we have to sort by the dir name, not the strings!
7310 * Gets a list of all plugin API functions for given plugin type, function
7311 * name, and filename.
7312 * @param string $plugintype Plugin type, e.g. 'mod' or 'report'
7313 * @param string $function Name of function after the frankenstyle prefix;
7314 * e.g. if the function is called report_courselist_hook then this value
7316 * @param string $file Name of file that includes function within plugin,
7318 * @return Array of plugin frankenstyle (e.g. 'report_courselist', 'mod_forum')
7319 * to valid, existing plugin function name (e.g. 'report_courselist_hook',
7322 function get_plugin_list_with_function($plugintype, $function, $file='lib.php') {
7323 global $CFG; // mandatory in case it is referenced by include()d PHP script
7326 // Loop through list of plugins with given type
7327 $list = get_plugin_list($plugintype);
7328 foreach($list as $plugin => $dir) {
7329 $path = $dir . '/' . $file;
7330 // If file exists, require it and look for function
7331 if (file_exists($path)) {
7332 include_once($path);
7333 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
7334 if (function_exists($fullfunction)) {
7335 // Function exists with standard name. Store, indexed by
7336 // frankenstyle name of plugin
7337 $result[$plugintype . '_' . $plugin] = $fullfunction;
7338 } else if ($plugintype === 'mod') {
7339 // For modules, we also allow plugin without full frankenstyle
7340 // but just starting with the module name
7341 $shortfunction = $plugin . '_' . $function;
7342 if (function_exists($shortfunction)) {
7343 $result[$plugintype . '_' . $plugin] = $shortfunction;
7352 * Lists plugin-like directories within specified directory
7354 * This function was originally used for standard Moodle plugins, please use
7355 * new get_plugin_list() now.
7357 * This function is used for general directory listing and backwards compatility.
7359 * @param string $directory relative directory from root
7360 * @param string $exclude dir name to exclude from the list (defaults to none)
7361 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
7362 * @return array Sorted array of directory names found under the requested parameters
7364 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
7369 if (empty($basedir)) {
7370 $basedir = $CFG->dirroot
.'/'. $directory;
7373 $basedir = $basedir .'/'. $directory;
7376 if (file_exists($basedir) && filetype($basedir) == 'dir') {
7377 $dirhandle = opendir($basedir);
7378 while (false !== ($dir = readdir($dirhandle))) {
7379 $firstchar = substr($dir, 0, 1);
7380 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
7383 if (filetype($basedir .'/'. $dir) != 'dir') {
7388 closedir($dirhandle);
7397 * Invoke plugin's callback functions
7399 * @param string $type plugin type e.g. 'mod'
7400 * @param string $name plugin name
7401 * @param string $feature feature name
7402 * @param string $action feature's action
7403 * @param array $params parameters of callback function, should be an array
7404 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
7407 * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
7409 function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
7410 return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
7414 * Invoke component's callback functions
7416 * @param string $component frankenstyle component name, e.g. 'mod_quiz'
7417 * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
7418 * @param array $params parameters of callback function
7419 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
7422 function component_callback($component, $function, array $params = array(), $default = null) {
7423 global $CFG; // this is needed for require_once() below
7425 $cleancomponent = clean_param($component, PARAM_SAFEDIR
);
7426 if (empty($cleancomponent)) {
7427 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
7429 $component = $cleancomponent;
7431 list($type, $name) = normalize_component($component);
7432 $component = $type . '_' . $name;
7434 $oldfunction = $name.'_'.$function;
7435 $function = $component.'_'.$function;
7437 $dir = get_component_directory($component);
7439 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
7442 // Load library and look for function
7443 if (file_exists($dir.'/lib.php')) {
7444 require_once($dir.'/lib.php');
7447 if (!function_exists($function) and function_exists($oldfunction)) {
7448 // In Moodle 2.2 and greater this will result in a debugging notice however
7449 // for 2.1+ we tolerate it.
7450 $function = $oldfunction;
7453 if (function_exists($function)) {
7454 // Function exists, so just return function result
7455 $ret = call_user_func_array($function, $params);
7456 if (is_null($ret)) {
7466 * Checks whether a plugin supports a specified feature.
7468 * @param string $type Plugin type e.g. 'mod'
7469 * @param string $name Plugin name e.g. 'forum'
7470 * @param string $feature Feature code (FEATURE_xx constant)
7471 * @param mixed $default default value if feature support unknown
7472 * @return mixed Feature result (false if not supported, null if feature is unknown,
7473 * otherwise usually true but may have other feature-specific value such as array)
7475 function plugin_supports($type, $name, $feature, $default = NULL) {
7478 $name = clean_param($name, PARAM_SAFEDIR
); //bit of extra security
7482 if ($type === 'mod') {
7483 // we need this special case because we support subplugins in modules,
7484 // otherwise it would end up in infinite loop
7485 if ($name === 'NEWMODULE') {
7486 //somebody forgot to rename the module template
7489 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
7490 include_once("$CFG->dirroot/mod/$name/lib.php");
7491 $function = $type.'_'.$name.'_supports';
7492 if (!function_exists($function)) {
7493 // legacy non-frankenstyle function name
7494 $function = $name.'_supports';
7499 if (!$path = get_plugin_directory($type, $name)) {
7500 // non existent plugin type
7503 if (file_exists("$path/lib.php")) {
7504 include_once("$path/lib.php");
7505 $function = $type.'_'.$name.'_supports';
7509 if ($function and function_exists($function)) {
7510 $supports = $function($feature);
7511 if (is_null($supports)) {
7512 // plugin does not know - use default
7519 //plugin does not care, so use default
7524 * Returns true if the current version of PHP is greater that the specified one.
7526 * @todo Check PHP version being required here is it too low?
7528 * @param string $version The version of php being tested.
7531 function check_php_version($version='5.2.4') {
7532 return (version_compare(phpversion(), $version) >= 0);
7536 * Checks to see if is the browser operating system matches the specified
7539 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
7542 * @param string $brand The operating system identifier being tested
7543 * @return bool true if the given brand below to the detected operating system
7545 function check_browser_operating_system($brand) {
7546 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7550 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
7558 * Checks to see if is a browser matches the specified
7559 * brand and is equal or better version.
7562 * @param string $brand The browser identifier being tested
7563 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
7564 * @return bool true if the given version is below that of the detected browser
7566 function check_browser_version($brand, $version = null) {
7567 if (empty($_SERVER['HTTP_USER_AGENT'])) {
7571 $agent = $_SERVER['HTTP_USER_AGENT'];
7575 case 'Camino': /// OSX browser using Gecke engine
7576 if (strpos($agent, 'Camino') === false) {
7579 if (empty($version)) {
7580 return true; // no version specified
7582 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
7583 if (version_compare($match[1], $version) >= 0) {
7590 case 'Firefox': /// Mozilla Firefox browsers
7591 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
7594 if (empty($version)) {
7595 return true; // no version specified
7597 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
7598 if (version_compare($match[2], $version) >= 0) {
7605 case 'Gecko': /// Gecko based browsers
7606 if (empty($version) and substr_count($agent, 'Camino')) {
7607 // MacOS X Camino support
7608 $version = 20041110;
7611 // the proper string - Gecko/CCYYMMDD Vendor/Version
7612 // Faster version and work-a-round No IDN problem.
7613 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
7614 if ($match[1] > $version) {
7621 case 'MSIE': /// Internet Explorer
7622 if (strpos($agent, 'Opera') !== false) { // Reject Opera
7625 // in case of IE we have to deal with BC of the version parameter
7626 if (is_null($version)) {
7627 $version = 5.5; // anything older is not considered a browser at all!
7630 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
7631 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
7632 if (version_compare($match[1], $version) >= 0) {
7639 case 'Opera': /// Opera
7640 if (strpos($agent, 'Opera') === false) {
7643 if (empty($version)) {
7644 return true; // no version specified
7646 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
7647 if (version_compare($match[1], $version) >= 0) {
7654 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
7655 if (strpos($agent, 'AppleWebKit') === false) {
7658 if (empty($version)) {
7659 return true; // no version specified
7661 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7662 if (version_compare($match[1], $version) >= 0) {
7669 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
7670 if (strpos($agent, 'AppleWebKit') === false) {
7673 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
7674 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
7677 if (strpos($agent, 'Shiira')) { // Reject Shiira
7680 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
7683 if (strpos($agent, 'Android')) { // Reject Androids too
7686 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
7687 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
7690 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
7694 if (empty($version)) {
7695 return true; // no version specified
7697 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7698 if (version_compare($match[1], $version) >= 0) {
7706 if (strpos($agent, 'Chrome') === false) {
7709 if (empty($version)) {
7710 return true; // no version specified
7712 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
7713 if (version_compare($match[1], $version) >= 0) {
7720 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
7721 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
7724 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
7727 if (empty($version)) {
7728 return true; // no version specified
7730 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7731 if (version_compare($match[1], $version) >= 0) {
7738 case 'WebKit Android': /// WebKit browser on Android
7739 if (strpos($agent, 'Linux; U; Android') === false) {
7742 if (empty($version)) {
7743 return true; // no version specified
7745 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
7746 if (version_compare($match[1], $version) >= 0) {
7758 * Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
7759 * an optional admin specified regular expression. If enabledevicedetection is set to no or not set
7760 * it returns default
7762 * @return string device type
7764 function get_device_type() {
7767 if (empty($CFG->enabledevicedetection
) ||
empty($_SERVER['HTTP_USER_AGENT'])) {
7771 $useragent = $_SERVER['HTTP_USER_AGENT'];
7773 if (!empty($CFG->devicedetectregex
)) {
7774 $regexes = json_decode($CFG->devicedetectregex
);
7776 foreach ($regexes as $value=>$regex) {
7777 if (preg_match($regex, $useragent)) {
7783 //mobile detection PHP direct copy from open source detectmobilebrowser.com
7784 $phonesregex = '/android .+ mobile|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i';
7785 $modelsregex = '/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i';
7786 if (preg_match($phonesregex,$useragent) ||
preg_match($modelsregex,substr($useragent, 0, 4))){
7790 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
7791 if (preg_match($tabletregex, $useragent)) {
7795 // Safe way to check for IE6 and not get false positives for some IE 7/8 users
7796 if (substr($_SERVER['HTTP_USER_AGENT'], 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
7804 * Returns a list of the device types supporting by Moodle
7806 * @param boolean $incusertypes includes types specified using the devicedetectregex admin setting
7807 * @return array $types
7809 function get_device_type_list($incusertypes = true) {
7812 $types = array('default', 'legacy', 'mobile', 'tablet');
7814 if ($incusertypes && !empty($CFG->devicedetectregex
)) {
7815 $regexes = json_decode($CFG->devicedetectregex
);
7817 foreach ($regexes as $value => $regex) {
7826 * Returns the theme selected for a particular device or false if none selected.
7828 * @param string $devicetype
7829 * @return string|false The name of the theme to use for the device or the false if not set
7831 function get_selected_theme_for_device_type($devicetype = null) {
7834 if (empty($devicetype)) {
7835 $devicetype = get_user_device_type();
7838 $themevarname = get_device_cfg_var_name($devicetype);
7839 if (empty($CFG->$themevarname)) {
7843 return $CFG->$themevarname;
7847 * Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
7849 * @param string $devicetype
7850 * @return string The config variable to use to determine the theme
7852 function get_device_cfg_var_name($devicetype = null) {
7853 if ($devicetype == 'default' ||
empty($devicetype)) {
7857 return 'theme' . $devicetype;
7861 * Allows the user to switch the device they are seeing the theme for.
7862 * This allows mobile users to switch back to the default theme, or theme for any other device.
7864 * @param string $newdevice The device the user is currently using.
7865 * @return string The device the user has switched to
7867 function set_user_device_type($newdevice) {
7870 $devicetype = get_device_type();
7871 $devicetypes = get_device_type_list();
7873 if ($newdevice == $devicetype) {
7874 unset_user_preference('switchdevice'.$devicetype);
7875 } else if (in_array($newdevice, $devicetypes)) {
7876 set_user_preference('switchdevice'.$devicetype, $newdevice);
7881 * Returns the device the user is currently using, or if the user has chosen to switch devices
7882 * for the current device type the type they have switched to.
7884 * @return string The device the user is currently using or wishes to use
7886 function get_user_device_type() {
7887 $device = get_device_type();
7888 $switched = get_user_preferences('switchdevice'.$device, false);
7889 if ($switched != false) {
7896 * Returns one or several CSS class names that match the user's browser. These can be put
7897 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
7899 * @return array An array of browser version classes
7901 function get_browser_version_classes() {
7904 if (check_browser_version("MSIE", "0")) {
7906 if (check_browser_version("MSIE", 9)) {
7908 } else if (check_browser_version("MSIE", 8)) {
7910 } elseif (check_browser_version("MSIE", 7)) {
7912 } elseif (check_browser_version("MSIE", 6)) {
7916 } else if (check_browser_version("Firefox") ||
check_browser_version("Gecko") ||
check_browser_version("Camino")) {
7917 $classes[] = 'gecko';
7918 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
7919 $classes[] = "gecko{$matches[1]}{$matches[2]}";
7922 } else if (check_browser_version("WebKit")) {
7923 $classes[] = 'safari';
7924 if (check_browser_version("Safari iOS")) {
7927 } else if (check_browser_version("WebKit Android")) {
7928 $classes[] = 'android';
7931 } else if (check_browser_version("Opera")) {
7932 $classes[] = 'opera';
7940 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
7942 * @return bool True for yes, false for no
7944 function can_use_rotated_text() {
7946 return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader
;;
7950 * Hack to find out the GD version by parsing phpinfo output
7952 * @return int GD version (1, 2, or 0)
7954 function check_gd_version() {
7957 if (function_exists('gd_info')){
7958 $gd_info = gd_info();
7959 if (substr_count($gd_info['GD Version'], '2.')) {
7961 } else if (substr_count($gd_info['GD Version'], '1.')) {
7967 phpinfo(INFO_MODULES
);
7968 $phpinfo = ob_get_contents();
7971 $phpinfo = explode("\n", $phpinfo);
7974 foreach ($phpinfo as $text) {
7975 $parts = explode('</td>', $text);
7976 foreach ($parts as $key => $val) {
7977 $parts[$key] = trim(strip_tags($val));
7979 if ($parts[0] == 'GD Version') {
7980 if (substr_count($parts[1], '2.0')) {
7983 $gdversion = intval($parts[1]);
7988 return $gdversion; // 1, 2 or 0
7992 * Determine if moodle installation requires update
7994 * Checks version numbers of main code and all modules to see
7995 * if there are any mismatches
8001 function moodle_needs_upgrading() {
8002 global $CFG, $DB, $OUTPUT;
8004 if (empty($CFG->version
)) {
8008 // main versio nfirst
8010 include($CFG->dirroot
.'/version.php'); // defines $version and upgrades
8011 if ($version > $CFG->version
) {
8016 $mods = get_plugin_list('mod');
8017 $installed = $DB->get_records('modules', array(), '', 'name, version');
8018 foreach ($mods as $mod => $fullmod) {
8019 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
8022 $module = new stdClass();
8023 if (!is_readable($fullmod.'/version.php')) {
8026 include($fullmod.'/version.php'); // defines $module with version etc
8027 if (empty($installed[$mod])) {
8029 } else if ($module->version
> $installed[$mod]->version
) {
8036 $blocks = get_plugin_list('block');
8037 $installed = $DB->get_records('block', array(), '', 'name, version');
8038 require_once($CFG->dirroot
.'/blocks/moodleblock.class.php');
8039 foreach ($blocks as $blockname=>$fullblock) {
8040 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
8043 if (!is_readable($fullblock.'/version.php')) {
8046 $plugin = new stdClass();
8047 $plugin->version
= NULL;
8048 include($fullblock.'/version.php');
8049 if (empty($installed[$blockname])) {
8051 } else if ($plugin->version
> $installed[$blockname]->version
) {
8057 // now the rest of plugins
8058 $plugintypes = get_plugin_types();
8059 unset($plugintypes['mod']);
8060 unset($plugintypes['block']);
8061 foreach ($plugintypes as $type=>$unused) {
8062 $plugs = get_plugin_list($type);
8063 foreach ($plugs as $plug=>$fullplug) {
8064 $component = $type.'_'.$plug;
8065 if (!is_readable($fullplug.'/version.php')) {
8068 $plugin = new stdClass();
8069 include($fullplug.'/version.php'); // defines $plugin with version etc
8070 $installedversion = get_config($component, 'version');
8071 if (empty($installedversion)) { // new installation
8073 } else if ($installedversion < $plugin->version
) { // upgrade
8083 * Sets maximum expected time needed for upgrade task.
8084 * Please always make sure that upgrade will not run longer!
8086 * The script may be automatically aborted if upgrade times out.
8089 * @param int $max_execution_time in seconds (can not be less than 60 s)
8091 function upgrade_set_timeout($max_execution_time=300) {
8094 if (!isset($CFG->upgraderunning
) or $CFG->upgraderunning
< time()) {
8095 $upgraderunning = get_config(null, 'upgraderunning');
8097 $upgraderunning = $CFG->upgraderunning
;
8100 if (!$upgraderunning) {
8101 // upgrade not running or aborted
8102 print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
8106 if ($max_execution_time < 60) {
8107 // protection against 0 here
8108 $max_execution_time = 60;
8111 $expected_end = time() +
$max_execution_time;
8113 if ($expected_end < $upgraderunning +
10 and $expected_end > $upgraderunning - 10) {
8114 // no need to store new end, it is nearly the same ;-)
8119 // there is no point in timing out of CLI scripts, admins can stop them if necessary
8122 set_time_limit($max_execution_time);
8124 set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
8127 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
8130 * Notify admin users or admin user of any failed logins (since last notification).
8132 * Note that this function must be only executed from the cron script
8133 * It uses the cache_flags system to store temporary records, deleting them
8134 * by name before finishing
8139 * @return bool True if executed, false if not
8141 function notify_login_failures() {
8142 global $CFG, $DB, $OUTPUT;
8144 if (empty($CFG->notifyloginfailures
)) {
8148 if (empty($CFG->lastnotifyfailure
)) {
8149 $CFG->lastnotifyfailure
=0;
8152 $recip = get_users_from_config($CFG->notifyloginfailures
, 'moodle/site:config');
8154 // If it has been less than an hour, or if there are no recipients, don't execute.
8155 if (((time() - HOURSECS
) < $CFG->lastnotifyfailure
) ||
!is_array($recip) ||
count($recip) <= 0) {
8159 // we need to deal with the threshold stuff first.
8160 if (empty($CFG->notifyloginthreshold
)) {
8161 $CFG->notifyloginthreshold
= 10; // default to something sensible.
8164 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
8165 /// and insert them into the cache_flags temp table
8166 $sql = "SELECT ip, COUNT(*)
8168 WHERE module = 'login' AND action = 'error'
8171 HAVING COUNT(*) >= ?";
8172 $params = array($CFG->lastnotifyfailure
, $CFG->notifyloginthreshold
);
8173 $rs = $DB->get_recordset_sql($sql, $params);
8174 foreach ($rs as $iprec) {
8175 if (!empty($iprec->ip
)) {
8176 set_cache_flag('login_failure_by_ip', $iprec->ip
, '1', 0);
8181 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
8182 /// and insert them into the cache_flags temp table
8183 $sql = "SELECT info, count(*)
8185 WHERE module = 'login' AND action = 'error'
8188 HAVING count(*) >= ?";
8189 $params = array($CFG->lastnotifyfailure
, $CFG->notifyloginthreshold
);
8190 $rs = $DB->get_recordset_sql($sql, $params);
8191 foreach ($rs as $inforec) {
8192 if (!empty($inforec->info
)) {
8193 set_cache_flag('login_failure_by_info', $inforec->info
, '1', 0);
8198 /// Now, select all the login error logged records belonging to the ips and infos
8199 /// since lastnotifyfailure, that we have stored in the cache_flags table
8200 $sql = "SELECT * FROM (
8201 SELECT l.*, u.firstname, u.lastname
8203 JOIN {cache_flags} cf ON l.ip = cf.name
8204 LEFT JOIN {user} u ON l.userid = u.id
8205 WHERE l.module = 'login' AND l.action = 'error'
8207 AND cf.flagtype = 'login_failure_by_ip'
8209 SELECT l.*, u.firstname, u.lastname
8211 JOIN {cache_flags} cf ON l.info = cf.name
8212 LEFT JOIN {user} u ON l.userid = u.id
8213 WHERE l.module = 'login' AND l.action = 'error'
8215 AND cf.flagtype = 'login_failure_by_info') t
8216 ORDER BY t.time DESC";
8217 $params = array($CFG->lastnotifyfailure
, $CFG->lastnotifyfailure
);
8219 /// Init some variables
8222 /// Iterate over the logs recordset
8223 $rs = $DB->get_recordset_sql($sql, $params);
8224 foreach ($rs as $log) {
8225 $log->time
= userdate($log->time
);
8226 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
8231 // If we have something useful to report.
8234 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname
));
8235 /// Calculate the complete body of notification (start + messages + end)
8236 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot
) .
8237 (($CFG->lastnotifyfailure
!= 0) ?
'('.userdate($CFG->lastnotifyfailure
).')' : '')."\n\n" .
8239 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot
)."\n\n";
8241 /// For each destination, send mail
8242 mtrace('Emailing admins about '. $count .' failed login attempts');
8243 foreach ($recip as $admin) {
8244 //emailing the admins directly rather than putting these through the messaging system
8245 email_to_user($admin,get_admin(), $subject, $body);
8248 /// Update lastnotifyfailure with current time
8249 set_config('lastnotifyfailure', time());
8252 /// Finally, delete all the temp records we have created in cache_flags
8253 $DB->delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
8259 * Sets the system locale
8261 * @todo Finish documenting this function
8264 * @param string $locale Can be used to force a locale
8266 function moodle_setlocale($locale='') {
8269 static $currentlocale = ''; // last locale caching
8271 $oldlocale = $currentlocale;
8273 /// Fetch the correct locale based on ostype
8274 if ($CFG->ostype
== 'WINDOWS') {
8275 $stringtofetch = 'localewin';
8277 $stringtofetch = 'locale';
8280 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
8281 if (!empty($locale)) {
8282 $currentlocale = $locale;
8283 } else if (!empty($CFG->locale
)) { // override locale for all language packs
8284 $currentlocale = $CFG->locale
;
8286 $currentlocale = get_string($stringtofetch, 'langconfig');
8289 /// do nothing if locale already set up
8290 if ($oldlocale == $currentlocale) {
8294 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
8295 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
8296 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
8298 /// Get current values
8299 $monetary= setlocale (LC_MONETARY
, 0);
8300 $numeric = setlocale (LC_NUMERIC
, 0);
8301 $ctype = setlocale (LC_CTYPE
, 0);
8302 if ($CFG->ostype
!= 'WINDOWS') {
8303 $messages= setlocale (LC_MESSAGES
, 0);
8305 /// Set locale to all
8306 setlocale (LC_ALL
, $currentlocale);
8308 setlocale (LC_MONETARY
, $monetary);
8309 setlocale (LC_NUMERIC
, $numeric);
8310 if ($CFG->ostype
!= 'WINDOWS') {
8311 setlocale (LC_MESSAGES
, $messages);
8313 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
8314 setlocale (LC_CTYPE
, $ctype);
8319 * Converts string to lowercase using most compatible function available.
8321 * @todo Remove this function when no longer in use
8322 * @deprecated Use textlib->strtolower($text) instead.
8324 * @param string $string The string to convert to all lowercase characters.
8325 * @param string $encoding The encoding on the string.
8328 function moodle_strtolower ($string, $encoding='') {
8330 //If not specified use utf8
8331 if (empty($encoding)) {
8332 $encoding = 'UTF-8';
8335 $textlib = textlib_get_instance();
8337 return $textlib->strtolower($string, $encoding);
8341 * Count words in a string.
8343 * Words are defined as things between whitespace.
8345 * @param string $string The text to be searched for words.
8346 * @return int The count of words in the specified string
8348 function count_words($string) {
8349 $string = strip_tags($string);
8350 return count(preg_split("/\w\b/", $string)) - 1;
8353 /** Count letters in a string.
8355 * Letters are defined as chars not in tags and different from whitespace.
8357 * @param string $string The text to be searched for letters.
8358 * @return int The count of letters in the specified text.
8360 function count_letters($string) {
8361 /// Loading the textlib singleton instance. We are going to need it.
8362 $textlib = textlib_get_instance();
8364 $string = strip_tags($string); // Tags are out now
8365 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
8367 return $textlib->strlen($string);
8371 * Generate and return a random string of the specified length.
8373 * @param int $length The length of the string to be created.
8376 function random_string ($length=15) {
8377 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8378 $pool .= 'abcdefghijklmnopqrstuvwxyz';
8379 $pool .= '0123456789';
8380 $poollen = strlen($pool);
8381 mt_srand ((double) microtime() * 1000000);
8383 for ($i = 0; $i < $length; $i++
) {
8384 $string .= substr($pool, (mt_rand()%
($poollen)), 1);
8390 * Generate a complex random string (useful for md5 salts)
8392 * This function is based on the above {@link random_string()} however it uses a
8393 * larger pool of characters and generates a string between 24 and 32 characters
8395 * @param int $length Optional if set generates a string to exactly this length
8398 function complex_random_string($length=null) {
8399 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
8400 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
8401 $poollen = strlen($pool);
8402 mt_srand ((double) microtime() * 1000000);
8403 if ($length===null) {
8404 $length = floor(rand(24,32));
8407 for ($i = 0; $i < $length; $i++
) {
8408 $string .= $pool[(mt_rand()%
$poollen)];
8414 * Given some text (which may contain HTML) and an ideal length,
8415 * this function truncates the text neatly on a word boundary if possible
8418 * @param string $text - text to be shortened
8419 * @param int $ideal - ideal string length
8420 * @param boolean $exact if false, $text will not be cut mid-word
8421 * @param string $ending The string to append if the passed string is truncated
8422 * @return string $truncate - shortened string
8424 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
8428 // if the plain text is shorter than the maximum length, return the whole text
8429 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
8433 // Splits on HTML tags. Each open/close/empty tag will be the first thing
8434 // and only tag in its 'line'
8435 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER
);
8437 $total_length = strlen($ending);
8440 // This array stores information about open and close tags and their position
8441 // in the truncated string. Each item in the array is an object with fields
8442 // ->open (true if open), ->tag (tag name in lower case), and ->pos
8443 // (byte position in truncated text)
8444 $tagdetails = array();
8446 foreach ($lines as $line_matchings) {
8447 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
8448 if (!empty($line_matchings[1])) {
8449 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
8450 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
8452 // if tag is a closing tag (f.e. </b>)
8453 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
8454 // record closing tag
8455 $tagdetails[] = (object)array('open'=>false,
8456 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8457 // if tag is an opening tag (f.e. <b>)
8458 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
8459 // record opening tag
8460 $tagdetails[] = (object)array('open'=>true,
8461 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
8463 // add html-tag to $truncate'd text
8464 $truncate .= $line_matchings[1];
8467 // calculate the length of the plain text part of the line; handle entities as one character
8468 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
8469 if ($total_length+
$content_length > $ideal) {
8470 // the number of characters which are left
8471 $left = $ideal - $total_length;
8472 $entities_length = 0;
8473 // search for html entities
8474 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
)) {
8475 // calculate the real length of all entities in the legal range
8476 foreach ($entities[0] as $entity) {
8477 if ($entity[1]+
1-$entities_length <= $left) {
8479 $entities_length +
= strlen($entity[0]);
8481 // no more characters left
8486 $truncate .= substr($line_matchings[2], 0, $left+
$entities_length);
8487 // maximum length is reached, so get off the loop
8490 $truncate .= $line_matchings[2];
8491 $total_length +
= $content_length;
8494 // if the maximum length is reached, get off the loop
8495 if($total_length >= $ideal) {
8500 // if the words shouldn't be cut in the middle...
8502 // ...search the last occurence of a space...
8503 for ($k=strlen($truncate);$k>0;$k--) {
8504 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
8505 if ($char == '.' or $char == ' ') {
8508 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
8509 $breakpos = $k; // can be truncated at any UTF-8
8510 break; // character boundary.
8515 if (isset($breakpos)) {
8516 // ...and cut the text in this position
8517 $truncate = substr($truncate, 0, $breakpos);
8521 // add the defined ending to the text
8522 $truncate .= $ending;
8524 // Now calculate the list of open html tags based on the truncate position
8525 $open_tags = array();
8526 foreach ($tagdetails as $taginfo) {
8527 if(isset($breakpos) && $taginfo->pos
>= $breakpos) {
8528 // Don't include tags after we made the break!
8531 if($taginfo->open
) {
8532 // add tag to the beginning of $open_tags list
8533 array_unshift($open_tags, $taginfo->tag
);
8535 $pos = array_search($taginfo->tag
, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
8536 if ($pos !== false) {
8537 unset($open_tags[$pos]);
8542 // close all unclosed html-tags
8543 foreach ($open_tags as $tag) {
8544 $truncate .= '</' . $tag . '>';
8552 * Given dates in seconds, how many weeks is the date from startdate
8553 * The first week is 1, the second 2 etc ...
8555 * @todo Finish documenting this function
8558 * @param int $startdate Timestamp for the start date
8559 * @param int $thedate Timestamp for the end date
8562 function getweek ($startdate, $thedate) {
8563 if ($thedate < $startdate) { // error
8567 return floor(($thedate - $startdate) / WEEKSECS
) +
1;
8571 * returns a randomly generated password of length $maxlen. inspired by
8573 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
8574 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
8577 * @param int $maxlen The maximum size of the password being generated.
8580 function generate_password($maxlen=10) {
8583 if (empty($CFG->passwordpolicy
)) {
8584 $fillers = PASSWORD_DIGITS
;
8585 $wordlist = file($CFG->wordlist
);
8586 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8587 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
8588 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
8589 $password = $word1 . $filler1 . $word2;
8591 $minlen = !empty($CFG->minpasswordlength
) ?
$CFG->minpasswordlength
: 0;
8592 $digits = $CFG->minpassworddigits
;
8593 $lower = $CFG->minpasswordlower
;
8594 $upper = $CFG->minpasswordupper
;
8595 $nonalphanum = $CFG->minpasswordnonalphanum
;
8596 $total = $lower +
$upper +
$digits +
$nonalphanum;
8597 // minlength should be the greater one of the two ( $minlen and $total )
8598 $minlen = $minlen < $total ?
$total : $minlen;
8599 // maxlen can never be smaller than minlen
8600 $maxlen = $minlen > $maxlen ?
$minlen : $maxlen;
8601 $additional = $maxlen - $total;
8603 // Make sure we have enough characters to fulfill
8604 // complexity requirements
8605 $passworddigits = PASSWORD_DIGITS
;
8606 while ($digits > strlen($passworddigits)) {
8607 $passworddigits .= PASSWORD_DIGITS
;
8609 $passwordlower = PASSWORD_LOWER
;
8610 while ($lower > strlen($passwordlower)) {
8611 $passwordlower .= PASSWORD_LOWER
;
8613 $passwordupper = PASSWORD_UPPER
;
8614 while ($upper > strlen($passwordupper)) {
8615 $passwordupper .= PASSWORD_UPPER
;
8617 $passwordnonalphanum = PASSWORD_NONALPHANUM
;
8618 while ($nonalphanum > strlen($passwordnonalphanum)) {
8619 $passwordnonalphanum .= PASSWORD_NONALPHANUM
;
8622 // Now mix and shuffle it all
8623 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
8624 substr(str_shuffle ($passwordupper), 0, $upper) .
8625 substr(str_shuffle ($passworddigits), 0, $digits) .
8626 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
8627 substr(str_shuffle ($passwordlower .
8630 $passwordnonalphanum), 0 , $additional));
8633 return substr ($password, 0, $maxlen);
8637 * Given a float, prints it nicely.
8638 * Localized floats must not be used in calculations!
8640 * @param float $float The float to print
8641 * @param int $places The number of decimal places to print.
8642 * @param bool $localized use localized decimal separator
8643 * @return string locale float
8645 function format_float($float, $decimalpoints=1, $localized=true) {
8646 if (is_null($float)) {
8650 return number_format($float, $decimalpoints, get_string('decsep', 'langconfig'), '');
8652 return number_format($float, $decimalpoints, '.', '');
8657 * Converts locale specific floating point/comma number back to standard PHP float value
8658 * Do NOT try to do any math operations before this conversion on any user submitted floats!
8660 * @param string $locale_float locale aware float representation
8663 function unformat_float($locale_float) {
8664 $locale_float = trim($locale_float);
8666 if ($locale_float == '') {
8670 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
8672 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
8676 * Given a simple array, this shuffles it up just like shuffle()
8677 * Unlike PHP's shuffle() this function works on any machine.
8679 * @param array $array The array to be rearranged
8682 function swapshuffle($array) {
8684 srand ((double) microtime() * 10000000);
8685 $last = count($array) - 1;
8686 for ($i=0;$i<=$last;$i++
) {
8687 $from = rand(0,$last);
8689 $array[$i] = $array[$from];
8690 $array[$from] = $curr;
8696 * Like {@link swapshuffle()}, but works on associative arrays
8698 * @param array $array The associative array to be rearranged
8701 function swapshuffle_assoc($array) {
8703 $newarray = array();
8704 $newkeys = swapshuffle(array_keys($array));
8706 foreach ($newkeys as $newkey) {
8707 $newarray[$newkey] = $array[$newkey];
8713 * Given an arbitrary array, and a number of draws,
8714 * this function returns an array with that amount
8715 * of items. The indexes are retained.
8717 * @todo Finish documenting this function
8719 * @param array $array
8723 function draw_rand_array($array, $draws) {
8724 srand ((double) microtime() * 10000000);
8728 $last = count($array);
8730 if ($draws > $last) {
8734 while ($draws > 0) {
8737 $keys = array_keys($array);
8738 $rand = rand(0, $last);
8740 $return[$keys[$rand]] = $array[$keys[$rand]];
8741 unset($array[$keys[$rand]]);
8750 * Calculate the difference between two microtimes
8752 * @param string $a The first Microtime
8753 * @param string $b The second Microtime
8756 function microtime_diff($a, $b) {
8757 list($a_dec, $a_sec) = explode(' ', $a);
8758 list($b_dec, $b_sec) = explode(' ', $b);
8759 return $b_sec - $a_sec +
$b_dec - $a_dec;
8763 * Given a list (eg a,b,c,d,e) this function returns
8764 * an array of 1->a, 2->b, 3->c etc
8766 * @param string $list The string to explode into array bits
8767 * @param string $separator The separator used within the list string
8768 * @return array The now assembled array
8770 function make_menu_from_list($list, $separator=',') {
8772 $array = array_reverse(explode($separator, $list), true);
8773 foreach ($array as $key => $item) {
8774 $outarray[$key+
1] = trim($item);
8780 * Creates an array that represents all the current grades that
8781 * can be chosen using the given grading type.
8784 * are scales, zero is no grade, and positive numbers are maximum
8787 * @todo Finish documenting this function or better deprecated this completely!
8789 * @param int $gradingtype
8792 function make_grades_menu($gradingtype) {
8796 if ($gradingtype < 0) {
8797 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
8798 return make_menu_from_list($scale->scale
);
8800 } else if ($gradingtype > 0) {
8801 for ($i=$gradingtype; $i>=0; $i--) {
8802 $grades[$i] = $i .' / '. $gradingtype;
8810 * This function returns the number of activities
8811 * using scaleid in a courseid
8813 * @todo Finish documenting this function
8817 * @param int $courseid ?
8818 * @param int $scaleid ?
8821 function course_scale_used($courseid, $scaleid) {
8826 if (!empty($scaleid)) {
8827 if ($cms = get_course_mods($courseid)) {
8828 foreach ($cms as $cm) {
8829 //Check cm->name/lib.php exists
8830 if (file_exists($CFG->dirroot
.'/mod/'.$cm->modname
.'/lib.php')) {
8831 include_once($CFG->dirroot
.'/mod/'.$cm->modname
.'/lib.php');
8832 $function_name = $cm->modname
.'_scale_used';
8833 if (function_exists($function_name)) {
8834 if ($function_name($cm->instance
,$scaleid)) {
8842 // check if any course grade item makes use of the scale
8843 $return +
= $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
8845 // check if any outcome in the course makes use of the scale
8846 $return +
= $DB->count_records_sql("SELECT COUNT('x')
8847 FROM {grade_outcomes_courses} goc,
8849 WHERE go.id = goc.outcomeid
8850 AND go.scaleid = ? AND goc.courseid = ?",
8851 array($scaleid, $courseid));
8857 * This function returns the number of activities
8858 * using scaleid in the entire site
8860 * @param int $scaleid
8861 * @param array $courses
8864 function site_scale_used($scaleid, &$courses) {
8867 if (!is_array($courses) ||
count($courses) == 0) {
8868 $courses = get_courses("all",false,"c.id,c.shortname");
8871 if (!empty($scaleid)) {
8872 if (is_array($courses) && count($courses) > 0) {
8873 foreach ($courses as $course) {
8874 $return +
= course_scale_used($course->id
,$scaleid);
8882 * make_unique_id_code
8884 * @todo Finish documenting this function
8887 * @param string $extra Extra string to append to the end of the code
8890 function make_unique_id_code($extra='') {
8892 $hostname = 'unknownhost';
8893 if (!empty($_SERVER['HTTP_HOST'])) {
8894 $hostname = $_SERVER['HTTP_HOST'];
8895 } else if (!empty($_ENV['HTTP_HOST'])) {
8896 $hostname = $_ENV['HTTP_HOST'];
8897 } else if (!empty($_SERVER['SERVER_NAME'])) {
8898 $hostname = $_SERVER['SERVER_NAME'];
8899 } else if (!empty($_ENV['SERVER_NAME'])) {
8900 $hostname = $_ENV['SERVER_NAME'];
8903 $date = gmdate("ymdHis");
8905 $random = random_string(6);
8908 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
8910 return $hostname .'+'. $date .'+'. $random;
8916 * Function to check the passed address is within the passed subnet
8918 * The parameter is a comma separated string of subnet definitions.
8919 * Subnet strings can be in one of three formats:
8920 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
8921 * 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group)
8922 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
8923 * Code for type 1 modified from user posted comments by mediator at
8924 * {@link http://au.php.net/manual/en/function.ip2long.php}
8926 * @param string $addr The address you are checking
8927 * @param string $subnetstr The string of subnet addresses
8930 function address_in_subnet($addr, $subnetstr) {
8932 if ($addr == '0.0.0.0') {
8935 $subnets = explode(',', $subnetstr);
8937 $addr = trim($addr);
8938 $addr = cleanremoteaddr($addr, false); // normalise
8939 if ($addr === null) {
8942 $addrparts = explode(':', $addr);
8944 $ipv6 = strpos($addr, ':');
8946 foreach ($subnets as $subnet) {
8947 $subnet = trim($subnet);
8948 if ($subnet === '') {
8952 if (strpos($subnet, '/') !== false) {
8953 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
8954 list($ip, $mask) = explode('/', $subnet);
8955 $mask = trim($mask);
8956 if (!is_number($mask)) {
8957 continue; // incorect mask number, eh?
8959 $ip = cleanremoteaddr($ip, false); // normalise
8963 if (strpos($ip, ':') !== false) {
8968 if ($mask > 128 or $mask < 0) {
8969 continue; // nonsense
8972 return true; // any address
8975 if ($ip === $addr) {
8980 $ipparts = explode(':', $ip);
8981 $modulo = $mask %
16;
8982 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
8983 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
8984 if (implode(':', $ipnet) === implode(':', $addrnet)) {
8988 $pos = ($mask-$modulo)/16;
8989 $ipnet = hexdec($ipparts[$pos]);
8990 $addrnet = hexdec($addrparts[$pos]);
8991 $mask = 0xffff << (16 - $modulo);
8992 if (($addrnet & $mask) == ($ipnet & $mask)) {
9002 if ($mask > 32 or $mask < 0) {
9003 continue; // nonsense
9009 if ($ip === $addr) {
9014 $mask = 0xffffffff << (32 - $mask);
9015 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
9020 } else if (strpos($subnet, '-') !== false) {
9021 /// 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy ...a range of IP addresses in the last group.
9022 $parts = explode('-', $subnet);
9023 if (count($parts) != 2) {
9027 if (strpos($subnet, ':') !== false) {
9032 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9033 if ($ipstart === null) {
9036 $ipparts = explode(':', $ipstart);
9037 $start = hexdec(array_pop($ipparts));
9038 $ipparts[] = trim($parts[1]);
9039 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
9040 if ($ipend === null) {
9044 $ipnet = implode(':', $ipparts);
9045 if (strpos($addr, $ipnet) !== 0) {
9048 $ipparts = explode(':', $ipend);
9049 $end = hexdec($ipparts[7]);
9051 $addrend = hexdec($addrparts[7]);
9053 if (($addrend >= $start) and ($addrend <= $end)) {
9062 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9063 if ($ipstart === null) {
9066 $ipparts = explode('.', $ipstart);
9067 $ipparts[3] = trim($parts[1]);
9068 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
9069 if ($ipend === null) {
9073 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
9079 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
9080 if (strpos($subnet, ':') !== false) {
9085 $parts = explode(':', $subnet);
9086 $count = count($parts);
9087 if ($parts[$count-1] === '') {
9088 unset($parts[$count-1]); // trim trailing :
9090 $subnet = implode('.', $parts);
9092 $isip = cleanremoteaddr($subnet, false); // normalise
9093 if ($isip !== null) {
9094 if ($isip === $addr) {
9098 } else if ($count > 8) {
9101 $zeros = array_fill(0, 8-$count, '0');
9102 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
9103 if (address_in_subnet($addr, $subnet)) {
9112 $parts = explode('.', $subnet);
9113 $count = count($parts);
9114 if ($parts[$count-1] === '') {
9115 unset($parts[$count-1]); // trim trailing .
9117 $subnet = implode('.', $parts);
9120 $subnet = cleanremoteaddr($subnet, false); // normalise
9121 if ($subnet === $addr) {
9125 } else if ($count > 4) {
9128 $zeros = array_fill(0, 4-$count, '0');
9129 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
9130 if (address_in_subnet($addr, $subnet)) {
9141 * For outputting debugging info
9144 * @param string $string The string to write
9145 * @param string $eol The end of line char(s) to use
9146 * @param string $sleep Period to make the application sleep
9147 * This ensures any messages have time to display before redirect
9149 function mtrace($string, $eol="\n", $sleep=0) {
9151 if (defined('STDOUT')) {
9152 fwrite(STDOUT
, $string.$eol);
9154 echo $string . $eol;
9159 //delay to keep message on user's screen in case of subsequent redirect
9166 * Replace 1 or more slashes or backslashes to 1 slash
9168 * @param string $path The path to strip
9169 * @return string the path with double slashes removed
9171 function cleardoubleslashes ($path) {
9172 return preg_replace('/(\/|\\\){1,}/','/',$path);
9176 * Is current ip in give list?
9178 * @param string $list
9181 function remoteip_in_list($list){
9183 $client_ip = getremoteaddr(null);
9186 // ensure access on cli
9190 $list = explode("\n", $list);
9191 foreach($list as $subnet) {
9192 $subnet = trim($subnet);
9193 if (address_in_subnet($client_ip, $subnet)) {
9202 * Returns most reliable client address
9205 * @param string $default If an address can't be determined, then return this
9206 * @return string The remote IP address
9208 function getremoteaddr($default='0.0.0.0') {
9211 if (empty($CFG->getremoteaddrconf
)) {
9212 // This will happen, for example, before just after the upgrade, as the
9213 // user is redirected to the admin screen.
9214 $variablestoskip = 0;
9216 $variablestoskip = $CFG->getremoteaddrconf
;
9218 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP
)) {
9219 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
9220 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
9221 return $address ?
$address : $default;
9224 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR
)) {
9225 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
9226 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
9227 return $address ?
$address : $default;
9230 if (!empty($_SERVER['REMOTE_ADDR'])) {
9231 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
9232 return $address ?
$address : $default;
9239 * Cleans an ip address. Internal addresses are now allowed.
9240 * (Originally local addresses were not allowed.)
9242 * @param string $addr IPv4 or IPv6 address
9243 * @param bool $compress use IPv6 address compression
9244 * @return string normalised ip address string, null if error
9246 function cleanremoteaddr($addr, $compress=false) {
9247 $addr = trim($addr);
9249 //TODO: maybe add a separate function is_addr_public() or something like this
9251 if (strpos($addr, ':') !== false) {
9253 $parts = explode(':', $addr);
9254 $count = count($parts);
9256 if (strpos($parts[$count-1], '.') !== false) {
9257 //legacy ipv4 notation
9258 $last = array_pop($parts);
9259 $ipv4 = cleanremoteaddr($last, true);
9260 if ($ipv4 === null) {
9263 $bits = explode('.', $ipv4);
9264 $parts[] = dechex($bits[0]).dechex($bits[1]);
9265 $parts[] = dechex($bits[2]).dechex($bits[3]);
9266 $count = count($parts);
9267 $addr = implode(':', $parts);
9270 if ($count < 3 or $count > 8) {
9271 return null; // severly malformed
9275 if (strpos($addr, '::') === false) {
9276 return null; // malformed
9279 $insertat = array_search('', $parts, true);
9280 $missing = array_fill(0, 1 +
8 - $count, '0');
9281 array_splice($parts, $insertat, 1, $missing);
9282 foreach ($parts as $key=>$part) {
9289 $adr = implode(':', $parts);
9290 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
9291 return null; // incorrect format - sorry
9294 // normalise 0s and case
9295 $parts = array_map('hexdec', $parts);
9296 $parts = array_map('dechex', $parts);
9298 $result = implode(':', $parts);
9304 if ($result === '0:0:0:0:0:0:0:0') {
9305 return '::'; // all addresses
9308 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
9309 if ($compressed !== $result) {
9313 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
9314 if ($compressed !== $result) {
9318 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
9319 if ($compressed !== $result) {
9326 // first get all things that look like IPv4 addresses
9328 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
9333 foreach ($parts as $key=>$match) {
9337 $parts[$key] = (int)$match; // normalise 0s
9340 return implode('.', $parts);
9344 * This function will make a complete copy of anything it's given,
9345 * regardless of whether it's an object or not.
9347 * @param mixed $thing Something you want cloned
9348 * @return mixed What ever it is you passed it
9350 function fullclone($thing) {
9351 return unserialize(serialize($thing));
9356 * This function expects to called during shutdown
9357 * should be set via register_shutdown_function()
9358 * in lib/setup.php .
9362 function moodle_request_shutdown() {
9365 // help apache server if possible
9366 $apachereleasemem = false;
9367 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
9368 && ini_get_bool('child_terminate')) {
9370 $limit = (empty($CFG->apachemaxmem
) ?
64*1024*1024 : $CFG->apachemaxmem
); //64MB default
9371 if (memory_get_usage() > get_real_size($limit)) {
9372 $apachereleasemem = $limit;
9373 @apache_child_terminate
();
9377 // deal with perf logging
9378 if (defined('MDL_PERF') ||
(!empty($CFG->perfdebug
) and $CFG->perfdebug
> 7)) {
9379 if ($apachereleasemem) {
9380 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
9382 if (defined('MDL_PERFTOLOG')) {
9383 $perf = get_performance_info();
9384 error_log("PERF: " . $perf['txt']);
9386 if (defined('MDL_PERFINC')) {
9387 $inc = get_included_files();
9389 foreach($inc as $f) {
9390 if (preg_match(':^/:', $f)) {
9393 $hfs = display_size($fs);
9394 error_log(substr($f,strlen($CFG->dirroot
)) . " size: $fs ($hfs)"
9397 error_log($f , NULL, NULL, 0);
9401 $hts = display_size($ts);
9402 error_log("Total size of files included: $ts ($hts)");
9409 * If new messages are waiting for the current user, then insert
9410 * JavaScript to pop up the messaging window into the page
9412 * @global moodle_page $PAGE
9415 function message_popup_window() {
9416 global $USER, $DB, $PAGE, $CFG, $SITE;
9418 if (!$PAGE->get_popup_notification_allowed() ||
empty($CFG->messaging
)) {
9422 if (!isloggedin() ||
isguestuser()) {
9426 if (!isset($USER->message_lastpopup
)) {
9427 $USER->message_lastpopup
= 0;
9428 } else if ($USER->message_lastpopup
> (time()-120)) {
9429 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
9433 //a quick query to check whether the user has new messages
9434 $messagecount = $DB->count_records('message', array('useridto' => $USER->id
));
9435 if ($messagecount<1) {
9439 //got unread messages so now do another query that joins with the user table
9440 $messagesql = "SELECT m.id, m.smallmessage, m.fullmessageformat, m.notification, u.firstname, u.lastname
9442 JOIN {message_working} mw ON m.id=mw.unreadmessageid
9443 JOIN {message_processors} p ON mw.processorid=p.id
9444 JOIN {user} u ON m.useridfrom=u.id
9445 WHERE m.useridto = :userid
9446 AND p.name='popup'";
9448 //if the user was last notified over an hour ago we can renotify them of old messages
9449 //so don't worry about when the new message was sent
9450 $lastnotifiedlongago = $USER->message_lastpopup
< (time()-3600);
9451 if (!$lastnotifiedlongago) {
9452 $messagesql .= 'AND m.timecreated > :lastpopuptime';
9455 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id
, 'lastpopuptime'=>$USER->message_lastpopup
));
9457 //if we have new messages to notify the user about
9458 if (!empty($message_users)) {
9461 if (count($message_users)>1) {
9462 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
9464 $message_users = reset($message_users);
9466 //show who the message is from if its not a notification
9467 if (!$message_users->notification
) {
9468 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
9471 //try to display the small version of the message
9472 $smallmessage = null;
9473 if (!empty($message_users->smallmessage
)) {
9474 //display the first 200 chars of the message in the popup
9475 $textlib = textlib_get_instance();
9476 $smallmessage = null;
9477 if ($textlib->strlen($message_users->smallmessage
) > 200) {
9478 $smallmessage = $textlib->substr($message_users->smallmessage
,0,200).'...';
9480 $smallmessage = $message_users->smallmessage
;
9483 //prevent html symbols being displayed
9484 if ($message_users->fullmessageformat
== FORMAT_HTML
) {
9485 $smallmessage = html_to_text($smallmessage);
9487 $smallmessage = s($smallmessage);
9489 } else if ($message_users->notification
) {
9490 //its a notification with no smallmessage so just say they have a notification
9491 $smallmessage = get_string('unreadnewnotification', 'message');
9493 if (!empty($smallmessage)) {
9494 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
9498 $strgomessage = get_string('gotomessages', 'message');
9499 $strstaymessage = get_string('ignore','admin');
9501 $url = $CFG->wwwroot
.'/message/index.php';
9502 $content = html_writer
::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
9503 html_writer
::start_tag('div', array('id'=>'newmessagetext')).
9505 html_writer
::end_tag('div').
9507 html_writer
::start_tag('div', array('id'=>'newmessagelinks')).
9508 html_writer
::link($url, $strgomessage, array('id'=>'notificationyes')).' '.
9509 html_writer
::link('', $strstaymessage, array('id'=>'notificationno')).
9510 html_writer
::end_tag('div');
9511 html_writer
::end_tag('div');
9513 $PAGE->requires
->js_init_call('M.core_message.init_notification', array('', $content, $url));
9515 $USER->message_lastpopup
= time();
9520 * Used to make sure that $min <= $value <= $max
9522 * Make sure that value is between min, and max
9524 * @param int $min The minimum value
9525 * @param int $value The value to check
9526 * @param int $max The maximum value
9528 function bounded_number($min, $value, $max) {
9539 * Check if there is a nested array within the passed array
9541 * @param array $array
9542 * @return bool true if there is a nested array false otherwise
9544 function array_is_nested($array) {
9545 foreach ($array as $value) {
9546 if (is_array($value)) {
9554 * get_performance_info() pairs up with init_performance_info()
9555 * loaded in setup.php. Returns an array with 'html' and 'txt'
9556 * values ready for use, and each of the individual stats provided
9557 * separately as well.
9564 function get_performance_info() {
9565 global $CFG, $PERF, $DB, $PAGE;
9568 $info['html'] = ''; // holds userfriendly HTML representation
9569 $info['txt'] = me() . ' '; // holds log-friendly representation
9571 $info['realtime'] = microtime_diff($PERF->starttime
, microtime());
9573 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
9574 $info['txt'] .= 'time: '.$info['realtime'].'s ';
9576 if (function_exists('memory_get_usage')) {
9577 $info['memory_total'] = memory_get_usage();
9578 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory
;
9579 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
9580 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
9583 if (function_exists('memory_get_peak_usage')) {
9584 $info['memory_peak'] = memory_get_peak_usage();
9585 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
9586 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
9589 $inc = get_included_files();
9590 //error_log(print_r($inc,1));
9591 $info['includecount'] = count($inc);
9592 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
9593 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
9595 $filtermanager = filter_manager
::instance();
9596 if (method_exists($filtermanager, 'get_performance_summary')) {
9597 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
9598 $info = array_merge($filterinfo, $info);
9599 foreach ($filterinfo as $key => $value) {
9600 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9601 $info['txt'] .= "$key: $value ";
9605 $stringmanager = get_string_manager();
9606 if (method_exists($stringmanager, 'get_performance_summary')) {
9607 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
9608 $info = array_merge($filterinfo, $info);
9609 foreach ($filterinfo as $key => $value) {
9610 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
9611 $info['txt'] .= "$key: $value ";
9615 $jsmodules = $PAGE->requires
->get_loaded_modules();
9620 foreach ($jsmodules as $module => $backtraces) {
9621 if (strpos($module, 'yui') === 0) {
9626 $details .= "<div class='yui-module'><p>$module</p>";
9627 foreach ($backtraces as $backtrace) {
9628 $details .= "<div class='backtrace'>$backtrace</div>";
9630 $details .= '</div>';
9632 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
9633 $info['txt'] .= "includedyuimodules: $yuicount ";
9634 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
9635 $info['txt'] .= "includedjsmodules: $othercount ";
9636 // Slightly odd to output the details in a display: none div. The point
9637 // Is that it takes a lot of space, and if you care you can reveal it
9639 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
9642 if (!empty($PERF->logwrites
)) {
9643 $info['logwrites'] = $PERF->logwrites
;
9644 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
9645 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
9648 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites
);
9649 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
9650 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
9652 if (!empty($PERF->profiling
) && $PERF->profiling
) {
9653 require_once($CFG->dirroot
.'/lib/profilerlib.php');
9654 $info['html'] .= '<span class="profilinginfo">'.Profiler
::get_profiling(array('-R')).'</span>';
9657 if (function_exists('posix_times')) {
9658 $ptimes = posix_times();
9659 if (is_array($ptimes)) {
9660 foreach ($ptimes as $key => $val) {
9661 $info[$key] = $ptimes[$key] - $PERF->startposixtimes
[$key];
9663 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
9664 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
9668 // Grab the load average for the last minute
9669 // /proc will only work under some linux configurations
9670 // while uptime is there under MacOSX/Darwin and other unices
9671 if (is_readable('/proc/loadavg') && $loadavg = @file
('/proc/loadavg')) {
9672 list($server_load) = explode(' ', $loadavg[0]);
9674 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `
/usr
/bin
/uptime`
) {
9675 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
9676 $server_load = $matches[1];
9678 trigger_error('Could not parse uptime output!');
9681 if (!empty($server_load)) {
9682 $info['serverload'] = $server_load;
9683 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
9684 $info['txt'] .= "serverload: {$info['serverload']} ";
9687 // Display size of session if session started
9689 $info['sessionsize'] = display_size(strlen(session_encode()));
9690 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
9691 $info['txt'] .= "Session: {$info['sessionsize']} ";
9694 /* if (isset($rcache->hits) && isset($rcache->misses)) {
9695 $info['rcachehits'] = $rcache->hits;
9696 $info['rcachemisses'] = $rcache->misses;
9697 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
9698 "{$rcache->hits}/{$rcache->misses}</span> ";
9699 $info['txt'] .= 'rcache: '.
9700 "{$rcache->hits}/{$rcache->misses} ";
9702 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
9707 * @todo Document this function linux people
9709 function apd_get_profiling() {
9710 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
9714 * Delete directory or only it's content
9716 * @param string $dir directory path
9717 * @param bool $content_only
9718 * @return bool success, true also if dir does not exist
9720 function remove_dir($dir, $content_only=false) {
9721 if (!file_exists($dir)) {
9725 $handle = opendir($dir);
9727 while (false!==($item = readdir($handle))) {
9728 if($item != '.' && $item != '..') {
9729 if(is_dir($dir.'/'.$item)) {
9730 $result = remove_dir($dir.'/'.$item) && $result;
9732 $result = unlink($dir.'/'.$item) && $result;
9737 if ($content_only) {
9740 return rmdir($dir); // if anything left the result will be false, no need for && $result
9744 * Detect if an object or a class contains a given property
9745 * will take an actual object or the name of a class
9747 * @param mix $obj Name of class or real object to test
9748 * @param string $property name of property to find
9749 * @return bool true if property exists
9751 function object_property_exists( $obj, $property ) {
9752 if (is_string( $obj )) {
9753 $properties = get_class_vars( $obj );
9756 $properties = get_object_vars( $obj );
9758 return array_key_exists( $property, $properties );
9763 * Detect a custom script replacement in the data directory that will
9764 * replace an existing moodle script
9766 * @param string $urlpath path to the original script
9767 * @return string|bool full path name if a custom script exists, false if no custom script exists
9769 function custom_script_path($urlpath='') {
9772 // set default $urlpath, if necessary
9773 if (empty($urlpath)) {
9774 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
9777 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
9778 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot
) === false )) {
9782 // replace wwwroot with the path to the customscripts folder and clean path
9783 $scriptpath = $CFG->customscripts
. clean_param(substr($urlpath, strlen($CFG->wwwroot
)), PARAM_PATH
);
9785 // remove the query string, if any
9786 if (($strpos = strpos($scriptpath, '?')) !== false) {
9787 $scriptpath = substr($scriptpath, 0, $strpos);
9790 // remove trailing slashes, if any
9791 $scriptpath = rtrim($scriptpath, '/\\');
9793 // append index.php, if necessary
9794 if (is_dir($scriptpath)) {
9795 $scriptpath .= '/index.php';
9798 // check the custom script exists
9799 if (file_exists($scriptpath)) {
9807 * Returns whether or not the user object is a remote MNET user. This function
9808 * is in moodlelib because it does not rely on loading any of the MNET code.
9811 * @param object $user A valid user object
9812 * @return bool True if the user is from a remote Moodle.
9814 function is_mnet_remote_user($user) {
9817 if (!isset($CFG->mnet_localhost_id
)) {
9818 include_once $CFG->dirroot
. '/mnet/lib.php';
9819 $env = new mnet_environment();
9824 return (!empty($user->mnethostid
) && $user->mnethostid
!= $CFG->mnet_localhost_id
);
9828 * This function will search for browser prefereed languages, setting Moodle
9829 * to use the best one available if $SESSION->lang is undefined
9835 function setup_lang_from_browser() {
9837 global $CFG, $SESSION, $USER;
9839 if (!empty($SESSION->lang
) or !empty($USER->lang
) or empty($CFG->autolang
)) {
9840 // Lang is defined in session or user profile, nothing to do
9844 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
9848 /// Extract and clean langs from headers
9849 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
9850 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
9851 $rawlangs = explode(',', $rawlangs); // Convert to array
9855 foreach ($rawlangs as $lang) {
9856 if (strpos($lang, ';') === false) {
9857 $langs[(string)$order] = $lang;
9858 $order = $order-0.01;
9860 $parts = explode(';', $lang);
9861 $pos = strpos($parts[1], '=');
9862 $langs[substr($parts[1], $pos+
1)] = $parts[0];
9865 krsort($langs, SORT_NUMERIC
);
9867 /// Look for such langs under standard locations
9868 foreach ($langs as $lang) {
9869 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR
)); // clean it properly for include
9870 if (get_string_manager()->translation_exists($lang, false)) {
9871 $SESSION->lang
= $lang; /// Lang exists, set it in session
9872 break; /// We have finished. Go out
9879 * check if $url matches anything in proxybypass list
9881 * any errors just result in the proxy being used (least bad)
9884 * @param string $url url to check
9885 * @return boolean true if we should bypass the proxy
9887 function is_proxybypass( $url ) {
9891 if (empty($CFG->proxyhost
) or empty($CFG->proxybypass
)) {
9895 // get the host part out of the url
9896 if (!$host = parse_url( $url, PHP_URL_HOST
)) {
9900 // get the possible bypass hosts into an array
9901 $matches = explode( ',', $CFG->proxybypass
);
9903 // check for a match
9904 // (IPs need to match the left hand side and hosts the right of the url,
9905 // but we can recklessly check both as there can't be a false +ve)
9907 foreach ($matches as $match) {
9908 $match = trim($match);
9910 // try for IP match (Left side)
9911 $lhs = substr($host,0,strlen($match));
9912 if (strcasecmp($match,$lhs)==0) {
9916 // try for host match (Right side)
9917 $rhs = substr($host,-strlen($match));
9918 if (strcasecmp($match,$rhs)==0) {
9928 ////////////////////////////////////////////////////////////////////////////////
9931 * Check if the passed navigation is of the new style
9933 * @param mixed $navigation
9934 * @return bool true for yes false for no
9936 function is_newnav($navigation) {
9937 if (is_array($navigation) && !empty($navigation['newnav'])) {
9945 * Checks whether the given variable name is defined as a variable within the given object.
9947 * This will NOT work with stdClass objects, which have no class variables.
9949 * @param string $var The variable name
9950 * @param object $object The object to check
9953 function in_object_vars($var, $object) {
9954 $class_vars = get_class_vars(get_class($object));
9955 $class_vars = array_keys($class_vars);
9956 return in_array($var, $class_vars);
9960 * Returns an array without repeated objects.
9961 * This function is similar to array_unique, but for arrays that have objects as values
9963 * @param array $array
9964 * @param bool $keep_key_assoc
9967 function object_array_unique($array, $keep_key_assoc = true) {
9968 $duplicate_keys = array();
9971 foreach ($array as $key=>$val) {
9972 // convert objects to arrays, in_array() does not support objects
9973 if (is_object($val)) {
9977 if (!in_array($val, $tmp)) {
9980 $duplicate_keys[] = $key;
9984 foreach ($duplicate_keys as $key) {
9985 unset($array[$key]);
9988 return $keep_key_assoc ?
$array : array_values($array);
9992 * Is a userid the primary administrator?
9994 * @param int $userid int id of user to check
9997 function is_primary_admin($userid){
9998 $primaryadmin = get_admin();
10000 if($userid == $primaryadmin->id
){
10008 * Returns the site identifier
10011 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
10013 function get_site_identifier() {
10015 // Check to see if it is missing. If so, initialise it.
10016 if (empty($CFG->siteidentifier
)) {
10017 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
10020 return $CFG->siteidentifier
;
10024 * Check whether the given password has no more than the specified
10025 * number of consecutive identical characters.
10027 * @param string $password password to be checked against the password policy
10028 * @param integer $maxchars maximum number of consecutive identical characters
10030 function check_consecutive_identical_characters($password, $maxchars) {
10032 if ($maxchars < 1) {
10033 return true; // 0 is to disable this check
10035 if (strlen($password) <= $maxchars) {
10036 return true; // too short to fail this test
10039 $previouschar = '';
10040 $consecutivecount = 1;
10041 foreach (str_split($password) as $char) {
10042 if ($char != $previouschar) {
10043 $consecutivecount = 1;
10046 $consecutivecount++
;
10047 if ($consecutivecount > $maxchars) {
10048 return false; // check failed already
10052 $previouschar = $char;
10059 * helper function to do partial function binding
10060 * so we can use it for preg_replace_callback, for example
10061 * this works with php functions, user functions, static methods and class methods
10062 * it returns you a callback that you can pass on like so:
10064 * $callback = partial('somefunction', $arg1, $arg2);
10066 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
10068 * $obj = new someclass();
10069 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
10071 * and then the arguments that are passed through at calltime are appended to the argument list.
10073 * @param mixed $function a php callback
10074 * $param mixed $arg1.. $argv arguments to partially bind with
10078 function partial() {
10079 if (!class_exists('partial')) {
10081 var $values = array();
10084 function __construct($func, $args) {
10085 $this->values
= $args;
10086 $this->func
= $func;
10089 function method() {
10090 $args = func_get_args();
10091 return call_user_func_array($this->func
, array_merge($this->values
, $args));
10095 $args = func_get_args();
10096 $func = array_shift($args);
10097 $p = new partial($func, $args);
10098 return array($p, 'method');
10102 * helper function to load up and initialise the mnet environment
10103 * this must be called before you use mnet functions.
10105 * @return mnet_environment the equivalent of old $MNET global
10107 function get_mnet_environment() {
10109 require_once($CFG->dirroot
. '/mnet/lib.php');
10110 static $instance = null;
10111 if (empty($instance)) {
10112 $instance = new mnet_environment();
10119 * during xmlrpc server code execution, any code wishing to access
10120 * information about the remote peer must use this to get it.
10122 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
10124 function get_mnet_remote_client() {
10125 if (!defined('MNET_SERVER')) {
10126 debugging(get_string('notinxmlrpcserver', 'mnet'));
10129 global $MNET_REMOTE_CLIENT;
10130 if (isset($MNET_REMOTE_CLIENT)) {
10131 return $MNET_REMOTE_CLIENT;
10137 * during the xmlrpc server code execution, this will be called
10138 * to setup the object returned by {@see get_mnet_remote_client}
10140 * @param mnet_remote_client $client the client to set up
10142 function set_mnet_remote_client($client) {
10143 if (!defined('MNET_SERVER')) {
10144 throw new moodle_exception('notinxmlrpcserver', 'mnet');
10146 global $MNET_REMOTE_CLIENT;
10147 $MNET_REMOTE_CLIENT = $client;
10151 * return the jump url for a given remote user
10152 * this is used for rewriting forum post links in emails, etc
10154 * @param stdclass $user the user to get the idp url for
10156 function mnet_get_idp_jump_url($user) {
10159 static $mnetjumps = array();
10160 if (!array_key_exists($user->mnethostid
, $mnetjumps)) {
10161 $idp = mnet_get_peer_host($user->mnethostid
);
10162 $idpjumppath = mnet_get_app_jumppath($idp->applicationid
);
10163 $mnetjumps[$user->mnethostid
] = $idp->wwwroot
. $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot
. '&wantsurl=';
10165 return $mnetjumps[$user->mnethostid
];
10169 * Gets the homepage to use for the current user
10171 * @return int One of HOMEPAGE_*
10173 function get_home_page() {
10176 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage
)) {
10177 if ($CFG->defaulthomepage
== HOMEPAGE_MY
) {
10178 return HOMEPAGE_MY
;
10180 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY
);
10183 return HOMEPAGE_SITE
;