MDL-35669 gravatar Provide default image URL to Gravatar
[moodle.git] / lib / moodlelib.php
blob7e2eebe355c6032f4f0ad6804a5023862ac3b47a
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * moodlelib.php - Moodle main library
20 * Main library file of miscellaneous general-purpose Moodle functions.
21 * Other main libraries:
22 * - weblib.php - functions that produce web output
23 * - datalib.php - functions that access the database
25 * @package core
26 * @subpackage lib
27 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31 defined('MOODLE_INTERNAL') || die();
33 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
35 /// Date and time constants ///
36 /**
37 * Time constant - the number of seconds in a year
39 define('YEARSECS', 31536000);
41 /**
42 * Time constant - the number of seconds in a week
44 define('WEEKSECS', 604800);
46 /**
47 * Time constant - the number of seconds in a day
49 define('DAYSECS', 86400);
51 /**
52 * Time constant - the number of seconds in an hour
54 define('HOURSECS', 3600);
56 /**
57 * Time constant - the number of seconds in a minute
59 define('MINSECS', 60);
61 /**
62 * Time constant - the number of minutes in a day
64 define('DAYMINS', 1440);
66 /**
67 * Time constant - the number of minutes in an hour
69 define('HOURMINS', 60);
71 /// Parameter constants - every call to optional_param(), required_param() ///
72 /// or clean_param() should have a specified type of parameter. //////////////
76 /**
77 * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
79 define('PARAM_ALPHA', 'alpha');
81 /**
82 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "_-" allowed
83 * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
85 define('PARAM_ALPHAEXT', 'alphaext');
87 /**
88 * PARAM_ALPHANUM - expected numbers and letters only.
90 define('PARAM_ALPHANUM', 'alphanum');
92 /**
93 * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
95 define('PARAM_ALPHANUMEXT', 'alphanumext');
97 /**
98 * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
100 define('PARAM_AUTH', 'auth');
103 * PARAM_BASE64 - Base 64 encoded format
105 define('PARAM_BASE64', 'base64');
108 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
110 define('PARAM_BOOL', 'bool');
113 * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
114 * checked against the list of capabilities in the database.
116 define('PARAM_CAPABILITY', 'capability');
119 * PARAM_CLEANHTML - cleans submitted HTML code. use only for text in HTML format. This cleaning may fix xhtml strictness too.
121 define('PARAM_CLEANHTML', 'cleanhtml');
124 * PARAM_EMAIL - an email address following the RFC
126 define('PARAM_EMAIL', 'email');
129 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
131 define('PARAM_FILE', 'file');
134 * PARAM_FLOAT - a real/floating point number.
136 * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
137 * It does not work for languages that use , as a decimal separator.
138 * Instead, do something like
139 * $rawvalue = required_param('name', PARAM_RAW);
140 * // ... other code including require_login, which sets current lang ...
141 * $realvalue = unformat_float($rawvalue);
142 * // ... then use $realvalue
144 define('PARAM_FLOAT', 'float');
147 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
149 define('PARAM_HOST', 'host');
152 * PARAM_INT - integers only, use when expecting only numbers.
154 define('PARAM_INT', 'int');
157 * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
159 define('PARAM_LANG', 'lang');
162 * 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!)
164 define('PARAM_LOCALURL', 'localurl');
167 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
169 define('PARAM_NOTAGS', 'notags');
172 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
173 * note: the leading slash is not removed, window drive letter is not allowed
175 define('PARAM_PATH', 'path');
178 * PARAM_PEM - Privacy Enhanced Mail format
180 define('PARAM_PEM', 'pem');
183 * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
185 define('PARAM_PERMISSION', 'permission');
188 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
190 define('PARAM_RAW', 'raw');
193 * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
195 define('PARAM_RAW_TRIMMED', 'raw_trimmed');
198 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
200 define('PARAM_SAFEDIR', 'safedir');
203 * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
205 define('PARAM_SAFEPATH', 'safepath');
208 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
210 define('PARAM_SEQUENCE', 'sequence');
213 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
215 define('PARAM_TAG', 'tag');
218 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
220 define('PARAM_TAGLIST', 'taglist');
223 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
225 define('PARAM_TEXT', 'text');
228 * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
230 define('PARAM_THEME', 'theme');
233 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
235 define('PARAM_URL', 'url');
238 * 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!!
240 define('PARAM_USERNAME', 'username');
243 * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
245 define('PARAM_STRINGID', 'stringid');
247 ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
249 * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
250 * It was one of the first types, that is why it is abused so much ;-)
251 * @deprecated since 2.0
253 define('PARAM_CLEAN', 'clean');
256 * PARAM_INTEGER - deprecated alias for PARAM_INT
257 * @deprecated since 2.0
259 define('PARAM_INTEGER', 'int');
262 * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
263 * @deprecated since 2.0
265 define('PARAM_NUMBER', 'float');
268 * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
269 * NOTE: originally alias for PARAM_APLHA
270 * @deprecated since 2.0
272 define('PARAM_ACTION', 'alphanumext');
275 * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
276 * NOTE: originally alias for PARAM_APLHA
277 * @deprecated since 2.0
279 define('PARAM_FORMAT', 'alphanumext');
282 * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
283 * @deprecated since 2.0
285 define('PARAM_MULTILANG', 'text');
288 * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
289 * string seperated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
290 * America/Port-au-Prince)
292 define('PARAM_TIMEZONE', 'timezone');
295 * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
297 define('PARAM_CLEANFILE', 'file');
300 * PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
301 * Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
302 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
303 * NOTE: numbers and underscores are strongly discouraged in plugin names!
305 define('PARAM_COMPONENT', 'component');
308 * PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
309 * It is usually used together with context id and component.
310 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
312 define('PARAM_AREA', 'area');
315 * PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'radius', 'paypal', 'completionstatus'.
316 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
317 * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
319 define('PARAM_PLUGIN', 'plugin');
322 /// Web Services ///
325 * VALUE_REQUIRED - if the parameter is not supplied, there is an error
327 define('VALUE_REQUIRED', 1);
330 * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
332 define('VALUE_OPTIONAL', 2);
335 * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
337 define('VALUE_DEFAULT', 0);
340 * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
342 define('NULL_NOT_ALLOWED', false);
345 * NULL_ALLOWED - the parameter can be set to null in the database
347 define('NULL_ALLOWED', true);
349 /// Page types ///
351 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
353 define('PAGE_COURSE_VIEW', 'course-view');
355 /** Get remote addr constant */
356 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
357 /** Get remote addr constant */
358 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
360 /// Blog access level constant declaration ///
361 define ('BLOG_USER_LEVEL', 1);
362 define ('BLOG_GROUP_LEVEL', 2);
363 define ('BLOG_COURSE_LEVEL', 3);
364 define ('BLOG_SITE_LEVEL', 4);
365 define ('BLOG_GLOBAL_LEVEL', 5);
368 ///Tag constants///
370 * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
371 * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
372 * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
374 * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
376 define('TAG_MAX_LENGTH', 50);
378 /// Password policy constants ///
379 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
380 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
381 define ('PASSWORD_DIGITS', '0123456789');
382 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
384 /// Feature constants ///
385 // Used for plugin_supports() to report features that are, or are not, supported by a module.
387 /** True if module can provide a grade */
388 define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
389 /** True if module supports outcomes */
390 define('FEATURE_GRADE_OUTCOMES', 'outcomes');
391 /** True if module supports advanced grading methods */
392 define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
393 /** True if module controls the grade visibility over the gradebook */
394 define('FEATURE_CONTROLS_GRADE_VISIBILITY', 'controlsgradevisbility');
395 /** True if module supports plagiarism plugins */
396 define('FEATURE_PLAGIARISM', 'plagiarism');
398 /** True if module has code to track whether somebody viewed it */
399 define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
400 /** True if module has custom completion rules */
401 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
403 /** True if module has no 'view' page (like label) */
404 define('FEATURE_NO_VIEW_LINK', 'viewlink');
405 /** True if module supports outcomes */
406 define('FEATURE_IDNUMBER', 'idnumber');
407 /** True if module supports groups */
408 define('FEATURE_GROUPS', 'groups');
409 /** True if module supports groupings */
410 define('FEATURE_GROUPINGS', 'groupings');
411 /** True if module supports groupmembersonly */
412 define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
414 /** Type of module */
415 define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
416 /** True if module supports intro editor */
417 define('FEATURE_MOD_INTRO', 'mod_intro');
418 /** True if module has default completion */
419 define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
421 define('FEATURE_COMMENT', 'comment');
423 define('FEATURE_RATE', 'rate');
424 /** True if module supports backup/restore of moodle2 format */
425 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
427 /** True if module can show description on course main page */
428 define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
430 /** Unspecified module archetype */
431 define('MOD_ARCHETYPE_OTHER', 0);
432 /** Resource-like type module */
433 define('MOD_ARCHETYPE_RESOURCE', 1);
434 /** Assignment module archetype */
435 define('MOD_ARCHETYPE_ASSIGNMENT', 2);
436 /** System (not user-addable) module archetype */
437 define('MOD_ARCHETYPE_SYSTEM', 3);
440 * Security token used for allowing access
441 * from external application such as web services.
442 * Scripts do not use any session, performance is relatively
443 * low because we need to load access info in each request.
444 * Scripts are executed in parallel.
446 define('EXTERNAL_TOKEN_PERMANENT', 0);
449 * Security token used for allowing access
450 * of embedded applications, the code is executed in the
451 * active user session. Token is invalidated after user logs out.
452 * Scripts are executed serially - normal session locking is used.
454 define('EXTERNAL_TOKEN_EMBEDDED', 1);
457 * The home page should be the site home
459 define('HOMEPAGE_SITE', 0);
461 * The home page should be the users my page
463 define('HOMEPAGE_MY', 1);
465 * The home page can be chosen by the user
467 define('HOMEPAGE_USER', 2);
470 * Hub directory url (should be moodle.org)
472 define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
476 * Moodle.org url (should be moodle.org)
478 define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
481 * Moodle mobile app service name
483 define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
486 * Indicates the user has the capabilities required to ignore activity and course file size restrictions
488 define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1);
491 * Course display settings
493 define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page
494 define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section
496 /// PARAMETER HANDLING ////////////////////////////////////////////////////
499 * Returns a particular value for the named variable, taken from
500 * POST or GET. If the parameter doesn't exist then an error is
501 * thrown because we require this variable.
503 * This function should be used to initialise all required values
504 * in a script that are based on parameters. Usually it will be
505 * used like this:
506 * $id = required_param('id', PARAM_INT);
508 * Please note the $type parameter is now required and the value can not be array.
510 * @param string $parname the name of the page parameter we want
511 * @param string $type expected type of parameter
512 * @return mixed
514 function required_param($parname, $type) {
515 if (func_num_args() != 2 or empty($parname) or empty($type)) {
516 throw new coding_exception('required_param() requires $parname and $type to be specified (parameter: '.$parname.')');
518 if (isset($_POST[$parname])) { // POST has precedence
519 $param = $_POST[$parname];
520 } else if (isset($_GET[$parname])) {
521 $param = $_GET[$parname];
522 } else {
523 print_error('missingparam', '', '', $parname);
526 if (is_array($param)) {
527 debugging('Invalid array parameter detected in required_param(): '.$parname);
528 // TODO: switch to fatal error in Moodle 2.3
529 //print_error('missingparam', '', '', $parname);
530 return required_param_array($parname, $type);
533 return clean_param($param, $type);
537 * Returns a particular array value for the named variable, taken from
538 * POST or GET. If the parameter doesn't exist then an error is
539 * thrown because we require this variable.
541 * This function should be used to initialise all required values
542 * in a script that are based on parameters. Usually it will be
543 * used like this:
544 * $ids = required_param_array('ids', PARAM_INT);
546 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
548 * @param string $parname the name of the page parameter we want
549 * @param string $type expected type of parameter
550 * @return array
552 function required_param_array($parname, $type) {
553 if (func_num_args() != 2 or empty($parname) or empty($type)) {
554 throw new coding_exception('required_param_array() requires $parname and $type to be specified (parameter: '.$parname.')');
556 if (isset($_POST[$parname])) { // POST has precedence
557 $param = $_POST[$parname];
558 } else if (isset($_GET[$parname])) {
559 $param = $_GET[$parname];
560 } else {
561 print_error('missingparam', '', '', $parname);
563 if (!is_array($param)) {
564 print_error('missingparam', '', '', $parname);
567 $result = array();
568 foreach($param as $key=>$value) {
569 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
570 debugging('Invalid key name in required_param_array() detected: '.$key.', parameter: '.$parname);
571 continue;
573 $result[$key] = clean_param($value, $type);
576 return $result;
580 * Returns a particular value for the named variable, taken from
581 * POST or GET, otherwise returning a given default.
583 * This function should be used to initialise all optional values
584 * in a script that are based on parameters. Usually it will be
585 * used like this:
586 * $name = optional_param('name', 'Fred', PARAM_TEXT);
588 * Please note the $type parameter is now required and the value can not be array.
590 * @param string $parname the name of the page parameter we want
591 * @param mixed $default the default value to return if nothing is found
592 * @param string $type expected type of parameter
593 * @return mixed
595 function optional_param($parname, $default, $type) {
596 if (func_num_args() != 3 or empty($parname) or empty($type)) {
597 throw new coding_exception('optional_param() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
599 if (!isset($default)) {
600 $default = null;
603 if (isset($_POST[$parname])) { // POST has precedence
604 $param = $_POST[$parname];
605 } else if (isset($_GET[$parname])) {
606 $param = $_GET[$parname];
607 } else {
608 return $default;
611 if (is_array($param)) {
612 debugging('Invalid array parameter detected in required_param(): '.$parname);
613 // TODO: switch to $default in Moodle 2.3
614 //return $default;
615 return optional_param_array($parname, $default, $type);
618 return clean_param($param, $type);
622 * Returns a particular array value for the named variable, taken from
623 * POST or GET, otherwise returning a given default.
625 * This function should be used to initialise all optional values
626 * in a script that are based on parameters. Usually it will be
627 * used like this:
628 * $ids = optional_param('id', array(), PARAM_INT);
630 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
632 * @param string $parname the name of the page parameter we want
633 * @param mixed $default the default value to return if nothing is found
634 * @param string $type expected type of parameter
635 * @return array
637 function optional_param_array($parname, $default, $type) {
638 if (func_num_args() != 3 or empty($parname) or empty($type)) {
639 throw new coding_exception('optional_param_array() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
642 if (isset($_POST[$parname])) { // POST has precedence
643 $param = $_POST[$parname];
644 } else if (isset($_GET[$parname])) {
645 $param = $_GET[$parname];
646 } else {
647 return $default;
649 if (!is_array($param)) {
650 debugging('optional_param_array() expects array parameters only: '.$parname);
651 return $default;
654 $result = array();
655 foreach($param as $key=>$value) {
656 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
657 debugging('Invalid key name in optional_param_array() detected: '.$key.', parameter: '.$parname);
658 continue;
660 $result[$key] = clean_param($value, $type);
663 return $result;
667 * Strict validation of parameter values, the values are only converted
668 * to requested PHP type. Internally it is using clean_param, the values
669 * before and after cleaning must be equal - otherwise
670 * an invalid_parameter_exception is thrown.
671 * Objects and classes are not accepted.
673 * @param mixed $param
674 * @param string $type PARAM_ constant
675 * @param bool $allownull are nulls valid value?
676 * @param string $debuginfo optional debug information
677 * @return mixed the $param value converted to PHP type
678 * @throws invalid_parameter_exception if $param is not of given type
680 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
681 if (is_null($param)) {
682 if ($allownull == NULL_ALLOWED) {
683 return null;
684 } else {
685 throw new invalid_parameter_exception($debuginfo);
688 if (is_array($param) or is_object($param)) {
689 throw new invalid_parameter_exception($debuginfo);
692 $cleaned = clean_param($param, $type);
694 if ($type == PARAM_FLOAT) {
695 // Do not detect precision loss here.
696 if (is_float($param) or is_int($param)) {
697 // These always fit.
698 } else if (!is_numeric($param) or !preg_match('/^[\+-]?[0-9]*\.?[0-9]*(e[-+]?[0-9]+)?$/i', (string)$param)) {
699 throw new invalid_parameter_exception($debuginfo);
701 } else if ((string)$param !== (string)$cleaned) {
702 // conversion to string is usually lossless
703 throw new invalid_parameter_exception($debuginfo);
706 return $cleaned;
710 * Makes sure array contains only the allowed types,
711 * this function does not validate array key names!
712 * <code>
713 * $options = clean_param($options, PARAM_INT);
714 * </code>
716 * @param array $param the variable array we are cleaning
717 * @param string $type expected format of param after cleaning.
718 * @param bool $recursive clean recursive arrays
719 * @return array
721 function clean_param_array(array $param = null, $type, $recursive = false) {
722 $param = (array)$param; // convert null to empty array
723 foreach ($param as $key => $value) {
724 if (is_array($value)) {
725 if ($recursive) {
726 $param[$key] = clean_param_array($value, $type, true);
727 } else {
728 throw new coding_exception('clean_param_array() can not process multidimensional arrays when $recursive is false.');
730 } else {
731 $param[$key] = clean_param($value, $type);
734 return $param;
738 * Used by {@link optional_param()} and {@link required_param()} to
739 * clean the variables and/or cast to specific types, based on
740 * an options field.
741 * <code>
742 * $course->format = clean_param($course->format, PARAM_ALPHA);
743 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
744 * </code>
746 * @param mixed $param the variable we are cleaning
747 * @param string $type expected format of param after cleaning.
748 * @return mixed
750 function clean_param($param, $type) {
752 global $CFG;
754 if (is_array($param)) {
755 throw new coding_exception('clean_param() can not process arrays, please use clean_param_array() instead.');
756 } else if (is_object($param)) {
757 if (method_exists($param, '__toString')) {
758 $param = $param->__toString();
759 } else {
760 throw new coding_exception('clean_param() can not process objects, please use clean_param_array() instead.');
764 switch ($type) {
765 case PARAM_RAW: // no cleaning at all
766 $param = fix_utf8($param);
767 return $param;
769 case PARAM_RAW_TRIMMED: // no cleaning, but strip leading and trailing whitespace.
770 $param = fix_utf8($param);
771 return trim($param);
773 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
774 // this is deprecated!, please use more specific type instead
775 if (is_numeric($param)) {
776 return $param;
778 $param = fix_utf8($param);
779 return clean_text($param); // Sweep for scripts, etc
781 case PARAM_CLEANHTML: // clean html fragment
782 $param = fix_utf8($param);
783 $param = clean_text($param, FORMAT_HTML); // Sweep for scripts, etc
784 return trim($param);
786 case PARAM_INT:
787 return (int)$param; // Convert to integer
789 case PARAM_FLOAT:
790 return (float)$param; // Convert to float
792 case PARAM_ALPHA: // Remove everything not a-z
793 return preg_replace('/[^a-zA-Z]/i', '', $param);
795 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z_- (originally allowed "/" too)
796 return preg_replace('/[^a-zA-Z_-]/i', '', $param);
798 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
799 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
801 case PARAM_ALPHANUMEXT: // Remove everything not a-zA-Z0-9_-
802 return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
804 case PARAM_SEQUENCE: // Remove everything not 0-9,
805 return preg_replace('/[^0-9,]/i', '', $param);
807 case PARAM_BOOL: // Convert to 1 or 0
808 $tempstr = strtolower($param);
809 if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
810 $param = 1;
811 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
812 $param = 0;
813 } else {
814 $param = empty($param) ? 0 : 1;
816 return $param;
818 case PARAM_NOTAGS: // Strip all tags
819 $param = fix_utf8($param);
820 return strip_tags($param);
822 case PARAM_TEXT: // leave only tags needed for multilang
823 $param = fix_utf8($param);
824 // if the multilang syntax is not correct we strip all tags
825 // because it would break xhtml strict which is required for accessibility standards
826 // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
827 do {
828 if (strpos($param, '</lang>') !== false) {
829 // old and future mutilang syntax
830 $param = strip_tags($param, '<lang>');
831 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
832 break;
834 $open = false;
835 foreach ($matches[0] as $match) {
836 if ($match === '</lang>') {
837 if ($open) {
838 $open = false;
839 continue;
840 } else {
841 break 2;
844 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
845 break 2;
846 } else {
847 $open = true;
850 if ($open) {
851 break;
853 return $param;
855 } else if (strpos($param, '</span>') !== false) {
856 // current problematic multilang syntax
857 $param = strip_tags($param, '<span>');
858 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
859 break;
861 $open = false;
862 foreach ($matches[0] as $match) {
863 if ($match === '</span>') {
864 if ($open) {
865 $open = false;
866 continue;
867 } else {
868 break 2;
871 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
872 break 2;
873 } else {
874 $open = true;
877 if ($open) {
878 break;
880 return $param;
882 } while (false);
883 // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
884 return strip_tags($param);
886 case PARAM_COMPONENT:
887 // we do not want any guessing here, either the name is correct or not
888 // please note only normalised component names are accepted
889 if (!preg_match('/^[a-z]+(_[a-z][a-z0-9_]*)?[a-z0-9]$/', $param)) {
890 return '';
892 if (strpos($param, '__') !== false) {
893 return '';
895 if (strpos($param, 'mod_') === 0) {
896 // module names must not contain underscores because we need to differentiate them from invalid plugin types
897 if (substr_count($param, '_') != 1) {
898 return '';
901 return $param;
903 case PARAM_PLUGIN:
904 case PARAM_AREA:
905 // we do not want any guessing here, either the name is correct or not
906 if (!preg_match('/^[a-z][a-z0-9_]*[a-z0-9]$/', $param)) {
907 return '';
909 if (strpos($param, '__') !== false) {
910 return '';
912 return $param;
914 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
915 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
917 case PARAM_SAFEPATH: // Remove everything not a-zA-Z0-9/_-
918 return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
920 case PARAM_FILE: // Strip all suspicious characters from filename
921 $param = fix_utf8($param);
922 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
923 $param = preg_replace('~\.\.+~', '', $param);
924 if ($param === '.') {
925 $param = '';
927 return $param;
929 case PARAM_PATH: // Strip all suspicious characters from file path
930 $param = fix_utf8($param);
931 $param = str_replace('\\', '/', $param);
932 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
933 $param = preg_replace('~\.\.+~', '', $param);
934 $param = preg_replace('~//+~', '/', $param);
935 return preg_replace('~/(\./)+~', '/', $param);
937 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
938 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
939 // match ipv4 dotted quad
940 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
941 // confirm values are ok
942 if ( $match[0] > 255
943 || $match[1] > 255
944 || $match[3] > 255
945 || $match[4] > 255 ) {
946 // hmmm, what kind of dotted quad is this?
947 $param = '';
949 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
950 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
951 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
953 // all is ok - $param is respected
954 } else {
955 // all is not ok...
956 $param='';
958 return $param;
960 case PARAM_URL: // allow safe ftp, http, mailto urls
961 $param = fix_utf8($param);
962 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
963 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
964 // all is ok, param is respected
965 } else {
966 $param =''; // not really ok
968 return $param;
970 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
971 $param = clean_param($param, PARAM_URL);
972 if (!empty($param)) {
973 if (preg_match(':^/:', $param)) {
974 // root-relative, ok!
975 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
976 // absolute, and matches our wwwroot
977 } else {
978 // relative - let's make sure there are no tricks
979 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
980 // looks ok.
981 } else {
982 $param = '';
986 return $param;
988 case PARAM_PEM:
989 $param = trim($param);
990 // PEM formatted strings may contain letters/numbers and the symbols
991 // forward slash: /
992 // plus sign: +
993 // equal sign: =
994 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
995 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
996 list($wholething, $body) = $matches;
997 unset($wholething, $matches);
998 $b64 = clean_param($body, PARAM_BASE64);
999 if (!empty($b64)) {
1000 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
1001 } else {
1002 return '';
1005 return '';
1007 case PARAM_BASE64:
1008 if (!empty($param)) {
1009 // PEM formatted strings may contain letters/numbers and the symbols
1010 // forward slash: /
1011 // plus sign: +
1012 // equal sign: =
1013 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
1014 return '';
1016 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
1017 // Each line of base64 encoded data must be 64 characters in
1018 // length, except for the last line which may be less than (or
1019 // equal to) 64 characters long.
1020 for ($i=0, $j=count($lines); $i < $j; $i++) {
1021 if ($i + 1 == $j) {
1022 if (64 < strlen($lines[$i])) {
1023 return '';
1025 continue;
1028 if (64 != strlen($lines[$i])) {
1029 return '';
1032 return implode("\n",$lines);
1033 } else {
1034 return '';
1037 case PARAM_TAG:
1038 $param = fix_utf8($param);
1039 // Please note it is not safe to use the tag name directly anywhere,
1040 // it must be processed with s(), urlencode() before embedding anywhere.
1041 // remove some nasties
1042 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
1043 //convert many whitespace chars into one
1044 $param = preg_replace('/\s+/', ' ', $param);
1045 $param = textlib::substr(trim($param), 0, TAG_MAX_LENGTH);
1046 return $param;
1048 case PARAM_TAGLIST:
1049 $param = fix_utf8($param);
1050 $tags = explode(',', $param);
1051 $result = array();
1052 foreach ($tags as $tag) {
1053 $res = clean_param($tag, PARAM_TAG);
1054 if ($res !== '') {
1055 $result[] = $res;
1058 if ($result) {
1059 return implode(',', $result);
1060 } else {
1061 return '';
1064 case PARAM_CAPABILITY:
1065 if (get_capability_info($param)) {
1066 return $param;
1067 } else {
1068 return '';
1071 case PARAM_PERMISSION:
1072 $param = (int)$param;
1073 if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
1074 return $param;
1075 } else {
1076 return CAP_INHERIT;
1079 case PARAM_AUTH:
1080 $param = clean_param($param, PARAM_PLUGIN);
1081 if (empty($param)) {
1082 return '';
1083 } else if (exists_auth_plugin($param)) {
1084 return $param;
1085 } else {
1086 return '';
1089 case PARAM_LANG:
1090 $param = clean_param($param, PARAM_SAFEDIR);
1091 if (get_string_manager()->translation_exists($param)) {
1092 return $param;
1093 } else {
1094 return ''; // Specified language is not installed or param malformed
1097 case PARAM_THEME:
1098 $param = clean_param($param, PARAM_PLUGIN);
1099 if (empty($param)) {
1100 return '';
1101 } else if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
1102 return $param;
1103 } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
1104 return $param;
1105 } else {
1106 return ''; // Specified theme is not installed
1109 case PARAM_USERNAME:
1110 $param = fix_utf8($param);
1111 $param = str_replace(" " , "", $param);
1112 $param = textlib::strtolower($param); // Convert uppercase to lowercase MDL-16919
1113 if (empty($CFG->extendedusernamechars)) {
1114 // regular expression, eliminate all chars EXCEPT:
1115 // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
1116 $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
1118 return $param;
1120 case PARAM_EMAIL:
1121 $param = fix_utf8($param);
1122 if (validate_email($param)) {
1123 return $param;
1124 } else {
1125 return '';
1128 case PARAM_STRINGID:
1129 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
1130 return $param;
1131 } else {
1132 return '';
1135 case PARAM_TIMEZONE: //can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'
1136 $param = fix_utf8($param);
1137 $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3]|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
1138 if (preg_match($timezonepattern, $param)) {
1139 return $param;
1140 } else {
1141 return '';
1144 default: // throw error, switched parameters in optional_param or another serious problem
1145 print_error("unknownparamtype", '', '', $type);
1150 * Makes sure the data is using valid utf8, invalid characters are discarded.
1152 * Note: this function is not intended for full objects with methods and private properties.
1154 * @param mixed $value
1155 * @return mixed with proper utf-8 encoding
1157 function fix_utf8($value) {
1158 if (is_null($value) or $value === '') {
1159 return $value;
1161 } else if (is_string($value)) {
1162 if ((string)(int)$value === $value) {
1163 // shortcut
1164 return $value;
1167 // Lower error reporting because glibc throws bogus notices.
1168 $olderror = error_reporting();
1169 if ($olderror & E_NOTICE) {
1170 error_reporting($olderror ^ E_NOTICE);
1173 // Note: this duplicates min_fix_utf8() intentionally.
1174 static $buggyiconv = null;
1175 if ($buggyiconv === null) {
1176 $buggyiconv = (!function_exists('iconv') or iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
1179 if ($buggyiconv) {
1180 if (function_exists('mb_convert_encoding')) {
1181 $subst = mb_substitute_character();
1182 mb_substitute_character('');
1183 $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
1184 mb_substitute_character($subst);
1186 } else {
1187 // Warn admins on admin/index.php page.
1188 $result = $value;
1191 } else {
1192 $result = iconv('UTF-8', 'UTF-8//IGNORE', $value);
1195 if ($olderror & E_NOTICE) {
1196 error_reporting($olderror);
1199 return $result;
1201 } else if (is_array($value)) {
1202 foreach ($value as $k=>$v) {
1203 $value[$k] = fix_utf8($v);
1205 return $value;
1207 } else if (is_object($value)) {
1208 $value = clone($value); // do not modify original
1209 foreach ($value as $k=>$v) {
1210 $value->$k = fix_utf8($v);
1212 return $value;
1214 } else {
1215 // this is some other type, no utf-8 here
1216 return $value;
1221 * Return true if given value is integer or string with integer value
1223 * @param mixed $value String or Int
1224 * @return bool true if number, false if not
1226 function is_number($value) {
1227 if (is_int($value)) {
1228 return true;
1229 } else if (is_string($value)) {
1230 return ((string)(int)$value) === $value;
1231 } else {
1232 return false;
1237 * Returns host part from url
1238 * @param string $url full url
1239 * @return string host, null if not found
1241 function get_host_from_url($url) {
1242 preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
1243 if ($matches) {
1244 return $matches[1];
1246 return null;
1250 * Tests whether anything was returned by text editor
1252 * This function is useful for testing whether something you got back from
1253 * the HTML editor actually contains anything. Sometimes the HTML editor
1254 * appear to be empty, but actually you get back a <br> tag or something.
1256 * @param string $string a string containing HTML.
1257 * @return boolean does the string contain any actual content - that is text,
1258 * images, objects, etc.
1260 function html_is_blank($string) {
1261 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
1265 * Set a key in global configuration
1267 * Set a key/value pair in both this session's {@link $CFG} global variable
1268 * and in the 'config' database table for future sessions.
1270 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
1271 * In that case it doesn't affect $CFG.
1273 * A NULL value will delete the entry.
1275 * @global object
1276 * @global object
1277 * @param string $name the key to set
1278 * @param string $value the value to set (without magic quotes)
1279 * @param string $plugin (optional) the plugin scope, default NULL
1280 * @return bool true or exception
1282 function set_config($name, $value, $plugin=NULL) {
1283 global $CFG, $DB;
1285 if (empty($plugin)) {
1286 if (!array_key_exists($name, $CFG->config_php_settings)) {
1287 // So it's defined for this invocation at least
1288 if (is_null($value)) {
1289 unset($CFG->$name);
1290 } else {
1291 $CFG->$name = (string)$value; // settings from db are always strings
1295 if ($DB->get_field('config', 'name', array('name'=>$name))) {
1296 if ($value === null) {
1297 $DB->delete_records('config', array('name'=>$name));
1298 } else {
1299 $DB->set_field('config', 'value', $value, array('name'=>$name));
1301 } else {
1302 if ($value !== null) {
1303 $config = new stdClass();
1304 $config->name = $name;
1305 $config->value = $value;
1306 $DB->insert_record('config', $config, false);
1310 } else { // plugin scope
1311 if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
1312 if ($value===null) {
1313 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1314 } else {
1315 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
1317 } else {
1318 if ($value !== null) {
1319 $config = new stdClass();
1320 $config->plugin = $plugin;
1321 $config->name = $name;
1322 $config->value = $value;
1323 $DB->insert_record('config_plugins', $config, false);
1328 return true;
1332 * Get configuration values from the global config table
1333 * or the config_plugins table.
1335 * If called with one parameter, it will load all the config
1336 * variables for one plugin, and return them as an object.
1338 * If called with 2 parameters it will return a string single
1339 * value or false if the value is not found.
1341 * @param string $plugin full component name
1342 * @param string $name default NULL
1343 * @return mixed hash-like object or single value, return false no config found
1345 function get_config($plugin, $name = NULL) {
1346 global $CFG, $DB;
1348 // normalise component name
1349 if ($plugin === 'moodle' or $plugin === 'core') {
1350 $plugin = NULL;
1353 if (!empty($name)) { // the user is asking for a specific value
1354 if (!empty($plugin)) {
1355 if (isset($CFG->forced_plugin_settings[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings[$plugin])) {
1356 // setting forced in config file
1357 return $CFG->forced_plugin_settings[$plugin][$name];
1358 } else {
1359 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1361 } else {
1362 if (array_key_exists($name, $CFG->config_php_settings)) {
1363 // setting force in config file
1364 return $CFG->config_php_settings[$name];
1365 } else {
1366 return $DB->get_field('config', 'value', array('name'=>$name));
1371 // the user is after a recordset
1372 if ($plugin) {
1373 $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
1374 if (isset($CFG->forced_plugin_settings[$plugin])) {
1375 foreach($CFG->forced_plugin_settings[$plugin] as $n=>$v) {
1376 if (is_null($v) or is_array($v) or is_object($v)) {
1377 // we do not want any extra mess here, just real settings that could be saved in db
1378 unset($localcfg[$n]);
1379 } else {
1380 //convert to string as if it went through the DB
1381 $localcfg[$n] = (string)$v;
1385 if ($localcfg) {
1386 return (object)$localcfg;
1387 } else {
1388 return new stdClass();
1391 } else {
1392 // this part is not really used any more, but anyway...
1393 $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
1394 foreach($CFG->config_php_settings as $n=>$v) {
1395 if (is_null($v) or is_array($v) or is_object($v)) {
1396 // we do not want any extra mess here, just real settings that could be saved in db
1397 unset($localcfg[$n]);
1398 } else {
1399 //convert to string as if it went through the DB
1400 $localcfg[$n] = (string)$v;
1403 return (object)$localcfg;
1408 * Removes a key from global configuration
1410 * @param string $name the key to set
1411 * @param string $plugin (optional) the plugin scope
1412 * @global object
1413 * @return boolean whether the operation succeeded.
1415 function unset_config($name, $plugin=NULL) {
1416 global $CFG, $DB;
1418 if (empty($plugin)) {
1419 unset($CFG->$name);
1420 $DB->delete_records('config', array('name'=>$name));
1421 } else {
1422 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1425 return true;
1429 * Remove all the config variables for a given plugin.
1431 * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1432 * @return boolean whether the operation succeeded.
1434 function unset_all_config_for_plugin($plugin) {
1435 global $DB;
1436 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1437 $like = $DB->sql_like('name', '?', true, true, false, '|');
1438 $params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
1439 $DB->delete_records_select('config', $like, $params);
1440 return true;
1444 * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1446 * All users are verified if they still have the necessary capability.
1448 * @param string $value the value of the config setting.
1449 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1450 * @param bool $include admins, include administrators
1451 * @return array of user objects.
1453 function get_users_from_config($value, $capability, $includeadmins = true) {
1454 global $CFG, $DB;
1456 if (empty($value) or $value === '$@NONE@$') {
1457 return array();
1460 // we have to make sure that users still have the necessary capability,
1461 // it should be faster to fetch them all first and then test if they are present
1462 // instead of validating them one-by-one
1463 $users = get_users_by_capability(context_system::instance(), $capability);
1464 if ($includeadmins) {
1465 $admins = get_admins();
1466 foreach ($admins as $admin) {
1467 $users[$admin->id] = $admin;
1471 if ($value === '$@ALL@$') {
1472 return $users;
1475 $result = array(); // result in correct order
1476 $allowed = explode(',', $value);
1477 foreach ($allowed as $uid) {
1478 if (isset($users[$uid])) {
1479 $user = $users[$uid];
1480 $result[$user->id] = $user;
1484 return $result;
1489 * Invalidates browser caches and cached data in temp
1490 * @return void
1492 function purge_all_caches() {
1493 global $CFG;
1495 reset_text_filters_cache();
1496 js_reset_all_caches();
1497 theme_reset_all_caches();
1498 get_string_manager()->reset_caches();
1499 textlib::reset_caches();
1501 // purge all other caches: rss, simplepie, etc.
1502 remove_dir($CFG->cachedir.'', true);
1504 // make sure cache dir is writable, throws exception if not
1505 make_cache_directory('');
1507 // hack: this script may get called after the purifier was initialised,
1508 // but we do not want to verify repeatedly this exists in each call
1509 make_cache_directory('htmlpurifier');
1513 * Get volatile flags
1515 * @param string $type
1516 * @param int $changedsince default null
1517 * @return records array
1519 function get_cache_flags($type, $changedsince=NULL) {
1520 global $DB;
1522 $params = array('type'=>$type, 'expiry'=>time());
1523 $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1524 if ($changedsince !== NULL) {
1525 $params['changedsince'] = $changedsince;
1526 $sqlwhere .= " AND timemodified > :changedsince";
1528 $cf = array();
1530 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1531 foreach ($flags as $flag) {
1532 $cf[$flag->name] = $flag->value;
1535 return $cf;
1539 * Get volatile flags
1541 * @param string $type
1542 * @param string $name
1543 * @param int $changedsince default null
1544 * @return records array
1546 function get_cache_flag($type, $name, $changedsince=NULL) {
1547 global $DB;
1549 $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
1551 $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1552 if ($changedsince !== NULL) {
1553 $params['changedsince'] = $changedsince;
1554 $sqlwhere .= " AND timemodified > :changedsince";
1557 return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1561 * Set a volatile flag
1563 * @param string $type the "type" namespace for the key
1564 * @param string $name the key to set
1565 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
1566 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1567 * @return bool Always returns true
1569 function set_cache_flag($type, $name, $value, $expiry=NULL) {
1570 global $DB;
1572 $timemodified = time();
1573 if ($expiry===NULL || $expiry < $timemodified) {
1574 $expiry = $timemodified + 24 * 60 * 60;
1575 } else {
1576 $expiry = (int)$expiry;
1579 if ($value === NULL) {
1580 unset_cache_flag($type,$name);
1581 return true;
1584 if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE)) { // this is a potential problem in DEBUG_DEVELOPER
1585 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
1586 return true; //no need to update; helps rcache too
1588 $f->value = $value;
1589 $f->expiry = $expiry;
1590 $f->timemodified = $timemodified;
1591 $DB->update_record('cache_flags', $f);
1592 } else {
1593 $f = new stdClass();
1594 $f->flagtype = $type;
1595 $f->name = $name;
1596 $f->value = $value;
1597 $f->expiry = $expiry;
1598 $f->timemodified = $timemodified;
1599 $DB->insert_record('cache_flags', $f);
1601 return true;
1605 * Removes a single volatile flag
1607 * @global object
1608 * @param string $type the "type" namespace for the key
1609 * @param string $name the key to set
1610 * @return bool
1612 function unset_cache_flag($type, $name) {
1613 global $DB;
1614 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1615 return true;
1619 * Garbage-collect volatile flags
1621 * @return bool Always returns true
1623 function gc_cache_flags() {
1624 global $DB;
1625 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1626 return true;
1629 // USER PREFERENCE API
1632 * Refresh user preference cache. This is used most often for $USER
1633 * object that is stored in session, but it also helps with performance in cron script.
1635 * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1637 * @package core
1638 * @category preference
1639 * @access public
1640 * @param stdClass $user User object. Preferences are preloaded into 'preference' property
1641 * @param int $cachelifetime Cache life time on the current page (in seconds)
1642 * @throws coding_exception
1643 * @return null
1645 function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
1646 global $DB;
1647 static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
1649 if (!isset($user->id)) {
1650 throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1653 if (empty($user->id) or isguestuser($user->id)) {
1654 // No permanent storage for not-logged-in users and guest
1655 if (!isset($user->preference)) {
1656 $user->preference = array();
1658 return;
1661 $timenow = time();
1663 if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
1664 // Already loaded at least once on this page. Are we up to date?
1665 if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
1666 // no need to reload - we are on the same page and we loaded prefs just a moment ago
1667 return;
1669 } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
1670 // no change since the lastcheck on this page
1671 $user->preference['_lastloaded'] = $timenow;
1672 return;
1676 // OK, so we have to reload all preferences
1677 $loadedusers[$user->id] = true;
1678 $user->preference = $DB->get_records_menu('user_preferences', array('userid'=>$user->id), '', 'name,value'); // All values
1679 $user->preference['_lastloaded'] = $timenow;
1683 * Called from set/unset_user_preferences, so that the prefs can
1684 * be correctly reloaded in different sessions.
1686 * NOTE: internal function, do not call from other code.
1688 * @package core
1689 * @access private
1690 * @param integer $userid the user whose prefs were changed.
1692 function mark_user_preferences_changed($userid) {
1693 global $CFG;
1695 if (empty($userid) or isguestuser($userid)) {
1696 // no cache flags for guest and not-logged-in users
1697 return;
1700 set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
1704 * Sets a preference for the specified user.
1706 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1708 * @package core
1709 * @category preference
1710 * @access public
1711 * @param string $name The key to set as preference for the specified user
1712 * @param string $value The value to set for the $name key in the specified user's
1713 * record, null means delete current value.
1714 * @param stdClass|int|null $user A moodle user object or id, null means current user
1715 * @throws coding_exception
1716 * @return bool Always true or exception
1718 function set_user_preference($name, $value, $user = null) {
1719 global $USER, $DB;
1721 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1722 throw new coding_exception('Invalid preference name in set_user_preference() call');
1725 if (is_null($value)) {
1726 // null means delete current
1727 return unset_user_preference($name, $user);
1728 } else if (is_object($value)) {
1729 throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1730 } else if (is_array($value)) {
1731 throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1733 $value = (string)$value;
1734 if (textlib::strlen($value) > 1333) { //value column maximum length is 1333 characters
1735 throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
1738 if (is_null($user)) {
1739 $user = $USER;
1740 } else if (isset($user->id)) {
1741 // $user is valid object
1742 } else if (is_numeric($user)) {
1743 $user = (object)array('id'=>(int)$user);
1744 } else {
1745 throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1748 check_user_preferences_loaded($user);
1750 if (empty($user->id) or isguestuser($user->id)) {
1751 // no permanent storage for not-logged-in users and guest
1752 $user->preference[$name] = $value;
1753 return true;
1756 if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>$name))) {
1757 if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
1758 // preference already set to this value
1759 return true;
1761 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id));
1763 } else {
1764 $preference = new stdClass();
1765 $preference->userid = $user->id;
1766 $preference->name = $name;
1767 $preference->value = $value;
1768 $DB->insert_record('user_preferences', $preference);
1771 // update value in cache
1772 $user->preference[$name] = $value;
1774 // set reload flag for other sessions
1775 mark_user_preferences_changed($user->id);
1777 return true;
1781 * Sets a whole array of preferences for the current user
1783 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1785 * @package core
1786 * @category preference
1787 * @access public
1788 * @param array $prefarray An array of key/value pairs to be set
1789 * @param stdClass|int|null $user A moodle user object or id, null means current user
1790 * @return bool Always true or exception
1792 function set_user_preferences(array $prefarray, $user = null) {
1793 foreach ($prefarray as $name => $value) {
1794 set_user_preference($name, $value, $user);
1796 return true;
1800 * Unsets a preference completely by deleting it from the database
1802 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1804 * @package core
1805 * @category preference
1806 * @access public
1807 * @param string $name The key to unset as preference for the specified user
1808 * @param stdClass|int|null $user A moodle user object or id, null means current user
1809 * @throws coding_exception
1810 * @return bool Always true or exception
1812 function unset_user_preference($name, $user = null) {
1813 global $USER, $DB;
1815 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1816 throw new coding_exception('Invalid preference name in unset_user_preference() call');
1819 if (is_null($user)) {
1820 $user = $USER;
1821 } else if (isset($user->id)) {
1822 // $user is valid object
1823 } else if (is_numeric($user)) {
1824 $user = (object)array('id'=>(int)$user);
1825 } else {
1826 throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1829 check_user_preferences_loaded($user);
1831 if (empty($user->id) or isguestuser($user->id)) {
1832 // no permanent storage for not-logged-in user and guest
1833 unset($user->preference[$name]);
1834 return true;
1837 // delete from DB
1838 $DB->delete_records('user_preferences', array('userid'=>$user->id, 'name'=>$name));
1840 // delete the preference from cache
1841 unset($user->preference[$name]);
1843 // set reload flag for other sessions
1844 mark_user_preferences_changed($user->id);
1846 return true;
1850 * Used to fetch user preference(s)
1852 * If no arguments are supplied this function will return
1853 * all of the current user preferences as an array.
1855 * If a name is specified then this function
1856 * attempts to return that particular preference value. If
1857 * none is found, then the optional value $default is returned,
1858 * otherwise NULL.
1860 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1862 * @package core
1863 * @category preference
1864 * @access public
1865 * @param string $name Name of the key to use in finding a preference value
1866 * @param mixed|null $default Value to be returned if the $name key is not set in the user preferences
1867 * @param stdClass|int|null $user A moodle user object or id, null means current user
1868 * @throws coding_exception
1869 * @return string|mixed|null A string containing the value of a single preference. An
1870 * array with all of the preferences or null
1872 function get_user_preferences($name = null, $default = null, $user = null) {
1873 global $USER;
1875 if (is_null($name)) {
1876 // all prefs
1877 } else if (is_numeric($name) or $name === '_lastloaded') {
1878 throw new coding_exception('Invalid preference name in get_user_preferences() call');
1881 if (is_null($user)) {
1882 $user = $USER;
1883 } else if (isset($user->id)) {
1884 // $user is valid object
1885 } else if (is_numeric($user)) {
1886 $user = (object)array('id'=>(int)$user);
1887 } else {
1888 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1891 check_user_preferences_loaded($user);
1893 if (empty($name)) {
1894 return $user->preference; // All values
1895 } else if (isset($user->preference[$name])) {
1896 return $user->preference[$name]; // The single string value
1897 } else {
1898 return $default; // Default value (null if not specified)
1902 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1905 * Given date parts in user time produce a GMT timestamp.
1907 * @package core
1908 * @category time
1909 * @param int $year The year part to create timestamp of
1910 * @param int $month The month part to create timestamp of
1911 * @param int $day The day part to create timestamp of
1912 * @param int $hour The hour part to create timestamp of
1913 * @param int $minute The minute part to create timestamp of
1914 * @param int $second The second part to create timestamp of
1915 * @param int|float|string $timezone Timezone modifier, used to calculate GMT time offset.
1916 * if 99 then default user's timezone is used {@link http://docs.moodle.org/dev/Time_API#Timezone}
1917 * @param bool $applydst Toggle Daylight Saving Time, default true, will be
1918 * applied only if timezone is 99 or string.
1919 * @return int GMT timestamp
1921 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1923 //save input timezone, required for dst offset check.
1924 $passedtimezone = $timezone;
1926 $timezone = get_user_timezone_offset($timezone);
1928 if (abs($timezone) > 13) { //server time
1929 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1930 } else {
1931 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1932 $time = usertime($time, $timezone);
1934 //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
1935 if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
1936 $time -= dst_offset_on($time, $passedtimezone);
1940 return $time;
1945 * Format a date/time (seconds) as weeks, days, hours etc as needed
1947 * Given an amount of time in seconds, returns string
1948 * formatted nicely as weeks, days, hours etc as needed
1950 * @package core
1951 * @category time
1952 * @uses MINSECS
1953 * @uses HOURSECS
1954 * @uses DAYSECS
1955 * @uses YEARSECS
1956 * @param int $totalsecs Time in seconds
1957 * @param object $str Should be a time object
1958 * @return string A nicely formatted date/time string
1960 function format_time($totalsecs, $str=NULL) {
1962 $totalsecs = abs($totalsecs);
1964 if (!$str) { // Create the str structure the slow way
1965 $str = new stdClass();
1966 $str->day = get_string('day');
1967 $str->days = get_string('days');
1968 $str->hour = get_string('hour');
1969 $str->hours = get_string('hours');
1970 $str->min = get_string('min');
1971 $str->mins = get_string('mins');
1972 $str->sec = get_string('sec');
1973 $str->secs = get_string('secs');
1974 $str->year = get_string('year');
1975 $str->years = get_string('years');
1979 $years = floor($totalsecs/YEARSECS);
1980 $remainder = $totalsecs - ($years*YEARSECS);
1981 $days = floor($remainder/DAYSECS);
1982 $remainder = $totalsecs - ($days*DAYSECS);
1983 $hours = floor($remainder/HOURSECS);
1984 $remainder = $remainder - ($hours*HOURSECS);
1985 $mins = floor($remainder/MINSECS);
1986 $secs = $remainder - ($mins*MINSECS);
1988 $ss = ($secs == 1) ? $str->sec : $str->secs;
1989 $sm = ($mins == 1) ? $str->min : $str->mins;
1990 $sh = ($hours == 1) ? $str->hour : $str->hours;
1991 $sd = ($days == 1) ? $str->day : $str->days;
1992 $sy = ($years == 1) ? $str->year : $str->years;
1994 $oyears = '';
1995 $odays = '';
1996 $ohours = '';
1997 $omins = '';
1998 $osecs = '';
2000 if ($years) $oyears = $years .' '. $sy;
2001 if ($days) $odays = $days .' '. $sd;
2002 if ($hours) $ohours = $hours .' '. $sh;
2003 if ($mins) $omins = $mins .' '. $sm;
2004 if ($secs) $osecs = $secs .' '. $ss;
2006 if ($years) return trim($oyears .' '. $odays);
2007 if ($days) return trim($odays .' '. $ohours);
2008 if ($hours) return trim($ohours .' '. $omins);
2009 if ($mins) return trim($omins .' '. $osecs);
2010 if ($secs) return $osecs;
2011 return get_string('now');
2015 * Returns a formatted string that represents a date in user time
2017 * Returns a formatted string that represents a date in user time
2018 * <b>WARNING: note that the format is for strftime(), not date().</b>
2019 * Because of a bug in most Windows time libraries, we can't use
2020 * the nicer %e, so we have to use %d which has leading zeroes.
2021 * A lot of the fuss in the function is just getting rid of these leading
2022 * zeroes as efficiently as possible.
2024 * If parameter fixday = true (default), then take off leading
2025 * zero from %d, else maintain it.
2027 * @package core
2028 * @category time
2029 * @param int $date the timestamp in UTC, as obtained from the database.
2030 * @param string $format strftime format. You should probably get this using
2031 * get_string('strftime...', 'langconfig');
2032 * @param int|float|string $timezone by default, uses the user's time zone. if numeric and
2033 * not 99 then daylight saving will not be added.
2034 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2035 * @param bool $fixday If true (default) then the leading zero from %d is removed.
2036 * If false then the leading zero is maintained.
2037 * @param bool $fixhour If true (default) then the leading zero from %I is removed.
2038 * @return string the formatted date/time.
2040 function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true) {
2042 global $CFG;
2044 if (empty($format)) {
2045 $format = get_string('strftimedaydatetime', 'langconfig');
2048 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
2049 $fixday = false;
2050 } else if ($fixday) {
2051 $formatnoday = str_replace('%d', 'DD', $format);
2052 $fixday = ($formatnoday != $format);
2053 $format = $formatnoday;
2056 // Note: This logic about fixing 12-hour time to remove unnecessary leading
2057 // zero is required because on Windows, PHP strftime function does not
2058 // support the correct 'hour without leading zero' parameter (%l).
2059 if (!empty($CFG->nofixhour)) {
2060 // Config.php can force %I not to be fixed.
2061 $fixhour = false;
2062 } else if ($fixhour) {
2063 $formatnohour = str_replace('%I', 'HH', $format);
2064 $fixhour = ($formatnohour != $format);
2065 $format = $formatnohour;
2068 //add daylight saving offset for string timezones only, as we can't get dst for
2069 //float values. if timezone is 99 (user default timezone), then try update dst.
2070 if ((99 == $timezone) || !is_numeric($timezone)) {
2071 $date += dst_offset_on($date, $timezone);
2074 $timezone = get_user_timezone_offset($timezone);
2076 // If we are running under Windows convert to windows encoding and then back to UTF-8
2077 // (because it's impossible to specify UTF-8 to fetch locale info in Win32)
2079 if (abs($timezone) > 13) { /// Server time
2080 if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
2081 $format = textlib::convert($format, 'utf-8', $localewincharset);
2082 $datestring = strftime($format, $date);
2083 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2084 } else {
2085 $datestring = strftime($format, $date);
2087 if ($fixday) {
2088 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
2089 $datestring = str_replace('DD', $daystring, $datestring);
2091 if ($fixhour) {
2092 $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
2093 $datestring = str_replace('HH', $hourstring, $datestring);
2096 } else {
2097 $date += (int)($timezone * 3600);
2098 if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
2099 $format = textlib::convert($format, 'utf-8', $localewincharset);
2100 $datestring = gmstrftime($format, $date);
2101 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2102 } else {
2103 $datestring = gmstrftime($format, $date);
2105 if ($fixday) {
2106 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
2107 $datestring = str_replace('DD', $daystring, $datestring);
2109 if ($fixhour) {
2110 $hourstring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %I', $date)));
2111 $datestring = str_replace('HH', $hourstring, $datestring);
2115 return $datestring;
2119 * Given a $time timestamp in GMT (seconds since epoch),
2120 * returns an array that represents the date in user time
2122 * @package core
2123 * @category time
2124 * @uses HOURSECS
2125 * @param int $time Timestamp in GMT
2126 * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
2127 * dst offset is applyed {@link http://docs.moodle.org/dev/Time_API#Timezone}
2128 * @return array An array that represents the date in user time
2130 function usergetdate($time, $timezone=99) {
2132 //save input timezone, required for dst offset check.
2133 $passedtimezone = $timezone;
2135 $timezone = get_user_timezone_offset($timezone);
2137 if (abs($timezone) > 13) { // Server time
2138 return getdate($time);
2141 //add daylight saving offset for string timezones only, as we can't get dst for
2142 //float values. if timezone is 99 (user default timezone), then try update dst.
2143 if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
2144 $time += dst_offset_on($time, $passedtimezone);
2147 $time += intval((float)$timezone * HOURSECS);
2149 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
2151 //be careful to ensure the returned array matches that produced by getdate() above
2152 list(
2153 $getdate['month'],
2154 $getdate['weekday'],
2155 $getdate['yday'],
2156 $getdate['year'],
2157 $getdate['mon'],
2158 $getdate['wday'],
2159 $getdate['mday'],
2160 $getdate['hours'],
2161 $getdate['minutes'],
2162 $getdate['seconds']
2163 ) = explode('_', $datestring);
2165 // set correct datatype to match with getdate()
2166 $getdate['seconds'] = (int)$getdate['seconds'];
2167 $getdate['yday'] = (int)$getdate['yday'] - 1; // gettime returns 0 through 365
2168 $getdate['year'] = (int)$getdate['year'];
2169 $getdate['mon'] = (int)$getdate['mon'];
2170 $getdate['wday'] = (int)$getdate['wday'];
2171 $getdate['mday'] = (int)$getdate['mday'];
2172 $getdate['hours'] = (int)$getdate['hours'];
2173 $getdate['minutes'] = (int)$getdate['minutes'];
2174 return $getdate;
2178 * Given a GMT timestamp (seconds since epoch), offsets it by
2179 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
2181 * @package core
2182 * @category time
2183 * @uses HOURSECS
2184 * @param int $date Timestamp in GMT
2185 * @param float|int|string $timezone timezone to calculate GMT time offset before
2186 * calculating user time, 99 is default user timezone
2187 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2188 * @return int
2190 function usertime($date, $timezone=99) {
2192 $timezone = get_user_timezone_offset($timezone);
2194 if (abs($timezone) > 13) {
2195 return $date;
2197 return $date - (int)($timezone * HOURSECS);
2201 * Given a time, return the GMT timestamp of the most recent midnight
2202 * for the current user.
2204 * @package core
2205 * @category time
2206 * @param int $date Timestamp in GMT
2207 * @param float|int|string $timezone timezone to calculate GMT time offset before
2208 * calculating user midnight time, 99 is default user timezone
2209 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2210 * @return int Returns a GMT timestamp
2212 function usergetmidnight($date, $timezone=99) {
2214 $userdate = usergetdate($date, $timezone);
2216 // Time of midnight of this user's day, in GMT
2217 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
2222 * Returns a string that prints the user's timezone
2224 * @package core
2225 * @category time
2226 * @param float|int|string $timezone timezone to calculate GMT time offset before
2227 * calculating user timezone, 99 is default user timezone
2228 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2229 * @return string
2231 function usertimezone($timezone=99) {
2233 $tz = get_user_timezone($timezone);
2235 if (!is_float($tz)) {
2236 return $tz;
2239 if(abs($tz) > 13) { // Server time
2240 return get_string('serverlocaltime');
2243 if($tz == intval($tz)) {
2244 // Don't show .0 for whole hours
2245 $tz = intval($tz);
2248 if($tz == 0) {
2249 return 'UTC';
2251 else if($tz > 0) {
2252 return 'UTC+'.$tz;
2254 else {
2255 return 'UTC'.$tz;
2261 * Returns a float which represents the user's timezone difference from GMT in hours
2262 * Checks various settings and picks the most dominant of those which have a value
2264 * @package core
2265 * @category time
2266 * @param float|int|string $tz timezone to calculate GMT time offset for user,
2267 * 99 is default user timezone
2268 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2269 * @return float
2271 function get_user_timezone_offset($tz = 99) {
2273 global $USER, $CFG;
2275 $tz = get_user_timezone($tz);
2277 if (is_float($tz)) {
2278 return $tz;
2279 } else {
2280 $tzrecord = get_timezone_record($tz);
2281 if (empty($tzrecord)) {
2282 return 99.0;
2284 return (float)$tzrecord->gmtoff / HOURMINS;
2289 * Returns an int which represents the systems's timezone difference from GMT in seconds
2291 * @package core
2292 * @category time
2293 * @param float|int|string $tz timezone for which offset is required.
2294 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2295 * @return int|bool if found, false is timezone 99 or error
2297 function get_timezone_offset($tz) {
2298 global $CFG;
2300 if ($tz == 99) {
2301 return false;
2304 if (is_numeric($tz)) {
2305 return intval($tz * 60*60);
2308 if (!$tzrecord = get_timezone_record($tz)) {
2309 return false;
2311 return intval($tzrecord->gmtoff * 60);
2315 * Returns a float or a string which denotes the user's timezone
2316 * 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)
2317 * means that for this timezone there are also DST rules to be taken into account
2318 * Checks various settings and picks the most dominant of those which have a value
2320 * @package core
2321 * @category time
2322 * @param float|int|string $tz timezone to calculate GMT time offset before
2323 * calculating user timezone, 99 is default user timezone
2324 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2325 * @return float|string
2327 function get_user_timezone($tz = 99) {
2328 global $USER, $CFG;
2330 $timezones = array(
2331 $tz,
2332 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
2333 isset($USER->timezone) ? $USER->timezone : 99,
2334 isset($CFG->timezone) ? $CFG->timezone : 99,
2337 $tz = 99;
2339 // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array
2340 while(((empty($tz) && !is_numeric($tz)) || $tz == 99) && $next = each($timezones)) {
2341 $tz = $next['value'];
2343 return is_numeric($tz) ? (float) $tz : $tz;
2347 * Returns cached timezone record for given $timezonename
2349 * @package core
2350 * @param string $timezonename name of the timezone
2351 * @return stdClass|bool timezonerecord or false
2353 function get_timezone_record($timezonename) {
2354 global $CFG, $DB;
2355 static $cache = NULL;
2357 if ($cache === NULL) {
2358 $cache = array();
2361 if (isset($cache[$timezonename])) {
2362 return $cache[$timezonename];
2365 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
2366 WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
2370 * Build and store the users Daylight Saving Time (DST) table
2372 * @package core
2373 * @param int $from_year Start year for the table, defaults to 1971
2374 * @param int $to_year End year for the table, defaults to 2035
2375 * @param int|float|string $strtimezone, timezone to check if dst should be applyed.
2376 * @return bool
2378 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
2379 global $CFG, $SESSION, $DB;
2381 $usertz = get_user_timezone($strtimezone);
2383 if (is_float($usertz)) {
2384 // Trivial timezone, no DST
2385 return false;
2388 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
2389 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2390 unset($SESSION->dst_offsets);
2391 unset($SESSION->dst_range);
2394 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
2395 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
2396 // This will be the return path most of the time, pretty light computationally
2397 return true;
2400 // Reaching here means we either need to extend our table or create it from scratch
2402 // Remember which TZ we calculated these changes for
2403 $SESSION->dst_offsettz = $usertz;
2405 if(empty($SESSION->dst_offsets)) {
2406 // If we 're creating from scratch, put the two guard elements in there
2407 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
2409 if(empty($SESSION->dst_range)) {
2410 // If creating from scratch
2411 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
2412 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
2414 // Fill in the array with the extra years we need to process
2415 $yearstoprocess = array();
2416 for($i = $from; $i <= $to; ++$i) {
2417 $yearstoprocess[] = $i;
2420 // Take note of which years we have processed for future calls
2421 $SESSION->dst_range = array($from, $to);
2423 else {
2424 // If needing to extend the table, do the same
2425 $yearstoprocess = array();
2427 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
2428 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
2430 if($from < $SESSION->dst_range[0]) {
2431 // Take note of which years we need to process and then note that we have processed them for future calls
2432 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2433 $yearstoprocess[] = $i;
2435 $SESSION->dst_range[0] = $from;
2437 if($to > $SESSION->dst_range[1]) {
2438 // Take note of which years we need to process and then note that we have processed them for future calls
2439 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2440 $yearstoprocess[] = $i;
2442 $SESSION->dst_range[1] = $to;
2446 if(empty($yearstoprocess)) {
2447 // This means that there was a call requesting a SMALLER range than we have already calculated
2448 return true;
2451 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2452 // Also, the array is sorted in descending timestamp order!
2454 // Get DB data
2456 static $presets_cache = array();
2457 if (!isset($presets_cache[$usertz])) {
2458 $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');
2460 if(empty($presets_cache[$usertz])) {
2461 return false;
2464 // Remove ending guard (first element of the array)
2465 reset($SESSION->dst_offsets);
2466 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2468 // Add all required change timestamps
2469 foreach($yearstoprocess as $y) {
2470 // Find the record which is in effect for the year $y
2471 foreach($presets_cache[$usertz] as $year => $preset) {
2472 if($year <= $y) {
2473 break;
2477 $changes = dst_changes_for_year($y, $preset);
2479 if($changes === NULL) {
2480 continue;
2482 if($changes['dst'] != 0) {
2483 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2485 if($changes['std'] != 0) {
2486 $SESSION->dst_offsets[$changes['std']] = 0;
2490 // Put in a guard element at the top
2491 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2492 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2494 // Sort again
2495 krsort($SESSION->dst_offsets);
2497 return true;
2501 * Calculates the required DST change and returns a Timestamp Array
2503 * @package core
2504 * @category time
2505 * @uses HOURSECS
2506 * @uses MINSECS
2507 * @param int|string $year Int or String Year to focus on
2508 * @param object $timezone Instatiated Timezone object
2509 * @return array|null Array dst=>xx, 0=>xx, std=>yy, 1=>yy or NULL
2511 function dst_changes_for_year($year, $timezone) {
2513 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2514 return NULL;
2517 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2518 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2520 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
2521 list($std_hour, $std_min) = explode(':', $timezone->std_time);
2523 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2524 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2526 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2527 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2528 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2530 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
2531 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
2533 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2537 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2538 * - Note: Daylight saving only works for string timezones and not for float.
2540 * @package core
2541 * @category time
2542 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2543 * @param int|float|string $strtimezone timezone for which offset is expected, if 99 or null
2544 * then user's default timezone is used. {@link http://docs.moodle.org/dev/Time_API#Timezone}
2545 * @return int
2547 function dst_offset_on($time, $strtimezone = NULL) {
2548 global $SESSION;
2550 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
2551 return 0;
2554 reset($SESSION->dst_offsets);
2555 while(list($from, $offset) = each($SESSION->dst_offsets)) {
2556 if($from <= $time) {
2557 break;
2561 // This is the normal return path
2562 if($offset !== NULL) {
2563 return $offset;
2566 // Reaching this point means we haven't calculated far enough, do it now:
2567 // Calculate extra DST changes if needed and recurse. The recursion always
2568 // moves toward the stopping condition, so will always end.
2570 if($from == 0) {
2571 // We need a year smaller than $SESSION->dst_range[0]
2572 if($SESSION->dst_range[0] == 1971) {
2573 return 0;
2575 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
2576 return dst_offset_on($time, $strtimezone);
2578 else {
2579 // We need a year larger than $SESSION->dst_range[1]
2580 if($SESSION->dst_range[1] == 2035) {
2581 return 0;
2583 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
2584 return dst_offset_on($time, $strtimezone);
2589 * Calculates when the day appears in specific month
2591 * @package core
2592 * @category time
2593 * @param int $startday starting day of the month
2594 * @param int $weekday The day when week starts (normally taken from user preferences)
2595 * @param int $month The month whose day is sought
2596 * @param int $year The year of the month whose day is sought
2597 * @return int
2599 function find_day_in_month($startday, $weekday, $month, $year) {
2601 $daysinmonth = days_in_month($month, $year);
2603 if($weekday == -1) {
2604 // Don't care about weekday, so return:
2605 // abs($startday) if $startday != -1
2606 // $daysinmonth otherwise
2607 return ($startday == -1) ? $daysinmonth : abs($startday);
2610 // From now on we 're looking for a specific weekday
2612 // Give "end of month" its actual value, since we know it
2613 if($startday == -1) {
2614 $startday = -1 * $daysinmonth;
2617 // Starting from day $startday, the sign is the direction
2619 if($startday < 1) {
2621 $startday = abs($startday);
2622 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2624 // This is the last such weekday of the month
2625 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2626 if($lastinmonth > $daysinmonth) {
2627 $lastinmonth -= 7;
2630 // Find the first such weekday <= $startday
2631 while($lastinmonth > $startday) {
2632 $lastinmonth -= 7;
2635 return $lastinmonth;
2638 else {
2640 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2642 $diff = $weekday - $indexweekday;
2643 if($diff < 0) {
2644 $diff += 7;
2647 // This is the first such weekday of the month equal to or after $startday
2648 $firstfromindex = $startday + $diff;
2650 return $firstfromindex;
2656 * Calculate the number of days in a given month
2658 * @package core
2659 * @category time
2660 * @param int $month The month whose day count is sought
2661 * @param int $year The year of the month whose day count is sought
2662 * @return int
2664 function days_in_month($month, $year) {
2665 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2669 * Calculate the position in the week of a specific calendar day
2671 * @package core
2672 * @category time
2673 * @param int $day The day of the date whose position in the week is sought
2674 * @param int $month The month of the date whose position in the week is sought
2675 * @param int $year The year of the date whose position in the week is sought
2676 * @return int
2678 function dayofweek($day, $month, $year) {
2679 // I wonder if this is any different from
2680 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2681 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2684 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2687 * Returns full login url.
2689 * @return string login url
2691 function get_login_url() {
2692 global $CFG;
2694 $url = "$CFG->wwwroot/login/index.php";
2696 if (!empty($CFG->loginhttps)) {
2697 $url = str_replace('http:', 'https:', $url);
2700 return $url;
2704 * This function checks that the current user is logged in and has the
2705 * required privileges
2707 * This function checks that the current user is logged in, and optionally
2708 * whether they are allowed to be in a particular course and view a particular
2709 * course module.
2710 * If they are not logged in, then it redirects them to the site login unless
2711 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2712 * case they are automatically logged in as guests.
2713 * If $courseid is given and the user is not enrolled in that course then the
2714 * user is redirected to the course enrolment page.
2715 * If $cm is given and the course module is hidden and the user is not a teacher
2716 * in the course then the user is redirected to the course home page.
2718 * When $cm parameter specified, this function sets page layout to 'module'.
2719 * You need to change it manually later if some other layout needed.
2721 * @package core_access
2722 * @category access
2724 * @param mixed $courseorid id of the course or course object
2725 * @param bool $autologinguest default true
2726 * @param object $cm course module object
2727 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2728 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2729 * in order to keep redirects working properly. MDL-14495
2730 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2731 * @return mixed Void, exit, and die depending on path
2733 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2734 global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
2736 // setup global $COURSE, themes, language and locale
2737 if (!empty($courseorid)) {
2738 if (is_object($courseorid)) {
2739 $course = $courseorid;
2740 } else if ($courseorid == SITEID) {
2741 $course = clone($SITE);
2742 } else {
2743 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2745 if ($cm) {
2746 if ($cm->course != $course->id) {
2747 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2749 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2750 if (!($cm instanceof cm_info)) {
2751 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2752 // db queries so this is not really a performance concern, however it is obviously
2753 // better if you use get_fast_modinfo to get the cm before calling this.
2754 $modinfo = get_fast_modinfo($course);
2755 $cm = $modinfo->get_cm($cm->id);
2757 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2758 $PAGE->set_pagelayout('incourse');
2759 } else {
2760 $PAGE->set_course($course); // set's up global $COURSE
2762 } else {
2763 // do not touch global $COURSE via $PAGE->set_course(),
2764 // the reasons is we need to be able to call require_login() at any time!!
2765 $course = $SITE;
2766 if ($cm) {
2767 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2771 // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2772 // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2773 // risk leading the user back to the AJAX request URL.
2774 if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
2775 $setwantsurltome = false;
2778 // If the user is not even logged in yet then make sure they are
2779 if (!isloggedin()) {
2780 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2781 if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2782 // misconfigured site guest, just redirect to login page
2783 redirect(get_login_url());
2784 exit; // never reached
2786 $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2787 complete_user_login($guest);
2788 $USER->autologinguest = true;
2789 $SESSION->lang = $lang;
2790 } else {
2791 //NOTE: $USER->site check was obsoleted by session test cookie,
2792 // $USER->confirmed test is in login/index.php
2793 if ($preventredirect) {
2794 throw new require_login_exception('You are not logged in');
2797 if ($setwantsurltome) {
2798 $SESSION->wantsurl = qualified_me();
2800 if (!empty($_SERVER['HTTP_REFERER'])) {
2801 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
2803 redirect(get_login_url());
2804 exit; // never reached
2808 // loginas as redirection if needed
2809 if ($course->id != SITEID and session_is_loggedinas()) {
2810 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2811 if ($USER->loginascontext->instanceid != $course->id) {
2812 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2817 // check whether the user should be changing password (but only if it is REALLY them)
2818 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2819 $userauth = get_auth_plugin($USER->auth);
2820 if ($userauth->can_change_password() and !$preventredirect) {
2821 if ($setwantsurltome) {
2822 $SESSION->wantsurl = qualified_me();
2824 if ($changeurl = $userauth->change_password_url()) {
2825 //use plugin custom url
2826 redirect($changeurl);
2827 } else {
2828 //use moodle internal method
2829 if (empty($CFG->loginhttps)) {
2830 redirect($CFG->wwwroot .'/login/change_password.php');
2831 } else {
2832 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
2833 redirect($wwwroot .'/login/change_password.php');
2836 } else {
2837 print_error('nopasswordchangeforced', 'auth');
2841 // Check that the user account is properly set up
2842 if (user_not_fully_set_up($USER)) {
2843 if ($preventredirect) {
2844 throw new require_login_exception('User not fully set-up');
2846 if ($setwantsurltome) {
2847 $SESSION->wantsurl = qualified_me();
2849 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2852 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2853 sesskey();
2855 // Do not bother admins with any formalities
2856 if (is_siteadmin()) {
2857 //set accesstime or the user will appear offline which messes up messaging
2858 user_accesstime_log($course->id);
2859 return;
2862 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2863 if (!$USER->policyagreed and !is_siteadmin()) {
2864 if (!empty($CFG->sitepolicy) and !isguestuser()) {
2865 if ($preventredirect) {
2866 throw new require_login_exception('Policy not agreed');
2868 if ($setwantsurltome) {
2869 $SESSION->wantsurl = qualified_me();
2871 redirect($CFG->wwwroot .'/user/policy.php');
2872 } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
2873 if ($preventredirect) {
2874 throw new require_login_exception('Policy not agreed');
2876 if ($setwantsurltome) {
2877 $SESSION->wantsurl = qualified_me();
2879 redirect($CFG->wwwroot .'/user/policy.php');
2883 // Fetch the system context, the course context, and prefetch its child contexts
2884 $sysctx = context_system::instance();
2885 $coursecontext = context_course::instance($course->id, MUST_EXIST);
2886 if ($cm) {
2887 $cmcontext = context_module::instance($cm->id, MUST_EXIST);
2888 } else {
2889 $cmcontext = null;
2892 // If the site is currently under maintenance, then print a message
2893 if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
2894 if ($preventredirect) {
2895 throw new require_login_exception('Maintenance in progress');
2898 print_maintenance_message();
2901 // make sure the course itself is not hidden
2902 if ($course->id == SITEID) {
2903 // frontpage can not be hidden
2904 } else {
2905 if (is_role_switched($course->id)) {
2906 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2907 } else {
2908 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2909 // originally there was also test of parent category visibility,
2910 // BUT is was very slow in complex queries involving "my courses"
2911 // now it is also possible to simply hide all courses user is not enrolled in :-)
2912 if ($preventredirect) {
2913 throw new require_login_exception('Course is hidden');
2915 // We need to override the navigation URL as the course won't have
2916 // been added to the navigation and thus the navigation will mess up
2917 // when trying to find it.
2918 navigation_node::override_active_url(new moodle_url('/'));
2919 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2924 // is the user enrolled?
2925 if ($course->id == SITEID) {
2926 // everybody is enrolled on the frontpage
2928 } else {
2929 if (session_is_loggedinas()) {
2930 // Make sure the REAL person can access this course first
2931 $realuser = session_get_realuser();
2932 if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
2933 if ($preventredirect) {
2934 throw new require_login_exception('Invalid course login-as access');
2936 echo $OUTPUT->header();
2937 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2941 $access = false;
2943 if (is_role_switched($course->id)) {
2944 // ok, user had to be inside this course before the switch
2945 $access = true;
2947 } else if (is_viewing($coursecontext, $USER)) {
2948 // ok, no need to mess with enrol
2949 $access = true;
2951 } else {
2952 if (isset($USER->enrol['enrolled'][$course->id])) {
2953 if ($USER->enrol['enrolled'][$course->id] > time()) {
2954 $access = true;
2955 if (isset($USER->enrol['tempguest'][$course->id])) {
2956 unset($USER->enrol['tempguest'][$course->id]);
2957 remove_temp_course_roles($coursecontext);
2959 } else {
2960 //expired
2961 unset($USER->enrol['enrolled'][$course->id]);
2964 if (isset($USER->enrol['tempguest'][$course->id])) {
2965 if ($USER->enrol['tempguest'][$course->id] == 0) {
2966 $access = true;
2967 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
2968 $access = true;
2969 } else {
2970 //expired
2971 unset($USER->enrol['tempguest'][$course->id]);
2972 remove_temp_course_roles($coursecontext);
2976 if ($access) {
2977 // cache ok
2978 } else {
2979 $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
2980 if ($until !== false) {
2981 // active participants may always access, a timestamp in the future, 0 (always) or false.
2982 if ($until == 0) {
2983 $until = ENROL_MAX_TIMESTAMP;
2985 $USER->enrol['enrolled'][$course->id] = $until;
2986 $access = true;
2988 } else {
2989 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2990 $enrols = enrol_get_plugins(true);
2991 // first ask all enabled enrol instances in course if they want to auto enrol user
2992 foreach($instances as $instance) {
2993 if (!isset($enrols[$instance->enrol])) {
2994 continue;
2996 // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
2997 $until = $enrols[$instance->enrol]->try_autoenrol($instance);
2998 if ($until !== false) {
2999 if ($until == 0) {
3000 $until = ENROL_MAX_TIMESTAMP;
3002 $USER->enrol['enrolled'][$course->id] = $until;
3003 $access = true;
3004 break;
3007 // if not enrolled yet try to gain temporary guest access
3008 if (!$access) {
3009 foreach($instances as $instance) {
3010 if (!isset($enrols[$instance->enrol])) {
3011 continue;
3013 // Get a duration for the guest access, a timestamp in the future or false.
3014 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
3015 if ($until !== false and $until > time()) {
3016 $USER->enrol['tempguest'][$course->id] = $until;
3017 $access = true;
3018 break;
3026 if (!$access) {
3027 if ($preventredirect) {
3028 throw new require_login_exception('Not enrolled');
3030 if ($setwantsurltome) {
3031 $SESSION->wantsurl = qualified_me();
3033 redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
3037 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
3038 // conditional availability, etc
3039 if ($cm && !$cm->uservisible) {
3040 if ($preventredirect) {
3041 throw new require_login_exception('Activity is hidden');
3043 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
3046 // Finally access granted, update lastaccess times
3047 user_accesstime_log($course->id);
3052 * This function just makes sure a user is logged out.
3054 * @package core_access
3056 function require_logout() {
3057 global $USER;
3059 $params = $USER;
3061 if (isloggedin()) {
3062 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
3064 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
3065 foreach($authsequence as $authname) {
3066 $authplugin = get_auth_plugin($authname);
3067 $authplugin->prelogout_hook();
3071 events_trigger('user_logout', $params);
3072 session_get_instance()->terminate_current();
3073 unset($params);
3077 * Weaker version of require_login()
3079 * This is a weaker version of {@link require_login()} which only requires login
3080 * when called from within a course rather than the site page, unless
3081 * the forcelogin option is turned on.
3082 * @see require_login()
3084 * @package core_access
3085 * @category access
3087 * @param mixed $courseorid The course object or id in question
3088 * @param bool $autologinguest Allow autologin guests if that is wanted
3089 * @param object $cm Course activity module if known
3090 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
3091 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
3092 * in order to keep redirects working properly. MDL-14495
3093 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
3094 * @return void
3096 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
3097 global $CFG, $PAGE, $SITE;
3098 $issite = (is_object($courseorid) and $courseorid->id == SITEID)
3099 or (!is_object($courseorid) and $courseorid == SITEID);
3100 if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
3101 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
3102 // db queries so this is not really a performance concern, however it is obviously
3103 // better if you use get_fast_modinfo to get the cm before calling this.
3104 if (is_object($courseorid)) {
3105 $course = $courseorid;
3106 } else {
3107 $course = clone($SITE);
3109 $modinfo = get_fast_modinfo($course);
3110 $cm = $modinfo->get_cm($cm->id);
3112 if (!empty($CFG->forcelogin)) {
3113 // login required for both SITE and courses
3114 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3116 } else if ($issite && !empty($cm) and !$cm->uservisible) {
3117 // always login for hidden activities
3118 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3120 } else if ($issite) {
3121 //login for SITE not required
3122 if ($cm and empty($cm->visible)) {
3123 // hidden activities are not accessible without login
3124 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3125 } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
3126 // not-logged-in users do not have any group membership
3127 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3128 } else {
3129 // We still need to instatiate PAGE vars properly so that things
3130 // that rely on it like navigation function correctly.
3131 if (!empty($courseorid)) {
3132 if (is_object($courseorid)) {
3133 $course = $courseorid;
3134 } else {
3135 $course = clone($SITE);
3137 if ($cm) {
3138 if ($cm->course != $course->id) {
3139 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
3141 $PAGE->set_cm($cm, $course);
3142 $PAGE->set_pagelayout('incourse');
3143 } else {
3144 $PAGE->set_course($course);
3146 } else {
3147 // If $PAGE->course, and hence $PAGE->context, have not already been set
3148 // up properly, set them up now.
3149 $PAGE->set_course($PAGE->course);
3151 //TODO: verify conditional activities here
3152 user_accesstime_log(SITEID);
3153 return;
3156 } else {
3157 // course login always required
3158 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3163 * Require key login. Function terminates with error if key not found or incorrect.
3165 * @global object
3166 * @global object
3167 * @global object
3168 * @global object
3169 * @uses NO_MOODLE_COOKIES
3170 * @uses PARAM_ALPHANUM
3171 * @param string $script unique script identifier
3172 * @param int $instance optional instance id
3173 * @return int Instance ID
3175 function require_user_key_login($script, $instance=null) {
3176 global $USER, $SESSION, $CFG, $DB;
3178 if (!NO_MOODLE_COOKIES) {
3179 print_error('sessioncookiesdisable');
3182 /// extra safety
3183 @session_write_close();
3185 $keyvalue = required_param('key', PARAM_ALPHANUM);
3187 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
3188 print_error('invalidkey');
3191 if (!empty($key->validuntil) and $key->validuntil < time()) {
3192 print_error('expiredkey');
3195 if ($key->iprestriction) {
3196 $remoteaddr = getremoteaddr(null);
3197 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
3198 print_error('ipmismatch');
3202 if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
3203 print_error('invaliduserid');
3206 /// emulate normal session
3207 enrol_check_plugins($user);
3208 session_set_user($user);
3210 /// note we are not using normal login
3211 if (!defined('USER_KEY_LOGIN')) {
3212 define('USER_KEY_LOGIN', true);
3215 /// return instance id - it might be empty
3216 return $key->instance;
3220 * Creates a new private user access key.
3222 * @global object
3223 * @param string $script unique target identifier
3224 * @param int $userid
3225 * @param int $instance optional instance id
3226 * @param string $iprestriction optional ip restricted access
3227 * @param timestamp $validuntil key valid only until given data
3228 * @return string access key value
3230 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3231 global $DB;
3233 $key = new stdClass();
3234 $key->script = $script;
3235 $key->userid = $userid;
3236 $key->instance = $instance;
3237 $key->iprestriction = $iprestriction;
3238 $key->validuntil = $validuntil;
3239 $key->timecreated = time();
3241 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
3242 while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
3243 // must be unique
3244 $key->value = md5($userid.'_'.time().random_string(40));
3246 $DB->insert_record('user_private_key', $key);
3247 return $key->value;
3251 * Delete the user's new private user access keys for a particular script.
3253 * @global object
3254 * @param string $script unique target identifier
3255 * @param int $userid
3256 * @return void
3258 function delete_user_key($script,$userid) {
3259 global $DB;
3260 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
3264 * Gets a private user access key (and creates one if one doesn't exist).
3266 * @global object
3267 * @param string $script unique target identifier
3268 * @param int $userid
3269 * @param int $instance optional instance id
3270 * @param string $iprestriction optional ip restricted access
3271 * @param timestamp $validuntil key valid only until given data
3272 * @return string access key value
3274 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3275 global $DB;
3277 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
3278 'instance'=>$instance, 'iprestriction'=>$iprestriction,
3279 'validuntil'=>$validuntil))) {
3280 return $key->value;
3281 } else {
3282 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
3288 * Modify the user table by setting the currently logged in user's
3289 * last login to now.
3291 * @global object
3292 * @global object
3293 * @return bool Always returns true
3295 function update_user_login_times() {
3296 global $USER, $DB;
3298 if (isguestuser()) {
3299 // Do not update guest access times/ips for performance.
3300 return true;
3303 $now = time();
3305 $user = new stdClass();
3306 $user->id = $USER->id;
3308 // Make sure all users that logged in have some firstaccess.
3309 if ($USER->firstaccess == 0) {
3310 $USER->firstaccess = $user->firstaccess = $now;
3313 // Store the previous current as lastlogin.
3314 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
3316 $USER->currentlogin = $user->currentlogin = $now;
3318 // Function user_accesstime_log() may not update immediately, better do it here.
3319 $USER->lastaccess = $user->lastaccess = $now;
3320 $USER->lastip = $user->lastip = getremoteaddr();
3322 $DB->update_record('user', $user);
3323 return true;
3327 * Determines if a user has completed setting up their account.
3329 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
3330 * @return bool
3332 function user_not_fully_set_up($user) {
3333 if (isguestuser($user)) {
3334 return false;
3336 return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
3340 * Check whether the user has exceeded the bounce threshold
3342 * @global object
3343 * @global object
3344 * @param user $user A {@link $USER} object
3345 * @return bool true=>User has exceeded bounce threshold
3347 function over_bounce_threshold($user) {
3348 global $CFG, $DB;
3350 if (empty($CFG->handlebounces)) {
3351 return false;
3354 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3355 return false;
3358 // set sensible defaults
3359 if (empty($CFG->minbounces)) {
3360 $CFG->minbounces = 10;
3362 if (empty($CFG->bounceratio)) {
3363 $CFG->bounceratio = .20;
3365 $bouncecount = 0;
3366 $sendcount = 0;
3367 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3368 $bouncecount = $bounce->value;
3370 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3371 $sendcount = $send->value;
3373 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
3377 * Used to increment or reset email sent count
3379 * @global object
3380 * @param user $user object containing an id
3381 * @param bool $reset will reset the count to 0
3382 * @return void
3384 function set_send_count($user,$reset=false) {
3385 global $DB;
3387 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3388 return;
3391 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3392 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3393 $DB->update_record('user_preferences', $pref);
3395 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3396 // make a new one
3397 $pref = new stdClass();
3398 $pref->name = 'email_send_count';
3399 $pref->value = 1;
3400 $pref->userid = $user->id;
3401 $DB->insert_record('user_preferences', $pref, false);
3406 * Increment or reset user's email bounce count
3408 * @global object
3409 * @param user $user object containing an id
3410 * @param bool $reset will reset the count to 0
3412 function set_bounce_count($user,$reset=false) {
3413 global $DB;
3415 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3416 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3417 $DB->update_record('user_preferences', $pref);
3419 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3420 // make a new one
3421 $pref = new stdClass();
3422 $pref->name = 'email_bounce_count';
3423 $pref->value = 1;
3424 $pref->userid = $user->id;
3425 $DB->insert_record('user_preferences', $pref, false);
3430 * Keeps track of login attempts
3432 * @global object
3434 function update_login_count() {
3435 global $SESSION;
3437 $max_logins = 10;
3439 if (empty($SESSION->logincount)) {
3440 $SESSION->logincount = 1;
3441 } else {
3442 $SESSION->logincount++;
3445 if ($SESSION->logincount > $max_logins) {
3446 unset($SESSION->wantsurl);
3447 print_error('errortoomanylogins');
3452 * Resets login attempts
3454 * @global object
3456 function reset_login_count() {
3457 global $SESSION;
3459 $SESSION->logincount = 0;
3463 * Determines if the currently logged in user is in editing mode.
3464 * Note: originally this function had $userid parameter - it was not usable anyway
3466 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3467 * @todo Deprecated function remove when ready
3469 * @global object
3470 * @uses DEBUG_DEVELOPER
3471 * @return bool
3473 function isediting() {
3474 global $PAGE;
3475 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
3476 return $PAGE->user_is_editing();
3480 * Determines if the logged in user is currently moving an activity
3482 * @global object
3483 * @param int $courseid The id of the course being tested
3484 * @return bool
3486 function ismoving($courseid) {
3487 global $USER;
3489 if (!empty($USER->activitycopy)) {
3490 return ($USER->activitycopycourse == $courseid);
3492 return false;
3496 * Returns a persons full name
3498 * Given an object containing firstname and lastname
3499 * values, this function returns a string with the
3500 * full name of the person.
3501 * The result may depend on system settings
3502 * or language. 'override' will force both names
3503 * to be used even if system settings specify one.
3505 * @global object
3506 * @global object
3507 * @param object $user A {@link $USER} object to get full name of
3508 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3509 * @return string
3511 function fullname($user, $override=false) {
3512 global $CFG, $SESSION;
3514 if (!isset($user->firstname) and !isset($user->lastname)) {
3515 return '';
3518 if (!$override) {
3519 if (!empty($CFG->forcefirstname)) {
3520 $user->firstname = $CFG->forcefirstname;
3522 if (!empty($CFG->forcelastname)) {
3523 $user->lastname = $CFG->forcelastname;
3527 if (!empty($SESSION->fullnamedisplay)) {
3528 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3531 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
3532 return $user->firstname .' '. $user->lastname;
3534 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
3535 return $user->lastname .' '. $user->firstname;
3537 } else if ($CFG->fullnamedisplay == 'firstname') {
3538 if ($override) {
3539 return get_string('fullnamedisplay', '', $user);
3540 } else {
3541 return $user->firstname;
3545 return get_string('fullnamedisplay', '', $user);
3549 * Checks if current user is shown any extra fields when listing users.
3550 * @param object $context Context
3551 * @param array $already Array of fields that we're going to show anyway
3552 * so don't bother listing them
3553 * @return array Array of field names from user table, not including anything
3554 * listed in $already
3556 function get_extra_user_fields($context, $already = array()) {
3557 global $CFG;
3559 // Only users with permission get the extra fields
3560 if (!has_capability('moodle/site:viewuseridentity', $context)) {
3561 return array();
3564 // Split showuseridentity on comma
3565 if (empty($CFG->showuseridentity)) {
3566 // Explode gives wrong result with empty string
3567 $extra = array();
3568 } else {
3569 $extra = explode(',', $CFG->showuseridentity);
3571 $renumber = false;
3572 foreach ($extra as $key => $field) {
3573 if (in_array($field, $already)) {
3574 unset($extra[$key]);
3575 $renumber = true;
3578 if ($renumber) {
3579 // For consistency, if entries are removed from array, renumber it
3580 // so they are numbered as you would expect
3581 $extra = array_merge($extra);
3583 return $extra;
3587 * If the current user is to be shown extra user fields when listing or
3588 * selecting users, returns a string suitable for including in an SQL select
3589 * clause to retrieve those fields.
3590 * @param object $context Context
3591 * @param string $alias Alias of user table, e.g. 'u' (default none)
3592 * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none)
3593 * @param array $already Array of fields that we're going to include anyway
3594 * so don't list them (default none)
3595 * @return string Partial SQL select clause, beginning with comma, for example
3596 * ',u.idnumber,u.department' unless it is blank
3598 function get_extra_user_fields_sql($context, $alias='', $prefix='',
3599 $already = array()) {
3600 $fields = get_extra_user_fields($context, $already);
3601 $result = '';
3602 // Add punctuation for alias
3603 if ($alias !== '') {
3604 $alias .= '.';
3606 foreach ($fields as $field) {
3607 $result .= ', ' . $alias . $field;
3608 if ($prefix) {
3609 $result .= ' AS ' . $prefix . $field;
3612 return $result;
3616 * Returns the display name of a field in the user table. Works for most fields
3617 * that are commonly displayed to users.
3618 * @param string $field Field name, e.g. 'phone1'
3619 * @return string Text description taken from language file, e.g. 'Phone number'
3621 function get_user_field_name($field) {
3622 // Some fields have language strings which are not the same as field name
3623 switch ($field) {
3624 case 'phone1' : return get_string('phone');
3625 case 'url' : return get_string('webpage');
3626 case 'icq' : return get_string('icqnumber');
3627 case 'skype' : return get_string('skypeid');
3628 case 'aim' : return get_string('aimid');
3629 case 'yahoo' : return get_string('yahooid');
3630 case 'msn' : return get_string('msnid');
3632 // Otherwise just use the same lang string
3633 return get_string($field);
3637 * Returns whether a given authentication plugin exists.
3639 * @global object
3640 * @param string $auth Form of authentication to check for. Defaults to the
3641 * global setting in {@link $CFG}.
3642 * @return boolean Whether the plugin is available.
3644 function exists_auth_plugin($auth) {
3645 global $CFG;
3647 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3648 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3650 return false;
3654 * Checks if a given plugin is in the list of enabled authentication plugins.
3656 * @param string $auth Authentication plugin.
3657 * @return boolean Whether the plugin is enabled.
3659 function is_enabled_auth($auth) {
3660 if (empty($auth)) {
3661 return false;
3664 $enabled = get_enabled_auth_plugins();
3666 return in_array($auth, $enabled);
3670 * Returns an authentication plugin instance.
3672 * @global object
3673 * @param string $auth name of authentication plugin
3674 * @return auth_plugin_base An instance of the required authentication plugin.
3676 function get_auth_plugin($auth) {
3677 global $CFG;
3679 // check the plugin exists first
3680 if (! exists_auth_plugin($auth)) {
3681 print_error('authpluginnotfound', 'debug', '', $auth);
3684 // return auth plugin instance
3685 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3686 $class = "auth_plugin_$auth";
3687 return new $class;
3691 * Returns array of active auth plugins.
3693 * @param bool $fix fix $CFG->auth if needed
3694 * @return array
3696 function get_enabled_auth_plugins($fix=false) {
3697 global $CFG;
3699 $default = array('manual', 'nologin');
3701 if (empty($CFG->auth)) {
3702 $auths = array();
3703 } else {
3704 $auths = explode(',', $CFG->auth);
3707 if ($fix) {
3708 $auths = array_unique($auths);
3709 foreach($auths as $k=>$authname) {
3710 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3711 unset($auths[$k]);
3714 $newconfig = implode(',', $auths);
3715 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
3716 set_config('auth', $newconfig);
3720 return (array_merge($default, $auths));
3724 * Returns true if an internal authentication method is being used.
3725 * if method not specified then, global default is assumed
3727 * @param string $auth Form of authentication required
3728 * @return bool
3730 function is_internal_auth($auth) {
3731 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3732 return $authplugin->is_internal();
3736 * Returns true if the user is a 'restored' one
3738 * Used in the login process to inform the user
3739 * and allow him/her to reset the password
3741 * @uses $CFG
3742 * @uses $DB
3743 * @param string $username username to be checked
3744 * @return bool
3746 function is_restored_user($username) {
3747 global $CFG, $DB;
3749 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
3753 * Returns an array of user fields
3755 * @return array User field/column names
3757 function get_user_fieldnames() {
3758 global $DB;
3760 $fieldarray = $DB->get_columns('user');
3761 unset($fieldarray['id']);
3762 $fieldarray = array_keys($fieldarray);
3764 return $fieldarray;
3768 * Creates a bare-bones user record
3770 * @todo Outline auth types and provide code example
3772 * @param string $username New user's username to add to record
3773 * @param string $password New user's password to add to record
3774 * @param string $auth Form of authentication required
3775 * @return stdClass A complete user object
3777 function create_user_record($username, $password, $auth = 'manual') {
3778 global $CFG, $DB;
3780 //just in case check text case
3781 $username = trim(textlib::strtolower($username));
3783 $authplugin = get_auth_plugin($auth);
3785 $newuser = new stdClass();
3787 if ($newinfo = $authplugin->get_userinfo($username)) {
3788 $newinfo = truncate_userinfo($newinfo);
3789 foreach ($newinfo as $key => $value){
3790 $newuser->$key = $value;
3794 if (!empty($newuser->email)) {
3795 if (email_is_not_allowed($newuser->email)) {
3796 unset($newuser->email);
3800 if (!isset($newuser->city)) {
3801 $newuser->city = '';
3804 $newuser->auth = $auth;
3805 $newuser->username = $username;
3807 // fix for MDL-8480
3808 // user CFG lang for user if $newuser->lang is empty
3809 // or $user->lang is not an installed language
3810 if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
3811 $newuser->lang = $CFG->lang;
3813 $newuser->confirmed = 1;
3814 $newuser->lastip = getremoteaddr();
3815 $newuser->timecreated = time();
3816 $newuser->timemodified = $newuser->timecreated;
3817 $newuser->mnethostid = $CFG->mnet_localhost_id;
3819 $newuser->id = $DB->insert_record('user', $newuser);
3820 $user = get_complete_user_data('id', $newuser->id);
3821 if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
3822 set_user_preference('auth_forcepasswordchange', 1, $user);
3824 update_internal_user_password($user, $password);
3826 // fetch full user record for the event, the complete user data contains too much info
3827 // and we want to be consistent with other places that trigger this event
3828 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
3830 return $user;
3834 * Will update a local user record from an external source.
3835 * (MNET users can not be updated using this method!)
3837 * @param string $username user's username to update the record
3838 * @return stdClass A complete user object
3840 function update_user_record($username) {
3841 global $DB, $CFG;
3843 $username = trim(textlib::strtolower($username)); /// just in case check text case
3845 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
3846 $newuser = array();
3847 $userauth = get_auth_plugin($oldinfo->auth);
3849 if ($newinfo = $userauth->get_userinfo($username)) {
3850 $newinfo = truncate_userinfo($newinfo);
3851 foreach ($newinfo as $key => $value){
3852 $key = strtolower($key);
3853 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3854 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3855 // unknown or must not be changed
3856 continue;
3858 $confval = $userauth->config->{'field_updatelocal_' . $key};
3859 $lockval = $userauth->config->{'field_lock_' . $key};
3860 if (empty($confval) || empty($lockval)) {
3861 continue;
3863 if ($confval === 'onlogin') {
3864 // MDL-4207 Don't overwrite modified user profile values with
3865 // empty LDAP values when 'unlocked if empty' is set. The purpose
3866 // of the setting 'unlocked if empty' is to allow the user to fill
3867 // in a value for the selected field _if LDAP is giving
3868 // nothing_ for this field. Thus it makes sense to let this value
3869 // stand in until LDAP is giving a value for this field.
3870 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3871 if ((string)$oldinfo->$key !== (string)$value) {
3872 $newuser[$key] = (string)$value;
3877 if ($newuser) {
3878 $newuser['id'] = $oldinfo->id;
3879 $newuser['timemodified'] = time();
3880 $DB->update_record('user', $newuser);
3881 // fetch full user record for the event, the complete user data contains too much info
3882 // and we want to be consistent with other places that trigger this event
3883 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
3887 return get_complete_user_data('id', $oldinfo->id);
3891 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3892 * which may have large fields
3894 * @todo Add vartype handling to ensure $info is an array
3896 * @param array $info Array of user properties to truncate if needed
3897 * @return array The now truncated information that was passed in
3899 function truncate_userinfo($info) {
3900 // define the limits
3901 $limit = array(
3902 'username' => 100,
3903 'idnumber' => 255,
3904 'firstname' => 100,
3905 'lastname' => 100,
3906 'email' => 100,
3907 'icq' => 15,
3908 'phone1' => 20,
3909 'phone2' => 20,
3910 'institution' => 40,
3911 'department' => 30,
3912 'address' => 70,
3913 'city' => 120,
3914 'country' => 2,
3915 'url' => 255,
3918 // apply where needed
3919 foreach (array_keys($info) as $key) {
3920 if (!empty($limit[$key])) {
3921 $info[$key] = trim(textlib::substr($info[$key],0, $limit[$key]));
3925 return $info;
3929 * Marks user deleted in internal user database and notifies the auth plugin.
3930 * Also unenrols user from all roles and does other cleanup.
3932 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3934 * @param stdClass $user full user object before delete
3935 * @return boolean success
3936 * @throws coding_exception if invalid $user parameter detected
3938 function delete_user(stdClass $user) {
3939 global $CFG, $DB;
3940 require_once($CFG->libdir.'/grouplib.php');
3941 require_once($CFG->libdir.'/gradelib.php');
3942 require_once($CFG->dirroot.'/message/lib.php');
3943 require_once($CFG->dirroot.'/tag/lib.php');
3945 // Make sure nobody sends bogus record type as parameter.
3946 if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
3947 throw new coding_exception('Invalid $user parameter in delete_user() detected');
3950 // Better not trust the parameter and fetch the latest info,
3951 // this will be very expensive anyway.
3952 if (!$user = $DB->get_record('user', array('id'=>$user->id))) {
3953 debugging('Attempt to delete unknown user account.');
3954 return false;
3957 // There must be always exactly one guest record,
3958 // originally the guest account was identified by username only,
3959 // now we use $CFG->siteguest for performance reasons.
3960 if ($user->username === 'guest' or isguestuser($user)) {
3961 debugging('Guest user account can not be deleted.');
3962 return false;
3965 // Admin can be theoretically from different auth plugin,
3966 // but we want to prevent deletion of internal accoutns only,
3967 // if anything goes wrong ppl may force somebody to be admin via
3968 // config.php setting $CFG->siteadmins.
3969 if ($user->auth === 'manual' and is_siteadmin($user)) {
3970 debugging('Local administrator accounts can not be deleted.');
3971 return false;
3974 // delete all grades - backup is kept in grade_grades_history table
3975 grade_user_delete($user->id);
3977 //move unread messages from this user to read
3978 message_move_userfrom_unread2read($user->id);
3980 // TODO: remove from cohorts using standard API here
3982 // remove user tags
3983 tag_set('user', $user->id, array());
3985 // unconditionally unenrol from all courses
3986 enrol_user_delete($user);
3988 // unenrol from all roles in all contexts
3989 role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
3991 //now do a brute force cleanup
3993 // remove from all cohorts
3994 $DB->delete_records('cohort_members', array('userid'=>$user->id));
3996 // remove from all groups
3997 $DB->delete_records('groups_members', array('userid'=>$user->id));
3999 // brute force unenrol from all courses
4000 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
4002 // purge user preferences
4003 $DB->delete_records('user_preferences', array('userid'=>$user->id));
4005 // purge user extra profile info
4006 $DB->delete_records('user_info_data', array('userid'=>$user->id));
4008 // last course access not necessary either
4009 $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
4011 // remove all user tokens
4012 $DB->delete_records('external_tokens', array('userid'=>$user->id));
4014 // unauthorise the user for all services
4015 $DB->delete_records('external_services_users', array('userid'=>$user->id));
4017 // force logout - may fail if file based sessions used, sorry
4018 session_kill_user($user->id);
4020 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
4021 delete_context(CONTEXT_USER, $user->id);
4023 // workaround for bulk deletes of users with the same email address
4024 $delname = "$user->email.".time();
4025 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
4026 $delname++;
4029 // mark internal user record as "deleted"
4030 $updateuser = new stdClass();
4031 $updateuser->id = $user->id;
4032 $updateuser->deleted = 1;
4033 $updateuser->username = $delname; // Remember it just in case
4034 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
4035 $updateuser->idnumber = ''; // Clear this field to free it up
4036 $updateuser->picture = 0;
4037 $updateuser->timemodified = time();
4039 $DB->update_record('user', $updateuser);
4040 // Add this action to log
4041 add_to_log(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
4044 // We will update the user's timemodified, as it will be passed to the user_deleted event, which
4045 // should know about this updated property persisted to the user's table.
4046 $user->timemodified = $updateuser->timemodified;
4048 // notify auth plugin - do not block the delete even when plugin fails
4049 $authplugin = get_auth_plugin($user->auth);
4050 $authplugin->user_delete($user);
4052 // any plugin that needs to cleanup should register this event
4053 events_trigger('user_deleted', $user);
4055 return true;
4059 * Retrieve the guest user object
4061 * @global object
4062 * @global object
4063 * @return user A {@link $USER} object
4065 function guest_user() {
4066 global $CFG, $DB;
4068 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
4069 $newuser->confirmed = 1;
4070 $newuser->lang = $CFG->lang;
4071 $newuser->lastip = getremoteaddr();
4074 return $newuser;
4078 * Authenticates a user against the chosen authentication mechanism
4080 * Given a username and password, this function looks them
4081 * up using the currently selected authentication mechanism,
4082 * and if the authentication is successful, it returns a
4083 * valid $user object from the 'user' table.
4085 * Uses auth_ functions from the currently active auth module
4087 * After authenticate_user_login() returns success, you will need to
4088 * log that the user has logged in, and call complete_user_login() to set
4089 * the session up.
4091 * Note: this function works only with non-mnet accounts!
4093 * @param string $username User's username
4094 * @param string $password User's password
4095 * @return user|flase A {@link $USER} object or false if error
4097 function authenticate_user_login($username, $password) {
4098 global $CFG, $DB;
4100 $authsenabled = get_enabled_auth_plugins();
4102 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
4103 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
4104 if (!empty($user->suspended)) {
4105 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4106 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4107 return false;
4109 if ($auth=='nologin' or !is_enabled_auth($auth)) {
4110 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4111 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4112 return false;
4114 $auths = array($auth);
4116 } else {
4117 // Check if there's a deleted record (cheaply), this should not happen because we mangle usernames in delete_user().
4118 if ($DB->get_field('user', 'id', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>1))) {
4119 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4120 return false;
4123 // Do not try to authenticate non-existent accounts when user creation is not disabled.
4124 if (!empty($CFG->authpreventaccountcreation)) {
4125 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4126 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Unknown user, can not create new accounts: $username ".$_SERVER['HTTP_USER_AGENT']);
4127 return false;
4130 // User does not exist
4131 $auths = $authsenabled;
4132 $user = new stdClass();
4133 $user->id = 0;
4136 foreach ($auths as $auth) {
4137 $authplugin = get_auth_plugin($auth);
4139 // on auth fail fall through to the next plugin
4140 if (!$authplugin->user_login($username, $password)) {
4141 continue;
4144 // successful authentication
4145 if ($user->id) { // User already exists in database
4146 if (empty($user->auth)) { // For some reason auth isn't set yet
4147 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
4148 $user->auth = $auth;
4151 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
4153 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
4154 $user = update_user_record($username);
4156 } else {
4157 // Create account, we verified above that user creation is allowed.
4158 $user = create_user_record($username, $password, $auth);
4161 $authplugin->sync_roles($user);
4163 foreach ($authsenabled as $hau) {
4164 $hauth = get_auth_plugin($hau);
4165 $hauth->user_authenticated_hook($user, $username, $password);
4168 if (empty($user->id)) {
4169 return false;
4172 if (!empty($user->suspended)) {
4173 // just in case some auth plugin suspended account
4174 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4175 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4176 return false;
4179 return $user;
4182 // failed if all the plugins have failed
4183 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4184 if (debugging('', DEBUG_ALL)) {
4185 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4187 return false;
4191 * Call to complete the user login process after authenticate_user_login()
4192 * has succeeded. It will setup the $USER variable and other required bits
4193 * and pieces.
4195 * NOTE:
4196 * - It will NOT log anything -- up to the caller to decide what to log.
4197 * - this function does not set any cookies any more!
4199 * @param object $user
4200 * @return object A {@link $USER} object - BC only, do not use
4202 function complete_user_login($user) {
4203 global $CFG, $USER;
4205 // regenerate session id and delete old session,
4206 // this helps prevent session fixation attacks from the same domain
4207 session_regenerate_id(true);
4209 // let enrol plugins deal with new enrolments if necessary
4210 enrol_check_plugins($user);
4212 // check enrolments, load caps and setup $USER object
4213 session_set_user($user);
4215 // reload preferences from DB
4216 unset($USER->preference);
4217 check_user_preferences_loaded($USER);
4219 // update login times
4220 update_user_login_times();
4222 // extra session prefs init
4223 set_login_session_preferences();
4225 if (isguestuser()) {
4226 // no need to continue when user is THE guest
4227 return $USER;
4230 /// Select password change url
4231 $userauth = get_auth_plugin($USER->auth);
4233 /// check whether the user should be changing password
4234 if (get_user_preferences('auth_forcepasswordchange', false)){
4235 if ($userauth->can_change_password()) {
4236 if ($changeurl = $userauth->change_password_url()) {
4237 redirect($changeurl);
4238 } else {
4239 redirect($CFG->httpswwwroot.'/login/change_password.php');
4241 } else {
4242 print_error('nopasswordchangeforced', 'auth');
4245 return $USER;
4249 * Compare password against hash stored in internal user table.
4250 * If necessary it also updates the stored hash to new format.
4252 * @param stdClass $user (password property may be updated)
4253 * @param string $password plain text password
4254 * @return bool is password valid?
4256 function validate_internal_user_password($user, $password) {
4257 global $CFG;
4259 if (!isset($CFG->passwordsaltmain)) {
4260 $CFG->passwordsaltmain = '';
4263 $validated = false;
4265 if ($user->password === 'not cached') {
4266 // internal password is not used at all, it can not validate
4268 } else if ($user->password === md5($password.$CFG->passwordsaltmain)
4269 or $user->password === md5($password)
4270 or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
4271 or $user->password === md5(addslashes($password))) {
4272 // note: we are intentionally using the addslashes() here because we
4273 // need to accept old password hashes of passwords with magic quotes
4274 $validated = true;
4276 } else {
4277 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
4278 $alt = 'passwordsaltalt'.$i;
4279 if (!empty($CFG->$alt)) {
4280 if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
4281 $validated = true;
4282 break;
4288 if ($validated) {
4289 // force update of password hash using latest main password salt and encoding if needed
4290 update_internal_user_password($user, $password);
4293 return $validated;
4297 * Calculate hashed value from password using current hash mechanism.
4299 * @param string $password
4300 * @return string password hash
4302 function hash_internal_user_password($password) {
4303 global $CFG;
4305 if (isset($CFG->passwordsaltmain)) {
4306 return md5($password.$CFG->passwordsaltmain);
4307 } else {
4308 return md5($password);
4313 * Update password hash in user object.
4315 * @param stdClass $user (password property may be updated)
4316 * @param string $password plain text password
4317 * @return bool always returns true
4319 function update_internal_user_password($user, $password) {
4320 global $DB;
4322 $authplugin = get_auth_plugin($user->auth);
4323 if ($authplugin->prevent_local_passwords()) {
4324 $hashedpassword = 'not cached';
4325 } else {
4326 $hashedpassword = hash_internal_user_password($password);
4329 if ($user->password !== $hashedpassword) {
4330 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
4331 $user->password = $hashedpassword;
4334 return true;
4338 * Get a complete user record, which includes all the info
4339 * in the user record.
4341 * Intended for setting as $USER session variable
4343 * @param string $field The user field to be checked for a given value.
4344 * @param string $value The value to match for $field.
4345 * @param int $mnethostid
4346 * @return mixed False, or A {@link $USER} object.
4348 function get_complete_user_data($field, $value, $mnethostid = null) {
4349 global $CFG, $DB;
4351 if (!$field || !$value) {
4352 return false;
4355 /// Build the WHERE clause for an SQL query
4356 $params = array('fieldval'=>$value);
4357 $constraints = "$field = :fieldval AND deleted <> 1";
4359 // If we are loading user data based on anything other than id,
4360 // we must also restrict our search based on mnet host.
4361 if ($field != 'id') {
4362 if (empty($mnethostid)) {
4363 // if empty, we restrict to local users
4364 $mnethostid = $CFG->mnet_localhost_id;
4367 if (!empty($mnethostid)) {
4368 $params['mnethostid'] = $mnethostid;
4369 $constraints .= " AND mnethostid = :mnethostid";
4372 /// Get all the basic user data
4374 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
4375 return false;
4378 /// Get various settings and preferences
4380 // preload preference cache
4381 check_user_preferences_loaded($user);
4383 // load course enrolment related stuff
4384 $user->lastcourseaccess = array(); // during last session
4385 $user->currentcourseaccess = array(); // during current session
4386 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
4387 foreach ($lastaccesses as $lastaccess) {
4388 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
4392 $sql = "SELECT g.id, g.courseid
4393 FROM {groups} g, {groups_members} gm
4394 WHERE gm.groupid=g.id AND gm.userid=?";
4396 // this is a special hack to speedup calendar display
4397 $user->groupmember = array();
4398 if (!isguestuser($user)) {
4399 if ($groups = $DB->get_records_sql($sql, array($user->id))) {
4400 foreach ($groups as $group) {
4401 if (!array_key_exists($group->courseid, $user->groupmember)) {
4402 $user->groupmember[$group->courseid] = array();
4404 $user->groupmember[$group->courseid][$group->id] = $group->id;
4409 /// Add the custom profile fields to the user record
4410 $user->profile = array();
4411 if (!isguestuser($user)) {
4412 require_once($CFG->dirroot.'/user/profile/lib.php');
4413 profile_load_custom_fields($user);
4416 /// Rewrite some variables if necessary
4417 if (!empty($user->description)) {
4418 $user->description = true; // No need to cart all of it around
4420 if (isguestuser($user)) {
4421 $user->lang = $CFG->lang; // Guest language always same as site
4422 $user->firstname = get_string('guestuser'); // Name always in current language
4423 $user->lastname = ' ';
4426 return $user;
4430 * Validate a password against the configured password policy
4432 * @global object
4433 * @param string $password the password to be checked against the password policy
4434 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
4435 * @return bool true if the password is valid according to the policy. false otherwise.
4437 function check_password_policy($password, &$errmsg) {
4438 global $CFG;
4440 if (empty($CFG->passwordpolicy)) {
4441 return true;
4444 $errmsg = '';
4445 if (textlib::strlen($password) < $CFG->minpasswordlength) {
4446 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
4449 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
4450 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
4453 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
4454 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
4457 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
4458 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
4461 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
4462 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
4464 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
4465 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
4468 if ($errmsg == '') {
4469 return true;
4470 } else {
4471 return false;
4477 * When logging in, this function is run to set certain preferences
4478 * for the current SESSION
4480 * @global object
4481 * @global object
4483 function set_login_session_preferences() {
4484 global $SESSION, $CFG;
4486 $SESSION->justloggedin = true;
4488 unset($SESSION->lang);
4493 * Delete a course, including all related data from the database,
4494 * and any associated files.
4496 * @global object
4497 * @global object
4498 * @param mixed $courseorid The id of the course or course object to delete.
4499 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4500 * @return bool true if all the removals succeeded. false if there were any failures. If this
4501 * method returns false, some of the removals will probably have succeeded, and others
4502 * failed, but you have no way of knowing which.
4504 function delete_course($courseorid, $showfeedback = true) {
4505 global $DB;
4507 if (is_object($courseorid)) {
4508 $courseid = $courseorid->id;
4509 $course = $courseorid;
4510 } else {
4511 $courseid = $courseorid;
4512 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
4513 return false;
4516 $context = context_course::instance($courseid);
4518 // frontpage course can not be deleted!!
4519 if ($courseid == SITEID) {
4520 return false;
4523 // make the course completely empty
4524 remove_course_contents($courseid, $showfeedback);
4526 // delete the course and related context instance
4527 delete_context(CONTEXT_COURSE, $courseid);
4529 // We will update the course's timemodified, as it will be passed to the course_deleted event,
4530 // which should know about this updated property, as this event is meant to pass the full course record
4531 $course->timemodified = time();
4533 $DB->delete_records("course", array("id"=>$courseid));
4535 //trigger events
4536 $course->context = $context; // you can not fetch context in the event because it was already deleted
4537 events_trigger('course_deleted', $course);
4539 return true;
4543 * Clear a course out completely, deleting all content
4544 * but don't delete the course itself.
4545 * This function does not verify any permissions.
4547 * Please note this function also deletes all user enrolments,
4548 * enrolment instances and role assignments by default.
4550 * $options:
4551 * - 'keep_roles_and_enrolments' - false by default
4552 * - 'keep_groups_and_groupings' - false by default
4554 * @param int $courseid The id of the course that is being deleted
4555 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4556 * @param array $options extra options
4557 * @return bool true if all the removals succeeded. false if there were any failures. If this
4558 * method returns false, some of the removals will probably have succeeded, and others
4559 * failed, but you have no way of knowing which.
4561 function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
4562 global $CFG, $DB, $OUTPUT;
4563 require_once($CFG->libdir.'/completionlib.php');
4564 require_once($CFG->libdir.'/questionlib.php');
4565 require_once($CFG->libdir.'/gradelib.php');
4566 require_once($CFG->dirroot.'/group/lib.php');
4567 require_once($CFG->dirroot.'/tag/coursetagslib.php');
4568 require_once($CFG->dirroot.'/comment/lib.php');
4569 require_once($CFG->dirroot.'/rating/lib.php');
4571 // NOTE: these concatenated strings are suboptimal, but it is just extra info...
4572 $strdeleted = get_string('deleted').' - ';
4574 // Some crazy wishlist of stuff we should skip during purging of course content
4575 $options = (array)$options;
4577 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
4578 $coursecontext = context_course::instance($courseid);
4579 $fs = get_file_storage();
4581 // Delete course completion information, this has to be done before grades and enrols
4582 $cc = new completion_info($course);
4583 $cc->clear_criteria();
4584 if ($showfeedback) {
4585 echo $OUTPUT->notification($strdeleted.get_string('completion', 'completion'), 'notifysuccess');
4588 // Remove all data from gradebook - this needs to be done before course modules
4589 // because while deleting this information, the system may need to reference
4590 // the course modules that own the grades.
4591 remove_course_grades($courseid, $showfeedback);
4592 remove_grade_letters($coursecontext, $showfeedback);
4594 // Delete course blocks in any all child contexts,
4595 // they may depend on modules so delete them first
4596 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4597 foreach ($childcontexts as $childcontext) {
4598 blocks_delete_all_for_context($childcontext->id);
4600 unset($childcontexts);
4601 blocks_delete_all_for_context($coursecontext->id);
4602 if ($showfeedback) {
4603 echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
4606 // Delete every instance of every module,
4607 // this has to be done before deleting of course level stuff
4608 $locations = get_plugin_list('mod');
4609 foreach ($locations as $modname=>$moddir) {
4610 if ($modname === 'NEWMODULE') {
4611 continue;
4613 if ($module = $DB->get_record('modules', array('name'=>$modname))) {
4614 include_once("$moddir/lib.php"); // Shows php warning only if plugin defective
4615 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4616 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4618 if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
4619 foreach ($instances as $instance) {
4620 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
4621 /// Delete activity context questions and question categories
4622 question_delete_activity($cm, $showfeedback);
4624 if (function_exists($moddelete)) {
4625 // This purges all module data in related tables, extra user prefs, settings, etc.
4626 $moddelete($instance->id);
4627 } else {
4628 // NOTE: we should not allow installation of modules with missing delete support!
4629 debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
4630 $DB->delete_records($modname, array('id'=>$instance->id));
4633 if ($cm) {
4634 // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition
4635 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4636 $DB->delete_records('course_modules', array('id'=>$cm->id));
4640 if (function_exists($moddeletecourse)) {
4641 // Execute ptional course cleanup callback
4642 $moddeletecourse($course, $showfeedback);
4644 if ($instances and $showfeedback) {
4645 echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
4647 } else {
4648 // Ooops, this module is not properly installed, force-delete it in the next block
4652 // We have tried to delete everything the nice way - now let's force-delete any remaining module data
4654 // Remove all data from availability and completion tables that is associated
4655 // with course-modules belonging to this course. Note this is done even if the
4656 // features are not enabled now, in case they were enabled previously.
4657 $DB->delete_records_select('course_modules_completion',
4658 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4659 array($courseid));
4660 $DB->delete_records_select('course_modules_availability',
4661 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4662 array($courseid));
4663 $DB->delete_records_select('course_modules_avail_fields',
4664 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4665 array($courseid));
4667 // Remove course-module data.
4668 $cms = $DB->get_records('course_modules', array('course'=>$course->id));
4669 foreach ($cms as $cm) {
4670 if ($module = $DB->get_record('modules', array('id'=>$cm->module))) {
4671 try {
4672 $DB->delete_records($module->name, array('id'=>$cm->instance));
4673 } catch (Exception $e) {
4674 // Ignore weird or missing table problems
4677 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4678 $DB->delete_records('course_modules', array('id'=>$cm->id));
4681 if ($showfeedback) {
4682 echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
4685 // Cleanup the rest of plugins
4686 $cleanuplugintypes = array('report', 'coursereport', 'format');
4687 foreach ($cleanuplugintypes as $type) {
4688 $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
4689 foreach ($plugins as $plugin=>$pluginfunction) {
4690 $pluginfunction($course->id, $showfeedback);
4692 if ($showfeedback) {
4693 echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
4697 // Delete questions and question categories
4698 question_delete_course($course, $showfeedback);
4699 if ($showfeedback) {
4700 echo $OUTPUT->notification($strdeleted.get_string('questions', 'question'), 'notifysuccess');
4703 // Make sure there are no subcontexts left - all valid blocks and modules should be already gone
4704 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4705 foreach ($childcontexts as $childcontext) {
4706 $childcontext->delete();
4708 unset($childcontexts);
4710 // Remove all roles and enrolments by default
4711 if (empty($options['keep_roles_and_enrolments'])) {
4712 // this hack is used in restore when deleting contents of existing course
4713 role_unassign_all(array('contextid'=>$coursecontext->id, 'component'=>''), true);
4714 enrol_course_delete($course);
4715 if ($showfeedback) {
4716 echo $OUTPUT->notification($strdeleted.get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
4720 // Delete any groups, removing members and grouping/course links first.
4721 if (empty($options['keep_groups_and_groupings'])) {
4722 groups_delete_groupings($course->id, $showfeedback);
4723 groups_delete_groups($course->id, $showfeedback);
4726 // filters be gone!
4727 filter_delete_all_for_context($coursecontext->id);
4729 // die comments!
4730 comment::delete_comments($coursecontext->id);
4732 // ratings are history too
4733 $delopt = new stdclass();
4734 $delopt->contextid = $coursecontext->id;
4735 $rm = new rating_manager();
4736 $rm->delete_ratings($delopt);
4738 // Delete course tags
4739 coursetag_delete_course_tags($course->id, $showfeedback);
4741 // Delete calendar events
4742 $DB->delete_records('event', array('courseid'=>$course->id));
4743 $fs->delete_area_files($coursecontext->id, 'calendar');
4745 // Delete all related records in other core tables that may have a courseid
4746 // This array stores the tables that need to be cleared, as
4747 // table_name => column_name that contains the course id.
4748 $tablestoclear = array(
4749 'log' => 'course', // Course logs (NOTE: this might be changed in the future)
4750 'backup_courses' => 'courseid', // Scheduled backup stuff
4751 'user_lastaccess' => 'courseid', // User access info
4753 foreach ($tablestoclear as $table => $col) {
4754 $DB->delete_records($table, array($col=>$course->id));
4757 // delete all course backup files
4758 $fs->delete_area_files($coursecontext->id, 'backup');
4760 // cleanup course record - remove links to deleted stuff
4761 $oldcourse = new stdClass();
4762 $oldcourse->id = $course->id;
4763 $oldcourse->summary = '';
4764 $oldcourse->modinfo = NULL;
4765 $oldcourse->legacyfiles = 0;
4766 $oldcourse->enablecompletion = 0;
4767 if (!empty($options['keep_groups_and_groupings'])) {
4768 $oldcourse->defaultgroupingid = 0;
4770 $DB->update_record('course', $oldcourse);
4772 // Delete course sections and availability options.
4773 $DB->delete_records_select('course_sections_availability',
4774 'coursesectionid IN (SELECT id from {course_sections} WHERE course=?)',
4775 array($course->id));
4776 $DB->delete_records_select('course_sections_avail_fields',
4777 'coursesectionid IN (SELECT id from {course_sections} WHERE course=?)',
4778 array($course->id));
4779 $DB->delete_records('course_sections', array('course'=>$course->id));
4781 // delete legacy, section and any other course files
4782 $fs->delete_area_files($coursecontext->id, 'course'); // files from summary and section
4784 // Delete all remaining stuff linked to context such as files, comments, ratings, etc.
4785 if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
4786 // Easy, do not delete the context itself...
4787 $coursecontext->delete_content();
4789 } else {
4790 // Hack alert!!!!
4791 // We can not drop all context stuff because it would bork enrolments and roles,
4792 // there might be also files used by enrol plugins...
4795 // Delete legacy files - just in case some files are still left there after conversion to new file api,
4796 // also some non-standard unsupported plugins may try to store something there
4797 fulldelete($CFG->dataroot.'/'.$course->id);
4799 // Finally trigger the event
4800 $course->context = $coursecontext; // you can not access context in cron event later after course is deleted
4801 $course->options = $options; // not empty if we used any crazy hack
4802 events_trigger('course_content_removed', $course);
4804 return true;
4808 * Change dates in module - used from course reset.
4810 * @global object
4811 * @global object
4812 * @param string $modname forum, assignment, etc
4813 * @param array $fields array of date fields from mod table
4814 * @param int $timeshift time difference
4815 * @param int $courseid
4816 * @return bool success
4818 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4819 global $CFG, $DB;
4820 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
4822 $return = true;
4823 foreach ($fields as $field) {
4824 $updatesql = "UPDATE {".$modname."}
4825 SET $field = $field + ?
4826 WHERE course=? AND $field<>0";
4827 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4830 $refreshfunction = $modname.'_refresh_events';
4831 if (function_exists($refreshfunction)) {
4832 $refreshfunction($courseid);
4835 return $return;
4839 * This function will empty a course of user data.
4840 * It will retain the activities and the structure of the course.
4842 * @param object $data an object containing all the settings including courseid (without magic quotes)
4843 * @return array status array of array component, item, error
4845 function reset_course_userdata($data) {
4846 global $CFG, $USER, $DB;
4847 require_once($CFG->libdir.'/gradelib.php');
4848 require_once($CFG->libdir.'/completionlib.php');
4849 require_once($CFG->dirroot.'/group/lib.php');
4851 $data->courseid = $data->id;
4852 $context = context_course::instance($data->courseid);
4854 // calculate the time shift of dates
4855 if (!empty($data->reset_start_date)) {
4856 // time part of course startdate should be zero
4857 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
4858 } else {
4859 $data->timeshift = 0;
4862 // result array: component, item, error
4863 $status = array();
4865 // start the resetting
4866 $componentstr = get_string('general');
4868 // move the course start time
4869 if (!empty($data->reset_start_date) and $data->timeshift) {
4870 // change course start data
4871 $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
4872 // update all course and group events - do not move activity events
4873 $updatesql = "UPDATE {event}
4874 SET timestart = timestart + ?
4875 WHERE courseid=? AND instance=0";
4876 $DB->execute($updatesql, array($data->timeshift, $data->courseid));
4878 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4881 if (!empty($data->reset_logs)) {
4882 $DB->delete_records('log', array('course'=>$data->courseid));
4883 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4886 if (!empty($data->reset_events)) {
4887 $DB->delete_records('event', array('courseid'=>$data->courseid));
4888 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4891 if (!empty($data->reset_notes)) {
4892 require_once($CFG->dirroot.'/notes/lib.php');
4893 note_delete_all($data->courseid);
4894 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4897 if (!empty($data->delete_blog_associations)) {
4898 require_once($CFG->dirroot.'/blog/lib.php');
4899 blog_remove_associations_for_course($data->courseid);
4900 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4903 if (!empty($data->reset_completion)) {
4904 // Delete course and activity completion information.
4905 $course = $DB->get_record('course', array('id'=>$data->courseid));
4906 $cc = new completion_info($course);
4907 $cc->delete_all_completion_data();
4908 $status[] = array('component' => $componentstr,
4909 'item' => get_string('deletecompletiondata', 'completion'), 'error' => false);
4912 $componentstr = get_string('roles');
4914 if (!empty($data->reset_roles_overrides)) {
4915 $children = get_child_contexts($context);
4916 foreach ($children as $child) {
4917 $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
4919 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
4920 //force refresh for logged in users
4921 mark_context_dirty($context->path);
4922 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4925 if (!empty($data->reset_roles_local)) {
4926 $children = get_child_contexts($context);
4927 foreach ($children as $child) {
4928 role_unassign_all(array('contextid'=>$child->id));
4930 //force refresh for logged in users
4931 mark_context_dirty($context->path);
4932 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4935 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4936 $data->unenrolled = array();
4937 if (!empty($data->unenrol_users)) {
4938 $plugins = enrol_get_plugins(true);
4939 $instances = enrol_get_instances($data->courseid, true);
4940 foreach ($instances as $key=>$instance) {
4941 if (!isset($plugins[$instance->enrol])) {
4942 unset($instances[$key]);
4943 continue;
4947 foreach($data->unenrol_users as $withroleid) {
4948 if ($withroleid) {
4949 $sql = "SELECT ue.*
4950 FROM {user_enrolments} ue
4951 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4952 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4953 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4954 $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
4956 } else {
4957 // without any role assigned at course context
4958 $sql = "SELECT ue.*
4959 FROM {user_enrolments} ue
4960 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4961 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4962 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)
4963 WHERE ra.id IS NULL";
4964 $params = array('courseid'=>$data->courseid, 'courselevel'=>CONTEXT_COURSE);
4967 $rs = $DB->get_recordset_sql($sql, $params);
4968 foreach ($rs as $ue) {
4969 if (!isset($instances[$ue->enrolid])) {
4970 continue;
4972 $instance = $instances[$ue->enrolid];
4973 $plugin = $plugins[$instance->enrol];
4974 if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
4975 continue;
4978 $plugin->unenrol_user($instance, $ue->userid);
4979 $data->unenrolled[$ue->userid] = $ue->userid;
4981 $rs->close();
4984 if (!empty($data->unenrolled)) {
4985 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
4989 $componentstr = get_string('groups');
4991 // remove all group members
4992 if (!empty($data->reset_groups_members)) {
4993 groups_delete_group_members($data->courseid);
4994 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
4997 // remove all groups
4998 if (!empty($data->reset_groups_remove)) {
4999 groups_delete_groups($data->courseid, false);
5000 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
5003 // remove all grouping members
5004 if (!empty($data->reset_groupings_members)) {
5005 groups_delete_groupings_groups($data->courseid, false);
5006 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
5009 // remove all groupings
5010 if (!empty($data->reset_groupings_remove)) {
5011 groups_delete_groupings($data->courseid, false);
5012 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
5015 // Look in every instance of every module for data to delete
5016 $unsupported_mods = array();
5017 if ($allmods = $DB->get_records('modules') ) {
5018 foreach ($allmods as $mod) {
5019 $modname = $mod->name;
5020 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
5021 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
5022 if (file_exists($modfile)) {
5023 if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
5024 continue; // Skip mods with no instances
5026 include_once($modfile);
5027 if (function_exists($moddeleteuserdata)) {
5028 $modstatus = $moddeleteuserdata($data);
5029 if (is_array($modstatus)) {
5030 $status = array_merge($status, $modstatus);
5031 } else {
5032 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
5034 } else {
5035 $unsupported_mods[] = $mod;
5037 } else {
5038 debugging('Missing lib.php in '.$modname.' module!');
5043 // mention unsupported mods
5044 if (!empty($unsupported_mods)) {
5045 foreach($unsupported_mods as $mod) {
5046 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
5051 $componentstr = get_string('gradebook', 'grades');
5052 // reset gradebook
5053 if (!empty($data->reset_gradebook_items)) {
5054 remove_course_grades($data->courseid, false);
5055 grade_grab_course_grades($data->courseid);
5056 grade_regrade_final_grades($data->courseid);
5057 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
5059 } else if (!empty($data->reset_gradebook_grades)) {
5060 grade_course_reset($data->courseid);
5061 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
5063 // reset comments
5064 if (!empty($data->reset_comments)) {
5065 require_once($CFG->dirroot.'/comment/lib.php');
5066 comment::reset_course_page_comments($context);
5069 return $status;
5073 * Generate an email processing address
5075 * @param int $modid
5076 * @param string $modargs
5077 * @return string Returns email processing address
5079 function generate_email_processing_address($modid,$modargs) {
5080 global $CFG;
5082 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
5083 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
5089 * @todo Finish documenting this function
5091 * @global object
5092 * @param string $modargs
5093 * @param string $body Currently unused
5095 function moodle_process_email($modargs,$body) {
5096 global $DB;
5098 // the first char should be an unencoded letter. We'll take this as an action
5099 switch ($modargs{0}) {
5100 case 'B': { // bounce
5101 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
5102 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
5103 // check the half md5 of their email
5104 $md5check = substr(md5($user->email),0,16);
5105 if ($md5check == substr($modargs, -16)) {
5106 set_bounce_count($user);
5108 // else maybe they've already changed it?
5111 break;
5112 // maybe more later?
5116 /// CORRESPONDENCE ////////////////////////////////////////////////
5119 * Get mailer instance, enable buffering, flush buffer or disable buffering.
5121 * @global object
5122 * @param string $action 'get', 'buffer', 'close' or 'flush'
5123 * @return object|null mailer instance if 'get' used or nothing
5125 function get_mailer($action='get') {
5126 global $CFG;
5128 static $mailer = null;
5129 static $counter = 0;
5131 if (!isset($CFG->smtpmaxbulk)) {
5132 $CFG->smtpmaxbulk = 1;
5135 if ($action == 'get') {
5136 $prevkeepalive = false;
5138 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5139 if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
5140 $counter++;
5141 // reset the mailer
5142 $mailer->Priority = 3;
5143 $mailer->CharSet = 'UTF-8'; // our default
5144 $mailer->ContentType = "text/plain";
5145 $mailer->Encoding = "8bit";
5146 $mailer->From = "root@localhost";
5147 $mailer->FromName = "Root User";
5148 $mailer->Sender = "";
5149 $mailer->Subject = "";
5150 $mailer->Body = "";
5151 $mailer->AltBody = "";
5152 $mailer->ConfirmReadingTo = "";
5154 $mailer->ClearAllRecipients();
5155 $mailer->ClearReplyTos();
5156 $mailer->ClearAttachments();
5157 $mailer->ClearCustomHeaders();
5158 return $mailer;
5161 $prevkeepalive = $mailer->SMTPKeepAlive;
5162 get_mailer('flush');
5165 include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
5166 $mailer = new moodle_phpmailer();
5168 $counter = 1;
5170 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
5171 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
5172 $mailer->CharSet = 'UTF-8';
5174 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
5175 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
5176 $mailer->LE = "\r\n";
5177 } else {
5178 $mailer->LE = "\n";
5181 if ($CFG->smtphosts == 'qmail') {
5182 $mailer->IsQmail(); // use Qmail system
5184 } else if (empty($CFG->smtphosts)) {
5185 $mailer->IsMail(); // use PHP mail() = sendmail
5187 } else {
5188 $mailer->IsSMTP(); // use SMTP directly
5189 if (!empty($CFG->debugsmtp)) {
5190 $mailer->SMTPDebug = true;
5192 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
5193 $mailer->SMTPSecure = $CFG->smtpsecure; // specify secure connection protocol
5194 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
5196 if ($CFG->smtpuser) { // Use SMTP authentication
5197 $mailer->SMTPAuth = true;
5198 $mailer->Username = $CFG->smtpuser;
5199 $mailer->Password = $CFG->smtppass;
5203 return $mailer;
5206 $nothing = null;
5208 // keep smtp session open after sending
5209 if ($action == 'buffer') {
5210 if (!empty($CFG->smtpmaxbulk)) {
5211 get_mailer('flush');
5212 $m = get_mailer();
5213 if ($m->Mailer == 'smtp') {
5214 $m->SMTPKeepAlive = true;
5217 return $nothing;
5220 // close smtp session, but continue buffering
5221 if ($action == 'flush') {
5222 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5223 if (!empty($mailer->SMTPDebug)) {
5224 echo '<pre>'."\n";
5226 $mailer->SmtpClose();
5227 if (!empty($mailer->SMTPDebug)) {
5228 echo '</pre>';
5231 return $nothing;
5234 // close smtp session, do not buffer anymore
5235 if ($action == 'close') {
5236 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5237 get_mailer('flush');
5238 $mailer->SMTPKeepAlive = false;
5240 $mailer = null; // better force new instance
5241 return $nothing;
5246 * Send an email to a specified user
5248 * @global object
5249 * @global string
5250 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
5251 * @uses SITEID
5252 * @param stdClass $user A {@link $USER} object
5253 * @param stdClass $from A {@link $USER} object
5254 * @param string $subject plain text subject line of the email
5255 * @param string $messagetext plain text version of the message
5256 * @param string $messagehtml complete html version of the message (optional)
5257 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
5258 * @param string $attachname the name of the file (extension indicates MIME)
5259 * @param bool $usetrueaddress determines whether $from email address should
5260 * be sent out. Will be overruled by user profile setting for maildisplay
5261 * @param string $replyto Email address to reply to
5262 * @param string $replytoname Name of reply to recipient
5263 * @param int $wordwrapwidth custom word wrap width, default 79
5264 * @return bool Returns true if mail was sent OK and false if there was an error.
5266 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
5268 global $CFG;
5270 if (empty($user) || empty($user->email)) {
5271 $nulluser = 'User is null or has no email';
5272 error_log($nulluser);
5273 if (CLI_SCRIPT) {
5274 mtrace('Error: lib/moodlelib.php email_to_user(): '.$nulluser);
5276 return false;
5279 if (!empty($user->deleted)) {
5280 // do not mail deleted users
5281 $userdeleted = 'User is deleted';
5282 error_log($userdeleted);
5283 if (CLI_SCRIPT) {
5284 mtrace('Error: lib/moodlelib.php email_to_user(): '.$userdeleted);
5286 return false;
5289 if (!empty($CFG->noemailever)) {
5290 // hidden setting for development sites, set in config.php if needed
5291 $noemail = 'Not sending email due to noemailever config setting';
5292 error_log($noemail);
5293 if (CLI_SCRIPT) {
5294 mtrace('Error: lib/moodlelib.php email_to_user(): '.$noemail);
5296 return true;
5299 if (!empty($CFG->divertallemailsto)) {
5300 $subject = "[DIVERTED {$user->email}] $subject";
5301 $user = clone($user);
5302 $user->email = $CFG->divertallemailsto;
5305 // skip mail to suspended users
5306 if ((isset($user->auth) && $user->auth=='nologin') or (isset($user->suspended) && $user->suspended)) {
5307 return true;
5310 if (!validate_email($user->email)) {
5311 // we can not send emails to invalid addresses - it might create security issue or confuse the mailer
5312 $invalidemail = "User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.";
5313 error_log($invalidemail);
5314 if (CLI_SCRIPT) {
5315 mtrace('Error: lib/moodlelib.php email_to_user(): '.$invalidemail);
5317 return false;
5320 if (over_bounce_threshold($user)) {
5321 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
5322 error_log($bouncemsg);
5323 if (CLI_SCRIPT) {
5324 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
5326 return false;
5329 // If the user is a remote mnet user, parse the email text for URL to the
5330 // wwwroot and modify the url to direct the user's browser to login at their
5331 // home site (identity provider - idp) before hitting the link itself
5332 if (is_mnet_remote_user($user)) {
5333 require_once($CFG->dirroot.'/mnet/lib.php');
5335 $jumpurl = mnet_get_idp_jump_url($user);
5336 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
5338 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
5339 $callback,
5340 $messagetext);
5341 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
5342 $callback,
5343 $messagehtml);
5345 $mail = get_mailer();
5347 if (!empty($mail->SMTPDebug)) {
5348 echo '<pre>' . "\n";
5351 $temprecipients = array();
5352 $tempreplyto = array();
5354 $supportuser = generate_email_supportuser();
5356 // make up an email address for handling bounces
5357 if (!empty($CFG->handlebounces)) {
5358 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
5359 $mail->Sender = generate_email_processing_address(0,$modargs);
5360 } else {
5361 $mail->Sender = $supportuser->email;
5364 if (is_string($from)) { // So we can pass whatever we want if there is need
5365 $mail->From = $CFG->noreplyaddress;
5366 $mail->FromName = $from;
5367 } else if ($usetrueaddress and $from->maildisplay) {
5368 $mail->From = $from->email;
5369 $mail->FromName = fullname($from);
5370 } else {
5371 $mail->From = $CFG->noreplyaddress;
5372 $mail->FromName = fullname($from);
5373 if (empty($replyto)) {
5374 $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
5378 if (!empty($replyto)) {
5379 $tempreplyto[] = array($replyto, $replytoname);
5382 $mail->Subject = substr($subject, 0, 900);
5384 $temprecipients[] = array($user->email, fullname($user));
5386 $mail->WordWrap = $wordwrapwidth; // set word wrap
5388 if (!empty($from->customheaders)) { // Add custom headers
5389 if (is_array($from->customheaders)) {
5390 foreach ($from->customheaders as $customheader) {
5391 $mail->AddCustomHeader($customheader);
5393 } else {
5394 $mail->AddCustomHeader($from->customheaders);
5398 if (!empty($from->priority)) {
5399 $mail->Priority = $from->priority;
5402 if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
5403 $mail->IsHTML(true);
5404 $mail->Encoding = 'quoted-printable'; // Encoding to use
5405 $mail->Body = $messagehtml;
5406 $mail->AltBody = "\n$messagetext\n";
5407 } else {
5408 $mail->IsHTML(false);
5409 $mail->Body = "\n$messagetext\n";
5412 if ($attachment && $attachname) {
5413 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
5414 $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
5415 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
5416 } else {
5417 require_once($CFG->libdir.'/filelib.php');
5418 $mimetype = mimeinfo('type', $attachname);
5419 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
5423 // Check if the email should be sent in an other charset then the default UTF-8
5424 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
5426 // use the defined site mail charset or eventually the one preferred by the recipient
5427 $charset = $CFG->sitemailcharset;
5428 if (!empty($CFG->allowusermailcharset)) {
5429 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
5430 $charset = $useremailcharset;
5434 // convert all the necessary strings if the charset is supported
5435 $charsets = get_list_of_charsets();
5436 unset($charsets['UTF-8']);
5437 if (in_array($charset, $charsets)) {
5438 $mail->CharSet = $charset;
5439 $mail->FromName = textlib::convert($mail->FromName, 'utf-8', strtolower($charset));
5440 $mail->Subject = textlib::convert($mail->Subject, 'utf-8', strtolower($charset));
5441 $mail->Body = textlib::convert($mail->Body, 'utf-8', strtolower($charset));
5442 $mail->AltBody = textlib::convert($mail->AltBody, 'utf-8', strtolower($charset));
5444 foreach ($temprecipients as $key => $values) {
5445 $temprecipients[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5447 foreach ($tempreplyto as $key => $values) {
5448 $tempreplyto[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5453 foreach ($temprecipients as $values) {
5454 $mail->AddAddress($values[0], $values[1]);
5456 foreach ($tempreplyto as $values) {
5457 $mail->AddReplyTo($values[0], $values[1]);
5460 if ($mail->Send()) {
5461 set_send_count($user);
5462 $mail->IsSMTP(); // use SMTP directly
5463 if (!empty($mail->SMTPDebug)) {
5464 echo '</pre>';
5466 return true;
5467 } else {
5468 add_to_log(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: '. $mail->ErrorInfo);
5469 if (CLI_SCRIPT) {
5470 mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
5472 if (!empty($mail->SMTPDebug)) {
5473 echo '</pre>';
5475 return false;
5480 * Generate a signoff for emails based on support settings
5482 * @global object
5483 * @return string
5485 function generate_email_signoff() {
5486 global $CFG;
5488 $signoff = "\n";
5489 if (!empty($CFG->supportname)) {
5490 $signoff .= $CFG->supportname."\n";
5492 if (!empty($CFG->supportemail)) {
5493 $signoff .= $CFG->supportemail."\n";
5495 if (!empty($CFG->supportpage)) {
5496 $signoff .= $CFG->supportpage."\n";
5498 return $signoff;
5502 * Generate a fake user for emails based on support settings
5503 * @global object
5504 * @return object user info
5506 function generate_email_supportuser() {
5507 global $CFG;
5509 static $supportuser;
5511 if (!empty($supportuser)) {
5512 return $supportuser;
5515 $supportuser = new stdClass();
5516 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
5517 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
5518 $supportuser->lastname = '';
5519 $supportuser->maildisplay = true;
5521 return $supportuser;
5526 * Sets specified user's password and send the new password to the user via email.
5528 * @global object
5529 * @global object
5530 * @param user $user A {@link $USER} object
5531 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
5533 function setnew_password_and_mail($user) {
5534 global $CFG, $DB;
5536 // we try to send the mail in language the user understands,
5537 // unfortunately the filter_string() does not support alternative langs yet
5538 // so multilang will not work properly for site->fullname
5539 $lang = empty($user->lang) ? $CFG->lang : $user->lang;
5541 $site = get_site();
5543 $supportuser = generate_email_supportuser();
5545 $newpassword = generate_password();
5547 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
5549 $a = new stdClass();
5550 $a->firstname = fullname($user, true);
5551 $a->sitename = format_string($site->fullname);
5552 $a->username = $user->username;
5553 $a->newpassword = $newpassword;
5554 $a->link = $CFG->wwwroot .'/login/';
5555 $a->signoff = generate_email_signoff();
5557 $message = (string)new lang_string('newusernewpasswordtext', '', $a, $lang);
5559 $subject = format_string($site->fullname) .': '. (string)new lang_string('newusernewpasswordsubj', '', $a, $lang);
5561 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5562 return email_to_user($user, $supportuser, $subject, $message);
5567 * Resets specified user's password and send the new password to the user via email.
5569 * @param stdClass $user A {@link $USER} object
5570 * @return bool Returns true if mail was sent OK and false if there was an error.
5572 function reset_password_and_mail($user) {
5573 global $CFG;
5575 $site = get_site();
5576 $supportuser = generate_email_supportuser();
5578 $userauth = get_auth_plugin($user->auth);
5579 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
5580 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
5581 return false;
5584 $newpassword = generate_password();
5586 if (!$userauth->user_update_password($user, $newpassword)) {
5587 print_error("cannotsetpassword");
5590 $a = new stdClass();
5591 $a->firstname = $user->firstname;
5592 $a->lastname = $user->lastname;
5593 $a->sitename = format_string($site->fullname);
5594 $a->username = $user->username;
5595 $a->newpassword = $newpassword;
5596 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
5597 $a->signoff = generate_email_signoff();
5599 $message = get_string('newpasswordtext', '', $a);
5601 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
5603 unset_user_preference('create_password', $user); // prevent cron from generating the password
5605 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5606 return email_to_user($user, $supportuser, $subject, $message);
5611 * Send email to specified user with confirmation text and activation link.
5613 * @global object
5614 * @param user $user A {@link $USER} object
5615 * @return bool Returns true if mail was sent OK and false if there was an error.
5617 function send_confirmation_email($user) {
5618 global $CFG;
5620 $site = get_site();
5621 $supportuser = generate_email_supportuser();
5623 $data = new stdClass();
5624 $data->firstname = fullname($user);
5625 $data->sitename = format_string($site->fullname);
5626 $data->admin = generate_email_signoff();
5628 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
5630 $username = urlencode($user->username);
5631 $username = str_replace('.', '%2E', $username); // prevent problems with trailing dots
5632 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $username;
5633 $message = get_string('emailconfirmation', '', $data);
5634 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
5636 $user->mailformat = 1; // Always send HTML version as well
5638 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5639 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
5644 * send_password_change_confirmation_email.
5646 * @global object
5647 * @param user $user A {@link $USER} object
5648 * @return bool Returns true if mail was sent OK and false if there was an error.
5650 function send_password_change_confirmation_email($user) {
5651 global $CFG;
5653 $site = get_site();
5654 $supportuser = generate_email_supportuser();
5656 $data = new stdClass();
5657 $data->firstname = $user->firstname;
5658 $data->lastname = $user->lastname;
5659 $data->sitename = format_string($site->fullname);
5660 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
5661 $data->admin = generate_email_signoff();
5663 $message = get_string('emailpasswordconfirmation', '', $data);
5664 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
5666 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5667 return email_to_user($user, $supportuser, $subject, $message);
5672 * send_password_change_info.
5674 * @global object
5675 * @param user $user A {@link $USER} object
5676 * @return bool Returns true if mail was sent OK and false if there was an error.
5678 function send_password_change_info($user) {
5679 global $CFG;
5681 $site = get_site();
5682 $supportuser = generate_email_supportuser();
5683 $systemcontext = context_system::instance();
5685 $data = new stdClass();
5686 $data->firstname = $user->firstname;
5687 $data->lastname = $user->lastname;
5688 $data->sitename = format_string($site->fullname);
5689 $data->admin = generate_email_signoff();
5691 $userauth = get_auth_plugin($user->auth);
5693 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
5694 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
5695 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5696 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5697 return email_to_user($user, $supportuser, $subject, $message);
5700 if ($userauth->can_change_password() and $userauth->change_password_url()) {
5701 // we have some external url for password changing
5702 $data->link .= $userauth->change_password_url();
5704 } else {
5705 //no way to change password, sorry
5706 $data->link = '';
5709 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
5710 $message = get_string('emailpasswordchangeinfo', '', $data);
5711 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5712 } else {
5713 $message = get_string('emailpasswordchangeinfofail', '', $data);
5714 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5717 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5718 return email_to_user($user, $supportuser, $subject, $message);
5723 * Check that an email is allowed. It returns an error message if there
5724 * was a problem.
5726 * @global object
5727 * @param string $email Content of email
5728 * @return string|false
5730 function email_is_not_allowed($email) {
5731 global $CFG;
5733 if (!empty($CFG->allowemailaddresses)) {
5734 $allowed = explode(' ', $CFG->allowemailaddresses);
5735 foreach ($allowed as $allowedpattern) {
5736 $allowedpattern = trim($allowedpattern);
5737 if (!$allowedpattern) {
5738 continue;
5740 if (strpos($allowedpattern, '.') === 0) {
5741 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5742 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5743 return false;
5746 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5747 return false;
5750 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5752 } else if (!empty($CFG->denyemailaddresses)) {
5753 $denied = explode(' ', $CFG->denyemailaddresses);
5754 foreach ($denied as $deniedpattern) {
5755 $deniedpattern = trim($deniedpattern);
5756 if (!$deniedpattern) {
5757 continue;
5759 if (strpos($deniedpattern, '.') === 0) {
5760 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5761 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5762 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5765 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5766 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5771 return false;
5774 /// FILE HANDLING /////////////////////////////////////////////
5777 * Returns local file storage instance
5779 * @return file_storage
5781 function get_file_storage() {
5782 global $CFG;
5784 static $fs = null;
5786 if ($fs) {
5787 return $fs;
5790 require_once("$CFG->libdir/filelib.php");
5792 if (isset($CFG->filedir)) {
5793 $filedir = $CFG->filedir;
5794 } else {
5795 $filedir = $CFG->dataroot.'/filedir';
5798 if (isset($CFG->trashdir)) {
5799 $trashdirdir = $CFG->trashdir;
5800 } else {
5801 $trashdirdir = $CFG->dataroot.'/trashdir';
5804 $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5806 return $fs;
5810 * Returns local file storage instance
5812 * @return file_browser
5814 function get_file_browser() {
5815 global $CFG;
5817 static $fb = null;
5819 if ($fb) {
5820 return $fb;
5823 require_once("$CFG->libdir/filelib.php");
5825 $fb = new file_browser();
5827 return $fb;
5831 * Returns file packer
5833 * @param string $mimetype default application/zip
5834 * @return file_packer
5836 function get_file_packer($mimetype='application/zip') {
5837 global $CFG;
5839 static $fp = array();;
5841 if (isset($fp[$mimetype])) {
5842 return $fp[$mimetype];
5845 switch ($mimetype) {
5846 case 'application/zip':
5847 case 'application/vnd.moodle.backup':
5848 $classname = 'zip_packer';
5849 break;
5850 case 'application/x-tar':
5851 // $classname = 'tar_packer';
5852 // break;
5853 default:
5854 return false;
5857 require_once("$CFG->libdir/filestorage/$classname.php");
5858 $fp[$mimetype] = new $classname();
5860 return $fp[$mimetype];
5864 * Returns current name of file on disk if it exists.
5866 * @param string $newfile File to be verified
5867 * @return string Current name of file on disk if true
5869 function valid_uploaded_file($newfile) {
5870 if (empty($newfile)) {
5871 return '';
5873 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5874 return $newfile['tmp_name'];
5875 } else {
5876 return '';
5881 * Returns the maximum size for uploading files.
5883 * There are seven possible upload limits:
5884 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5885 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5886 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5887 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5888 * 5. by the Moodle admin in $CFG->maxbytes
5889 * 6. by the teacher in the current course $course->maxbytes
5890 * 7. by the teacher for the current module, eg $assignment->maxbytes
5892 * These last two are passed to this function as arguments (in bytes).
5893 * Anything defined as 0 is ignored.
5894 * The smallest of all the non-zero numbers is returned.
5896 * @todo Finish documenting this function
5898 * @param int $sizebytes Set maximum size
5899 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5900 * @param int $modulebytes Current module ->maxbytes (in bytes)
5901 * @return int The maximum size for uploading files.
5903 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5905 if (! $filesize = ini_get('upload_max_filesize')) {
5906 $filesize = '5M';
5908 $minimumsize = get_real_size($filesize);
5910 if ($postsize = ini_get('post_max_size')) {
5911 $postsize = get_real_size($postsize);
5912 if ($postsize < $minimumsize) {
5913 $minimumsize = $postsize;
5917 if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
5918 $minimumsize = $sitebytes;
5921 if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
5922 $minimumsize = $coursebytes;
5925 if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
5926 $minimumsize = $modulebytes;
5929 return $minimumsize;
5933 * Returns the maximum size for uploading files for the current user
5935 * This function takes in account @see:get_max_upload_file_size() the user's capabilities
5937 * @param context $context The context in which to check user capabilities
5938 * @param int $sizebytes Set maximum size
5939 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5940 * @param int $modulebytes Current module ->maxbytes (in bytes)
5941 * @param stdClass The user
5942 * @return int The maximum size for uploading files.
5944 function get_user_max_upload_file_size($context, $sitebytes=0, $coursebytes=0, $modulebytes=0, $user=null) {
5945 global $USER;
5947 if (empty($user)) {
5948 $user = $USER;
5951 if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
5952 return USER_CAN_IGNORE_FILE_SIZE_LIMITS;
5955 return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes);
5959 * Returns an array of possible sizes in local language
5961 * Related to {@link get_max_upload_file_size()} - this function returns an
5962 * array of possible sizes in an array, translated to the
5963 * local language.
5965 * @todo Finish documenting this function
5967 * @global object
5968 * @uses SORT_NUMERIC
5969 * @param int $sizebytes Set maximum size
5970 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5971 * @param int $modulebytes Current module ->maxbytes (in bytes)
5972 * @param int|array $custombytes custom upload size/s which will be added to list,
5973 * Only value/s smaller then maxsize will be added to list.
5974 * @return array
5976 function get_max_upload_sizes($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $custombytes = null) {
5977 global $CFG;
5979 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5980 return array();
5983 $filesize = array();
5984 $filesize[intval($maxsize)] = display_size($maxsize);
5986 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5987 5242880, 10485760, 20971520, 52428800, 104857600);
5989 // If custombytes is given and is valid then add it to the list.
5990 if (is_number($custombytes) and $custombytes > 0) {
5991 $custombytes = (int)$custombytes;
5992 if (!in_array($custombytes, $sizelist)) {
5993 $sizelist[] = $custombytes;
5995 } else if (is_array($custombytes)) {
5996 $sizelist = array_unique(array_merge($sizelist, $custombytes));
5999 // Allow maxbytes to be selected if it falls outside the above boundaries
6000 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
6001 // note: get_real_size() is used in order to prevent problems with invalid values
6002 $sizelist[] = get_real_size($CFG->maxbytes);
6005 foreach ($sizelist as $sizebytes) {
6006 if ($sizebytes < $maxsize) {
6007 $filesize[intval($sizebytes)] = display_size($sizebytes);
6011 krsort($filesize, SORT_NUMERIC);
6013 return $filesize;
6017 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
6019 * If excludefiles is defined, then that file/directory is ignored
6020 * If getdirs is true, then (sub)directories are included in the output
6021 * If getfiles is true, then files are included in the output
6022 * (at least one of these must be true!)
6024 * @todo Finish documenting this function. Add examples of $excludefile usage.
6026 * @param string $rootdir A given root directory to start from
6027 * @param string|array $excludefile If defined then the specified file/directory is ignored
6028 * @param bool $descend If true then subdirectories are recursed as well
6029 * @param bool $getdirs If true then (sub)directories are included in the output
6030 * @param bool $getfiles If true then files are included in the output
6031 * @return array An array with all the filenames in
6032 * all subdirectories, relative to the given rootdir
6034 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
6036 $dirs = array();
6038 if (!$getdirs and !$getfiles) { // Nothing to show
6039 return $dirs;
6042 if (!is_dir($rootdir)) { // Must be a directory
6043 return $dirs;
6046 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
6047 return $dirs;
6050 if (!is_array($excludefiles)) {
6051 $excludefiles = array($excludefiles);
6054 while (false !== ($file = readdir($dir))) {
6055 $firstchar = substr($file, 0, 1);
6056 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
6057 continue;
6059 $fullfile = $rootdir .'/'. $file;
6060 if (filetype($fullfile) == 'dir') {
6061 if ($getdirs) {
6062 $dirs[] = $file;
6064 if ($descend) {
6065 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
6066 foreach ($subdirs as $subdir) {
6067 $dirs[] = $file .'/'. $subdir;
6070 } else if ($getfiles) {
6071 $dirs[] = $file;
6074 closedir($dir);
6076 asort($dirs);
6078 return $dirs;
6083 * Adds up all the files in a directory and works out the size.
6085 * @todo Finish documenting this function
6087 * @param string $rootdir The directory to start from
6088 * @param string $excludefile A file to exclude when summing directory size
6089 * @return int The summed size of all files and subfiles within the root directory
6091 function get_directory_size($rootdir, $excludefile='') {
6092 global $CFG;
6094 // do it this way if we can, it's much faster
6095 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
6096 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
6097 $output = null;
6098 $return = null;
6099 exec($command,$output,$return);
6100 if (is_array($output)) {
6101 return get_real_size(intval($output[0]).'k'); // we told it to return k.
6105 if (!is_dir($rootdir)) { // Must be a directory
6106 return 0;
6109 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
6110 return 0;
6113 $size = 0;
6115 while (false !== ($file = readdir($dir))) {
6116 $firstchar = substr($file, 0, 1);
6117 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
6118 continue;
6120 $fullfile = $rootdir .'/'. $file;
6121 if (filetype($fullfile) == 'dir') {
6122 $size += get_directory_size($fullfile, $excludefile);
6123 } else {
6124 $size += filesize($fullfile);
6127 closedir($dir);
6129 return $size;
6133 * Converts bytes into display form
6135 * @todo Finish documenting this function. Verify return type.
6137 * @staticvar string $gb Localized string for size in gigabytes
6138 * @staticvar string $mb Localized string for size in megabytes
6139 * @staticvar string $kb Localized string for size in kilobytes
6140 * @staticvar string $b Localized string for size in bytes
6141 * @param int $size The size to convert to human readable form
6142 * @return string
6144 function display_size($size) {
6146 static $gb, $mb, $kb, $b;
6148 if ($size === USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
6149 return get_string('unlimited');
6152 if (empty($gb)) {
6153 $gb = get_string('sizegb');
6154 $mb = get_string('sizemb');
6155 $kb = get_string('sizekb');
6156 $b = get_string('sizeb');
6159 if ($size >= 1073741824) {
6160 $size = round($size / 1073741824 * 10) / 10 . $gb;
6161 } else if ($size >= 1048576) {
6162 $size = round($size / 1048576 * 10) / 10 . $mb;
6163 } else if ($size >= 1024) {
6164 $size = round($size / 1024 * 10) / 10 . $kb;
6165 } else {
6166 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
6168 return $size;
6172 * Cleans a given filename by removing suspicious or troublesome characters
6173 * @see clean_param()
6175 * @uses PARAM_FILE
6176 * @param string $string file name
6177 * @return string cleaned file name
6179 function clean_filename($string) {
6180 return clean_param($string, PARAM_FILE);
6184 /// STRING TRANSLATION ////////////////////////////////////////
6187 * Returns the code for the current language
6189 * @category string
6190 * @return string
6192 function current_language() {
6193 global $CFG, $USER, $SESSION, $COURSE;
6195 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
6196 $return = $COURSE->lang;
6198 } else if (!empty($SESSION->lang)) { // Session language can override other settings
6199 $return = $SESSION->lang;
6201 } else if (!empty($USER->lang)) {
6202 $return = $USER->lang;
6204 } else if (isset($CFG->lang)) {
6205 $return = $CFG->lang;
6207 } else {
6208 $return = 'en';
6211 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
6213 return $return;
6217 * Returns parent language of current active language if defined
6219 * @category string
6220 * @uses COURSE
6221 * @uses SESSION
6222 * @param string $lang null means current language
6223 * @return string
6225 function get_parent_language($lang=null) {
6226 global $COURSE, $SESSION;
6228 //let's hack around the current language
6229 if (!empty($lang)) {
6230 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
6231 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
6232 $COURSE->lang = '';
6233 $SESSION->lang = $lang;
6236 $parentlang = get_string('parentlanguage', 'langconfig');
6237 if ($parentlang === 'en') {
6238 $parentlang = '';
6241 //let's hack around the current language
6242 if (!empty($lang)) {
6243 $COURSE->lang = $old_course_lang;
6244 $SESSION->lang = $old_session_lang;
6247 return $parentlang;
6251 * Returns current string_manager instance.
6253 * The param $forcereload is needed for CLI installer only where the string_manager instance
6254 * must be replaced during the install.php script life time.
6256 * @category string
6257 * @param bool $forcereload shall the singleton be released and new instance created instead?
6258 * @return string_manager
6260 function get_string_manager($forcereload=false) {
6261 global $CFG;
6263 static $singleton = null;
6265 if ($forcereload) {
6266 $singleton = null;
6268 if ($singleton === null) {
6269 if (empty($CFG->early_install_lang)) {
6271 if (empty($CFG->langcacheroot)) {
6272 $langcacheroot = $CFG->cachedir . '/lang';
6273 } else {
6274 $langcacheroot = $CFG->langcacheroot;
6277 if (empty($CFG->langlist)) {
6278 $translist = array();
6279 } else {
6280 $translist = explode(',', $CFG->langlist);
6283 if (empty($CFG->langmenucachefile)) {
6284 $langmenucache = $CFG->cachedir . '/languages';
6285 } else {
6286 $langmenucache = $CFG->langmenucachefile;
6289 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
6290 !empty($CFG->langstringcache), $translist, $langmenucache);
6292 } else {
6293 $singleton = new install_string_manager();
6297 return $singleton;
6302 * Interface for string manager
6304 * Interface describing class which is responsible for getting
6305 * of localised strings from language packs.
6307 * @package core
6308 * @copyright 2010 Petr Skoda (http://skodak.org)
6309 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6311 interface string_manager {
6313 * Get String returns a requested string
6315 * @param string $identifier The identifier of the string to search for
6316 * @param string $component The module the string is associated with
6317 * @param string|object|array $a An object, string or number that can be used
6318 * within translation strings
6319 * @param string $lang moodle translation language, NULL means use current
6320 * @return string The String !
6322 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
6325 * Does the string actually exist?
6327 * get_string() is throwing debug warnings, sometimes we do not want them
6328 * or we want to display better explanation of the problem.
6330 * Use with care!
6332 * @param string $identifier The identifier of the string to search for
6333 * @param string $component The module the string is associated with
6334 * @return boot true if exists
6336 public function string_exists($identifier, $component);
6339 * Returns a localised list of all country names, sorted by country keys.
6340 * @param bool $returnall return all or just enabled
6341 * @param string $lang moodle translation language, NULL means use current
6342 * @return array two-letter country code => translated name.
6344 public function get_list_of_countries($returnall = false, $lang = NULL);
6347 * Returns a localised list of languages, sorted by code keys.
6349 * @param string $lang moodle translation language, NULL means use current
6350 * @param string $standard language list standard
6351 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6352 * @return array language code => translated name
6354 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
6357 * Checks if the translation exists for the language
6359 * @param string $lang moodle translation language code
6360 * @param bool $includeall include also disabled translations
6361 * @return bool true if exists
6363 public function translation_exists($lang, $includeall = true);
6366 * Returns localised list of installed translations
6367 * @param bool $returnall return all or just enabled
6368 * @return array moodle translation code => localised translation name
6370 public function get_list_of_translations($returnall = false);
6373 * Returns localised list of currencies.
6375 * @param string $lang moodle translation language, NULL means use current
6376 * @return array currency code => localised currency name
6378 public function get_list_of_currencies($lang = NULL);
6381 * Load all strings for one component
6382 * @param string $component The module the string is associated with
6383 * @param string $lang
6384 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6385 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6386 * @return array of all string for given component and lang
6388 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
6391 * Invalidates all caches, should the implementation use any
6392 * @param bool $phpunitreset true means called from our PHPUnit integration test reset
6394 public function reset_caches($phpunitreset = false);
6397 * Returns string revision counter, this is incremented after any
6398 * string cache reset.
6399 * @return int lang string revision counter, -1 if unknown
6401 public function get_revision();
6406 * Standard string_manager implementation
6408 * Implements string_manager with getting and printing localised strings
6410 * @package core
6411 * @category string
6412 * @copyright 2010 Petr Skoda (http://skodak.org)
6413 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6415 class core_string_manager implements string_manager {
6416 /** @var string location of all packs except 'en' */
6417 protected $otherroot;
6418 /** @var string location of all lang pack local modifications */
6419 protected $localroot;
6420 /** @var string location of on-disk cache of merged strings */
6421 protected $cacheroot;
6422 /** @var array lang string cache - it will be optimised more later */
6423 protected $cache = array();
6424 /** @var int get_string() counter */
6425 protected $countgetstring = 0;
6426 /** @var int in-memory cache hits counter */
6427 protected $countmemcache = 0;
6428 /** @var int on-disk cache hits counter */
6429 protected $countdiskcache = 0;
6430 /** @var bool use disk cache */
6431 protected $usediskcache;
6432 /** @var array limit list of translations */
6433 protected $translist;
6434 /** @var string location of a file that caches the list of available translations */
6435 protected $menucache;
6438 * Create new instance of string manager
6440 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
6441 * @param string $localroot usually the same as $otherroot
6442 * @param string $cacheroot usually lang dir in cache folder
6443 * @param bool $usediskcache use disk cache
6444 * @param array $translist limit list of visible translations
6445 * @param string $menucache the location of a file that caches the list of available translations
6447 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
6448 $this->otherroot = $otherroot;
6449 $this->localroot = $localroot;
6450 $this->cacheroot = $cacheroot;
6451 $this->usediskcache = $usediskcache;
6452 $this->translist = $translist;
6453 $this->menucache = $menucache;
6457 * Returns dependencies of current language, en is not included.
6459 * @param string $lang
6460 * @return array all parents, the lang itself is last
6462 public function get_language_dependencies($lang) {
6463 if ($lang === 'en') {
6464 return array();
6466 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
6467 return array();
6469 $string = array();
6470 include("$this->otherroot/$lang/langconfig.php");
6472 if (empty($string['parentlanguage'])) {
6473 return array($lang);
6474 } else {
6475 $parentlang = $string['parentlanguage'];
6476 unset($string);
6477 return array_merge($this->get_language_dependencies($parentlang), array($lang));
6482 * Load all strings for one component
6484 * @param string $component The module the string is associated with
6485 * @param string $lang
6486 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6487 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6488 * @return array of all string for given component and lang
6490 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6491 global $CFG;
6493 list($plugintype, $pluginname) = normalize_component($component);
6494 if ($plugintype == 'core' and is_null($pluginname)) {
6495 $component = 'core';
6496 } else {
6497 $component = $plugintype . '_' . $pluginname;
6500 if (!$disablecache and !$disablelocal) {
6501 // try in-memory cache first
6502 if (isset($this->cache[$lang][$component])) {
6503 $this->countmemcache++;
6504 return $this->cache[$lang][$component];
6507 // try on-disk cache then
6508 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
6509 $this->countdiskcache++;
6510 include($this->cacheroot . "/$lang/$component.php");
6511 return $this->cache[$lang][$component];
6515 // no cache found - let us merge all possible sources of the strings
6516 if ($plugintype === 'core') {
6517 $file = $pluginname;
6518 if ($file === null) {
6519 $file = 'moodle';
6521 $string = array();
6522 // first load english pack
6523 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
6524 return array();
6526 include("$CFG->dirroot/lang/en/$file.php");
6527 $originalkeys = array_keys($string);
6528 $originalkeys = array_flip($originalkeys);
6530 // and then corresponding local if present and allowed
6531 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6532 include("$this->localroot/en_local/$file.php");
6534 // now loop through all langs in correct order
6535 $deps = $this->get_language_dependencies($lang);
6536 foreach ($deps as $dep) {
6537 // the main lang string location
6538 if (file_exists("$this->otherroot/$dep/$file.php")) {
6539 include("$this->otherroot/$dep/$file.php");
6541 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6542 include("$this->localroot/{$dep}_local/$file.php");
6546 } else {
6547 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
6548 return array();
6550 if ($plugintype === 'mod') {
6551 // bloody mod hack
6552 $file = $pluginname;
6553 } else {
6554 $file = $plugintype . '_' . $pluginname;
6556 $string = array();
6557 // first load English pack
6558 if (!file_exists("$location/lang/en/$file.php")) {
6559 //English pack does not exist, so do not try to load anything else
6560 return array();
6562 include("$location/lang/en/$file.php");
6563 $originalkeys = array_keys($string);
6564 $originalkeys = array_flip($originalkeys);
6565 // and then corresponding local english if present
6566 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6567 include("$this->localroot/en_local/$file.php");
6570 // now loop through all langs in correct order
6571 $deps = $this->get_language_dependencies($lang);
6572 foreach ($deps as $dep) {
6573 // legacy location - used by contrib only
6574 if (file_exists("$location/lang/$dep/$file.php")) {
6575 include("$location/lang/$dep/$file.php");
6577 // the main lang string location
6578 if (file_exists("$this->otherroot/$dep/$file.php")) {
6579 include("$this->otherroot/$dep/$file.php");
6581 // local customisations
6582 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6583 include("$this->localroot/{$dep}_local/$file.php");
6588 // we do not want any extra strings from other languages - everything must be in en lang pack
6589 $string = array_intersect_key($string, $originalkeys);
6591 if (!$disablelocal) {
6592 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
6593 // caches so we do not need to do all this merging and dependencies resolving again
6594 $this->cache[$lang][$component] = $string;
6595 if ($this->usediskcache) {
6596 check_dir_exists("$this->cacheroot/$lang");
6597 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
6600 return $string;
6604 * Does the string actually exist?
6606 * get_string() is throwing debug warnings, sometimes we do not want them
6607 * or we want to display better explanation of the problem.
6608 * Note: Use with care!
6610 * @param string $identifier The identifier of the string to search for
6611 * @param string $component The module the string is associated with
6612 * @return boot true if exists
6614 public function string_exists($identifier, $component) {
6615 $identifier = clean_param($identifier, PARAM_STRINGID);
6616 if (empty($identifier)) {
6617 return false;
6619 $lang = current_language();
6620 $string = $this->load_component_strings($component, $lang);
6621 return isset($string[$identifier]);
6625 * Get String returns a requested string
6627 * @param string $identifier The identifier of the string to search for
6628 * @param string $component The module the string is associated with
6629 * @param string|object|array $a An object, string or number that can be used
6630 * within translation strings
6631 * @param string $lang moodle translation language, NULL means use current
6632 * @return string The String !
6634 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6635 $this->countgetstring++;
6636 // there are very many uses of these time formating strings without the 'langconfig' component,
6637 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
6638 static $langconfigstrs = array(
6639 'strftimedate' => 1,
6640 'strftimedatefullshort' => 1,
6641 'strftimedateshort' => 1,
6642 'strftimedatetime' => 1,
6643 'strftimedatetimeshort' => 1,
6644 'strftimedaydate' => 1,
6645 'strftimedaydatetime' => 1,
6646 'strftimedayshort' => 1,
6647 'strftimedaytime' => 1,
6648 'strftimemonthyear' => 1,
6649 'strftimerecent' => 1,
6650 'strftimerecentfull' => 1,
6651 'strftimetime' => 1);
6653 if (empty($component)) {
6654 if (isset($langconfigstrs[$identifier])) {
6655 $component = 'langconfig';
6656 } else {
6657 $component = 'moodle';
6661 if ($lang === NULL) {
6662 $lang = current_language();
6665 $string = $this->load_component_strings($component, $lang);
6667 if (!isset($string[$identifier])) {
6668 if ($component === 'pix' or $component === 'core_pix') {
6669 // this component contains only alt tags for emoticons,
6670 // not all of them are supposed to be defined
6671 return '';
6673 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
6674 // parentlanguage is a special string, undefined means use English if not defined
6675 return 'en';
6677 if ($this->usediskcache) {
6678 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources,
6679 // do NOT write the results to disk cache because it may end up in race conditions see MDL-31904
6680 $this->usediskcache = false;
6681 $string = $this->load_component_strings($component, $lang, true);
6682 $this->usediskcache = true;
6684 if (!isset($string[$identifier])) {
6685 // the string is still missing - should be fixed by developer
6686 list($plugintype, $pluginname) = normalize_component($component);
6687 if ($plugintype == 'core') {
6688 $file = "lang/en/{$component}.php";
6689 } else if ($plugintype == 'mod') {
6690 $file = "mod/{$pluginname}/lang/en/{$pluginname}.php";
6691 } else {
6692 $path = get_plugin_directory($plugintype, $pluginname);
6693 $file = "{$path}/lang/en/{$plugintype}_{$pluginname}.php";
6695 debugging("Invalid get_string() identifier: '{$identifier}' or component '{$component}'. " .
6696 "Perhaps you are missing \$string['{$identifier}'] = ''; in {$file}?", DEBUG_DEVELOPER);
6697 return "[[$identifier]]";
6701 $string = $string[$identifier];
6703 if ($a !== NULL) {
6704 // Process array's and objects (except lang_strings)
6705 if (is_array($a) or (is_object($a) && !($a instanceof lang_string))) {
6706 $a = (array)$a;
6707 $search = array();
6708 $replace = array();
6709 foreach ($a as $key=>$value) {
6710 if (is_int($key)) {
6711 // we do not support numeric keys - sorry!
6712 continue;
6714 if (is_array($value) or (is_object($value) && !($value instanceof lang_string))) {
6715 // we support just string or lang_string as value
6716 continue;
6718 $search[] = '{$a->'.$key.'}';
6719 $replace[] = (string)$value;
6721 if ($search) {
6722 $string = str_replace($search, $replace, $string);
6724 } else {
6725 $string = str_replace('{$a}', (string)$a, $string);
6729 return $string;
6733 * Returns information about the string_manager performance
6735 * @return array
6737 public function get_performance_summary() {
6738 return array(array(
6739 'langcountgetstring' => $this->countgetstring,
6740 'langcountmemcache' => $this->countmemcache,
6741 'langcountdiskcache' => $this->countdiskcache,
6742 ), array(
6743 'langcountgetstring' => 'get_string calls',
6744 'langcountmemcache' => 'strings mem cache hits',
6745 'langcountdiskcache' => 'strings disk cache hits',
6750 * Returns a localised list of all country names, sorted by localised name.
6752 * @param bool $returnall return all or just enabled
6753 * @param string $lang moodle translation language, NULL means use current
6754 * @return array two-letter country code => translated name.
6756 public function get_list_of_countries($returnall = false, $lang = NULL) {
6757 global $CFG;
6759 if ($lang === NULL) {
6760 $lang = current_language();
6763 $countries = $this->load_component_strings('core_countries', $lang);
6764 collatorlib::asort($countries);
6765 if (!$returnall and !empty($CFG->allcountrycodes)) {
6766 $enabled = explode(',', $CFG->allcountrycodes);
6767 $return = array();
6768 foreach ($enabled as $c) {
6769 if (isset($countries[$c])) {
6770 $return[$c] = $countries[$c];
6773 return $return;
6776 return $countries;
6780 * Returns a localised list of languages, sorted by code keys.
6782 * @param string $lang moodle translation language, NULL means use current
6783 * @param string $standard language list standard
6784 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
6785 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
6786 * @return array language code => translated name
6788 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
6789 if ($lang === NULL) {
6790 $lang = current_language();
6793 if ($standard === 'iso6392') {
6794 $langs = $this->load_component_strings('core_iso6392', $lang);
6795 ksort($langs);
6796 return $langs;
6798 } else if ($standard === 'iso6391') {
6799 $langs2 = $this->load_component_strings('core_iso6392', $lang);
6800 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
6801 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
6802 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
6803 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
6804 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
6805 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
6806 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
6807 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
6808 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
6809 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
6810 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
6811 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
6812 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
6813 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6814 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6815 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6816 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6817 $langs1 = array();
6818 foreach ($mapping as $c2=>$c1) {
6819 $langs1[$c1] = $langs2[$c2];
6821 ksort($langs1);
6822 return $langs1;
6824 } else {
6825 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6828 return array();
6832 * Checks if the translation exists for the language
6834 * @param string $lang moodle translation language code
6835 * @param bool $includeall include also disabled translations
6836 * @return bool true if exists
6838 public function translation_exists($lang, $includeall = true) {
6840 if (strpos($lang, '_local') !== false) {
6841 // _local packs are not real translations
6842 return false;
6844 if (!$includeall and !empty($this->translist)) {
6845 if (!in_array($lang, $this->translist)) {
6846 return false;
6849 if ($lang === 'en') {
6850 // part of distribution
6851 return true;
6853 return file_exists("$this->otherroot/$lang/langconfig.php");
6857 * Returns localised list of installed translations
6859 * @param bool $returnall return all or just enabled
6860 * @return array moodle translation code => localised translation name
6862 public function get_list_of_translations($returnall = false) {
6863 global $CFG;
6865 $languages = array();
6867 if (!empty($CFG->langcache) and is_readable($this->menucache)) {
6868 // try to re-use the cached list of all available languages
6869 $cachedlist = json_decode(file_get_contents($this->menucache), true);
6871 if (is_array($cachedlist) and !empty($cachedlist)) {
6872 // the cache file is restored correctly
6874 if (!$returnall and !empty($this->translist)) {
6875 // return just enabled translations
6876 foreach ($cachedlist as $langcode => $langname) {
6877 if (in_array($langcode, $this->translist)) {
6878 $languages[$langcode] = $langname;
6881 return $languages;
6883 } else {
6884 // return all translations
6885 return $cachedlist;
6890 // the cached list of languages is not available, let us populate the list
6892 if (!$returnall and !empty($this->translist)) {
6893 // return only some translations
6894 foreach ($this->translist as $lang) {
6895 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6896 if (strstr($lang, '_local') !== false) {
6897 continue;
6899 if (strstr($lang, '_utf8') !== false) {
6900 continue;
6902 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6903 // some broken or missing lang - can not switch to it anyway
6904 continue;
6906 $string = $this->load_component_strings('langconfig', $lang);
6907 if (!empty($string['thislanguage'])) {
6908 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6910 unset($string);
6913 } else {
6914 // return all languages available in system
6915 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6917 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6918 // Sort all
6920 // Loop through all langs and get info
6921 foreach ($langdirs as $lang) {
6922 if (strstr($lang, '_local') !== false) {
6923 continue;
6925 if (strstr($lang, '_utf8') !== false) {
6926 continue;
6928 $string = $this->load_component_strings('langconfig', $lang);
6929 if (!empty($string['thislanguage'])) {
6930 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6932 unset($string);
6935 if (!empty($CFG->langcache) and !empty($this->menucache)) {
6936 // cache the list so that it can be used next time
6937 collatorlib::asort($languages);
6938 check_dir_exists(dirname($this->menucache), true, true);
6939 file_put_contents($this->menucache, json_encode($languages));
6943 collatorlib::asort($languages);
6945 return $languages;
6949 * Returns localised list of currencies.
6951 * @param string $lang moodle translation language, NULL means use current
6952 * @return array currency code => localised currency name
6954 public function get_list_of_currencies($lang = NULL) {
6955 if ($lang === NULL) {
6956 $lang = current_language();
6959 $currencies = $this->load_component_strings('core_currencies', $lang);
6960 asort($currencies);
6962 return $currencies;
6966 * Clears both in-memory and on-disk caches
6967 * @param bool $phpunitreset true means called from our PHPUnit integration test reset
6969 public function reset_caches($phpunitreset = false) {
6970 global $CFG;
6971 require_once("$CFG->libdir/filelib.php");
6973 // clear the on-disk disk with aggregated string files
6974 fulldelete($this->cacheroot);
6976 // clear the in-memory cache of loaded strings
6977 $this->cache = array();
6979 if (!$phpunitreset) {
6980 // Increment the revision counter.
6981 $langrev = get_config('core', 'langrev');
6982 $next = time();
6983 if ($langrev !== false and $next <= $langrev and $langrev - $next < 60*60) {
6984 // This resolves problems when reset is requested repeatedly within 1s,
6985 // the < 1h condition prevents accidental switching to future dates
6986 // because we might not recover from it.
6987 $next = $langrev+1;
6989 set_config('langrev', $next);
6992 // clear the cache containing the list of available translations
6993 // and re-populate it again
6994 fulldelete($this->menucache);
6995 $this->get_list_of_translations(true);
6999 * Returns string revision counter, this is incremented after any
7000 * string cache reset.
7001 * @return int lang string revision counter, -1 if unknown
7003 public function get_revision() {
7004 global $CFG;
7005 if (!$this->usediskcache) {
7006 return -1;
7008 if (isset($CFG->langrev)) {
7009 return (int)$CFG->langrev;
7010 } else {
7011 return -1;
7018 * Fetches minimum strings for installation
7020 * Minimalistic string fetching implementation
7021 * that is used in installer before we fetch the wanted
7022 * language pack from moodle.org lang download site.
7024 * @package core
7025 * @copyright 2010 Petr Skoda (http://skodak.org)
7026 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7028 class install_string_manager implements string_manager {
7029 /** @var string location of pre-install packs for all langs */
7030 protected $installroot;
7033 * Crate new instance of install string manager
7035 public function __construct() {
7036 global $CFG;
7037 $this->installroot = "$CFG->dirroot/install/lang";
7041 * Load all strings for one component
7042 * @param string $component The module the string is associated with
7043 * @param string $lang
7044 * @param bool $disablecache Do not use caches, force fetching the strings from sources
7045 * @param bool $disablelocal Do not use customized strings in xx_local language packs
7046 * @return array of all string for given component and lang
7048 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
7049 // not needed in installer
7050 return array();
7054 * Does the string actually exist?
7056 * get_string() is throwing debug warnings, sometimes we do not want them
7057 * or we want to display better explanation of the problem.
7059 * Use with care!
7061 * @param string $identifier The identifier of the string to search for
7062 * @param string $component The module the string is associated with
7063 * @return boot true if exists
7065 public function string_exists($identifier, $component) {
7066 $identifier = clean_param($identifier, PARAM_STRINGID);
7067 if (empty($identifier)) {
7068 return false;
7070 // simple old style hack ;)
7071 $str = get_string($identifier, $component);
7072 return (strpos($str, '[[') === false);
7076 * Get String returns a requested string
7078 * @param string $identifier The identifier of the string to search for
7079 * @param string $component The module the string is associated with
7080 * @param string|object|array $a An object, string or number that can be used
7081 * within translation strings
7082 * @param string $lang moodle translation language, NULL means use current
7083 * @return string The String !
7085 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
7086 if (!$component) {
7087 $component = 'moodle';
7090 if ($lang === NULL) {
7091 $lang = current_language();
7094 //get parent lang
7095 $parent = '';
7096 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
7097 if (file_exists("$this->installroot/$lang/langconfig.php")) {
7098 $string = array();
7099 include("$this->installroot/$lang/langconfig.php");
7100 if (isset($string['parentlanguage'])) {
7101 $parent = $string['parentlanguage'];
7103 unset($string);
7107 // include en string first
7108 if (!file_exists("$this->installroot/en/$component.php")) {
7109 return "[[$identifier]]";
7111 $string = array();
7112 include("$this->installroot/en/$component.php");
7114 // now override en with parent if defined
7115 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
7116 include("$this->installroot/$parent/$component.php");
7119 // finally override with requested language
7120 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
7121 include("$this->installroot/$lang/$component.php");
7124 if (!isset($string[$identifier])) {
7125 return "[[$identifier]]";
7128 $string = $string[$identifier];
7130 if ($a !== NULL) {
7131 if (is_object($a) or is_array($a)) {
7132 $a = (array)$a;
7133 $search = array();
7134 $replace = array();
7135 foreach ($a as $key=>$value) {
7136 if (is_int($key)) {
7137 // we do not support numeric keys - sorry!
7138 continue;
7140 $search[] = '{$a->'.$key.'}';
7141 $replace[] = (string)$value;
7143 if ($search) {
7144 $string = str_replace($search, $replace, $string);
7146 } else {
7147 $string = str_replace('{$a}', (string)$a, $string);
7151 return $string;
7155 * Returns a localised list of all country names, sorted by country keys.
7157 * @param bool $returnall return all or just enabled
7158 * @param string $lang moodle translation language, NULL means use current
7159 * @return array two-letter country code => translated name.
7161 public function get_list_of_countries($returnall = false, $lang = NULL) {
7162 //not used in installer
7163 return array();
7167 * Returns a localised list of languages, sorted by code keys.
7169 * @param string $lang moodle translation language, NULL means use current
7170 * @param string $standard language list standard
7171 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
7172 * @return array language code => translated name
7174 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
7175 //not used in installer
7176 return array();
7180 * Checks if the translation exists for the language
7182 * @param string $lang moodle translation language code
7183 * @param bool $includeall include also disabled translations
7184 * @return bool true if exists
7186 public function translation_exists($lang, $includeall = true) {
7187 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
7191 * Returns localised list of installed translations
7192 * @param bool $returnall return all or just enabled
7193 * @return array moodle translation code => localised translation name
7195 public function get_list_of_translations($returnall = false) {
7196 // return all is ignored here - we need to know all langs in installer
7197 $languages = array();
7198 // Get raw list of lang directories
7199 $langdirs = get_list_of_plugins('install/lang');
7200 asort($langdirs);
7201 // Get some info from each lang
7202 foreach ($langdirs as $lang) {
7203 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
7204 $string = array();
7205 include($this->installroot.'/'.$lang.'/langconfig.php');
7206 if (!empty($string['thislanguage'])) {
7207 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
7211 // Return array
7212 return $languages;
7216 * Returns localised list of currencies.
7218 * @param string $lang moodle translation language, NULL means use current
7219 * @return array currency code => localised currency name
7221 public function get_list_of_currencies($lang = NULL) {
7222 // not used in installer
7223 return array();
7227 * This implementation does not use any caches
7228 * @param bool $phpunitreset true means called from our PHPUnit integration test reset
7230 public function reset_caches($phpunitreset = false) {
7231 // Nothing to do.
7235 * Returns string revision counter, this is incremented after any
7236 * string cache reset.
7237 * @return int lang string revision counter, -1 if unknown
7239 public function get_revision() {
7240 return -1;
7246 * Returns a localized string.
7248 * Returns the translated string specified by $identifier as
7249 * for $module. Uses the same format files as STphp.
7250 * $a is an object, string or number that can be used
7251 * within translation strings
7253 * eg 'hello {$a->firstname} {$a->lastname}'
7254 * or 'hello {$a}'
7256 * If you would like to directly echo the localized string use
7257 * the function {@link print_string()}
7259 * Example usage of this function involves finding the string you would
7260 * like a local equivalent of and using its identifier and module information
7261 * to retrieve it.<br/>
7262 * If you open moodle/lang/en/moodle.php and look near line 278
7263 * you will find a string to prompt a user for their word for 'course'
7264 * <code>
7265 * $string['course'] = 'Course';
7266 * </code>
7267 * So if you want to display the string 'Course'
7268 * in any language that supports it on your site
7269 * you just need to use the identifier 'course'
7270 * <code>
7271 * $mystring = '<strong>'. get_string('course') .'</strong>';
7272 * or
7273 * </code>
7274 * If the string you want is in another file you'd take a slightly
7275 * different approach. Looking in moodle/lang/en/calendar.php you find
7276 * around line 75:
7277 * <code>
7278 * $string['typecourse'] = 'Course event';
7279 * </code>
7280 * If you want to display the string "Course event" in any language
7281 * supported you would use the identifier 'typecourse' and the module 'calendar'
7282 * (because it is in the file calendar.php):
7283 * <code>
7284 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
7285 * </code>
7287 * As a last resort, should the identifier fail to map to a string
7288 * the returned string will be [[ $identifier ]]
7290 * In Moodle 2.3 there is a new argument to this function $lazyload.
7291 * Setting $lazyload to true causes get_string to return a lang_string object
7292 * rather than the string itself. The fetching of the string is then put off until
7293 * the string object is first used. The object can be used by calling it's out
7294 * method or by casting the object to a string, either directly e.g.
7295 * (string)$stringobject
7296 * or indirectly by using the string within another string or echoing it out e.g.
7297 * echo $stringobject
7298 * return "<p>{$stringobject}</p>";
7299 * It is worth noting that using $lazyload and attempting to use the string as an
7300 * array key will cause a fatal error as objects cannot be used as array keys.
7301 * But you should never do that anyway!
7302 * For more information {@see lang_string}
7304 * @category string
7305 * @param string $identifier The key identifier for the localized string
7306 * @param string $component The module where the key identifier is stored,
7307 * usually expressed as the filename in the language pack without the
7308 * .php on the end but can also be written as mod/forum or grade/export/xls.
7309 * If none is specified then moodle.php is used.
7310 * @param string|object|array $a An object, string or number that can be used
7311 * within translation strings
7312 * @param bool $lazyload If set to true a string object is returned instead of
7313 * the string itself. The string then isn't calculated until it is first used.
7314 * @return string The localized string.
7316 function get_string($identifier, $component = '', $a = NULL, $lazyload = false) {
7317 global $CFG;
7319 // If the lazy load argument has been supplied return a lang_string object
7320 // instead.
7321 // We need to make sure it is true (and a bool) as you will see below there
7322 // used to be a forth argument at one point.
7323 if ($lazyload === true) {
7324 return new lang_string($identifier, $component, $a);
7327 $identifier = clean_param($identifier, PARAM_STRINGID);
7328 if (empty($identifier)) {
7329 throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.');
7332 // There is now a forth argument again, this time it is a boolean however so
7333 // we can still check for the old extralocations parameter.
7334 if (!is_bool($lazyload) && !empty($lazyload)) {
7335 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
7338 if (strpos($component, '/') !== false) {
7339 debugging('The module name you passed to get_string is the deprecated format ' .
7340 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
7341 $componentpath = explode('/', $component);
7343 switch ($componentpath[0]) {
7344 case 'mod':
7345 $component = $componentpath[1];
7346 break;
7347 case 'blocks':
7348 case 'block':
7349 $component = 'block_'.$componentpath[1];
7350 break;
7351 case 'enrol':
7352 $component = 'enrol_'.$componentpath[1];
7353 break;
7354 case 'format':
7355 $component = 'format_'.$componentpath[1];
7356 break;
7357 case 'grade':
7358 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
7359 break;
7363 $result = get_string_manager()->get_string($identifier, $component, $a);
7365 // Debugging feature lets you display string identifier and component
7366 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
7367 $result .= ' {' . $identifier . '/' . $component . '}';
7369 return $result;
7373 * Converts an array of strings to their localized value.
7375 * @param array $array An array of strings
7376 * @param string $component The language module that these strings can be found in.
7377 * @return stdClass translated strings.
7379 function get_strings($array, $component = '') {
7380 $string = new stdClass;
7381 foreach ($array as $item) {
7382 $string->$item = get_string($item, $component);
7384 return $string;
7388 * Prints out a translated string.
7390 * Prints out a translated string using the return value from the {@link get_string()} function.
7392 * Example usage of this function when the string is in the moodle.php file:<br/>
7393 * <code>
7394 * echo '<strong>';
7395 * print_string('course');
7396 * echo '</strong>';
7397 * </code>
7399 * Example usage of this function when the string is not in the moodle.php file:<br/>
7400 * <code>
7401 * echo '<h1>';
7402 * print_string('typecourse', 'calendar');
7403 * echo '</h1>';
7404 * </code>
7406 * @category string
7407 * @param string $identifier The key identifier for the localized string
7408 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
7409 * @param string|object|array $a An object, string or number that can be used within translation strings
7411 function print_string($identifier, $component = '', $a = NULL) {
7412 echo get_string($identifier, $component, $a);
7416 * Returns a list of charset codes
7418 * Returns a list of charset codes. It's hardcoded, so they should be added manually
7419 * (checking that such charset is supported by the texlib library!)
7421 * @return array And associative array with contents in the form of charset => charset
7423 function get_list_of_charsets() {
7425 $charsets = array(
7426 'EUC-JP' => 'EUC-JP',
7427 'ISO-2022-JP'=> 'ISO-2022-JP',
7428 'ISO-8859-1' => 'ISO-8859-1',
7429 'SHIFT-JIS' => 'SHIFT-JIS',
7430 'GB2312' => 'GB2312',
7431 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
7432 'UTF-8' => 'UTF-8');
7434 asort($charsets);
7436 return $charsets;
7440 * Returns a list of valid and compatible themes
7442 * @return array
7444 function get_list_of_themes() {
7445 global $CFG;
7447 $themes = array();
7449 if (!empty($CFG->themelist)) { // use admin's list of themes
7450 $themelist = explode(',', $CFG->themelist);
7451 } else {
7452 $themelist = array_keys(get_plugin_list("theme"));
7455 foreach ($themelist as $key => $themename) {
7456 $theme = theme_config::load($themename);
7457 $themes[$themename] = $theme;
7460 collatorlib::asort_objects_by_method($themes, 'get_theme_name');
7462 return $themes;
7466 * Returns a list of timezones in the current language
7468 * @global object
7469 * @global object
7470 * @return array
7472 function get_list_of_timezones() {
7473 global $CFG, $DB;
7475 static $timezones;
7477 if (!empty($timezones)) { // This function has been called recently
7478 return $timezones;
7481 $timezones = array();
7483 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
7484 foreach($rawtimezones as $timezone) {
7485 if (!empty($timezone->name)) {
7486 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
7487 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
7488 } else {
7489 $timezones[$timezone->name] = $timezone->name;
7491 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
7492 $timezones[$timezone->name] = $timezone->name;
7498 asort($timezones);
7500 for ($i = -13; $i <= 13; $i += .5) {
7501 $tzstring = 'UTC';
7502 if ($i < 0) {
7503 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
7504 } else if ($i > 0) {
7505 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
7506 } else {
7507 $timezones[sprintf("%.1f", $i)] = $tzstring;
7511 return $timezones;
7515 * Factory function for emoticon_manager
7517 * @return emoticon_manager singleton
7519 function get_emoticon_manager() {
7520 static $singleton = null;
7522 if (is_null($singleton)) {
7523 $singleton = new emoticon_manager();
7526 return $singleton;
7530 * Provides core support for plugins that have to deal with
7531 * emoticons (like HTML editor or emoticon filter).
7533 * Whenever this manager mentiones 'emoticon object', the following data
7534 * structure is expected: stdClass with properties text, imagename, imagecomponent,
7535 * altidentifier and altcomponent
7537 * @see admin_setting_emoticons
7539 class emoticon_manager {
7542 * Returns the currently enabled emoticons
7544 * @return array of emoticon objects
7546 public function get_emoticons() {
7547 global $CFG;
7549 if (empty($CFG->emoticons)) {
7550 return array();
7553 $emoticons = $this->decode_stored_config($CFG->emoticons);
7555 if (!is_array($emoticons)) {
7556 // something is wrong with the format of stored setting
7557 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
7558 return array();
7561 return $emoticons;
7565 * Converts emoticon object into renderable pix_emoticon object
7567 * @param stdClass $emoticon emoticon object
7568 * @param array $attributes explicit HTML attributes to set
7569 * @return pix_emoticon
7571 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
7572 $stringmanager = get_string_manager();
7573 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
7574 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
7575 } else {
7576 $alt = s($emoticon->text);
7578 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
7582 * Encodes the array of emoticon objects into a string storable in config table
7584 * @see self::decode_stored_config()
7585 * @param array $emoticons array of emtocion objects
7586 * @return string
7588 public function encode_stored_config(array $emoticons) {
7589 return json_encode($emoticons);
7593 * Decodes the string into an array of emoticon objects
7595 * @see self::encode_stored_config()
7596 * @param string $encoded
7597 * @return string|null
7599 public function decode_stored_config($encoded) {
7600 $decoded = json_decode($encoded);
7601 if (!is_array($decoded)) {
7602 return null;
7604 return $decoded;
7608 * Returns default set of emoticons supported by Moodle
7610 * @return array of sdtClasses
7612 public function default_emoticons() {
7613 return array(
7614 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
7615 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
7616 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
7617 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
7618 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
7619 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
7620 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
7621 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
7622 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
7623 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
7624 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
7625 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
7626 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
7627 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
7628 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
7629 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
7630 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
7631 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
7632 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
7633 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
7634 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
7635 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
7636 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
7637 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
7638 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
7639 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
7640 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
7641 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
7642 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
7643 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
7648 * Helper method preparing the stdClass with the emoticon properties
7650 * @param string|array $text or array of strings
7651 * @param string $imagename to be used by {@see pix_emoticon}
7652 * @param string $altidentifier alternative string identifier, null for no alt
7653 * @param array $altcomponent where the alternative string is defined
7654 * @param string $imagecomponent to be used by {@see pix_emoticon}
7655 * @return stdClass
7657 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
7658 return (object)array(
7659 'text' => $text,
7660 'imagename' => $imagename,
7661 'imagecomponent' => $imagecomponent,
7662 'altidentifier' => $altidentifier,
7663 'altcomponent' => $altcomponent,
7668 /// ENCRYPTION ////////////////////////////////////////////////
7671 * rc4encrypt
7673 * Please note that in this version of moodle that the default for rc4encryption is
7674 * using the slightly more secure password key. There may be an issue when upgrading
7675 * from an older version of moodle.
7677 * @todo MDL-31836 Remove the old password key in version 2.4
7678 * Code also needs to be changed in sessionlib.php
7679 * @see get_moodle_cookie()
7680 * @see set_moodle_cookie()
7682 * @param string $data Data to encrypt.
7683 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7684 * @return string The now encrypted data.
7686 function rc4encrypt($data, $usesecurekey = true) {
7687 if (!$usesecurekey) {
7688 $passwordkey = 'nfgjeingjk';
7689 } else {
7690 $passwordkey = get_site_identifier();
7692 return endecrypt($passwordkey, $data, '');
7696 * rc4decrypt
7698 * Please note that in this version of moodle that the default for rc4encryption is
7699 * using the slightly more secure password key. There may be an issue when upgrading
7700 * from an older version of moodle.
7702 * @todo MDL-31836 Remove the old password key in version 2.4
7703 * Code also needs to be changed in sessionlib.php
7704 * @see get_moodle_cookie()
7705 * @see set_moodle_cookie()
7707 * @param string $data Data to decrypt.
7708 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7709 * @return string The now decrypted data.
7711 function rc4decrypt($data, $usesecurekey = true) {
7712 if (!$usesecurekey) {
7713 $passwordkey = 'nfgjeingjk';
7714 } else {
7715 $passwordkey = get_site_identifier();
7717 return endecrypt($passwordkey, $data, 'de');
7721 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
7723 * @todo Finish documenting this function
7725 * @param string $pwd The password to use when encrypting or decrypting
7726 * @param string $data The data to be decrypted/encrypted
7727 * @param string $case Either 'de' for decrypt or '' for encrypt
7728 * @return string
7730 function endecrypt ($pwd, $data, $case) {
7732 if ($case == 'de') {
7733 $data = urldecode($data);
7736 $key[] = '';
7737 $box[] = '';
7738 $temp_swap = '';
7739 $pwd_length = 0;
7741 $pwd_length = strlen($pwd);
7743 for ($i = 0; $i <= 255; $i++) {
7744 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
7745 $box[$i] = $i;
7748 $x = 0;
7750 for ($i = 0; $i <= 255; $i++) {
7751 $x = ($x + $box[$i] + $key[$i]) % 256;
7752 $temp_swap = $box[$i];
7753 $box[$i] = $box[$x];
7754 $box[$x] = $temp_swap;
7757 $temp = '';
7758 $k = '';
7760 $cipherby = '';
7761 $cipher = '';
7763 $a = 0;
7764 $j = 0;
7766 for ($i = 0; $i < strlen($data); $i++) {
7767 $a = ($a + 1) % 256;
7768 $j = ($j + $box[$a]) % 256;
7769 $temp = $box[$a];
7770 $box[$a] = $box[$j];
7771 $box[$j] = $temp;
7772 $k = $box[(($box[$a] + $box[$j]) % 256)];
7773 $cipherby = ord(substr($data, $i, 1)) ^ $k;
7774 $cipher .= chr($cipherby);
7777 if ($case == 'de') {
7778 $cipher = urldecode(urlencode($cipher));
7779 } else {
7780 $cipher = urlencode($cipher);
7783 return $cipher;
7786 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
7789 * Returns the exact absolute path to plugin directory.
7791 * @param string $plugintype type of plugin
7792 * @param string $name name of the plugin
7793 * @return string full path to plugin directory; NULL if not found
7795 function get_plugin_directory($plugintype, $name) {
7796 global $CFG;
7798 if ($plugintype === '') {
7799 $plugintype = 'mod';
7802 $types = get_plugin_types(true);
7803 if (!array_key_exists($plugintype, $types)) {
7804 return NULL;
7806 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
7808 if (!empty($CFG->themedir) and $plugintype === 'theme') {
7809 if (!is_dir($types['theme'] . '/' . $name)) {
7810 // ok, so the theme is supposed to be in the $CFG->themedir
7811 return $CFG->themedir . '/' . $name;
7815 return $types[$plugintype].'/'.$name;
7819 * Return exact absolute path to a plugin directory.
7821 * @param string $component name such as 'moodle', 'mod_forum'
7822 * @return string full path to component directory; NULL if not found
7824 function get_component_directory($component) {
7825 global $CFG;
7827 list($type, $plugin) = normalize_component($component);
7829 if ($type === 'core') {
7830 if ($plugin === NULL ) {
7831 $path = $CFG->libdir;
7832 } else {
7833 $subsystems = get_core_subsystems();
7834 if (isset($subsystems[$plugin])) {
7835 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
7836 } else {
7837 $path = NULL;
7841 } else {
7842 $path = get_plugin_directory($type, $plugin);
7845 return $path;
7849 * Normalize the component name using the "frankenstyle" names.
7850 * @param string $component
7851 * @return array $type+$plugin elements
7853 function normalize_component($component) {
7854 if ($component === 'moodle' or $component === 'core') {
7855 $type = 'core';
7856 $plugin = NULL;
7858 } else if (strpos($component, '_') === false) {
7859 $subsystems = get_core_subsystems();
7860 if (array_key_exists($component, $subsystems)) {
7861 $type = 'core';
7862 $plugin = $component;
7863 } else {
7864 // everything else is a module
7865 $type = 'mod';
7866 $plugin = $component;
7869 } else {
7870 list($type, $plugin) = explode('_', $component, 2);
7871 $plugintypes = get_plugin_types(false);
7872 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
7873 $type = 'mod';
7874 $plugin = $component;
7878 return array($type, $plugin);
7882 * List all core subsystems and their location
7884 * This is a whitelist of components that are part of the core and their
7885 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
7886 * plugin is not listed here and it does not have proper plugintype prefix,
7887 * then it is considered as course activity module.
7889 * The location is dirroot relative path. NULL means there is no special
7890 * directory for this subsystem. If the location is set, the subsystem's
7891 * renderer.php is expected to be there.
7893 * @return array of (string)name => (string|null)location
7895 function get_core_subsystems() {
7896 global $CFG;
7898 static $info = null;
7900 if (!$info) {
7901 $info = array(
7902 'access' => NULL,
7903 'admin' => $CFG->admin,
7904 'auth' => 'auth',
7905 'backup' => 'backup/util/ui',
7906 'block' => 'blocks',
7907 'blog' => 'blog',
7908 'bulkusers' => NULL,
7909 'calendar' => 'calendar',
7910 'cohort' => 'cohort',
7911 'condition' => NULL,
7912 'completion' => NULL,
7913 'countries' => NULL,
7914 'course' => 'course',
7915 'currencies' => NULL,
7916 'dbtransfer' => NULL,
7917 'debug' => NULL,
7918 'dock' => NULL,
7919 'editor' => 'lib/editor',
7920 'edufields' => NULL,
7921 'enrol' => 'enrol',
7922 'error' => NULL,
7923 'filepicker' => NULL,
7924 'files' => 'files',
7925 'filters' => NULL,
7926 'fonts' => NULL,
7927 'form' => 'lib/form',
7928 'grades' => 'grade',
7929 'grading' => 'grade/grading',
7930 'group' => 'group',
7931 'help' => NULL,
7932 'hub' => NULL,
7933 'imscc' => NULL,
7934 'install' => NULL,
7935 'iso6392' => NULL,
7936 'langconfig' => NULL,
7937 'license' => NULL,
7938 'mathslib' => NULL,
7939 'media' => 'media',
7940 'message' => 'message',
7941 'mimetypes' => NULL,
7942 'mnet' => 'mnet',
7943 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7944 'my' => 'my',
7945 'notes' => 'notes',
7946 'pagetype' => NULL,
7947 'pix' => NULL,
7948 'plagiarism' => 'plagiarism',
7949 'plugin' => NULL,
7950 'portfolio' => 'portfolio',
7951 'publish' => 'course/publish',
7952 'question' => 'question',
7953 'rating' => 'rating',
7954 'register' => 'admin/registration', //TODO: this is wrong, unfortunately we would need to modify hub code to pass around the correct url
7955 'repository' => 'repository',
7956 'rss' => 'rss',
7957 'role' => $CFG->admin.'/role',
7958 'search' => 'search',
7959 'table' => NULL,
7960 'tag' => 'tag',
7961 'timezones' => NULL,
7962 'user' => 'user',
7963 'userkey' => NULL,
7964 'webservice' => 'webservice',
7968 return $info;
7972 * Lists all plugin types
7973 * @param bool $fullpaths false means relative paths from dirroot
7974 * @return array Array of strings - name=>location
7976 function get_plugin_types($fullpaths=true) {
7977 global $CFG;
7979 static $info = null;
7980 static $fullinfo = null;
7982 if (!$info) {
7983 $info = array('qtype' => 'question/type',
7984 'mod' => 'mod',
7985 'auth' => 'auth',
7986 'enrol' => 'enrol',
7987 'message' => 'message/output',
7988 'block' => 'blocks',
7989 'filter' => 'filter',
7990 'editor' => 'lib/editor',
7991 'format' => 'course/format',
7992 'profilefield' => 'user/profile/field',
7993 'report' => 'report',
7994 'coursereport' => 'course/report', // must be after system reports
7995 'gradeexport' => 'grade/export',
7996 'gradeimport' => 'grade/import',
7997 'gradereport' => 'grade/report',
7998 'gradingform' => 'grade/grading/form',
7999 'mnetservice' => 'mnet/service',
8000 'webservice' => 'webservice',
8001 'repository' => 'repository',
8002 'portfolio' => 'portfolio',
8003 'qbehaviour' => 'question/behaviour',
8004 'qformat' => 'question/format',
8005 'plagiarism' => 'plagiarism',
8006 'tool' => $CFG->admin.'/tool',
8007 'theme' => 'theme', // this is a bit hacky, themes may be in $CFG->themedir too
8010 $subpluginowners = array_merge(array_values(get_plugin_list('mod')),
8011 array_values(get_plugin_list('editor')));
8012 foreach ($subpluginowners as $ownerdir) {
8013 if (file_exists("$ownerdir/db/subplugins.php")) {
8014 $subplugins = array();
8015 include("$ownerdir/db/subplugins.php");
8016 foreach ($subplugins as $subtype=>$dir) {
8017 $info[$subtype] = $dir;
8022 // local is always last!
8023 $info['local'] = 'local';
8025 $fullinfo = array();
8026 foreach ($info as $type => $dir) {
8027 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
8031 return ($fullpaths ? $fullinfo : $info);
8035 * Simplified version of get_list_of_plugins()
8036 * @param string $plugintype type of plugin
8037 * @return array name=>fulllocation pairs of plugins of given type
8039 function get_plugin_list($plugintype) {
8040 global $CFG;
8042 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
8043 if ($plugintype == 'auth') {
8044 // Historically we have had an auth plugin called 'db', so allow a special case.
8045 $key = array_search('db', $ignored);
8046 if ($key !== false) {
8047 unset($ignored[$key]);
8051 if ($plugintype === '') {
8052 $plugintype = 'mod';
8055 $fulldirs = array();
8057 if ($plugintype === 'mod') {
8058 // mod is an exception because we have to call this function from get_plugin_types()
8059 $fulldirs[] = $CFG->dirroot.'/mod';
8061 } else if ($plugintype === 'editor') {
8062 // Exception also needed for editor for same reason.
8063 $fulldirs[] = $CFG->dirroot . '/lib/editor';
8065 } else if ($plugintype === 'theme') {
8066 $fulldirs[] = $CFG->dirroot.'/theme';
8067 // themes are special because they may be stored also in separate directory
8068 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
8069 $fulldirs[] = $CFG->themedir;
8072 } else {
8073 $types = get_plugin_types(true);
8074 if (!array_key_exists($plugintype, $types)) {
8075 return array();
8077 $fulldir = $types[$plugintype];
8078 if (!file_exists($fulldir)) {
8079 return array();
8081 $fulldirs[] = $fulldir;
8083 $result = array();
8085 foreach ($fulldirs as $fulldir) {
8086 if (!is_dir($fulldir)) {
8087 continue;
8089 $items = new DirectoryIterator($fulldir);
8090 foreach ($items as $item) {
8091 if ($item->isDot() or !$item->isDir()) {
8092 continue;
8094 $pluginname = $item->getFilename();
8095 if (in_array($pluginname, $ignored)) {
8096 continue;
8098 $pluginname = clean_param($pluginname, PARAM_PLUGIN);
8099 if (empty($pluginname)) {
8100 // better ignore plugins with problematic names here
8101 continue;
8103 $result[$pluginname] = $fulldir.'/'.$pluginname;
8104 unset($item);
8106 unset($items);
8109 //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!
8110 ksort($result);
8111 return $result;
8115 * Get a list of all the plugins of a given type that contain a particular file.
8116 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8117 * @param string $file the name of file that must be present in the plugin.
8118 * (e.g. 'view.php', 'db/install.xml').
8119 * @param bool $include if true (default false), the file will be include_once-ed if found.
8120 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
8121 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
8123 function get_plugin_list_with_file($plugintype, $file, $include = false) {
8124 global $CFG; // Necessary in case it is referenced by include()d PHP scripts.
8126 $plugins = array();
8128 foreach(get_plugin_list($plugintype) as $plugin => $dir) {
8129 $path = $dir . '/' . $file;
8130 if (file_exists($path)) {
8131 if ($include) {
8132 include_once($path);
8134 $plugins[$plugin] = $path;
8138 return $plugins;
8142 * Get a list of all the plugins of a given type that define a certain API function
8143 * in a certain file. The plugin component names and function names are returned.
8145 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8146 * @param string $function the part of the name of the function after the
8147 * frankenstyle prefix. e.g 'hook' if you are looking for functions with
8148 * names like report_courselist_hook.
8149 * @param string $file the name of file within the plugin that defines the
8150 * function. Defaults to lib.php.
8151 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8152 * and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
8154 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
8155 $pluginfunctions = array();
8156 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8157 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
8159 if (function_exists($fullfunction)) {
8160 // Function exists with standard name. Store, indexed by
8161 // frankenstyle name of plugin
8162 $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
8164 } else if ($plugintype === 'mod') {
8165 // For modules, we also allow plugin without full frankenstyle
8166 // but just starting with the module name
8167 $shortfunction = $plugin . '_' . $function;
8168 if (function_exists($shortfunction)) {
8169 $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
8173 return $pluginfunctions;
8177 * Get a list of all the plugins of a given type that define a certain class
8178 * in a certain file. The plugin component names and class names are returned.
8180 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8181 * @param string $class the part of the name of the class after the
8182 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
8183 * names like report_courselist_thing. If you are looking for classes with
8184 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
8185 * @param string $file the name of file within the plugin that defines the class.
8186 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8187 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
8189 function get_plugin_list_with_class($plugintype, $class, $file) {
8190 if ($class) {
8191 $suffix = '_' . $class;
8192 } else {
8193 $suffix = '';
8196 $pluginclasses = array();
8197 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8198 $classname = $plugintype . '_' . $plugin . $suffix;
8199 if (class_exists($classname)) {
8200 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
8204 return $pluginclasses;
8208 * Lists plugin-like directories within specified directory
8210 * This function was originally used for standard Moodle plugins, please use
8211 * new get_plugin_list() now.
8213 * This function is used for general directory listing and backwards compatility.
8215 * @param string $directory relative directory from root
8216 * @param string $exclude dir name to exclude from the list (defaults to none)
8217 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
8218 * @return array Sorted array of directory names found under the requested parameters
8220 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
8221 global $CFG;
8223 $plugins = array();
8225 if (empty($basedir)) {
8226 $basedir = $CFG->dirroot .'/'. $directory;
8228 } else {
8229 $basedir = $basedir .'/'. $directory;
8232 if (file_exists($basedir) && filetype($basedir) == 'dir') {
8233 if (!$dirhandle = opendir($basedir)) {
8234 debugging("Directory permission error for plugin ({$directory}). Directory exists but cannot be read.", DEBUG_DEVELOPER);
8235 return array();
8237 while (false !== ($dir = readdir($dirhandle))) {
8238 $firstchar = substr($dir, 0, 1);
8239 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
8240 continue;
8242 if (filetype($basedir .'/'. $dir) != 'dir') {
8243 continue;
8245 $plugins[] = $dir;
8247 closedir($dirhandle);
8249 if ($plugins) {
8250 asort($plugins);
8252 return $plugins;
8256 * Invoke plugin's callback functions
8258 * @param string $type plugin type e.g. 'mod'
8259 * @param string $name plugin name
8260 * @param string $feature feature name
8261 * @param string $action feature's action
8262 * @param array $params parameters of callback function, should be an array
8263 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8264 * @return mixed
8266 * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
8268 function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
8269 return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
8273 * Invoke component's callback functions
8275 * @param string $component frankenstyle component name, e.g. 'mod_quiz'
8276 * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
8277 * @param array $params parameters of callback function
8278 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8279 * @return mixed
8281 function component_callback($component, $function, array $params = array(), $default = null) {
8282 global $CFG; // this is needed for require_once() below
8284 $cleancomponent = clean_param($component, PARAM_COMPONENT);
8285 if (empty($cleancomponent)) {
8286 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8288 $component = $cleancomponent;
8290 list($type, $name) = normalize_component($component);
8291 $component = $type . '_' . $name;
8293 $oldfunction = $name.'_'.$function;
8294 $function = $component.'_'.$function;
8296 $dir = get_component_directory($component);
8297 if (empty($dir)) {
8298 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8301 // Load library and look for function
8302 if (file_exists($dir.'/lib.php')) {
8303 require_once($dir.'/lib.php');
8306 if (!function_exists($function) and function_exists($oldfunction)) {
8307 if ($type !== 'mod' and $type !== 'core') {
8308 debugging("Please use new function name $function instead of legacy $oldfunction");
8310 $function = $oldfunction;
8313 if (function_exists($function)) {
8314 // Function exists, so just return function result
8315 $ret = call_user_func_array($function, $params);
8316 if (is_null($ret)) {
8317 return $default;
8318 } else {
8319 return $ret;
8322 return $default;
8326 * Checks whether a plugin supports a specified feature.
8328 * @param string $type Plugin type e.g. 'mod'
8329 * @param string $name Plugin name e.g. 'forum'
8330 * @param string $feature Feature code (FEATURE_xx constant)
8331 * @param mixed $default default value if feature support unknown
8332 * @return mixed Feature result (false if not supported, null if feature is unknown,
8333 * otherwise usually true but may have other feature-specific value such as array)
8335 function plugin_supports($type, $name, $feature, $default = NULL) {
8336 global $CFG;
8338 if ($type === 'mod' and $name === 'NEWMODULE') {
8339 //somebody forgot to rename the module template
8340 return false;
8343 $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
8344 if (empty($component)) {
8345 throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
8348 $function = null;
8350 if ($type === 'mod') {
8351 // we need this special case because we support subplugins in modules,
8352 // otherwise it would end up in infinite loop
8353 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
8354 include_once("$CFG->dirroot/mod/$name/lib.php");
8355 $function = $component.'_supports';
8356 if (!function_exists($function)) {
8357 // legacy non-frankenstyle function name
8358 $function = $name.'_supports';
8360 } else {
8361 // invalid module
8364 } else {
8365 if (!$path = get_plugin_directory($type, $name)) {
8366 // non existent plugin type
8367 return false;
8369 if (file_exists("$path/lib.php")) {
8370 include_once("$path/lib.php");
8371 $function = $component.'_supports';
8375 if ($function and function_exists($function)) {
8376 $supports = $function($feature);
8377 if (is_null($supports)) {
8378 // plugin does not know - use default
8379 return $default;
8380 } else {
8381 return $supports;
8385 //plugin does not care, so use default
8386 return $default;
8390 * Returns true if the current version of PHP is greater that the specified one.
8392 * @todo Check PHP version being required here is it too low?
8394 * @param string $version The version of php being tested.
8395 * @return bool
8397 function check_php_version($version='5.2.4') {
8398 return (version_compare(phpversion(), $version) >= 0);
8402 * Checks to see if is the browser operating system matches the specified
8403 * brand.
8405 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
8407 * @uses $_SERVER
8408 * @param string $brand The operating system identifier being tested
8409 * @return bool true if the given brand below to the detected operating system
8411 function check_browser_operating_system($brand) {
8412 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8413 return false;
8416 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
8417 return true;
8420 return false;
8424 * Checks to see if is a browser matches the specified
8425 * brand and is equal or better version.
8427 * @uses $_SERVER
8428 * @param string $brand The browser identifier being tested
8429 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
8430 * @return bool true if the given version is below that of the detected browser
8432 function check_browser_version($brand, $version = null) {
8433 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8434 return false;
8437 $agent = $_SERVER['HTTP_USER_AGENT'];
8439 switch ($brand) {
8441 case 'Camino': /// OSX browser using Gecke engine
8442 if (strpos($agent, 'Camino') === false) {
8443 return false;
8445 if (empty($version)) {
8446 return true; // no version specified
8448 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
8449 if (version_compare($match[1], $version) >= 0) {
8450 return true;
8453 break;
8456 case 'Firefox': /// Mozilla Firefox browsers
8457 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
8458 return false;
8460 if (empty($version)) {
8461 return true; // no version specified
8463 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8464 if (version_compare($match[2], $version) >= 0) {
8465 return true;
8468 break;
8471 case 'Gecko': /// Gecko based browsers
8472 if (empty($version) and substr_count($agent, 'Camino')) {
8473 // MacOS X Camino support
8474 $version = 20041110;
8477 // the proper string - Gecko/CCYYMMDD Vendor/Version
8478 // Faster version and work-a-round No IDN problem.
8479 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
8480 if ($match[1] > $version) {
8481 return true;
8484 break;
8487 case 'MSIE': /// Internet Explorer
8488 if (strpos($agent, 'Opera') !== false) { // Reject Opera
8489 return false;
8491 // in case of IE we have to deal with BC of the version parameter
8492 if (is_null($version)) {
8493 $version = 5.5; // anything older is not considered a browser at all!
8496 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
8497 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
8498 if (version_compare($match[1], $version) >= 0) {
8499 return true;
8502 break;
8505 case 'Opera': /// Opera
8506 if (strpos($agent, 'Opera') === false) {
8507 return false;
8509 if (empty($version)) {
8510 return true; // no version specified
8512 // Recent Opera useragents have Version/ with the actual version, e.g.:
8513 // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
8514 // That's Opera 12.01, not 9.8.
8515 if (preg_match("/Version\/([0-9\.]+)/i", $agent, $match)) {
8516 if (version_compare($match[1], $version) >= 0) {
8517 return true;
8519 } else if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
8520 if (version_compare($match[1], $version) >= 0) {
8521 return true;
8524 break;
8527 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
8528 if (strpos($agent, 'AppleWebKit') === false) {
8529 return false;
8531 if (empty($version)) {
8532 return true; // no version specified
8534 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8535 if (version_compare($match[1], $version) >= 0) {
8536 return true;
8539 break;
8542 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
8543 if (strpos($agent, 'AppleWebKit') === false) {
8544 return false;
8546 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
8547 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
8548 return false;
8550 if (strpos($agent, 'Shiira')) { // Reject Shiira
8551 return false;
8553 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
8554 return false;
8556 if (strpos($agent, 'Android')) { // Reject Androids too
8557 return false;
8559 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
8560 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
8561 return false;
8563 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
8564 return false;
8567 if (empty($version)) {
8568 return true; // no version specified
8570 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8571 if (version_compare($match[1], $version) >= 0) {
8572 return true;
8575 break;
8578 case 'Chrome':
8579 if (strpos($agent, 'Chrome') === false) {
8580 return false;
8582 if (empty($version)) {
8583 return true; // no version specified
8585 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
8586 if (version_compare($match[1], $version) >= 0) {
8587 return true;
8590 break;
8593 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
8594 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
8595 return false;
8597 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
8598 return false;
8600 if (empty($version)) {
8601 return true; // no version specified
8603 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8604 if (version_compare($match[1], $version) >= 0) {
8605 return true;
8608 break;
8611 case 'WebKit Android': /// WebKit browser on Android
8612 if (strpos($agent, 'Linux; U; Android') === false) {
8613 return false;
8615 if (empty($version)) {
8616 return true; // no version specified
8618 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8619 if (version_compare($match[1], $version) >= 0) {
8620 return true;
8623 break;
8627 return false;
8631 * Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
8632 * an optional admin specified regular expression. If enabledevicedetection is set to no or not set
8633 * it returns default
8635 * @return string device type
8637 function get_device_type() {
8638 global $CFG;
8640 if (empty($CFG->enabledevicedetection) || empty($_SERVER['HTTP_USER_AGENT'])) {
8641 return 'default';
8644 $useragent = $_SERVER['HTTP_USER_AGENT'];
8646 if (!empty($CFG->devicedetectregex)) {
8647 $regexes = json_decode($CFG->devicedetectregex);
8649 foreach ($regexes as $value=>$regex) {
8650 if (preg_match($regex, $useragent)) {
8651 return $value;
8656 //mobile detection PHP direct copy from open source detectmobilebrowser.com
8657 $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';
8658 $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';
8659 if (preg_match($phonesregex,$useragent) || preg_match($modelsregex,substr($useragent, 0, 4))){
8660 return 'mobile';
8663 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
8664 if (preg_match($tabletregex, $useragent)) {
8665 return 'tablet';
8668 // Safe way to check for IE6 and not get false positives for some IE 7/8 users
8669 if (substr($_SERVER['HTTP_USER_AGENT'], 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
8670 return 'legacy';
8673 return 'default';
8677 * Returns a list of the device types supporting by Moodle
8679 * @param boolean $incusertypes includes types specified using the devicedetectregex admin setting
8680 * @return array $types
8682 function get_device_type_list($incusertypes = true) {
8683 global $CFG;
8685 $types = array('default', 'legacy', 'mobile', 'tablet');
8687 if ($incusertypes && !empty($CFG->devicedetectregex)) {
8688 $regexes = json_decode($CFG->devicedetectregex);
8690 foreach ($regexes as $value => $regex) {
8691 $types[] = $value;
8695 return $types;
8699 * Returns the theme selected for a particular device or false if none selected.
8701 * @param string $devicetype
8702 * @return string|false The name of the theme to use for the device or the false if not set
8704 function get_selected_theme_for_device_type($devicetype = null) {
8705 global $CFG;
8707 if (empty($devicetype)) {
8708 $devicetype = get_user_device_type();
8711 $themevarname = get_device_cfg_var_name($devicetype);
8712 if (empty($CFG->$themevarname)) {
8713 return false;
8716 return $CFG->$themevarname;
8720 * Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
8722 * @param string $devicetype
8723 * @return string The config variable to use to determine the theme
8725 function get_device_cfg_var_name($devicetype = null) {
8726 if ($devicetype == 'default' || empty($devicetype)) {
8727 return 'theme';
8730 return 'theme' . $devicetype;
8734 * Allows the user to switch the device they are seeing the theme for.
8735 * This allows mobile users to switch back to the default theme, or theme for any other device.
8737 * @param string $newdevice The device the user is currently using.
8738 * @return string The device the user has switched to
8740 function set_user_device_type($newdevice) {
8741 global $USER;
8743 $devicetype = get_device_type();
8744 $devicetypes = get_device_type_list();
8746 if ($newdevice == $devicetype) {
8747 unset_user_preference('switchdevice'.$devicetype);
8748 } else if (in_array($newdevice, $devicetypes)) {
8749 set_user_preference('switchdevice'.$devicetype, $newdevice);
8754 * Returns the device the user is currently using, or if the user has chosen to switch devices
8755 * for the current device type the type they have switched to.
8757 * @return string The device the user is currently using or wishes to use
8759 function get_user_device_type() {
8760 $device = get_device_type();
8761 $switched = get_user_preferences('switchdevice'.$device, false);
8762 if ($switched != false) {
8763 return $switched;
8765 return $device;
8769 * Returns one or several CSS class names that match the user's browser. These can be put
8770 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
8772 * @return array An array of browser version classes
8774 function get_browser_version_classes() {
8775 $classes = array();
8777 if (check_browser_version("MSIE", "0")) {
8778 $classes[] = 'ie';
8779 if (check_browser_version("MSIE", 9)) {
8780 $classes[] = 'ie9';
8781 } else if (check_browser_version("MSIE", 8)) {
8782 $classes[] = 'ie8';
8783 } elseif (check_browser_version("MSIE", 7)) {
8784 $classes[] = 'ie7';
8785 } elseif (check_browser_version("MSIE", 6)) {
8786 $classes[] = 'ie6';
8789 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
8790 $classes[] = 'gecko';
8791 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
8792 $classes[] = "gecko{$matches[1]}{$matches[2]}";
8795 } else if (check_browser_version("WebKit")) {
8796 $classes[] = 'safari';
8797 if (check_browser_version("Safari iOS")) {
8798 $classes[] = 'ios';
8800 } else if (check_browser_version("WebKit Android")) {
8801 $classes[] = 'android';
8804 } else if (check_browser_version("Opera")) {
8805 $classes[] = 'opera';
8809 return $classes;
8813 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
8815 * @return bool True for yes, false for no
8817 function can_use_rotated_text() {
8818 return check_browser_version('MSIE', 9) || check_browser_version('Firefox', 2) ||
8819 check_browser_version('Chrome', 21) || check_browser_version('Safari', 536.25) ||
8820 check_browser_version('Opera', 12) || check_browser_version('Safari iOS', 533);
8824 * Hack to find out the GD version by parsing phpinfo output
8826 * @return int GD version (1, 2, or 0)
8828 function check_gd_version() {
8829 $gdversion = 0;
8831 if (function_exists('gd_info')){
8832 $gd_info = gd_info();
8833 if (substr_count($gd_info['GD Version'], '2.')) {
8834 $gdversion = 2;
8835 } else if (substr_count($gd_info['GD Version'], '1.')) {
8836 $gdversion = 1;
8839 } else {
8840 ob_start();
8841 phpinfo(INFO_MODULES);
8842 $phpinfo = ob_get_contents();
8843 ob_end_clean();
8845 $phpinfo = explode("\n", $phpinfo);
8848 foreach ($phpinfo as $text) {
8849 $parts = explode('</td>', $text);
8850 foreach ($parts as $key => $val) {
8851 $parts[$key] = trim(strip_tags($val));
8853 if ($parts[0] == 'GD Version') {
8854 if (substr_count($parts[1], '2.0')) {
8855 $parts[1] = '2.0';
8857 $gdversion = intval($parts[1]);
8862 return $gdversion; // 1, 2 or 0
8866 * Determine if moodle installation requires update
8868 * Checks version numbers of main code and all modules to see
8869 * if there are any mismatches
8871 * @global moodle_database $DB
8872 * @return bool
8874 function moodle_needs_upgrading() {
8875 global $CFG, $DB, $OUTPUT;
8877 if (empty($CFG->version)) {
8878 return true;
8881 // main versio nfirst
8882 $version = null;
8883 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
8884 if ($version > $CFG->version) {
8885 return true;
8888 // modules
8889 $mods = get_plugin_list('mod');
8890 $installed = $DB->get_records('modules', array(), '', 'name, version');
8891 foreach ($mods as $mod => $fullmod) {
8892 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
8893 continue;
8895 $module = new stdClass();
8896 if (!is_readable($fullmod.'/version.php')) {
8897 continue;
8899 include($fullmod.'/version.php'); // defines $module with version etc
8900 if (empty($installed[$mod])) {
8901 return true;
8902 } else if ($module->version > $installed[$mod]->version) {
8903 return true;
8906 unset($installed);
8908 // blocks
8909 $blocks = get_plugin_list('block');
8910 $installed = $DB->get_records('block', array(), '', 'name, version');
8911 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
8912 foreach ($blocks as $blockname=>$fullblock) {
8913 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
8914 continue;
8916 if (!is_readable($fullblock.'/version.php')) {
8917 continue;
8919 $plugin = new stdClass();
8920 $plugin->version = NULL;
8921 include($fullblock.'/version.php');
8922 if (empty($installed[$blockname])) {
8923 return true;
8924 } else if ($plugin->version > $installed[$blockname]->version) {
8925 return true;
8928 unset($installed);
8930 // now the rest of plugins
8931 $plugintypes = get_plugin_types();
8932 unset($plugintypes['mod']);
8933 unset($plugintypes['block']);
8935 $versions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin, value');
8936 foreach ($plugintypes as $type=>$unused) {
8937 $plugs = get_plugin_list($type);
8938 foreach ($plugs as $plug=>$fullplug) {
8939 $component = $type.'_'.$plug;
8940 if (!is_readable($fullplug.'/version.php')) {
8941 continue;
8943 $plugin = new stdClass();
8944 include($fullplug.'/version.php'); // defines $plugin with version etc
8945 if (array_key_exists($component, $versions)) {
8946 $installedversion = $versions[$component];
8947 } else {
8948 $installedversion = get_config($component, 'version');
8950 if (empty($installedversion)) { // new installation
8951 return true;
8952 } else if ($installedversion < $plugin->version) { // upgrade
8953 return true;
8958 return false;
8962 * Returns the major version of this site
8964 * Moodle version numbers consist of three numbers separated by a dot, for
8965 * example 1.9.11 or 2.0.2. The first two numbers, like 1.9 or 2.0, represent so
8966 * called major version. This function extracts the major version from either
8967 * $CFG->release (default) or eventually from the $release variable defined in
8968 * the main version.php.
8970 * @param bool $fromdisk should the version if source code files be used
8971 * @return string|false the major version like '2.3', false if could not be determined
8973 function moodle_major_version($fromdisk = false) {
8974 global $CFG;
8976 if ($fromdisk) {
8977 $release = null;
8978 require($CFG->dirroot.'/version.php');
8979 if (empty($release)) {
8980 return false;
8983 } else {
8984 if (empty($CFG->release)) {
8985 return false;
8987 $release = $CFG->release;
8990 if (preg_match('/^[0-9]+\.[0-9]+/', $release, $matches)) {
8991 return $matches[0];
8992 } else {
8993 return false;
8997 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
9000 * Sets the system locale
9002 * @category string
9003 * @param string $locale Can be used to force a locale
9005 function moodle_setlocale($locale='') {
9006 global $CFG;
9008 static $currentlocale = ''; // last locale caching
9010 $oldlocale = $currentlocale;
9012 /// Fetch the correct locale based on ostype
9013 if ($CFG->ostype == 'WINDOWS') {
9014 $stringtofetch = 'localewin';
9015 } else {
9016 $stringtofetch = 'locale';
9019 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
9020 if (!empty($locale)) {
9021 $currentlocale = $locale;
9022 } else if (!empty($CFG->locale)) { // override locale for all language packs
9023 $currentlocale = $CFG->locale;
9024 } else {
9025 $currentlocale = get_string($stringtofetch, 'langconfig');
9028 /// do nothing if locale already set up
9029 if ($oldlocale == $currentlocale) {
9030 return;
9033 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
9034 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
9035 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
9037 /// Get current values
9038 $monetary= setlocale (LC_MONETARY, 0);
9039 $numeric = setlocale (LC_NUMERIC, 0);
9040 $ctype = setlocale (LC_CTYPE, 0);
9041 if ($CFG->ostype != 'WINDOWS') {
9042 $messages= setlocale (LC_MESSAGES, 0);
9044 /// Set locale to all
9045 setlocale (LC_ALL, $currentlocale);
9046 /// Set old values
9047 setlocale (LC_MONETARY, $monetary);
9048 setlocale (LC_NUMERIC, $numeric);
9049 if ($CFG->ostype != 'WINDOWS') {
9050 setlocale (LC_MESSAGES, $messages);
9052 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
9053 setlocale (LC_CTYPE, $ctype);
9058 * Count words in a string.
9060 * Words are defined as things between whitespace.
9062 * @category string
9063 * @param string $string The text to be searched for words.
9064 * @return int The count of words in the specified string
9066 function count_words($string) {
9067 $string = strip_tags($string);
9068 return count(preg_split("/\w\b/", $string)) - 1;
9071 /** Count letters in a string.
9073 * Letters are defined as chars not in tags and different from whitespace.
9075 * @category string
9076 * @param string $string The text to be searched for letters.
9077 * @return int The count of letters in the specified text.
9079 function count_letters($string) {
9080 /// Loading the textlib singleton instance. We are going to need it.
9081 $string = strip_tags($string); // Tags are out now
9082 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
9084 return textlib::strlen($string);
9088 * Generate and return a random string of the specified length.
9090 * @param int $length The length of the string to be created.
9091 * @return string
9093 function random_string ($length=15) {
9094 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
9095 $pool .= 'abcdefghijklmnopqrstuvwxyz';
9096 $pool .= '0123456789';
9097 $poollen = strlen($pool);
9098 mt_srand ((double) microtime() * 1000000);
9099 $string = '';
9100 for ($i = 0; $i < $length; $i++) {
9101 $string .= substr($pool, (mt_rand()%($poollen)), 1);
9103 return $string;
9107 * Generate a complex random string (useful for md5 salts)
9109 * This function is based on the above {@link random_string()} however it uses a
9110 * larger pool of characters and generates a string between 24 and 32 characters
9112 * @param int $length Optional if set generates a string to exactly this length
9113 * @return string
9115 function complex_random_string($length=null) {
9116 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
9117 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
9118 $poollen = strlen($pool);
9119 mt_srand ((double) microtime() * 1000000);
9120 if ($length===null) {
9121 $length = floor(rand(24,32));
9123 $string = '';
9124 for ($i = 0; $i < $length; $i++) {
9125 $string .= $pool[(mt_rand()%$poollen)];
9127 return $string;
9131 * Given some text (which may contain HTML) and an ideal length,
9132 * this function truncates the text neatly on a word boundary if possible
9134 * @category string
9135 * @global stdClass $CFG
9136 * @param string $text text to be shortened
9137 * @param int $ideal ideal string length
9138 * @param boolean $exact if false, $text will not be cut mid-word
9139 * @param string $ending The string to append if the passed string is truncated
9140 * @return string $truncate shortened string
9142 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
9144 global $CFG;
9146 // if the plain text is shorter than the maximum length, return the whole text
9147 if (textlib::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
9148 return $text;
9151 // Splits on HTML tags. Each open/close/empty tag will be the first thing
9152 // and only tag in its 'line'
9153 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
9155 $total_length = textlib::strlen($ending);
9156 $truncate = '';
9158 // This array stores information about open and close tags and their position
9159 // in the truncated string. Each item in the array is an object with fields
9160 // ->open (true if open), ->tag (tag name in lower case), and ->pos
9161 // (byte position in truncated text)
9162 $tagdetails = array();
9164 foreach ($lines as $line_matchings) {
9165 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
9166 if (!empty($line_matchings[1])) {
9167 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
9168 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
9169 // do nothing
9170 // if tag is a closing tag (f.e. </b>)
9171 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
9172 // record closing tag
9173 $tagdetails[] = (object)array('open'=>false,
9174 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
9175 // if tag is an opening tag (f.e. <b>)
9176 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
9177 // record opening tag
9178 $tagdetails[] = (object)array('open'=>true,
9179 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
9181 // add html-tag to $truncate'd text
9182 $truncate .= $line_matchings[1];
9185 // calculate the length of the plain text part of the line; handle entities as one character
9186 $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]));
9187 if ($total_length+$content_length > $ideal) {
9188 // the number of characters which are left
9189 $left = $ideal - $total_length;
9190 $entities_length = 0;
9191 // search for html entities
9192 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)) {
9193 // calculate the real length of all entities in the legal range
9194 foreach ($entities[0] as $entity) {
9195 if ($entity[1]+1-$entities_length <= $left) {
9196 $left--;
9197 $entities_length += textlib::strlen($entity[0]);
9198 } else {
9199 // no more characters left
9200 break;
9204 $truncate .= textlib::substr($line_matchings[2], 0, $left+$entities_length);
9205 // maximum length is reached, so get off the loop
9206 break;
9207 } else {
9208 $truncate .= $line_matchings[2];
9209 $total_length += $content_length;
9212 // if the maximum length is reached, get off the loop
9213 if($total_length >= $ideal) {
9214 break;
9218 // if the words shouldn't be cut in the middle...
9219 if (!$exact) {
9220 // ...search the last occurence of a space...
9221 for ($k=textlib::strlen($truncate);$k>0;$k--) {
9222 if ($char = textlib::substr($truncate, $k, 1)) {
9223 if ($char === '.' or $char === ' ') {
9224 $breakpos = $k+1;
9225 break;
9226 } else if (strlen($char) > 2) { // Chinese/Japanese/Korean text
9227 $breakpos = $k+1; // can be truncated at any UTF-8
9228 break; // character boundary.
9233 if (isset($breakpos)) {
9234 // ...and cut the text in this position
9235 $truncate = textlib::substr($truncate, 0, $breakpos);
9239 // add the defined ending to the text
9240 $truncate .= $ending;
9242 // Now calculate the list of open html tags based on the truncate position
9243 $open_tags = array();
9244 foreach ($tagdetails as $taginfo) {
9245 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
9246 // Don't include tags after we made the break!
9247 break;
9249 if($taginfo->open) {
9250 // add tag to the beginning of $open_tags list
9251 array_unshift($open_tags, $taginfo->tag);
9252 } else {
9253 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
9254 if ($pos !== false) {
9255 unset($open_tags[$pos]);
9260 // close all unclosed html-tags
9261 foreach ($open_tags as $tag) {
9262 $truncate .= '</' . $tag . '>';
9265 return $truncate;
9270 * Given dates in seconds, how many weeks is the date from startdate
9271 * The first week is 1, the second 2 etc ...
9273 * @todo Finish documenting this function
9275 * @uses WEEKSECS
9276 * @param int $startdate Timestamp for the start date
9277 * @param int $thedate Timestamp for the end date
9278 * @return string
9280 function getweek ($startdate, $thedate) {
9281 if ($thedate < $startdate) { // error
9282 return 0;
9285 return floor(($thedate - $startdate) / WEEKSECS) + 1;
9289 * returns a randomly generated password of length $maxlen. inspired by
9291 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
9292 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
9294 * @global stdClass $CFG
9295 * @param int $maxlen The maximum size of the password being generated.
9296 * @return string
9298 function generate_password($maxlen=10) {
9299 global $CFG;
9301 if (empty($CFG->passwordpolicy)) {
9302 $fillers = PASSWORD_DIGITS;
9303 $wordlist = file($CFG->wordlist);
9304 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9305 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9306 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
9307 $password = $word1 . $filler1 . $word2;
9308 } else {
9309 $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
9310 $digits = $CFG->minpassworddigits;
9311 $lower = $CFG->minpasswordlower;
9312 $upper = $CFG->minpasswordupper;
9313 $nonalphanum = $CFG->minpasswordnonalphanum;
9314 $total = $lower + $upper + $digits + $nonalphanum;
9315 // minlength should be the greater one of the two ( $minlen and $total )
9316 $minlen = $minlen < $total ? $total : $minlen;
9317 // maxlen can never be smaller than minlen
9318 $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
9319 $additional = $maxlen - $total;
9321 // Make sure we have enough characters to fulfill
9322 // complexity requirements
9323 $passworddigits = PASSWORD_DIGITS;
9324 while ($digits > strlen($passworddigits)) {
9325 $passworddigits .= PASSWORD_DIGITS;
9327 $passwordlower = PASSWORD_LOWER;
9328 while ($lower > strlen($passwordlower)) {
9329 $passwordlower .= PASSWORD_LOWER;
9331 $passwordupper = PASSWORD_UPPER;
9332 while ($upper > strlen($passwordupper)) {
9333 $passwordupper .= PASSWORD_UPPER;
9335 $passwordnonalphanum = PASSWORD_NONALPHANUM;
9336 while ($nonalphanum > strlen($passwordnonalphanum)) {
9337 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
9340 // Now mix and shuffle it all
9341 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
9342 substr(str_shuffle ($passwordupper), 0, $upper) .
9343 substr(str_shuffle ($passworddigits), 0, $digits) .
9344 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
9345 substr(str_shuffle ($passwordlower .
9346 $passwordupper .
9347 $passworddigits .
9348 $passwordnonalphanum), 0 , $additional));
9351 return substr ($password, 0, $maxlen);
9355 * Given a float, prints it nicely.
9356 * Localized floats must not be used in calculations!
9358 * The stripzeros feature is intended for making numbers look nicer in small
9359 * areas where it is not necessary to indicate the degree of accuracy by showing
9360 * ending zeros. If you turn it on with $decimalpoints set to 3, for example,
9361 * then it will display '5.4' instead of '5.400' or '5' instead of '5.000'.
9363 * @param float $float The float to print
9364 * @param int $decimalpoints The number of decimal places to print.
9365 * @param bool $localized use localized decimal separator
9366 * @param bool $stripzeros If true, removes final zeros after decimal point
9367 * @return string locale float
9369 function format_float($float, $decimalpoints=1, $localized=true, $stripzeros=false) {
9370 if (is_null($float)) {
9371 return '';
9373 if ($localized) {
9374 $separator = get_string('decsep', 'langconfig');
9375 } else {
9376 $separator = '.';
9378 $result = number_format($float, $decimalpoints, $separator, '');
9379 if ($stripzeros) {
9380 // Remove zeros and final dot if not needed
9381 $result = preg_replace('~(' . preg_quote($separator) . ')?0+$~', '', $result);
9383 return $result;
9387 * Converts locale specific floating point/comma number back to standard PHP float value
9388 * Do NOT try to do any math operations before this conversion on any user submitted floats!
9390 * @param string $locale_float locale aware float representation
9391 * @return float
9393 function unformat_float($locale_float) {
9394 $locale_float = trim($locale_float);
9396 if ($locale_float == '') {
9397 return null;
9400 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
9402 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
9406 * Given a simple array, this shuffles it up just like shuffle()
9407 * Unlike PHP's shuffle() this function works on any machine.
9409 * @param array $array The array to be rearranged
9410 * @return array
9412 function swapshuffle($array) {
9414 srand ((double) microtime() * 10000000);
9415 $last = count($array) - 1;
9416 for ($i=0;$i<=$last;$i++) {
9417 $from = rand(0,$last);
9418 $curr = $array[$i];
9419 $array[$i] = $array[$from];
9420 $array[$from] = $curr;
9422 return $array;
9426 * Like {@link swapshuffle()}, but works on associative arrays
9428 * @param array $array The associative array to be rearranged
9429 * @return array
9431 function swapshuffle_assoc($array) {
9433 $newarray = array();
9434 $newkeys = swapshuffle(array_keys($array));
9436 foreach ($newkeys as $newkey) {
9437 $newarray[$newkey] = $array[$newkey];
9439 return $newarray;
9443 * Given an arbitrary array, and a number of draws,
9444 * this function returns an array with that amount
9445 * of items. The indexes are retained.
9447 * @todo Finish documenting this function
9449 * @param array $array
9450 * @param int $draws
9451 * @return array
9453 function draw_rand_array($array, $draws) {
9454 srand ((double) microtime() * 10000000);
9456 $return = array();
9458 $last = count($array);
9460 if ($draws > $last) {
9461 $draws = $last;
9464 while ($draws > 0) {
9465 $last--;
9467 $keys = array_keys($array);
9468 $rand = rand(0, $last);
9470 $return[$keys[$rand]] = $array[$keys[$rand]];
9471 unset($array[$keys[$rand]]);
9473 $draws--;
9476 return $return;
9480 * Calculate the difference between two microtimes
9482 * @param string $a The first Microtime
9483 * @param string $b The second Microtime
9484 * @return string
9486 function microtime_diff($a, $b) {
9487 list($a_dec, $a_sec) = explode(' ', $a);
9488 list($b_dec, $b_sec) = explode(' ', $b);
9489 return $b_sec - $a_sec + $b_dec - $a_dec;
9493 * Given a list (eg a,b,c,d,e) this function returns
9494 * an array of 1->a, 2->b, 3->c etc
9496 * @param string $list The string to explode into array bits
9497 * @param string $separator The separator used within the list string
9498 * @return array The now assembled array
9500 function make_menu_from_list($list, $separator=',') {
9502 $array = array_reverse(explode($separator, $list), true);
9503 foreach ($array as $key => $item) {
9504 $outarray[$key+1] = trim($item);
9506 return $outarray;
9510 * Creates an array that represents all the current grades that
9511 * can be chosen using the given grading type.
9513 * Negative numbers
9514 * are scales, zero is no grade, and positive numbers are maximum
9515 * grades.
9517 * @todo Finish documenting this function or better deprecated this completely!
9519 * @param int $gradingtype
9520 * @return array
9522 function make_grades_menu($gradingtype) {
9523 global $DB;
9525 $grades = array();
9526 if ($gradingtype < 0) {
9527 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
9528 return make_menu_from_list($scale->scale);
9530 } else if ($gradingtype > 0) {
9531 for ($i=$gradingtype; $i>=0; $i--) {
9532 $grades[$i] = $i .' / '. $gradingtype;
9534 return $grades;
9536 return $grades;
9540 * This function returns the number of activities
9541 * using scaleid in a courseid
9543 * @todo Finish documenting this function
9545 * @global object
9546 * @global object
9547 * @param int $courseid ?
9548 * @param int $scaleid ?
9549 * @return int
9551 function course_scale_used($courseid, $scaleid) {
9552 global $CFG, $DB;
9554 $return = 0;
9556 if (!empty($scaleid)) {
9557 if ($cms = get_course_mods($courseid)) {
9558 foreach ($cms as $cm) {
9559 //Check cm->name/lib.php exists
9560 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
9561 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
9562 $function_name = $cm->modname.'_scale_used';
9563 if (function_exists($function_name)) {
9564 if ($function_name($cm->instance,$scaleid)) {
9565 $return++;
9572 // check if any course grade item makes use of the scale
9573 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
9575 // check if any outcome in the course makes use of the scale
9576 $return += $DB->count_records_sql("SELECT COUNT('x')
9577 FROM {grade_outcomes_courses} goc,
9578 {grade_outcomes} go
9579 WHERE go.id = goc.outcomeid
9580 AND go.scaleid = ? AND goc.courseid = ?",
9581 array($scaleid, $courseid));
9583 return $return;
9587 * This function returns the number of activities
9588 * using scaleid in the entire site
9590 * @param int $scaleid
9591 * @param array $courses
9592 * @return int
9594 function site_scale_used($scaleid, &$courses) {
9595 $return = 0;
9597 if (!is_array($courses) || count($courses) == 0) {
9598 $courses = get_courses("all",false,"c.id,c.shortname");
9601 if (!empty($scaleid)) {
9602 if (is_array($courses) && count($courses) > 0) {
9603 foreach ($courses as $course) {
9604 $return += course_scale_used($course->id,$scaleid);
9608 return $return;
9612 * make_unique_id_code
9614 * @todo Finish documenting this function
9616 * @uses $_SERVER
9617 * @param string $extra Extra string to append to the end of the code
9618 * @return string
9620 function make_unique_id_code($extra='') {
9622 $hostname = 'unknownhost';
9623 if (!empty($_SERVER['HTTP_HOST'])) {
9624 $hostname = $_SERVER['HTTP_HOST'];
9625 } else if (!empty($_ENV['HTTP_HOST'])) {
9626 $hostname = $_ENV['HTTP_HOST'];
9627 } else if (!empty($_SERVER['SERVER_NAME'])) {
9628 $hostname = $_SERVER['SERVER_NAME'];
9629 } else if (!empty($_ENV['SERVER_NAME'])) {
9630 $hostname = $_ENV['SERVER_NAME'];
9633 $date = gmdate("ymdHis");
9635 $random = random_string(6);
9637 if ($extra) {
9638 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
9639 } else {
9640 return $hostname .'+'. $date .'+'. $random;
9646 * Function to check the passed address is within the passed subnet
9648 * The parameter is a comma separated string of subnet definitions.
9649 * Subnet strings can be in one of three formats:
9650 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
9651 * 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)
9652 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
9653 * Code for type 1 modified from user posted comments by mediator at
9654 * {@link http://au.php.net/manual/en/function.ip2long.php}
9656 * @param string $addr The address you are checking
9657 * @param string $subnetstr The string of subnet addresses
9658 * @return bool
9660 function address_in_subnet($addr, $subnetstr) {
9662 if ($addr == '0.0.0.0') {
9663 return false;
9665 $subnets = explode(',', $subnetstr);
9666 $found = false;
9667 $addr = trim($addr);
9668 $addr = cleanremoteaddr($addr, false); // normalise
9669 if ($addr === null) {
9670 return false;
9672 $addrparts = explode(':', $addr);
9674 $ipv6 = strpos($addr, ':');
9676 foreach ($subnets as $subnet) {
9677 $subnet = trim($subnet);
9678 if ($subnet === '') {
9679 continue;
9682 if (strpos($subnet, '/') !== false) {
9683 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
9684 list($ip, $mask) = explode('/', $subnet);
9685 $mask = trim($mask);
9686 if (!is_number($mask)) {
9687 continue; // incorect mask number, eh?
9689 $ip = cleanremoteaddr($ip, false); // normalise
9690 if ($ip === null) {
9691 continue;
9693 if (strpos($ip, ':') !== false) {
9694 // IPv6
9695 if (!$ipv6) {
9696 continue;
9698 if ($mask > 128 or $mask < 0) {
9699 continue; // nonsense
9701 if ($mask == 0) {
9702 return true; // any address
9704 if ($mask == 128) {
9705 if ($ip === $addr) {
9706 return true;
9708 continue;
9710 $ipparts = explode(':', $ip);
9711 $modulo = $mask % 16;
9712 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
9713 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
9714 if (implode(':', $ipnet) === implode(':', $addrnet)) {
9715 if ($modulo == 0) {
9716 return true;
9718 $pos = ($mask-$modulo)/16;
9719 $ipnet = hexdec($ipparts[$pos]);
9720 $addrnet = hexdec($addrparts[$pos]);
9721 $mask = 0xffff << (16 - $modulo);
9722 if (($addrnet & $mask) == ($ipnet & $mask)) {
9723 return true;
9727 } else {
9728 // IPv4
9729 if ($ipv6) {
9730 continue;
9732 if ($mask > 32 or $mask < 0) {
9733 continue; // nonsense
9735 if ($mask == 0) {
9736 return true;
9738 if ($mask == 32) {
9739 if ($ip === $addr) {
9740 return true;
9742 continue;
9744 $mask = 0xffffffff << (32 - $mask);
9745 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
9746 return true;
9750 } else if (strpos($subnet, '-') !== false) {
9751 /// 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.
9752 $parts = explode('-', $subnet);
9753 if (count($parts) != 2) {
9754 continue;
9757 if (strpos($subnet, ':') !== false) {
9758 // IPv6
9759 if (!$ipv6) {
9760 continue;
9762 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9763 if ($ipstart === null) {
9764 continue;
9766 $ipparts = explode(':', $ipstart);
9767 $start = hexdec(array_pop($ipparts));
9768 $ipparts[] = trim($parts[1]);
9769 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
9770 if ($ipend === null) {
9771 continue;
9773 $ipparts[7] = '';
9774 $ipnet = implode(':', $ipparts);
9775 if (strpos($addr, $ipnet) !== 0) {
9776 continue;
9778 $ipparts = explode(':', $ipend);
9779 $end = hexdec($ipparts[7]);
9781 $addrend = hexdec($addrparts[7]);
9783 if (($addrend >= $start) and ($addrend <= $end)) {
9784 return true;
9787 } else {
9788 // IPv4
9789 if ($ipv6) {
9790 continue;
9792 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9793 if ($ipstart === null) {
9794 continue;
9796 $ipparts = explode('.', $ipstart);
9797 $ipparts[3] = trim($parts[1]);
9798 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
9799 if ($ipend === null) {
9800 continue;
9803 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
9804 return true;
9808 } else {
9809 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
9810 if (strpos($subnet, ':') !== false) {
9811 // IPv6
9812 if (!$ipv6) {
9813 continue;
9815 $parts = explode(':', $subnet);
9816 $count = count($parts);
9817 if ($parts[$count-1] === '') {
9818 unset($parts[$count-1]); // trim trailing :
9819 $count--;
9820 $subnet = implode('.', $parts);
9822 $isip = cleanremoteaddr($subnet, false); // normalise
9823 if ($isip !== null) {
9824 if ($isip === $addr) {
9825 return true;
9827 continue;
9828 } else if ($count > 8) {
9829 continue;
9831 $zeros = array_fill(0, 8-$count, '0');
9832 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
9833 if (address_in_subnet($addr, $subnet)) {
9834 return true;
9837 } else {
9838 // IPv4
9839 if ($ipv6) {
9840 continue;
9842 $parts = explode('.', $subnet);
9843 $count = count($parts);
9844 if ($parts[$count-1] === '') {
9845 unset($parts[$count-1]); // trim trailing .
9846 $count--;
9847 $subnet = implode('.', $parts);
9849 if ($count == 4) {
9850 $subnet = cleanremoteaddr($subnet, false); // normalise
9851 if ($subnet === $addr) {
9852 return true;
9854 continue;
9855 } else if ($count > 4) {
9856 continue;
9858 $zeros = array_fill(0, 4-$count, '0');
9859 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
9860 if (address_in_subnet($addr, $subnet)) {
9861 return true;
9867 return false;
9871 * For outputting debugging info
9873 * @uses STDOUT
9874 * @param string $string The string to write
9875 * @param string $eol The end of line char(s) to use
9876 * @param string $sleep Period to make the application sleep
9877 * This ensures any messages have time to display before redirect
9879 function mtrace($string, $eol="\n", $sleep=0) {
9881 if (defined('STDOUT') and !PHPUNIT_TEST) {
9882 fwrite(STDOUT, $string.$eol);
9883 } else {
9884 echo $string . $eol;
9887 flush();
9889 //delay to keep message on user's screen in case of subsequent redirect
9890 if ($sleep) {
9891 sleep($sleep);
9896 * Replace 1 or more slashes or backslashes to 1 slash
9898 * @param string $path The path to strip
9899 * @return string the path with double slashes removed
9901 function cleardoubleslashes ($path) {
9902 return preg_replace('/(\/|\\\){1,}/','/',$path);
9906 * Is current ip in give list?
9908 * @param string $list
9909 * @return bool
9911 function remoteip_in_list($list){
9912 $inlist = false;
9913 $client_ip = getremoteaddr(null);
9915 if(!$client_ip){
9916 // ensure access on cli
9917 return true;
9920 $list = explode("\n", $list);
9921 foreach($list as $subnet) {
9922 $subnet = trim($subnet);
9923 if (address_in_subnet($client_ip, $subnet)) {
9924 $inlist = true;
9925 break;
9928 return $inlist;
9932 * Returns most reliable client address
9934 * @global object
9935 * @param string $default If an address can't be determined, then return this
9936 * @return string The remote IP address
9938 function getremoteaddr($default='0.0.0.0') {
9939 global $CFG;
9941 if (empty($CFG->getremoteaddrconf)) {
9942 // This will happen, for example, before just after the upgrade, as the
9943 // user is redirected to the admin screen.
9944 $variablestoskip = 0;
9945 } else {
9946 $variablestoskip = $CFG->getremoteaddrconf;
9948 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
9949 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
9950 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
9951 return $address ? $address : $default;
9954 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
9955 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
9956 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
9957 return $address ? $address : $default;
9960 if (!empty($_SERVER['REMOTE_ADDR'])) {
9961 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
9962 return $address ? $address : $default;
9963 } else {
9964 return $default;
9969 * Cleans an ip address. Internal addresses are now allowed.
9970 * (Originally local addresses were not allowed.)
9972 * @param string $addr IPv4 or IPv6 address
9973 * @param bool $compress use IPv6 address compression
9974 * @return string normalised ip address string, null if error
9976 function cleanremoteaddr($addr, $compress=false) {
9977 $addr = trim($addr);
9979 //TODO: maybe add a separate function is_addr_public() or something like this
9981 if (strpos($addr, ':') !== false) {
9982 // can be only IPv6
9983 $parts = explode(':', $addr);
9984 $count = count($parts);
9986 if (strpos($parts[$count-1], '.') !== false) {
9987 //legacy ipv4 notation
9988 $last = array_pop($parts);
9989 $ipv4 = cleanremoteaddr($last, true);
9990 if ($ipv4 === null) {
9991 return null;
9993 $bits = explode('.', $ipv4);
9994 $parts[] = dechex($bits[0]).dechex($bits[1]);
9995 $parts[] = dechex($bits[2]).dechex($bits[3]);
9996 $count = count($parts);
9997 $addr = implode(':', $parts);
10000 if ($count < 3 or $count > 8) {
10001 return null; // severly malformed
10004 if ($count != 8) {
10005 if (strpos($addr, '::') === false) {
10006 return null; // malformed
10008 // uncompress ::
10009 $insertat = array_search('', $parts, true);
10010 $missing = array_fill(0, 1 + 8 - $count, '0');
10011 array_splice($parts, $insertat, 1, $missing);
10012 foreach ($parts as $key=>$part) {
10013 if ($part === '') {
10014 $parts[$key] = '0';
10019 $adr = implode(':', $parts);
10020 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
10021 return null; // incorrect format - sorry
10024 // normalise 0s and case
10025 $parts = array_map('hexdec', $parts);
10026 $parts = array_map('dechex', $parts);
10028 $result = implode(':', $parts);
10030 if (!$compress) {
10031 return $result;
10034 if ($result === '0:0:0:0:0:0:0:0') {
10035 return '::'; // all addresses
10038 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
10039 if ($compressed !== $result) {
10040 return $compressed;
10043 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
10044 if ($compressed !== $result) {
10045 return $compressed;
10048 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
10049 if ($compressed !== $result) {
10050 return $compressed;
10053 return $result;
10056 // first get all things that look like IPv4 addresses
10057 $parts = array();
10058 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
10059 return null;
10061 unset($parts[0]);
10063 foreach ($parts as $key=>$match) {
10064 if ($match > 255) {
10065 return null;
10067 $parts[$key] = (int)$match; // normalise 0s
10070 return implode('.', $parts);
10074 * This function will make a complete copy of anything it's given,
10075 * regardless of whether it's an object or not.
10077 * @param mixed $thing Something you want cloned
10078 * @return mixed What ever it is you passed it
10080 function fullclone($thing) {
10081 return unserialize(serialize($thing));
10086 * This function expects to called during shutdown
10087 * should be set via register_shutdown_function()
10088 * in lib/setup.php .
10090 * @return void
10092 function moodle_request_shutdown() {
10093 global $CFG;
10095 // help apache server if possible
10096 $apachereleasemem = false;
10097 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
10098 && ini_get_bool('child_terminate')) {
10100 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
10101 if (memory_get_usage() > get_real_size($limit)) {
10102 $apachereleasemem = $limit;
10103 @apache_child_terminate();
10107 // deal with perf logging
10108 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
10109 if ($apachereleasemem) {
10110 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
10112 if (defined('MDL_PERFTOLOG')) {
10113 $perf = get_performance_info();
10114 error_log("PERF: " . $perf['txt']);
10116 if (defined('MDL_PERFINC')) {
10117 $inc = get_included_files();
10118 $ts = 0;
10119 foreach($inc as $f) {
10120 if (preg_match(':^/:', $f)) {
10121 $fs = filesize($f);
10122 $ts += $fs;
10123 $hfs = display_size($fs);
10124 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
10125 , NULL, NULL, 0);
10126 } else {
10127 error_log($f , NULL, NULL, 0);
10130 if ($ts > 0 ) {
10131 $hts = display_size($ts);
10132 error_log("Total size of files included: $ts ($hts)");
10139 * If new messages are waiting for the current user, then insert
10140 * JavaScript to pop up the messaging window into the page
10142 * @global moodle_page $PAGE
10143 * @return void
10145 function message_popup_window() {
10146 global $USER, $DB, $PAGE, $CFG, $SITE;
10148 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
10149 return;
10152 if (!isloggedin() || isguestuser()) {
10153 return;
10156 if (!isset($USER->message_lastpopup)) {
10157 $USER->message_lastpopup = 0;
10158 } else if ($USER->message_lastpopup > (time()-120)) {
10159 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
10160 return;
10163 //a quick query to check whether the user has new messages
10164 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
10165 if ($messagecount<1) {
10166 return;
10169 //got unread messages so now do another query that joins with the user table
10170 $messagesql = "SELECT m.id, m.smallmessage, m.fullmessageformat, m.notification, u.firstname, u.lastname
10171 FROM {message} m
10172 JOIN {message_working} mw ON m.id=mw.unreadmessageid
10173 JOIN {message_processors} p ON mw.processorid=p.id
10174 JOIN {user} u ON m.useridfrom=u.id
10175 WHERE m.useridto = :userid
10176 AND p.name='popup'";
10178 //if the user was last notified over an hour ago we can renotify them of old messages
10179 //so don't worry about when the new message was sent
10180 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
10181 if (!$lastnotifiedlongago) {
10182 $messagesql .= 'AND m.timecreated > :lastpopuptime';
10185 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
10187 //if we have new messages to notify the user about
10188 if (!empty($message_users)) {
10190 $strmessages = '';
10191 if (count($message_users)>1) {
10192 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
10193 } else {
10194 $message_users = reset($message_users);
10196 //show who the message is from if its not a notification
10197 if (!$message_users->notification) {
10198 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
10201 //try to display the small version of the message
10202 $smallmessage = null;
10203 if (!empty($message_users->smallmessage)) {
10204 //display the first 200 chars of the message in the popup
10205 $smallmessage = null;
10206 if (textlib::strlen($message_users->smallmessage) > 200) {
10207 $smallmessage = textlib::substr($message_users->smallmessage,0,200).'...';
10208 } else {
10209 $smallmessage = $message_users->smallmessage;
10212 //prevent html symbols being displayed
10213 if ($message_users->fullmessageformat == FORMAT_HTML) {
10214 $smallmessage = html_to_text($smallmessage);
10215 } else {
10216 $smallmessage = s($smallmessage);
10218 } else if ($message_users->notification) {
10219 //its a notification with no smallmessage so just say they have a notification
10220 $smallmessage = get_string('unreadnewnotification', 'message');
10222 if (!empty($smallmessage)) {
10223 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
10227 $strgomessage = get_string('gotomessages', 'message');
10228 $strstaymessage = get_string('ignore','admin');
10230 $url = $CFG->wwwroot.'/message/index.php';
10231 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
10232 html_writer::start_tag('div', array('id'=>'newmessagetext')).
10233 $strmessages.
10234 html_writer::end_tag('div').
10236 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
10237 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
10238 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
10239 html_writer::end_tag('div');
10240 html_writer::end_tag('div');
10242 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
10244 $USER->message_lastpopup = time();
10249 * Used to make sure that $min <= $value <= $max
10251 * Make sure that value is between min, and max
10253 * @param int $min The minimum value
10254 * @param int $value The value to check
10255 * @param int $max The maximum value
10257 function bounded_number($min, $value, $max) {
10258 if($value < $min) {
10259 return $min;
10261 if($value > $max) {
10262 return $max;
10264 return $value;
10268 * Check if there is a nested array within the passed array
10270 * @param array $array
10271 * @return bool true if there is a nested array false otherwise
10273 function array_is_nested($array) {
10274 foreach ($array as $value) {
10275 if (is_array($value)) {
10276 return true;
10279 return false;
10283 * get_performance_info() pairs up with init_performance_info()
10284 * loaded in setup.php. Returns an array with 'html' and 'txt'
10285 * values ready for use, and each of the individual stats provided
10286 * separately as well.
10288 * @global object
10289 * @global object
10290 * @global object
10291 * @return array
10293 function get_performance_info() {
10294 global $CFG, $PERF, $DB, $PAGE;
10296 $info = array();
10297 $info['html'] = ''; // holds userfriendly HTML representation
10298 $info['txt'] = me() . ' '; // holds log-friendly representation
10300 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
10302 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
10303 $info['txt'] .= 'time: '.$info['realtime'].'s ';
10305 if (function_exists('memory_get_usage')) {
10306 $info['memory_total'] = memory_get_usage();
10307 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
10308 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
10309 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
10312 if (function_exists('memory_get_peak_usage')) {
10313 $info['memory_peak'] = memory_get_peak_usage();
10314 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
10315 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
10318 $inc = get_included_files();
10319 //error_log(print_r($inc,1));
10320 $info['includecount'] = count($inc);
10321 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
10322 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
10324 $filtermanager = filter_manager::instance();
10325 if (method_exists($filtermanager, 'get_performance_summary')) {
10326 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
10327 $info = array_merge($filterinfo, $info);
10328 foreach ($filterinfo as $key => $value) {
10329 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10330 $info['txt'] .= "$key: $value ";
10334 $stringmanager = get_string_manager();
10335 if (method_exists($stringmanager, 'get_performance_summary')) {
10336 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
10337 $info = array_merge($filterinfo, $info);
10338 foreach ($filterinfo as $key => $value) {
10339 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10340 $info['txt'] .= "$key: $value ";
10344 $jsmodules = $PAGE->requires->get_loaded_modules();
10345 if ($jsmodules) {
10346 $yuicount = 0;
10347 $othercount = 0;
10348 $details = '';
10349 foreach ($jsmodules as $module => $backtraces) {
10350 if (strpos($module, 'yui') === 0) {
10351 $yuicount += 1;
10352 } else {
10353 $othercount += 1;
10355 if (!empty($CFG->yuimoduledebug)) {
10356 // hidden feature for developers working on YUI module infrastructure
10357 $details .= "<div class='yui-module'><p>$module</p>";
10358 foreach ($backtraces as $backtrace) {
10359 $details .= "<div class='backtrace'>$backtrace</div>";
10361 $details .= '</div>';
10364 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
10365 $info['txt'] .= "includedyuimodules: $yuicount ";
10366 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
10367 $info['txt'] .= "includedjsmodules: $othercount ";
10368 if ($details) {
10369 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
10373 if (!empty($PERF->logwrites)) {
10374 $info['logwrites'] = $PERF->logwrites;
10375 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
10376 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
10379 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
10380 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
10381 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
10383 if (function_exists('posix_times')) {
10384 $ptimes = posix_times();
10385 if (is_array($ptimes)) {
10386 foreach ($ptimes as $key => $val) {
10387 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
10389 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
10390 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
10394 // Grab the load average for the last minute
10395 // /proc will only work under some linux configurations
10396 // while uptime is there under MacOSX/Darwin and other unices
10397 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
10398 list($server_load) = explode(' ', $loadavg[0]);
10399 unset($loadavg);
10400 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
10401 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
10402 $server_load = $matches[1];
10403 } else {
10404 trigger_error('Could not parse uptime output!');
10407 if (!empty($server_load)) {
10408 $info['serverload'] = $server_load;
10409 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
10410 $info['txt'] .= "serverload: {$info['serverload']} ";
10413 // Display size of session if session started
10414 if (session_id()) {
10415 $info['sessionsize'] = display_size(strlen(session_encode()));
10416 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
10417 $info['txt'] .= "Session: {$info['sessionsize']} ";
10420 /* if (isset($rcache->hits) && isset($rcache->misses)) {
10421 $info['rcachehits'] = $rcache->hits;
10422 $info['rcachemisses'] = $rcache->misses;
10423 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
10424 "{$rcache->hits}/{$rcache->misses}</span> ";
10425 $info['txt'] .= 'rcache: '.
10426 "{$rcache->hits}/{$rcache->misses} ";
10428 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
10429 return $info;
10433 * @todo Document this function linux people
10435 function apd_get_profiling() {
10436 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
10440 * Delete directory or only its content
10442 * @param string $dir directory path
10443 * @param bool $content_only
10444 * @return bool success, true also if dir does not exist
10446 function remove_dir($dir, $content_only=false) {
10447 if (!file_exists($dir)) {
10448 // nothing to do
10449 return true;
10451 if (!$handle = opendir($dir)) {
10452 return false;
10454 $result = true;
10455 while (false!==($item = readdir($handle))) {
10456 if($item != '.' && $item != '..') {
10457 if(is_dir($dir.'/'.$item)) {
10458 $result = remove_dir($dir.'/'.$item) && $result;
10459 }else{
10460 $result = unlink($dir.'/'.$item) && $result;
10464 closedir($handle);
10465 if ($content_only) {
10466 clearstatcache(); // make sure file stat cache is properly invalidated
10467 return $result;
10469 $result = rmdir($dir); // if anything left the result will be false, no need for && $result
10470 clearstatcache(); // make sure file stat cache is properly invalidated
10471 return $result;
10475 * Detect if an object or a class contains a given property
10476 * will take an actual object or the name of a class
10478 * @param mix $obj Name of class or real object to test
10479 * @param string $property name of property to find
10480 * @return bool true if property exists
10482 function object_property_exists( $obj, $property ) {
10483 if (is_string( $obj )) {
10484 $properties = get_class_vars( $obj );
10486 else {
10487 $properties = get_object_vars( $obj );
10489 return array_key_exists( $property, $properties );
10493 * Converts an object into an associative array
10495 * This function converts an object into an associative array by iterating
10496 * over its public properties. Because this function uses the foreach
10497 * construct, Iterators are respected. It works recursively on arrays of objects.
10498 * Arrays and simple values are returned as is.
10500 * If class has magic properties, it can implement IteratorAggregate
10501 * and return all available properties in getIterator()
10503 * @param mixed $var
10504 * @return array
10506 function convert_to_array($var) {
10507 $result = array();
10509 // loop over elements/properties
10510 foreach ($var as $key => $value) {
10511 // recursively convert objects
10512 if (is_object($value) || is_array($value)) {
10513 $result[$key] = convert_to_array($value);
10514 } else {
10515 // simple values are untouched
10516 $result[$key] = $value;
10519 return $result;
10523 * Detect a custom script replacement in the data directory that will
10524 * replace an existing moodle script
10526 * @return string|bool full path name if a custom script exists, false if no custom script exists
10528 function custom_script_path() {
10529 global $CFG, $SCRIPT;
10531 if ($SCRIPT === null) {
10532 // Probably some weird external script
10533 return false;
10536 $scriptpath = $CFG->customscripts . $SCRIPT;
10538 // check the custom script exists
10539 if (file_exists($scriptpath) and is_file($scriptpath)) {
10540 return $scriptpath;
10541 } else {
10542 return false;
10547 * Returns whether or not the user object is a remote MNET user. This function
10548 * is in moodlelib because it does not rely on loading any of the MNET code.
10550 * @global object
10551 * @param object $user A valid user object
10552 * @return bool True if the user is from a remote Moodle.
10554 function is_mnet_remote_user($user) {
10555 global $CFG;
10557 if (!isset($CFG->mnet_localhost_id)) {
10558 include_once $CFG->dirroot . '/mnet/lib.php';
10559 $env = new mnet_environment();
10560 $env->init();
10561 unset($env);
10564 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
10568 * This function will search for browser prefereed languages, setting Moodle
10569 * to use the best one available if $SESSION->lang is undefined
10571 * @global object
10572 * @global object
10573 * @global object
10575 function setup_lang_from_browser() {
10577 global $CFG, $SESSION, $USER;
10579 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
10580 // Lang is defined in session or user profile, nothing to do
10581 return;
10584 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
10585 return;
10588 /// Extract and clean langs from headers
10589 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
10590 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
10591 $rawlangs = explode(',', $rawlangs); // Convert to array
10592 $langs = array();
10594 $order = 1.0;
10595 foreach ($rawlangs as $lang) {
10596 if (strpos($lang, ';') === false) {
10597 $langs[(string)$order] = $lang;
10598 $order = $order-0.01;
10599 } else {
10600 $parts = explode(';', $lang);
10601 $pos = strpos($parts[1], '=');
10602 $langs[substr($parts[1], $pos+1)] = $parts[0];
10605 krsort($langs, SORT_NUMERIC);
10607 /// Look for such langs under standard locations
10608 foreach ($langs as $lang) {
10609 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
10610 if (get_string_manager()->translation_exists($lang, false)) {
10611 $SESSION->lang = $lang; /// Lang exists, set it in session
10612 break; /// We have finished. Go out
10615 return;
10619 * check if $url matches anything in proxybypass list
10621 * any errors just result in the proxy being used (least bad)
10623 * @global object
10624 * @param string $url url to check
10625 * @return boolean true if we should bypass the proxy
10627 function is_proxybypass( $url ) {
10628 global $CFG;
10630 // sanity check
10631 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
10632 return false;
10635 // get the host part out of the url
10636 if (!$host = parse_url( $url, PHP_URL_HOST )) {
10637 return false;
10640 // get the possible bypass hosts into an array
10641 $matches = explode( ',', $CFG->proxybypass );
10643 // check for a match
10644 // (IPs need to match the left hand side and hosts the right of the url,
10645 // but we can recklessly check both as there can't be a false +ve)
10646 $bypass = false;
10647 foreach ($matches as $match) {
10648 $match = trim($match);
10650 // try for IP match (Left side)
10651 $lhs = substr($host,0,strlen($match));
10652 if (strcasecmp($match,$lhs)==0) {
10653 return true;
10656 // try for host match (Right side)
10657 $rhs = substr($host,-strlen($match));
10658 if (strcasecmp($match,$rhs)==0) {
10659 return true;
10663 // nothing matched.
10664 return false;
10668 ////////////////////////////////////////////////////////////////////////////////
10671 * Check if the passed navigation is of the new style
10673 * @param mixed $navigation
10674 * @return bool true for yes false for no
10676 function is_newnav($navigation) {
10677 if (is_array($navigation) && !empty($navigation['newnav'])) {
10678 return true;
10679 } else {
10680 return false;
10685 * Checks whether the given variable name is defined as a variable within the given object.
10687 * This will NOT work with stdClass objects, which have no class variables.
10689 * @param string $var The variable name
10690 * @param object $object The object to check
10691 * @return boolean
10693 function in_object_vars($var, $object) {
10694 $class_vars = get_class_vars(get_class($object));
10695 $class_vars = array_keys($class_vars);
10696 return in_array($var, $class_vars);
10700 * Returns an array without repeated objects.
10701 * This function is similar to array_unique, but for arrays that have objects as values
10703 * @param array $array
10704 * @param bool $keep_key_assoc
10705 * @return array
10707 function object_array_unique($array, $keep_key_assoc = true) {
10708 $duplicate_keys = array();
10709 $tmp = array();
10711 foreach ($array as $key=>$val) {
10712 // convert objects to arrays, in_array() does not support objects
10713 if (is_object($val)) {
10714 $val = (array)$val;
10717 if (!in_array($val, $tmp)) {
10718 $tmp[] = $val;
10719 } else {
10720 $duplicate_keys[] = $key;
10724 foreach ($duplicate_keys as $key) {
10725 unset($array[$key]);
10728 return $keep_key_assoc ? $array : array_values($array);
10732 * Is a userid the primary administrator?
10734 * @param int $userid int id of user to check
10735 * @return boolean
10737 function is_primary_admin($userid){
10738 $primaryadmin = get_admin();
10740 if($userid == $primaryadmin->id){
10741 return true;
10742 }else{
10743 return false;
10748 * Returns the site identifier
10750 * @global object
10751 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
10753 function get_site_identifier() {
10754 global $CFG;
10755 // Check to see if it is missing. If so, initialise it.
10756 if (empty($CFG->siteidentifier)) {
10757 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
10759 // Return it.
10760 return $CFG->siteidentifier;
10764 * Check whether the given password has no more than the specified
10765 * number of consecutive identical characters.
10767 * @param string $password password to be checked against the password policy
10768 * @param integer $maxchars maximum number of consecutive identical characters
10770 function check_consecutive_identical_characters($password, $maxchars) {
10772 if ($maxchars < 1) {
10773 return true; // 0 is to disable this check
10775 if (strlen($password) <= $maxchars) {
10776 return true; // too short to fail this test
10779 $previouschar = '';
10780 $consecutivecount = 1;
10781 foreach (str_split($password) as $char) {
10782 if ($char != $previouschar) {
10783 $consecutivecount = 1;
10785 else {
10786 $consecutivecount++;
10787 if ($consecutivecount > $maxchars) {
10788 return false; // check failed already
10792 $previouschar = $char;
10795 return true;
10799 * helper function to do partial function binding
10800 * so we can use it for preg_replace_callback, for example
10801 * this works with php functions, user functions, static methods and class methods
10802 * it returns you a callback that you can pass on like so:
10804 * $callback = partial('somefunction', $arg1, $arg2);
10805 * or
10806 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
10807 * or even
10808 * $obj = new someclass();
10809 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
10811 * and then the arguments that are passed through at calltime are appended to the argument list.
10813 * @param mixed $function a php callback
10814 * $param mixed $arg1.. $argv arguments to partially bind with
10816 * @return callback
10818 function partial() {
10819 if (!class_exists('partial')) {
10820 class partial{
10821 var $values = array();
10822 var $func;
10824 function __construct($func, $args) {
10825 $this->values = $args;
10826 $this->func = $func;
10829 function method() {
10830 $args = func_get_args();
10831 return call_user_func_array($this->func, array_merge($this->values, $args));
10835 $args = func_get_args();
10836 $func = array_shift($args);
10837 $p = new partial($func, $args);
10838 return array($p, 'method');
10842 * helper function to load up and initialise the mnet environment
10843 * this must be called before you use mnet functions.
10845 * @return mnet_environment the equivalent of old $MNET global
10847 function get_mnet_environment() {
10848 global $CFG;
10849 require_once($CFG->dirroot . '/mnet/lib.php');
10850 static $instance = null;
10851 if (empty($instance)) {
10852 $instance = new mnet_environment();
10853 $instance->init();
10855 return $instance;
10859 * during xmlrpc server code execution, any code wishing to access
10860 * information about the remote peer must use this to get it.
10862 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
10864 function get_mnet_remote_client() {
10865 if (!defined('MNET_SERVER')) {
10866 debugging(get_string('notinxmlrpcserver', 'mnet'));
10867 return false;
10869 global $MNET_REMOTE_CLIENT;
10870 if (isset($MNET_REMOTE_CLIENT)) {
10871 return $MNET_REMOTE_CLIENT;
10873 return false;
10877 * during the xmlrpc server code execution, this will be called
10878 * to setup the object returned by {@see get_mnet_remote_client}
10880 * @param mnet_remote_client $client the client to set up
10882 function set_mnet_remote_client($client) {
10883 if (!defined('MNET_SERVER')) {
10884 throw new moodle_exception('notinxmlrpcserver', 'mnet');
10886 global $MNET_REMOTE_CLIENT;
10887 $MNET_REMOTE_CLIENT = $client;
10891 * return the jump url for a given remote user
10892 * this is used for rewriting forum post links in emails, etc
10894 * @param stdclass $user the user to get the idp url for
10896 function mnet_get_idp_jump_url($user) {
10897 global $CFG;
10899 static $mnetjumps = array();
10900 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
10901 $idp = mnet_get_peer_host($user->mnethostid);
10902 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
10903 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
10905 return $mnetjumps[$user->mnethostid];
10909 * Gets the homepage to use for the current user
10911 * @return int One of HOMEPAGE_*
10913 function get_home_page() {
10914 global $CFG;
10916 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
10917 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
10918 return HOMEPAGE_MY;
10919 } else {
10920 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
10923 return HOMEPAGE_SITE;
10927 * Gets the name of a course to be displayed when showing a list of courses.
10928 * By default this is just $course->fullname but user can configure it. The
10929 * result of this function should be passed through print_string.
10930 * @param object $course Moodle course object
10931 * @return string Display name of course (either fullname or short + fullname)
10933 function get_course_display_name_for_list($course) {
10934 global $CFG;
10935 if (!empty($CFG->courselistshortnames)) {
10936 return get_string('courseextendednamedisplay', '', $course);
10937 } else {
10938 return $course->fullname;
10943 * The lang_string class
10945 * This special class is used to create an object representation of a string request.
10946 * It is special because processing doesn't occur until the object is first used.
10947 * The class was created especially to aid performance in areas where strings were
10948 * required to be generated but were not necessarily used.
10949 * As an example the admin tree when generated uses over 1500 strings, of which
10950 * normally only 1/3 are ever actually printed at any time.
10951 * The performance advantage is achieved by not actually processing strings that
10952 * arn't being used, as such reducing the processing required for the page.
10954 * How to use the lang_string class?
10955 * There are two methods of using the lang_string class, first through the
10956 * forth argument of the get_string function, and secondly directly.
10957 * The following are examples of both.
10958 * 1. Through get_string calls e.g.
10959 * $string = get_string($identifier, $component, $a, true);
10960 * $string = get_string('yes', 'moodle', null, true);
10961 * 2. Direct instantiation
10962 * $string = new lang_string($identifier, $component, $a, $lang);
10963 * $string = new lang_string('yes');
10965 * How do I use a lang_string object?
10966 * The lang_string object makes use of a magic __toString method so that you
10967 * are able to use the object exactly as you would use a string in most cases.
10968 * This means you are able to collect it into a variable and then directly
10969 * echo it, or concatenate it into another string, or similar.
10970 * The other thing you can do is manually get the string by calling the
10971 * lang_strings out method e.g.
10972 * $string = new lang_string('yes');
10973 * $string->out();
10974 * Also worth noting is that the out method can take one argument, $lang which
10975 * allows the developer to change the language on the fly.
10977 * When should I use a lang_string object?
10978 * The lang_string object is designed to be used in any situation where a
10979 * string may not be needed, but needs to be generated.
10980 * The admin tree is a good example of where lang_string objects should be
10981 * used.
10982 * A more practical example would be any class that requries strings that may
10983 * not be printed (after all classes get renderer by renderers and who knows
10984 * what they will do ;))
10986 * When should I not use a lang_string object?
10987 * Don't use lang_strings when you are going to use a string immediately.
10988 * There is no need as it will be processed immediately and there will be no
10989 * advantage, and in fact perhaps a negative hit as a class has to be
10990 * instantiated for a lang_string object, however get_string won't require
10991 * that.
10993 * Limitations:
10994 * 1. You cannot use a lang_string object as an array offset. Doing so will
10995 * result in PHP throwing an error. (You can use it as an object property!)
10997 * @package core
10998 * @category string
10999 * @copyright 2011 Sam Hemelryk
11000 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11002 class lang_string {
11004 /** @var string The strings identifier */
11005 protected $identifier;
11006 /** @var string The strings component. Default '' */
11007 protected $component = '';
11008 /** @var array|stdClass Any arguments required for the string. Default null */
11009 protected $a = null;
11010 /** @var string The language to use when processing the string. Default null */
11011 protected $lang = null;
11013 /** @var string The processed string (once processed) */
11014 protected $string = null;
11017 * A special boolean. If set to true then the object has been woken up and
11018 * cannot be regenerated. If this is set then $this->string MUST be used.
11019 * @var bool
11021 protected $forcedstring = false;
11024 * Constructs a lang_string object
11026 * This function should do as little processing as possible to ensure the best
11027 * performance for strings that won't be used.
11029 * @param string $identifier The strings identifier
11030 * @param string $component The strings component
11031 * @param stdClass|array $a Any arguments the string requires
11032 * @param string $lang The language to use when processing the string.
11034 public function __construct($identifier, $component = '', $a = null, $lang = null) {
11035 if (empty($component)) {
11036 $component = 'moodle';
11039 $this->identifier = $identifier;
11040 $this->component = $component;
11041 $this->lang = $lang;
11043 // We MUST duplicate $a to ensure that it if it changes by reference those
11044 // changes are not carried across.
11045 // To do this we always ensure $a or its properties/values are strings
11046 // and that any properties/values that arn't convertable are forgotten.
11047 if (!empty($a)) {
11048 if (is_scalar($a)) {
11049 $this->a = $a;
11050 } else if ($a instanceof lang_string) {
11051 $this->a = $a->out();
11052 } else if (is_object($a) or is_array($a)) {
11053 $a = (array)$a;
11054 $this->a = array();
11055 foreach ($a as $key => $value) {
11056 // Make sure conversion errors don't get displayed (results in '')
11057 if (is_array($value)) {
11058 $this->a[$key] = '';
11059 } else if (is_object($value)) {
11060 if (method_exists($value, '__toString')) {
11061 $this->a[$key] = $value->__toString();
11062 } else {
11063 $this->a[$key] = '';
11065 } else {
11066 $this->a[$key] = (string)$value;
11072 if (debugging(false, DEBUG_DEVELOPER)) {
11073 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
11074 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11076 if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') {
11077 throw new coding_exception('Invalid string compontent. Please check your string definition');
11079 if (!get_string_manager()->string_exists($this->identifier, $this->component)) {
11080 debugging('String does not exist. Please check your string definition for '.$this->identifier.'/'.$this->component, DEBUG_DEVELOPER);
11086 * Processes the string.
11088 * This function actually processes the string, stores it in the string property
11089 * and then returns it.
11090 * You will notice that this function is VERY similar to the get_string method.
11091 * That is because it is pretty much doing the same thing.
11092 * However as this function is an upgrade it isn't as tolerant to backwards
11093 * compatability.
11095 * @return string
11097 protected function get_string() {
11098 global $CFG;
11100 // Check if we need to process the string
11101 if ($this->string === null) {
11102 // Check the quality of the identifier.
11103 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
11104 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11107 // Process the string
11108 $this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
11109 // Debugging feature lets you display string identifier and component
11110 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
11111 $this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
11114 // Return the string
11115 return $this->string;
11119 * Returns the string
11121 * @param string $lang The langauge to use when processing the string
11122 * @return string
11124 public function out($lang = null) {
11125 if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
11126 if ($this->forcedstring) {
11127 debugging('lang_string objects that have been serialised and unserialised cannot be printed in another language. ('.$this->lang.' used)', DEBUG_DEVELOPER);
11128 return $this->get_string();
11130 $translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
11131 return $translatedstring->out();
11133 return $this->get_string();
11137 * Magic __toString method for printing a string
11139 * @return string
11141 public function __toString() {
11142 return $this->get_string();
11146 * Magic __set_state method used for var_export
11148 * @return string
11150 public function __set_state() {
11151 return $this->get_string();
11155 * Prepares the lang_string for sleep and stores only the forcedstring and
11156 * string properties... the string cannot be regenerated so we need to ensure
11157 * it is generated for this.
11159 * @return string
11161 public function __sleep() {
11162 $this->get_string();
11163 $this->forcedstring = true;
11164 return array('forcedstring', 'string', 'lang');