MDL-35537 - Right align registration text on login page, when in RTL mode
[moodle.git] / lib / moodlelib.php
blobfa8101229d8fa649d5904fc053b2aace61a2b73e
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * moodlelib.php - Moodle main library
21 * Main library file of miscellaneous general-purpose Moodle functions.
22 * Other main libraries:
23 * - weblib.php - functions that produce web output
24 * - datalib.php - functions that access the database
26 * @package core
27 * @subpackage lib
28 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32 defined('MOODLE_INTERNAL') || die();
34 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
36 /// Date and time constants ///
37 /**
38 * Time constant - the number of seconds in a year
40 define('YEARSECS', 31536000);
42 /**
43 * Time constant - the number of seconds in a week
45 define('WEEKSECS', 604800);
47 /**
48 * Time constant - the number of seconds in a day
50 define('DAYSECS', 86400);
52 /**
53 * Time constant - the number of seconds in an hour
55 define('HOURSECS', 3600);
57 /**
58 * Time constant - the number of seconds in a minute
60 define('MINSECS', 60);
62 /**
63 * Time constant - the number of minutes in a day
65 define('DAYMINS', 1440);
67 /**
68 * Time constant - the number of minutes in an hour
70 define('HOURMINS', 60);
72 /// Parameter constants - every call to optional_param(), required_param() ///
73 /// or clean_param() should have a specified type of parameter. //////////////
77 /**
78 * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
80 define('PARAM_ALPHA', 'alpha');
82 /**
83 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "_-" allowed
84 * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
86 define('PARAM_ALPHAEXT', 'alphaext');
88 /**
89 * PARAM_ALPHANUM - expected numbers and letters only.
91 define('PARAM_ALPHANUM', 'alphanum');
93 /**
94 * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
96 define('PARAM_ALPHANUMEXT', 'alphanumext');
98 /**
99 * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
101 define('PARAM_AUTH', 'auth');
104 * PARAM_BASE64 - Base 64 encoded format
106 define('PARAM_BASE64', 'base64');
109 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
111 define('PARAM_BOOL', 'bool');
114 * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
115 * checked against the list of capabilities in the database.
117 define('PARAM_CAPABILITY', 'capability');
120 * PARAM_CLEANHTML - cleans submitted HTML code. use only for text in HTML format. This cleaning may fix xhtml strictness too.
122 define('PARAM_CLEANHTML', 'cleanhtml');
125 * PARAM_EMAIL - an email address following the RFC
127 define('PARAM_EMAIL', 'email');
130 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
132 define('PARAM_FILE', 'file');
135 * PARAM_FLOAT - a real/floating point number.
137 * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
138 * It does not work for languages that use , as a decimal separator.
139 * Instead, do something like
140 * $rawvalue = required_param('name', PARAM_RAW);
141 * // ... other code including require_login, which sets current lang ...
142 * $realvalue = unformat_float($rawvalue);
143 * // ... then use $realvalue
145 define('PARAM_FLOAT', 'float');
148 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
150 define('PARAM_HOST', 'host');
153 * PARAM_INT - integers only, use when expecting only numbers.
155 define('PARAM_INT', 'int');
158 * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
160 define('PARAM_LANG', 'lang');
163 * 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!)
165 define('PARAM_LOCALURL', 'localurl');
168 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
170 define('PARAM_NOTAGS', 'notags');
173 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
174 * note: the leading slash is not removed, window drive letter is not allowed
176 define('PARAM_PATH', 'path');
179 * PARAM_PEM - Privacy Enhanced Mail format
181 define('PARAM_PEM', 'pem');
184 * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
186 define('PARAM_PERMISSION', 'permission');
189 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
191 define('PARAM_RAW', 'raw');
194 * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
196 define('PARAM_RAW_TRIMMED', 'raw_trimmed');
199 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
201 define('PARAM_SAFEDIR', 'safedir');
204 * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
206 define('PARAM_SAFEPATH', 'safepath');
209 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
211 define('PARAM_SEQUENCE', 'sequence');
214 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
216 define('PARAM_TAG', 'tag');
219 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
221 define('PARAM_TAGLIST', 'taglist');
224 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
226 define('PARAM_TEXT', 'text');
229 * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
231 define('PARAM_THEME', 'theme');
234 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
236 define('PARAM_URL', 'url');
239 * 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!!
241 define('PARAM_USERNAME', 'username');
244 * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
246 define('PARAM_STRINGID', 'stringid');
248 ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
250 * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
251 * It was one of the first types, that is why it is abused so much ;-)
252 * @deprecated since 2.0
254 define('PARAM_CLEAN', 'clean');
257 * PARAM_INTEGER - deprecated alias for PARAM_INT
259 define('PARAM_INTEGER', 'int');
262 * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
264 define('PARAM_NUMBER', 'float');
267 * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
268 * NOTE: originally alias for PARAM_APLHA
270 define('PARAM_ACTION', 'alphanumext');
273 * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
274 * NOTE: originally alias for PARAM_APLHA
276 define('PARAM_FORMAT', 'alphanumext');
279 * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
281 define('PARAM_MULTILANG', 'text');
284 * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
285 * string seperated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
286 * America/Port-au-Prince)
288 define('PARAM_TIMEZONE', 'timezone');
291 * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
293 define('PARAM_CLEANFILE', 'file');
296 * PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
297 * Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
298 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
299 * NOTE: numbers and underscores are strongly discouraged in plugin names!
301 define('PARAM_COMPONENT', 'component');
304 * PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
305 * It is usually used together with context id and component.
306 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
308 define('PARAM_AREA', 'area');
311 * PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'radius', 'paypal', 'completionstatus'.
312 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
313 * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
315 define('PARAM_PLUGIN', 'plugin');
318 /// Web Services ///
321 * VALUE_REQUIRED - if the parameter is not supplied, there is an error
323 define('VALUE_REQUIRED', 1);
326 * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
328 define('VALUE_OPTIONAL', 2);
331 * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
333 define('VALUE_DEFAULT', 0);
336 * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
338 define('NULL_NOT_ALLOWED', false);
341 * NULL_ALLOWED - the parameter can be set to null in the database
343 define('NULL_ALLOWED', true);
345 /// Page types ///
347 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
349 define('PAGE_COURSE_VIEW', 'course-view');
351 /** Get remote addr constant */
352 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
353 /** Get remote addr constant */
354 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
356 /// Blog access level constant declaration ///
357 define ('BLOG_USER_LEVEL', 1);
358 define ('BLOG_GROUP_LEVEL', 2);
359 define ('BLOG_COURSE_LEVEL', 3);
360 define ('BLOG_SITE_LEVEL', 4);
361 define ('BLOG_GLOBAL_LEVEL', 5);
364 ///Tag constants///
366 * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
367 * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
368 * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
370 * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
372 define('TAG_MAX_LENGTH', 50);
374 /// Password policy constants ///
375 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
376 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
377 define ('PASSWORD_DIGITS', '0123456789');
378 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
380 /// Feature constants ///
381 // Used for plugin_supports() to report features that are, or are not, supported by a module.
383 /** True if module can provide a grade */
384 define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
385 /** True if module supports outcomes */
386 define('FEATURE_GRADE_OUTCOMES', 'outcomes');
387 /** True if module supports advanced grading methods */
388 define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
389 /** True if module controls the grade visibility over the gradebook */
390 define('FEATURE_CONTROLS_GRADE_VISIBILITY', 'controlsgradevisbility');
392 /** True if module has code to track whether somebody viewed it */
393 define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
394 /** True if module has custom completion rules */
395 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
397 /** True if module has no 'view' page (like label) */
398 define('FEATURE_NO_VIEW_LINK', 'viewlink');
399 /** True if module supports outcomes */
400 define('FEATURE_IDNUMBER', 'idnumber');
401 /** True if module supports groups */
402 define('FEATURE_GROUPS', 'groups');
403 /** True if module supports groupings */
404 define('FEATURE_GROUPINGS', 'groupings');
405 /** True if module supports groupmembersonly */
406 define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
408 /** Type of module */
409 define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
410 /** True if module supports intro editor */
411 define('FEATURE_MOD_INTRO', 'mod_intro');
412 /** True if module has default completion */
413 define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
415 define('FEATURE_COMMENT', 'comment');
417 define('FEATURE_RATE', 'rate');
418 /** True if module supports backup/restore of moodle2 format */
419 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
421 /** True if module can show description on course main page */
422 define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
424 /** Unspecified module archetype */
425 define('MOD_ARCHETYPE_OTHER', 0);
426 /** Resource-like type module */
427 define('MOD_ARCHETYPE_RESOURCE', 1);
428 /** Assignment module archetype */
429 define('MOD_ARCHETYPE_ASSIGNMENT', 2);
432 * Security token used for allowing access
433 * from external application such as web services.
434 * Scripts do not use any session, performance is relatively
435 * low because we need to load access info in each request.
436 * Scripts are executed in parallel.
438 define('EXTERNAL_TOKEN_PERMANENT', 0);
441 * Security token used for allowing access
442 * of embedded applications, the code is executed in the
443 * active user session. Token is invalidated after user logs out.
444 * Scripts are executed serially - normal session locking is used.
446 define('EXTERNAL_TOKEN_EMBEDDED', 1);
449 * The home page should be the site home
451 define('HOMEPAGE_SITE', 0);
453 * The home page should be the users my page
455 define('HOMEPAGE_MY', 1);
457 * The home page can be chosen by the user
459 define('HOMEPAGE_USER', 2);
462 * Hub directory url (should be moodle.org)
464 define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
468 * Moodle.org url (should be moodle.org)
470 define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
473 * Moodle mobile app service name
475 define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
477 /// PARAMETER HANDLING ////////////////////////////////////////////////////
480 * Returns a particular value for the named variable, taken from
481 * POST or GET. If the parameter doesn't exist then an error is
482 * thrown because we require this variable.
484 * This function should be used to initialise all required values
485 * in a script that are based on parameters. Usually it will be
486 * used like this:
487 * $id = required_param('id', PARAM_INT);
489 * Please note the $type parameter is now required and the value can not be array.
491 * @param string $parname the name of the page parameter we want
492 * @param string $type expected type of parameter
493 * @return mixed
495 function required_param($parname, $type) {
496 if (func_num_args() != 2 or empty($parname) or empty($type)) {
497 throw new coding_exception('required_param() requires $parname and $type to be specified (parameter: '.$parname.')');
499 if (isset($_POST[$parname])) { // POST has precedence
500 $param = $_POST[$parname];
501 } else if (isset($_GET[$parname])) {
502 $param = $_GET[$parname];
503 } else {
504 print_error('missingparam', '', '', $parname);
507 if (is_array($param)) {
508 debugging('Invalid array parameter detected in required_param(): '.$parname);
509 // TODO: switch to fatal error in Moodle 2.3
510 //print_error('missingparam', '', '', $parname);
511 return required_param_array($parname, $type);
514 return clean_param($param, $type);
518 * Returns a particular array value for the named variable, taken from
519 * POST or GET. If the parameter doesn't exist then an error is
520 * thrown because we require this variable.
522 * This function should be used to initialise all required values
523 * in a script that are based on parameters. Usually it will be
524 * used like this:
525 * $ids = required_param_array('ids', PARAM_INT);
527 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
529 * @param string $parname the name of the page parameter we want
530 * @param string $type expected type of parameter
531 * @return array
533 function required_param_array($parname, $type) {
534 if (func_num_args() != 2 or empty($parname) or empty($type)) {
535 throw new coding_exception('required_param_array() requires $parname and $type to be specified (parameter: '.$parname.')');
537 if (isset($_POST[$parname])) { // POST has precedence
538 $param = $_POST[$parname];
539 } else if (isset($_GET[$parname])) {
540 $param = $_GET[$parname];
541 } else {
542 print_error('missingparam', '', '', $parname);
544 if (!is_array($param)) {
545 print_error('missingparam', '', '', $parname);
548 $result = array();
549 foreach($param as $key=>$value) {
550 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
551 debugging('Invalid key name in required_param_array() detected: '.$key.', parameter: '.$parname);
552 continue;
554 $result[$key] = clean_param($value, $type);
557 return $result;
561 * Returns a particular value for the named variable, taken from
562 * POST or GET, otherwise returning a given default.
564 * This function should be used to initialise all optional values
565 * in a script that are based on parameters. Usually it will be
566 * used like this:
567 * $name = optional_param('name', 'Fred', PARAM_TEXT);
569 * Please note the $type parameter is now required and the value can not be array.
571 * @param string $parname the name of the page parameter we want
572 * @param mixed $default the default value to return if nothing is found
573 * @param string $type expected type of parameter
574 * @return mixed
576 function optional_param($parname, $default, $type) {
577 if (func_num_args() != 3 or empty($parname) or empty($type)) {
578 throw new coding_exception('optional_param() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
580 if (!isset($default)) {
581 $default = null;
584 if (isset($_POST[$parname])) { // POST has precedence
585 $param = $_POST[$parname];
586 } else if (isset($_GET[$parname])) {
587 $param = $_GET[$parname];
588 } else {
589 return $default;
592 if (is_array($param)) {
593 debugging('Invalid array parameter detected in required_param(): '.$parname);
594 // TODO: switch to $default in Moodle 2.3
595 //return $default;
596 return optional_param_array($parname, $default, $type);
599 return clean_param($param, $type);
603 * Returns a particular array value for the named variable, taken from
604 * POST or GET, otherwise returning a given default.
606 * This function should be used to initialise all optional values
607 * in a script that are based on parameters. Usually it will be
608 * used like this:
609 * $ids = optional_param('id', array(), PARAM_INT);
611 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
613 * @param string $parname the name of the page parameter we want
614 * @param mixed $default the default value to return if nothing is found
615 * @param string $type expected type of parameter
616 * @return array
618 function optional_param_array($parname, $default, $type) {
619 if (func_num_args() != 3 or empty($parname) or empty($type)) {
620 throw new coding_exception('optional_param_array() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
623 if (isset($_POST[$parname])) { // POST has precedence
624 $param = $_POST[$parname];
625 } else if (isset($_GET[$parname])) {
626 $param = $_GET[$parname];
627 } else {
628 return $default;
630 if (!is_array($param)) {
631 debugging('optional_param_array() expects array parameters only: '.$parname);
632 return $default;
635 $result = array();
636 foreach($param as $key=>$value) {
637 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
638 debugging('Invalid key name in optional_param_array() detected: '.$key.', parameter: '.$parname);
639 continue;
641 $result[$key] = clean_param($value, $type);
644 return $result;
648 * Strict validation of parameter values, the values are only converted
649 * to requested PHP type. Internally it is using clean_param, the values
650 * before and after cleaning must be equal - otherwise
651 * an invalid_parameter_exception is thrown.
652 * Objects and classes are not accepted.
654 * @param mixed $param
655 * @param string $type PARAM_ constant
656 * @param bool $allownull are nulls valid value?
657 * @param string $debuginfo optional debug information
658 * @return mixed the $param value converted to PHP type
659 * @throws invalid_parameter_exception if $param is not of given type
661 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
662 if (is_null($param)) {
663 if ($allownull == NULL_ALLOWED) {
664 return null;
665 } else {
666 throw new invalid_parameter_exception($debuginfo);
669 if (is_array($param) or is_object($param)) {
670 throw new invalid_parameter_exception($debuginfo);
673 $cleaned = clean_param($param, $type);
675 if ($type == PARAM_FLOAT) {
676 // Do not detect precision loss here.
677 if (is_float($param) or is_int($param)) {
678 // These always fit.
679 } else if (!is_numeric($param) or !preg_match('/^[\+-]?[0-9]*\.?[0-9]*(e[-+]?[0-9]+)?$/i', (string)$param)) {
680 throw new invalid_parameter_exception($debuginfo);
682 } else if ((string)$param !== (string)$cleaned) {
683 // conversion to string is usually lossless
684 throw new invalid_parameter_exception($debuginfo);
687 return $cleaned;
691 * Makes sure array contains only the allowed types,
692 * this function does not validate array key names!
693 * <code>
694 * $options = clean_param($options, PARAM_INT);
695 * </code>
697 * @param array $param the variable array we are cleaning
698 * @param string $type expected format of param after cleaning.
699 * @param bool $recursive clean recursive arrays
700 * @return array
702 function clean_param_array(array $param = null, $type, $recursive = false) {
703 $param = (array)$param; // convert null to empty array
704 foreach ($param as $key => $value) {
705 if (is_array($value)) {
706 if ($recursive) {
707 $param[$key] = clean_param_array($value, $type, true);
708 } else {
709 throw new coding_exception('clean_param_array() can not process multidimensional arrays when $recursive is false.');
711 } else {
712 $param[$key] = clean_param($value, $type);
715 return $param;
719 * Used by {@link optional_param()} and {@link required_param()} to
720 * clean the variables and/or cast to specific types, based on
721 * an options field.
722 * <code>
723 * $course->format = clean_param($course->format, PARAM_ALPHA);
724 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
725 * </code>
727 * @param mixed $param the variable we are cleaning
728 * @param string $type expected format of param after cleaning.
729 * @return mixed
731 function clean_param($param, $type) {
733 global $CFG;
735 if (is_array($param)) {
736 throw new coding_exception('clean_param() can not process arrays, please use clean_param_array() instead.');
737 } else if (is_object($param)) {
738 if (method_exists($param, '__toString')) {
739 $param = $param->__toString();
740 } else {
741 throw new coding_exception('clean_param() can not process objects, please use clean_param_array() instead.');
745 switch ($type) {
746 case PARAM_RAW: // no cleaning at all
747 $param = fix_utf8($param);
748 return $param;
750 case PARAM_RAW_TRIMMED: // no cleaning, but strip leading and trailing whitespace.
751 $param = fix_utf8($param);
752 return trim($param);
754 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
755 // this is deprecated!, please use more specific type instead
756 if (is_numeric($param)) {
757 return $param;
759 $param = fix_utf8($param);
760 return clean_text($param); // Sweep for scripts, etc
762 case PARAM_CLEANHTML: // clean html fragment
763 $param = fix_utf8($param);
764 $param = clean_text($param, FORMAT_HTML); // Sweep for scripts, etc
765 return trim($param);
767 case PARAM_INT:
768 return (int)$param; // Convert to integer
770 case PARAM_FLOAT:
771 case PARAM_NUMBER:
772 return (float)$param; // Convert to float
774 case PARAM_ALPHA: // Remove everything not a-z
775 return preg_replace('/[^a-zA-Z]/i', '', $param);
777 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z_- (originally allowed "/" too)
778 return preg_replace('/[^a-zA-Z_-]/i', '', $param);
780 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
781 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
783 case PARAM_ALPHANUMEXT: // Remove everything not a-zA-Z0-9_-
784 return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
786 case PARAM_SEQUENCE: // Remove everything not 0-9,
787 return preg_replace('/[^0-9,]/i', '', $param);
789 case PARAM_BOOL: // Convert to 1 or 0
790 $tempstr = strtolower($param);
791 if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
792 $param = 1;
793 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
794 $param = 0;
795 } else {
796 $param = empty($param) ? 0 : 1;
798 return $param;
800 case PARAM_NOTAGS: // Strip all tags
801 $param = fix_utf8($param);
802 return strip_tags($param);
804 case PARAM_TEXT: // leave only tags needed for multilang
805 $param = fix_utf8($param);
806 // if the multilang syntax is not correct we strip all tags
807 // because it would break xhtml strict which is required for accessibility standards
808 // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
809 do {
810 if (strpos($param, '</lang>') !== false) {
811 // old and future mutilang syntax
812 $param = strip_tags($param, '<lang>');
813 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
814 break;
816 $open = false;
817 foreach ($matches[0] as $match) {
818 if ($match === '</lang>') {
819 if ($open) {
820 $open = false;
821 continue;
822 } else {
823 break 2;
826 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
827 break 2;
828 } else {
829 $open = true;
832 if ($open) {
833 break;
835 return $param;
837 } else if (strpos($param, '</span>') !== false) {
838 // current problematic multilang syntax
839 $param = strip_tags($param, '<span>');
840 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
841 break;
843 $open = false;
844 foreach ($matches[0] as $match) {
845 if ($match === '</span>') {
846 if ($open) {
847 $open = false;
848 continue;
849 } else {
850 break 2;
853 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
854 break 2;
855 } else {
856 $open = true;
859 if ($open) {
860 break;
862 return $param;
864 } while (false);
865 // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
866 return strip_tags($param);
868 case PARAM_COMPONENT:
869 // we do not want any guessing here, either the name is correct or not
870 // please note only normalised component names are accepted
871 if (!preg_match('/^[a-z]+(_[a-z][a-z0-9_]*)?[a-z0-9]$/', $param)) {
872 return '';
874 if (strpos($param, '__') !== false) {
875 return '';
877 if (strpos($param, 'mod_') === 0) {
878 // module names must not contain underscores because we need to differentiate them from invalid plugin types
879 if (substr_count($param, '_') != 1) {
880 return '';
883 return $param;
885 case PARAM_PLUGIN:
886 case PARAM_AREA:
887 // we do not want any guessing here, either the name is correct or not
888 if (!preg_match('/^[a-z][a-z0-9_]*[a-z0-9]$/', $param)) {
889 return '';
891 if (strpos($param, '__') !== false) {
892 return '';
894 return $param;
896 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
897 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
899 case PARAM_SAFEPATH: // Remove everything not a-zA-Z0-9/_-
900 return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
902 case PARAM_FILE: // Strip all suspicious characters from filename
903 $param = fix_utf8($param);
904 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
905 $param = preg_replace('~\.\.+~', '', $param);
906 if ($param === '.') {
907 $param = '';
909 return $param;
911 case PARAM_PATH: // Strip all suspicious characters from file path
912 $param = fix_utf8($param);
913 $param = str_replace('\\', '/', $param);
914 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
915 $param = preg_replace('~\.\.+~', '', $param);
916 $param = preg_replace('~//+~', '/', $param);
917 return preg_replace('~/(\./)+~', '/', $param);
919 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
920 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
921 // match ipv4 dotted quad
922 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
923 // confirm values are ok
924 if ( $match[0] > 255
925 || $match[1] > 255
926 || $match[3] > 255
927 || $match[4] > 255 ) {
928 // hmmm, what kind of dotted quad is this?
929 $param = '';
931 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
932 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
933 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
935 // all is ok - $param is respected
936 } else {
937 // all is not ok...
938 $param='';
940 return $param;
942 case PARAM_URL: // allow safe ftp, http, mailto urls
943 $param = fix_utf8($param);
944 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
945 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
946 // all is ok, param is respected
947 } else {
948 $param =''; // not really ok
950 return $param;
952 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
953 $param = clean_param($param, PARAM_URL);
954 if (!empty($param)) {
955 if (preg_match(':^/:', $param)) {
956 // root-relative, ok!
957 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
958 // absolute, and matches our wwwroot
959 } else {
960 // relative - let's make sure there are no tricks
961 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
962 // looks ok.
963 } else {
964 $param = '';
968 return $param;
970 case PARAM_PEM:
971 $param = trim($param);
972 // PEM formatted strings may contain letters/numbers and the symbols
973 // forward slash: /
974 // plus sign: +
975 // equal sign: =
976 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
977 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
978 list($wholething, $body) = $matches;
979 unset($wholething, $matches);
980 $b64 = clean_param($body, PARAM_BASE64);
981 if (!empty($b64)) {
982 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
983 } else {
984 return '';
987 return '';
989 case PARAM_BASE64:
990 if (!empty($param)) {
991 // PEM formatted strings may contain letters/numbers and the symbols
992 // forward slash: /
993 // plus sign: +
994 // equal sign: =
995 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
996 return '';
998 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
999 // Each line of base64 encoded data must be 64 characters in
1000 // length, except for the last line which may be less than (or
1001 // equal to) 64 characters long.
1002 for ($i=0, $j=count($lines); $i < $j; $i++) {
1003 if ($i + 1 == $j) {
1004 if (64 < strlen($lines[$i])) {
1005 return '';
1007 continue;
1010 if (64 != strlen($lines[$i])) {
1011 return '';
1014 return implode("\n",$lines);
1015 } else {
1016 return '';
1019 case PARAM_TAG:
1020 $param = fix_utf8($param);
1021 // Please note it is not safe to use the tag name directly anywhere,
1022 // it must be processed with s(), urlencode() before embedding anywhere.
1023 // remove some nasties
1024 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
1025 //convert many whitespace chars into one
1026 $param = preg_replace('/\s+/', ' ', $param);
1027 $textlib = textlib_get_instance();
1028 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
1029 return $param;
1031 case PARAM_TAGLIST:
1032 $param = fix_utf8($param);
1033 $tags = explode(',', $param);
1034 $result = array();
1035 foreach ($tags as $tag) {
1036 $res = clean_param($tag, PARAM_TAG);
1037 if ($res !== '') {
1038 $result[] = $res;
1041 if ($result) {
1042 return implode(',', $result);
1043 } else {
1044 return '';
1047 case PARAM_CAPABILITY:
1048 if (get_capability_info($param)) {
1049 return $param;
1050 } else {
1051 return '';
1054 case PARAM_PERMISSION:
1055 $param = (int)$param;
1056 if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
1057 return $param;
1058 } else {
1059 return CAP_INHERIT;
1062 case PARAM_AUTH:
1063 $param = clean_param($param, PARAM_PLUGIN);
1064 if (empty($param)) {
1065 return '';
1066 } else if (exists_auth_plugin($param)) {
1067 return $param;
1068 } else {
1069 return '';
1072 case PARAM_LANG:
1073 $param = clean_param($param, PARAM_SAFEDIR);
1074 if (get_string_manager()->translation_exists($param)) {
1075 return $param;
1076 } else {
1077 return ''; // Specified language is not installed or param malformed
1080 case PARAM_THEME:
1081 $param = clean_param($param, PARAM_PLUGIN);
1082 if (empty($param)) {
1083 return '';
1084 } else if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
1085 return $param;
1086 } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
1087 return $param;
1088 } else {
1089 return ''; // Specified theme is not installed
1092 case PARAM_USERNAME:
1093 $param = fix_utf8($param);
1094 $param = str_replace(" " , "", $param);
1095 $param = moodle_strtolower($param); // Convert uppercase to lowercase MDL-16919
1096 if (empty($CFG->extendedusernamechars)) {
1097 // regular expression, eliminate all chars EXCEPT:
1098 // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
1099 $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
1101 return $param;
1103 case PARAM_EMAIL:
1104 $param = fix_utf8($param);
1105 if (validate_email($param)) {
1106 return $param;
1107 } else {
1108 return '';
1111 case PARAM_STRINGID:
1112 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
1113 return $param;
1114 } else {
1115 return '';
1118 case PARAM_TIMEZONE: //can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'
1119 $param = fix_utf8($param);
1120 $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3]|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
1121 if (preg_match($timezonepattern, $param)) {
1122 return $param;
1123 } else {
1124 return '';
1127 default: // throw error, switched parameters in optional_param or another serious problem
1128 print_error("unknownparamtype", '', '', $type);
1133 * Makes sure the data is using valid utf8, invalid characters are discarded.
1135 * Note: this function is not intended for full objects with methods and private properties.
1137 * @param mixed $value
1138 * @return mixed with proper utf-8 encoding
1140 function fix_utf8($value) {
1141 if (is_null($value) or $value === '') {
1142 return $value;
1144 } else if (is_string($value)) {
1145 if ((string)(int)$value === $value) {
1146 // shortcut
1147 return $value;
1150 // Note: This is a partial backport of MDL-32586 and MDL-33007 to stable branches.
1151 // Lower error reporting because glibc throws bogus notices.
1152 $olderror = error_reporting();
1153 if ($olderror & E_NOTICE) {
1154 error_reporting($olderror ^ E_NOTICE);
1157 // Detect buggy iconv implementations borking results.
1158 static $buggyiconv = null;
1159 if ($buggyiconv === null) {
1160 $buggyiconv = (!function_exists('iconv') or iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
1163 if ($buggyiconv) {
1164 if (function_exists('mb_convert_encoding')) {
1165 // Fallback to mbstring if available.
1166 $subst = mb_substitute_character();
1167 mb_substitute_character('');
1168 $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
1169 mb_substitute_character($subst);
1171 } else {
1172 // Return unmodified text, mbstring not available.
1173 $result = $value;
1176 } else {
1177 // Working iconv, use it normally (with PHP notices disabled)
1178 $result = iconv('UTF-8', 'UTF-8//IGNORE', $value);
1181 // Back to original reporting level
1182 if ($olderror & E_NOTICE) {
1183 error_reporting($olderror);
1186 return $result;
1188 } else if (is_array($value)) {
1189 foreach ($value as $k=>$v) {
1190 $value[$k] = fix_utf8($v);
1192 return $value;
1194 } else if (is_object($value)) {
1195 $value = clone($value); // do not modify original
1196 foreach ($value as $k=>$v) {
1197 $value->$k = fix_utf8($v);
1199 return $value;
1201 } else {
1202 // this is some other type, no utf-8 here
1203 return $value;
1208 * Return true if given value is integer or string with integer value
1210 * @param mixed $value String or Int
1211 * @return bool true if number, false if not
1213 function is_number($value) {
1214 if (is_int($value)) {
1215 return true;
1216 } else if (is_string($value)) {
1217 return ((string)(int)$value) === $value;
1218 } else {
1219 return false;
1224 * Returns host part from url
1225 * @param string $url full url
1226 * @return string host, null if not found
1228 function get_host_from_url($url) {
1229 preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
1230 if ($matches) {
1231 return $matches[1];
1233 return null;
1237 * Tests whether anything was returned by text editor
1239 * This function is useful for testing whether something you got back from
1240 * the HTML editor actually contains anything. Sometimes the HTML editor
1241 * appear to be empty, but actually you get back a <br> tag or something.
1243 * @param string $string a string containing HTML.
1244 * @return boolean does the string contain any actual content - that is text,
1245 * images, objects, etc.
1247 function html_is_blank($string) {
1248 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
1252 * Set a key in global configuration
1254 * Set a key/value pair in both this session's {@link $CFG} global variable
1255 * and in the 'config' database table for future sessions.
1257 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
1258 * In that case it doesn't affect $CFG.
1260 * A NULL value will delete the entry.
1262 * @global object
1263 * @global object
1264 * @param string $name the key to set
1265 * @param string $value the value to set (without magic quotes)
1266 * @param string $plugin (optional) the plugin scope, default NULL
1267 * @return bool true or exception
1269 function set_config($name, $value, $plugin=NULL) {
1270 global $CFG, $DB;
1272 if (empty($plugin)) {
1273 if (!array_key_exists($name, $CFG->config_php_settings)) {
1274 // So it's defined for this invocation at least
1275 if (is_null($value)) {
1276 unset($CFG->$name);
1277 } else {
1278 $CFG->$name = (string)$value; // settings from db are always strings
1282 if ($DB->get_field('config', 'name', array('name'=>$name))) {
1283 if ($value === null) {
1284 $DB->delete_records('config', array('name'=>$name));
1285 } else {
1286 $DB->set_field('config', 'value', $value, array('name'=>$name));
1288 } else {
1289 if ($value !== null) {
1290 $config = new stdClass();
1291 $config->name = $name;
1292 $config->value = $value;
1293 $DB->insert_record('config', $config, false);
1297 } else { // plugin scope
1298 if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
1299 if ($value===null) {
1300 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1301 } else {
1302 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
1304 } else {
1305 if ($value !== null) {
1306 $config = new stdClass();
1307 $config->plugin = $plugin;
1308 $config->name = $name;
1309 $config->value = $value;
1310 $DB->insert_record('config_plugins', $config, false);
1315 return true;
1319 * Get configuration values from the global config table
1320 * or the config_plugins table.
1322 * If called with one parameter, it will load all the config
1323 * variables for one plugin, and return them as an object.
1325 * If called with 2 parameters it will return a string single
1326 * value or false if the value is not found.
1328 * @param string $plugin full component name
1329 * @param string $name default NULL
1330 * @return mixed hash-like object or single value, return false no config found
1332 function get_config($plugin, $name = NULL) {
1333 global $CFG, $DB;
1335 // normalise component name
1336 if ($plugin === 'moodle' or $plugin === 'core') {
1337 $plugin = NULL;
1340 if (!empty($name)) { // the user is asking for a specific value
1341 if (!empty($plugin)) {
1342 if (isset($CFG->forced_plugin_settings[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings[$plugin])) {
1343 // setting forced in config file
1344 return $CFG->forced_plugin_settings[$plugin][$name];
1345 } else {
1346 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1348 } else {
1349 if (array_key_exists($name, $CFG->config_php_settings)) {
1350 // setting force in config file
1351 return $CFG->config_php_settings[$name];
1352 } else {
1353 return $DB->get_field('config', 'value', array('name'=>$name));
1358 // the user is after a recordset
1359 if ($plugin) {
1360 $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
1361 if (isset($CFG->forced_plugin_settings[$plugin])) {
1362 foreach($CFG->forced_plugin_settings[$plugin] as $n=>$v) {
1363 if (is_null($v) or is_array($v) or is_object($v)) {
1364 // we do not want any extra mess here, just real settings that could be saved in db
1365 unset($localcfg[$n]);
1366 } else {
1367 //convert to string as if it went through the DB
1368 $localcfg[$n] = (string)$v;
1372 if ($localcfg) {
1373 return (object)$localcfg;
1374 } else {
1375 return new stdClass();
1378 } else {
1379 // this part is not really used any more, but anyway...
1380 $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
1381 foreach($CFG->config_php_settings as $n=>$v) {
1382 if (is_null($v) or is_array($v) or is_object($v)) {
1383 // we do not want any extra mess here, just real settings that could be saved in db
1384 unset($localcfg[$n]);
1385 } else {
1386 //convert to string as if it went through the DB
1387 $localcfg[$n] = (string)$v;
1390 return (object)$localcfg;
1395 * Removes a key from global configuration
1397 * @param string $name the key to set
1398 * @param string $plugin (optional) the plugin scope
1399 * @global object
1400 * @return boolean whether the operation succeeded.
1402 function unset_config($name, $plugin=NULL) {
1403 global $CFG, $DB;
1405 if (empty($plugin)) {
1406 unset($CFG->$name);
1407 $DB->delete_records('config', array('name'=>$name));
1408 } else {
1409 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1412 return true;
1416 * Remove all the config variables for a given plugin.
1418 * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1419 * @return boolean whether the operation succeeded.
1421 function unset_all_config_for_plugin($plugin) {
1422 global $DB;
1423 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1424 $like = $DB->sql_like('name', '?', true, true, false, '|');
1425 $params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
1426 $DB->delete_records_select('config', $like, $params);
1427 return true;
1431 * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1433 * All users are verified if they still have the necessary capability.
1435 * @param string $value the value of the config setting.
1436 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1437 * @param bool $include admins, include administrators
1438 * @return array of user objects.
1440 function get_users_from_config($value, $capability, $includeadmins = true) {
1441 global $CFG, $DB;
1443 if (empty($value) or $value === '$@NONE@$') {
1444 return array();
1447 // we have to make sure that users still have the necessary capability,
1448 // it should be faster to fetch them all first and then test if they are present
1449 // instead of validating them one-by-one
1450 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
1451 if ($includeadmins) {
1452 $admins = get_admins();
1453 foreach ($admins as $admin) {
1454 $users[$admin->id] = $admin;
1458 if ($value === '$@ALL@$') {
1459 return $users;
1462 $result = array(); // result in correct order
1463 $allowed = explode(',', $value);
1464 foreach ($allowed as $uid) {
1465 if (isset($users[$uid])) {
1466 $user = $users[$uid];
1467 $result[$user->id] = $user;
1471 return $result;
1476 * Invalidates browser caches and cached data in temp
1477 * @return void
1479 function purge_all_caches() {
1480 global $CFG;
1482 reset_text_filters_cache();
1483 js_reset_all_caches();
1484 theme_reset_all_caches();
1485 get_string_manager()->reset_caches();
1487 // purge all other caches: rss, simplepie, etc.
1488 remove_dir($CFG->cachedir.'', true);
1490 // make sure cache dir is writable, throws exception if not
1491 make_cache_directory('');
1493 // hack: this script may get called after the purifier was initialised,
1494 // but we do not want to verify repeatedly this exists in each call
1495 make_cache_directory('htmlpurifier');
1499 * Get volatile flags
1501 * @param string $type
1502 * @param int $changedsince default null
1503 * @return records array
1505 function get_cache_flags($type, $changedsince=NULL) {
1506 global $DB;
1508 $params = array('type'=>$type, 'expiry'=>time());
1509 $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1510 if ($changedsince !== NULL) {
1511 $params['changedsince'] = $changedsince;
1512 $sqlwhere .= " AND timemodified > :changedsince";
1514 $cf = array();
1516 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1517 foreach ($flags as $flag) {
1518 $cf[$flag->name] = $flag->value;
1521 return $cf;
1525 * Get volatile flags
1527 * @param string $type
1528 * @param string $name
1529 * @param int $changedsince default null
1530 * @return records array
1532 function get_cache_flag($type, $name, $changedsince=NULL) {
1533 global $DB;
1535 $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
1537 $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1538 if ($changedsince !== NULL) {
1539 $params['changedsince'] = $changedsince;
1540 $sqlwhere .= " AND timemodified > :changedsince";
1543 return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1547 * Set a volatile flag
1549 * @param string $type the "type" namespace for the key
1550 * @param string $name the key to set
1551 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
1552 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1553 * @return bool Always returns true
1555 function set_cache_flag($type, $name, $value, $expiry=NULL) {
1556 global $DB;
1558 $timemodified = time();
1559 if ($expiry===NULL || $expiry < $timemodified) {
1560 $expiry = $timemodified + 24 * 60 * 60;
1561 } else {
1562 $expiry = (int)$expiry;
1565 if ($value === NULL) {
1566 unset_cache_flag($type,$name);
1567 return true;
1570 if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE)) { // this is a potential problem in DEBUG_DEVELOPER
1571 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
1572 return true; //no need to update; helps rcache too
1574 $f->value = $value;
1575 $f->expiry = $expiry;
1576 $f->timemodified = $timemodified;
1577 $DB->update_record('cache_flags', $f);
1578 } else {
1579 $f = new stdClass();
1580 $f->flagtype = $type;
1581 $f->name = $name;
1582 $f->value = $value;
1583 $f->expiry = $expiry;
1584 $f->timemodified = $timemodified;
1585 $DB->insert_record('cache_flags', $f);
1587 return true;
1591 * Removes a single volatile flag
1593 * @global object
1594 * @param string $type the "type" namespace for the key
1595 * @param string $name the key to set
1596 * @return bool
1598 function unset_cache_flag($type, $name) {
1599 global $DB;
1600 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1601 return true;
1605 * Garbage-collect volatile flags
1607 * @return bool Always returns true
1609 function gc_cache_flags() {
1610 global $DB;
1611 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1612 return true;
1615 /// FUNCTIONS FOR HANDLING USER PREFERENCES ////////////////////////////////////
1618 * Refresh user preference cache. This is used most often for $USER
1619 * object that is stored in session, but it also helps with performance in cron script.
1621 * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1623 * @param stdClass $user user object, preferences are preloaded into ->preference property
1624 * @param int $cachelifetime cache life time on the current page (ins seconds)
1625 * @return void
1627 function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
1628 global $DB;
1629 static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
1631 if (!isset($user->id)) {
1632 throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1635 if (empty($user->id) or isguestuser($user->id)) {
1636 // No permanent storage for not-logged-in users and guest
1637 if (!isset($user->preference)) {
1638 $user->preference = array();
1640 return;
1643 $timenow = time();
1645 if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
1646 // Already loaded at least once on this page. Are we up to date?
1647 if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
1648 // no need to reload - we are on the same page and we loaded prefs just a moment ago
1649 return;
1651 } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
1652 // no change since the lastcheck on this page
1653 $user->preference['_lastloaded'] = $timenow;
1654 return;
1658 // OK, so we have to reload all preferences
1659 $loadedusers[$user->id] = true;
1660 $user->preference = $DB->get_records_menu('user_preferences', array('userid'=>$user->id), '', 'name,value'); // All values
1661 $user->preference['_lastloaded'] = $timenow;
1665 * Called from set/delete_user_preferences, so that the prefs can
1666 * be correctly reloaded in different sessions.
1668 * NOTE: internal function, do not call from other code.
1670 * @param integer $userid the user whose prefs were changed.
1671 * @return void
1673 function mark_user_preferences_changed($userid) {
1674 global $CFG;
1676 if (empty($userid) or isguestuser($userid)) {
1677 // no cache flags for guest and not-logged-in users
1678 return;
1681 set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
1685 * Sets a preference for the specified user.
1687 * If user object submitted, 'preference' property contains the preferences cache.
1689 * @param string $name The key to set as preference for the specified user
1690 * @param string $value The value to set for the $name key in the specified user's record,
1691 * null means delete current value
1692 * @param stdClass|int $user A moodle user object or id, null means current user
1693 * @return bool always true or exception
1695 function set_user_preference($name, $value, $user = null) {
1696 global $USER, $DB;
1698 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1699 throw new coding_exception('Invalid preference name in set_user_preference() call');
1702 if (is_null($value)) {
1703 // null means delete current
1704 return unset_user_preference($name, $user);
1705 } else if (is_object($value)) {
1706 throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1707 } else if (is_array($value)) {
1708 throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1710 $value = (string)$value;
1711 if (textlib::strlen($value) > 1333) { //value column maximum length is 1333 characters
1712 throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
1715 if (is_null($user)) {
1716 $user = $USER;
1717 } else if (isset($user->id)) {
1718 // $user is valid object
1719 } else if (is_numeric($user)) {
1720 $user = (object)array('id'=>(int)$user);
1721 } else {
1722 throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1725 check_user_preferences_loaded($user);
1727 if (empty($user->id) or isguestuser($user->id)) {
1728 // no permanent storage for not-logged-in users and guest
1729 $user->preference[$name] = $value;
1730 return true;
1733 if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>$name))) {
1734 if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
1735 // preference already set to this value
1736 return true;
1738 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id));
1740 } else {
1741 $preference = new stdClass();
1742 $preference->userid = $user->id;
1743 $preference->name = $name;
1744 $preference->value = $value;
1745 $DB->insert_record('user_preferences', $preference);
1748 // update value in cache
1749 $user->preference[$name] = $value;
1751 // set reload flag for other sessions
1752 mark_user_preferences_changed($user->id);
1754 return true;
1758 * Sets a whole array of preferences for the current user
1760 * If user object submitted, 'preference' property contains the preferences cache.
1762 * @param array $prefarray An array of key/value pairs to be set
1763 * @param stdClass|int $user A moodle user object or id, null means current user
1764 * @return bool always true or exception
1766 function set_user_preferences(array $prefarray, $user = null) {
1767 foreach ($prefarray as $name => $value) {
1768 set_user_preference($name, $value, $user);
1770 return true;
1774 * Unsets a preference completely by deleting it from the database
1776 * If user object submitted, 'preference' property contains the preferences cache.
1778 * @param string $name The key to unset as preference for the specified user
1779 * @param stdClass|int $user A moodle user object or id, null means current user
1780 * @return bool always true or exception
1782 function unset_user_preference($name, $user = null) {
1783 global $USER, $DB;
1785 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1786 throw new coding_exception('Invalid preference name in unset_user_preference() call');
1789 if (is_null($user)) {
1790 $user = $USER;
1791 } else if (isset($user->id)) {
1792 // $user is valid object
1793 } else if (is_numeric($user)) {
1794 $user = (object)array('id'=>(int)$user);
1795 } else {
1796 throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1799 check_user_preferences_loaded($user);
1801 if (empty($user->id) or isguestuser($user->id)) {
1802 // no permanent storage for not-logged-in user and guest
1803 unset($user->preference[$name]);
1804 return true;
1807 // delete from DB
1808 $DB->delete_records('user_preferences', array('userid'=>$user->id, 'name'=>$name));
1810 // delete the preference from cache
1811 unset($user->preference[$name]);
1813 // set reload flag for other sessions
1814 mark_user_preferences_changed($user->id);
1816 return true;
1820 * Used to fetch user preference(s)
1822 * If no arguments are supplied this function will return
1823 * all of the current user preferences as an array.
1825 * If a name is specified then this function
1826 * attempts to return that particular preference value. If
1827 * none is found, then the optional value $default is returned,
1828 * otherwise NULL.
1830 * If user object submitted, 'preference' property contains the preferences cache.
1832 * @param string $name Name of the key to use in finding a preference value
1833 * @param mixed $default Value to be returned if the $name key is not set in the user preferences
1834 * @param stdClass|int $user A moodle user object or id, null means current user
1835 * @return mixed string value or default
1837 function get_user_preferences($name = null, $default = null, $user = null) {
1838 global $USER;
1840 if (is_null($name)) {
1841 // all prefs
1842 } else if (is_numeric($name) or $name === '_lastloaded') {
1843 throw new coding_exception('Invalid preference name in get_user_preferences() call');
1846 if (is_null($user)) {
1847 $user = $USER;
1848 } else if (isset($user->id)) {
1849 // $user is valid object
1850 } else if (is_numeric($user)) {
1851 $user = (object)array('id'=>(int)$user);
1852 } else {
1853 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1856 check_user_preferences_loaded($user);
1858 if (empty($name)) {
1859 return $user->preference; // All values
1860 } else if (isset($user->preference[$name])) {
1861 return $user->preference[$name]; // The single string value
1862 } else {
1863 return $default; // Default value (null if not specified)
1867 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1870 * Given date parts in user time produce a GMT timestamp.
1872 * @todo Finish documenting this function
1873 * @param int $year The year part to create timestamp of
1874 * @param int $month The month part to create timestamp of
1875 * @param int $day The day part to create timestamp of
1876 * @param int $hour The hour part to create timestamp of
1877 * @param int $minute The minute part to create timestamp of
1878 * @param int $second The second part to create timestamp of
1879 * @param mixed $timezone Timezone modifier, if 99 then use default user's timezone
1880 * @param bool $applydst Toggle Daylight Saving Time, default true, will be
1881 * applied only if timezone is 99 or string.
1882 * @return int timestamp
1884 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1886 //save input timezone, required for dst offset check.
1887 $passedtimezone = $timezone;
1889 $timezone = get_user_timezone_offset($timezone);
1891 if (abs($timezone) > 13) { //server time
1892 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1893 } else {
1894 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1895 $time = usertime($time, $timezone);
1897 //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
1898 if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
1899 $time -= dst_offset_on($time, $passedtimezone);
1903 return $time;
1908 * Format a date/time (seconds) as weeks, days, hours etc as needed
1910 * Given an amount of time in seconds, returns string
1911 * formatted nicely as weeks, days, hours etc as needed
1913 * @uses MINSECS
1914 * @uses HOURSECS
1915 * @uses DAYSECS
1916 * @uses YEARSECS
1917 * @param int $totalsecs Time in seconds
1918 * @param object $str Should be a time object
1919 * @return string A nicely formatted date/time string
1921 function format_time($totalsecs, $str=NULL) {
1923 $totalsecs = abs($totalsecs);
1925 if (!$str) { // Create the str structure the slow way
1926 $str = new stdClass();
1927 $str->day = get_string('day');
1928 $str->days = get_string('days');
1929 $str->hour = get_string('hour');
1930 $str->hours = get_string('hours');
1931 $str->min = get_string('min');
1932 $str->mins = get_string('mins');
1933 $str->sec = get_string('sec');
1934 $str->secs = get_string('secs');
1935 $str->year = get_string('year');
1936 $str->years = get_string('years');
1940 $years = floor($totalsecs/YEARSECS);
1941 $remainder = $totalsecs - ($years*YEARSECS);
1942 $days = floor($remainder/DAYSECS);
1943 $remainder = $totalsecs - ($days*DAYSECS);
1944 $hours = floor($remainder/HOURSECS);
1945 $remainder = $remainder - ($hours*HOURSECS);
1946 $mins = floor($remainder/MINSECS);
1947 $secs = $remainder - ($mins*MINSECS);
1949 $ss = ($secs == 1) ? $str->sec : $str->secs;
1950 $sm = ($mins == 1) ? $str->min : $str->mins;
1951 $sh = ($hours == 1) ? $str->hour : $str->hours;
1952 $sd = ($days == 1) ? $str->day : $str->days;
1953 $sy = ($years == 1) ? $str->year : $str->years;
1955 $oyears = '';
1956 $odays = '';
1957 $ohours = '';
1958 $omins = '';
1959 $osecs = '';
1961 if ($years) $oyears = $years .' '. $sy;
1962 if ($days) $odays = $days .' '. $sd;
1963 if ($hours) $ohours = $hours .' '. $sh;
1964 if ($mins) $omins = $mins .' '. $sm;
1965 if ($secs) $osecs = $secs .' '. $ss;
1967 if ($years) return trim($oyears .' '. $odays);
1968 if ($days) return trim($odays .' '. $ohours);
1969 if ($hours) return trim($ohours .' '. $omins);
1970 if ($mins) return trim($omins .' '. $osecs);
1971 if ($secs) return $osecs;
1972 return get_string('now');
1976 * Returns a formatted string that represents a date in user time
1978 * Returns a formatted string that represents a date in user time
1979 * <b>WARNING: note that the format is for strftime(), not date().</b>
1980 * Because of a bug in most Windows time libraries, we can't use
1981 * the nicer %e, so we have to use %d which has leading zeroes.
1982 * A lot of the fuss in the function is just getting rid of these leading
1983 * zeroes as efficiently as possible.
1985 * If parameter fixday = true (default), then take off leading
1986 * zero from %d, else maintain it.
1988 * @param int $date the timestamp in UTC, as obtained from the database.
1989 * @param string $format strftime format. You should probably get this using
1990 * get_string('strftime...', 'langconfig');
1991 * @param mixed $timezone by default, uses the user's time zone. if numeric and
1992 * not 99 then daylight saving will not be added.
1993 * @param bool $fixday If true (default) then the leading zero from %d is removed.
1994 * If false then the leading zero is maintained.
1995 * @return string the formatted date/time.
1997 function userdate($date, $format = '', $timezone = 99, $fixday = true) {
1999 global $CFG;
2001 if (empty($format)) {
2002 $format = get_string('strftimedaydatetime', 'langconfig');
2005 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
2006 $fixday = false;
2007 } else if ($fixday) {
2008 $formatnoday = str_replace('%d', 'DD', $format);
2009 $fixday = ($formatnoday != $format);
2012 //add daylight saving offset for string timezones only, as we can't get dst for
2013 //float values. if timezone is 99 (user default timezone), then try update dst.
2014 if ((99 == $timezone) || !is_numeric($timezone)) {
2015 $date += dst_offset_on($date, $timezone);
2018 $timezone = get_user_timezone_offset($timezone);
2020 if (abs($timezone) > 13) { /// Server time
2021 if ($fixday) {
2022 $datestring = strftime($formatnoday, $date);
2023 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
2024 $datestring = str_replace('DD', $daystring, $datestring);
2025 } else {
2026 $datestring = strftime($format, $date);
2028 } else {
2029 $date += (int)($timezone * 3600);
2030 if ($fixday) {
2031 $datestring = gmstrftime($formatnoday, $date);
2032 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
2033 $datestring = str_replace('DD', $daystring, $datestring);
2034 } else {
2035 $datestring = gmstrftime($format, $date);
2039 /// If we are running under Windows convert from windows encoding to UTF-8
2040 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
2042 if ($CFG->ostype == 'WINDOWS') {
2043 if ($localewincharset = get_string('localewincharset', 'langconfig')) {
2044 $textlib = textlib_get_instance();
2045 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
2049 return $datestring;
2053 * Given a $time timestamp in GMT (seconds since epoch),
2054 * returns an array that represents the date in user time
2056 * @todo Finish documenting this function
2057 * @uses HOURSECS
2058 * @param int $time Timestamp in GMT
2059 * @param mixed $timezone offset time with timezone, if float and not 99, then no
2060 * dst offset is applyed
2061 * @return array An array that represents the date in user time
2063 function usergetdate($time, $timezone=99) {
2065 //save input timezone, required for dst offset check.
2066 $passedtimezone = $timezone;
2068 $timezone = get_user_timezone_offset($timezone);
2070 if (abs($timezone) > 13) { // Server time
2071 return getdate($time);
2074 //add daylight saving offset for string timezones only, as we can't get dst for
2075 //float values. if timezone is 99 (user default timezone), then try update dst.
2076 if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
2077 $time += dst_offset_on($time, $passedtimezone);
2080 $time += intval((float)$timezone * HOURSECS);
2082 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
2084 //be careful to ensure the returned array matches that produced by getdate() above
2085 list(
2086 $getdate['month'],
2087 $getdate['weekday'],
2088 $getdate['yday'],
2089 $getdate['year'],
2090 $getdate['mon'],
2091 $getdate['wday'],
2092 $getdate['mday'],
2093 $getdate['hours'],
2094 $getdate['minutes'],
2095 $getdate['seconds']
2096 ) = explode('_', $datestring);
2098 // set correct datatype to match with getdate()
2099 $getdate['seconds'] = (int)$getdate['seconds'];
2100 $getdate['yday'] = (int)$getdate['yday'] - 1; // gettime returns 0 through 365
2101 $getdate['year'] = (int)$getdate['year'];
2102 $getdate['mon'] = (int)$getdate['mon'];
2103 $getdate['wday'] = (int)$getdate['wday'];
2104 $getdate['mday'] = (int)$getdate['mday'];
2105 $getdate['hours'] = (int)$getdate['hours'];
2106 $getdate['minutes'] = (int)$getdate['minutes'];
2107 return $getdate;
2111 * Given a GMT timestamp (seconds since epoch), offsets it by
2112 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
2114 * @uses HOURSECS
2115 * @param int $date Timestamp in GMT
2116 * @param float $timezone
2117 * @return int
2119 function usertime($date, $timezone=99) {
2121 $timezone = get_user_timezone_offset($timezone);
2123 if (abs($timezone) > 13) {
2124 return $date;
2126 return $date - (int)($timezone * HOURSECS);
2130 * Given a time, return the GMT timestamp of the most recent midnight
2131 * for the current user.
2133 * @param int $date Timestamp in GMT
2134 * @param float $timezone Defaults to user's timezone
2135 * @return int Returns a GMT timestamp
2137 function usergetmidnight($date, $timezone=99) {
2139 $userdate = usergetdate($date, $timezone);
2141 // Time of midnight of this user's day, in GMT
2142 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
2147 * Returns a string that prints the user's timezone
2149 * @param float $timezone The user's timezone
2150 * @return string
2152 function usertimezone($timezone=99) {
2154 $tz = get_user_timezone($timezone);
2156 if (!is_float($tz)) {
2157 return $tz;
2160 if(abs($tz) > 13) { // Server time
2161 return get_string('serverlocaltime');
2164 if($tz == intval($tz)) {
2165 // Don't show .0 for whole hours
2166 $tz = intval($tz);
2169 if($tz == 0) {
2170 return 'UTC';
2172 else if($tz > 0) {
2173 return 'UTC+'.$tz;
2175 else {
2176 return 'UTC'.$tz;
2182 * Returns a float which represents the user's timezone difference from GMT in hours
2183 * Checks various settings and picks the most dominant of those which have a value
2185 * @global object
2186 * @global object
2187 * @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
2188 * @return float
2190 function get_user_timezone_offset($tz = 99) {
2192 global $USER, $CFG;
2194 $tz = get_user_timezone($tz);
2196 if (is_float($tz)) {
2197 return $tz;
2198 } else {
2199 $tzrecord = get_timezone_record($tz);
2200 if (empty($tzrecord)) {
2201 return 99.0;
2203 return (float)$tzrecord->gmtoff / HOURMINS;
2208 * Returns an int which represents the systems's timezone difference from GMT in seconds
2210 * @global object
2211 * @param mixed $tz timezone
2212 * @return int if found, false is timezone 99 or error
2214 function get_timezone_offset($tz) {
2215 global $CFG;
2217 if ($tz == 99) {
2218 return false;
2221 if (is_numeric($tz)) {
2222 return intval($tz * 60*60);
2225 if (!$tzrecord = get_timezone_record($tz)) {
2226 return false;
2228 return intval($tzrecord->gmtoff * 60);
2232 * Returns a float or a string which denotes the user's timezone
2233 * 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)
2234 * means that for this timezone there are also DST rules to be taken into account
2235 * Checks various settings and picks the most dominant of those which have a value
2237 * @global object
2238 * @global object
2239 * @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
2240 * @return mixed
2242 function get_user_timezone($tz = 99) {
2243 global $USER, $CFG;
2245 $timezones = array(
2246 $tz,
2247 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
2248 isset($USER->timezone) ? $USER->timezone : 99,
2249 isset($CFG->timezone) ? $CFG->timezone : 99,
2252 $tz = 99;
2254 // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array
2255 while(((empty($tz) && !is_numeric($tz)) || $tz == 99) && $next = each($timezones)) {
2256 $tz = $next['value'];
2258 return is_numeric($tz) ? (float) $tz : $tz;
2262 * Returns cached timezone record for given $timezonename
2264 * @global object
2265 * @global object
2266 * @param string $timezonename
2267 * @return mixed timezonerecord object or false
2269 function get_timezone_record($timezonename) {
2270 global $CFG, $DB;
2271 static $cache = NULL;
2273 if ($cache === NULL) {
2274 $cache = array();
2277 if (isset($cache[$timezonename])) {
2278 return $cache[$timezonename];
2281 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
2282 WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
2286 * Build and store the users Daylight Saving Time (DST) table
2288 * @global object
2289 * @global object
2290 * @global object
2291 * @param mixed $from_year Start year for the table, defaults to 1971
2292 * @param mixed $to_year End year for the table, defaults to 2035
2293 * @param mixed $strtimezone, if null or 99 then user's default timezone is used
2294 * @return bool
2296 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
2297 global $CFG, $SESSION, $DB;
2299 $usertz = get_user_timezone($strtimezone);
2301 if (is_float($usertz)) {
2302 // Trivial timezone, no DST
2303 return false;
2306 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
2307 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2308 unset($SESSION->dst_offsets);
2309 unset($SESSION->dst_range);
2312 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
2313 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
2314 // This will be the return path most of the time, pretty light computationally
2315 return true;
2318 // Reaching here means we either need to extend our table or create it from scratch
2320 // Remember which TZ we calculated these changes for
2321 $SESSION->dst_offsettz = $usertz;
2323 if(empty($SESSION->dst_offsets)) {
2324 // If we 're creating from scratch, put the two guard elements in there
2325 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
2327 if(empty($SESSION->dst_range)) {
2328 // If creating from scratch
2329 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
2330 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
2332 // Fill in the array with the extra years we need to process
2333 $yearstoprocess = array();
2334 for($i = $from; $i <= $to; ++$i) {
2335 $yearstoprocess[] = $i;
2338 // Take note of which years we have processed for future calls
2339 $SESSION->dst_range = array($from, $to);
2341 else {
2342 // If needing to extend the table, do the same
2343 $yearstoprocess = array();
2345 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
2346 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
2348 if($from < $SESSION->dst_range[0]) {
2349 // Take note of which years we need to process and then note that we have processed them for future calls
2350 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2351 $yearstoprocess[] = $i;
2353 $SESSION->dst_range[0] = $from;
2355 if($to > $SESSION->dst_range[1]) {
2356 // Take note of which years we need to process and then note that we have processed them for future calls
2357 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2358 $yearstoprocess[] = $i;
2360 $SESSION->dst_range[1] = $to;
2364 if(empty($yearstoprocess)) {
2365 // This means that there was a call requesting a SMALLER range than we have already calculated
2366 return true;
2369 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2370 // Also, the array is sorted in descending timestamp order!
2372 // Get DB data
2374 static $presets_cache = array();
2375 if (!isset($presets_cache[$usertz])) {
2376 $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');
2378 if(empty($presets_cache[$usertz])) {
2379 return false;
2382 // Remove ending guard (first element of the array)
2383 reset($SESSION->dst_offsets);
2384 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2386 // Add all required change timestamps
2387 foreach($yearstoprocess as $y) {
2388 // Find the record which is in effect for the year $y
2389 foreach($presets_cache[$usertz] as $year => $preset) {
2390 if($year <= $y) {
2391 break;
2395 $changes = dst_changes_for_year($y, $preset);
2397 if($changes === NULL) {
2398 continue;
2400 if($changes['dst'] != 0) {
2401 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2403 if($changes['std'] != 0) {
2404 $SESSION->dst_offsets[$changes['std']] = 0;
2408 // Put in a guard element at the top
2409 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2410 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2412 // Sort again
2413 krsort($SESSION->dst_offsets);
2415 return true;
2419 * Calculates the required DST change and returns a Timestamp Array
2421 * @uses HOURSECS
2422 * @uses MINSECS
2423 * @param mixed $year Int or String Year to focus on
2424 * @param object $timezone Instatiated Timezone object
2425 * @return mixed Null, or Array dst=>xx, 0=>xx, std=>yy, 1=>yy
2427 function dst_changes_for_year($year, $timezone) {
2429 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2430 return NULL;
2433 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2434 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2436 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
2437 list($std_hour, $std_min) = explode(':', $timezone->std_time);
2439 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2440 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2442 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2443 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2444 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2446 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
2447 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
2449 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2453 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2454 * - Note: Daylight saving only works for string timezones and not for float.
2456 * @global object
2457 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2458 * @param mixed $strtimezone timezone for which offset is expected, if 99 or null
2459 * then user's default timezone is used.
2460 * @return int
2462 function dst_offset_on($time, $strtimezone = NULL) {
2463 global $SESSION;
2465 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
2466 return 0;
2469 reset($SESSION->dst_offsets);
2470 while(list($from, $offset) = each($SESSION->dst_offsets)) {
2471 if($from <= $time) {
2472 break;
2476 // This is the normal return path
2477 if($offset !== NULL) {
2478 return $offset;
2481 // Reaching this point means we haven't calculated far enough, do it now:
2482 // Calculate extra DST changes if needed and recurse. The recursion always
2483 // moves toward the stopping condition, so will always end.
2485 if($from == 0) {
2486 // We need a year smaller than $SESSION->dst_range[0]
2487 if($SESSION->dst_range[0] == 1971) {
2488 return 0;
2490 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
2491 return dst_offset_on($time, $strtimezone);
2493 else {
2494 // We need a year larger than $SESSION->dst_range[1]
2495 if($SESSION->dst_range[1] == 2035) {
2496 return 0;
2498 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
2499 return dst_offset_on($time, $strtimezone);
2506 * @todo Document what this function does
2507 * @param int $startday
2508 * @param int $weekday
2509 * @param int $month
2510 * @param int $year
2511 * @return int
2513 function find_day_in_month($startday, $weekday, $month, $year) {
2515 $daysinmonth = days_in_month($month, $year);
2517 if($weekday == -1) {
2518 // Don't care about weekday, so return:
2519 // abs($startday) if $startday != -1
2520 // $daysinmonth otherwise
2521 return ($startday == -1) ? $daysinmonth : abs($startday);
2524 // From now on we 're looking for a specific weekday
2526 // Give "end of month" its actual value, since we know it
2527 if($startday == -1) {
2528 $startday = -1 * $daysinmonth;
2531 // Starting from day $startday, the sign is the direction
2533 if($startday < 1) {
2535 $startday = abs($startday);
2536 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2538 // This is the last such weekday of the month
2539 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2540 if($lastinmonth > $daysinmonth) {
2541 $lastinmonth -= 7;
2544 // Find the first such weekday <= $startday
2545 while($lastinmonth > $startday) {
2546 $lastinmonth -= 7;
2549 return $lastinmonth;
2552 else {
2554 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2556 $diff = $weekday - $indexweekday;
2557 if($diff < 0) {
2558 $diff += 7;
2561 // This is the first such weekday of the month equal to or after $startday
2562 $firstfromindex = $startday + $diff;
2564 return $firstfromindex;
2570 * Calculate the number of days in a given month
2572 * @param int $month The month whose day count is sought
2573 * @param int $year The year of the month whose day count is sought
2574 * @return int
2576 function days_in_month($month, $year) {
2577 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2581 * Calculate the position in the week of a specific calendar day
2583 * @param int $day The day of the date whose position in the week is sought
2584 * @param int $month The month of the date whose position in the week is sought
2585 * @param int $year The year of the date whose position in the week is sought
2586 * @return int
2588 function dayofweek($day, $month, $year) {
2589 // I wonder if this is any different from
2590 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2591 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2594 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2597 * Returns full login url.
2599 * @return string login url
2601 function get_login_url() {
2602 global $CFG;
2604 $url = "$CFG->wwwroot/login/index.php";
2606 if (!empty($CFG->loginhttps)) {
2607 $url = str_replace('http:', 'https:', $url);
2610 return $url;
2614 * This function checks that the current user is logged in and has the
2615 * required privileges
2617 * This function checks that the current user is logged in, and optionally
2618 * whether they are allowed to be in a particular course and view a particular
2619 * course module.
2620 * If they are not logged in, then it redirects them to the site login unless
2621 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2622 * case they are automatically logged in as guests.
2623 * If $courseid is given and the user is not enrolled in that course then the
2624 * user is redirected to the course enrolment page.
2625 * If $cm is given and the course module is hidden and the user is not a teacher
2626 * in the course then the user is redirected to the course home page.
2628 * When $cm parameter specified, this function sets page layout to 'module'.
2629 * You need to change it manually later if some other layout needed.
2631 * @param mixed $courseorid id of the course or course object
2632 * @param bool $autologinguest default true
2633 * @param object $cm course module object
2634 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2635 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2636 * in order to keep redirects working properly. MDL-14495
2637 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2638 * @return mixed Void, exit, and die depending on path
2640 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2641 global $CFG, $SESSION, $USER, $FULLME, $PAGE, $SITE, $DB, $OUTPUT;
2643 // setup global $COURSE, themes, language and locale
2644 if (!empty($courseorid)) {
2645 if (is_object($courseorid)) {
2646 $course = $courseorid;
2647 } else if ($courseorid == SITEID) {
2648 $course = clone($SITE);
2649 } else {
2650 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2652 if ($cm) {
2653 if ($cm->course != $course->id) {
2654 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2656 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2657 if (!($cm instanceof cm_info)) {
2658 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2659 // db queries so this is not really a performance concern, however it is obviously
2660 // better if you use get_fast_modinfo to get the cm before calling this.
2661 $modinfo = get_fast_modinfo($course);
2662 $cm = $modinfo->get_cm($cm->id);
2664 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2665 $PAGE->set_pagelayout('incourse');
2666 } else {
2667 $PAGE->set_course($course); // set's up global $COURSE
2669 } else {
2670 // do not touch global $COURSE via $PAGE->set_course(),
2671 // the reasons is we need to be able to call require_login() at any time!!
2672 $course = $SITE;
2673 if ($cm) {
2674 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2678 // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2679 // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2680 // risk leading the user back to the AJAX request URL.
2681 if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
2682 $setwantsurltome = false;
2685 // If the user is not even logged in yet then make sure they are
2686 if (!isloggedin()) {
2687 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2688 if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2689 // misconfigured site guest, just redirect to login page
2690 redirect(get_login_url());
2691 exit; // never reached
2693 $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2694 complete_user_login($guest);
2695 $USER->autologinguest = true;
2696 $SESSION->lang = $lang;
2697 } else {
2698 //NOTE: $USER->site check was obsoleted by session test cookie,
2699 // $USER->confirmed test is in login/index.php
2700 if ($preventredirect) {
2701 throw new require_login_exception('You are not logged in');
2704 if ($setwantsurltome) {
2705 // TODO: switch to PAGE->url
2706 $SESSION->wantsurl = $FULLME;
2708 if (!empty($_SERVER['HTTP_REFERER'])) {
2709 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
2711 redirect(get_login_url());
2712 exit; // never reached
2716 // loginas as redirection if needed
2717 if ($course->id != SITEID and session_is_loggedinas()) {
2718 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2719 if ($USER->loginascontext->instanceid != $course->id) {
2720 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2725 // check whether the user should be changing password (but only if it is REALLY them)
2726 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2727 $userauth = get_auth_plugin($USER->auth);
2728 if ($userauth->can_change_password() and !$preventredirect) {
2729 if ($setwantsurltome) {
2730 $SESSION->wantsurl = $FULLME;
2732 if ($changeurl = $userauth->change_password_url()) {
2733 //use plugin custom url
2734 redirect($changeurl);
2735 } else {
2736 //use moodle internal method
2737 if (empty($CFG->loginhttps)) {
2738 redirect($CFG->wwwroot .'/login/change_password.php');
2739 } else {
2740 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
2741 redirect($wwwroot .'/login/change_password.php');
2744 } else {
2745 print_error('nopasswordchangeforced', 'auth');
2749 // Check that the user account is properly set up
2750 if (user_not_fully_set_up($USER)) {
2751 if ($preventredirect) {
2752 throw new require_login_exception('User not fully set-up');
2754 if ($setwantsurltome) {
2755 $SESSION->wantsurl = $FULLME;
2757 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2760 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2761 sesskey();
2763 // Do not bother admins with any formalities
2764 if (is_siteadmin()) {
2765 //set accesstime or the user will appear offline which messes up messaging
2766 user_accesstime_log($course->id);
2767 return;
2770 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2771 if (!$USER->policyagreed and !is_siteadmin()) {
2772 if (!empty($CFG->sitepolicy) and !isguestuser()) {
2773 if ($preventredirect) {
2774 throw new require_login_exception('Policy not agreed');
2776 if ($setwantsurltome) {
2777 $SESSION->wantsurl = $FULLME;
2779 redirect($CFG->wwwroot .'/user/policy.php');
2780 } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
2781 if ($preventredirect) {
2782 throw new require_login_exception('Policy not agreed');
2784 if ($setwantsurltome) {
2785 $SESSION->wantsurl = $FULLME;
2787 redirect($CFG->wwwroot .'/user/policy.php');
2791 // Fetch the system context, the course context, and prefetch its child contexts
2792 $sysctx = get_context_instance(CONTEXT_SYSTEM);
2793 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
2794 if ($cm) {
2795 $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
2796 } else {
2797 $cmcontext = null;
2800 // If the site is currently under maintenance, then print a message
2801 if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
2802 if ($preventredirect) {
2803 throw new require_login_exception('Maintenance in progress');
2806 print_maintenance_message();
2809 // make sure the course itself is not hidden
2810 if ($course->id == SITEID) {
2811 // frontpage can not be hidden
2812 } else {
2813 if (is_role_switched($course->id)) {
2814 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2815 } else {
2816 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2817 // originally there was also test of parent category visibility,
2818 // BUT is was very slow in complex queries involving "my courses"
2819 // now it is also possible to simply hide all courses user is not enrolled in :-)
2820 if ($preventredirect) {
2821 throw new require_login_exception('Course is hidden');
2823 // We need to override the navigation URL as the course won't have
2824 // been added to the navigation and thus the navigation will mess up
2825 // when trying to find it.
2826 navigation_node::override_active_url(new moodle_url('/'));
2827 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2832 // is the user enrolled?
2833 if ($course->id == SITEID) {
2834 // everybody is enrolled on the frontpage
2836 } else {
2837 if (session_is_loggedinas()) {
2838 // Make sure the REAL person can access this course first
2839 $realuser = session_get_realuser();
2840 if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
2841 if ($preventredirect) {
2842 throw new require_login_exception('Invalid course login-as access');
2844 echo $OUTPUT->header();
2845 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2849 $access = false;
2851 if (is_role_switched($course->id)) {
2852 // ok, user had to be inside this course before the switch
2853 $access = true;
2855 } else if (is_viewing($coursecontext, $USER)) {
2856 // ok, no need to mess with enrol
2857 $access = true;
2859 } else {
2860 if (isset($USER->enrol['enrolled'][$course->id])) {
2861 if ($USER->enrol['enrolled'][$course->id] > time()) {
2862 $access = true;
2863 if (isset($USER->enrol['tempguest'][$course->id])) {
2864 unset($USER->enrol['tempguest'][$course->id]);
2865 remove_temp_course_roles($coursecontext);
2867 } else {
2868 //expired
2869 unset($USER->enrol['enrolled'][$course->id]);
2872 if (isset($USER->enrol['tempguest'][$course->id])) {
2873 if ($USER->enrol['tempguest'][$course->id] == 0) {
2874 $access = true;
2875 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
2876 $access = true;
2877 } else {
2878 //expired
2879 unset($USER->enrol['tempguest'][$course->id]);
2880 remove_temp_course_roles($coursecontext);
2884 if ($access) {
2885 // cache ok
2886 } else {
2887 $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
2888 if ($until !== false) {
2889 // active participants may always access, a timestamp in the future, 0 (always) or false.
2890 if ($until == 0) {
2891 $until = ENROL_MAX_TIMESTAMP;
2893 $USER->enrol['enrolled'][$course->id] = $until;
2894 $access = true;
2896 } else {
2897 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2898 $enrols = enrol_get_plugins(true);
2899 // first ask all enabled enrol instances in course if they want to auto enrol user
2900 foreach($instances as $instance) {
2901 if (!isset($enrols[$instance->enrol])) {
2902 continue;
2904 // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
2905 $until = $enrols[$instance->enrol]->try_autoenrol($instance);
2906 if ($until !== false) {
2907 if ($until == 0) {
2908 $until = ENROL_MAX_TIMESTAMP;
2910 $USER->enrol['enrolled'][$course->id] = $until;
2911 $access = true;
2912 break;
2915 // if not enrolled yet try to gain temporary guest access
2916 if (!$access) {
2917 foreach($instances as $instance) {
2918 if (!isset($enrols[$instance->enrol])) {
2919 continue;
2921 // Get a duration for the guest access, a timestamp in the future or false.
2922 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
2923 if ($until !== false and $until > time()) {
2924 $USER->enrol['tempguest'][$course->id] = $until;
2925 $access = true;
2926 break;
2934 if (!$access) {
2935 if ($preventredirect) {
2936 throw new require_login_exception('Not enrolled');
2938 if ($setwantsurltome) {
2939 $SESSION->wantsurl = $FULLME;
2941 redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
2945 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
2946 // conditional availability, etc
2947 if ($cm && !$cm->uservisible) {
2948 if ($preventredirect) {
2949 throw new require_login_exception('Activity is hidden');
2951 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
2954 // Finally access granted, update lastaccess times
2955 user_accesstime_log($course->id);
2960 * This function just makes sure a user is logged out.
2962 * @global object
2964 function require_logout() {
2965 global $USER;
2967 $params = $USER;
2969 if (isloggedin()) {
2970 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2972 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2973 foreach($authsequence as $authname) {
2974 $authplugin = get_auth_plugin($authname);
2975 $authplugin->prelogout_hook();
2979 events_trigger('user_logout', $params);
2980 session_get_instance()->terminate_current();
2981 unset($params);
2985 * Weaker version of require_login()
2987 * This is a weaker version of {@link require_login()} which only requires login
2988 * when called from within a course rather than the site page, unless
2989 * the forcelogin option is turned on.
2990 * @see require_login()
2992 * @global object
2993 * @param mixed $courseorid The course object or id in question
2994 * @param bool $autologinguest Allow autologin guests if that is wanted
2995 * @param object $cm Course activity module if known
2996 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2997 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2998 * in order to keep redirects working properly. MDL-14495
2999 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
3000 * @return void
3002 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
3003 global $CFG, $PAGE, $SITE;
3004 $issite = (is_object($courseorid) and $courseorid->id == SITEID)
3005 or (!is_object($courseorid) and $courseorid == SITEID);
3006 if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
3007 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
3008 // db queries so this is not really a performance concern, however it is obviously
3009 // better if you use get_fast_modinfo to get the cm before calling this.
3010 if (is_object($courseorid)) {
3011 $course = $courseorid;
3012 } else {
3013 $course = clone($SITE);
3015 $modinfo = get_fast_modinfo($course);
3016 $cm = $modinfo->get_cm($cm->id);
3018 if (!empty($CFG->forcelogin)) {
3019 // login required for both SITE and courses
3020 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3022 } else if ($issite && !empty($cm) and !$cm->uservisible) {
3023 // always login for hidden activities
3024 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3026 } else if ($issite) {
3027 //login for SITE not required
3028 if ($cm and empty($cm->visible)) {
3029 // hidden activities are not accessible without login
3030 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3031 } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
3032 // not-logged-in users do not have any group membership
3033 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3034 } else {
3035 // We still need to instatiate PAGE vars properly so that things
3036 // that rely on it like navigation function correctly.
3037 if (!empty($courseorid)) {
3038 if (is_object($courseorid)) {
3039 $course = $courseorid;
3040 } else {
3041 $course = clone($SITE);
3043 if ($cm) {
3044 if ($cm->course != $course->id) {
3045 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
3047 $PAGE->set_cm($cm, $course);
3048 $PAGE->set_pagelayout('incourse');
3049 } else {
3050 $PAGE->set_course($course);
3052 } else {
3053 // If $PAGE->course, and hence $PAGE->context, have not already been set
3054 // up properly, set them up now.
3055 $PAGE->set_course($PAGE->course);
3057 //TODO: verify conditional activities here
3058 user_accesstime_log(SITEID);
3059 return;
3062 } else {
3063 // course login always required
3064 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3069 * Require key login. Function terminates with error if key not found or incorrect.
3071 * @global object
3072 * @global object
3073 * @global object
3074 * @global object
3075 * @uses NO_MOODLE_COOKIES
3076 * @uses PARAM_ALPHANUM
3077 * @param string $script unique script identifier
3078 * @param int $instance optional instance id
3079 * @return int Instance ID
3081 function require_user_key_login($script, $instance=null) {
3082 global $USER, $SESSION, $CFG, $DB;
3084 if (!NO_MOODLE_COOKIES) {
3085 print_error('sessioncookiesdisable');
3088 /// extra safety
3089 @session_write_close();
3091 $keyvalue = required_param('key', PARAM_ALPHANUM);
3093 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
3094 print_error('invalidkey');
3097 if (!empty($key->validuntil) and $key->validuntil < time()) {
3098 print_error('expiredkey');
3101 if ($key->iprestriction) {
3102 $remoteaddr = getremoteaddr(null);
3103 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
3104 print_error('ipmismatch');
3108 if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
3109 print_error('invaliduserid');
3112 /// emulate normal session
3113 enrol_check_plugins($user);
3114 session_set_user($user);
3116 /// note we are not using normal login
3117 if (!defined('USER_KEY_LOGIN')) {
3118 define('USER_KEY_LOGIN', true);
3121 /// return instance id - it might be empty
3122 return $key->instance;
3126 * Creates a new private user access key.
3128 * @global object
3129 * @param string $script unique target identifier
3130 * @param int $userid
3131 * @param int $instance optional instance id
3132 * @param string $iprestriction optional ip restricted access
3133 * @param timestamp $validuntil key valid only until given data
3134 * @return string access key value
3136 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3137 global $DB;
3139 $key = new stdClass();
3140 $key->script = $script;
3141 $key->userid = $userid;
3142 $key->instance = $instance;
3143 $key->iprestriction = $iprestriction;
3144 $key->validuntil = $validuntil;
3145 $key->timecreated = time();
3147 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
3148 while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
3149 // must be unique
3150 $key->value = md5($userid.'_'.time().random_string(40));
3152 $DB->insert_record('user_private_key', $key);
3153 return $key->value;
3157 * Delete the user's new private user access keys for a particular script.
3159 * @global object
3160 * @param string $script unique target identifier
3161 * @param int $userid
3162 * @return void
3164 function delete_user_key($script,$userid) {
3165 global $DB;
3166 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
3170 * Gets a private user access key (and creates one if one doesn't exist).
3172 * @global object
3173 * @param string $script unique target identifier
3174 * @param int $userid
3175 * @param int $instance optional instance id
3176 * @param string $iprestriction optional ip restricted access
3177 * @param timestamp $validuntil key valid only until given data
3178 * @return string access key value
3180 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3181 global $DB;
3183 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
3184 'instance'=>$instance, 'iprestriction'=>$iprestriction,
3185 'validuntil'=>$validuntil))) {
3186 return $key->value;
3187 } else {
3188 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
3194 * Modify the user table by setting the currently logged in user's
3195 * last login to now.
3197 * @global object
3198 * @global object
3199 * @return bool Always returns true
3201 function update_user_login_times() {
3202 global $USER, $DB;
3204 if (isguestuser()) {
3205 // Do not update guest access times/ips for performance.
3206 return true;
3209 $now = time();
3211 $user = new stdClass();
3212 $user->id = $USER->id;
3214 // Make sure all users that logged in have some firstaccess.
3215 if ($USER->firstaccess == 0) {
3216 $USER->firstaccess = $user->firstaccess = $now;
3219 // Store the previous current as lastlogin.
3220 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
3222 $USER->currentlogin = $user->currentlogin = $now;
3224 // Function user_accesstime_log() may not update immediately, better do it here.
3225 $USER->lastaccess = $user->lastaccess = $now;
3226 $USER->lastip = $user->lastip = getremoteaddr();
3228 $DB->update_record('user', $user);
3229 return true;
3233 * Determines if a user has completed setting up their account.
3235 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
3236 * @return bool
3238 function user_not_fully_set_up($user) {
3239 if (isguestuser($user)) {
3240 return false;
3242 return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
3246 * Check whether the user has exceeded the bounce threshold
3248 * @global object
3249 * @global object
3250 * @param user $user A {@link $USER} object
3251 * @return bool true=>User has exceeded bounce threshold
3253 function over_bounce_threshold($user) {
3254 global $CFG, $DB;
3256 if (empty($CFG->handlebounces)) {
3257 return false;
3260 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3261 return false;
3264 // set sensible defaults
3265 if (empty($CFG->minbounces)) {
3266 $CFG->minbounces = 10;
3268 if (empty($CFG->bounceratio)) {
3269 $CFG->bounceratio = .20;
3271 $bouncecount = 0;
3272 $sendcount = 0;
3273 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3274 $bouncecount = $bounce->value;
3276 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3277 $sendcount = $send->value;
3279 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
3283 * Used to increment or reset email sent count
3285 * @global object
3286 * @param user $user object containing an id
3287 * @param bool $reset will reset the count to 0
3288 * @return void
3290 function set_send_count($user,$reset=false) {
3291 global $DB;
3293 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3294 return;
3297 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3298 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3299 $DB->update_record('user_preferences', $pref);
3301 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3302 // make a new one
3303 $pref = new stdClass();
3304 $pref->name = 'email_send_count';
3305 $pref->value = 1;
3306 $pref->userid = $user->id;
3307 $DB->insert_record('user_preferences', $pref, false);
3312 * Increment or reset user's email bounce count
3314 * @global object
3315 * @param user $user object containing an id
3316 * @param bool $reset will reset the count to 0
3318 function set_bounce_count($user,$reset=false) {
3319 global $DB;
3321 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3322 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3323 $DB->update_record('user_preferences', $pref);
3325 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3326 // make a new one
3327 $pref = new stdClass();
3328 $pref->name = 'email_bounce_count';
3329 $pref->value = 1;
3330 $pref->userid = $user->id;
3331 $DB->insert_record('user_preferences', $pref, false);
3336 * Keeps track of login attempts
3338 * @global object
3340 function update_login_count() {
3341 global $SESSION;
3343 $max_logins = 10;
3345 if (empty($SESSION->logincount)) {
3346 $SESSION->logincount = 1;
3347 } else {
3348 $SESSION->logincount++;
3351 if ($SESSION->logincount > $max_logins) {
3352 unset($SESSION->wantsurl);
3353 print_error('errortoomanylogins');
3358 * Resets login attempts
3360 * @global object
3362 function reset_login_count() {
3363 global $SESSION;
3365 $SESSION->logincount = 0;
3369 * Determines if the currently logged in user is in editing mode.
3370 * Note: originally this function had $userid parameter - it was not usable anyway
3372 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3373 * @todo Deprecated function remove when ready
3375 * @global object
3376 * @uses DEBUG_DEVELOPER
3377 * @return bool
3379 function isediting() {
3380 global $PAGE;
3381 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
3382 return $PAGE->user_is_editing();
3386 * Determines if the logged in user is currently moving an activity
3388 * @global object
3389 * @param int $courseid The id of the course being tested
3390 * @return bool
3392 function ismoving($courseid) {
3393 global $USER;
3395 if (!empty($USER->activitycopy)) {
3396 return ($USER->activitycopycourse == $courseid);
3398 return false;
3402 * Returns a persons full name
3404 * Given an object containing firstname and lastname
3405 * values, this function returns a string with the
3406 * full name of the person.
3407 * The result may depend on system settings
3408 * or language. 'override' will force both names
3409 * to be used even if system settings specify one.
3411 * @global object
3412 * @global object
3413 * @param object $user A {@link $USER} object to get full name of
3414 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3415 * @return string
3417 function fullname($user, $override=false) {
3418 global $CFG, $SESSION;
3420 if (!isset($user->firstname) and !isset($user->lastname)) {
3421 return '';
3424 if (!$override) {
3425 if (!empty($CFG->forcefirstname)) {
3426 $user->firstname = $CFG->forcefirstname;
3428 if (!empty($CFG->forcelastname)) {
3429 $user->lastname = $CFG->forcelastname;
3433 if (!empty($SESSION->fullnamedisplay)) {
3434 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3437 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
3438 return $user->firstname .' '. $user->lastname;
3440 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
3441 return $user->lastname .' '. $user->firstname;
3443 } else if ($CFG->fullnamedisplay == 'firstname') {
3444 if ($override) {
3445 return get_string('fullnamedisplay', '', $user);
3446 } else {
3447 return $user->firstname;
3451 return get_string('fullnamedisplay', '', $user);
3455 * Checks if current user is shown any extra fields when listing users.
3456 * @param object $context Context
3457 * @param array $already Array of fields that we're going to show anyway
3458 * so don't bother listing them
3459 * @return array Array of field names from user table, not including anything
3460 * listed in $already
3462 function get_extra_user_fields($context, $already = array()) {
3463 global $CFG;
3465 // Only users with permission get the extra fields
3466 if (!has_capability('moodle/site:viewuseridentity', $context)) {
3467 return array();
3470 // Split showuseridentity on comma
3471 if (empty($CFG->showuseridentity)) {
3472 // Explode gives wrong result with empty string
3473 $extra = array();
3474 } else {
3475 $extra = explode(',', $CFG->showuseridentity);
3477 $renumber = false;
3478 foreach ($extra as $key => $field) {
3479 if (in_array($field, $already)) {
3480 unset($extra[$key]);
3481 $renumber = true;
3484 if ($renumber) {
3485 // For consistency, if entries are removed from array, renumber it
3486 // so they are numbered as you would expect
3487 $extra = array_merge($extra);
3489 return $extra;
3493 * If the current user is to be shown extra user fields when listing or
3494 * selecting users, returns a string suitable for including in an SQL select
3495 * clause to retrieve those fields.
3496 * @param object $context Context
3497 * @param string $alias Alias of user table, e.g. 'u' (default none)
3498 * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none)
3499 * @param array $already Array of fields that we're going to include anyway
3500 * so don't list them (default none)
3501 * @return string Partial SQL select clause, beginning with comma, for example
3502 * ',u.idnumber,u.department' unless it is blank
3504 function get_extra_user_fields_sql($context, $alias='', $prefix='',
3505 $already = array()) {
3506 $fields = get_extra_user_fields($context, $already);
3507 $result = '';
3508 // Add punctuation for alias
3509 if ($alias !== '') {
3510 $alias .= '.';
3512 foreach ($fields as $field) {
3513 $result .= ', ' . $alias . $field;
3514 if ($prefix) {
3515 $result .= ' AS ' . $prefix . $field;
3518 return $result;
3522 * Returns the display name of a field in the user table. Works for most fields
3523 * that are commonly displayed to users.
3524 * @param string $field Field name, e.g. 'phone1'
3525 * @return string Text description taken from language file, e.g. 'Phone number'
3527 function get_user_field_name($field) {
3528 // Some fields have language strings which are not the same as field name
3529 switch ($field) {
3530 case 'phone1' : return get_string('phone');
3532 // Otherwise just use the same lang string
3533 return get_string($field);
3537 * Returns whether a given authentication plugin exists.
3539 * @global object
3540 * @param string $auth Form of authentication to check for. Defaults to the
3541 * global setting in {@link $CFG}.
3542 * @return boolean Whether the plugin is available.
3544 function exists_auth_plugin($auth) {
3545 global $CFG;
3547 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3548 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3550 return false;
3554 * Checks if a given plugin is in the list of enabled authentication plugins.
3556 * @param string $auth Authentication plugin.
3557 * @return boolean Whether the plugin is enabled.
3559 function is_enabled_auth($auth) {
3560 if (empty($auth)) {
3561 return false;
3564 $enabled = get_enabled_auth_plugins();
3566 return in_array($auth, $enabled);
3570 * Returns an authentication plugin instance.
3572 * @global object
3573 * @param string $auth name of authentication plugin
3574 * @return auth_plugin_base An instance of the required authentication plugin.
3576 function get_auth_plugin($auth) {
3577 global $CFG;
3579 // check the plugin exists first
3580 if (! exists_auth_plugin($auth)) {
3581 print_error('authpluginnotfound', 'debug', '', $auth);
3584 // return auth plugin instance
3585 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3586 $class = "auth_plugin_$auth";
3587 return new $class;
3591 * Returns array of active auth plugins.
3593 * @param bool $fix fix $CFG->auth if needed
3594 * @return array
3596 function get_enabled_auth_plugins($fix=false) {
3597 global $CFG;
3599 $default = array('manual', 'nologin');
3601 if (empty($CFG->auth)) {
3602 $auths = array();
3603 } else {
3604 $auths = explode(',', $CFG->auth);
3607 if ($fix) {
3608 $auths = array_unique($auths);
3609 foreach($auths as $k=>$authname) {
3610 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3611 unset($auths[$k]);
3614 $newconfig = implode(',', $auths);
3615 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
3616 set_config('auth', $newconfig);
3620 return (array_merge($default, $auths));
3624 * Returns true if an internal authentication method is being used.
3625 * if method not specified then, global default is assumed
3627 * @param string $auth Form of authentication required
3628 * @return bool
3630 function is_internal_auth($auth) {
3631 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3632 return $authplugin->is_internal();
3636 * Returns true if the user is a 'restored' one
3638 * Used in the login process to inform the user
3639 * and allow him/her to reset the password
3641 * @uses $CFG
3642 * @uses $DB
3643 * @param string $username username to be checked
3644 * @return bool
3646 function is_restored_user($username) {
3647 global $CFG, $DB;
3649 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
3653 * Returns an array of user fields
3655 * @return array User field/column names
3657 function get_user_fieldnames() {
3658 global $DB;
3660 $fieldarray = $DB->get_columns('user');
3661 unset($fieldarray['id']);
3662 $fieldarray = array_keys($fieldarray);
3664 return $fieldarray;
3668 * Creates a bare-bones user record
3670 * @todo Outline auth types and provide code example
3672 * @param string $username New user's username to add to record
3673 * @param string $password New user's password to add to record
3674 * @param string $auth Form of authentication required
3675 * @return stdClass A complete user object
3677 function create_user_record($username, $password, $auth = 'manual') {
3678 global $CFG, $DB;
3680 //just in case check text case
3681 $username = trim(moodle_strtolower($username));
3683 $authplugin = get_auth_plugin($auth);
3685 $newuser = new stdClass();
3687 if ($newinfo = $authplugin->get_userinfo($username)) {
3688 $newinfo = truncate_userinfo($newinfo);
3689 foreach ($newinfo as $key => $value){
3690 $newuser->$key = $value;
3694 if (!empty($newuser->email)) {
3695 if (email_is_not_allowed($newuser->email)) {
3696 unset($newuser->email);
3700 if (!isset($newuser->city)) {
3701 $newuser->city = '';
3704 $newuser->auth = $auth;
3705 $newuser->username = $username;
3707 // fix for MDL-8480
3708 // user CFG lang for user if $newuser->lang is empty
3709 // or $user->lang is not an installed language
3710 if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
3711 $newuser->lang = $CFG->lang;
3713 $newuser->confirmed = 1;
3714 $newuser->lastip = getremoteaddr();
3715 $newuser->timecreated = time();
3716 $newuser->timemodified = $newuser->timecreated;
3717 $newuser->mnethostid = $CFG->mnet_localhost_id;
3719 $newuser->id = $DB->insert_record('user', $newuser);
3720 $user = get_complete_user_data('id', $newuser->id);
3721 if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
3722 set_user_preference('auth_forcepasswordchange', 1, $user);
3724 update_internal_user_password($user, $password);
3726 // fetch full user record for the event, the complete user data contains too much info
3727 // and we want to be consistent with other places that trigger this event
3728 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
3730 return $user;
3734 * Will update a local user record from an external source.
3735 * (MNET users can not be updated using this method!)
3737 * @param string $username user's username to update the record
3738 * @return stdClass A complete user object
3740 function update_user_record($username) {
3741 global $DB, $CFG;
3743 $username = trim(moodle_strtolower($username)); /// just in case check text case
3745 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
3746 $newuser = array();
3747 $userauth = get_auth_plugin($oldinfo->auth);
3749 if ($newinfo = $userauth->get_userinfo($username)) {
3750 $newinfo = truncate_userinfo($newinfo);
3751 foreach ($newinfo as $key => $value){
3752 $key = strtolower($key);
3753 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3754 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3755 // unknown or must not be changed
3756 continue;
3758 $confval = $userauth->config->{'field_updatelocal_' . $key};
3759 $lockval = $userauth->config->{'field_lock_' . $key};
3760 if (empty($confval) || empty($lockval)) {
3761 continue;
3763 if ($confval === 'onlogin') {
3764 // MDL-4207 Don't overwrite modified user profile values with
3765 // empty LDAP values when 'unlocked if empty' is set. The purpose
3766 // of the setting 'unlocked if empty' is to allow the user to fill
3767 // in a value for the selected field _if LDAP is giving
3768 // nothing_ for this field. Thus it makes sense to let this value
3769 // stand in until LDAP is giving a value for this field.
3770 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3771 if ((string)$oldinfo->$key !== (string)$value) {
3772 $newuser[$key] = (string)$value;
3777 if ($newuser) {
3778 $newuser['id'] = $oldinfo->id;
3779 $newuser['timemodified'] = time();
3780 $DB->update_record('user', $newuser);
3781 // fetch full user record for the event, the complete user data contains too much info
3782 // and we want to be consistent with other places that trigger this event
3783 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
3787 return get_complete_user_data('id', $oldinfo->id);
3791 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3792 * which may have large fields
3794 * @todo Add vartype handling to ensure $info is an array
3796 * @param array $info Array of user properties to truncate if needed
3797 * @return array The now truncated information that was passed in
3799 function truncate_userinfo($info) {
3800 // define the limits
3801 $limit = array(
3802 'username' => 100,
3803 'idnumber' => 255,
3804 'firstname' => 100,
3805 'lastname' => 100,
3806 'email' => 100,
3807 'icq' => 15,
3808 'phone1' => 20,
3809 'phone2' => 20,
3810 'institution' => 40,
3811 'department' => 30,
3812 'address' => 70,
3813 'city' => 120,
3814 'country' => 2,
3815 'url' => 255,
3818 $textlib = textlib_get_instance();
3819 // apply where needed
3820 foreach (array_keys($info) as $key) {
3821 if (!empty($limit[$key])) {
3822 $info[$key] = trim($textlib->substr($info[$key],0, $limit[$key]));
3826 return $info;
3830 * Marks user deleted in internal user database and notifies the auth plugin.
3831 * Also unenrols user from all roles and does other cleanup.
3833 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3835 * @param stdClass $user full user object before delete
3836 * @return boolean success
3837 * @throws coding_exception if invalid $user parameter detected
3839 function delete_user(stdClass $user) {
3840 global $CFG, $DB;
3841 require_once($CFG->libdir.'/grouplib.php');
3842 require_once($CFG->libdir.'/gradelib.php');
3843 require_once($CFG->dirroot.'/message/lib.php');
3844 require_once($CFG->dirroot.'/tag/lib.php');
3846 // Make sure nobody sends bogus record type as parameter.
3847 if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
3848 throw new coding_exception('Invalid $user parameter in delete_user() detected');
3851 // Better not trust the parameter and fetch the latest info,
3852 // this will be very expensive anyway.
3853 if (!$user = $DB->get_record('user', array('id'=>$user->id))) {
3854 debugging('Attempt to delete unknown user account.');
3855 return false;
3858 // There must be always exactly one guest record,
3859 // originally the guest account was identified by username only,
3860 // now we use $CFG->siteguest for performance reasons.
3861 if ($user->username === 'guest' or isguestuser($user)) {
3862 debugging('Guest user account can not be deleted.');
3863 return false;
3866 // Admin can be theoretically from different auth plugin,
3867 // but we want to prevent deletion of internal accoutns only,
3868 // if anything goes wrong ppl may force somebody to be admin via
3869 // config.php setting $CFG->siteadmins.
3870 if ($user->auth === 'manual' and is_siteadmin($user)) {
3871 debugging('Local administrator accounts can not be deleted.');
3872 return false;
3875 // delete all grades - backup is kept in grade_grades_history table
3876 grade_user_delete($user->id);
3878 //move unread messages from this user to read
3879 message_move_userfrom_unread2read($user->id);
3881 // TODO: remove from cohorts using standard API here
3883 // remove user tags
3884 tag_set('user', $user->id, array());
3886 // unconditionally unenrol from all courses
3887 enrol_user_delete($user);
3889 // unenrol from all roles in all contexts
3890 role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
3892 //now do a brute force cleanup
3894 // remove from all cohorts
3895 $DB->delete_records('cohort_members', array('userid'=>$user->id));
3897 // remove from all groups
3898 $DB->delete_records('groups_members', array('userid'=>$user->id));
3900 // brute force unenrol from all courses
3901 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
3903 // purge user preferences
3904 $DB->delete_records('user_preferences', array('userid'=>$user->id));
3906 // purge user extra profile info
3907 $DB->delete_records('user_info_data', array('userid'=>$user->id));
3909 // last course access not necessary either
3910 $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
3912 // remove all user tokens
3913 $DB->delete_records('external_tokens', array('userid'=>$user->id));
3915 // unauthorise the user for all services
3916 $DB->delete_records('external_services_users', array('userid'=>$user->id));
3918 // force logout - may fail if file based sessions used, sorry
3919 session_kill_user($user->id);
3921 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
3922 delete_context(CONTEXT_USER, $user->id);
3924 // workaround for bulk deletes of users with the same email address
3925 $delname = "$user->email.".time();
3926 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
3927 $delname++;
3930 // mark internal user record as "deleted"
3931 $updateuser = new stdClass();
3932 $updateuser->id = $user->id;
3933 $updateuser->deleted = 1;
3934 $updateuser->username = $delname; // Remember it just in case
3935 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
3936 $updateuser->idnumber = ''; // Clear this field to free it up
3937 $updateuser->timemodified = time();
3939 $DB->update_record('user', $updateuser);
3940 // Add this action to log
3941 add_to_log(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
3944 // We will update the user's timemodified, as it will be passed to the user_deleted event, which
3945 // should know about this updated property persisted to the user's table.
3946 $user->timemodified = $updateuser->timemodified;
3948 // notify auth plugin - do not block the delete even when plugin fails
3949 $authplugin = get_auth_plugin($user->auth);
3950 $authplugin->user_delete($user);
3952 // any plugin that needs to cleanup should register this event
3953 events_trigger('user_deleted', $user);
3955 return true;
3959 * Retrieve the guest user object
3961 * @global object
3962 * @global object
3963 * @return user A {@link $USER} object
3965 function guest_user() {
3966 global $CFG, $DB;
3968 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
3969 $newuser->confirmed = 1;
3970 $newuser->lang = $CFG->lang;
3971 $newuser->lastip = getremoteaddr();
3974 return $newuser;
3978 * Authenticates a user against the chosen authentication mechanism
3980 * Given a username and password, this function looks them
3981 * up using the currently selected authentication mechanism,
3982 * and if the authentication is successful, it returns a
3983 * valid $user object from the 'user' table.
3985 * Uses auth_ functions from the currently active auth module
3987 * After authenticate_user_login() returns success, you will need to
3988 * log that the user has logged in, and call complete_user_login() to set
3989 * the session up.
3991 * Note: this function works only with non-mnet accounts!
3993 * @param string $username User's username
3994 * @param string $password User's password
3995 * @return user|flase A {@link $USER} object or false if error
3997 function authenticate_user_login($username, $password) {
3998 global $CFG, $DB;
4000 $authsenabled = get_enabled_auth_plugins();
4002 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
4003 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
4004 if (!empty($user->suspended)) {
4005 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4006 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4007 return false;
4009 if ($auth=='nologin' or !is_enabled_auth($auth)) {
4010 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4011 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4012 return false;
4014 $auths = array($auth);
4016 } else {
4017 // check if there's a deleted record (cheaply)
4018 if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
4019 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4020 return false;
4023 // User does not exist
4024 $auths = $authsenabled;
4025 $user = new stdClass();
4026 $user->id = 0;
4029 foreach ($auths as $auth) {
4030 $authplugin = get_auth_plugin($auth);
4032 // on auth fail fall through to the next plugin
4033 if (!$authplugin->user_login($username, $password)) {
4034 continue;
4037 // successful authentication
4038 if ($user->id) { // User already exists in database
4039 if (empty($user->auth)) { // For some reason auth isn't set yet
4040 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
4041 $user->auth = $auth;
4044 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
4046 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
4047 $user = update_user_record($username);
4049 } else {
4050 // if user not found and user creation is not disabled, create it
4051 if (empty($CFG->authpreventaccountcreation)) {
4052 $user = create_user_record($username, $password, $auth);
4053 } else {
4054 continue;
4058 $authplugin->sync_roles($user);
4060 foreach ($authsenabled as $hau) {
4061 $hauth = get_auth_plugin($hau);
4062 $hauth->user_authenticated_hook($user, $username, $password);
4065 if (empty($user->id)) {
4066 return false;
4069 if (!empty($user->suspended)) {
4070 // just in case some auth plugin suspended account
4071 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4072 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4073 return false;
4076 return $user;
4079 // failed if all the plugins have failed
4080 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4081 if (debugging('', DEBUG_ALL)) {
4082 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4084 return false;
4088 * Call to complete the user login process after authenticate_user_login()
4089 * has succeeded. It will setup the $USER variable and other required bits
4090 * and pieces.
4092 * NOTE:
4093 * - It will NOT log anything -- up to the caller to decide what to log.
4094 * - this function does not set any cookies any more!
4096 * @param object $user
4097 * @return object A {@link $USER} object - BC only, do not use
4099 function complete_user_login($user) {
4100 global $CFG, $USER;
4102 // regenerate session id and delete old session,
4103 // this helps prevent session fixation attacks from the same domain
4104 session_regenerate_id(true);
4106 // let enrol plugins deal with new enrolments if necessary
4107 enrol_check_plugins($user);
4109 // check enrolments, load caps and setup $USER object
4110 session_set_user($user);
4112 // reload preferences from DB
4113 unset($USER->preference);
4114 check_user_preferences_loaded($USER);
4116 // update login times
4117 update_user_login_times();
4119 // extra session prefs init
4120 set_login_session_preferences();
4122 if (isguestuser()) {
4123 // no need to continue when user is THE guest
4124 return $USER;
4127 /// Select password change url
4128 $userauth = get_auth_plugin($USER->auth);
4130 /// check whether the user should be changing password
4131 if (get_user_preferences('auth_forcepasswordchange', false)){
4132 if ($userauth->can_change_password()) {
4133 if ($changeurl = $userauth->change_password_url()) {
4134 redirect($changeurl);
4135 } else {
4136 redirect($CFG->httpswwwroot.'/login/change_password.php');
4138 } else {
4139 print_error('nopasswordchangeforced', 'auth');
4142 return $USER;
4146 * Compare password against hash stored in internal user table.
4147 * If necessary it also updates the stored hash to new format.
4149 * @param stdClass $user (password property may be updated)
4150 * @param string $password plain text password
4151 * @return bool is password valid?
4153 function validate_internal_user_password($user, $password) {
4154 global $CFG;
4156 if (!isset($CFG->passwordsaltmain)) {
4157 $CFG->passwordsaltmain = '';
4160 $validated = false;
4162 if ($user->password === 'not cached') {
4163 // internal password is not used at all, it can not validate
4165 } else if ($user->password === md5($password.$CFG->passwordsaltmain)
4166 or $user->password === md5($password)
4167 or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
4168 or $user->password === md5(addslashes($password))) {
4169 // note: we are intentionally using the addslashes() here because we
4170 // need to accept old password hashes of passwords with magic quotes
4171 $validated = true;
4173 } else {
4174 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
4175 $alt = 'passwordsaltalt'.$i;
4176 if (!empty($CFG->$alt)) {
4177 if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
4178 $validated = true;
4179 break;
4185 if ($validated) {
4186 // force update of password hash using latest main password salt and encoding if needed
4187 update_internal_user_password($user, $password);
4190 return $validated;
4194 * Calculate hashed value from password using current hash mechanism.
4196 * @param string $password
4197 * @return string password hash
4199 function hash_internal_user_password($password) {
4200 global $CFG;
4202 if (isset($CFG->passwordsaltmain)) {
4203 return md5($password.$CFG->passwordsaltmain);
4204 } else {
4205 return md5($password);
4210 * Update password hash in user object.
4212 * @param stdClass $user (password property may be updated)
4213 * @param string $password plain text password
4214 * @return bool always returns true
4216 function update_internal_user_password($user, $password) {
4217 global $DB;
4219 $authplugin = get_auth_plugin($user->auth);
4220 if ($authplugin->prevent_local_passwords()) {
4221 $hashedpassword = 'not cached';
4222 } else {
4223 $hashedpassword = hash_internal_user_password($password);
4226 if ($user->password !== $hashedpassword) {
4227 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
4228 $user->password = $hashedpassword;
4231 return true;
4235 * Get a complete user record, which includes all the info
4236 * in the user record.
4238 * Intended for setting as $USER session variable
4240 * @param string $field The user field to be checked for a given value.
4241 * @param string $value The value to match for $field.
4242 * @param int $mnethostid
4243 * @return mixed False, or A {@link $USER} object.
4245 function get_complete_user_data($field, $value, $mnethostid = null) {
4246 global $CFG, $DB;
4248 if (!$field || !$value) {
4249 return false;
4252 /// Build the WHERE clause for an SQL query
4253 $params = array('fieldval'=>$value);
4254 $constraints = "$field = :fieldval AND deleted <> 1";
4256 // If we are loading user data based on anything other than id,
4257 // we must also restrict our search based on mnet host.
4258 if ($field != 'id') {
4259 if (empty($mnethostid)) {
4260 // if empty, we restrict to local users
4261 $mnethostid = $CFG->mnet_localhost_id;
4264 if (!empty($mnethostid)) {
4265 $params['mnethostid'] = $mnethostid;
4266 $constraints .= " AND mnethostid = :mnethostid";
4269 /// Get all the basic user data
4271 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
4272 return false;
4275 /// Get various settings and preferences
4277 // preload preference cache
4278 check_user_preferences_loaded($user);
4280 // load course enrolment related stuff
4281 $user->lastcourseaccess = array(); // during last session
4282 $user->currentcourseaccess = array(); // during current session
4283 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
4284 foreach ($lastaccesses as $lastaccess) {
4285 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
4289 $sql = "SELECT g.id, g.courseid
4290 FROM {groups} g, {groups_members} gm
4291 WHERE gm.groupid=g.id AND gm.userid=?";
4293 // this is a special hack to speedup calendar display
4294 $user->groupmember = array();
4295 if (!isguestuser($user)) {
4296 if ($groups = $DB->get_records_sql($sql, array($user->id))) {
4297 foreach ($groups as $group) {
4298 if (!array_key_exists($group->courseid, $user->groupmember)) {
4299 $user->groupmember[$group->courseid] = array();
4301 $user->groupmember[$group->courseid][$group->id] = $group->id;
4306 /// Add the custom profile fields to the user record
4307 $user->profile = array();
4308 if (!isguestuser($user)) {
4309 require_once($CFG->dirroot.'/user/profile/lib.php');
4310 profile_load_custom_fields($user);
4313 /// Rewrite some variables if necessary
4314 if (!empty($user->description)) {
4315 $user->description = true; // No need to cart all of it around
4317 if (isguestuser($user)) {
4318 $user->lang = $CFG->lang; // Guest language always same as site
4319 $user->firstname = get_string('guestuser'); // Name always in current language
4320 $user->lastname = ' ';
4323 return $user;
4327 * Validate a password against the configured password policy
4329 * @global object
4330 * @param string $password the password to be checked against the password policy
4331 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
4332 * @return bool true if the password is valid according to the policy. false otherwise.
4334 function check_password_policy($password, &$errmsg) {
4335 global $CFG;
4337 if (empty($CFG->passwordpolicy)) {
4338 return true;
4341 $textlib = textlib_get_instance();
4342 $errmsg = '';
4343 if ($textlib->strlen($password) < $CFG->minpasswordlength) {
4344 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
4347 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
4348 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
4351 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
4352 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
4355 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
4356 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
4359 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
4360 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
4362 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
4363 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
4366 if ($errmsg == '') {
4367 return true;
4368 } else {
4369 return false;
4375 * When logging in, this function is run to set certain preferences
4376 * for the current SESSION
4378 * @global object
4379 * @global object
4381 function set_login_session_preferences() {
4382 global $SESSION, $CFG;
4384 $SESSION->justloggedin = true;
4386 unset($SESSION->lang);
4391 * Delete a course, including all related data from the database,
4392 * and any associated files.
4394 * @global object
4395 * @global object
4396 * @param mixed $courseorid The id of the course or course object to delete.
4397 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4398 * @return bool true if all the removals succeeded. false if there were any failures. If this
4399 * method returns false, some of the removals will probably have succeeded, and others
4400 * failed, but you have no way of knowing which.
4402 function delete_course($courseorid, $showfeedback = true) {
4403 global $DB;
4405 if (is_object($courseorid)) {
4406 $courseid = $courseorid->id;
4407 $course = $courseorid;
4408 } else {
4409 $courseid = $courseorid;
4410 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
4411 return false;
4414 $context = get_context_instance(CONTEXT_COURSE, $courseid);
4416 // frontpage course can not be deleted!!
4417 if ($courseid == SITEID) {
4418 return false;
4421 // make the course completely empty
4422 remove_course_contents($courseid, $showfeedback);
4424 // delete the course and related context instance
4425 delete_context(CONTEXT_COURSE, $courseid);
4427 // We will update the course's timemodified, as it will be passed to the course_deleted event,
4428 // which should know about this updated property, as this event is meant to pass the full course record
4429 $course->timemodified = time();
4431 $DB->delete_records("course", array("id"=>$courseid));
4433 //trigger events
4434 $course->context = $context; // you can not fetch context in the event because it was already deleted
4435 events_trigger('course_deleted', $course);
4437 return true;
4441 * Clear a course out completely, deleting all content
4442 * but don't delete the course itself.
4443 * This function does not verify any permissions.
4445 * Please note this function also deletes all user enrolments,
4446 * enrolment instances and role assignments by default.
4448 * $options:
4449 * - 'keep_roles_and_enrolments' - false by default
4450 * - 'keep_groups_and_groupings' - false by default
4452 * @param int $courseid The id of the course that is being deleted
4453 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4454 * @param array $options extra options
4455 * @return bool true if all the removals succeeded. false if there were any failures. If this
4456 * method returns false, some of the removals will probably have succeeded, and others
4457 * failed, but you have no way of knowing which.
4459 function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
4460 global $CFG, $DB, $OUTPUT;
4461 require_once($CFG->libdir.'/completionlib.php');
4462 require_once($CFG->libdir.'/questionlib.php');
4463 require_once($CFG->libdir.'/gradelib.php');
4464 require_once($CFG->dirroot.'/group/lib.php');
4465 require_once($CFG->dirroot.'/tag/coursetagslib.php');
4466 require_once($CFG->dirroot.'/comment/lib.php');
4467 require_once($CFG->dirroot.'/rating/lib.php');
4469 // NOTE: these concatenated strings are suboptimal, but it is just extra info...
4470 $strdeleted = get_string('deleted').' - ';
4472 // Some crazy wishlist of stuff we should skip during purging of course content
4473 $options = (array)$options;
4475 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
4476 $coursecontext = context_course::instance($courseid);
4477 $fs = get_file_storage();
4479 // Delete course completion information, this has to be done before grades and enrols
4480 $cc = new completion_info($course);
4481 $cc->clear_criteria();
4482 if ($showfeedback) {
4483 echo $OUTPUT->notification($strdeleted.get_string('completion', 'completion'), 'notifysuccess');
4486 // Remove all data from gradebook - this needs to be done before course modules
4487 // because while deleting this information, the system may need to reference
4488 // the course modules that own the grades.
4489 remove_course_grades($courseid, $showfeedback);
4490 remove_grade_letters($coursecontext, $showfeedback);
4492 // Delete course blocks in any all child contexts,
4493 // they may depend on modules so delete them first
4494 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4495 foreach ($childcontexts as $childcontext) {
4496 blocks_delete_all_for_context($childcontext->id);
4498 unset($childcontexts);
4499 blocks_delete_all_for_context($coursecontext->id);
4500 if ($showfeedback) {
4501 echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
4504 // Delete every instance of every module,
4505 // this has to be done before deleting of course level stuff
4506 $locations = get_plugin_list('mod');
4507 foreach ($locations as $modname=>$moddir) {
4508 if ($modname === 'NEWMODULE') {
4509 continue;
4511 if ($module = $DB->get_record('modules', array('name'=>$modname))) {
4512 include_once("$moddir/lib.php"); // Shows php warning only if plugin defective
4513 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4514 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4516 if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
4517 foreach ($instances as $instance) {
4518 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
4519 /// Delete activity context questions and question categories
4520 question_delete_activity($cm, $showfeedback);
4522 if (function_exists($moddelete)) {
4523 // This purges all module data in related tables, extra user prefs, settings, etc.
4524 $moddelete($instance->id);
4525 } else {
4526 // NOTE: we should not allow installation of modules with missing delete support!
4527 debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
4528 $DB->delete_records($modname, array('id'=>$instance->id));
4531 if ($cm) {
4532 // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition
4533 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4534 $DB->delete_records('course_modules', array('id'=>$cm->id));
4538 if (function_exists($moddeletecourse)) {
4539 // Execute ptional course cleanup callback
4540 $moddeletecourse($course, $showfeedback);
4542 if ($instances and $showfeedback) {
4543 echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
4545 } else {
4546 // Ooops, this module is not properly installed, force-delete it in the next block
4550 // We have tried to delete everything the nice way - now let's force-delete any remaining module data
4552 // Remove all data from availability and completion tables that is associated
4553 // with course-modules belonging to this course. Note this is done even if the
4554 // features are not enabled now, in case they were enabled previously.
4555 $DB->delete_records_select('course_modules_completion',
4556 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4557 array($courseid));
4558 $DB->delete_records_select('course_modules_availability',
4559 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4560 array($courseid));
4562 // Remove course-module data.
4563 $cms = $DB->get_records('course_modules', array('course'=>$course->id));
4564 foreach ($cms as $cm) {
4565 if ($module = $DB->get_record('modules', array('id'=>$cm->module))) {
4566 try {
4567 $DB->delete_records($module->name, array('id'=>$cm->instance));
4568 } catch (Exception $e) {
4569 // Ignore weird or missing table problems
4572 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4573 $DB->delete_records('course_modules', array('id'=>$cm->id));
4576 if ($showfeedback) {
4577 echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
4580 // Cleanup the rest of plugins
4581 $cleanuplugintypes = array('report', 'coursereport', 'format');
4582 foreach ($cleanuplugintypes as $type) {
4583 $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
4584 foreach ($plugins as $plugin=>$pluginfunction) {
4585 $pluginfunction($course->id, $showfeedback);
4587 if ($showfeedback) {
4588 echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
4592 // Delete questions and question categories
4593 question_delete_course($course, $showfeedback);
4594 if ($showfeedback) {
4595 echo $OUTPUT->notification($strdeleted.get_string('questions', 'question'), 'notifysuccess');
4598 // Make sure there are no subcontexts left - all valid blocks and modules should be already gone
4599 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4600 foreach ($childcontexts as $childcontext) {
4601 $childcontext->delete();
4603 unset($childcontexts);
4605 // Remove all roles and enrolments by default
4606 if (empty($options['keep_roles_and_enrolments'])) {
4607 // this hack is used in restore when deleting contents of existing course
4608 role_unassign_all(array('contextid'=>$coursecontext->id), true);
4609 enrol_course_delete($course);
4610 if ($showfeedback) {
4611 echo $OUTPUT->notification($strdeleted.get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
4615 // Delete any groups, removing members and grouping/course links first.
4616 if (empty($options['keep_groups_and_groupings'])) {
4617 groups_delete_groupings($course->id, $showfeedback);
4618 groups_delete_groups($course->id, $showfeedback);
4621 // filters be gone!
4622 filter_delete_all_for_context($coursecontext->id);
4624 // die comments!
4625 comment::delete_comments($coursecontext->id);
4627 // ratings are history too
4628 $delopt = new stdclass();
4629 $delopt->contextid = $coursecontext->id;
4630 $rm = new rating_manager();
4631 $rm->delete_ratings($delopt);
4633 // Delete course tags
4634 coursetag_delete_course_tags($course->id, $showfeedback);
4636 // Delete calendar events
4637 $DB->delete_records('event', array('courseid'=>$course->id));
4638 $fs->delete_area_files($coursecontext->id, 'calendar');
4640 // Delete all related records in other core tables that may have a courseid
4641 // This array stores the tables that need to be cleared, as
4642 // table_name => column_name that contains the course id.
4643 $tablestoclear = array(
4644 'log' => 'course', // Course logs (NOTE: this might be changed in the future)
4645 'backup_courses' => 'courseid', // Scheduled backup stuff
4646 'user_lastaccess' => 'courseid', // User access info
4648 foreach ($tablestoclear as $table => $col) {
4649 $DB->delete_records($table, array($col=>$course->id));
4652 // delete all course backup files
4653 $fs->delete_area_files($coursecontext->id, 'backup');
4655 // cleanup course record - remove links to deleted stuff
4656 $oldcourse = new stdClass();
4657 $oldcourse->id = $course->id;
4658 $oldcourse->summary = '';
4659 $oldcourse->modinfo = NULL;
4660 $oldcourse->legacyfiles = 0;
4661 $oldcourse->enablecompletion = 0;
4662 if (!empty($options['keep_groups_and_groupings'])) {
4663 $oldcourse->defaultgroupingid = 0;
4665 $DB->update_record('course', $oldcourse);
4667 // Delete course sections and user selections
4668 $DB->delete_records('course_sections', array('course'=>$course->id));
4669 $DB->delete_records('course_display', array('course'=>$course->id));
4671 // delete legacy, section and any other course files
4672 $fs->delete_area_files($coursecontext->id, 'course'); // files from summary and section
4674 // Delete all remaining stuff linked to context such as files, comments, ratings, etc.
4675 if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
4676 // Easy, do not delete the context itself...
4677 $coursecontext->delete_content();
4679 } else {
4680 // Hack alert!!!!
4681 // We can not drop all context stuff because it would bork enrolments and roles,
4682 // there might be also files used by enrol plugins...
4685 // Delete legacy files - just in case some files are still left there after conversion to new file api,
4686 // also some non-standard unsupported plugins may try to store something there
4687 fulldelete($CFG->dataroot.'/'.$course->id);
4689 // Finally trigger the event
4690 $course->context = $coursecontext; // you can not access context in cron event later after course is deleted
4691 $course->options = $options; // not empty if we used any crazy hack
4692 events_trigger('course_content_removed', $course);
4694 return true;
4698 * Change dates in module - used from course reset.
4700 * @global object
4701 * @global object
4702 * @param string $modname forum, assignment, etc
4703 * @param array $fields array of date fields from mod table
4704 * @param int $timeshift time difference
4705 * @param int $courseid
4706 * @return bool success
4708 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4709 global $CFG, $DB;
4710 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
4712 $return = true;
4713 foreach ($fields as $field) {
4714 $updatesql = "UPDATE {".$modname."}
4715 SET $field = $field + ?
4716 WHERE course=? AND $field<>0";
4717 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4720 $refreshfunction = $modname.'_refresh_events';
4721 if (function_exists($refreshfunction)) {
4722 $refreshfunction($courseid);
4725 return $return;
4729 * This function will empty a course of user data.
4730 * It will retain the activities and the structure of the course.
4732 * @param object $data an object containing all the settings including courseid (without magic quotes)
4733 * @return array status array of array component, item, error
4735 function reset_course_userdata($data) {
4736 global $CFG, $USER, $DB;
4737 require_once($CFG->libdir.'/gradelib.php');
4738 require_once($CFG->libdir.'/completionlib.php');
4739 require_once($CFG->dirroot.'/group/lib.php');
4741 $data->courseid = $data->id;
4742 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
4744 // calculate the time shift of dates
4745 if (!empty($data->reset_start_date)) {
4746 // time part of course startdate should be zero
4747 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
4748 } else {
4749 $data->timeshift = 0;
4752 // result array: component, item, error
4753 $status = array();
4755 // start the resetting
4756 $componentstr = get_string('general');
4758 // move the course start time
4759 if (!empty($data->reset_start_date) and $data->timeshift) {
4760 // change course start data
4761 $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
4762 // update all course and group events - do not move activity events
4763 $updatesql = "UPDATE {event}
4764 SET timestart = timestart + ?
4765 WHERE courseid=? AND instance=0";
4766 $DB->execute($updatesql, array($data->timeshift, $data->courseid));
4768 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4771 if (!empty($data->reset_logs)) {
4772 $DB->delete_records('log', array('course'=>$data->courseid));
4773 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4776 if (!empty($data->reset_events)) {
4777 $DB->delete_records('event', array('courseid'=>$data->courseid));
4778 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4781 if (!empty($data->reset_notes)) {
4782 require_once($CFG->dirroot.'/notes/lib.php');
4783 note_delete_all($data->courseid);
4784 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4787 if (!empty($data->delete_blog_associations)) {
4788 require_once($CFG->dirroot.'/blog/lib.php');
4789 blog_remove_associations_for_course($data->courseid);
4790 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4793 if (!empty($data->reset_completion)) {
4794 // Delete course and activity completion information.
4795 $course = $DB->get_record('course', array('id'=>$data->courseid));
4796 $cc = new completion_info($course);
4797 $cc->delete_all_completion_data();
4798 $status[] = array('component' => $componentstr,
4799 'item' => get_string('deletecompletiondata', 'completion'), 'error' => false);
4802 $componentstr = get_string('roles');
4804 if (!empty($data->reset_roles_overrides)) {
4805 $children = get_child_contexts($context);
4806 foreach ($children as $child) {
4807 $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
4809 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
4810 //force refresh for logged in users
4811 mark_context_dirty($context->path);
4812 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4815 if (!empty($data->reset_roles_local)) {
4816 $children = get_child_contexts($context);
4817 foreach ($children as $child) {
4818 role_unassign_all(array('contextid'=>$child->id));
4820 //force refresh for logged in users
4821 mark_context_dirty($context->path);
4822 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4825 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4826 $data->unenrolled = array();
4827 if (!empty($data->unenrol_users)) {
4828 $plugins = enrol_get_plugins(true);
4829 $instances = enrol_get_instances($data->courseid, true);
4830 foreach ($instances as $key=>$instance) {
4831 if (!isset($plugins[$instance->enrol])) {
4832 unset($instances[$key]);
4833 continue;
4835 if (!$plugins[$instance->enrol]->allow_unenrol($instance)) {
4836 unset($instances[$key]);
4840 $sqlempty = $DB->sql_empty();
4841 foreach($data->unenrol_users as $withroleid) {
4842 $sql = "SELECT DISTINCT ue.userid, ue.enrolid
4843 FROM {user_enrolments} ue
4844 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4845 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4846 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4847 $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
4849 $rs = $DB->get_recordset_sql($sql, $params);
4850 foreach ($rs as $ue) {
4851 if (!isset($instances[$ue->enrolid])) {
4852 continue;
4854 $plugins[$instances[$ue->enrolid]->enrol]->unenrol_user($instances[$ue->enrolid], $ue->userid);
4855 $data->unenrolled[$ue->userid] = $ue->userid;
4859 if (!empty($data->unenrolled)) {
4860 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
4864 $componentstr = get_string('groups');
4866 // remove all group members
4867 if (!empty($data->reset_groups_members)) {
4868 groups_delete_group_members($data->courseid);
4869 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
4872 // remove all groups
4873 if (!empty($data->reset_groups_remove)) {
4874 groups_delete_groups($data->courseid, false);
4875 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
4878 // remove all grouping members
4879 if (!empty($data->reset_groupings_members)) {
4880 groups_delete_groupings_groups($data->courseid, false);
4881 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
4884 // remove all groupings
4885 if (!empty($data->reset_groupings_remove)) {
4886 groups_delete_groupings($data->courseid, false);
4887 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
4890 // Look in every instance of every module for data to delete
4891 $unsupported_mods = array();
4892 if ($allmods = $DB->get_records('modules') ) {
4893 foreach ($allmods as $mod) {
4894 $modname = $mod->name;
4895 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
4896 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
4897 if (file_exists($modfile)) {
4898 if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
4899 continue; // Skip mods with no instances
4901 include_once($modfile);
4902 if (function_exists($moddeleteuserdata)) {
4903 $modstatus = $moddeleteuserdata($data);
4904 if (is_array($modstatus)) {
4905 $status = array_merge($status, $modstatus);
4906 } else {
4907 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
4909 } else {
4910 $unsupported_mods[] = $mod;
4912 } else {
4913 debugging('Missing lib.php in '.$modname.' module!');
4918 // mention unsupported mods
4919 if (!empty($unsupported_mods)) {
4920 foreach($unsupported_mods as $mod) {
4921 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
4926 $componentstr = get_string('gradebook', 'grades');
4927 // reset gradebook
4928 if (!empty($data->reset_gradebook_items)) {
4929 remove_course_grades($data->courseid, false);
4930 grade_grab_course_grades($data->courseid);
4931 grade_regrade_final_grades($data->courseid);
4932 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4934 } else if (!empty($data->reset_gradebook_grades)) {
4935 grade_course_reset($data->courseid);
4936 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4938 // reset comments
4939 if (!empty($data->reset_comments)) {
4940 require_once($CFG->dirroot.'/comment/lib.php');
4941 comment::reset_course_page_comments($context);
4944 return $status;
4948 * Generate an email processing address
4950 * @param int $modid
4951 * @param string $modargs
4952 * @return string Returns email processing address
4954 function generate_email_processing_address($modid,$modargs) {
4955 global $CFG;
4957 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4958 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
4964 * @todo Finish documenting this function
4966 * @global object
4967 * @param string $modargs
4968 * @param string $body Currently unused
4970 function moodle_process_email($modargs,$body) {
4971 global $DB;
4973 // the first char should be an unencoded letter. We'll take this as an action
4974 switch ($modargs{0}) {
4975 case 'B': { // bounce
4976 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4977 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
4978 // check the half md5 of their email
4979 $md5check = substr(md5($user->email),0,16);
4980 if ($md5check == substr($modargs, -16)) {
4981 set_bounce_count($user);
4983 // else maybe they've already changed it?
4986 break;
4987 // maybe more later?
4991 /// CORRESPONDENCE ////////////////////////////////////////////////
4994 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4996 * @global object
4997 * @param string $action 'get', 'buffer', 'close' or 'flush'
4998 * @return object|null mailer instance if 'get' used or nothing
5000 function get_mailer($action='get') {
5001 global $CFG;
5003 static $mailer = null;
5004 static $counter = 0;
5006 if (!isset($CFG->smtpmaxbulk)) {
5007 $CFG->smtpmaxbulk = 1;
5010 if ($action == 'get') {
5011 $prevkeepalive = false;
5013 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5014 if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
5015 $counter++;
5016 // reset the mailer
5017 $mailer->Priority = 3;
5018 $mailer->CharSet = 'UTF-8'; // our default
5019 $mailer->ContentType = "text/plain";
5020 $mailer->Encoding = "8bit";
5021 $mailer->From = "root@localhost";
5022 $mailer->FromName = "Root User";
5023 $mailer->Sender = "";
5024 $mailer->Subject = "";
5025 $mailer->Body = "";
5026 $mailer->AltBody = "";
5027 $mailer->ConfirmReadingTo = "";
5029 $mailer->ClearAllRecipients();
5030 $mailer->ClearReplyTos();
5031 $mailer->ClearAttachments();
5032 $mailer->ClearCustomHeaders();
5033 return $mailer;
5036 $prevkeepalive = $mailer->SMTPKeepAlive;
5037 get_mailer('flush');
5040 include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
5041 $mailer = new moodle_phpmailer();
5043 $counter = 1;
5045 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
5046 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
5047 $mailer->CharSet = 'UTF-8';
5049 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
5050 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
5051 $mailer->LE = "\r\n";
5052 } else {
5053 $mailer->LE = "\n";
5056 if ($CFG->smtphosts == 'qmail') {
5057 $mailer->IsQmail(); // use Qmail system
5059 } else if (empty($CFG->smtphosts)) {
5060 $mailer->IsMail(); // use PHP mail() = sendmail
5062 } else {
5063 $mailer->IsSMTP(); // use SMTP directly
5064 if (!empty($CFG->debugsmtp)) {
5065 $mailer->SMTPDebug = true;
5067 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
5068 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
5070 if ($CFG->smtpuser) { // Use SMTP authentication
5071 $mailer->SMTPAuth = true;
5072 $mailer->Username = $CFG->smtpuser;
5073 $mailer->Password = $CFG->smtppass;
5077 return $mailer;
5080 $nothing = null;
5082 // keep smtp session open after sending
5083 if ($action == 'buffer') {
5084 if (!empty($CFG->smtpmaxbulk)) {
5085 get_mailer('flush');
5086 $m = get_mailer();
5087 if ($m->Mailer == 'smtp') {
5088 $m->SMTPKeepAlive = true;
5091 return $nothing;
5094 // close smtp session, but continue buffering
5095 if ($action == 'flush') {
5096 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5097 if (!empty($mailer->SMTPDebug)) {
5098 echo '<pre>'."\n";
5100 $mailer->SmtpClose();
5101 if (!empty($mailer->SMTPDebug)) {
5102 echo '</pre>';
5105 return $nothing;
5108 // close smtp session, do not buffer anymore
5109 if ($action == 'close') {
5110 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5111 get_mailer('flush');
5112 $mailer->SMTPKeepAlive = false;
5114 $mailer = null; // better force new instance
5115 return $nothing;
5120 * Send an email to a specified user
5122 * @global object
5123 * @global string
5124 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
5125 * @uses SITEID
5126 * @param stdClass $user A {@link $USER} object
5127 * @param stdClass $from A {@link $USER} object
5128 * @param string $subject plain text subject line of the email
5129 * @param string $messagetext plain text version of the message
5130 * @param string $messagehtml complete html version of the message (optional)
5131 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
5132 * @param string $attachname the name of the file (extension indicates MIME)
5133 * @param bool $usetrueaddress determines whether $from email address should
5134 * be sent out. Will be overruled by user profile setting for maildisplay
5135 * @param string $replyto Email address to reply to
5136 * @param string $replytoname Name of reply to recipient
5137 * @param int $wordwrapwidth custom word wrap width, default 79
5138 * @return bool Returns true if mail was sent OK and false if there was an error.
5140 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
5142 global $CFG, $FULLME;
5144 if (empty($user) || empty($user->email)) {
5145 $nulluser = 'User is null or has no email';
5146 error_log($nulluser);
5147 if (CLI_SCRIPT) {
5148 mtrace('Error: lib/moodlelib.php email_to_user(): '.$nulluser);
5150 return false;
5153 if (!empty($user->deleted)) {
5154 // do not mail deleted users
5155 $userdeleted = 'User is deleted';
5156 error_log($userdeleted);
5157 if (CLI_SCRIPT) {
5158 mtrace('Error: lib/moodlelib.php email_to_user(): '.$userdeleted);
5160 return false;
5163 if (!empty($CFG->noemailever)) {
5164 // hidden setting for development sites, set in config.php if needed
5165 $noemail = 'Not sending email due to noemailever config setting';
5166 error_log($noemail);
5167 if (CLI_SCRIPT) {
5168 mtrace('Error: lib/moodlelib.php email_to_user(): '.$noemail);
5170 return true;
5173 if (!empty($CFG->divertallemailsto)) {
5174 $subject = "[DIVERTED {$user->email}] $subject";
5175 $user = clone($user);
5176 $user->email = $CFG->divertallemailsto;
5179 // skip mail to suspended users
5180 if ((isset($user->auth) && $user->auth=='nologin') or (isset($user->suspended) && $user->suspended)) {
5181 return true;
5184 if (!validate_email($user->email)) {
5185 // we can not send emails to invalid addresses - it might create security issue or confuse the mailer
5186 $invalidemail = "User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.";
5187 error_log($invalidemail);
5188 if (CLI_SCRIPT) {
5189 mtrace('Error: lib/moodlelib.php email_to_user(): '.$invalidemail);
5191 return false;
5194 if (over_bounce_threshold($user)) {
5195 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
5196 error_log($bouncemsg);
5197 if (CLI_SCRIPT) {
5198 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
5200 return false;
5203 // If the user is a remote mnet user, parse the email text for URL to the
5204 // wwwroot and modify the url to direct the user's browser to login at their
5205 // home site (identity provider - idp) before hitting the link itself
5206 if (is_mnet_remote_user($user)) {
5207 require_once($CFG->dirroot.'/mnet/lib.php');
5209 $jumpurl = mnet_get_idp_jump_url($user);
5210 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
5212 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
5213 $callback,
5214 $messagetext);
5215 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
5216 $callback,
5217 $messagehtml);
5219 $mail = get_mailer();
5221 if (!empty($mail->SMTPDebug)) {
5222 echo '<pre>' . "\n";
5225 $temprecipients = array();
5226 $tempreplyto = array();
5228 $supportuser = generate_email_supportuser();
5230 // make up an email address for handling bounces
5231 if (!empty($CFG->handlebounces)) {
5232 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
5233 $mail->Sender = generate_email_processing_address(0,$modargs);
5234 } else {
5235 $mail->Sender = $supportuser->email;
5238 if (is_string($from)) { // So we can pass whatever we want if there is need
5239 $mail->From = $CFG->noreplyaddress;
5240 $mail->FromName = $from;
5241 } else if ($usetrueaddress and $from->maildisplay) {
5242 $mail->From = $from->email;
5243 $mail->FromName = fullname($from);
5244 } else {
5245 $mail->From = $CFG->noreplyaddress;
5246 $mail->FromName = fullname($from);
5247 if (empty($replyto)) {
5248 $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
5252 if (!empty($replyto)) {
5253 $tempreplyto[] = array($replyto, $replytoname);
5256 $mail->Subject = substr($subject, 0, 900);
5258 $temprecipients[] = array($user->email, fullname($user));
5260 $mail->WordWrap = $wordwrapwidth; // set word wrap
5262 if (!empty($from->customheaders)) { // Add custom headers
5263 if (is_array($from->customheaders)) {
5264 foreach ($from->customheaders as $customheader) {
5265 $mail->AddCustomHeader($customheader);
5267 } else {
5268 $mail->AddCustomHeader($from->customheaders);
5272 if (!empty($from->priority)) {
5273 $mail->Priority = $from->priority;
5276 if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
5277 $mail->IsHTML(true);
5278 $mail->Encoding = 'quoted-printable'; // Encoding to use
5279 $mail->Body = $messagehtml;
5280 $mail->AltBody = "\n$messagetext\n";
5281 } else {
5282 $mail->IsHTML(false);
5283 $mail->Body = "\n$messagetext\n";
5286 if ($attachment && $attachname) {
5287 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
5288 $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
5289 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
5290 } else {
5291 require_once($CFG->libdir.'/filelib.php');
5292 $mimetype = mimeinfo('type', $attachname);
5293 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
5297 // Check if the email should be sent in an other charset then the default UTF-8
5298 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
5300 // use the defined site mail charset or eventually the one preferred by the recipient
5301 $charset = $CFG->sitemailcharset;
5302 if (!empty($CFG->allowusermailcharset)) {
5303 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
5304 $charset = $useremailcharset;
5308 // convert all the necessary strings if the charset is supported
5309 $charsets = get_list_of_charsets();
5310 unset($charsets['UTF-8']);
5311 if (in_array($charset, $charsets)) {
5312 $textlib = textlib_get_instance();
5313 $mail->CharSet = $charset;
5314 $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', strtolower($charset));
5315 $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', strtolower($charset));
5316 $mail->Body = $textlib->convert($mail->Body, 'utf-8', strtolower($charset));
5317 $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', strtolower($charset));
5319 foreach ($temprecipients as $key => $values) {
5320 $temprecipients[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
5322 foreach ($tempreplyto as $key => $values) {
5323 $tempreplyto[$key][1] = $textlib->convert($values[1], 'utf-8', strtolower($charset));
5328 foreach ($temprecipients as $values) {
5329 $mail->AddAddress($values[0], $values[1]);
5331 foreach ($tempreplyto as $values) {
5332 $mail->AddReplyTo($values[0], $values[1]);
5335 if ($mail->Send()) {
5336 set_send_count($user);
5337 $mail->IsSMTP(); // use SMTP directly
5338 if (!empty($mail->SMTPDebug)) {
5339 echo '</pre>';
5341 return true;
5342 } else {
5343 add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
5344 if (CLI_SCRIPT) {
5345 mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
5347 if (!empty($mail->SMTPDebug)) {
5348 echo '</pre>';
5350 return false;
5355 * Generate a signoff for emails based on support settings
5357 * @global object
5358 * @return string
5360 function generate_email_signoff() {
5361 global $CFG;
5363 $signoff = "\n";
5364 if (!empty($CFG->supportname)) {
5365 $signoff .= $CFG->supportname."\n";
5367 if (!empty($CFG->supportemail)) {
5368 $signoff .= $CFG->supportemail."\n";
5370 if (!empty($CFG->supportpage)) {
5371 $signoff .= $CFG->supportpage."\n";
5373 return $signoff;
5377 * Generate a fake user for emails based on support settings
5378 * @global object
5379 * @return object user info
5381 function generate_email_supportuser() {
5382 global $CFG;
5384 static $supportuser;
5386 if (!empty($supportuser)) {
5387 return $supportuser;
5390 $supportuser = new stdClass();
5391 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
5392 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
5393 $supportuser->lastname = '';
5394 $supportuser->maildisplay = true;
5396 return $supportuser;
5401 * Sets specified user's password and send the new password to the user via email.
5403 * @global object
5404 * @global object
5405 * @param user $user A {@link $USER} object
5406 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
5408 function setnew_password_and_mail($user) {
5409 global $CFG, $DB;
5411 $site = get_site();
5413 $supportuser = generate_email_supportuser();
5415 $newpassword = generate_password();
5417 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
5419 $a = new stdClass();
5420 $a->firstname = fullname($user, true);
5421 $a->sitename = format_string($site->fullname);
5422 $a->username = $user->username;
5423 $a->newpassword = $newpassword;
5424 $a->link = $CFG->wwwroot .'/login/';
5425 $a->signoff = generate_email_signoff();
5427 $message = get_string('newusernewpasswordtext', '', $a);
5429 $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
5431 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5432 return email_to_user($user, $supportuser, $subject, $message);
5437 * Resets specified user's password and send the new password to the user via email.
5439 * @param stdClass $user A {@link $USER} object
5440 * @return bool Returns true if mail was sent OK and false if there was an error.
5442 function reset_password_and_mail($user) {
5443 global $CFG;
5445 $site = get_site();
5446 $supportuser = generate_email_supportuser();
5448 $userauth = get_auth_plugin($user->auth);
5449 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
5450 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
5451 return false;
5454 $newpassword = generate_password();
5456 if (!$userauth->user_update_password($user, $newpassword)) {
5457 print_error("cannotsetpassword");
5460 $a = new stdClass();
5461 $a->firstname = $user->firstname;
5462 $a->lastname = $user->lastname;
5463 $a->sitename = format_string($site->fullname);
5464 $a->username = $user->username;
5465 $a->newpassword = $newpassword;
5466 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
5467 $a->signoff = generate_email_signoff();
5469 $message = get_string('newpasswordtext', '', $a);
5471 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
5473 unset_user_preference('create_password', $user); // prevent cron from generating the password
5475 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5476 return email_to_user($user, $supportuser, $subject, $message);
5481 * Send email to specified user with confirmation text and activation link.
5483 * @global object
5484 * @param user $user A {@link $USER} object
5485 * @return bool Returns true if mail was sent OK and false if there was an error.
5487 function send_confirmation_email($user) {
5488 global $CFG;
5490 $site = get_site();
5491 $supportuser = generate_email_supportuser();
5493 $data = new stdClass();
5494 $data->firstname = fullname($user);
5495 $data->sitename = format_string($site->fullname);
5496 $data->admin = generate_email_signoff();
5498 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
5500 $username = urlencode($user->username);
5501 $username = str_replace('.', '%2E', $username); // prevent problems with trailing dots
5502 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $username;
5503 $message = get_string('emailconfirmation', '', $data);
5504 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
5506 $user->mailformat = 1; // Always send HTML version as well
5508 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5509 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
5514 * send_password_change_confirmation_email.
5516 * @global object
5517 * @param user $user A {@link $USER} object
5518 * @return bool Returns true if mail was sent OK and false if there was an error.
5520 function send_password_change_confirmation_email($user) {
5521 global $CFG;
5523 $site = get_site();
5524 $supportuser = generate_email_supportuser();
5526 $data = new stdClass();
5527 $data->firstname = $user->firstname;
5528 $data->lastname = $user->lastname;
5529 $data->sitename = format_string($site->fullname);
5530 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
5531 $data->admin = generate_email_signoff();
5533 $message = get_string('emailpasswordconfirmation', '', $data);
5534 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
5536 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5537 return email_to_user($user, $supportuser, $subject, $message);
5542 * send_password_change_info.
5544 * @global object
5545 * @param user $user A {@link $USER} object
5546 * @return bool Returns true if mail was sent OK and false if there was an error.
5548 function send_password_change_info($user) {
5549 global $CFG;
5551 $site = get_site();
5552 $supportuser = generate_email_supportuser();
5553 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
5555 $data = new stdClass();
5556 $data->firstname = $user->firstname;
5557 $data->lastname = $user->lastname;
5558 $data->sitename = format_string($site->fullname);
5559 $data->admin = generate_email_signoff();
5561 $userauth = get_auth_plugin($user->auth);
5563 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
5564 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
5565 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5566 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5567 return email_to_user($user, $supportuser, $subject, $message);
5570 if ($userauth->can_change_password() and $userauth->change_password_url()) {
5571 // we have some external url for password changing
5572 $data->link .= $userauth->change_password_url();
5574 } else {
5575 //no way to change password, sorry
5576 $data->link = '';
5579 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
5580 $message = get_string('emailpasswordchangeinfo', '', $data);
5581 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5582 } else {
5583 $message = get_string('emailpasswordchangeinfofail', '', $data);
5584 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5587 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5588 return email_to_user($user, $supportuser, $subject, $message);
5593 * Check that an email is allowed. It returns an error message if there
5594 * was a problem.
5596 * @global object
5597 * @param string $email Content of email
5598 * @return string|false
5600 function email_is_not_allowed($email) {
5601 global $CFG;
5603 if (!empty($CFG->allowemailaddresses)) {
5604 $allowed = explode(' ', $CFG->allowemailaddresses);
5605 foreach ($allowed as $allowedpattern) {
5606 $allowedpattern = trim($allowedpattern);
5607 if (!$allowedpattern) {
5608 continue;
5610 if (strpos($allowedpattern, '.') === 0) {
5611 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5612 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5613 return false;
5616 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5617 return false;
5620 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5622 } else if (!empty($CFG->denyemailaddresses)) {
5623 $denied = explode(' ', $CFG->denyemailaddresses);
5624 foreach ($denied as $deniedpattern) {
5625 $deniedpattern = trim($deniedpattern);
5626 if (!$deniedpattern) {
5627 continue;
5629 if (strpos($deniedpattern, '.') === 0) {
5630 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5631 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5632 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5635 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5636 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5641 return false;
5644 /// FILE HANDLING /////////////////////////////////////////////
5647 * Returns local file storage instance
5649 * @return file_storage
5651 function get_file_storage() {
5652 global $CFG;
5654 static $fs = null;
5656 if ($fs) {
5657 return $fs;
5660 require_once("$CFG->libdir/filelib.php");
5662 if (isset($CFG->filedir)) {
5663 $filedir = $CFG->filedir;
5664 } else {
5665 $filedir = $CFG->dataroot.'/filedir';
5668 if (isset($CFG->trashdir)) {
5669 $trashdirdir = $CFG->trashdir;
5670 } else {
5671 $trashdirdir = $CFG->dataroot.'/trashdir';
5674 $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5676 return $fs;
5680 * Returns local file storage instance
5682 * @return file_browser
5684 function get_file_browser() {
5685 global $CFG;
5687 static $fb = null;
5689 if ($fb) {
5690 return $fb;
5693 require_once("$CFG->libdir/filelib.php");
5695 $fb = new file_browser();
5697 return $fb;
5701 * Returns file packer
5703 * @param string $mimetype default application/zip
5704 * @return file_packer
5706 function get_file_packer($mimetype='application/zip') {
5707 global $CFG;
5709 static $fp = array();;
5711 if (isset($fp[$mimetype])) {
5712 return $fp[$mimetype];
5715 switch ($mimetype) {
5716 case 'application/zip':
5717 case 'application/vnd.moodle.backup':
5718 $classname = 'zip_packer';
5719 break;
5720 case 'application/x-tar':
5721 // $classname = 'tar_packer';
5722 // break;
5723 default:
5724 return false;
5727 require_once("$CFG->libdir/filestorage/$classname.php");
5728 $fp[$mimetype] = new $classname();
5730 return $fp[$mimetype];
5734 * Returns current name of file on disk if it exists.
5736 * @param string $newfile File to be verified
5737 * @return string Current name of file on disk if true
5739 function valid_uploaded_file($newfile) {
5740 if (empty($newfile)) {
5741 return '';
5743 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5744 return $newfile['tmp_name'];
5745 } else {
5746 return '';
5751 * Returns the maximum size for uploading files.
5753 * There are seven possible upload limits:
5754 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5755 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5756 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5757 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5758 * 5. by the Moodle admin in $CFG->maxbytes
5759 * 6. by the teacher in the current course $course->maxbytes
5760 * 7. by the teacher for the current module, eg $assignment->maxbytes
5762 * These last two are passed to this function as arguments (in bytes).
5763 * Anything defined as 0 is ignored.
5764 * The smallest of all the non-zero numbers is returned.
5766 * @todo Finish documenting this function
5768 * @param int $sizebytes Set maximum size
5769 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5770 * @param int $modulebytes Current module ->maxbytes (in bytes)
5771 * @return int The maximum size for uploading files.
5773 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5775 if (! $filesize = ini_get('upload_max_filesize')) {
5776 $filesize = '5M';
5778 $minimumsize = get_real_size($filesize);
5780 if ($postsize = ini_get('post_max_size')) {
5781 $postsize = get_real_size($postsize);
5782 if ($postsize < $minimumsize) {
5783 $minimumsize = $postsize;
5787 if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
5788 $minimumsize = $sitebytes;
5791 if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
5792 $minimumsize = $coursebytes;
5795 if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
5796 $minimumsize = $modulebytes;
5799 return $minimumsize;
5803 * Returns an array of possible sizes in local language
5805 * Related to {@link get_max_upload_file_size()} - this function returns an
5806 * array of possible sizes in an array, translated to the
5807 * local language.
5809 * @todo Finish documenting this function
5811 * @global object
5812 * @uses SORT_NUMERIC
5813 * @param int $sizebytes Set maximum size
5814 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5815 * @param int $modulebytes Current module ->maxbytes (in bytes)
5816 * @param int|array $custombytes custom upload size/s which will be added to list,
5817 * Only value/s smaller then maxsize will be added to list.
5818 * @return array
5820 function get_max_upload_sizes($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $custombytes = null) {
5821 global $CFG;
5823 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5824 return array();
5827 $filesize = array();
5828 $filesize[intval($maxsize)] = display_size($maxsize);
5830 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5831 5242880, 10485760, 20971520, 52428800, 104857600);
5833 // If custombytes is given and is valid then add it to the list.
5834 if (is_number($custombytes) and $custombytes > 0) {
5835 $custombytes = (int)$custombytes;
5836 if (!in_array($custombytes, $sizelist)) {
5837 $sizelist[] = $custombytes;
5839 } else if (is_array($custombytes)) {
5840 $sizelist = array_unique(array_merge($sizelist, $custombytes));
5843 // Allow maxbytes to be selected if it falls outside the above boundaries
5844 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
5845 // note: get_real_size() is used in order to prevent problems with invalid values
5846 $sizelist[] = get_real_size($CFG->maxbytes);
5849 foreach ($sizelist as $sizebytes) {
5850 if ($sizebytes < $maxsize) {
5851 $filesize[intval($sizebytes)] = display_size($sizebytes);
5855 krsort($filesize, SORT_NUMERIC);
5857 return $filesize;
5861 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
5863 * If excludefiles is defined, then that file/directory is ignored
5864 * If getdirs is true, then (sub)directories are included in the output
5865 * If getfiles is true, then files are included in the output
5866 * (at least one of these must be true!)
5868 * @todo Finish documenting this function. Add examples of $excludefile usage.
5870 * @param string $rootdir A given root directory to start from
5871 * @param string|array $excludefile If defined then the specified file/directory is ignored
5872 * @param bool $descend If true then subdirectories are recursed as well
5873 * @param bool $getdirs If true then (sub)directories are included in the output
5874 * @param bool $getfiles If true then files are included in the output
5875 * @return array An array with all the filenames in
5876 * all subdirectories, relative to the given rootdir
5878 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5880 $dirs = array();
5882 if (!$getdirs and !$getfiles) { // Nothing to show
5883 return $dirs;
5886 if (!is_dir($rootdir)) { // Must be a directory
5887 return $dirs;
5890 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5891 return $dirs;
5894 if (!is_array($excludefiles)) {
5895 $excludefiles = array($excludefiles);
5898 while (false !== ($file = readdir($dir))) {
5899 $firstchar = substr($file, 0, 1);
5900 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5901 continue;
5903 $fullfile = $rootdir .'/'. $file;
5904 if (filetype($fullfile) == 'dir') {
5905 if ($getdirs) {
5906 $dirs[] = $file;
5908 if ($descend) {
5909 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5910 foreach ($subdirs as $subdir) {
5911 $dirs[] = $file .'/'. $subdir;
5914 } else if ($getfiles) {
5915 $dirs[] = $file;
5918 closedir($dir);
5920 asort($dirs);
5922 return $dirs;
5927 * Adds up all the files in a directory and works out the size.
5929 * @todo Finish documenting this function
5931 * @param string $rootdir The directory to start from
5932 * @param string $excludefile A file to exclude when summing directory size
5933 * @return int The summed size of all files and subfiles within the root directory
5935 function get_directory_size($rootdir, $excludefile='') {
5936 global $CFG;
5938 // do it this way if we can, it's much faster
5939 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5940 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5941 $output = null;
5942 $return = null;
5943 exec($command,$output,$return);
5944 if (is_array($output)) {
5945 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5949 if (!is_dir($rootdir)) { // Must be a directory
5950 return 0;
5953 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
5954 return 0;
5957 $size = 0;
5959 while (false !== ($file = readdir($dir))) {
5960 $firstchar = substr($file, 0, 1);
5961 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5962 continue;
5964 $fullfile = $rootdir .'/'. $file;
5965 if (filetype($fullfile) == 'dir') {
5966 $size += get_directory_size($fullfile, $excludefile);
5967 } else {
5968 $size += filesize($fullfile);
5971 closedir($dir);
5973 return $size;
5977 * Converts bytes into display form
5979 * @todo Finish documenting this function. Verify return type.
5981 * @staticvar string $gb Localized string for size in gigabytes
5982 * @staticvar string $mb Localized string for size in megabytes
5983 * @staticvar string $kb Localized string for size in kilobytes
5984 * @staticvar string $b Localized string for size in bytes
5985 * @param int $size The size to convert to human readable form
5986 * @return string
5988 function display_size($size) {
5990 static $gb, $mb, $kb, $b;
5992 if (empty($gb)) {
5993 $gb = get_string('sizegb');
5994 $mb = get_string('sizemb');
5995 $kb = get_string('sizekb');
5996 $b = get_string('sizeb');
5999 if ($size >= 1073741824) {
6000 $size = round($size / 1073741824 * 10) / 10 . $gb;
6001 } else if ($size >= 1048576) {
6002 $size = round($size / 1048576 * 10) / 10 . $mb;
6003 } else if ($size >= 1024) {
6004 $size = round($size / 1024 * 10) / 10 . $kb;
6005 } else {
6006 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
6008 return $size;
6012 * Cleans a given filename by removing suspicious or troublesome characters
6013 * @see clean_param()
6015 * @uses PARAM_FILE
6016 * @param string $string file name
6017 * @return string cleaned file name
6019 function clean_filename($string) {
6020 return clean_param($string, PARAM_FILE);
6024 /// STRING TRANSLATION ////////////////////////////////////////
6027 * Returns the code for the current language
6029 * @return string
6031 function current_language() {
6032 global $CFG, $USER, $SESSION, $COURSE;
6034 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
6035 $return = $COURSE->lang;
6037 } else if (!empty($SESSION->lang)) { // Session language can override other settings
6038 $return = $SESSION->lang;
6040 } else if (!empty($USER->lang)) {
6041 $return = $USER->lang;
6043 } else if (isset($CFG->lang)) {
6044 $return = $CFG->lang;
6046 } else {
6047 $return = 'en';
6050 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
6052 return $return;
6056 * Returns parent language of current active language if defined
6058 * @uses COURSE
6059 * @uses SESSION
6060 * @param string $lang null means current language
6061 * @return string
6063 function get_parent_language($lang=null) {
6064 global $COURSE, $SESSION;
6066 //let's hack around the current language
6067 if (!empty($lang)) {
6068 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
6069 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
6070 $COURSE->lang = '';
6071 $SESSION->lang = $lang;
6074 $parentlang = get_string('parentlanguage', 'langconfig');
6075 if ($parentlang === 'en') {
6076 $parentlang = '';
6079 //let's hack around the current language
6080 if (!empty($lang)) {
6081 $COURSE->lang = $old_course_lang;
6082 $SESSION->lang = $old_session_lang;
6085 return $parentlang;
6089 * Returns current string_manager instance.
6091 * The param $forcereload is needed for CLI installer only where the string_manager instance
6092 * must be replaced during the install.php script life time.
6094 * @param bool $forcereload shall the singleton be released and new instance created instead?
6095 * @return string_manager
6097 function get_string_manager($forcereload=false) {
6098 global $CFG;
6100 static $singleton = null;
6102 if ($forcereload) {
6103 $singleton = null;
6105 if ($singleton === null) {
6106 if (empty($CFG->early_install_lang)) {
6108 if (empty($CFG->langcacheroot)) {
6109 $langcacheroot = $CFG->cachedir . '/lang';
6110 } else {
6111 $langcacheroot = $CFG->langcacheroot;
6114 if (empty($CFG->langlist)) {
6115 $translist = array();
6116 } else {
6117 $translist = explode(',', $CFG->langlist);
6120 if (empty($CFG->langmenucachefile)) {
6121 $langmenucache = $CFG->cachedir . '/languages';
6122 } else {
6123 $langmenucache = $CFG->langmenucachefile;
6126 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
6127 !empty($CFG->langstringcache), $translist, $langmenucache);
6129 } else {
6130 $singleton = new install_string_manager();
6134 return $singleton;
6139 * Interface describing class which is responsible for getting
6140 * of localised strings from language packs.
6142 * @package moodlecore
6143 * @copyright 2010 Petr Skoda (http://skodak.org)
6144 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6146 interface string_manager {
6148 * Get String returns a requested string
6150 * @param string $identifier The identifier of the string to search for
6151 * @param string $component The module the string is associated with
6152 * @param string|object|array $a An object, string or number that can be used
6153 * within translation strings
6154 * @param string $lang moodle translation language, NULL means use current
6155 * @return string The String !
6157 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
6160 * Does the string actually exist?
6162 * get_string() is throwing debug warnings, sometimes we do not want them
6163 * or we want to display better explanation of the problem.
6165 * Use with care!
6167 * @param string $identifier The identifier of the string to search for
6168 * @param string $component The module the string is associated with
6169 * @return boot true if exists
6171 public function string_exists($identifier, $component);
6174 * Returns a localised list of all country names, sorted by country keys.
6175 * @param bool $returnall return all or just enabled
6176 * @param string $lang moodle translation language, NULL means use current
6177 * @return array two-letter country code => translated name.
6179 public function get_list_of_countries($returnall = false, $lang = NULL);
6182 * Returns a localised list of languages, sorted by code keys.
6184 * @param string $lang moodle translation language, NULL means use current
6185 * @param string $standard language list standard
6186 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6187 * @return array language code => translated name
6189 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
6192 * Does the translation exist?
6194 * @param string $lang moodle translation language code
6195 * @param bool include also disabled translations?
6196 * @return boot true if exists
6198 public function translation_exists($lang, $includeall = true);
6201 * Returns localised list of installed translations
6202 * @param bool $returnall return all or just enabled
6203 * @return array moodle translation code => localised translation name
6205 public function get_list_of_translations($returnall = false);
6208 * Returns localised list of currencies.
6210 * @param string $lang moodle translation language, NULL means use current
6211 * @return array currency code => localised currency name
6213 public function get_list_of_currencies($lang = NULL);
6216 * Load all strings for one component
6217 * @param string $component The module the string is associated with
6218 * @param string $lang
6219 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6220 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6221 * @return array of all string for given component and lang
6223 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
6226 * Invalidates all caches, should the implementation use any
6228 public function reset_caches();
6233 * Standard string_manager implementation
6235 * @package moodlecore
6236 * @copyright 2010 Petr Skoda (http://skodak.org)
6237 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6239 class core_string_manager implements string_manager {
6240 /** @var string location of all packs except 'en' */
6241 protected $otherroot;
6242 /** @var string location of all lang pack local modifications */
6243 protected $localroot;
6244 /** @var string location of on-disk cache of merged strings */
6245 protected $cacheroot;
6246 /** @var array lang string cache - it will be optimised more later */
6247 protected $cache = array();
6248 /** @var int get_string() counter */
6249 protected $countgetstring = 0;
6250 /** @var int in-memory cache hits counter */
6251 protected $countmemcache = 0;
6252 /** @var int on-disk cache hits counter */
6253 protected $countdiskcache = 0;
6254 /** @var bool use disk cache */
6255 protected $usediskcache;
6256 /* @var array limit list of translations */
6257 protected $translist;
6258 /** @var string location of a file that caches the list of available translations */
6259 protected $menucache;
6262 * Create new instance of string manager
6264 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
6265 * @param string $localroot usually the same as $otherroot
6266 * @param string $cacheroot usually lang dir in cache folder
6267 * @param bool $usediskcache use disk cache
6268 * @param array $translist limit list of visible translations
6269 * @param string $menucache the location of a file that caches the list of available translations
6271 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
6272 $this->otherroot = $otherroot;
6273 $this->localroot = $localroot;
6274 $this->cacheroot = $cacheroot;
6275 $this->usediskcache = $usediskcache;
6276 $this->translist = $translist;
6277 $this->menucache = $menucache;
6281 * Returns dependencies of current language, en is not included.
6282 * @param string $lang
6283 * @return array all parents, the lang itself is last
6285 public function get_language_dependencies($lang) {
6286 if ($lang === 'en') {
6287 return array();
6289 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
6290 return array();
6292 $string = array();
6293 include("$this->otherroot/$lang/langconfig.php");
6295 if (empty($string['parentlanguage'])) {
6296 return array($lang);
6297 } else {
6298 $parentlang = $string['parentlanguage'];
6299 unset($string);
6300 return array_merge($this->get_language_dependencies($parentlang), array($lang));
6305 * Load all strings for one component
6306 * @param string $component The module the string is associated with
6307 * @param string $lang
6308 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6309 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6310 * @return array of all string for given component and lang
6312 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6313 global $CFG;
6315 list($plugintype, $pluginname) = normalize_component($component);
6316 if ($plugintype == 'core' and is_null($pluginname)) {
6317 $component = 'core';
6318 } else {
6319 $component = $plugintype . '_' . $pluginname;
6322 if (!$disablecache) {
6323 // try in-memory cache first
6324 if (isset($this->cache[$lang][$component])) {
6325 $this->countmemcache++;
6326 return $this->cache[$lang][$component];
6329 // try on-disk cache then
6330 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
6331 $this->countdiskcache++;
6332 include($this->cacheroot . "/$lang/$component.php");
6333 return $this->cache[$lang][$component];
6337 // no cache found - let us merge all possible sources of the strings
6338 if ($plugintype === 'core') {
6339 $file = $pluginname;
6340 if ($file === null) {
6341 $file = 'moodle';
6343 $string = array();
6344 // first load english pack
6345 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
6346 return array();
6348 include("$CFG->dirroot/lang/en/$file.php");
6349 $originalkeys = array_keys($string);
6350 $originalkeys = array_flip($originalkeys);
6352 // and then corresponding local if present and allowed
6353 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6354 include("$this->localroot/en_local/$file.php");
6356 // now loop through all langs in correct order
6357 $deps = $this->get_language_dependencies($lang);
6358 foreach ($deps as $dep) {
6359 // the main lang string location
6360 if (file_exists("$this->otherroot/$dep/$file.php")) {
6361 include("$this->otherroot/$dep/$file.php");
6363 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6364 include("$this->localroot/{$dep}_local/$file.php");
6368 } else {
6369 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
6370 return array();
6372 if ($plugintype === 'mod') {
6373 // bloody mod hack
6374 $file = $pluginname;
6375 } else {
6376 $file = $plugintype . '_' . $pluginname;
6378 $string = array();
6379 // first load English pack
6380 if (!file_exists("$location/lang/en/$file.php")) {
6381 //English pack does not exist, so do not try to load anything else
6382 return array();
6384 include("$location/lang/en/$file.php");
6385 $originalkeys = array_keys($string);
6386 $originalkeys = array_flip($originalkeys);
6387 // and then corresponding local english if present
6388 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6389 include("$this->localroot/en_local/$file.php");
6392 // now loop through all langs in correct order
6393 $deps = $this->get_language_dependencies($lang);
6394 foreach ($deps as $dep) {
6395 // legacy location - used by contrib only
6396 if (file_exists("$location/lang/$dep/$file.php")) {
6397 include("$location/lang/$dep/$file.php");
6399 // the main lang string location
6400 if (file_exists("$this->otherroot/$dep/$file.php")) {
6401 include("$this->otherroot/$dep/$file.php");
6403 // local customisations
6404 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6405 include("$this->localroot/{$dep}_local/$file.php");
6410 // we do not want any extra strings from other languages - everything must be in en lang pack
6411 $string = array_intersect_key($string, $originalkeys);
6413 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
6414 // caches so we do not need to do all this merging and dependencies resolving again
6415 $this->cache[$lang][$component] = $string;
6416 if ($this->usediskcache) {
6417 check_dir_exists("$this->cacheroot/$lang");
6418 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
6420 return $string;
6424 * Does the string actually exist?
6426 * get_string() is throwing debug warnings, sometimes we do not want them
6427 * or we want to display better explanation of the problem.
6429 * Use with care!
6431 * @param string $identifier The identifier of the string to search for
6432 * @param string $component The module the string is associated with
6433 * @return boot true if exists
6435 public function string_exists($identifier, $component) {
6436 $identifier = clean_param($identifier, PARAM_STRINGID);
6437 if (empty($identifier)) {
6438 return false;
6440 $lang = current_language();
6441 $string = $this->load_component_strings($component, $lang);
6442 return isset($string[$identifier]);
6446 * Get String returns a requested string
6448 * @param string $identifier The identifier of the string to search for
6449 * @param string $component The module the string is associated with
6450 * @param string|object|array $a An object, string or number that can be used
6451 * within translation strings
6452 * @param string $lang moodle translation language, NULL means use current
6453 * @return string The String !
6455 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6456 $this->countgetstring++;
6457 // there are very many uses of these time formating strings without the 'langconfig' component,
6458 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
6459 static $langconfigstrs = array(
6460 'strftimedate' => 1,
6461 'strftimedatefullshort' => 1,
6462 'strftimedateshort' => 1,
6463 'strftimedatetime' => 1,
6464 'strftimedatetimeshort' => 1,
6465 'strftimedaydate' => 1,
6466 'strftimedaydatetime' => 1,
6467 'strftimedayshort' => 1,
6468 'strftimedaytime' => 1,
6469 'strftimemonthyear' => 1,
6470 'strftimerecent' => 1,
6471 'strftimerecentfull' => 1,
6472 'strftimetime' => 1);
6474 if (empty($component)) {
6475 if (isset($langconfigstrs[$identifier])) {
6476 $component = 'langconfig';
6477 } else {
6478 $component = 'moodle';
6482 if ($lang === NULL) {
6483 $lang = current_language();
6486 $string = $this->load_component_strings($component, $lang);
6488 if (!isset($string[$identifier])) {
6489 if ($component === 'pix' or $component === 'core_pix') {
6490 // this component contains only alt tags for emoticons,
6491 // not all of them are supposed to be defined
6492 return '';
6494 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
6495 // parentlanguage is a special string, undefined means use English if not defined
6496 return 'en';
6498 if ($this->usediskcache) {
6499 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources
6500 $string = $this->load_component_strings($component, $lang, true);
6502 if (!isset($string[$identifier])) {
6503 // the string is still missing - should be fixed by developer
6504 list($plugintype, $pluginname) = normalize_component($component);
6505 if ($plugintype == 'core') {
6506 $file = "lang/en/{$component}.php";
6507 } else if ($plugintype == 'mod') {
6508 $file = "mod/{$pluginname}/lang/en/{$pluginname}.php";
6509 } else {
6510 $path = get_plugin_directory($plugintype, $pluginname);
6511 $file = "{$path}/lang/en/{$plugintype}_{$pluginname}.php";
6513 debugging("Invalid get_string() identifier: '{$identifier}' or component '{$component}'. " .
6514 "Perhaps you are missing \$string['{$identifier}'] = ''; in {$file}?", DEBUG_DEVELOPER);
6515 return "[[$identifier]]";
6519 $string = $string[$identifier];
6521 if ($a !== NULL) {
6522 if (is_object($a) or is_array($a)) {
6523 $a = (array)$a;
6524 $search = array();
6525 $replace = array();
6526 foreach ($a as $key=>$value) {
6527 if (is_int($key)) {
6528 // we do not support numeric keys - sorry!
6529 continue;
6531 if (is_object($value) or is_array($value)) {
6532 // we support just string as value
6533 continue;
6535 $search[] = '{$a->'.$key.'}';
6536 $replace[] = (string)$value;
6538 if ($search) {
6539 $string = str_replace($search, $replace, $string);
6541 } else {
6542 $string = str_replace('{$a}', (string)$a, $string);
6546 return $string;
6550 * Returns information about the string_manager performance
6551 * @return array
6553 public function get_performance_summary() {
6554 return array(array(
6555 'langcountgetstring' => $this->countgetstring,
6556 'langcountmemcache' => $this->countmemcache,
6557 'langcountdiskcache' => $this->countdiskcache,
6558 ), array(
6559 'langcountgetstring' => 'get_string calls',
6560 'langcountmemcache' => 'strings mem cache hits',
6561 'langcountdiskcache' => 'strings disk cache hits',
6566 * Returns a localised list of all country names, sorted by localised name.
6568 * @param bool $returnall return all or just enabled
6569 * @param string $lang moodle translation language, NULL means use current
6570 * @return array two-letter country code => translated name.
6572 public function get_list_of_countries($returnall = false, $lang = NULL) {
6573 global $CFG;
6575 if ($lang === NULL) {
6576 $lang = current_language();
6579 $countries = $this->load_component_strings('core_countries', $lang);
6580 collatorlib::asort($countries);
6581 if (!$returnall and !empty($CFG->allcountrycodes)) {
6582 $enabled = explode(',', $CFG->allcountrycodes);
6583 $return = array();
6584 foreach ($enabled as $c) {
6585 if (isset($countries[$c])) {
6586 $return[$c] = $countries[$c];
6589 return $return;
6592 return $countries;
6596 * Returns a localised list of languages, sorted by code keys.
6598 * @param string $lang moodle translation language, NULL means use current
6599 * @param string $standard language list standard
6600 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
6601 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
6602 * @return array language code => translated name
6604 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
6605 if ($lang === NULL) {
6606 $lang = current_language();
6609 if ($standard === 'iso6392') {
6610 $langs = $this->load_component_strings('core_iso6392', $lang);
6611 ksort($langs);
6612 return $langs;
6614 } else if ($standard === 'iso6391') {
6615 $langs2 = $this->load_component_strings('core_iso6392', $lang);
6616 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
6617 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
6618 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
6619 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
6620 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
6621 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
6622 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
6623 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
6624 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
6625 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
6626 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
6627 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
6628 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
6629 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6630 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6631 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6632 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6633 $langs1 = array();
6634 foreach ($mapping as $c2=>$c1) {
6635 $langs1[$c1] = $langs2[$c2];
6637 ksort($langs1);
6638 return $langs1;
6640 } else {
6641 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6644 return array();
6648 * Does the translation exist?
6650 * @param string $lang moodle translation language code
6651 * @param bool include also disabled translations?
6652 * @return boot true if exists
6654 public function translation_exists($lang, $includeall = true) {
6656 if (strpos($lang, '_local') !== false) {
6657 // _local packs are not real translations
6658 return false;
6660 if (!$includeall and !empty($this->translist)) {
6661 if (!in_array($lang, $this->translist)) {
6662 return false;
6665 if ($lang === 'en') {
6666 // part of distribution
6667 return true;
6669 return file_exists("$this->otherroot/$lang/langconfig.php");
6673 * Returns localised list of installed translations
6674 * @param bool $returnall return all or just enabled
6675 * @return array moodle translation code => localised translation name
6677 public function get_list_of_translations($returnall = false) {
6678 global $CFG;
6680 $languages = array();
6682 if (!empty($CFG->langcache) and is_readable($this->menucache)) {
6683 // try to re-use the cached list of all available languages
6684 $cachedlist = json_decode(file_get_contents($this->menucache), true);
6686 if (is_array($cachedlist) and !empty($cachedlist)) {
6687 // the cache file is restored correctly
6689 if (!$returnall and !empty($this->translist)) {
6690 // return just enabled translations
6691 foreach ($cachedlist as $langcode => $langname) {
6692 if (in_array($langcode, $this->translist)) {
6693 $languages[$langcode] = $langname;
6696 return $languages;
6698 } else {
6699 // return all translations
6700 return $cachedlist;
6705 // the cached list of languages is not available, let us populate the list
6707 if (!$returnall and !empty($this->translist)) {
6708 // return only some translations
6709 foreach ($this->translist as $lang) {
6710 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6711 if (strstr($lang, '_local') !== false) {
6712 continue;
6714 if (strstr($lang, '_utf8') !== false) {
6715 continue;
6717 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6718 // some broken or missing lang - can not switch to it anyway
6719 continue;
6721 $string = $this->load_component_strings('langconfig', $lang);
6722 if (!empty($string['thislanguage'])) {
6723 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6725 unset($string);
6728 } else {
6729 // return all languages available in system
6730 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6732 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6733 // Sort all
6735 // Loop through all langs and get info
6736 foreach ($langdirs as $lang) {
6737 if (strstr($lang, '_local') !== false) {
6738 continue;
6740 if (strstr($lang, '_utf8') !== false) {
6741 continue;
6743 $string = $this->load_component_strings('langconfig', $lang);
6744 if (!empty($string['thislanguage'])) {
6745 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6747 unset($string);
6750 if (!empty($CFG->langcache) and !empty($this->menucache)) {
6751 // cache the list so that it can be used next time
6752 collatorlib::asort($languages);
6753 check_dir_exists(dirname($this->menucache), true, true);
6754 file_put_contents($this->menucache, json_encode($languages));
6758 collatorlib::asort($languages);
6760 return $languages;
6764 * Returns localised list of currencies.
6766 * @param string $lang moodle translation language, NULL means use current
6767 * @return array currency code => localised currency name
6769 public function get_list_of_currencies($lang = NULL) {
6770 if ($lang === NULL) {
6771 $lang = current_language();
6774 $currencies = $this->load_component_strings('core_currencies', $lang);
6775 asort($currencies);
6777 return $currencies;
6781 * Clears both in-memory and on-disk caches
6783 public function reset_caches() {
6784 global $CFG;
6785 require_once("$CFG->libdir/filelib.php");
6787 // clear the on-disk disk with aggregated string files
6788 fulldelete($this->cacheroot);
6790 // clear the in-memory cache of loaded strings
6791 $this->cache = array();
6793 // clear the cache containing the list of available translations
6794 // and re-populate it again
6795 fulldelete($this->menucache);
6796 $this->get_list_of_translations(true);
6802 * Minimalistic string fetching implementation
6803 * that is used in installer before we fetch the wanted
6804 * language pack from moodle.org lang download site.
6806 * @package moodlecore
6807 * @copyright 2010 Petr Skoda (http://skodak.org)
6808 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6810 class install_string_manager implements string_manager {
6811 /** @var string location of pre-install packs for all langs */
6812 protected $installroot;
6815 * Crate new instance of install string manager
6817 public function __construct() {
6818 global $CFG;
6819 $this->installroot = "$CFG->dirroot/install/lang";
6823 * Load all strings for one component
6824 * @param string $component The module the string is associated with
6825 * @param string $lang
6826 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6827 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6828 * @return array of all string for given component and lang
6830 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6831 // not needed in installer
6832 return array();
6836 * Does the string actually exist?
6838 * get_string() is throwing debug warnings, sometimes we do not want them
6839 * or we want to display better explanation of the problem.
6841 * Use with care!
6843 * @param string $identifier The identifier of the string to search for
6844 * @param string $component The module the string is associated with
6845 * @return boot true if exists
6847 public function string_exists($identifier, $component) {
6848 $identifier = clean_param($identifier, PARAM_STRINGID);
6849 if (empty($identifier)) {
6850 return false;
6852 // simple old style hack ;)
6853 $str = get_string($identifier, $component);
6854 return (strpos($str, '[[') === false);
6858 * Get String returns a requested string
6860 * @param string $identifier The identifier of the string to search for
6861 * @param string $component The module the string is associated with
6862 * @param string|object|array $a An object, string or number that can be used
6863 * within translation strings
6864 * @param string $lang moodle translation language, NULL means use current
6865 * @return string The String !
6867 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6868 if (!$component) {
6869 $component = 'moodle';
6872 if ($lang === NULL) {
6873 $lang = current_language();
6876 //get parent lang
6877 $parent = '';
6878 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
6879 if (file_exists("$this->installroot/$lang/langconfig.php")) {
6880 $string = array();
6881 include("$this->installroot/$lang/langconfig.php");
6882 if (isset($string['parentlanguage'])) {
6883 $parent = $string['parentlanguage'];
6885 unset($string);
6889 // include en string first
6890 if (!file_exists("$this->installroot/en/$component.php")) {
6891 return "[[$identifier]]";
6893 $string = array();
6894 include("$this->installroot/en/$component.php");
6896 // now override en with parent if defined
6897 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
6898 include("$this->installroot/$parent/$component.php");
6901 // finally override with requested language
6902 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
6903 include("$this->installroot/$lang/$component.php");
6906 if (!isset($string[$identifier])) {
6907 return "[[$identifier]]";
6910 $string = $string[$identifier];
6912 if ($a !== NULL) {
6913 if (is_object($a) or is_array($a)) {
6914 $a = (array)$a;
6915 $search = array();
6916 $replace = array();
6917 foreach ($a as $key=>$value) {
6918 if (is_int($key)) {
6919 // we do not support numeric keys - sorry!
6920 continue;
6922 $search[] = '{$a->'.$key.'}';
6923 $replace[] = (string)$value;
6925 if ($search) {
6926 $string = str_replace($search, $replace, $string);
6928 } else {
6929 $string = str_replace('{$a}', (string)$a, $string);
6933 return $string;
6937 * Returns a localised list of all country names, sorted by country keys.
6939 * @param bool $returnall return all or just enabled
6940 * @param string $lang moodle translation language, NULL means use current
6941 * @return array two-letter country code => translated name.
6943 public function get_list_of_countries($returnall = false, $lang = NULL) {
6944 //not used in installer
6945 return array();
6949 * Returns a localised list of languages, sorted by code keys.
6951 * @param string $lang moodle translation language, NULL means use current
6952 * @param string $standard language list standard
6953 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6954 * @return array language code => translated name
6956 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
6957 //not used in installer
6958 return array();
6962 * Does the translation exist?
6964 * @param string $lang moodle translation language code
6965 * @param bool include also disabled translations?
6966 * @return boot true if exists
6968 public function translation_exists($lang, $includeall = true) {
6969 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
6973 * Returns localised list of installed translations
6974 * @param bool $returnall return all or just enabled
6975 * @return array moodle translation code => localised translation name
6977 public function get_list_of_translations($returnall = false) {
6978 // return all is ignored here - we need to know all langs in installer
6979 $languages = array();
6980 // Get raw list of lang directories
6981 $langdirs = get_list_of_plugins('install/lang');
6982 asort($langdirs);
6983 // Get some info from each lang
6984 foreach ($langdirs as $lang) {
6985 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
6986 $string = array();
6987 include($this->installroot.'/'.$lang.'/langconfig.php');
6988 if (!empty($string['thislanguage'])) {
6989 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
6993 // Return array
6994 return $languages;
6998 * Returns localised list of currencies.
7000 * @param string $lang moodle translation language, NULL means use current
7001 * @return array currency code => localised currency name
7003 public function get_list_of_currencies($lang = NULL) {
7004 // not used in installer
7005 return array();
7009 * This implementation does not use any caches
7011 public function reset_caches() {}
7016 * Returns a localized string.
7018 * Returns the translated string specified by $identifier as
7019 * for $module. Uses the same format files as STphp.
7020 * $a is an object, string or number that can be used
7021 * within translation strings
7023 * eg 'hello {$a->firstname} {$a->lastname}'
7024 * or 'hello {$a}'
7026 * If you would like to directly echo the localized string use
7027 * the function {@link print_string()}
7029 * Example usage of this function involves finding the string you would
7030 * like a local equivalent of and using its identifier and module information
7031 * to retrieve it.<br/>
7032 * If you open moodle/lang/en/moodle.php and look near line 278
7033 * you will find a string to prompt a user for their word for 'course'
7034 * <code>
7035 * $string['course'] = 'Course';
7036 * </code>
7037 * So if you want to display the string 'Course'
7038 * in any language that supports it on your site
7039 * you just need to use the identifier 'course'
7040 * <code>
7041 * $mystring = '<strong>'. get_string('course') .'</strong>';
7042 * or
7043 * </code>
7044 * If the string you want is in another file you'd take a slightly
7045 * different approach. Looking in moodle/lang/en/calendar.php you find
7046 * around line 75:
7047 * <code>
7048 * $string['typecourse'] = 'Course event';
7049 * </code>
7050 * If you want to display the string "Course event" in any language
7051 * supported you would use the identifier 'typecourse' and the module 'calendar'
7052 * (because it is in the file calendar.php):
7053 * <code>
7054 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
7055 * </code>
7057 * As a last resort, should the identifier fail to map to a string
7058 * the returned string will be [[ $identifier ]]
7060 * @param string $identifier The key identifier for the localized string
7061 * @param string $component The module where the key identifier is stored,
7062 * usually expressed as the filename in the language pack without the
7063 * .php on the end but can also be written as mod/forum or grade/export/xls.
7064 * If none is specified then moodle.php is used.
7065 * @param string|object|array $a An object, string or number that can be used
7066 * within translation strings
7067 * @return string The localized string.
7069 function get_string($identifier, $component = '', $a = NULL) {
7070 global $CFG;
7072 $identifier = clean_param($identifier, PARAM_STRINGID);
7073 if (empty($identifier)) {
7074 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');
7077 if (func_num_args() > 3) {
7078 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
7081 if (strpos($component, '/') !== false) {
7082 debugging('The module name you passed to get_string is the deprecated format ' .
7083 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
7084 $componentpath = explode('/', $component);
7086 switch ($componentpath[0]) {
7087 case 'mod':
7088 $component = $componentpath[1];
7089 break;
7090 case 'blocks':
7091 case 'block':
7092 $component = 'block_'.$componentpath[1];
7093 break;
7094 case 'enrol':
7095 $component = 'enrol_'.$componentpath[1];
7096 break;
7097 case 'format':
7098 $component = 'format_'.$componentpath[1];
7099 break;
7100 case 'grade':
7101 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
7102 break;
7106 $result = get_string_manager()->get_string($identifier, $component, $a);
7108 // Debugging feature lets you display string identifier and component
7109 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
7110 $result .= ' {' . $identifier . '/' . $component . '}';
7112 return $result;
7116 * Converts an array of strings to their localized value.
7118 * @param array $array An array of strings
7119 * @param string $module The language module that these strings can be found in.
7120 * @return array and array of translated strings.
7122 function get_strings($array, $component = '') {
7123 $string = new stdClass;
7124 foreach ($array as $item) {
7125 $string->$item = get_string($item, $component);
7127 return $string;
7131 * Prints out a translated string.
7133 * Prints out a translated string using the return value from the {@link get_string()} function.
7135 * Example usage of this function when the string is in the moodle.php file:<br/>
7136 * <code>
7137 * echo '<strong>';
7138 * print_string('course');
7139 * echo '</strong>';
7140 * </code>
7142 * Example usage of this function when the string is not in the moodle.php file:<br/>
7143 * <code>
7144 * echo '<h1>';
7145 * print_string('typecourse', 'calendar');
7146 * echo '</h1>';
7147 * </code>
7149 * @param string $identifier The key identifier for the localized string
7150 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
7151 * @param mixed $a An object, string or number that can be used within translation strings
7153 function print_string($identifier, $component = '', $a = NULL) {
7154 echo get_string($identifier, $component, $a);
7158 * Returns a list of charset codes
7160 * Returns a list of charset codes. It's hardcoded, so they should be added manually
7161 * (checking that such charset is supported by the texlib library!)
7163 * @return array And associative array with contents in the form of charset => charset
7165 function get_list_of_charsets() {
7167 $charsets = array(
7168 'EUC-JP' => 'EUC-JP',
7169 'ISO-2022-JP'=> 'ISO-2022-JP',
7170 'ISO-8859-1' => 'ISO-8859-1',
7171 'SHIFT-JIS' => 'SHIFT-JIS',
7172 'GB2312' => 'GB2312',
7173 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
7174 'UTF-8' => 'UTF-8');
7176 asort($charsets);
7178 return $charsets;
7182 * Returns a list of valid and compatible themes
7184 * @return array
7186 function get_list_of_themes() {
7187 global $CFG;
7189 $themes = array();
7191 if (!empty($CFG->themelist)) { // use admin's list of themes
7192 $themelist = explode(',', $CFG->themelist);
7193 } else {
7194 $themelist = array_keys(get_plugin_list("theme"));
7197 foreach ($themelist as $key => $themename) {
7198 $theme = theme_config::load($themename);
7199 $themes[$themename] = $theme;
7202 collatorlib::asort_objects_by_method($themes, 'get_theme_name');
7204 return $themes;
7208 * Returns a list of timezones in the current language
7210 * @global object
7211 * @global object
7212 * @return array
7214 function get_list_of_timezones() {
7215 global $CFG, $DB;
7217 static $timezones;
7219 if (!empty($timezones)) { // This function has been called recently
7220 return $timezones;
7223 $timezones = array();
7225 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
7226 foreach($rawtimezones as $timezone) {
7227 if (!empty($timezone->name)) {
7228 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
7229 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
7230 } else {
7231 $timezones[$timezone->name] = $timezone->name;
7233 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
7234 $timezones[$timezone->name] = $timezone->name;
7240 asort($timezones);
7242 for ($i = -13; $i <= 13; $i += .5) {
7243 $tzstring = 'UTC';
7244 if ($i < 0) {
7245 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
7246 } else if ($i > 0) {
7247 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
7248 } else {
7249 $timezones[sprintf("%.1f", $i)] = $tzstring;
7253 return $timezones;
7257 * Factory function for emoticon_manager
7259 * @return emoticon_manager singleton
7261 function get_emoticon_manager() {
7262 static $singleton = null;
7264 if (is_null($singleton)) {
7265 $singleton = new emoticon_manager();
7268 return $singleton;
7272 * Provides core support for plugins that have to deal with
7273 * emoticons (like HTML editor or emoticon filter).
7275 * Whenever this manager mentiones 'emoticon object', the following data
7276 * structure is expected: stdClass with properties text, imagename, imagecomponent,
7277 * altidentifier and altcomponent
7279 * @see admin_setting_emoticons
7281 class emoticon_manager {
7284 * Returns the currently enabled emoticons
7286 * @return array of emoticon objects
7288 public function get_emoticons() {
7289 global $CFG;
7291 if (empty($CFG->emoticons)) {
7292 return array();
7295 $emoticons = $this->decode_stored_config($CFG->emoticons);
7297 if (!is_array($emoticons)) {
7298 // something is wrong with the format of stored setting
7299 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
7300 return array();
7303 return $emoticons;
7307 * Converts emoticon object into renderable pix_emoticon object
7309 * @param stdClass $emoticon emoticon object
7310 * @param array $attributes explicit HTML attributes to set
7311 * @return pix_emoticon
7313 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
7314 $stringmanager = get_string_manager();
7315 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
7316 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
7317 } else {
7318 $alt = s($emoticon->text);
7320 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
7324 * Encodes the array of emoticon objects into a string storable in config table
7326 * @see self::decode_stored_config()
7327 * @param array $emoticons array of emtocion objects
7328 * @return string
7330 public function encode_stored_config(array $emoticons) {
7331 return json_encode($emoticons);
7335 * Decodes the string into an array of emoticon objects
7337 * @see self::encode_stored_config()
7338 * @param string $encoded
7339 * @return string|null
7341 public function decode_stored_config($encoded) {
7342 $decoded = json_decode($encoded);
7343 if (!is_array($decoded)) {
7344 return null;
7346 return $decoded;
7350 * Returns default set of emoticons supported by Moodle
7352 * @return array of sdtClasses
7354 public function default_emoticons() {
7355 return array(
7356 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
7357 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
7358 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
7359 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
7360 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
7361 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
7362 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
7363 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
7364 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
7365 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
7366 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
7367 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
7368 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
7369 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
7370 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
7371 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
7372 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
7373 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
7374 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
7375 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
7376 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
7377 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
7378 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
7379 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
7380 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
7381 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
7382 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
7383 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
7384 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
7385 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
7390 * Helper method preparing the stdClass with the emoticon properties
7392 * @param string|array $text or array of strings
7393 * @param string $imagename to be used by {@see pix_emoticon}
7394 * @param string $altidentifier alternative string identifier, null for no alt
7395 * @param array $altcomponent where the alternative string is defined
7396 * @param string $imagecomponent to be used by {@see pix_emoticon}
7397 * @return stdClass
7399 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
7400 return (object)array(
7401 'text' => $text,
7402 'imagename' => $imagename,
7403 'imagecomponent' => $imagecomponent,
7404 'altidentifier' => $altidentifier,
7405 'altcomponent' => $altcomponent,
7410 /// ENCRYPTION ////////////////////////////////////////////////
7413 * rc4encrypt
7415 * @todo Finish documenting this function
7417 * @param string $data Data to encrypt.
7418 * @param bool $usesecurekey Lets us know if we are using the old or new password.
7419 * @return string The now encrypted data.
7421 function rc4encrypt($data, $usesecurekey = false) {
7422 if (!$usesecurekey) {
7423 $passwordkey = 'nfgjeingjk';
7424 } else {
7425 $passwordkey = get_site_identifier();
7427 return endecrypt($passwordkey, $data, '');
7431 * rc4decrypt
7433 * @todo Finish documenting this function
7435 * @param string $data Data to decrypt.
7436 * @param bool $usesecurekey Lets us know if we are using the old or new password.
7437 * @return string The now decrypted data.
7439 function rc4decrypt($data, $usesecurekey = false) {
7440 if (!$usesecurekey) {
7441 $passwordkey = 'nfgjeingjk';
7442 } else {
7443 $passwordkey = get_site_identifier();
7445 return endecrypt($passwordkey, $data, 'de');
7449 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
7451 * @todo Finish documenting this function
7453 * @param string $pwd The password to use when encrypting or decrypting
7454 * @param string $data The data to be decrypted/encrypted
7455 * @param string $case Either 'de' for decrypt or '' for encrypt
7456 * @return string
7458 function endecrypt ($pwd, $data, $case) {
7460 if ($case == 'de') {
7461 $data = urldecode($data);
7464 $key[] = '';
7465 $box[] = '';
7466 $temp_swap = '';
7467 $pwd_length = 0;
7469 $pwd_length = strlen($pwd);
7471 for ($i = 0; $i <= 255; $i++) {
7472 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
7473 $box[$i] = $i;
7476 $x = 0;
7478 for ($i = 0; $i <= 255; $i++) {
7479 $x = ($x + $box[$i] + $key[$i]) % 256;
7480 $temp_swap = $box[$i];
7481 $box[$i] = $box[$x];
7482 $box[$x] = $temp_swap;
7485 $temp = '';
7486 $k = '';
7488 $cipherby = '';
7489 $cipher = '';
7491 $a = 0;
7492 $j = 0;
7494 for ($i = 0; $i < strlen($data); $i++) {
7495 $a = ($a + 1) % 256;
7496 $j = ($j + $box[$a]) % 256;
7497 $temp = $box[$a];
7498 $box[$a] = $box[$j];
7499 $box[$j] = $temp;
7500 $k = $box[(($box[$a] + $box[$j]) % 256)];
7501 $cipherby = ord(substr($data, $i, 1)) ^ $k;
7502 $cipher .= chr($cipherby);
7505 if ($case == 'de') {
7506 $cipher = urldecode(urlencode($cipher));
7507 } else {
7508 $cipher = urlencode($cipher);
7511 return $cipher;
7514 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
7517 * Returns the exact absolute path to plugin directory.
7519 * @param string $plugintype type of plugin
7520 * @param string $name name of the plugin
7521 * @return string full path to plugin directory; NULL if not found
7523 function get_plugin_directory($plugintype, $name) {
7524 global $CFG;
7526 if ($plugintype === '') {
7527 $plugintype = 'mod';
7530 $types = get_plugin_types(true);
7531 if (!array_key_exists($plugintype, $types)) {
7532 return NULL;
7534 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
7536 if (!empty($CFG->themedir) and $plugintype === 'theme') {
7537 if (!is_dir($types['theme'] . '/' . $name)) {
7538 // ok, so the theme is supposed to be in the $CFG->themedir
7539 return $CFG->themedir . '/' . $name;
7543 return $types[$plugintype].'/'.$name;
7547 * Return exact absolute path to a plugin directory,
7548 * this method support "simpletest_" prefix designed for unit testing.
7550 * @param string $component name such as 'moodle', 'mod_forum' or special simpletest value
7551 * @return string full path to component directory; NULL if not found
7553 function get_component_directory($component) {
7554 global $CFG;
7556 $simpletest = false;
7557 if (strpos($component, 'simpletest_') === 0) {
7558 $subdir = substr($component, strlen('simpletest_'));
7559 //TODO: this looks borked, where is it used actually?
7560 return $subdir;
7563 list($type, $plugin) = normalize_component($component);
7565 if ($type === 'core') {
7566 if ($plugin === NULL ) {
7567 $path = $CFG->libdir;
7568 } else {
7569 $subsystems = get_core_subsystems();
7570 if (isset($subsystems[$plugin])) {
7571 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
7572 } else {
7573 $path = NULL;
7577 } else {
7578 $path = get_plugin_directory($type, $plugin);
7581 return $path;
7585 * Normalize the component name using the "frankenstyle" names.
7586 * @param string $component
7587 * @return array $type+$plugin elements
7589 function normalize_component($component) {
7590 if ($component === 'moodle' or $component === 'core') {
7591 $type = 'core';
7592 $plugin = NULL;
7594 } else if (strpos($component, '_') === false) {
7595 $subsystems = get_core_subsystems();
7596 if (array_key_exists($component, $subsystems)) {
7597 $type = 'core';
7598 $plugin = $component;
7599 } else {
7600 // everything else is a module
7601 $type = 'mod';
7602 $plugin = $component;
7605 } else {
7606 list($type, $plugin) = explode('_', $component, 2);
7607 $plugintypes = get_plugin_types(false);
7608 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
7609 $type = 'mod';
7610 $plugin = $component;
7614 return array($type, $plugin);
7618 * List all core subsystems and their location
7620 * This is a whitelist of components that are part of the core and their
7621 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
7622 * plugin is not listed here and it does not have proper plugintype prefix,
7623 * then it is considered as course activity module.
7625 * The location is dirroot relative path. NULL means there is no special
7626 * directory for this subsystem. If the location is set, the subsystem's
7627 * renderer.php is expected to be there.
7629 * @return array of (string)name => (string|null)location
7631 function get_core_subsystems() {
7632 global $CFG;
7634 static $info = null;
7636 if (!$info) {
7637 $info = array(
7638 'access' => NULL,
7639 'admin' => $CFG->admin,
7640 'auth' => 'auth',
7641 'backup' => 'backup/util/ui',
7642 'block' => 'blocks',
7643 'blog' => 'blog',
7644 'bulkusers' => NULL,
7645 'calendar' => 'calendar',
7646 'cohort' => 'cohort',
7647 'condition' => NULL,
7648 'completion' => NULL,
7649 'countries' => NULL,
7650 'course' => 'course',
7651 'currencies' => NULL,
7652 'dbtransfer' => NULL,
7653 'debug' => NULL,
7654 'dock' => NULL,
7655 'editor' => 'lib/editor',
7656 'edufields' => NULL,
7657 'enrol' => 'enrol',
7658 'error' => NULL,
7659 'filepicker' => NULL,
7660 'files' => 'files',
7661 'filters' => NULL,
7662 'fonts' => NULL,
7663 'form' => 'lib/form',
7664 'grades' => 'grade',
7665 'grading' => 'grade/grading',
7666 'group' => 'group',
7667 'help' => NULL,
7668 'hub' => NULL,
7669 'imscc' => NULL,
7670 'install' => NULL,
7671 'iso6392' => NULL,
7672 'langconfig' => NULL,
7673 'license' => NULL,
7674 'mathslib' => NULL,
7675 'message' => 'message',
7676 'mimetypes' => NULL,
7677 'mnet' => 'mnet',
7678 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7679 'my' => 'my',
7680 'notes' => 'notes',
7681 'pagetype' => NULL,
7682 'pix' => NULL,
7683 'plagiarism' => 'plagiarism',
7684 'plugin' => NULL,
7685 'portfolio' => 'portfolio',
7686 'publish' => 'course/publish',
7687 'question' => 'question',
7688 'rating' => 'rating',
7689 'register' => 'admin/registration', //TODO: this is wrong, unfortunately we would need to modify hub code to pass around the correct url
7690 'repository' => 'repository',
7691 'rss' => 'rss',
7692 'role' => $CFG->admin.'/role',
7693 'search' => 'search',
7694 'table' => NULL,
7695 'tag' => 'tag',
7696 'timezones' => NULL,
7697 'user' => 'user',
7698 'userkey' => NULL,
7699 'webservice' => 'webservice',
7703 return $info;
7707 * Lists all plugin types
7708 * @param bool $fullpaths false means relative paths from dirroot
7709 * @return array Array of strings - name=>location
7711 function get_plugin_types($fullpaths=true) {
7712 global $CFG;
7714 static $info = null;
7715 static $fullinfo = null;
7717 if (!$info) {
7718 $info = array('qtype' => 'question/type',
7719 'mod' => 'mod',
7720 'auth' => 'auth',
7721 'enrol' => 'enrol',
7722 'message' => 'message/output',
7723 'block' => 'blocks',
7724 'filter' => 'filter',
7725 'editor' => 'lib/editor',
7726 'format' => 'course/format',
7727 'profilefield' => 'user/profile/field',
7728 'report' => 'report',
7729 'coursereport' => 'course/report', // must be after system reports
7730 'gradeexport' => 'grade/export',
7731 'gradeimport' => 'grade/import',
7732 'gradereport' => 'grade/report',
7733 'gradingform' => 'grade/grading/form',
7734 'mnetservice' => 'mnet/service',
7735 'webservice' => 'webservice',
7736 'repository' => 'repository',
7737 'portfolio' => 'portfolio',
7738 'qbehaviour' => 'question/behaviour',
7739 'qformat' => 'question/format',
7740 'plagiarism' => 'plagiarism',
7741 'tool' => $CFG->admin.'/tool',
7742 'theme' => 'theme', // this is a bit hacky, themes may be in $CFG->themedir too
7745 $mods = get_plugin_list('mod');
7746 foreach ($mods as $mod => $moddir) {
7747 if (file_exists("$moddir/db/subplugins.php")) {
7748 $subplugins = array();
7749 include("$moddir/db/subplugins.php");
7750 foreach ($subplugins as $subtype=>$dir) {
7751 $info[$subtype] = $dir;
7756 // local is always last!
7757 $info['local'] = 'local';
7759 $fullinfo = array();
7760 foreach ($info as $type => $dir) {
7761 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
7765 return ($fullpaths ? $fullinfo : $info);
7769 * Simplified version of get_list_of_plugins()
7770 * @param string $plugintype type of plugin
7771 * @return array name=>fulllocation pairs of plugins of given type
7773 function get_plugin_list($plugintype) {
7774 global $CFG;
7776 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
7777 if ($plugintype == 'auth') {
7778 // Historically we have had an auth plugin called 'db', so allow a special case.
7779 $key = array_search('db', $ignored);
7780 if ($key !== false) {
7781 unset($ignored[$key]);
7785 if ($plugintype === '') {
7786 $plugintype = 'mod';
7789 $fulldirs = array();
7791 if ($plugintype === 'mod') {
7792 // mod is an exception because we have to call this function from get_plugin_types()
7793 $fulldirs[] = $CFG->dirroot.'/mod';
7795 } else if ($plugintype === 'theme') {
7796 $fulldirs[] = $CFG->dirroot.'/theme';
7797 // themes are special because they may be stored also in separate directory
7798 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
7799 $fulldirs[] = $CFG->themedir;
7802 } else {
7803 $types = get_plugin_types(true);
7804 if (!array_key_exists($plugintype, $types)) {
7805 return array();
7807 $fulldir = $types[$plugintype];
7808 if (!file_exists($fulldir)) {
7809 return array();
7811 $fulldirs[] = $fulldir;
7814 $result = array();
7816 foreach ($fulldirs as $fulldir) {
7817 if (!is_dir($fulldir)) {
7818 continue;
7820 $items = new DirectoryIterator($fulldir);
7821 foreach ($items as $item) {
7822 if ($item->isDot() or !$item->isDir()) {
7823 continue;
7825 $pluginname = $item->getFilename();
7826 if (in_array($pluginname, $ignored)) {
7827 continue;
7829 $pluginname = clean_param($pluginname, PARAM_PLUGIN);
7830 if (empty($pluginname)) {
7831 // better ignore plugins with problematic names here
7832 continue;
7834 $result[$pluginname] = $fulldir.'/'.$pluginname;
7835 unset($item);
7837 unset($items);
7840 //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!
7841 ksort($result);
7842 return $result;
7846 * Get a list of all the plugins of a given type that contain a particular file.
7847 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
7848 * @param string $file the name of file that must be present in the plugin.
7849 * (e.g. 'view.php', 'db/install.xml').
7850 * @param bool $include if true (default false), the file will be include_once-ed if found.
7851 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
7852 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
7854 function get_plugin_list_with_file($plugintype, $file, $include = false) {
7855 global $CFG; // Necessary in case it is referenced by include()d PHP scripts.
7857 $plugins = array();
7859 foreach(get_plugin_list($plugintype) as $plugin => $dir) {
7860 $path = $dir . '/' . $file;
7861 if (file_exists($path)) {
7862 if ($include) {
7863 include_once($path);
7865 $plugins[$plugin] = $path;
7869 return $plugins;
7873 * Get a list of all the plugins of a given type that define a certain API function
7874 * in a certain file. The plugin component names and function names are returned.
7876 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
7877 * @param string $function the part of the name of the function after the
7878 * frankenstyle prefix. e.g 'hook' if you are looking for functions with
7879 * names like report_courselist_hook.
7880 * @param string $file the name of file within the plugin that defines the
7881 * function. Defaults to lib.php.
7882 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
7883 * and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
7885 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
7886 $pluginfunctions = array();
7887 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
7888 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
7890 if (function_exists($fullfunction)) {
7891 // Function exists with standard name. Store, indexed by
7892 // frankenstyle name of plugin
7893 $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
7895 } else if ($plugintype === 'mod') {
7896 // For modules, we also allow plugin without full frankenstyle
7897 // but just starting with the module name
7898 $shortfunction = $plugin . '_' . $function;
7899 if (function_exists($shortfunction)) {
7900 $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
7904 return $pluginfunctions;
7908 * Get a list of all the plugins of a given type that define a certain class
7909 * in a certain file. The plugin component names and class names are returned.
7911 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
7912 * @param string $class the part of the name of the class after the
7913 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
7914 * names like report_courselist_thing. If you are looking for classes with
7915 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
7916 * @param string $file the name of file within the plugin that defines the class.
7917 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
7918 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
7920 function get_plugin_list_with_class($plugintype, $class, $file) {
7921 if ($class) {
7922 $suffix = '_' . $class;
7923 } else {
7924 $suffix = '';
7927 $pluginclasses = array();
7928 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
7929 $classname = $plugintype . '_' . $plugin . $suffix;
7930 if (class_exists($classname)) {
7931 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
7935 return $pluginclasses;
7939 * Lists plugin-like directories within specified directory
7941 * This function was originally used for standard Moodle plugins, please use
7942 * new get_plugin_list() now.
7944 * This function is used for general directory listing and backwards compatility.
7946 * @param string $directory relative directory from root
7947 * @param string $exclude dir name to exclude from the list (defaults to none)
7948 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
7949 * @return array Sorted array of directory names found under the requested parameters
7951 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
7952 global $CFG;
7954 $plugins = array();
7956 if (empty($basedir)) {
7957 $basedir = $CFG->dirroot .'/'. $directory;
7959 } else {
7960 $basedir = $basedir .'/'. $directory;
7963 if (file_exists($basedir) && filetype($basedir) == 'dir') {
7964 $dirhandle = opendir($basedir);
7965 while (false !== ($dir = readdir($dirhandle))) {
7966 $firstchar = substr($dir, 0, 1);
7967 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
7968 continue;
7970 if (filetype($basedir .'/'. $dir) != 'dir') {
7971 continue;
7973 $plugins[] = $dir;
7975 closedir($dirhandle);
7977 if ($plugins) {
7978 asort($plugins);
7980 return $plugins;
7984 * Invoke plugin's callback functions
7986 * @param string $type plugin type e.g. 'mod'
7987 * @param string $name plugin name
7988 * @param string $feature feature name
7989 * @param string $action feature's action
7990 * @param array $params parameters of callback function, should be an array
7991 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
7992 * @return mixed
7994 * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
7996 function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
7997 return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
8001 * Invoke component's callback functions
8003 * @param string $component frankenstyle component name, e.g. 'mod_quiz'
8004 * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
8005 * @param array $params parameters of callback function
8006 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8007 * @return mixed
8009 function component_callback($component, $function, array $params = array(), $default = null) {
8010 global $CFG; // this is needed for require_once() below
8012 $cleancomponent = clean_param($component, PARAM_COMPONENT);
8013 if (empty($cleancomponent)) {
8014 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8016 $component = $cleancomponent;
8018 list($type, $name) = normalize_component($component);
8019 $component = $type . '_' . $name;
8021 $oldfunction = $name.'_'.$function;
8022 $function = $component.'_'.$function;
8024 $dir = get_component_directory($component);
8025 if (empty($dir)) {
8026 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8029 // Load library and look for function
8030 if (file_exists($dir.'/lib.php')) {
8031 require_once($dir.'/lib.php');
8034 if (!function_exists($function) and function_exists($oldfunction)) {
8035 if ($type !== 'mod' and $type !== 'core') {
8036 debugging("Please use new function name $function instead of legacy $oldfunction");
8038 $function = $oldfunction;
8041 if (function_exists($function)) {
8042 // Function exists, so just return function result
8043 $ret = call_user_func_array($function, $params);
8044 if (is_null($ret)) {
8045 return $default;
8046 } else {
8047 return $ret;
8050 return $default;
8054 * Checks whether a plugin supports a specified feature.
8056 * @param string $type Plugin type e.g. 'mod'
8057 * @param string $name Plugin name e.g. 'forum'
8058 * @param string $feature Feature code (FEATURE_xx constant)
8059 * @param mixed $default default value if feature support unknown
8060 * @return mixed Feature result (false if not supported, null if feature is unknown,
8061 * otherwise usually true but may have other feature-specific value such as array)
8063 function plugin_supports($type, $name, $feature, $default = NULL) {
8064 global $CFG;
8066 if ($type === 'mod' and $name === 'NEWMODULE') {
8067 //somebody forgot to rename the module template
8068 return false;
8071 $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
8072 if (empty($component)) {
8073 throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
8076 $function = null;
8078 if ($type === 'mod') {
8079 // we need this special case because we support subplugins in modules,
8080 // otherwise it would end up in infinite loop
8081 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
8082 include_once("$CFG->dirroot/mod/$name/lib.php");
8083 $function = $component.'_supports';
8084 if (!function_exists($function)) {
8085 // legacy non-frankenstyle function name
8086 $function = $name.'_supports';
8088 } else {
8089 // invalid module
8092 } else {
8093 if (!$path = get_plugin_directory($type, $name)) {
8094 // non existent plugin type
8095 return false;
8097 if (file_exists("$path/lib.php")) {
8098 include_once("$path/lib.php");
8099 $function = $component.'_supports';
8103 if ($function and function_exists($function)) {
8104 $supports = $function($feature);
8105 if (is_null($supports)) {
8106 // plugin does not know - use default
8107 return $default;
8108 } else {
8109 return $supports;
8113 //plugin does not care, so use default
8114 return $default;
8118 * Returns true if the current version of PHP is greater that the specified one.
8120 * @todo Check PHP version being required here is it too low?
8122 * @param string $version The version of php being tested.
8123 * @return bool
8125 function check_php_version($version='5.2.4') {
8126 return (version_compare(phpversion(), $version) >= 0);
8130 * Checks to see if is the browser operating system matches the specified
8131 * brand.
8133 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
8135 * @uses $_SERVER
8136 * @param string $brand The operating system identifier being tested
8137 * @return bool true if the given brand below to the detected operating system
8139 function check_browser_operating_system($brand) {
8140 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8141 return false;
8144 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
8145 return true;
8148 return false;
8152 * Checks to see if is a browser matches the specified
8153 * brand and is equal or better version.
8155 * @uses $_SERVER
8156 * @param string $brand The browser identifier being tested
8157 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
8158 * @return bool true if the given version is below that of the detected browser
8160 function check_browser_version($brand, $version = null) {
8161 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8162 return false;
8165 $agent = $_SERVER['HTTP_USER_AGENT'];
8167 switch ($brand) {
8169 case 'Camino': /// OSX browser using Gecke engine
8170 if (strpos($agent, 'Camino') === false) {
8171 return false;
8173 if (empty($version)) {
8174 return true; // no version specified
8176 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
8177 if (version_compare($match[1], $version) >= 0) {
8178 return true;
8181 break;
8184 case 'Firefox': /// Mozilla Firefox browsers
8185 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
8186 return false;
8188 if (empty($version)) {
8189 return true; // no version specified
8191 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8192 if (version_compare($match[2], $version) >= 0) {
8193 return true;
8196 break;
8199 case 'Gecko': /// Gecko based browsers
8200 if (empty($version) and substr_count($agent, 'Camino')) {
8201 // MacOS X Camino support
8202 $version = 20041110;
8205 // the proper string - Gecko/CCYYMMDD Vendor/Version
8206 // Faster version and work-a-round No IDN problem.
8207 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
8208 if ($match[1] > $version) {
8209 return true;
8212 break;
8215 case 'MSIE': /// Internet Explorer
8216 if (strpos($agent, 'Opera') !== false) { // Reject Opera
8217 return false;
8219 // in case of IE we have to deal with BC of the version parameter
8220 if (is_null($version)) {
8221 $version = 5.5; // anything older is not considered a browser at all!
8224 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
8225 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
8226 if (version_compare($match[1], $version) >= 0) {
8227 return true;
8230 break;
8233 case 'Opera': /// Opera
8234 if (strpos($agent, 'Opera') === false) {
8235 return false;
8237 if (empty($version)) {
8238 return true; // no version specified
8240 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
8241 if (version_compare($match[1], $version) >= 0) {
8242 return true;
8245 break;
8248 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
8249 if (strpos($agent, 'AppleWebKit') === false) {
8250 return false;
8252 if (empty($version)) {
8253 return true; // no version specified
8255 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8256 if (version_compare($match[1], $version) >= 0) {
8257 return true;
8260 break;
8263 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
8264 if (strpos($agent, 'AppleWebKit') === false) {
8265 return false;
8267 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
8268 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
8269 return false;
8271 if (strpos($agent, 'Shiira')) { // Reject Shiira
8272 return false;
8274 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
8275 return false;
8277 if (strpos($agent, 'Android')) { // Reject Androids too
8278 return false;
8280 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
8281 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
8282 return false;
8284 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
8285 return false;
8288 if (empty($version)) {
8289 return true; // no version specified
8291 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8292 if (version_compare($match[1], $version) >= 0) {
8293 return true;
8296 break;
8299 case 'Chrome':
8300 if (strpos($agent, 'Chrome') === false) {
8301 return false;
8303 if (empty($version)) {
8304 return true; // no version specified
8306 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
8307 if (version_compare($match[1], $version) >= 0) {
8308 return true;
8311 break;
8314 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
8315 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
8316 return false;
8318 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
8319 return false;
8321 if (empty($version)) {
8322 return true; // no version specified
8324 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8325 if (version_compare($match[1], $version) >= 0) {
8326 return true;
8329 break;
8332 case 'WebKit Android': /// WebKit browser on Android
8333 if (strpos($agent, 'Linux; U; Android') === false) {
8334 return false;
8336 if (empty($version)) {
8337 return true; // no version specified
8339 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8340 if (version_compare($match[1], $version) >= 0) {
8341 return true;
8344 break;
8348 return false;
8352 * Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
8353 * an optional admin specified regular expression. If enabledevicedetection is set to no or not set
8354 * it returns default
8356 * @return string device type
8358 function get_device_type() {
8359 global $CFG;
8361 if (empty($CFG->enabledevicedetection) || empty($_SERVER['HTTP_USER_AGENT'])) {
8362 return 'default';
8365 $useragent = $_SERVER['HTTP_USER_AGENT'];
8367 if (!empty($CFG->devicedetectregex)) {
8368 $regexes = json_decode($CFG->devicedetectregex);
8370 foreach ($regexes as $value=>$regex) {
8371 if (preg_match($regex, $useragent)) {
8372 return $value;
8377 //mobile detection PHP direct copy from open source detectmobilebrowser.com
8378 $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';
8379 $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';
8380 if (preg_match($phonesregex,$useragent) || preg_match($modelsregex,substr($useragent, 0, 4))){
8381 return 'mobile';
8384 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
8385 if (preg_match($tabletregex, $useragent)) {
8386 return 'tablet';
8389 // Safe way to check for IE6 and not get false positives for some IE 7/8 users
8390 if (substr($_SERVER['HTTP_USER_AGENT'], 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
8391 return 'legacy';
8394 return 'default';
8398 * Returns a list of the device types supporting by Moodle
8400 * @param boolean $incusertypes includes types specified using the devicedetectregex admin setting
8401 * @return array $types
8403 function get_device_type_list($incusertypes = true) {
8404 global $CFG;
8406 $types = array('default', 'legacy', 'mobile', 'tablet');
8408 if ($incusertypes && !empty($CFG->devicedetectregex)) {
8409 $regexes = json_decode($CFG->devicedetectregex);
8411 foreach ($regexes as $value => $regex) {
8412 $types[] = $value;
8416 return $types;
8420 * Returns the theme selected for a particular device or false if none selected.
8422 * @param string $devicetype
8423 * @return string|false The name of the theme to use for the device or the false if not set
8425 function get_selected_theme_for_device_type($devicetype = null) {
8426 global $CFG;
8428 if (empty($devicetype)) {
8429 $devicetype = get_user_device_type();
8432 $themevarname = get_device_cfg_var_name($devicetype);
8433 if (empty($CFG->$themevarname)) {
8434 return false;
8437 return $CFG->$themevarname;
8441 * Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
8443 * @param string $devicetype
8444 * @return string The config variable to use to determine the theme
8446 function get_device_cfg_var_name($devicetype = null) {
8447 if ($devicetype == 'default' || empty($devicetype)) {
8448 return 'theme';
8451 return 'theme' . $devicetype;
8455 * Allows the user to switch the device they are seeing the theme for.
8456 * This allows mobile users to switch back to the default theme, or theme for any other device.
8458 * @param string $newdevice The device the user is currently using.
8459 * @return string The device the user has switched to
8461 function set_user_device_type($newdevice) {
8462 global $USER;
8464 $devicetype = get_device_type();
8465 $devicetypes = get_device_type_list();
8467 if ($newdevice == $devicetype) {
8468 unset_user_preference('switchdevice'.$devicetype);
8469 } else if (in_array($newdevice, $devicetypes)) {
8470 set_user_preference('switchdevice'.$devicetype, $newdevice);
8475 * Returns the device the user is currently using, or if the user has chosen to switch devices
8476 * for the current device type the type they have switched to.
8478 * @return string The device the user is currently using or wishes to use
8480 function get_user_device_type() {
8481 $device = get_device_type();
8482 $switched = get_user_preferences('switchdevice'.$device, false);
8483 if ($switched != false) {
8484 return $switched;
8486 return $device;
8490 * Returns one or several CSS class names that match the user's browser. These can be put
8491 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
8493 * @return array An array of browser version classes
8495 function get_browser_version_classes() {
8496 $classes = array();
8498 if (check_browser_version("MSIE", "0")) {
8499 $classes[] = 'ie';
8500 if (check_browser_version("MSIE", 9)) {
8501 $classes[] = 'ie9';
8502 } else if (check_browser_version("MSIE", 8)) {
8503 $classes[] = 'ie8';
8504 } elseif (check_browser_version("MSIE", 7)) {
8505 $classes[] = 'ie7';
8506 } elseif (check_browser_version("MSIE", 6)) {
8507 $classes[] = 'ie6';
8510 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
8511 $classes[] = 'gecko';
8512 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
8513 $classes[] = "gecko{$matches[1]}{$matches[2]}";
8516 } else if (check_browser_version("WebKit")) {
8517 $classes[] = 'safari';
8518 if (check_browser_version("Safari iOS")) {
8519 $classes[] = 'ios';
8521 } else if (check_browser_version("WebKit Android")) {
8522 $classes[] = 'android';
8525 } else if (check_browser_version("Opera")) {
8526 $classes[] = 'opera';
8530 return $classes;
8534 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
8536 * @return bool True for yes, false for no
8538 function can_use_rotated_text() {
8539 global $USER;
8540 return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader;;
8544 * Hack to find out the GD version by parsing phpinfo output
8546 * @return int GD version (1, 2, or 0)
8548 function check_gd_version() {
8549 $gdversion = 0;
8551 if (function_exists('gd_info')){
8552 $gd_info = gd_info();
8553 if (substr_count($gd_info['GD Version'], '2.')) {
8554 $gdversion = 2;
8555 } else if (substr_count($gd_info['GD Version'], '1.')) {
8556 $gdversion = 1;
8559 } else {
8560 ob_start();
8561 phpinfo(INFO_MODULES);
8562 $phpinfo = ob_get_contents();
8563 ob_end_clean();
8565 $phpinfo = explode("\n", $phpinfo);
8568 foreach ($phpinfo as $text) {
8569 $parts = explode('</td>', $text);
8570 foreach ($parts as $key => $val) {
8571 $parts[$key] = trim(strip_tags($val));
8573 if ($parts[0] == 'GD Version') {
8574 if (substr_count($parts[1], '2.0')) {
8575 $parts[1] = '2.0';
8577 $gdversion = intval($parts[1]);
8582 return $gdversion; // 1, 2 or 0
8586 * Determine if moodle installation requires update
8588 * Checks version numbers of main code and all modules to see
8589 * if there are any mismatches
8591 * @global object
8592 * @global object
8593 * @return bool
8595 function moodle_needs_upgrading() {
8596 global $CFG, $DB, $OUTPUT;
8598 if (empty($CFG->version)) {
8599 return true;
8602 // main versio nfirst
8603 $version = null;
8604 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
8605 if ($version > $CFG->version) {
8606 return true;
8609 // modules
8610 $mods = get_plugin_list('mod');
8611 $installed = $DB->get_records('modules', array(), '', 'name, version');
8612 foreach ($mods as $mod => $fullmod) {
8613 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
8614 continue;
8616 $module = new stdClass();
8617 if (!is_readable($fullmod.'/version.php')) {
8618 continue;
8620 include($fullmod.'/version.php'); // defines $module with version etc
8621 if (empty($installed[$mod])) {
8622 return true;
8623 } else if ($module->version > $installed[$mod]->version) {
8624 return true;
8627 unset($installed);
8629 // blocks
8630 $blocks = get_plugin_list('block');
8631 $installed = $DB->get_records('block', array(), '', 'name, version');
8632 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
8633 foreach ($blocks as $blockname=>$fullblock) {
8634 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
8635 continue;
8637 if (!is_readable($fullblock.'/version.php')) {
8638 continue;
8640 $plugin = new stdClass();
8641 $plugin->version = NULL;
8642 include($fullblock.'/version.php');
8643 if (empty($installed[$blockname])) {
8644 return true;
8645 } else if ($plugin->version > $installed[$blockname]->version) {
8646 return true;
8649 unset($installed);
8651 // now the rest of plugins
8652 $plugintypes = get_plugin_types();
8653 unset($plugintypes['mod']);
8654 unset($plugintypes['block']);
8655 foreach ($plugintypes as $type=>$unused) {
8656 $plugs = get_plugin_list($type);
8657 foreach ($plugs as $plug=>$fullplug) {
8658 $component = $type.'_'.$plug;
8659 if (!is_readable($fullplug.'/version.php')) {
8660 continue;
8662 $plugin = new stdClass();
8663 include($fullplug.'/version.php'); // defines $plugin with version etc
8664 $installedversion = get_config($component, 'version');
8665 if (empty($installedversion)) { // new installation
8666 return true;
8667 } else if ($installedversion < $plugin->version) { // upgrade
8668 return true;
8673 return false;
8677 * Sets maximum expected time needed for upgrade task.
8678 * Please always make sure that upgrade will not run longer!
8680 * The script may be automatically aborted if upgrade times out.
8682 * @global object
8683 * @param int $max_execution_time in seconds (can not be less than 60 s)
8685 function upgrade_set_timeout($max_execution_time=300) {
8686 global $CFG;
8688 if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
8689 $upgraderunning = get_config(null, 'upgraderunning');
8690 } else {
8691 $upgraderunning = $CFG->upgraderunning;
8694 if (!$upgraderunning) {
8695 if (CLI_SCRIPT) {
8696 // never stop CLI upgrades
8697 $upgraderunning = 0;
8698 } else {
8699 // web upgrade not running or aborted
8700 print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
8704 if ($max_execution_time < 60) {
8705 // protection against 0 here
8706 $max_execution_time = 60;
8709 $expected_end = time() + $max_execution_time;
8711 if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
8712 // no need to store new end, it is nearly the same ;-)
8713 return;
8716 if (CLI_SCRIPT) {
8717 // there is no point in timing out of CLI scripts, admins can stop them if necessary
8718 set_time_limit(0);
8719 } else {
8720 set_time_limit($max_execution_time);
8722 set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
8725 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
8728 * Sets the system locale
8730 * @todo Finish documenting this function
8732 * @global object
8733 * @param string $locale Can be used to force a locale
8735 function moodle_setlocale($locale='') {
8736 global $CFG;
8738 static $currentlocale = ''; // last locale caching
8740 $oldlocale = $currentlocale;
8742 /// Fetch the correct locale based on ostype
8743 if ($CFG->ostype == 'WINDOWS') {
8744 $stringtofetch = 'localewin';
8745 } else {
8746 $stringtofetch = 'locale';
8749 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
8750 if (!empty($locale)) {
8751 $currentlocale = $locale;
8752 } else if (!empty($CFG->locale)) { // override locale for all language packs
8753 $currentlocale = $CFG->locale;
8754 } else {
8755 $currentlocale = get_string($stringtofetch, 'langconfig');
8758 /// do nothing if locale already set up
8759 if ($oldlocale == $currentlocale) {
8760 return;
8763 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
8764 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
8765 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
8767 /// Get current values
8768 $monetary= setlocale (LC_MONETARY, 0);
8769 $numeric = setlocale (LC_NUMERIC, 0);
8770 $ctype = setlocale (LC_CTYPE, 0);
8771 if ($CFG->ostype != 'WINDOWS') {
8772 $messages= setlocale (LC_MESSAGES, 0);
8774 /// Set locale to all
8775 setlocale (LC_ALL, $currentlocale);
8776 /// Set old values
8777 setlocale (LC_MONETARY, $monetary);
8778 setlocale (LC_NUMERIC, $numeric);
8779 if ($CFG->ostype != 'WINDOWS') {
8780 setlocale (LC_MESSAGES, $messages);
8782 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
8783 setlocale (LC_CTYPE, $ctype);
8788 * Converts string to lowercase using most compatible function available.
8790 * @todo Remove this function when no longer in use
8791 * @deprecated Use textlib->strtolower($text) instead.
8793 * @param string $string The string to convert to all lowercase characters.
8794 * @param string $encoding The encoding on the string.
8795 * @return string
8797 function moodle_strtolower ($string, $encoding='') {
8799 //If not specified use utf8
8800 if (empty($encoding)) {
8801 $encoding = 'UTF-8';
8803 //Use text services
8804 $textlib = textlib_get_instance();
8806 return $textlib->strtolower($string, $encoding);
8810 * Count words in a string.
8812 * Words are defined as things between whitespace.
8814 * @param string $string The text to be searched for words.
8815 * @return int The count of words in the specified string
8817 function count_words($string) {
8818 $string = strip_tags($string);
8819 return count(preg_split("/\w\b/", $string)) - 1;
8822 /** Count letters in a string.
8824 * Letters are defined as chars not in tags and different from whitespace.
8826 * @param string $string The text to be searched for letters.
8827 * @return int The count of letters in the specified text.
8829 function count_letters($string) {
8830 /// Loading the textlib singleton instance. We are going to need it.
8831 $textlib = textlib_get_instance();
8833 $string = strip_tags($string); // Tags are out now
8834 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
8836 return $textlib->strlen($string);
8840 * Generate and return a random string of the specified length.
8842 * @param int $length The length of the string to be created.
8843 * @return string
8845 function random_string ($length=15) {
8846 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8847 $pool .= 'abcdefghijklmnopqrstuvwxyz';
8848 $pool .= '0123456789';
8849 $poollen = strlen($pool);
8850 mt_srand ((double) microtime() * 1000000);
8851 $string = '';
8852 for ($i = 0; $i < $length; $i++) {
8853 $string .= substr($pool, (mt_rand()%($poollen)), 1);
8855 return $string;
8859 * Generate a complex random string (useful for md5 salts)
8861 * This function is based on the above {@link random_string()} however it uses a
8862 * larger pool of characters and generates a string between 24 and 32 characters
8864 * @param int $length Optional if set generates a string to exactly this length
8865 * @return string
8867 function complex_random_string($length=null) {
8868 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
8869 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
8870 $poollen = strlen($pool);
8871 mt_srand ((double) microtime() * 1000000);
8872 if ($length===null) {
8873 $length = floor(rand(24,32));
8875 $string = '';
8876 for ($i = 0; $i < $length; $i++) {
8877 $string .= $pool[(mt_rand()%$poollen)];
8879 return $string;
8883 * Given some text (which may contain HTML) and an ideal length,
8884 * this function truncates the text neatly on a word boundary if possible
8886 * @global object
8887 * @param string $text - text to be shortened
8888 * @param int $ideal - ideal string length
8889 * @param boolean $exact if false, $text will not be cut mid-word
8890 * @param string $ending The string to append if the passed string is truncated
8891 * @return string $truncate - shortened string
8893 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
8895 global $CFG;
8897 // if the plain text is shorter than the maximum length, return the whole text
8898 if (textlib::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
8899 return $text;
8902 // Splits on HTML tags. Each open/close/empty tag will be the first thing
8903 // and only tag in its 'line'
8904 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
8906 $total_length = textlib::strlen($ending);
8907 $truncate = '';
8909 // This array stores information about open and close tags and their position
8910 // in the truncated string. Each item in the array is an object with fields
8911 // ->open (true if open), ->tag (tag name in lower case), and ->pos
8912 // (byte position in truncated text)
8913 $tagdetails = array();
8915 foreach ($lines as $line_matchings) {
8916 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
8917 if (!empty($line_matchings[1])) {
8918 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
8919 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
8920 // do nothing
8921 // if tag is a closing tag (f.e. </b>)
8922 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
8923 // record closing tag
8924 $tagdetails[] = (object)array('open'=>false,
8925 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
8926 // if tag is an opening tag (f.e. <b>)
8927 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
8928 // record opening tag
8929 $tagdetails[] = (object)array('open'=>true,
8930 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
8932 // add html-tag to $truncate'd text
8933 $truncate .= $line_matchings[1];
8936 // calculate the length of the plain text part of the line; handle entities as one character
8937 $content_length = textlib::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
8938 if ($total_length+$content_length > $ideal) {
8939 // the number of characters which are left
8940 $left = $ideal - $total_length;
8941 $entities_length = 0;
8942 // search for html entities
8943 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)) {
8944 // calculate the real length of all entities in the legal range
8945 foreach ($entities[0] as $entity) {
8946 if ($entity[1]+1-$entities_length <= $left) {
8947 $left--;
8948 $entities_length += textlib::strlen($entity[0]);
8949 } else {
8950 // no more characters left
8951 break;
8955 $truncate .= textlib::substr($line_matchings[2], 0, $left+$entities_length);
8956 // maximum length is reached, so get off the loop
8957 break;
8958 } else {
8959 $truncate .= $line_matchings[2];
8960 $total_length += $content_length;
8963 // if the maximum length is reached, get off the loop
8964 if($total_length >= $ideal) {
8965 break;
8969 // if the words shouldn't be cut in the middle...
8970 if (!$exact) {
8971 // ...search the last occurence of a space...
8972 for ($k=textlib::strlen($truncate);$k>0;$k--) {
8973 if ($char = textlib::substr($truncate, $k, 1)) {
8974 if ($char === '.' or $char === ' ') {
8975 $breakpos = $k+1;
8976 break;
8977 } else if (strlen($char) > 2) { // Chinese/Japanese/Korean text
8978 $breakpos = $k+1; // can be truncated at any UTF-8
8979 break; // character boundary.
8984 if (isset($breakpos)) {
8985 // ...and cut the text in this position
8986 $truncate = textlib::substr($truncate, 0, $breakpos);
8990 // add the defined ending to the text
8991 $truncate .= $ending;
8993 // Now calculate the list of open html tags based on the truncate position
8994 $open_tags = array();
8995 foreach ($tagdetails as $taginfo) {
8996 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
8997 // Don't include tags after we made the break!
8998 break;
9000 if($taginfo->open) {
9001 // add tag to the beginning of $open_tags list
9002 array_unshift($open_tags, $taginfo->tag);
9003 } else {
9004 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
9005 if ($pos !== false) {
9006 unset($open_tags[$pos]);
9011 // close all unclosed html-tags
9012 foreach ($open_tags as $tag) {
9013 $truncate .= '</' . $tag . '>';
9016 return $truncate;
9021 * Given dates in seconds, how many weeks is the date from startdate
9022 * The first week is 1, the second 2 etc ...
9024 * @todo Finish documenting this function
9026 * @uses WEEKSECS
9027 * @param int $startdate Timestamp for the start date
9028 * @param int $thedate Timestamp for the end date
9029 * @return string
9031 function getweek ($startdate, $thedate) {
9032 if ($thedate < $startdate) { // error
9033 return 0;
9036 return floor(($thedate - $startdate) / WEEKSECS) + 1;
9040 * returns a randomly generated password of length $maxlen. inspired by
9042 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
9043 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
9045 * @global object
9046 * @param int $maxlen The maximum size of the password being generated.
9047 * @return string
9049 function generate_password($maxlen=10) {
9050 global $CFG;
9052 if (empty($CFG->passwordpolicy)) {
9053 $fillers = PASSWORD_DIGITS;
9054 $wordlist = file($CFG->wordlist);
9055 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9056 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9057 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
9058 $password = $word1 . $filler1 . $word2;
9059 } else {
9060 $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
9061 $digits = $CFG->minpassworddigits;
9062 $lower = $CFG->minpasswordlower;
9063 $upper = $CFG->minpasswordupper;
9064 $nonalphanum = $CFG->minpasswordnonalphanum;
9065 $total = $lower + $upper + $digits + $nonalphanum;
9066 // minlength should be the greater one of the two ( $minlen and $total )
9067 $minlen = $minlen < $total ? $total : $minlen;
9068 // maxlen can never be smaller than minlen
9069 $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
9070 $additional = $maxlen - $total;
9072 // Make sure we have enough characters to fulfill
9073 // complexity requirements
9074 $passworddigits = PASSWORD_DIGITS;
9075 while ($digits > strlen($passworddigits)) {
9076 $passworddigits .= PASSWORD_DIGITS;
9078 $passwordlower = PASSWORD_LOWER;
9079 while ($lower > strlen($passwordlower)) {
9080 $passwordlower .= PASSWORD_LOWER;
9082 $passwordupper = PASSWORD_UPPER;
9083 while ($upper > strlen($passwordupper)) {
9084 $passwordupper .= PASSWORD_UPPER;
9086 $passwordnonalphanum = PASSWORD_NONALPHANUM;
9087 while ($nonalphanum > strlen($passwordnonalphanum)) {
9088 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
9091 // Now mix and shuffle it all
9092 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
9093 substr(str_shuffle ($passwordupper), 0, $upper) .
9094 substr(str_shuffle ($passworddigits), 0, $digits) .
9095 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
9096 substr(str_shuffle ($passwordlower .
9097 $passwordupper .
9098 $passworddigits .
9099 $passwordnonalphanum), 0 , $additional));
9102 return substr ($password, 0, $maxlen);
9106 * Given a float, prints it nicely.
9107 * Localized floats must not be used in calculations!
9109 * @param float $float The float to print
9110 * @param int $places The number of decimal places to print.
9111 * @param bool $localized use localized decimal separator
9112 * @return string locale float
9114 function format_float($float, $decimalpoints=1, $localized=true) {
9115 if (is_null($float)) {
9116 return '';
9118 if ($localized) {
9119 return number_format($float, $decimalpoints, get_string('decsep', 'langconfig'), '');
9120 } else {
9121 return number_format($float, $decimalpoints, '.', '');
9126 * Converts locale specific floating point/comma number back to standard PHP float value
9127 * Do NOT try to do any math operations before this conversion on any user submitted floats!
9129 * @param string $locale_float locale aware float representation
9130 * @return float
9132 function unformat_float($locale_float) {
9133 $locale_float = trim($locale_float);
9135 if ($locale_float == '') {
9136 return null;
9139 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
9141 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
9145 * Given a simple array, this shuffles it up just like shuffle()
9146 * Unlike PHP's shuffle() this function works on any machine.
9148 * @param array $array The array to be rearranged
9149 * @return array
9151 function swapshuffle($array) {
9153 srand ((double) microtime() * 10000000);
9154 $last = count($array) - 1;
9155 for ($i=0;$i<=$last;$i++) {
9156 $from = rand(0,$last);
9157 $curr = $array[$i];
9158 $array[$i] = $array[$from];
9159 $array[$from] = $curr;
9161 return $array;
9165 * Like {@link swapshuffle()}, but works on associative arrays
9167 * @param array $array The associative array to be rearranged
9168 * @return array
9170 function swapshuffle_assoc($array) {
9172 $newarray = array();
9173 $newkeys = swapshuffle(array_keys($array));
9175 foreach ($newkeys as $newkey) {
9176 $newarray[$newkey] = $array[$newkey];
9178 return $newarray;
9182 * Given an arbitrary array, and a number of draws,
9183 * this function returns an array with that amount
9184 * of items. The indexes are retained.
9186 * @todo Finish documenting this function
9188 * @param array $array
9189 * @param int $draws
9190 * @return array
9192 function draw_rand_array($array, $draws) {
9193 srand ((double) microtime() * 10000000);
9195 $return = array();
9197 $last = count($array);
9199 if ($draws > $last) {
9200 $draws = $last;
9203 while ($draws > 0) {
9204 $last--;
9206 $keys = array_keys($array);
9207 $rand = rand(0, $last);
9209 $return[$keys[$rand]] = $array[$keys[$rand]];
9210 unset($array[$keys[$rand]]);
9212 $draws--;
9215 return $return;
9219 * Calculate the difference between two microtimes
9221 * @param string $a The first Microtime
9222 * @param string $b The second Microtime
9223 * @return string
9225 function microtime_diff($a, $b) {
9226 list($a_dec, $a_sec) = explode(' ', $a);
9227 list($b_dec, $b_sec) = explode(' ', $b);
9228 return $b_sec - $a_sec + $b_dec - $a_dec;
9232 * Given a list (eg a,b,c,d,e) this function returns
9233 * an array of 1->a, 2->b, 3->c etc
9235 * @param string $list The string to explode into array bits
9236 * @param string $separator The separator used within the list string
9237 * @return array The now assembled array
9239 function make_menu_from_list($list, $separator=',') {
9241 $array = array_reverse(explode($separator, $list), true);
9242 foreach ($array as $key => $item) {
9243 $outarray[$key+1] = trim($item);
9245 return $outarray;
9249 * Creates an array that represents all the current grades that
9250 * can be chosen using the given grading type.
9252 * Negative numbers
9253 * are scales, zero is no grade, and positive numbers are maximum
9254 * grades.
9256 * @todo Finish documenting this function or better deprecated this completely!
9258 * @param int $gradingtype
9259 * @return array
9261 function make_grades_menu($gradingtype) {
9262 global $DB;
9264 $grades = array();
9265 if ($gradingtype < 0) {
9266 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
9267 return make_menu_from_list($scale->scale);
9269 } else if ($gradingtype > 0) {
9270 for ($i=$gradingtype; $i>=0; $i--) {
9271 $grades[$i] = $i .' / '. $gradingtype;
9273 return $grades;
9275 return $grades;
9279 * This function returns the number of activities
9280 * using scaleid in a courseid
9282 * @todo Finish documenting this function
9284 * @global object
9285 * @global object
9286 * @param int $courseid ?
9287 * @param int $scaleid ?
9288 * @return int
9290 function course_scale_used($courseid, $scaleid) {
9291 global $CFG, $DB;
9293 $return = 0;
9295 if (!empty($scaleid)) {
9296 if ($cms = get_course_mods($courseid)) {
9297 foreach ($cms as $cm) {
9298 //Check cm->name/lib.php exists
9299 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
9300 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
9301 $function_name = $cm->modname.'_scale_used';
9302 if (function_exists($function_name)) {
9303 if ($function_name($cm->instance,$scaleid)) {
9304 $return++;
9311 // check if any course grade item makes use of the scale
9312 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
9314 // check if any outcome in the course makes use of the scale
9315 $return += $DB->count_records_sql("SELECT COUNT('x')
9316 FROM {grade_outcomes_courses} goc,
9317 {grade_outcomes} go
9318 WHERE go.id = goc.outcomeid
9319 AND go.scaleid = ? AND goc.courseid = ?",
9320 array($scaleid, $courseid));
9322 return $return;
9326 * This function returns the number of activities
9327 * using scaleid in the entire site
9329 * @param int $scaleid
9330 * @param array $courses
9331 * @return int
9333 function site_scale_used($scaleid, &$courses) {
9334 $return = 0;
9336 if (!is_array($courses) || count($courses) == 0) {
9337 $courses = get_courses("all",false,"c.id,c.shortname");
9340 if (!empty($scaleid)) {
9341 if (is_array($courses) && count($courses) > 0) {
9342 foreach ($courses as $course) {
9343 $return += course_scale_used($course->id,$scaleid);
9347 return $return;
9351 * make_unique_id_code
9353 * @todo Finish documenting this function
9355 * @uses $_SERVER
9356 * @param string $extra Extra string to append to the end of the code
9357 * @return string
9359 function make_unique_id_code($extra='') {
9361 $hostname = 'unknownhost';
9362 if (!empty($_SERVER['HTTP_HOST'])) {
9363 $hostname = $_SERVER['HTTP_HOST'];
9364 } else if (!empty($_ENV['HTTP_HOST'])) {
9365 $hostname = $_ENV['HTTP_HOST'];
9366 } else if (!empty($_SERVER['SERVER_NAME'])) {
9367 $hostname = $_SERVER['SERVER_NAME'];
9368 } else if (!empty($_ENV['SERVER_NAME'])) {
9369 $hostname = $_ENV['SERVER_NAME'];
9372 $date = gmdate("ymdHis");
9374 $random = random_string(6);
9376 if ($extra) {
9377 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
9378 } else {
9379 return $hostname .'+'. $date .'+'. $random;
9385 * Function to check the passed address is within the passed subnet
9387 * The parameter is a comma separated string of subnet definitions.
9388 * Subnet strings can be in one of three formats:
9389 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
9390 * 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)
9391 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
9392 * Code for type 1 modified from user posted comments by mediator at
9393 * {@link http://au.php.net/manual/en/function.ip2long.php}
9395 * @param string $addr The address you are checking
9396 * @param string $subnetstr The string of subnet addresses
9397 * @return bool
9399 function address_in_subnet($addr, $subnetstr) {
9401 if ($addr == '0.0.0.0') {
9402 return false;
9404 $subnets = explode(',', $subnetstr);
9405 $found = false;
9406 $addr = trim($addr);
9407 $addr = cleanremoteaddr($addr, false); // normalise
9408 if ($addr === null) {
9409 return false;
9411 $addrparts = explode(':', $addr);
9413 $ipv6 = strpos($addr, ':');
9415 foreach ($subnets as $subnet) {
9416 $subnet = trim($subnet);
9417 if ($subnet === '') {
9418 continue;
9421 if (strpos($subnet, '/') !== false) {
9422 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
9423 list($ip, $mask) = explode('/', $subnet);
9424 $mask = trim($mask);
9425 if (!is_number($mask)) {
9426 continue; // incorect mask number, eh?
9428 $ip = cleanremoteaddr($ip, false); // normalise
9429 if ($ip === null) {
9430 continue;
9432 if (strpos($ip, ':') !== false) {
9433 // IPv6
9434 if (!$ipv6) {
9435 continue;
9437 if ($mask > 128 or $mask < 0) {
9438 continue; // nonsense
9440 if ($mask == 0) {
9441 return true; // any address
9443 if ($mask == 128) {
9444 if ($ip === $addr) {
9445 return true;
9447 continue;
9449 $ipparts = explode(':', $ip);
9450 $modulo = $mask % 16;
9451 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
9452 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
9453 if (implode(':', $ipnet) === implode(':', $addrnet)) {
9454 if ($modulo == 0) {
9455 return true;
9457 $pos = ($mask-$modulo)/16;
9458 $ipnet = hexdec($ipparts[$pos]);
9459 $addrnet = hexdec($addrparts[$pos]);
9460 $mask = 0xffff << (16 - $modulo);
9461 if (($addrnet & $mask) == ($ipnet & $mask)) {
9462 return true;
9466 } else {
9467 // IPv4
9468 if ($ipv6) {
9469 continue;
9471 if ($mask > 32 or $mask < 0) {
9472 continue; // nonsense
9474 if ($mask == 0) {
9475 return true;
9477 if ($mask == 32) {
9478 if ($ip === $addr) {
9479 return true;
9481 continue;
9483 $mask = 0xffffffff << (32 - $mask);
9484 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
9485 return true;
9489 } else if (strpos($subnet, '-') !== false) {
9490 /// 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.
9491 $parts = explode('-', $subnet);
9492 if (count($parts) != 2) {
9493 continue;
9496 if (strpos($subnet, ':') !== false) {
9497 // IPv6
9498 if (!$ipv6) {
9499 continue;
9501 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9502 if ($ipstart === null) {
9503 continue;
9505 $ipparts = explode(':', $ipstart);
9506 $start = hexdec(array_pop($ipparts));
9507 $ipparts[] = trim($parts[1]);
9508 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
9509 if ($ipend === null) {
9510 continue;
9512 $ipparts[7] = '';
9513 $ipnet = implode(':', $ipparts);
9514 if (strpos($addr, $ipnet) !== 0) {
9515 continue;
9517 $ipparts = explode(':', $ipend);
9518 $end = hexdec($ipparts[7]);
9520 $addrend = hexdec($addrparts[7]);
9522 if (($addrend >= $start) and ($addrend <= $end)) {
9523 return true;
9526 } else {
9527 // IPv4
9528 if ($ipv6) {
9529 continue;
9531 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9532 if ($ipstart === null) {
9533 continue;
9535 $ipparts = explode('.', $ipstart);
9536 $ipparts[3] = trim($parts[1]);
9537 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
9538 if ($ipend === null) {
9539 continue;
9542 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
9543 return true;
9547 } else {
9548 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
9549 if (strpos($subnet, ':') !== false) {
9550 // IPv6
9551 if (!$ipv6) {
9552 continue;
9554 $parts = explode(':', $subnet);
9555 $count = count($parts);
9556 if ($parts[$count-1] === '') {
9557 unset($parts[$count-1]); // trim trailing :
9558 $count--;
9559 $subnet = implode('.', $parts);
9561 $isip = cleanremoteaddr($subnet, false); // normalise
9562 if ($isip !== null) {
9563 if ($isip === $addr) {
9564 return true;
9566 continue;
9567 } else if ($count > 8) {
9568 continue;
9570 $zeros = array_fill(0, 8-$count, '0');
9571 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
9572 if (address_in_subnet($addr, $subnet)) {
9573 return true;
9576 } else {
9577 // IPv4
9578 if ($ipv6) {
9579 continue;
9581 $parts = explode('.', $subnet);
9582 $count = count($parts);
9583 if ($parts[$count-1] === '') {
9584 unset($parts[$count-1]); // trim trailing .
9585 $count--;
9586 $subnet = implode('.', $parts);
9588 if ($count == 4) {
9589 $subnet = cleanremoteaddr($subnet, false); // normalise
9590 if ($subnet === $addr) {
9591 return true;
9593 continue;
9594 } else if ($count > 4) {
9595 continue;
9597 $zeros = array_fill(0, 4-$count, '0');
9598 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
9599 if (address_in_subnet($addr, $subnet)) {
9600 return true;
9606 return false;
9610 * For outputting debugging info
9612 * @uses STDOUT
9613 * @param string $string The string to write
9614 * @param string $eol The end of line char(s) to use
9615 * @param string $sleep Period to make the application sleep
9616 * This ensures any messages have time to display before redirect
9618 function mtrace($string, $eol="\n", $sleep=0) {
9620 if (defined('STDOUT')) {
9621 fwrite(STDOUT, $string.$eol);
9622 } else {
9623 echo $string . $eol;
9626 flush();
9628 //delay to keep message on user's screen in case of subsequent redirect
9629 if ($sleep) {
9630 sleep($sleep);
9635 * Replace 1 or more slashes or backslashes to 1 slash
9637 * @param string $path The path to strip
9638 * @return string the path with double slashes removed
9640 function cleardoubleslashes ($path) {
9641 return preg_replace('/(\/|\\\){1,}/','/',$path);
9645 * Is current ip in give list?
9647 * @param string $list
9648 * @return bool
9650 function remoteip_in_list($list){
9651 $inlist = false;
9652 $client_ip = getremoteaddr(null);
9654 if(!$client_ip){
9655 // ensure access on cli
9656 return true;
9659 $list = explode("\n", $list);
9660 foreach($list as $subnet) {
9661 $subnet = trim($subnet);
9662 if (address_in_subnet($client_ip, $subnet)) {
9663 $inlist = true;
9664 break;
9667 return $inlist;
9671 * Returns most reliable client address
9673 * @global object
9674 * @param string $default If an address can't be determined, then return this
9675 * @return string The remote IP address
9677 function getremoteaddr($default='0.0.0.0') {
9678 global $CFG;
9680 if (empty($CFG->getremoteaddrconf)) {
9681 // This will happen, for example, before just after the upgrade, as the
9682 // user is redirected to the admin screen.
9683 $variablestoskip = 0;
9684 } else {
9685 $variablestoskip = $CFG->getremoteaddrconf;
9687 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
9688 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
9689 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
9690 return $address ? $address : $default;
9693 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
9694 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
9695 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
9696 return $address ? $address : $default;
9699 if (!empty($_SERVER['REMOTE_ADDR'])) {
9700 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
9701 return $address ? $address : $default;
9702 } else {
9703 return $default;
9708 * Cleans an ip address. Internal addresses are now allowed.
9709 * (Originally local addresses were not allowed.)
9711 * @param string $addr IPv4 or IPv6 address
9712 * @param bool $compress use IPv6 address compression
9713 * @return string normalised ip address string, null if error
9715 function cleanremoteaddr($addr, $compress=false) {
9716 $addr = trim($addr);
9718 //TODO: maybe add a separate function is_addr_public() or something like this
9720 if (strpos($addr, ':') !== false) {
9721 // can be only IPv6
9722 $parts = explode(':', $addr);
9723 $count = count($parts);
9725 if (strpos($parts[$count-1], '.') !== false) {
9726 //legacy ipv4 notation
9727 $last = array_pop($parts);
9728 $ipv4 = cleanremoteaddr($last, true);
9729 if ($ipv4 === null) {
9730 return null;
9732 $bits = explode('.', $ipv4);
9733 $parts[] = dechex($bits[0]).dechex($bits[1]);
9734 $parts[] = dechex($bits[2]).dechex($bits[3]);
9735 $count = count($parts);
9736 $addr = implode(':', $parts);
9739 if ($count < 3 or $count > 8) {
9740 return null; // severly malformed
9743 if ($count != 8) {
9744 if (strpos($addr, '::') === false) {
9745 return null; // malformed
9747 // uncompress ::
9748 $insertat = array_search('', $parts, true);
9749 $missing = array_fill(0, 1 + 8 - $count, '0');
9750 array_splice($parts, $insertat, 1, $missing);
9751 foreach ($parts as $key=>$part) {
9752 if ($part === '') {
9753 $parts[$key] = '0';
9758 $adr = implode(':', $parts);
9759 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
9760 return null; // incorrect format - sorry
9763 // normalise 0s and case
9764 $parts = array_map('hexdec', $parts);
9765 $parts = array_map('dechex', $parts);
9767 $result = implode(':', $parts);
9769 if (!$compress) {
9770 return $result;
9773 if ($result === '0:0:0:0:0:0:0:0') {
9774 return '::'; // all addresses
9777 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
9778 if ($compressed !== $result) {
9779 return $compressed;
9782 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
9783 if ($compressed !== $result) {
9784 return $compressed;
9787 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
9788 if ($compressed !== $result) {
9789 return $compressed;
9792 return $result;
9795 // first get all things that look like IPv4 addresses
9796 $parts = array();
9797 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
9798 return null;
9800 unset($parts[0]);
9802 foreach ($parts as $key=>$match) {
9803 if ($match > 255) {
9804 return null;
9806 $parts[$key] = (int)$match; // normalise 0s
9809 return implode('.', $parts);
9813 * This function will make a complete copy of anything it's given,
9814 * regardless of whether it's an object or not.
9816 * @param mixed $thing Something you want cloned
9817 * @return mixed What ever it is you passed it
9819 function fullclone($thing) {
9820 return unserialize(serialize($thing));
9825 * This function expects to called during shutdown
9826 * should be set via register_shutdown_function()
9827 * in lib/setup.php .
9829 * @return void
9831 function moodle_request_shutdown() {
9832 global $CFG;
9834 // help apache server if possible
9835 $apachereleasemem = false;
9836 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
9837 && ini_get_bool('child_terminate')) {
9839 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
9840 if (memory_get_usage() > get_real_size($limit)) {
9841 $apachereleasemem = $limit;
9842 @apache_child_terminate();
9846 // deal with perf logging
9847 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
9848 if ($apachereleasemem) {
9849 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
9851 if (defined('MDL_PERFTOLOG')) {
9852 $perf = get_performance_info();
9853 error_log("PERF: " . $perf['txt']);
9855 if (defined('MDL_PERFINC')) {
9856 $inc = get_included_files();
9857 $ts = 0;
9858 foreach($inc as $f) {
9859 if (preg_match(':^/:', $f)) {
9860 $fs = filesize($f);
9861 $ts += $fs;
9862 $hfs = display_size($fs);
9863 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
9864 , NULL, NULL, 0);
9865 } else {
9866 error_log($f , NULL, NULL, 0);
9869 if ($ts > 0 ) {
9870 $hts = display_size($ts);
9871 error_log("Total size of files included: $ts ($hts)");
9878 * If new messages are waiting for the current user, then insert
9879 * JavaScript to pop up the messaging window into the page
9881 * @global moodle_page $PAGE
9882 * @return void
9884 function message_popup_window() {
9885 global $USER, $DB, $PAGE, $CFG, $SITE;
9887 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
9888 return;
9891 if (!isloggedin() || isguestuser()) {
9892 return;
9895 if (!isset($USER->message_lastpopup)) {
9896 $USER->message_lastpopup = 0;
9897 } else if ($USER->message_lastpopup > (time()-120)) {
9898 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
9899 return;
9902 //a quick query to check whether the user has new messages
9903 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
9904 if ($messagecount<1) {
9905 return;
9908 //got unread messages so now do another query that joins with the user table
9909 $messagesql = "SELECT m.id, m.smallmessage, m.fullmessageformat, m.notification, u.firstname, u.lastname
9910 FROM {message} m
9911 JOIN {message_working} mw ON m.id=mw.unreadmessageid
9912 JOIN {message_processors} p ON mw.processorid=p.id
9913 JOIN {user} u ON m.useridfrom=u.id
9914 WHERE m.useridto = :userid
9915 AND p.name='popup'";
9917 //if the user was last notified over an hour ago we can renotify them of old messages
9918 //so don't worry about when the new message was sent
9919 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
9920 if (!$lastnotifiedlongago) {
9921 $messagesql .= 'AND m.timecreated > :lastpopuptime';
9924 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
9926 //if we have new messages to notify the user about
9927 if (!empty($message_users)) {
9929 $strmessages = '';
9930 if (count($message_users)>1) {
9931 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
9932 } else {
9933 $message_users = reset($message_users);
9935 //show who the message is from if its not a notification
9936 if (!$message_users->notification) {
9937 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
9940 //try to display the small version of the message
9941 $smallmessage = null;
9942 if (!empty($message_users->smallmessage)) {
9943 //display the first 200 chars of the message in the popup
9944 $textlib = textlib_get_instance();
9945 $smallmessage = null;
9946 if ($textlib->strlen($message_users->smallmessage) > 200) {
9947 $smallmessage = $textlib->substr($message_users->smallmessage,0,200).'...';
9948 } else {
9949 $smallmessage = $message_users->smallmessage;
9952 //prevent html symbols being displayed
9953 if ($message_users->fullmessageformat == FORMAT_HTML) {
9954 $smallmessage = html_to_text($smallmessage);
9955 } else {
9956 $smallmessage = s($smallmessage);
9958 } else if ($message_users->notification) {
9959 //its a notification with no smallmessage so just say they have a notification
9960 $smallmessage = get_string('unreadnewnotification', 'message');
9962 if (!empty($smallmessage)) {
9963 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
9967 $strgomessage = get_string('gotomessages', 'message');
9968 $strstaymessage = get_string('ignore','admin');
9970 $url = $CFG->wwwroot.'/message/index.php';
9971 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
9972 html_writer::start_tag('div', array('id'=>'newmessagetext')).
9973 $strmessages.
9974 html_writer::end_tag('div').
9976 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
9977 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
9978 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
9979 html_writer::end_tag('div');
9980 html_writer::end_tag('div');
9982 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
9984 $USER->message_lastpopup = time();
9989 * Used to make sure that $min <= $value <= $max
9991 * Make sure that value is between min, and max
9993 * @param int $min The minimum value
9994 * @param int $value The value to check
9995 * @param int $max The maximum value
9997 function bounded_number($min, $value, $max) {
9998 if($value < $min) {
9999 return $min;
10001 if($value > $max) {
10002 return $max;
10004 return $value;
10008 * Check if there is a nested array within the passed array
10010 * @param array $array
10011 * @return bool true if there is a nested array false otherwise
10013 function array_is_nested($array) {
10014 foreach ($array as $value) {
10015 if (is_array($value)) {
10016 return true;
10019 return false;
10023 * get_performance_info() pairs up with init_performance_info()
10024 * loaded in setup.php. Returns an array with 'html' and 'txt'
10025 * values ready for use, and each of the individual stats provided
10026 * separately as well.
10028 * @global object
10029 * @global object
10030 * @global object
10031 * @return array
10033 function get_performance_info() {
10034 global $CFG, $PERF, $DB, $PAGE;
10036 $info = array();
10037 $info['html'] = ''; // holds userfriendly HTML representation
10038 $info['txt'] = me() . ' '; // holds log-friendly representation
10040 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
10042 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
10043 $info['txt'] .= 'time: '.$info['realtime'].'s ';
10045 if (function_exists('memory_get_usage')) {
10046 $info['memory_total'] = memory_get_usage();
10047 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
10048 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
10049 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
10052 if (function_exists('memory_get_peak_usage')) {
10053 $info['memory_peak'] = memory_get_peak_usage();
10054 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
10055 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
10058 $inc = get_included_files();
10059 //error_log(print_r($inc,1));
10060 $info['includecount'] = count($inc);
10061 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
10062 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
10064 $filtermanager = filter_manager::instance();
10065 if (method_exists($filtermanager, 'get_performance_summary')) {
10066 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
10067 $info = array_merge($filterinfo, $info);
10068 foreach ($filterinfo as $key => $value) {
10069 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10070 $info['txt'] .= "$key: $value ";
10074 $stringmanager = get_string_manager();
10075 if (method_exists($stringmanager, 'get_performance_summary')) {
10076 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
10077 $info = array_merge($filterinfo, $info);
10078 foreach ($filterinfo as $key => $value) {
10079 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10080 $info['txt'] .= "$key: $value ";
10084 $jsmodules = $PAGE->requires->get_loaded_modules();
10085 if ($jsmodules) {
10086 $yuicount = 0;
10087 $othercount = 0;
10088 $details = '';
10089 foreach ($jsmodules as $module => $backtraces) {
10090 if (strpos($module, 'yui') === 0) {
10091 $yuicount += 1;
10092 } else {
10093 $othercount += 1;
10095 $details .= "<div class='yui-module'><p>$module</p>";
10096 foreach ($backtraces as $backtrace) {
10097 $details .= "<div class='backtrace'>$backtrace</div>";
10099 $details .= '</div>';
10101 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
10102 $info['txt'] .= "includedyuimodules: $yuicount ";
10103 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
10104 $info['txt'] .= "includedjsmodules: $othercount ";
10105 // Slightly odd to output the details in a display: none div. The point
10106 // Is that it takes a lot of space, and if you care you can reveal it
10107 // using firebug.
10108 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
10111 if (!empty($PERF->logwrites)) {
10112 $info['logwrites'] = $PERF->logwrites;
10113 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
10114 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
10117 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
10118 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
10119 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
10121 if (function_exists('posix_times')) {
10122 $ptimes = posix_times();
10123 if (is_array($ptimes)) {
10124 foreach ($ptimes as $key => $val) {
10125 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
10127 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
10128 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
10132 // Grab the load average for the last minute
10133 // /proc will only work under some linux configurations
10134 // while uptime is there under MacOSX/Darwin and other unices
10135 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
10136 list($server_load) = explode(' ', $loadavg[0]);
10137 unset($loadavg);
10138 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
10139 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
10140 $server_load = $matches[1];
10141 } else {
10142 trigger_error('Could not parse uptime output!');
10145 if (!empty($server_load)) {
10146 $info['serverload'] = $server_load;
10147 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
10148 $info['txt'] .= "serverload: {$info['serverload']} ";
10151 // Display size of session if session started
10152 if (session_id()) {
10153 $info['sessionsize'] = display_size(strlen(session_encode()));
10154 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
10155 $info['txt'] .= "Session: {$info['sessionsize']} ";
10158 /* if (isset($rcache->hits) && isset($rcache->misses)) {
10159 $info['rcachehits'] = $rcache->hits;
10160 $info['rcachemisses'] = $rcache->misses;
10161 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
10162 "{$rcache->hits}/{$rcache->misses}</span> ";
10163 $info['txt'] .= 'rcache: '.
10164 "{$rcache->hits}/{$rcache->misses} ";
10166 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
10167 return $info;
10171 * @todo Document this function linux people
10173 function apd_get_profiling() {
10174 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
10178 * Delete directory or only it's content
10180 * @param string $dir directory path
10181 * @param bool $content_only
10182 * @return bool success, true also if dir does not exist
10184 function remove_dir($dir, $content_only=false) {
10185 if (!file_exists($dir)) {
10186 // nothing to do
10187 return true;
10189 $handle = opendir($dir);
10190 $result = true;
10191 while (false!==($item = readdir($handle))) {
10192 if($item != '.' && $item != '..') {
10193 if(is_dir($dir.'/'.$item)) {
10194 $result = remove_dir($dir.'/'.$item) && $result;
10195 }else{
10196 $result = unlink($dir.'/'.$item) && $result;
10200 closedir($handle);
10201 if ($content_only) {
10202 clearstatcache(); // make sure file stat cache is properly invalidated
10203 return $result;
10205 $result = rmdir($dir); // if anything left the result will be false, no need for && $result
10206 clearstatcache(); // make sure file stat cache is properly invalidated
10207 return $result;
10211 * Detect if an object or a class contains a given property
10212 * will take an actual object or the name of a class
10214 * @param mix $obj Name of class or real object to test
10215 * @param string $property name of property to find
10216 * @return bool true if property exists
10218 function object_property_exists( $obj, $property ) {
10219 if (is_string( $obj )) {
10220 $properties = get_class_vars( $obj );
10222 else {
10223 $properties = get_object_vars( $obj );
10225 return array_key_exists( $property, $properties );
10229 * Converts an object into an associative array
10231 * This function converts an object into an associative array by iterating
10232 * over its public properties. Because this function uses the foreach
10233 * construct, Iterators are respected. It works recursively on arrays of objects.
10234 * Arrays and simple values are returned as is.
10236 * If class has magic properties, it can implement IteratorAggregate
10237 * and return all available properties in getIterator()
10239 * @param mixed $var
10240 * @return array
10242 function convert_to_array($var) {
10243 $result = array();
10245 // loop over elements/properties
10246 foreach ($var as $key => $value) {
10247 // recursively convert objects
10248 if (is_object($value) || is_array($value)) {
10249 $result[$key] = convert_to_array($value);
10250 } else {
10251 // simple values are untouched
10252 $result[$key] = $value;
10255 return $result;
10259 * Detect a custom script replacement in the data directory that will
10260 * replace an existing moodle script
10262 * @return string|bool full path name if a custom script exists, false if no custom script exists
10264 function custom_script_path() {
10265 global $CFG, $SCRIPT;
10267 if ($SCRIPT === null) {
10268 // Probably some weird external script
10269 return false;
10272 $scriptpath = $CFG->customscripts . $SCRIPT;
10274 // check the custom script exists
10275 if (file_exists($scriptpath) and is_file($scriptpath)) {
10276 return $scriptpath;
10277 } else {
10278 return false;
10283 * Returns whether or not the user object is a remote MNET user. This function
10284 * is in moodlelib because it does not rely on loading any of the MNET code.
10286 * @global object
10287 * @param object $user A valid user object
10288 * @return bool True if the user is from a remote Moodle.
10290 function is_mnet_remote_user($user) {
10291 global $CFG;
10293 if (!isset($CFG->mnet_localhost_id)) {
10294 include_once $CFG->dirroot . '/mnet/lib.php';
10295 $env = new mnet_environment();
10296 $env->init();
10297 unset($env);
10300 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
10304 * This function will search for browser prefereed languages, setting Moodle
10305 * to use the best one available if $SESSION->lang is undefined
10307 * @global object
10308 * @global object
10309 * @global object
10311 function setup_lang_from_browser() {
10313 global $CFG, $SESSION, $USER;
10315 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
10316 // Lang is defined in session or user profile, nothing to do
10317 return;
10320 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
10321 return;
10324 /// Extract and clean langs from headers
10325 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
10326 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
10327 $rawlangs = explode(',', $rawlangs); // Convert to array
10328 $langs = array();
10330 $order = 1.0;
10331 foreach ($rawlangs as $lang) {
10332 if (strpos($lang, ';') === false) {
10333 $langs[(string)$order] = $lang;
10334 $order = $order-0.01;
10335 } else {
10336 $parts = explode(';', $lang);
10337 $pos = strpos($parts[1], '=');
10338 $langs[substr($parts[1], $pos+1)] = $parts[0];
10341 krsort($langs, SORT_NUMERIC);
10343 /// Look for such langs under standard locations
10344 foreach ($langs as $lang) {
10345 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
10346 if (get_string_manager()->translation_exists($lang, false)) {
10347 $SESSION->lang = $lang; /// Lang exists, set it in session
10348 break; /// We have finished. Go out
10351 return;
10355 * check if $url matches anything in proxybypass list
10357 * any errors just result in the proxy being used (least bad)
10359 * @global object
10360 * @param string $url url to check
10361 * @return boolean true if we should bypass the proxy
10363 function is_proxybypass( $url ) {
10364 global $CFG;
10366 // sanity check
10367 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
10368 return false;
10371 // get the host part out of the url
10372 if (!$host = parse_url( $url, PHP_URL_HOST )) {
10373 return false;
10376 // get the possible bypass hosts into an array
10377 $matches = explode( ',', $CFG->proxybypass );
10379 // check for a match
10380 // (IPs need to match the left hand side and hosts the right of the url,
10381 // but we can recklessly check both as there can't be a false +ve)
10382 $bypass = false;
10383 foreach ($matches as $match) {
10384 $match = trim($match);
10386 // try for IP match (Left side)
10387 $lhs = substr($host,0,strlen($match));
10388 if (strcasecmp($match,$lhs)==0) {
10389 return true;
10392 // try for host match (Right side)
10393 $rhs = substr($host,-strlen($match));
10394 if (strcasecmp($match,$rhs)==0) {
10395 return true;
10399 // nothing matched.
10400 return false;
10404 ////////////////////////////////////////////////////////////////////////////////
10407 * Check if the passed navigation is of the new style
10409 * @param mixed $navigation
10410 * @return bool true for yes false for no
10412 function is_newnav($navigation) {
10413 if (is_array($navigation) && !empty($navigation['newnav'])) {
10414 return true;
10415 } else {
10416 return false;
10421 * Checks whether the given variable name is defined as a variable within the given object.
10423 * This will NOT work with stdClass objects, which have no class variables.
10425 * @param string $var The variable name
10426 * @param object $object The object to check
10427 * @return boolean
10429 function in_object_vars($var, $object) {
10430 $class_vars = get_class_vars(get_class($object));
10431 $class_vars = array_keys($class_vars);
10432 return in_array($var, $class_vars);
10436 * Returns an array without repeated objects.
10437 * This function is similar to array_unique, but for arrays that have objects as values
10439 * @param array $array
10440 * @param bool $keep_key_assoc
10441 * @return array
10443 function object_array_unique($array, $keep_key_assoc = true) {
10444 $duplicate_keys = array();
10445 $tmp = array();
10447 foreach ($array as $key=>$val) {
10448 // convert objects to arrays, in_array() does not support objects
10449 if (is_object($val)) {
10450 $val = (array)$val;
10453 if (!in_array($val, $tmp)) {
10454 $tmp[] = $val;
10455 } else {
10456 $duplicate_keys[] = $key;
10460 foreach ($duplicate_keys as $key) {
10461 unset($array[$key]);
10464 return $keep_key_assoc ? $array : array_values($array);
10468 * Is a userid the primary administrator?
10470 * @param int $userid int id of user to check
10471 * @return boolean
10473 function is_primary_admin($userid){
10474 $primaryadmin = get_admin();
10476 if($userid == $primaryadmin->id){
10477 return true;
10478 }else{
10479 return false;
10484 * Returns the site identifier
10486 * @global object
10487 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
10489 function get_site_identifier() {
10490 global $CFG;
10491 // Check to see if it is missing. If so, initialise it.
10492 if (empty($CFG->siteidentifier)) {
10493 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
10495 // Return it.
10496 return $CFG->siteidentifier;
10500 * Check whether the given password has no more than the specified
10501 * number of consecutive identical characters.
10503 * @param string $password password to be checked against the password policy
10504 * @param integer $maxchars maximum number of consecutive identical characters
10506 function check_consecutive_identical_characters($password, $maxchars) {
10508 if ($maxchars < 1) {
10509 return true; // 0 is to disable this check
10511 if (strlen($password) <= $maxchars) {
10512 return true; // too short to fail this test
10515 $previouschar = '';
10516 $consecutivecount = 1;
10517 foreach (str_split($password) as $char) {
10518 if ($char != $previouschar) {
10519 $consecutivecount = 1;
10521 else {
10522 $consecutivecount++;
10523 if ($consecutivecount > $maxchars) {
10524 return false; // check failed already
10528 $previouschar = $char;
10531 return true;
10535 * helper function to do partial function binding
10536 * so we can use it for preg_replace_callback, for example
10537 * this works with php functions, user functions, static methods and class methods
10538 * it returns you a callback that you can pass on like so:
10540 * $callback = partial('somefunction', $arg1, $arg2);
10541 * or
10542 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
10543 * or even
10544 * $obj = new someclass();
10545 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
10547 * and then the arguments that are passed through at calltime are appended to the argument list.
10549 * @param mixed $function a php callback
10550 * $param mixed $arg1.. $argv arguments to partially bind with
10552 * @return callback
10554 function partial() {
10555 if (!class_exists('partial')) {
10556 class partial{
10557 var $values = array();
10558 var $func;
10560 function __construct($func, $args) {
10561 $this->values = $args;
10562 $this->func = $func;
10565 function method() {
10566 $args = func_get_args();
10567 return call_user_func_array($this->func, array_merge($this->values, $args));
10571 $args = func_get_args();
10572 $func = array_shift($args);
10573 $p = new partial($func, $args);
10574 return array($p, 'method');
10578 * helper function to load up and initialise the mnet environment
10579 * this must be called before you use mnet functions.
10581 * @return mnet_environment the equivalent of old $MNET global
10583 function get_mnet_environment() {
10584 global $CFG;
10585 require_once($CFG->dirroot . '/mnet/lib.php');
10586 static $instance = null;
10587 if (empty($instance)) {
10588 $instance = new mnet_environment();
10589 $instance->init();
10591 return $instance;
10595 * during xmlrpc server code execution, any code wishing to access
10596 * information about the remote peer must use this to get it.
10598 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
10600 function get_mnet_remote_client() {
10601 if (!defined('MNET_SERVER')) {
10602 debugging(get_string('notinxmlrpcserver', 'mnet'));
10603 return false;
10605 global $MNET_REMOTE_CLIENT;
10606 if (isset($MNET_REMOTE_CLIENT)) {
10607 return $MNET_REMOTE_CLIENT;
10609 return false;
10613 * during the xmlrpc server code execution, this will be called
10614 * to setup the object returned by {@see get_mnet_remote_client}
10616 * @param mnet_remote_client $client the client to set up
10618 function set_mnet_remote_client($client) {
10619 if (!defined('MNET_SERVER')) {
10620 throw new moodle_exception('notinxmlrpcserver', 'mnet');
10622 global $MNET_REMOTE_CLIENT;
10623 $MNET_REMOTE_CLIENT = $client;
10627 * return the jump url for a given remote user
10628 * this is used for rewriting forum post links in emails, etc
10630 * @param stdclass $user the user to get the idp url for
10632 function mnet_get_idp_jump_url($user) {
10633 global $CFG;
10635 static $mnetjumps = array();
10636 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
10637 $idp = mnet_get_peer_host($user->mnethostid);
10638 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
10639 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
10641 return $mnetjumps[$user->mnethostid];
10645 * Gets the homepage to use for the current user
10647 * @return int One of HOMEPAGE_*
10649 function get_home_page() {
10650 global $CFG;
10652 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
10653 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
10654 return HOMEPAGE_MY;
10655 } else {
10656 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
10659 return HOMEPAGE_SITE;
10663 * Gets the name of a course to be displayed when showing a list of courses.
10664 * By default this is just $course->fullname but user can configure it. The
10665 * result of this function should be passed through print_string.
10666 * @param object $course Moodle course object
10667 * @return string Display name of course (either fullname or short + fullname)
10669 function get_course_display_name_for_list($course) {
10670 global $CFG;
10671 if (!empty($CFG->courselistshortnames)) {
10672 return get_string('courseextendednamedisplay', '', $course);
10673 } else {
10674 return $course->fullname;