Merge branch 'MDL-39444_23' of git://github.com/timhunt/moodle into MOODLE_23_STABLE
[moodle.git] / lib / moodlelib.php
blob1ddfafb2905a82f2ca8981676bf7e5f5c779d817
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. Note that you almost never want
120 * to use this. The normal mode of operation is to use PARAM_RAW when recieving
121 * the input (required/optional_param or formslib) and then sanitse the HTML
122 * using format_text on output. This is for the rare cases when you want to
123 * sanitise the HTML on input. This cleaning may also fix xhtml strictness.
125 define('PARAM_CLEANHTML', 'cleanhtml');
128 * PARAM_EMAIL - an email address following the RFC
130 define('PARAM_EMAIL', 'email');
133 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
135 define('PARAM_FILE', 'file');
138 * PARAM_FLOAT - a real/floating point number.
140 * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
141 * It does not work for languages that use , as a decimal separator.
142 * Instead, do something like
143 * $rawvalue = required_param('name', PARAM_RAW);
144 * // ... other code including require_login, which sets current lang ...
145 * $realvalue = unformat_float($rawvalue);
146 * // ... then use $realvalue
148 define('PARAM_FLOAT', 'float');
151 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
153 define('PARAM_HOST', 'host');
156 * PARAM_INT - integers only, use when expecting only numbers.
158 define('PARAM_INT', 'int');
161 * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
163 define('PARAM_LANG', 'lang');
166 * 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!)
168 define('PARAM_LOCALURL', 'localurl');
171 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
173 define('PARAM_NOTAGS', 'notags');
176 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
177 * note: the leading slash is not removed, window drive letter is not allowed
179 define('PARAM_PATH', 'path');
182 * PARAM_PEM - Privacy Enhanced Mail format
184 define('PARAM_PEM', 'pem');
187 * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
189 define('PARAM_PERMISSION', 'permission');
192 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
194 define('PARAM_RAW', 'raw');
197 * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
199 define('PARAM_RAW_TRIMMED', 'raw_trimmed');
202 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
204 define('PARAM_SAFEDIR', 'safedir');
207 * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
209 define('PARAM_SAFEPATH', 'safepath');
212 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
214 define('PARAM_SEQUENCE', 'sequence');
217 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
219 define('PARAM_TAG', 'tag');
222 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
224 define('PARAM_TAGLIST', 'taglist');
227 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
229 define('PARAM_TEXT', 'text');
232 * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
234 define('PARAM_THEME', 'theme');
237 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
239 define('PARAM_URL', 'url');
242 * 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!!
244 define('PARAM_USERNAME', 'username');
247 * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
249 define('PARAM_STRINGID', 'stringid');
251 ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
253 * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
254 * It was one of the first types, that is why it is abused so much ;-)
255 * @deprecated since 2.0
257 define('PARAM_CLEAN', 'clean');
260 * PARAM_INTEGER - deprecated alias for PARAM_INT
262 define('PARAM_INTEGER', 'int');
265 * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
267 define('PARAM_NUMBER', 'float');
270 * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
271 * NOTE: originally alias for PARAM_APLHA
273 define('PARAM_ACTION', 'alphanumext');
276 * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
277 * NOTE: originally alias for PARAM_APLHA
279 define('PARAM_FORMAT', 'alphanumext');
282 * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
284 define('PARAM_MULTILANG', 'text');
287 * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
288 * string seperated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
289 * America/Port-au-Prince)
291 define('PARAM_TIMEZONE', 'timezone');
294 * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
296 define('PARAM_CLEANFILE', 'file');
299 * PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
300 * Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
301 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
302 * NOTE: numbers and underscores are strongly discouraged in plugin names!
304 define('PARAM_COMPONENT', 'component');
307 * PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
308 * It is usually used together with context id and component.
309 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
311 define('PARAM_AREA', 'area');
314 * PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'radius', 'paypal', 'completionstatus'.
315 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
316 * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
318 define('PARAM_PLUGIN', 'plugin');
321 /// Web Services ///
324 * VALUE_REQUIRED - if the parameter is not supplied, there is an error
326 define('VALUE_REQUIRED', 1);
329 * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
331 define('VALUE_OPTIONAL', 2);
334 * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
336 define('VALUE_DEFAULT', 0);
339 * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
341 define('NULL_NOT_ALLOWED', false);
344 * NULL_ALLOWED - the parameter can be set to null in the database
346 define('NULL_ALLOWED', true);
348 /// Page types ///
350 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
352 define('PAGE_COURSE_VIEW', 'course-view');
354 /** Get remote addr constant */
355 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
356 /** Get remote addr constant */
357 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
359 /// Blog access level constant declaration ///
360 define ('BLOG_USER_LEVEL', 1);
361 define ('BLOG_GROUP_LEVEL', 2);
362 define ('BLOG_COURSE_LEVEL', 3);
363 define ('BLOG_SITE_LEVEL', 4);
364 define ('BLOG_GLOBAL_LEVEL', 5);
367 ///Tag constants///
369 * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
370 * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
371 * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
373 * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
375 define('TAG_MAX_LENGTH', 50);
377 /// Password policy constants ///
378 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
379 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
380 define ('PASSWORD_DIGITS', '0123456789');
381 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
383 /// Feature constants ///
384 // Used for plugin_supports() to report features that are, or are not, supported by a module.
386 /** True if module can provide a grade */
387 define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
388 /** True if module supports outcomes */
389 define('FEATURE_GRADE_OUTCOMES', 'outcomes');
390 /** True if module supports advanced grading methods */
391 define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
392 /** True if module controls the grade visibility over the gradebook */
393 define('FEATURE_CONTROLS_GRADE_VISIBILITY', 'controlsgradevisbility');
395 /** True if module has code to track whether somebody viewed it */
396 define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
397 /** True if module has custom completion rules */
398 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
400 /** True if module has no 'view' page (like label) */
401 define('FEATURE_NO_VIEW_LINK', 'viewlink');
402 /** True if module supports outcomes */
403 define('FEATURE_IDNUMBER', 'idnumber');
404 /** True if module supports groups */
405 define('FEATURE_GROUPS', 'groups');
406 /** True if module supports groupings */
407 define('FEATURE_GROUPINGS', 'groupings');
408 /** True if module supports groupmembersonly */
409 define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
411 /** Type of module */
412 define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
413 /** True if module supports intro editor */
414 define('FEATURE_MOD_INTRO', 'mod_intro');
415 /** True if module has default completion */
416 define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
418 define('FEATURE_COMMENT', 'comment');
420 define('FEATURE_RATE', 'rate');
421 /** True if module supports backup/restore of moodle2 format */
422 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
424 /** True if module can show description on course main page */
425 define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
427 /** Unspecified module archetype */
428 define('MOD_ARCHETYPE_OTHER', 0);
429 /** Resource-like type module */
430 define('MOD_ARCHETYPE_RESOURCE', 1);
431 /** Assignment module archetype */
432 define('MOD_ARCHETYPE_ASSIGNMENT', 2);
433 /** System (not user-addable) module archetype */
434 define('MOD_ARCHETYPE_SYSTEM', 3);
437 * Security token used for allowing access
438 * from external application such as web services.
439 * Scripts do not use any session, performance is relatively
440 * low because we need to load access info in each request.
441 * Scripts are executed in parallel.
443 define('EXTERNAL_TOKEN_PERMANENT', 0);
446 * Security token used for allowing access
447 * of embedded applications, the code is executed in the
448 * active user session. Token is invalidated after user logs out.
449 * Scripts are executed serially - normal session locking is used.
451 define('EXTERNAL_TOKEN_EMBEDDED', 1);
454 * The home page should be the site home
456 define('HOMEPAGE_SITE', 0);
458 * The home page should be the users my page
460 define('HOMEPAGE_MY', 1);
462 * The home page can be chosen by the user
464 define('HOMEPAGE_USER', 2);
467 * Hub directory url (should be moodle.org)
469 define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
473 * Moodle.org url (should be moodle.org)
475 define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
478 * Moodle mobile app service name
480 define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
483 * Indicates the user has the capabilities required to ignore activity and course file size restrictions
485 define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1);
488 * Course display settings
490 define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page
491 define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section
493 /// PARAMETER HANDLING ////////////////////////////////////////////////////
496 * Returns a particular value for the named variable, taken from
497 * POST or GET. If the parameter doesn't exist then an error is
498 * thrown because we require this variable.
500 * This function should be used to initialise all required values
501 * in a script that are based on parameters. Usually it will be
502 * used like this:
503 * $id = required_param('id', PARAM_INT);
505 * Please note the $type parameter is now required and the value can not be array.
507 * @param string $parname the name of the page parameter we want
508 * @param string $type expected type of parameter
509 * @return mixed
511 function required_param($parname, $type) {
512 if (func_num_args() != 2 or empty($parname) or empty($type)) {
513 throw new coding_exception('required_param() requires $parname and $type to be specified (parameter: '.$parname.')');
515 if (isset($_POST[$parname])) { // POST has precedence
516 $param = $_POST[$parname];
517 } else if (isset($_GET[$parname])) {
518 $param = $_GET[$parname];
519 } else {
520 print_error('missingparam', '', '', $parname);
523 if (is_array($param)) {
524 debugging('Invalid array parameter detected in required_param(): '.$parname);
525 // TODO: switch to fatal error in Moodle 2.3
526 //print_error('missingparam', '', '', $parname);
527 return required_param_array($parname, $type);
530 return clean_param($param, $type);
534 * Returns a particular array value for the named variable, taken from
535 * POST or GET. If the parameter doesn't exist then an error is
536 * thrown because we require this variable.
538 * This function should be used to initialise all required values
539 * in a script that are based on parameters. Usually it will be
540 * used like this:
541 * $ids = required_param_array('ids', PARAM_INT);
543 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
545 * @param string $parname the name of the page parameter we want
546 * @param string $type expected type of parameter
547 * @return array
549 function required_param_array($parname, $type) {
550 if (func_num_args() != 2 or empty($parname) or empty($type)) {
551 throw new coding_exception('required_param_array() requires $parname and $type to be specified (parameter: '.$parname.')');
553 if (isset($_POST[$parname])) { // POST has precedence
554 $param = $_POST[$parname];
555 } else if (isset($_GET[$parname])) {
556 $param = $_GET[$parname];
557 } else {
558 print_error('missingparam', '', '', $parname);
560 if (!is_array($param)) {
561 print_error('missingparam', '', '', $parname);
564 $result = array();
565 foreach($param as $key=>$value) {
566 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
567 debugging('Invalid key name in required_param_array() detected: '.$key.', parameter: '.$parname);
568 continue;
570 $result[$key] = clean_param($value, $type);
573 return $result;
577 * Returns a particular value for the named variable, taken from
578 * POST or GET, otherwise returning a given default.
580 * This function should be used to initialise all optional values
581 * in a script that are based on parameters. Usually it will be
582 * used like this:
583 * $name = optional_param('name', 'Fred', PARAM_TEXT);
585 * Please note the $type parameter is now required and the value can not be array.
587 * @param string $parname the name of the page parameter we want
588 * @param mixed $default the default value to return if nothing is found
589 * @param string $type expected type of parameter
590 * @return mixed
592 function optional_param($parname, $default, $type) {
593 if (func_num_args() != 3 or empty($parname) or empty($type)) {
594 throw new coding_exception('optional_param() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
596 if (!isset($default)) {
597 $default = null;
600 if (isset($_POST[$parname])) { // POST has precedence
601 $param = $_POST[$parname];
602 } else if (isset($_GET[$parname])) {
603 $param = $_GET[$parname];
604 } else {
605 return $default;
608 if (is_array($param)) {
609 debugging('Invalid array parameter detected in required_param(): '.$parname);
610 // TODO: switch to $default in Moodle 2.3
611 //return $default;
612 return optional_param_array($parname, $default, $type);
615 return clean_param($param, $type);
619 * Returns a particular array value for the named variable, taken from
620 * POST or GET, otherwise returning a given default.
622 * This function should be used to initialise all optional values
623 * in a script that are based on parameters. Usually it will be
624 * used like this:
625 * $ids = optional_param('id', array(), PARAM_INT);
627 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
629 * @param string $parname the name of the page parameter we want
630 * @param mixed $default the default value to return if nothing is found
631 * @param string $type expected type of parameter
632 * @return array
634 function optional_param_array($parname, $default, $type) {
635 if (func_num_args() != 3 or empty($parname) or empty($type)) {
636 throw new coding_exception('optional_param_array() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
639 if (isset($_POST[$parname])) { // POST has precedence
640 $param = $_POST[$parname];
641 } else if (isset($_GET[$parname])) {
642 $param = $_GET[$parname];
643 } else {
644 return $default;
646 if (!is_array($param)) {
647 debugging('optional_param_array() expects array parameters only: '.$parname);
648 return $default;
651 $result = array();
652 foreach($param as $key=>$value) {
653 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
654 debugging('Invalid key name in optional_param_array() detected: '.$key.', parameter: '.$parname);
655 continue;
657 $result[$key] = clean_param($value, $type);
660 return $result;
664 * Strict validation of parameter values, the values are only converted
665 * to requested PHP type. Internally it is using clean_param, the values
666 * before and after cleaning must be equal - otherwise
667 * an invalid_parameter_exception is thrown.
668 * Objects and classes are not accepted.
670 * @param mixed $param
671 * @param string $type PARAM_ constant
672 * @param bool $allownull are nulls valid value?
673 * @param string $debuginfo optional debug information
674 * @return mixed the $param value converted to PHP type
675 * @throws invalid_parameter_exception if $param is not of given type
677 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
678 if (is_null($param)) {
679 if ($allownull == NULL_ALLOWED) {
680 return null;
681 } else {
682 throw new invalid_parameter_exception($debuginfo);
685 if (is_array($param) or is_object($param)) {
686 throw new invalid_parameter_exception($debuginfo);
689 $cleaned = clean_param($param, $type);
691 if ($type == PARAM_FLOAT) {
692 // Do not detect precision loss here.
693 if (is_float($param) or is_int($param)) {
694 // These always fit.
695 } else if (!is_numeric($param) or !preg_match('/^[\+-]?[0-9]*\.?[0-9]*(e[-+]?[0-9]+)?$/i', (string)$param)) {
696 throw new invalid_parameter_exception($debuginfo);
698 } else if ((string)$param !== (string)$cleaned) {
699 // conversion to string is usually lossless
700 throw new invalid_parameter_exception($debuginfo);
703 return $cleaned;
707 * Makes sure array contains only the allowed types,
708 * this function does not validate array key names!
709 * <code>
710 * $options = clean_param($options, PARAM_INT);
711 * </code>
713 * @param array $param the variable array we are cleaning
714 * @param string $type expected format of param after cleaning.
715 * @param bool $recursive clean recursive arrays
716 * @return array
718 function clean_param_array(array $param = null, $type, $recursive = false) {
719 $param = (array)$param; // convert null to empty array
720 foreach ($param as $key => $value) {
721 if (is_array($value)) {
722 if ($recursive) {
723 $param[$key] = clean_param_array($value, $type, true);
724 } else {
725 throw new coding_exception('clean_param_array() can not process multidimensional arrays when $recursive is false.');
727 } else {
728 $param[$key] = clean_param($value, $type);
731 return $param;
735 * Used by {@link optional_param()} and {@link required_param()} to
736 * clean the variables and/or cast to specific types, based on
737 * an options field.
738 * <code>
739 * $course->format = clean_param($course->format, PARAM_ALPHA);
740 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
741 * </code>
743 * @param mixed $param the variable we are cleaning
744 * @param string $type expected format of param after cleaning.
745 * @return mixed
747 function clean_param($param, $type) {
749 global $CFG;
751 if (is_array($param)) {
752 throw new coding_exception('clean_param() can not process arrays, please use clean_param_array() instead.');
753 } else if (is_object($param)) {
754 if (method_exists($param, '__toString')) {
755 $param = $param->__toString();
756 } else {
757 throw new coding_exception('clean_param() can not process objects, please use clean_param_array() instead.');
761 switch ($type) {
762 case PARAM_RAW: // no cleaning at all
763 $param = fix_utf8($param);
764 return $param;
766 case PARAM_RAW_TRIMMED: // no cleaning, but strip leading and trailing whitespace.
767 $param = fix_utf8($param);
768 return trim($param);
770 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
771 // this is deprecated!, please use more specific type instead
772 if (is_numeric($param)) {
773 return $param;
775 $param = fix_utf8($param);
776 return clean_text($param); // Sweep for scripts, etc
778 case PARAM_CLEANHTML: // clean html fragment
779 $param = fix_utf8($param);
780 $param = clean_text($param, FORMAT_HTML); // Sweep for scripts, etc
781 return trim($param);
783 case PARAM_INT:
784 return (int)$param; // Convert to integer
786 case PARAM_FLOAT:
787 case PARAM_NUMBER:
788 return (float)$param; // Convert to float
790 case PARAM_ALPHA: // Remove everything not a-z
791 return preg_replace('/[^a-zA-Z]/i', '', $param);
793 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z_- (originally allowed "/" too)
794 return preg_replace('/[^a-zA-Z_-]/i', '', $param);
796 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
797 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
799 case PARAM_ALPHANUMEXT: // Remove everything not a-zA-Z0-9_-
800 return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
802 case PARAM_SEQUENCE: // Remove everything not 0-9,
803 return preg_replace('/[^0-9,]/i', '', $param);
805 case PARAM_BOOL: // Convert to 1 or 0
806 $tempstr = strtolower($param);
807 if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
808 $param = 1;
809 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
810 $param = 0;
811 } else {
812 $param = empty($param) ? 0 : 1;
814 return $param;
816 case PARAM_NOTAGS: // Strip all tags
817 $param = fix_utf8($param);
818 return strip_tags($param);
820 case PARAM_TEXT: // leave only tags needed for multilang
821 $param = fix_utf8($param);
822 // if the multilang syntax is not correct we strip all tags
823 // because it would break xhtml strict which is required for accessibility standards
824 // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
825 do {
826 if (strpos($param, '</lang>') !== false) {
827 // old and future mutilang syntax
828 $param = strip_tags($param, '<lang>');
829 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
830 break;
832 $open = false;
833 foreach ($matches[0] as $match) {
834 if ($match === '</lang>') {
835 if ($open) {
836 $open = false;
837 continue;
838 } else {
839 break 2;
842 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
843 break 2;
844 } else {
845 $open = true;
848 if ($open) {
849 break;
851 return $param;
853 } else if (strpos($param, '</span>') !== false) {
854 // current problematic multilang syntax
855 $param = strip_tags($param, '<span>');
856 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
857 break;
859 $open = false;
860 foreach ($matches[0] as $match) {
861 if ($match === '</span>') {
862 if ($open) {
863 $open = false;
864 continue;
865 } else {
866 break 2;
869 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
870 break 2;
871 } else {
872 $open = true;
875 if ($open) {
876 break;
878 return $param;
880 } while (false);
881 // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
882 return strip_tags($param);
884 case PARAM_COMPONENT:
885 // we do not want any guessing here, either the name is correct or not
886 // please note only normalised component names are accepted
887 if (!preg_match('/^[a-z]+(_[a-z][a-z0-9_]*)?[a-z0-9]$/', $param)) {
888 return '';
890 if (strpos($param, '__') !== false) {
891 return '';
893 if (strpos($param, 'mod_') === 0) {
894 // module names must not contain underscores because we need to differentiate them from invalid plugin types
895 if (substr_count($param, '_') != 1) {
896 return '';
899 return $param;
901 case PARAM_PLUGIN:
902 case PARAM_AREA:
903 // we do not want any guessing here, either the name is correct or not
904 if (!preg_match('/^[a-z][a-z0-9_]*[a-z0-9]$/', $param)) {
905 return '';
907 if (strpos($param, '__') !== false) {
908 return '';
910 return $param;
912 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
913 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
915 case PARAM_SAFEPATH: // Remove everything not a-zA-Z0-9/_-
916 return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
918 case PARAM_FILE: // Strip all suspicious characters from filename
919 $param = fix_utf8($param);
920 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
921 $param = preg_replace('~\.\.+~', '', $param);
922 if ($param === '.') {
923 $param = '';
925 return $param;
927 case PARAM_PATH: // Strip all suspicious characters from file path
928 $param = fix_utf8($param);
929 $param = str_replace('\\', '/', $param);
930 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
931 $param = preg_replace('~\.\.+~', '', $param);
932 $param = preg_replace('~//+~', '/', $param);
933 return preg_replace('~/(\./)+~', '/', $param);
935 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
936 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
937 // match ipv4 dotted quad
938 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
939 // confirm values are ok
940 if ( $match[0] > 255
941 || $match[1] > 255
942 || $match[3] > 255
943 || $match[4] > 255 ) {
944 // hmmm, what kind of dotted quad is this?
945 $param = '';
947 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
948 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
949 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
951 // all is ok - $param is respected
952 } else {
953 // all is not ok...
954 $param='';
956 return $param;
958 case PARAM_URL: // allow safe ftp, http, mailto urls
959 $param = fix_utf8($param);
960 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
961 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
962 // all is ok, param is respected
963 } else {
964 $param =''; // not really ok
966 return $param;
968 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
969 $param = clean_param($param, PARAM_URL);
970 if (!empty($param)) {
971 if (preg_match(':^/:', $param)) {
972 // root-relative, ok!
973 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
974 // absolute, and matches our wwwroot
975 } else {
976 // relative - let's make sure there are no tricks
977 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
978 // looks ok.
979 } else {
980 $param = '';
984 return $param;
986 case PARAM_PEM:
987 $param = trim($param);
988 // PEM formatted strings may contain letters/numbers and the symbols
989 // forward slash: /
990 // plus sign: +
991 // equal sign: =
992 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
993 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
994 list($wholething, $body) = $matches;
995 unset($wholething, $matches);
996 $b64 = clean_param($body, PARAM_BASE64);
997 if (!empty($b64)) {
998 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
999 } else {
1000 return '';
1003 return '';
1005 case PARAM_BASE64:
1006 if (!empty($param)) {
1007 // PEM formatted strings may contain letters/numbers and the symbols
1008 // forward slash: /
1009 // plus sign: +
1010 // equal sign: =
1011 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
1012 return '';
1014 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
1015 // Each line of base64 encoded data must be 64 characters in
1016 // length, except for the last line which may be less than (or
1017 // equal to) 64 characters long.
1018 for ($i=0, $j=count($lines); $i < $j; $i++) {
1019 if ($i + 1 == $j) {
1020 if (64 < strlen($lines[$i])) {
1021 return '';
1023 continue;
1026 if (64 != strlen($lines[$i])) {
1027 return '';
1030 return implode("\n",$lines);
1031 } else {
1032 return '';
1035 case PARAM_TAG:
1036 $param = fix_utf8($param);
1037 // Please note it is not safe to use the tag name directly anywhere,
1038 // it must be processed with s(), urlencode() before embedding anywhere.
1039 // remove some nasties
1040 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
1041 //convert many whitespace chars into one
1042 $param = preg_replace('/\s+/', ' ', $param);
1043 $param = textlib::substr(trim($param), 0, TAG_MAX_LENGTH);
1044 return $param;
1046 case PARAM_TAGLIST:
1047 $param = fix_utf8($param);
1048 $tags = explode(',', $param);
1049 $result = array();
1050 foreach ($tags as $tag) {
1051 $res = clean_param($tag, PARAM_TAG);
1052 if ($res !== '') {
1053 $result[] = $res;
1056 if ($result) {
1057 return implode(',', $result);
1058 } else {
1059 return '';
1062 case PARAM_CAPABILITY:
1063 if (get_capability_info($param)) {
1064 return $param;
1065 } else {
1066 return '';
1069 case PARAM_PERMISSION:
1070 $param = (int)$param;
1071 if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
1072 return $param;
1073 } else {
1074 return CAP_INHERIT;
1077 case PARAM_AUTH:
1078 $param = clean_param($param, PARAM_PLUGIN);
1079 if (empty($param)) {
1080 return '';
1081 } else if (exists_auth_plugin($param)) {
1082 return $param;
1083 } else {
1084 return '';
1087 case PARAM_LANG:
1088 $param = clean_param($param, PARAM_SAFEDIR);
1089 if (get_string_manager()->translation_exists($param)) {
1090 return $param;
1091 } else {
1092 return ''; // Specified language is not installed or param malformed
1095 case PARAM_THEME:
1096 $param = clean_param($param, PARAM_PLUGIN);
1097 if (empty($param)) {
1098 return '';
1099 } else if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
1100 return $param;
1101 } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
1102 return $param;
1103 } else {
1104 return ''; // Specified theme is not installed
1107 case PARAM_USERNAME:
1108 $param = fix_utf8($param);
1109 $param = str_replace(" " , "", $param);
1110 $param = textlib::strtolower($param); // Convert uppercase to lowercase MDL-16919
1111 if (empty($CFG->extendedusernamechars)) {
1112 // regular expression, eliminate all chars EXCEPT:
1113 // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
1114 $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
1116 return $param;
1118 case PARAM_EMAIL:
1119 $param = fix_utf8($param);
1120 if (validate_email($param)) {
1121 return $param;
1122 } else {
1123 return '';
1126 case PARAM_STRINGID:
1127 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
1128 return $param;
1129 } else {
1130 return '';
1133 case PARAM_TIMEZONE: //can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'
1134 $param = fix_utf8($param);
1135 $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3](\.0)?|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
1136 if (preg_match($timezonepattern, $param)) {
1137 return $param;
1138 } else {
1139 return '';
1142 default: // throw error, switched parameters in optional_param or another serious problem
1143 print_error("unknownparamtype", '', '', $type);
1148 * Makes sure the data is using valid utf8, invalid characters are discarded.
1150 * Note: this function is not intended for full objects with methods and private properties.
1152 * @param mixed $value
1153 * @return mixed with proper utf-8 encoding
1155 function fix_utf8($value) {
1156 if (is_null($value) or $value === '') {
1157 return $value;
1159 } else if (is_string($value)) {
1160 if ((string)(int)$value === $value) {
1161 // shortcut
1162 return $value;
1165 // Lower error reporting because glibc throws bogus notices.
1166 $olderror = error_reporting();
1167 if ($olderror & E_NOTICE) {
1168 error_reporting($olderror ^ E_NOTICE);
1171 // Note: this duplicates min_fix_utf8() intentionally.
1172 static $buggyiconv = null;
1173 if ($buggyiconv === null) {
1174 $buggyiconv = (!function_exists('iconv') or iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
1177 if ($buggyiconv) {
1178 if (function_exists('mb_convert_encoding')) {
1179 $subst = mb_substitute_character();
1180 mb_substitute_character('');
1181 $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
1182 mb_substitute_character($subst);
1184 } else {
1185 // Warn admins on admin/index.php page.
1186 $result = $value;
1189 } else {
1190 $result = iconv('UTF-8', 'UTF-8//IGNORE', $value);
1193 if ($olderror & E_NOTICE) {
1194 error_reporting($olderror);
1197 return $result;
1199 } else if (is_array($value)) {
1200 foreach ($value as $k=>$v) {
1201 $value[$k] = fix_utf8($v);
1203 return $value;
1205 } else if (is_object($value)) {
1206 $value = clone($value); // do not modify original
1207 foreach ($value as $k=>$v) {
1208 $value->$k = fix_utf8($v);
1210 return $value;
1212 } else {
1213 // this is some other type, no utf-8 here
1214 return $value;
1219 * Return true if given value is integer or string with integer value
1221 * @param mixed $value String or Int
1222 * @return bool true if number, false if not
1224 function is_number($value) {
1225 if (is_int($value)) {
1226 return true;
1227 } else if (is_string($value)) {
1228 return ((string)(int)$value) === $value;
1229 } else {
1230 return false;
1235 * Returns host part from url
1236 * @param string $url full url
1237 * @return string host, null if not found
1239 function get_host_from_url($url) {
1240 preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
1241 if ($matches) {
1242 return $matches[1];
1244 return null;
1248 * Tests whether anything was returned by text editor
1250 * This function is useful for testing whether something you got back from
1251 * the HTML editor actually contains anything. Sometimes the HTML editor
1252 * appear to be empty, but actually you get back a <br> tag or something.
1254 * @param string $string a string containing HTML.
1255 * @return boolean does the string contain any actual content - that is text,
1256 * images, objects, etc.
1258 function html_is_blank($string) {
1259 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
1263 * Set a key in global configuration
1265 * Set a key/value pair in both this session's {@link $CFG} global variable
1266 * and in the 'config' database table for future sessions.
1268 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
1269 * In that case it doesn't affect $CFG.
1271 * A NULL value will delete the entry.
1273 * @global object
1274 * @global object
1275 * @param string $name the key to set
1276 * @param string $value the value to set (without magic quotes)
1277 * @param string $plugin (optional) the plugin scope, default NULL
1278 * @return bool true or exception
1280 function set_config($name, $value, $plugin=NULL) {
1281 global $CFG, $DB;
1283 if (empty($plugin)) {
1284 if (!array_key_exists($name, $CFG->config_php_settings)) {
1285 // So it's defined for this invocation at least
1286 if (is_null($value)) {
1287 unset($CFG->$name);
1288 } else {
1289 $CFG->$name = (string)$value; // settings from db are always strings
1293 if ($DB->get_field('config', 'name', array('name'=>$name))) {
1294 if ($value === null) {
1295 $DB->delete_records('config', array('name'=>$name));
1296 } else {
1297 $DB->set_field('config', 'value', $value, array('name'=>$name));
1299 } else {
1300 if ($value !== null) {
1301 $config = new stdClass();
1302 $config->name = $name;
1303 $config->value = $value;
1304 $DB->insert_record('config', $config, false);
1308 } else { // plugin scope
1309 if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
1310 if ($value===null) {
1311 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1312 } else {
1313 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
1315 } else {
1316 if ($value !== null) {
1317 $config = new stdClass();
1318 $config->plugin = $plugin;
1319 $config->name = $name;
1320 $config->value = $value;
1321 $DB->insert_record('config_plugins', $config, false);
1326 return true;
1330 * Get configuration values from the global config table
1331 * or the config_plugins table.
1333 * If called with one parameter, it will load all the config
1334 * variables for one plugin, and return them as an object.
1336 * If called with 2 parameters it will return a string single
1337 * value or false if the value is not found.
1339 * @param string $plugin full component name
1340 * @param string $name default NULL
1341 * @return mixed hash-like object or single value, return false no config found
1343 function get_config($plugin, $name = NULL) {
1344 global $CFG, $DB;
1346 // normalise component name
1347 if ($plugin === 'moodle' or $plugin === 'core') {
1348 $plugin = NULL;
1351 if (!empty($name)) { // the user is asking for a specific value
1352 if (!empty($plugin)) {
1353 if (isset($CFG->forced_plugin_settings[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings[$plugin])) {
1354 // setting forced in config file
1355 return $CFG->forced_plugin_settings[$plugin][$name];
1356 } else {
1357 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1359 } else {
1360 if (array_key_exists($name, $CFG->config_php_settings)) {
1361 // setting force in config file
1362 return $CFG->config_php_settings[$name];
1363 } else {
1364 return $DB->get_field('config', 'value', array('name'=>$name));
1369 // the user is after a recordset
1370 if ($plugin) {
1371 $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
1372 if (isset($CFG->forced_plugin_settings[$plugin])) {
1373 foreach($CFG->forced_plugin_settings[$plugin] as $n=>$v) {
1374 if (is_null($v) or is_array($v) or is_object($v)) {
1375 // we do not want any extra mess here, just real settings that could be saved in db
1376 unset($localcfg[$n]);
1377 } else {
1378 //convert to string as if it went through the DB
1379 $localcfg[$n] = (string)$v;
1383 if ($localcfg) {
1384 return (object)$localcfg;
1385 } else {
1386 return new stdClass();
1389 } else {
1390 // this part is not really used any more, but anyway...
1391 $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
1392 foreach($CFG->config_php_settings as $n=>$v) {
1393 if (is_null($v) or is_array($v) or is_object($v)) {
1394 // we do not want any extra mess here, just real settings that could be saved in db
1395 unset($localcfg[$n]);
1396 } else {
1397 //convert to string as if it went through the DB
1398 $localcfg[$n] = (string)$v;
1401 return (object)$localcfg;
1406 * Removes a key from global configuration
1408 * @param string $name the key to set
1409 * @param string $plugin (optional) the plugin scope
1410 * @global object
1411 * @return boolean whether the operation succeeded.
1413 function unset_config($name, $plugin=NULL) {
1414 global $CFG, $DB;
1416 if (empty($plugin)) {
1417 unset($CFG->$name);
1418 $DB->delete_records('config', array('name'=>$name));
1419 } else {
1420 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1423 return true;
1427 * Remove all the config variables for a given plugin.
1429 * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1430 * @return boolean whether the operation succeeded.
1432 function unset_all_config_for_plugin($plugin) {
1433 global $DB;
1434 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1435 $like = $DB->sql_like('name', '?', true, true, false, '|');
1436 $params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
1437 $DB->delete_records_select('config', $like, $params);
1438 return true;
1442 * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1444 * All users are verified if they still have the necessary capability.
1446 * @param string $value the value of the config setting.
1447 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1448 * @param bool $include admins, include administrators
1449 * @return array of user objects.
1451 function get_users_from_config($value, $capability, $includeadmins = true) {
1452 global $CFG, $DB;
1454 if (empty($value) or $value === '$@NONE@$') {
1455 return array();
1458 // we have to make sure that users still have the necessary capability,
1459 // it should be faster to fetch them all first and then test if they are present
1460 // instead of validating them one-by-one
1461 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
1462 if ($includeadmins) {
1463 $admins = get_admins();
1464 foreach ($admins as $admin) {
1465 $users[$admin->id] = $admin;
1469 if ($value === '$@ALL@$') {
1470 return $users;
1473 $result = array(); // result in correct order
1474 $allowed = explode(',', $value);
1475 foreach ($allowed as $uid) {
1476 if (isset($users[$uid])) {
1477 $user = $users[$uid];
1478 $result[$user->id] = $user;
1482 return $result;
1487 * Invalidates browser caches and cached data in temp
1488 * @return void
1490 function purge_all_caches() {
1491 global $CFG;
1493 reset_text_filters_cache();
1494 js_reset_all_caches();
1495 theme_reset_all_caches();
1496 get_string_manager()->reset_caches();
1497 textlib::reset_caches();
1499 // purge all other caches: rss, simplepie, etc.
1500 remove_dir($CFG->cachedir.'', true);
1502 // make sure cache dir is writable, throws exception if not
1503 make_cache_directory('');
1505 // hack: this script may get called after the purifier was initialised,
1506 // but we do not want to verify repeatedly this exists in each call
1507 make_cache_directory('htmlpurifier');
1511 * Get volatile flags
1513 * @param string $type
1514 * @param int $changedsince default null
1515 * @return records array
1517 function get_cache_flags($type, $changedsince=NULL) {
1518 global $DB;
1520 $params = array('type'=>$type, 'expiry'=>time());
1521 $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1522 if ($changedsince !== NULL) {
1523 $params['changedsince'] = $changedsince;
1524 $sqlwhere .= " AND timemodified > :changedsince";
1526 $cf = array();
1528 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1529 foreach ($flags as $flag) {
1530 $cf[$flag->name] = $flag->value;
1533 return $cf;
1537 * Get volatile flags
1539 * @param string $type
1540 * @param string $name
1541 * @param int $changedsince default null
1542 * @return records array
1544 function get_cache_flag($type, $name, $changedsince=NULL) {
1545 global $DB;
1547 $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
1549 $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1550 if ($changedsince !== NULL) {
1551 $params['changedsince'] = $changedsince;
1552 $sqlwhere .= " AND timemodified > :changedsince";
1555 return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1559 * Set a volatile flag
1561 * @param string $type the "type" namespace for the key
1562 * @param string $name the key to set
1563 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
1564 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1565 * @return bool Always returns true
1567 function set_cache_flag($type, $name, $value, $expiry=NULL) {
1568 global $DB;
1570 $timemodified = time();
1571 if ($expiry===NULL || $expiry < $timemodified) {
1572 $expiry = $timemodified + 24 * 60 * 60;
1573 } else {
1574 $expiry = (int)$expiry;
1577 if ($value === NULL) {
1578 unset_cache_flag($type,$name);
1579 return true;
1582 if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE)) { // this is a potential problem in DEBUG_DEVELOPER
1583 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
1584 return true; //no need to update; helps rcache too
1586 $f->value = $value;
1587 $f->expiry = $expiry;
1588 $f->timemodified = $timemodified;
1589 $DB->update_record('cache_flags', $f);
1590 } else {
1591 $f = new stdClass();
1592 $f->flagtype = $type;
1593 $f->name = $name;
1594 $f->value = $value;
1595 $f->expiry = $expiry;
1596 $f->timemodified = $timemodified;
1597 $DB->insert_record('cache_flags', $f);
1599 return true;
1603 * Removes a single volatile flag
1605 * @global object
1606 * @param string $type the "type" namespace for the key
1607 * @param string $name the key to set
1608 * @return bool
1610 function unset_cache_flag($type, $name) {
1611 global $DB;
1612 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1613 return true;
1617 * Garbage-collect volatile flags
1619 * @return bool Always returns true
1621 function gc_cache_flags() {
1622 global $DB;
1623 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1624 return true;
1627 // USER PREFERENCE API
1630 * Refresh user preference cache. This is used most often for $USER
1631 * object that is stored in session, but it also helps with performance in cron script.
1633 * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1635 * @package core
1636 * @category preference
1637 * @access public
1638 * @param stdClass $user User object. Preferences are preloaded into 'preference' property
1639 * @param int $cachelifetime Cache life time on the current page (in seconds)
1640 * @throws coding_exception
1641 * @return null
1643 function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
1644 global $DB;
1645 static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
1647 if (!isset($user->id)) {
1648 throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1651 if (empty($user->id) or isguestuser($user->id)) {
1652 // No permanent storage for not-logged-in users and guest
1653 if (!isset($user->preference)) {
1654 $user->preference = array();
1656 return;
1659 $timenow = time();
1661 if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
1662 // Already loaded at least once on this page. Are we up to date?
1663 if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
1664 // no need to reload - we are on the same page and we loaded prefs just a moment ago
1665 return;
1667 } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
1668 // no change since the lastcheck on this page
1669 $user->preference['_lastloaded'] = $timenow;
1670 return;
1674 // OK, so we have to reload all preferences
1675 $loadedusers[$user->id] = true;
1676 $user->preference = $DB->get_records_menu('user_preferences', array('userid'=>$user->id), '', 'name,value'); // All values
1677 $user->preference['_lastloaded'] = $timenow;
1681 * Called from set/unset_user_preferences, so that the prefs can
1682 * be correctly reloaded in different sessions.
1684 * NOTE: internal function, do not call from other code.
1686 * @package core
1687 * @access private
1688 * @param integer $userid the user whose prefs were changed.
1690 function mark_user_preferences_changed($userid) {
1691 global $CFG;
1693 if (empty($userid) or isguestuser($userid)) {
1694 // no cache flags for guest and not-logged-in users
1695 return;
1698 set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
1702 * Sets a preference for the specified user.
1704 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1706 * @package core
1707 * @category preference
1708 * @access public
1709 * @param string $name The key to set as preference for the specified user
1710 * @param string $value The value to set for the $name key in the specified user's
1711 * record, null means delete current value.
1712 * @param stdClass|int|null $user A moodle user object or id, null means current user
1713 * @throws coding_exception
1714 * @return bool Always true or exception
1716 function set_user_preference($name, $value, $user = null) {
1717 global $USER, $DB;
1719 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1720 throw new coding_exception('Invalid preference name in set_user_preference() call');
1723 if (is_null($value)) {
1724 // null means delete current
1725 return unset_user_preference($name, $user);
1726 } else if (is_object($value)) {
1727 throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1728 } else if (is_array($value)) {
1729 throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1731 $value = (string)$value;
1732 if (textlib::strlen($value) > 1333) { //value column maximum length is 1333 characters
1733 throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
1736 if (is_null($user)) {
1737 $user = $USER;
1738 } else if (isset($user->id)) {
1739 // $user is valid object
1740 } else if (is_numeric($user)) {
1741 $user = (object)array('id'=>(int)$user);
1742 } else {
1743 throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1746 check_user_preferences_loaded($user);
1748 if (empty($user->id) or isguestuser($user->id)) {
1749 // no permanent storage for not-logged-in users and guest
1750 $user->preference[$name] = $value;
1751 return true;
1754 if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>$name))) {
1755 if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
1756 // preference already set to this value
1757 return true;
1759 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id));
1761 } else {
1762 $preference = new stdClass();
1763 $preference->userid = $user->id;
1764 $preference->name = $name;
1765 $preference->value = $value;
1766 $DB->insert_record('user_preferences', $preference);
1769 // update value in cache
1770 $user->preference[$name] = $value;
1772 // set reload flag for other sessions
1773 mark_user_preferences_changed($user->id);
1775 return true;
1779 * Sets a whole array of preferences for the current user
1781 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1783 * @package core
1784 * @category preference
1785 * @access public
1786 * @param array $prefarray An array of key/value pairs to be set
1787 * @param stdClass|int|null $user A moodle user object or id, null means current user
1788 * @return bool Always true or exception
1790 function set_user_preferences(array $prefarray, $user = null) {
1791 foreach ($prefarray as $name => $value) {
1792 set_user_preference($name, $value, $user);
1794 return true;
1798 * Unsets a preference completely by deleting it from the database
1800 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1802 * @package core
1803 * @category preference
1804 * @access public
1805 * @param string $name The key to unset as preference for the specified user
1806 * @param stdClass|int|null $user A moodle user object or id, null means current user
1807 * @throws coding_exception
1808 * @return bool Always true or exception
1810 function unset_user_preference($name, $user = null) {
1811 global $USER, $DB;
1813 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1814 throw new coding_exception('Invalid preference name in unset_user_preference() call');
1817 if (is_null($user)) {
1818 $user = $USER;
1819 } else if (isset($user->id)) {
1820 // $user is valid object
1821 } else if (is_numeric($user)) {
1822 $user = (object)array('id'=>(int)$user);
1823 } else {
1824 throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1827 check_user_preferences_loaded($user);
1829 if (empty($user->id) or isguestuser($user->id)) {
1830 // no permanent storage for not-logged-in user and guest
1831 unset($user->preference[$name]);
1832 return true;
1835 // delete from DB
1836 $DB->delete_records('user_preferences', array('userid'=>$user->id, 'name'=>$name));
1838 // delete the preference from cache
1839 unset($user->preference[$name]);
1841 // set reload flag for other sessions
1842 mark_user_preferences_changed($user->id);
1844 return true;
1848 * Used to fetch user preference(s)
1850 * If no arguments are supplied this function will return
1851 * all of the current user preferences as an array.
1853 * If a name is specified then this function
1854 * attempts to return that particular preference value. If
1855 * none is found, then the optional value $default is returned,
1856 * otherwise NULL.
1858 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1860 * @package core
1861 * @category preference
1862 * @access public
1863 * @param string $name Name of the key to use in finding a preference value
1864 * @param mixed|null $default Value to be returned if the $name key is not set in the user preferences
1865 * @param stdClass|int|null $user A moodle user object or id, null means current user
1866 * @throws coding_exception
1867 * @return string|mixed|null A string containing the value of a single preference. An
1868 * array with all of the preferences or null
1870 function get_user_preferences($name = null, $default = null, $user = null) {
1871 global $USER;
1873 if (is_null($name)) {
1874 // all prefs
1875 } else if (is_numeric($name) or $name === '_lastloaded') {
1876 throw new coding_exception('Invalid preference name in get_user_preferences() call');
1879 if (is_null($user)) {
1880 $user = $USER;
1881 } else if (isset($user->id)) {
1882 // $user is valid object
1883 } else if (is_numeric($user)) {
1884 $user = (object)array('id'=>(int)$user);
1885 } else {
1886 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1889 check_user_preferences_loaded($user);
1891 if (empty($name)) {
1892 return $user->preference; // All values
1893 } else if (isset($user->preference[$name])) {
1894 return $user->preference[$name]; // The single string value
1895 } else {
1896 return $default; // Default value (null if not specified)
1900 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1903 * Given date parts in user time produce a GMT timestamp.
1905 * @package core
1906 * @category time
1907 * @param int $year The year part to create timestamp of
1908 * @param int $month The month part to create timestamp of
1909 * @param int $day The day part to create timestamp of
1910 * @param int $hour The hour part to create timestamp of
1911 * @param int $minute The minute part to create timestamp of
1912 * @param int $second The second part to create timestamp of
1913 * @param int|float|string $timezone Timezone modifier, used to calculate GMT time offset.
1914 * if 99 then default user's timezone is used {@link http://docs.moodle.org/dev/Time_API#Timezone}
1915 * @param bool $applydst Toggle Daylight Saving Time, default true, will be
1916 * applied only if timezone is 99 or string.
1917 * @return int GMT timestamp
1919 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1921 //save input timezone, required for dst offset check.
1922 $passedtimezone = $timezone;
1924 $timezone = get_user_timezone_offset($timezone);
1926 if (abs($timezone) > 13) { //server time
1927 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1928 } else {
1929 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1930 $time = usertime($time, $timezone);
1932 //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
1933 if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
1934 $time -= dst_offset_on($time, $passedtimezone);
1938 return $time;
1943 * Format a date/time (seconds) as weeks, days, hours etc as needed
1945 * Given an amount of time in seconds, returns string
1946 * formatted nicely as weeks, days, hours etc as needed
1948 * @package core
1949 * @category time
1950 * @uses MINSECS
1951 * @uses HOURSECS
1952 * @uses DAYSECS
1953 * @uses YEARSECS
1954 * @param int $totalsecs Time in seconds
1955 * @param object $str Should be a time object
1956 * @return string A nicely formatted date/time string
1958 function format_time($totalsecs, $str=NULL) {
1960 $totalsecs = abs($totalsecs);
1962 if (!$str) { // Create the str structure the slow way
1963 $str = new stdClass();
1964 $str->day = get_string('day');
1965 $str->days = get_string('days');
1966 $str->hour = get_string('hour');
1967 $str->hours = get_string('hours');
1968 $str->min = get_string('min');
1969 $str->mins = get_string('mins');
1970 $str->sec = get_string('sec');
1971 $str->secs = get_string('secs');
1972 $str->year = get_string('year');
1973 $str->years = get_string('years');
1977 $years = floor($totalsecs/YEARSECS);
1978 $remainder = $totalsecs - ($years*YEARSECS);
1979 $days = floor($remainder/DAYSECS);
1980 $remainder = $totalsecs - ($days*DAYSECS);
1981 $hours = floor($remainder/HOURSECS);
1982 $remainder = $remainder - ($hours*HOURSECS);
1983 $mins = floor($remainder/MINSECS);
1984 $secs = $remainder - ($mins*MINSECS);
1986 $ss = ($secs == 1) ? $str->sec : $str->secs;
1987 $sm = ($mins == 1) ? $str->min : $str->mins;
1988 $sh = ($hours == 1) ? $str->hour : $str->hours;
1989 $sd = ($days == 1) ? $str->day : $str->days;
1990 $sy = ($years == 1) ? $str->year : $str->years;
1992 $oyears = '';
1993 $odays = '';
1994 $ohours = '';
1995 $omins = '';
1996 $osecs = '';
1998 if ($years) $oyears = $years .' '. $sy;
1999 if ($days) $odays = $days .' '. $sd;
2000 if ($hours) $ohours = $hours .' '. $sh;
2001 if ($mins) $omins = $mins .' '. $sm;
2002 if ($secs) $osecs = $secs .' '. $ss;
2004 if ($years) return trim($oyears .' '. $odays);
2005 if ($days) return trim($odays .' '. $ohours);
2006 if ($hours) return trim($ohours .' '. $omins);
2007 if ($mins) return trim($omins .' '. $osecs);
2008 if ($secs) return $osecs;
2009 return get_string('now');
2013 * Returns a formatted string that represents a date in user time
2015 * Returns a formatted string that represents a date in user time
2016 * <b>WARNING: note that the format is for strftime(), not date().</b>
2017 * Because of a bug in most Windows time libraries, we can't use
2018 * the nicer %e, so we have to use %d which has leading zeroes.
2019 * A lot of the fuss in the function is just getting rid of these leading
2020 * zeroes as efficiently as possible.
2022 * If parameter fixday = true (default), then take off leading
2023 * zero from %d, else maintain it.
2025 * @package core
2026 * @category time
2027 * @param int $date the timestamp in UTC, as obtained from the database.
2028 * @param string $format strftime format. You should probably get this using
2029 * get_string('strftime...', 'langconfig');
2030 * @param int|float|string $timezone by default, uses the user's time zone. if numeric and
2031 * not 99 then daylight saving will not be added.
2032 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2033 * @param bool $fixday If true (default) then the leading zero from %d is removed.
2034 * If false then the leading zero is maintained.
2035 * @param bool $fixhour If true (default) then the leading zero from %I is removed.
2036 * @return string the formatted date/time.
2038 function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true) {
2040 global $CFG;
2042 if (empty($format)) {
2043 $format = get_string('strftimedaydatetime', 'langconfig');
2046 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
2047 $fixday = false;
2048 } else if ($fixday) {
2049 $formatnoday = str_replace('%d', 'DD', $format);
2050 $fixday = ($formatnoday != $format);
2051 $format = $formatnoday;
2054 // Note: This logic about fixing 12-hour time to remove unnecessary leading
2055 // zero is required because on Windows, PHP strftime function does not
2056 // support the correct 'hour without leading zero' parameter (%l).
2057 if (!empty($CFG->nofixhour)) {
2058 // Config.php can force %I not to be fixed.
2059 $fixhour = false;
2060 } else if ($fixhour) {
2061 $formatnohour = str_replace('%I', 'HH', $format);
2062 $fixhour = ($formatnohour != $format);
2063 $format = $formatnohour;
2066 //add daylight saving offset for string timezones only, as we can't get dst for
2067 //float values. if timezone is 99 (user default timezone), then try update dst.
2068 if ((99 == $timezone) || !is_numeric($timezone)) {
2069 $date += dst_offset_on($date, $timezone);
2072 $timezone = get_user_timezone_offset($timezone);
2074 // If we are running under Windows convert to windows encoding and then back to UTF-8
2075 // (because it's impossible to specify UTF-8 to fetch locale info in Win32)
2077 if (abs($timezone) > 13) { /// Server time
2078 $datestring = date_format_string($date, $format, $timezone);
2079 if ($fixday) {
2080 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
2081 $datestring = str_replace('DD', $daystring, $datestring);
2083 if ($fixhour) {
2084 $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
2085 $datestring = str_replace('HH', $hourstring, $datestring);
2088 } else {
2089 $date += (int)($timezone * 3600);
2090 $datestring = date_format_string($date, $format, $timezone);
2091 if ($fixday) {
2092 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
2093 $datestring = str_replace('DD', $daystring, $datestring);
2095 if ($fixhour) {
2096 $hourstring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %I', $date)));
2097 $datestring = str_replace('HH', $hourstring, $datestring);
2101 return $datestring;
2105 * Returns a formatted date ensuring it is UTF-8.
2107 * If we are running under Windows convert to Windows encoding and then back to UTF-8
2108 * (because it's impossible to specify UTF-8 to fetch locale info in Win32).
2110 * This function does not do any calculation regarding the user preferences and should
2111 * therefore receive the final date timestamp, format and timezone. Timezone being only used
2112 * to differenciate the use of server time or not (strftime() against gmstrftime()).
2114 * @param int $date the timestamp.
2115 * @param string $format strftime format.
2116 * @param int|float $timezone the numerical timezone, typically returned by {@link get_user_timezone_offset()}.
2117 * @return string the formatted date/time.
2118 * @since 2.3.3
2120 function date_format_string($date, $format, $tz = 99) {
2121 global $CFG;
2122 if (abs($tz) > 13) {
2123 if ($CFG->ostype == 'WINDOWS') {
2124 $localewincharset = get_string('localewincharset', 'langconfig');
2125 $format = textlib::convert($format, 'utf-8', $localewincharset);
2126 $datestring = strftime($format, $date);
2127 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2128 } else {
2129 $datestring = strftime($format, $date);
2131 } else {
2132 if ($CFG->ostype == 'WINDOWS') {
2133 $localewincharset = get_string('localewincharset', 'langconfig');
2134 $format = textlib::convert($format, 'utf-8', $localewincharset);
2135 $datestring = gmstrftime($format, $date);
2136 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2137 } else {
2138 $datestring = gmstrftime($format, $date);
2141 return $datestring;
2145 * Given a $time timestamp in GMT (seconds since epoch),
2146 * returns an array that represents the date in user time
2148 * @package core
2149 * @category time
2150 * @uses HOURSECS
2151 * @param int $time Timestamp in GMT
2152 * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
2153 * dst offset is applyed {@link http://docs.moodle.org/dev/Time_API#Timezone}
2154 * @return array An array that represents the date in user time
2156 function usergetdate($time, $timezone=99) {
2158 //save input timezone, required for dst offset check.
2159 $passedtimezone = $timezone;
2161 $timezone = get_user_timezone_offset($timezone);
2163 if (abs($timezone) > 13) { // Server time
2164 return getdate($time);
2167 //add daylight saving offset for string timezones only, as we can't get dst for
2168 //float values. if timezone is 99 (user default timezone), then try update dst.
2169 if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
2170 $time += dst_offset_on($time, $passedtimezone);
2173 $time += intval((float)$timezone * HOURSECS);
2175 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
2177 //be careful to ensure the returned array matches that produced by getdate() above
2178 list(
2179 $getdate['month'],
2180 $getdate['weekday'],
2181 $getdate['yday'],
2182 $getdate['year'],
2183 $getdate['mon'],
2184 $getdate['wday'],
2185 $getdate['mday'],
2186 $getdate['hours'],
2187 $getdate['minutes'],
2188 $getdate['seconds']
2189 ) = explode('_', $datestring);
2191 // set correct datatype to match with getdate()
2192 $getdate['seconds'] = (int)$getdate['seconds'];
2193 $getdate['yday'] = (int)$getdate['yday'] - 1; // gettime returns 0 through 365
2194 $getdate['year'] = (int)$getdate['year'];
2195 $getdate['mon'] = (int)$getdate['mon'];
2196 $getdate['wday'] = (int)$getdate['wday'];
2197 $getdate['mday'] = (int)$getdate['mday'];
2198 $getdate['hours'] = (int)$getdate['hours'];
2199 $getdate['minutes'] = (int)$getdate['minutes'];
2200 return $getdate;
2204 * Given a GMT timestamp (seconds since epoch), offsets it by
2205 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
2207 * @package core
2208 * @category time
2209 * @uses HOURSECS
2210 * @param int $date Timestamp in GMT
2211 * @param float|int|string $timezone timezone to calculate GMT time offset before
2212 * calculating user time, 99 is default user timezone
2213 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2214 * @return int
2216 function usertime($date, $timezone=99) {
2218 $timezone = get_user_timezone_offset($timezone);
2220 if (abs($timezone) > 13) {
2221 return $date;
2223 return $date - (int)($timezone * HOURSECS);
2227 * Given a time, return the GMT timestamp of the most recent midnight
2228 * for the current user.
2230 * @package core
2231 * @category time
2232 * @param int $date Timestamp in GMT
2233 * @param float|int|string $timezone timezone to calculate GMT time offset before
2234 * calculating user midnight time, 99 is default user timezone
2235 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2236 * @return int Returns a GMT timestamp
2238 function usergetmidnight($date, $timezone=99) {
2240 $userdate = usergetdate($date, $timezone);
2242 // Time of midnight of this user's day, in GMT
2243 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
2248 * Returns a string that prints the user's timezone
2250 * @package core
2251 * @category time
2252 * @param float|int|string $timezone timezone to calculate GMT time offset before
2253 * calculating user timezone, 99 is default user timezone
2254 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2255 * @return string
2257 function usertimezone($timezone=99) {
2259 $tz = get_user_timezone($timezone);
2261 if (!is_float($tz)) {
2262 return $tz;
2265 if(abs($tz) > 13) { // Server time
2266 return get_string('serverlocaltime');
2269 if($tz == intval($tz)) {
2270 // Don't show .0 for whole hours
2271 $tz = intval($tz);
2274 if($tz == 0) {
2275 return 'UTC';
2277 else if($tz > 0) {
2278 return 'UTC+'.$tz;
2280 else {
2281 return 'UTC'.$tz;
2287 * Returns a float which represents the user's timezone difference from GMT in hours
2288 * Checks various settings and picks the most dominant of those which have a value
2290 * @package core
2291 * @category time
2292 * @param float|int|string $tz timezone to calculate GMT time offset for user,
2293 * 99 is default user timezone
2294 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2295 * @return float
2297 function get_user_timezone_offset($tz = 99) {
2299 global $USER, $CFG;
2301 $tz = get_user_timezone($tz);
2303 if (is_float($tz)) {
2304 return $tz;
2305 } else {
2306 $tzrecord = get_timezone_record($tz);
2307 if (empty($tzrecord)) {
2308 return 99.0;
2310 return (float)$tzrecord->gmtoff / HOURMINS;
2315 * Returns an int which represents the systems's timezone difference from GMT in seconds
2317 * @package core
2318 * @category time
2319 * @param float|int|string $tz timezone for which offset is required.
2320 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2321 * @return int|bool if found, false is timezone 99 or error
2323 function get_timezone_offset($tz) {
2324 global $CFG;
2326 if ($tz == 99) {
2327 return false;
2330 if (is_numeric($tz)) {
2331 return intval($tz * 60*60);
2334 if (!$tzrecord = get_timezone_record($tz)) {
2335 return false;
2337 return intval($tzrecord->gmtoff * 60);
2341 * Returns a float or a string which denotes the user's timezone
2342 * 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)
2343 * means that for this timezone there are also DST rules to be taken into account
2344 * Checks various settings and picks the most dominant of those which have a value
2346 * @package core
2347 * @category time
2348 * @param float|int|string $tz timezone to calculate GMT time offset before
2349 * calculating user timezone, 99 is default user timezone
2350 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2351 * @return float|string
2353 function get_user_timezone($tz = 99) {
2354 global $USER, $CFG;
2356 $timezones = array(
2357 $tz,
2358 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
2359 isset($USER->timezone) ? $USER->timezone : 99,
2360 isset($CFG->timezone) ? $CFG->timezone : 99,
2363 $tz = 99;
2365 // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array
2366 while(((empty($tz) && !is_numeric($tz)) || $tz == 99) && $next = each($timezones)) {
2367 $tz = $next['value'];
2369 return is_numeric($tz) ? (float) $tz : $tz;
2373 * Returns cached timezone record for given $timezonename
2375 * @package core
2376 * @param string $timezonename name of the timezone
2377 * @return stdClass|bool timezonerecord or false
2379 function get_timezone_record($timezonename) {
2380 global $CFG, $DB;
2381 static $cache = NULL;
2383 if ($cache === NULL) {
2384 $cache = array();
2387 if (isset($cache[$timezonename])) {
2388 return $cache[$timezonename];
2391 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
2392 WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
2396 * Build and store the users Daylight Saving Time (DST) table
2398 * @package core
2399 * @param int $from_year Start year for the table, defaults to 1971
2400 * @param int $to_year End year for the table, defaults to 2035
2401 * @param int|float|string $strtimezone, timezone to check if dst should be applyed.
2402 * @return bool
2404 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
2405 global $CFG, $SESSION, $DB;
2407 $usertz = get_user_timezone($strtimezone);
2409 if (is_float($usertz)) {
2410 // Trivial timezone, no DST
2411 return false;
2414 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
2415 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2416 unset($SESSION->dst_offsets);
2417 unset($SESSION->dst_range);
2420 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
2421 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
2422 // This will be the return path most of the time, pretty light computationally
2423 return true;
2426 // Reaching here means we either need to extend our table or create it from scratch
2428 // Remember which TZ we calculated these changes for
2429 $SESSION->dst_offsettz = $usertz;
2431 if(empty($SESSION->dst_offsets)) {
2432 // If we 're creating from scratch, put the two guard elements in there
2433 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
2435 if(empty($SESSION->dst_range)) {
2436 // If creating from scratch
2437 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
2438 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
2440 // Fill in the array with the extra years we need to process
2441 $yearstoprocess = array();
2442 for($i = $from; $i <= $to; ++$i) {
2443 $yearstoprocess[] = $i;
2446 // Take note of which years we have processed for future calls
2447 $SESSION->dst_range = array($from, $to);
2449 else {
2450 // If needing to extend the table, do the same
2451 $yearstoprocess = array();
2453 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
2454 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
2456 if($from < $SESSION->dst_range[0]) {
2457 // Take note of which years we need to process and then note that we have processed them for future calls
2458 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2459 $yearstoprocess[] = $i;
2461 $SESSION->dst_range[0] = $from;
2463 if($to > $SESSION->dst_range[1]) {
2464 // Take note of which years we need to process and then note that we have processed them for future calls
2465 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2466 $yearstoprocess[] = $i;
2468 $SESSION->dst_range[1] = $to;
2472 if(empty($yearstoprocess)) {
2473 // This means that there was a call requesting a SMALLER range than we have already calculated
2474 return true;
2477 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2478 // Also, the array is sorted in descending timestamp order!
2480 // Get DB data
2482 static $presets_cache = array();
2483 if (!isset($presets_cache[$usertz])) {
2484 $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');
2486 if(empty($presets_cache[$usertz])) {
2487 return false;
2490 // Remove ending guard (first element of the array)
2491 reset($SESSION->dst_offsets);
2492 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2494 // Add all required change timestamps
2495 foreach($yearstoprocess as $y) {
2496 // Find the record which is in effect for the year $y
2497 foreach($presets_cache[$usertz] as $year => $preset) {
2498 if($year <= $y) {
2499 break;
2503 $changes = dst_changes_for_year($y, $preset);
2505 if($changes === NULL) {
2506 continue;
2508 if($changes['dst'] != 0) {
2509 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2511 if($changes['std'] != 0) {
2512 $SESSION->dst_offsets[$changes['std']] = 0;
2516 // Put in a guard element at the top
2517 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2518 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2520 // Sort again
2521 krsort($SESSION->dst_offsets);
2523 return true;
2527 * Calculates the required DST change and returns a Timestamp Array
2529 * @package core
2530 * @category time
2531 * @uses HOURSECS
2532 * @uses MINSECS
2533 * @param int|string $year Int or String Year to focus on
2534 * @param object $timezone Instatiated Timezone object
2535 * @return array|null Array dst=>xx, 0=>xx, std=>yy, 1=>yy or NULL
2537 function dst_changes_for_year($year, $timezone) {
2539 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2540 return NULL;
2543 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2544 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2546 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
2547 list($std_hour, $std_min) = explode(':', $timezone->std_time);
2549 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2550 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2552 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2553 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2554 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2556 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
2557 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
2559 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2563 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2564 * - Note: Daylight saving only works for string timezones and not for float.
2566 * @package core
2567 * @category time
2568 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2569 * @param int|float|string $strtimezone timezone for which offset is expected, if 99 or null
2570 * then user's default timezone is used. {@link http://docs.moodle.org/dev/Time_API#Timezone}
2571 * @return int
2573 function dst_offset_on($time, $strtimezone = NULL) {
2574 global $SESSION;
2576 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
2577 return 0;
2580 reset($SESSION->dst_offsets);
2581 while(list($from, $offset) = each($SESSION->dst_offsets)) {
2582 if($from <= $time) {
2583 break;
2587 // This is the normal return path
2588 if($offset !== NULL) {
2589 return $offset;
2592 // Reaching this point means we haven't calculated far enough, do it now:
2593 // Calculate extra DST changes if needed and recurse. The recursion always
2594 // moves toward the stopping condition, so will always end.
2596 if($from == 0) {
2597 // We need a year smaller than $SESSION->dst_range[0]
2598 if($SESSION->dst_range[0] == 1971) {
2599 return 0;
2601 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
2602 return dst_offset_on($time, $strtimezone);
2604 else {
2605 // We need a year larger than $SESSION->dst_range[1]
2606 if($SESSION->dst_range[1] == 2035) {
2607 return 0;
2609 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
2610 return dst_offset_on($time, $strtimezone);
2615 * Calculates when the day appears in specific month
2617 * @package core
2618 * @category time
2619 * @param int $startday starting day of the month
2620 * @param int $weekday The day when week starts (normally taken from user preferences)
2621 * @param int $month The month whose day is sought
2622 * @param int $year The year of the month whose day is sought
2623 * @return int
2625 function find_day_in_month($startday, $weekday, $month, $year) {
2627 $daysinmonth = days_in_month($month, $year);
2629 if($weekday == -1) {
2630 // Don't care about weekday, so return:
2631 // abs($startday) if $startday != -1
2632 // $daysinmonth otherwise
2633 return ($startday == -1) ? $daysinmonth : abs($startday);
2636 // From now on we 're looking for a specific weekday
2638 // Give "end of month" its actual value, since we know it
2639 if($startday == -1) {
2640 $startday = -1 * $daysinmonth;
2643 // Starting from day $startday, the sign is the direction
2645 if($startday < 1) {
2647 $startday = abs($startday);
2648 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2650 // This is the last such weekday of the month
2651 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2652 if($lastinmonth > $daysinmonth) {
2653 $lastinmonth -= 7;
2656 // Find the first such weekday <= $startday
2657 while($lastinmonth > $startday) {
2658 $lastinmonth -= 7;
2661 return $lastinmonth;
2664 else {
2666 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2668 $diff = $weekday - $indexweekday;
2669 if($diff < 0) {
2670 $diff += 7;
2673 // This is the first such weekday of the month equal to or after $startday
2674 $firstfromindex = $startday + $diff;
2676 return $firstfromindex;
2682 * Calculate the number of days in a given month
2684 * @package core
2685 * @category time
2686 * @param int $month The month whose day count is sought
2687 * @param int $year The year of the month whose day count is sought
2688 * @return int
2690 function days_in_month($month, $year) {
2691 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2695 * Calculate the position in the week of a specific calendar day
2697 * @package core
2698 * @category time
2699 * @param int $day The day of the date whose position in the week is sought
2700 * @param int $month The month of the date whose position in the week is sought
2701 * @param int $year The year of the date whose position in the week is sought
2702 * @return int
2704 function dayofweek($day, $month, $year) {
2705 // I wonder if this is any different from
2706 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2707 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2710 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2713 * Returns full login url.
2715 * @return string login url
2717 function get_login_url() {
2718 global $CFG;
2720 $url = "$CFG->wwwroot/login/index.php";
2722 if (!empty($CFG->loginhttps)) {
2723 $url = str_replace('http:', 'https:', $url);
2726 return $url;
2730 * This function checks that the current user is logged in and has the
2731 * required privileges
2733 * This function checks that the current user is logged in, and optionally
2734 * whether they are allowed to be in a particular course and view a particular
2735 * course module.
2736 * If they are not logged in, then it redirects them to the site login unless
2737 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2738 * case they are automatically logged in as guests.
2739 * If $courseid is given and the user is not enrolled in that course then the
2740 * user is redirected to the course enrolment page.
2741 * If $cm is given and the course module is hidden and the user is not a teacher
2742 * in the course then the user is redirected to the course home page.
2744 * When $cm parameter specified, this function sets page layout to 'module'.
2745 * You need to change it manually later if some other layout needed.
2747 * @package core_access
2748 * @category access
2750 * @param mixed $courseorid id of the course or course object
2751 * @param bool $autologinguest default true
2752 * @param object $cm course module object
2753 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2754 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2755 * in order to keep redirects working properly. MDL-14495
2756 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2757 * @return mixed Void, exit, and die depending on path
2759 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2760 global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
2762 // setup global $COURSE, themes, language and locale
2763 if (!empty($courseorid)) {
2764 if (is_object($courseorid)) {
2765 $course = $courseorid;
2766 } else if ($courseorid == SITEID) {
2767 $course = clone($SITE);
2768 } else {
2769 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2771 if ($cm) {
2772 if ($cm->course != $course->id) {
2773 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2775 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2776 if (!($cm instanceof cm_info)) {
2777 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2778 // db queries so this is not really a performance concern, however it is obviously
2779 // better if you use get_fast_modinfo to get the cm before calling this.
2780 $modinfo = get_fast_modinfo($course);
2781 $cm = $modinfo->get_cm($cm->id);
2783 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2784 $PAGE->set_pagelayout('incourse');
2785 } else {
2786 $PAGE->set_course($course); // set's up global $COURSE
2788 } else {
2789 // do not touch global $COURSE via $PAGE->set_course(),
2790 // the reasons is we need to be able to call require_login() at any time!!
2791 $course = $SITE;
2792 if ($cm) {
2793 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2797 // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2798 // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2799 // risk leading the user back to the AJAX request URL.
2800 if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
2801 $setwantsurltome = false;
2804 // If the user is not even logged in yet then make sure they are
2805 if (!isloggedin()) {
2806 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2807 if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2808 // misconfigured site guest, just redirect to login page
2809 redirect(get_login_url());
2810 exit; // never reached
2812 $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2813 complete_user_login($guest);
2814 $USER->autologinguest = true;
2815 $SESSION->lang = $lang;
2816 } else {
2817 //NOTE: $USER->site check was obsoleted by session test cookie,
2818 // $USER->confirmed test is in login/index.php
2819 if ($preventredirect) {
2820 throw new require_login_exception('You are not logged in');
2823 if ($setwantsurltome) {
2824 $SESSION->wantsurl = qualified_me();
2826 if (!empty($_SERVER['HTTP_REFERER'])) {
2827 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
2829 redirect(get_login_url());
2830 exit; // never reached
2834 // loginas as redirection if needed
2835 if ($course->id != SITEID and session_is_loggedinas()) {
2836 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2837 if ($USER->loginascontext->instanceid != $course->id) {
2838 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2843 // check whether the user should be changing password (but only if it is REALLY them)
2844 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2845 $userauth = get_auth_plugin($USER->auth);
2846 if ($userauth->can_change_password() and !$preventredirect) {
2847 if ($setwantsurltome) {
2848 $SESSION->wantsurl = qualified_me();
2850 if ($changeurl = $userauth->change_password_url()) {
2851 //use plugin custom url
2852 redirect($changeurl);
2853 } else {
2854 //use moodle internal method
2855 if (empty($CFG->loginhttps)) {
2856 redirect($CFG->wwwroot .'/login/change_password.php');
2857 } else {
2858 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
2859 redirect($wwwroot .'/login/change_password.php');
2862 } else {
2863 print_error('nopasswordchangeforced', 'auth');
2867 // Check that the user account is properly set up
2868 if (user_not_fully_set_up($USER)) {
2869 if ($preventredirect) {
2870 throw new require_login_exception('User not fully set-up');
2872 if ($setwantsurltome) {
2873 $SESSION->wantsurl = qualified_me();
2875 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2878 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2879 sesskey();
2881 // Do not bother admins with any formalities
2882 if (is_siteadmin()) {
2883 //set accesstime or the user will appear offline which messes up messaging
2884 user_accesstime_log($course->id);
2885 return;
2888 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2889 if (!$USER->policyagreed and !is_siteadmin()) {
2890 if (!empty($CFG->sitepolicy) and !isguestuser()) {
2891 if ($preventredirect) {
2892 throw new require_login_exception('Policy not agreed');
2894 if ($setwantsurltome) {
2895 $SESSION->wantsurl = qualified_me();
2897 redirect($CFG->wwwroot .'/user/policy.php');
2898 } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
2899 if ($preventredirect) {
2900 throw new require_login_exception('Policy not agreed');
2902 if ($setwantsurltome) {
2903 $SESSION->wantsurl = qualified_me();
2905 redirect($CFG->wwwroot .'/user/policy.php');
2909 // Fetch the system context, the course context, and prefetch its child contexts
2910 $sysctx = get_context_instance(CONTEXT_SYSTEM);
2911 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
2912 if ($cm) {
2913 $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
2914 } else {
2915 $cmcontext = null;
2918 // If the site is currently under maintenance, then print a message
2919 if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
2920 if ($preventredirect) {
2921 throw new require_login_exception('Maintenance in progress');
2924 print_maintenance_message();
2927 // make sure the course itself is not hidden
2928 if ($course->id == SITEID) {
2929 // frontpage can not be hidden
2930 } else {
2931 if (is_role_switched($course->id)) {
2932 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2933 } else {
2934 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2935 // originally there was also test of parent category visibility,
2936 // BUT is was very slow in complex queries involving "my courses"
2937 // now it is also possible to simply hide all courses user is not enrolled in :-)
2938 if ($preventredirect) {
2939 throw new require_login_exception('Course is hidden');
2941 // We need to override the navigation URL as the course won't have
2942 // been added to the navigation and thus the navigation will mess up
2943 // when trying to find it.
2944 navigation_node::override_active_url(new moodle_url('/'));
2945 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2950 // is the user enrolled?
2951 if ($course->id == SITEID) {
2952 // everybody is enrolled on the frontpage
2954 } else {
2955 if (session_is_loggedinas()) {
2956 // Make sure the REAL person can access this course first
2957 $realuser = session_get_realuser();
2958 if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
2959 if ($preventredirect) {
2960 throw new require_login_exception('Invalid course login-as access');
2962 echo $OUTPUT->header();
2963 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2967 $access = false;
2969 if (is_role_switched($course->id)) {
2970 // ok, user had to be inside this course before the switch
2971 $access = true;
2973 } else if (is_viewing($coursecontext, $USER)) {
2974 // ok, no need to mess with enrol
2975 $access = true;
2977 } else {
2978 if (isset($USER->enrol['enrolled'][$course->id])) {
2979 if ($USER->enrol['enrolled'][$course->id] > time()) {
2980 $access = true;
2981 if (isset($USER->enrol['tempguest'][$course->id])) {
2982 unset($USER->enrol['tempguest'][$course->id]);
2983 remove_temp_course_roles($coursecontext);
2985 } else {
2986 //expired
2987 unset($USER->enrol['enrolled'][$course->id]);
2990 if (isset($USER->enrol['tempguest'][$course->id])) {
2991 if ($USER->enrol['tempguest'][$course->id] == 0) {
2992 $access = true;
2993 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
2994 $access = true;
2995 } else {
2996 //expired
2997 unset($USER->enrol['tempguest'][$course->id]);
2998 remove_temp_course_roles($coursecontext);
3002 if ($access) {
3003 // cache ok
3004 } else {
3005 $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
3006 if ($until !== false) {
3007 // active participants may always access, a timestamp in the future, 0 (always) or false.
3008 if ($until == 0) {
3009 $until = ENROL_MAX_TIMESTAMP;
3011 $USER->enrol['enrolled'][$course->id] = $until;
3012 $access = true;
3014 } else {
3015 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
3016 $enrols = enrol_get_plugins(true);
3017 // first ask all enabled enrol instances in course if they want to auto enrol user
3018 foreach($instances as $instance) {
3019 if (!isset($enrols[$instance->enrol])) {
3020 continue;
3022 // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
3023 $until = $enrols[$instance->enrol]->try_autoenrol($instance);
3024 if ($until !== false) {
3025 if ($until == 0) {
3026 $until = ENROL_MAX_TIMESTAMP;
3028 $USER->enrol['enrolled'][$course->id] = $until;
3029 $access = true;
3030 break;
3033 // if not enrolled yet try to gain temporary guest access
3034 if (!$access) {
3035 foreach($instances as $instance) {
3036 if (!isset($enrols[$instance->enrol])) {
3037 continue;
3039 // Get a duration for the guest access, a timestamp in the future or false.
3040 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
3041 if ($until !== false and $until > time()) {
3042 $USER->enrol['tempguest'][$course->id] = $until;
3043 $access = true;
3044 break;
3052 if (!$access) {
3053 if ($preventredirect) {
3054 throw new require_login_exception('Not enrolled');
3056 if ($setwantsurltome) {
3057 $SESSION->wantsurl = qualified_me();
3059 redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
3063 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
3064 // conditional availability, etc
3065 if ($cm && !$cm->uservisible) {
3066 if ($preventredirect) {
3067 throw new require_login_exception('Activity is hidden');
3069 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
3072 // Finally access granted, update lastaccess times
3073 user_accesstime_log($course->id);
3078 * This function just makes sure a user is logged out.
3080 * @package core_access
3082 function require_logout() {
3083 global $USER;
3085 $params = $USER;
3087 if (isloggedin()) {
3088 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
3090 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
3091 foreach($authsequence as $authname) {
3092 $authplugin = get_auth_plugin($authname);
3093 $authplugin->prelogout_hook();
3097 events_trigger('user_logout', $params);
3098 session_get_instance()->terminate_current();
3099 unset($params);
3103 * Weaker version of require_login()
3105 * This is a weaker version of {@link require_login()} which only requires login
3106 * when called from within a course rather than the site page, unless
3107 * the forcelogin option is turned on.
3108 * @see require_login()
3110 * @package core_access
3111 * @category access
3113 * @param mixed $courseorid The course object or id in question
3114 * @param bool $autologinguest Allow autologin guests if that is wanted
3115 * @param object $cm Course activity module if known
3116 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
3117 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
3118 * in order to keep redirects working properly. MDL-14495
3119 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
3120 * @return void
3122 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
3123 global $CFG, $PAGE, $SITE;
3124 $issite = (is_object($courseorid) and $courseorid->id == SITEID)
3125 or (!is_object($courseorid) and $courseorid == SITEID);
3126 if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
3127 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
3128 // db queries so this is not really a performance concern, however it is obviously
3129 // better if you use get_fast_modinfo to get the cm before calling this.
3130 if (is_object($courseorid)) {
3131 $course = $courseorid;
3132 } else {
3133 $course = clone($SITE);
3135 $modinfo = get_fast_modinfo($course);
3136 $cm = $modinfo->get_cm($cm->id);
3138 if (!empty($CFG->forcelogin)) {
3139 // login required for both SITE and courses
3140 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3142 } else if ($issite && !empty($cm) and !$cm->uservisible) {
3143 // always login for hidden activities
3144 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3146 } else if ($issite) {
3147 //login for SITE not required
3148 if ($cm and empty($cm->visible)) {
3149 // hidden activities are not accessible without login
3150 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3151 } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
3152 // not-logged-in users do not have any group membership
3153 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3154 } else {
3155 // We still need to instatiate PAGE vars properly so that things
3156 // that rely on it like navigation function correctly.
3157 if (!empty($courseorid)) {
3158 if (is_object($courseorid)) {
3159 $course = $courseorid;
3160 } else {
3161 $course = clone($SITE);
3163 if ($cm) {
3164 if ($cm->course != $course->id) {
3165 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
3167 $PAGE->set_cm($cm, $course);
3168 $PAGE->set_pagelayout('incourse');
3169 } else {
3170 $PAGE->set_course($course);
3172 } else {
3173 // If $PAGE->course, and hence $PAGE->context, have not already been set
3174 // up properly, set them up now.
3175 $PAGE->set_course($PAGE->course);
3177 //TODO: verify conditional activities here
3178 user_accesstime_log(SITEID);
3179 return;
3182 } else {
3183 // course login always required
3184 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3189 * Require key login. Function terminates with error if key not found or incorrect.
3191 * @global object
3192 * @global object
3193 * @global object
3194 * @global object
3195 * @uses NO_MOODLE_COOKIES
3196 * @uses PARAM_ALPHANUM
3197 * @param string $script unique script identifier
3198 * @param int $instance optional instance id
3199 * @return int Instance ID
3201 function require_user_key_login($script, $instance=null) {
3202 global $USER, $SESSION, $CFG, $DB;
3204 if (!NO_MOODLE_COOKIES) {
3205 print_error('sessioncookiesdisable');
3208 /// extra safety
3209 @session_write_close();
3211 $keyvalue = required_param('key', PARAM_ALPHANUM);
3213 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
3214 print_error('invalidkey');
3217 if (!empty($key->validuntil) and $key->validuntil < time()) {
3218 print_error('expiredkey');
3221 if ($key->iprestriction) {
3222 $remoteaddr = getremoteaddr(null);
3223 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
3224 print_error('ipmismatch');
3228 if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
3229 print_error('invaliduserid');
3232 /// emulate normal session
3233 enrol_check_plugins($user);
3234 session_set_user($user);
3236 /// note we are not using normal login
3237 if (!defined('USER_KEY_LOGIN')) {
3238 define('USER_KEY_LOGIN', true);
3241 /// return instance id - it might be empty
3242 return $key->instance;
3246 * Creates a new private user access key.
3248 * @global object
3249 * @param string $script unique target identifier
3250 * @param int $userid
3251 * @param int $instance optional instance id
3252 * @param string $iprestriction optional ip restricted access
3253 * @param timestamp $validuntil key valid only until given data
3254 * @return string access key value
3256 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3257 global $DB;
3259 $key = new stdClass();
3260 $key->script = $script;
3261 $key->userid = $userid;
3262 $key->instance = $instance;
3263 $key->iprestriction = $iprestriction;
3264 $key->validuntil = $validuntil;
3265 $key->timecreated = time();
3267 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
3268 while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
3269 // must be unique
3270 $key->value = md5($userid.'_'.time().random_string(40));
3272 $DB->insert_record('user_private_key', $key);
3273 return $key->value;
3277 * Delete the user's new private user access keys for a particular script.
3279 * @global object
3280 * @param string $script unique target identifier
3281 * @param int $userid
3282 * @return void
3284 function delete_user_key($script,$userid) {
3285 global $DB;
3286 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
3290 * Gets a private user access key (and creates one if one doesn't exist).
3292 * @global object
3293 * @param string $script unique target identifier
3294 * @param int $userid
3295 * @param int $instance optional instance id
3296 * @param string $iprestriction optional ip restricted access
3297 * @param timestamp $validuntil key valid only until given data
3298 * @return string access key value
3300 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3301 global $DB;
3303 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
3304 'instance'=>$instance, 'iprestriction'=>$iprestriction,
3305 'validuntil'=>$validuntil))) {
3306 return $key->value;
3307 } else {
3308 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
3314 * Modify the user table by setting the currently logged in user's
3315 * last login to now.
3317 * @global object
3318 * @global object
3319 * @return bool Always returns true
3321 function update_user_login_times() {
3322 global $USER, $DB;
3324 if (isguestuser()) {
3325 // Do not update guest access times/ips for performance.
3326 return true;
3329 $now = time();
3331 $user = new stdClass();
3332 $user->id = $USER->id;
3334 // Make sure all users that logged in have some firstaccess.
3335 if ($USER->firstaccess == 0) {
3336 $USER->firstaccess = $user->firstaccess = $now;
3339 // Store the previous current as lastlogin.
3340 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
3342 $USER->currentlogin = $user->currentlogin = $now;
3344 // Function user_accesstime_log() may not update immediately, better do it here.
3345 $USER->lastaccess = $user->lastaccess = $now;
3346 $USER->lastip = $user->lastip = getremoteaddr();
3348 $DB->update_record('user', $user);
3349 return true;
3353 * Determines if a user has completed setting up their account.
3355 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
3356 * @return bool
3358 function user_not_fully_set_up($user) {
3359 if (isguestuser($user)) {
3360 return false;
3362 return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
3366 * Check whether the user has exceeded the bounce threshold
3368 * @global object
3369 * @global object
3370 * @param user $user A {@link $USER} object
3371 * @return bool true=>User has exceeded bounce threshold
3373 function over_bounce_threshold($user) {
3374 global $CFG, $DB;
3376 if (empty($CFG->handlebounces)) {
3377 return false;
3380 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3381 return false;
3384 // set sensible defaults
3385 if (empty($CFG->minbounces)) {
3386 $CFG->minbounces = 10;
3388 if (empty($CFG->bounceratio)) {
3389 $CFG->bounceratio = .20;
3391 $bouncecount = 0;
3392 $sendcount = 0;
3393 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3394 $bouncecount = $bounce->value;
3396 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3397 $sendcount = $send->value;
3399 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
3403 * Used to increment or reset email sent count
3405 * @global object
3406 * @param user $user object containing an id
3407 * @param bool $reset will reset the count to 0
3408 * @return void
3410 function set_send_count($user,$reset=false) {
3411 global $DB;
3413 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3414 return;
3417 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3418 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3419 $DB->update_record('user_preferences', $pref);
3421 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3422 // make a new one
3423 $pref = new stdClass();
3424 $pref->name = 'email_send_count';
3425 $pref->value = 1;
3426 $pref->userid = $user->id;
3427 $DB->insert_record('user_preferences', $pref, false);
3432 * Increment or reset user's email bounce count
3434 * @global object
3435 * @param user $user object containing an id
3436 * @param bool $reset will reset the count to 0
3438 function set_bounce_count($user,$reset=false) {
3439 global $DB;
3441 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3442 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3443 $DB->update_record('user_preferences', $pref);
3445 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3446 // make a new one
3447 $pref = new stdClass();
3448 $pref->name = 'email_bounce_count';
3449 $pref->value = 1;
3450 $pref->userid = $user->id;
3451 $DB->insert_record('user_preferences', $pref, false);
3456 * Keeps track of login attempts
3458 * @global object
3460 function update_login_count() {
3461 global $SESSION;
3463 $max_logins = 10;
3465 if (empty($SESSION->logincount)) {
3466 $SESSION->logincount = 1;
3467 } else {
3468 $SESSION->logincount++;
3471 if ($SESSION->logincount > $max_logins) {
3472 unset($SESSION->wantsurl);
3473 print_error('errortoomanylogins');
3478 * Resets login attempts
3480 * @global object
3482 function reset_login_count() {
3483 global $SESSION;
3485 $SESSION->logincount = 0;
3489 * Determines if the currently logged in user is in editing mode.
3490 * Note: originally this function had $userid parameter - it was not usable anyway
3492 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3493 * @todo Deprecated function remove when ready
3495 * @global object
3496 * @uses DEBUG_DEVELOPER
3497 * @return bool
3499 function isediting() {
3500 global $PAGE;
3501 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
3502 return $PAGE->user_is_editing();
3506 * Determines if the logged in user is currently moving an activity
3508 * @global object
3509 * @param int $courseid The id of the course being tested
3510 * @return bool
3512 function ismoving($courseid) {
3513 global $USER;
3515 if (!empty($USER->activitycopy)) {
3516 return ($USER->activitycopycourse == $courseid);
3518 return false;
3522 * Returns a persons full name
3524 * Given an object containing firstname and lastname
3525 * values, this function returns a string with the
3526 * full name of the person.
3527 * The result may depend on system settings
3528 * or language. 'override' will force both names
3529 * to be used even if system settings specify one.
3531 * @global object
3532 * @global object
3533 * @param object $user A {@link $USER} object to get full name of
3534 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3535 * @return string
3537 function fullname($user, $override=false) {
3538 global $CFG, $SESSION;
3540 if (!isset($user->firstname) and !isset($user->lastname)) {
3541 return '';
3544 if (!$override) {
3545 if (!empty($CFG->forcefirstname)) {
3546 $user->firstname = $CFG->forcefirstname;
3548 if (!empty($CFG->forcelastname)) {
3549 $user->lastname = $CFG->forcelastname;
3553 if (!empty($SESSION->fullnamedisplay)) {
3554 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3557 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
3558 return $user->firstname .' '. $user->lastname;
3560 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
3561 return $user->lastname .' '. $user->firstname;
3563 } else if ($CFG->fullnamedisplay == 'firstname') {
3564 if ($override) {
3565 return get_string('fullnamedisplay', '', $user);
3566 } else {
3567 return $user->firstname;
3571 return get_string('fullnamedisplay', '', $user);
3575 * Checks if current user is shown any extra fields when listing users.
3576 * @param object $context Context
3577 * @param array $already Array of fields that we're going to show anyway
3578 * so don't bother listing them
3579 * @return array Array of field names from user table, not including anything
3580 * listed in $already
3582 function get_extra_user_fields($context, $already = array()) {
3583 global $CFG;
3585 // Only users with permission get the extra fields
3586 if (!has_capability('moodle/site:viewuseridentity', $context)) {
3587 return array();
3590 // Split showuseridentity on comma
3591 if (empty($CFG->showuseridentity)) {
3592 // Explode gives wrong result with empty string
3593 $extra = array();
3594 } else {
3595 $extra = explode(',', $CFG->showuseridentity);
3597 $renumber = false;
3598 foreach ($extra as $key => $field) {
3599 if (in_array($field, $already)) {
3600 unset($extra[$key]);
3601 $renumber = true;
3604 if ($renumber) {
3605 // For consistency, if entries are removed from array, renumber it
3606 // so they are numbered as you would expect
3607 $extra = array_merge($extra);
3609 return $extra;
3613 * If the current user is to be shown extra user fields when listing or
3614 * selecting users, returns a string suitable for including in an SQL select
3615 * clause to retrieve those fields.
3616 * @param object $context Context
3617 * @param string $alias Alias of user table, e.g. 'u' (default none)
3618 * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none)
3619 * @param array $already Array of fields that we're going to include anyway
3620 * so don't list them (default none)
3621 * @return string Partial SQL select clause, beginning with comma, for example
3622 * ',u.idnumber,u.department' unless it is blank
3624 function get_extra_user_fields_sql($context, $alias='', $prefix='',
3625 $already = array()) {
3626 $fields = get_extra_user_fields($context, $already);
3627 $result = '';
3628 // Add punctuation for alias
3629 if ($alias !== '') {
3630 $alias .= '.';
3632 foreach ($fields as $field) {
3633 $result .= ', ' . $alias . $field;
3634 if ($prefix) {
3635 $result .= ' AS ' . $prefix . $field;
3638 return $result;
3642 * Returns the display name of a field in the user table. Works for most fields
3643 * that are commonly displayed to users.
3644 * @param string $field Field name, e.g. 'phone1'
3645 * @return string Text description taken from language file, e.g. 'Phone number'
3647 function get_user_field_name($field) {
3648 // Some fields have language strings which are not the same as field name
3649 switch ($field) {
3650 case 'phone1' : return get_string('phone');
3652 // Otherwise just use the same lang string
3653 return get_string($field);
3657 * Returns whether a given authentication plugin exists.
3659 * @global object
3660 * @param string $auth Form of authentication to check for. Defaults to the
3661 * global setting in {@link $CFG}.
3662 * @return boolean Whether the plugin is available.
3664 function exists_auth_plugin($auth) {
3665 global $CFG;
3667 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3668 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3670 return false;
3674 * Checks if a given plugin is in the list of enabled authentication plugins.
3676 * @param string $auth Authentication plugin.
3677 * @return boolean Whether the plugin is enabled.
3679 function is_enabled_auth($auth) {
3680 if (empty($auth)) {
3681 return false;
3684 $enabled = get_enabled_auth_plugins();
3686 return in_array($auth, $enabled);
3690 * Returns an authentication plugin instance.
3692 * @global object
3693 * @param string $auth name of authentication plugin
3694 * @return auth_plugin_base An instance of the required authentication plugin.
3696 function get_auth_plugin($auth) {
3697 global $CFG;
3699 // check the plugin exists first
3700 if (! exists_auth_plugin($auth)) {
3701 print_error('authpluginnotfound', 'debug', '', $auth);
3704 // return auth plugin instance
3705 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3706 $class = "auth_plugin_$auth";
3707 return new $class;
3711 * Returns array of active auth plugins.
3713 * @param bool $fix fix $CFG->auth if needed
3714 * @return array
3716 function get_enabled_auth_plugins($fix=false) {
3717 global $CFG;
3719 $default = array('manual', 'nologin');
3721 if (empty($CFG->auth)) {
3722 $auths = array();
3723 } else {
3724 $auths = explode(',', $CFG->auth);
3727 if ($fix) {
3728 $auths = array_unique($auths);
3729 foreach($auths as $k=>$authname) {
3730 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3731 unset($auths[$k]);
3734 $newconfig = implode(',', $auths);
3735 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
3736 set_config('auth', $newconfig);
3740 return (array_merge($default, $auths));
3744 * Returns true if an internal authentication method is being used.
3745 * if method not specified then, global default is assumed
3747 * @param string $auth Form of authentication required
3748 * @return bool
3750 function is_internal_auth($auth) {
3751 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3752 return $authplugin->is_internal();
3756 * Returns true if the user is a 'restored' one
3758 * Used in the login process to inform the user
3759 * and allow him/her to reset the password
3761 * @uses $CFG
3762 * @uses $DB
3763 * @param string $username username to be checked
3764 * @return bool
3766 function is_restored_user($username) {
3767 global $CFG, $DB;
3769 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
3773 * Returns an array of user fields
3775 * @return array User field/column names
3777 function get_user_fieldnames() {
3778 global $DB;
3780 $fieldarray = $DB->get_columns('user');
3781 unset($fieldarray['id']);
3782 $fieldarray = array_keys($fieldarray);
3784 return $fieldarray;
3788 * Creates a bare-bones user record
3790 * @todo Outline auth types and provide code example
3792 * @param string $username New user's username to add to record
3793 * @param string $password New user's password to add to record
3794 * @param string $auth Form of authentication required
3795 * @return stdClass A complete user object
3797 function create_user_record($username, $password, $auth = 'manual') {
3798 global $CFG, $DB;
3800 //just in case check text case
3801 $username = trim(textlib::strtolower($username));
3803 $authplugin = get_auth_plugin($auth);
3805 $newuser = new stdClass();
3807 if ($newinfo = $authplugin->get_userinfo($username)) {
3808 $newinfo = truncate_userinfo($newinfo);
3809 foreach ($newinfo as $key => $value){
3810 $newuser->$key = $value;
3814 if (!empty($newuser->email)) {
3815 if (email_is_not_allowed($newuser->email)) {
3816 unset($newuser->email);
3820 if (!isset($newuser->city)) {
3821 $newuser->city = '';
3824 $newuser->auth = $auth;
3825 $newuser->username = $username;
3827 // fix for MDL-8480
3828 // user CFG lang for user if $newuser->lang is empty
3829 // or $user->lang is not an installed language
3830 if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
3831 $newuser->lang = $CFG->lang;
3833 $newuser->confirmed = 1;
3834 $newuser->lastip = getremoteaddr();
3835 $newuser->timecreated = time();
3836 $newuser->timemodified = $newuser->timecreated;
3837 $newuser->mnethostid = $CFG->mnet_localhost_id;
3839 $newuser->id = $DB->insert_record('user', $newuser);
3840 $user = get_complete_user_data('id', $newuser->id);
3841 if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
3842 set_user_preference('auth_forcepasswordchange', 1, $user);
3844 update_internal_user_password($user, $password);
3846 // fetch full user record for the event, the complete user data contains too much info
3847 // and we want to be consistent with other places that trigger this event
3848 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
3850 return $user;
3854 * Will update a local user record from an external source.
3855 * (MNET users can not be updated using this method!)
3857 * @param string $username user's username to update the record
3858 * @return stdClass A complete user object
3860 function update_user_record($username) {
3861 global $DB, $CFG;
3863 $username = trim(textlib::strtolower($username)); /// just in case check text case
3865 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
3866 $newuser = array();
3867 $userauth = get_auth_plugin($oldinfo->auth);
3869 if ($newinfo = $userauth->get_userinfo($username)) {
3870 $newinfo = truncate_userinfo($newinfo);
3871 foreach ($newinfo as $key => $value){
3872 $key = strtolower($key);
3873 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3874 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3875 // unknown or must not be changed
3876 continue;
3878 $confval = $userauth->config->{'field_updatelocal_' . $key};
3879 $lockval = $userauth->config->{'field_lock_' . $key};
3880 if (empty($confval) || empty($lockval)) {
3881 continue;
3883 if ($confval === 'onlogin') {
3884 // MDL-4207 Don't overwrite modified user profile values with
3885 // empty LDAP values when 'unlocked if empty' is set. The purpose
3886 // of the setting 'unlocked if empty' is to allow the user to fill
3887 // in a value for the selected field _if LDAP is giving
3888 // nothing_ for this field. Thus it makes sense to let this value
3889 // stand in until LDAP is giving a value for this field.
3890 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3891 if ((string)$oldinfo->$key !== (string)$value) {
3892 $newuser[$key] = (string)$value;
3897 if ($newuser) {
3898 $newuser['id'] = $oldinfo->id;
3899 $newuser['timemodified'] = time();
3900 $DB->update_record('user', $newuser);
3901 // fetch full user record for the event, the complete user data contains too much info
3902 // and we want to be consistent with other places that trigger this event
3903 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
3907 return get_complete_user_data('id', $oldinfo->id);
3911 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3912 * which may have large fields
3914 * @todo Add vartype handling to ensure $info is an array
3916 * @param array $info Array of user properties to truncate if needed
3917 * @return array The now truncated information that was passed in
3919 function truncate_userinfo($info) {
3920 // define the limits
3921 $limit = array(
3922 'username' => 100,
3923 'idnumber' => 255,
3924 'firstname' => 100,
3925 'lastname' => 100,
3926 'email' => 100,
3927 'icq' => 15,
3928 'phone1' => 20,
3929 'phone2' => 20,
3930 'institution' => 40,
3931 'department' => 30,
3932 'address' => 70,
3933 'city' => 120,
3934 'country' => 2,
3935 'url' => 255,
3938 // apply where needed
3939 foreach (array_keys($info) as $key) {
3940 if (!empty($limit[$key])) {
3941 $info[$key] = trim(textlib::substr($info[$key],0, $limit[$key]));
3945 return $info;
3949 * Marks user deleted in internal user database and notifies the auth plugin.
3950 * Also unenrols user from all roles and does other cleanup.
3952 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3954 * @param stdClass $user full user object before delete
3955 * @return boolean success
3956 * @throws coding_exception if invalid $user parameter detected
3958 function delete_user(stdClass $user) {
3959 global $CFG, $DB;
3960 require_once($CFG->libdir.'/grouplib.php');
3961 require_once($CFG->libdir.'/gradelib.php');
3962 require_once($CFG->dirroot.'/message/lib.php');
3963 require_once($CFG->dirroot.'/tag/lib.php');
3965 // Make sure nobody sends bogus record type as parameter.
3966 if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
3967 throw new coding_exception('Invalid $user parameter in delete_user() detected');
3970 // Better not trust the parameter and fetch the latest info,
3971 // this will be very expensive anyway.
3972 if (!$user = $DB->get_record('user', array('id'=>$user->id))) {
3973 debugging('Attempt to delete unknown user account.');
3974 return false;
3977 // There must be always exactly one guest record,
3978 // originally the guest account was identified by username only,
3979 // now we use $CFG->siteguest for performance reasons.
3980 if ($user->username === 'guest' or isguestuser($user)) {
3981 debugging('Guest user account can not be deleted.');
3982 return false;
3985 // Admin can be theoretically from different auth plugin,
3986 // but we want to prevent deletion of internal accoutns only,
3987 // if anything goes wrong ppl may force somebody to be admin via
3988 // config.php setting $CFG->siteadmins.
3989 if ($user->auth === 'manual' and is_siteadmin($user)) {
3990 debugging('Local administrator accounts can not be deleted.');
3991 return false;
3994 // delete all grades - backup is kept in grade_grades_history table
3995 grade_user_delete($user->id);
3997 //move unread messages from this user to read
3998 message_move_userfrom_unread2read($user->id);
4000 // TODO: remove from cohorts using standard API here
4002 // remove user tags
4003 tag_set('user', $user->id, array());
4005 // unconditionally unenrol from all courses
4006 enrol_user_delete($user);
4008 // unenrol from all roles in all contexts
4009 role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
4011 //now do a brute force cleanup
4013 // remove from all cohorts
4014 $DB->delete_records('cohort_members', array('userid'=>$user->id));
4016 // remove from all groups
4017 $DB->delete_records('groups_members', array('userid'=>$user->id));
4019 // brute force unenrol from all courses
4020 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
4022 // purge user preferences
4023 $DB->delete_records('user_preferences', array('userid'=>$user->id));
4025 // purge user extra profile info
4026 $DB->delete_records('user_info_data', array('userid'=>$user->id));
4028 // last course access not necessary either
4029 $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
4031 // remove all user tokens
4032 $DB->delete_records('external_tokens', array('userid'=>$user->id));
4034 // unauthorise the user for all services
4035 $DB->delete_records('external_services_users', array('userid'=>$user->id));
4037 // force logout - may fail if file based sessions used, sorry
4038 session_kill_user($user->id);
4040 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
4041 delete_context(CONTEXT_USER, $user->id);
4043 // workaround for bulk deletes of users with the same email address
4044 $delname = "$user->email.".time();
4045 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
4046 $delname++;
4049 // mark internal user record as "deleted"
4050 $updateuser = new stdClass();
4051 $updateuser->id = $user->id;
4052 $updateuser->deleted = 1;
4053 $updateuser->username = $delname; // Remember it just in case
4054 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
4055 $updateuser->idnumber = ''; // Clear this field to free it up
4056 $updateuser->picture = 0;
4057 $updateuser->timemodified = time();
4059 $DB->update_record('user', $updateuser);
4060 // Add this action to log
4061 add_to_log(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
4064 // We will update the user's timemodified, as it will be passed to the user_deleted event, which
4065 // should know about this updated property persisted to the user's table.
4066 $user->timemodified = $updateuser->timemodified;
4068 // notify auth plugin - do not block the delete even when plugin fails
4069 $authplugin = get_auth_plugin($user->auth);
4070 $authplugin->user_delete($user);
4072 // any plugin that needs to cleanup should register this event
4073 events_trigger('user_deleted', $user);
4075 return true;
4079 * Retrieve the guest user object
4081 * @global object
4082 * @global object
4083 * @return user A {@link $USER} object
4085 function guest_user() {
4086 global $CFG, $DB;
4088 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
4089 $newuser->confirmed = 1;
4090 $newuser->lang = $CFG->lang;
4091 $newuser->lastip = getremoteaddr();
4094 return $newuser;
4098 * Authenticates a user against the chosen authentication mechanism
4100 * Given a username and password, this function looks them
4101 * up using the currently selected authentication mechanism,
4102 * and if the authentication is successful, it returns a
4103 * valid $user object from the 'user' table.
4105 * Uses auth_ functions from the currently active auth module
4107 * After authenticate_user_login() returns success, you will need to
4108 * log that the user has logged in, and call complete_user_login() to set
4109 * the session up.
4111 * Note: this function works only with non-mnet accounts!
4113 * @param string $username User's username
4114 * @param string $password User's password
4115 * @return user|flase A {@link $USER} object or false if error
4117 function authenticate_user_login($username, $password) {
4118 global $CFG, $DB;
4120 $authsenabled = get_enabled_auth_plugins();
4122 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
4123 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
4124 if (!empty($user->suspended)) {
4125 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4126 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4127 return false;
4129 if ($auth=='nologin' or !is_enabled_auth($auth)) {
4130 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4131 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4132 return false;
4134 $auths = array($auth);
4136 } else {
4137 // check if there's a deleted record (cheaply)
4138 if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
4139 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4140 return false;
4143 // User does not exist
4144 $auths = $authsenabled;
4145 $user = new stdClass();
4146 $user->id = 0;
4149 foreach ($auths as $auth) {
4150 $authplugin = get_auth_plugin($auth);
4152 // on auth fail fall through to the next plugin
4153 if (!$authplugin->user_login($username, $password)) {
4154 continue;
4157 // successful authentication
4158 if ($user->id) { // User already exists in database
4159 if (empty($user->auth)) { // For some reason auth isn't set yet
4160 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
4161 $user->auth = $auth;
4164 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
4166 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
4167 $user = update_user_record($username);
4169 } else {
4170 // if user not found and user creation is not disabled, create it
4171 if (empty($CFG->authpreventaccountcreation)) {
4172 $user = create_user_record($username, $password, $auth);
4173 } else {
4174 continue;
4178 $authplugin->sync_roles($user);
4180 foreach ($authsenabled as $hau) {
4181 $hauth = get_auth_plugin($hau);
4182 $hauth->user_authenticated_hook($user, $username, $password);
4185 if (empty($user->id)) {
4186 return false;
4189 if (!empty($user->suspended)) {
4190 // just in case some auth plugin suspended account
4191 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4192 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4193 return false;
4196 return $user;
4199 // failed if all the plugins have failed
4200 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4201 if (debugging('', DEBUG_ALL)) {
4202 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4204 return false;
4208 * Call to complete the user login process after authenticate_user_login()
4209 * has succeeded. It will setup the $USER variable and other required bits
4210 * and pieces.
4212 * NOTE:
4213 * - It will NOT log anything -- up to the caller to decide what to log.
4214 * - this function does not set any cookies any more!
4216 * @param object $user
4217 * @return object A {@link $USER} object - BC only, do not use
4219 function complete_user_login($user) {
4220 global $CFG, $USER;
4222 // regenerate session id and delete old session,
4223 // this helps prevent session fixation attacks from the same domain
4224 session_regenerate_id(true);
4226 // let enrol plugins deal with new enrolments if necessary
4227 enrol_check_plugins($user);
4229 // check enrolments, load caps and setup $USER object
4230 session_set_user($user);
4232 // reload preferences from DB
4233 unset($USER->preference);
4234 check_user_preferences_loaded($USER);
4236 // update login times
4237 update_user_login_times();
4239 // extra session prefs init
4240 set_login_session_preferences();
4242 if (isguestuser()) {
4243 // no need to continue when user is THE guest
4244 return $USER;
4247 /// Select password change url
4248 $userauth = get_auth_plugin($USER->auth);
4250 /// check whether the user should be changing password
4251 if (get_user_preferences('auth_forcepasswordchange', false)){
4252 if ($userauth->can_change_password()) {
4253 if ($changeurl = $userauth->change_password_url()) {
4254 redirect($changeurl);
4255 } else {
4256 redirect($CFG->httpswwwroot.'/login/change_password.php');
4258 } else {
4259 print_error('nopasswordchangeforced', 'auth');
4262 return $USER;
4266 * Compare password against hash stored in internal user table.
4267 * If necessary it also updates the stored hash to new format.
4269 * @param stdClass $user (password property may be updated)
4270 * @param string $password plain text password
4271 * @return bool is password valid?
4273 function validate_internal_user_password($user, $password) {
4274 global $CFG;
4276 if (!isset($CFG->passwordsaltmain)) {
4277 $CFG->passwordsaltmain = '';
4280 $validated = false;
4282 if ($user->password === 'not cached') {
4283 // internal password is not used at all, it can not validate
4285 } else if ($user->password === md5($password.$CFG->passwordsaltmain)
4286 or $user->password === md5($password)
4287 or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
4288 or $user->password === md5(addslashes($password))) {
4289 // note: we are intentionally using the addslashes() here because we
4290 // need to accept old password hashes of passwords with magic quotes
4291 $validated = true;
4293 } else {
4294 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
4295 $alt = 'passwordsaltalt'.$i;
4296 if (!empty($CFG->$alt)) {
4297 if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
4298 $validated = true;
4299 break;
4305 if ($validated) {
4306 // force update of password hash using latest main password salt and encoding if needed
4307 update_internal_user_password($user, $password);
4310 return $validated;
4314 * Calculate hashed value from password using current hash mechanism.
4316 * @param string $password
4317 * @return string password hash
4319 function hash_internal_user_password($password) {
4320 global $CFG;
4322 if (isset($CFG->passwordsaltmain)) {
4323 return md5($password.$CFG->passwordsaltmain);
4324 } else {
4325 return md5($password);
4330 * Update password hash in user object.
4332 * @param stdClass $user (password property may be updated)
4333 * @param string $password plain text password
4334 * @return bool always returns true
4336 function update_internal_user_password($user, $password) {
4337 global $DB;
4339 $authplugin = get_auth_plugin($user->auth);
4340 if ($authplugin->prevent_local_passwords()) {
4341 $hashedpassword = 'not cached';
4342 } else {
4343 $hashedpassword = hash_internal_user_password($password);
4346 if ($user->password !== $hashedpassword) {
4347 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
4348 $user->password = $hashedpassword;
4351 return true;
4355 * Get a complete user record, which includes all the info
4356 * in the user record.
4358 * Intended for setting as $USER session variable
4360 * @param string $field The user field to be checked for a given value.
4361 * @param string $value The value to match for $field.
4362 * @param int $mnethostid
4363 * @return mixed False, or A {@link $USER} object.
4365 function get_complete_user_data($field, $value, $mnethostid = null) {
4366 global $CFG, $DB;
4368 if (!$field || !$value) {
4369 return false;
4372 /// Build the WHERE clause for an SQL query
4373 $params = array('fieldval'=>$value);
4374 $constraints = "$field = :fieldval AND deleted <> 1";
4376 // If we are loading user data based on anything other than id,
4377 // we must also restrict our search based on mnet host.
4378 if ($field != 'id') {
4379 if (empty($mnethostid)) {
4380 // if empty, we restrict to local users
4381 $mnethostid = $CFG->mnet_localhost_id;
4384 if (!empty($mnethostid)) {
4385 $params['mnethostid'] = $mnethostid;
4386 $constraints .= " AND mnethostid = :mnethostid";
4389 /// Get all the basic user data
4391 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
4392 return false;
4395 /// Get various settings and preferences
4397 // preload preference cache
4398 check_user_preferences_loaded($user);
4400 // load course enrolment related stuff
4401 $user->lastcourseaccess = array(); // during last session
4402 $user->currentcourseaccess = array(); // during current session
4403 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
4404 foreach ($lastaccesses as $lastaccess) {
4405 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
4409 $sql = "SELECT g.id, g.courseid
4410 FROM {groups} g, {groups_members} gm
4411 WHERE gm.groupid=g.id AND gm.userid=?";
4413 // this is a special hack to speedup calendar display
4414 $user->groupmember = array();
4415 if (!isguestuser($user)) {
4416 if ($groups = $DB->get_records_sql($sql, array($user->id))) {
4417 foreach ($groups as $group) {
4418 if (!array_key_exists($group->courseid, $user->groupmember)) {
4419 $user->groupmember[$group->courseid] = array();
4421 $user->groupmember[$group->courseid][$group->id] = $group->id;
4426 /// Add the custom profile fields to the user record
4427 $user->profile = array();
4428 if (!isguestuser($user)) {
4429 require_once($CFG->dirroot.'/user/profile/lib.php');
4430 profile_load_custom_fields($user);
4433 /// Rewrite some variables if necessary
4434 if (!empty($user->description)) {
4435 $user->description = true; // No need to cart all of it around
4437 if (isguestuser($user)) {
4438 $user->lang = $CFG->lang; // Guest language always same as site
4439 $user->firstname = get_string('guestuser'); // Name always in current language
4440 $user->lastname = ' ';
4443 return $user;
4447 * Validate a password against the configured password policy
4449 * @global object
4450 * @param string $password the password to be checked against the password policy
4451 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
4452 * @return bool true if the password is valid according to the policy. false otherwise.
4454 function check_password_policy($password, &$errmsg) {
4455 global $CFG;
4457 if (empty($CFG->passwordpolicy)) {
4458 return true;
4461 $errmsg = '';
4462 if (textlib::strlen($password) < $CFG->minpasswordlength) {
4463 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
4466 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
4467 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
4470 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
4471 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
4474 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
4475 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
4478 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
4479 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
4481 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
4482 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
4485 if ($errmsg == '') {
4486 return true;
4487 } else {
4488 return false;
4494 * When logging in, this function is run to set certain preferences
4495 * for the current SESSION
4497 * @global object
4498 * @global object
4500 function set_login_session_preferences() {
4501 global $SESSION, $CFG;
4503 $SESSION->justloggedin = true;
4505 unset($SESSION->lang);
4510 * Delete a course, including all related data from the database,
4511 * and any associated files.
4513 * @global object
4514 * @global object
4515 * @param mixed $courseorid The id of the course or course object to delete.
4516 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4517 * @return bool true if all the removals succeeded. false if there were any failures. If this
4518 * method returns false, some of the removals will probably have succeeded, and others
4519 * failed, but you have no way of knowing which.
4521 function delete_course($courseorid, $showfeedback = true) {
4522 global $DB;
4524 if (is_object($courseorid)) {
4525 $courseid = $courseorid->id;
4526 $course = $courseorid;
4527 } else {
4528 $courseid = $courseorid;
4529 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
4530 return false;
4533 $context = get_context_instance(CONTEXT_COURSE, $courseid);
4535 // frontpage course can not be deleted!!
4536 if ($courseid == SITEID) {
4537 return false;
4540 // make the course completely empty
4541 remove_course_contents($courseid, $showfeedback);
4543 // delete the course and related context instance
4544 delete_context(CONTEXT_COURSE, $courseid);
4546 // We will update the course's timemodified, as it will be passed to the course_deleted event,
4547 // which should know about this updated property, as this event is meant to pass the full course record
4548 $course->timemodified = time();
4550 $DB->delete_records("course", array("id"=>$courseid));
4552 //trigger events
4553 $course->context = $context; // you can not fetch context in the event because it was already deleted
4554 events_trigger('course_deleted', $course);
4556 return true;
4560 * Clear a course out completely, deleting all content
4561 * but don't delete the course itself.
4562 * This function does not verify any permissions.
4564 * Please note this function also deletes all user enrolments,
4565 * enrolment instances and role assignments by default.
4567 * $options:
4568 * - 'keep_roles_and_enrolments' - false by default
4569 * - 'keep_groups_and_groupings' - false by default
4571 * @param int $courseid The id of the course that is being deleted
4572 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4573 * @param array $options extra options
4574 * @return bool true if all the removals succeeded. false if there were any failures. If this
4575 * method returns false, some of the removals will probably have succeeded, and others
4576 * failed, but you have no way of knowing which.
4578 function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
4579 global $CFG, $DB, $OUTPUT;
4580 require_once($CFG->libdir.'/completionlib.php');
4581 require_once($CFG->libdir.'/questionlib.php');
4582 require_once($CFG->libdir.'/gradelib.php');
4583 require_once($CFG->dirroot.'/group/lib.php');
4584 require_once($CFG->dirroot.'/tag/coursetagslib.php');
4585 require_once($CFG->dirroot.'/comment/lib.php');
4586 require_once($CFG->dirroot.'/rating/lib.php');
4588 // NOTE: these concatenated strings are suboptimal, but it is just extra info...
4589 $strdeleted = get_string('deleted').' - ';
4591 // Some crazy wishlist of stuff we should skip during purging of course content
4592 $options = (array)$options;
4594 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
4595 $coursecontext = context_course::instance($courseid);
4596 $fs = get_file_storage();
4598 // Delete course completion information, this has to be done before grades and enrols
4599 $cc = new completion_info($course);
4600 $cc->clear_criteria();
4601 if ($showfeedback) {
4602 echo $OUTPUT->notification($strdeleted.get_string('completion', 'completion'), 'notifysuccess');
4605 // Remove all data from gradebook - this needs to be done before course modules
4606 // because while deleting this information, the system may need to reference
4607 // the course modules that own the grades.
4608 remove_course_grades($courseid, $showfeedback);
4609 remove_grade_letters($coursecontext, $showfeedback);
4611 // Delete course blocks in any all child contexts,
4612 // they may depend on modules so delete them first
4613 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4614 foreach ($childcontexts as $childcontext) {
4615 blocks_delete_all_for_context($childcontext->id);
4617 unset($childcontexts);
4618 blocks_delete_all_for_context($coursecontext->id);
4619 if ($showfeedback) {
4620 echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
4623 // Delete every instance of every module,
4624 // this has to be done before deleting of course level stuff
4625 $locations = get_plugin_list('mod');
4626 foreach ($locations as $modname=>$moddir) {
4627 if ($modname === 'NEWMODULE') {
4628 continue;
4630 if ($module = $DB->get_record('modules', array('name'=>$modname))) {
4631 include_once("$moddir/lib.php"); // Shows php warning only if plugin defective
4632 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4633 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4635 if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
4636 foreach ($instances as $instance) {
4637 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
4638 /// Delete activity context questions and question categories
4639 question_delete_activity($cm, $showfeedback);
4641 if (function_exists($moddelete)) {
4642 // This purges all module data in related tables, extra user prefs, settings, etc.
4643 $moddelete($instance->id);
4644 } else {
4645 // NOTE: we should not allow installation of modules with missing delete support!
4646 debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
4647 $DB->delete_records($modname, array('id'=>$instance->id));
4650 if ($cm) {
4651 // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition
4652 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4653 $DB->delete_records('course_modules', array('id'=>$cm->id));
4657 if (function_exists($moddeletecourse)) {
4658 // Execute ptional course cleanup callback
4659 $moddeletecourse($course, $showfeedback);
4661 if ($instances and $showfeedback) {
4662 echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
4664 } else {
4665 // Ooops, this module is not properly installed, force-delete it in the next block
4669 // We have tried to delete everything the nice way - now let's force-delete any remaining module data
4671 // Remove all data from availability and completion tables that is associated
4672 // with course-modules belonging to this course. Note this is done even if the
4673 // features are not enabled now, in case they were enabled previously.
4674 $DB->delete_records_select('course_modules_completion',
4675 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4676 array($courseid));
4677 $DB->delete_records_select('course_modules_availability',
4678 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4679 array($courseid));
4681 // Remove course-module data.
4682 $cms = $DB->get_records('course_modules', array('course'=>$course->id));
4683 foreach ($cms as $cm) {
4684 if ($module = $DB->get_record('modules', array('id'=>$cm->module))) {
4685 try {
4686 $DB->delete_records($module->name, array('id'=>$cm->instance));
4687 } catch (Exception $e) {
4688 // Ignore weird or missing table problems
4691 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4692 $DB->delete_records('course_modules', array('id'=>$cm->id));
4695 if ($showfeedback) {
4696 echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
4699 // Cleanup the rest of plugins
4700 $cleanuplugintypes = array('report', 'coursereport', 'format');
4701 foreach ($cleanuplugintypes as $type) {
4702 $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
4703 foreach ($plugins as $plugin=>$pluginfunction) {
4704 $pluginfunction($course->id, $showfeedback);
4706 if ($showfeedback) {
4707 echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
4711 // Delete questions and question categories
4712 question_delete_course($course, $showfeedback);
4713 if ($showfeedback) {
4714 echo $OUTPUT->notification($strdeleted.get_string('questions', 'question'), 'notifysuccess');
4717 // Make sure there are no subcontexts left - all valid blocks and modules should be already gone
4718 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4719 foreach ($childcontexts as $childcontext) {
4720 $childcontext->delete();
4722 unset($childcontexts);
4724 // Remove all roles and enrolments by default
4725 if (empty($options['keep_roles_and_enrolments'])) {
4726 // this hack is used in restore when deleting contents of existing course
4727 role_unassign_all(array('contextid'=>$coursecontext->id, 'component'=>''), true);
4728 enrol_course_delete($course);
4729 if ($showfeedback) {
4730 echo $OUTPUT->notification($strdeleted.get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
4734 // Delete any groups, removing members and grouping/course links first.
4735 if (empty($options['keep_groups_and_groupings'])) {
4736 groups_delete_groupings($course->id, $showfeedback);
4737 groups_delete_groups($course->id, $showfeedback);
4740 // filters be gone!
4741 filter_delete_all_for_context($coursecontext->id);
4743 // die comments!
4744 comment::delete_comments($coursecontext->id);
4746 // ratings are history too
4747 $delopt = new stdclass();
4748 $delopt->contextid = $coursecontext->id;
4749 $rm = new rating_manager();
4750 $rm->delete_ratings($delopt);
4752 // Delete course tags
4753 coursetag_delete_course_tags($course->id, $showfeedback);
4755 // Delete calendar events
4756 $DB->delete_records('event', array('courseid'=>$course->id));
4757 $fs->delete_area_files($coursecontext->id, 'calendar');
4759 // Delete all related records in other core tables that may have a courseid
4760 // This array stores the tables that need to be cleared, as
4761 // table_name => column_name that contains the course id.
4762 $tablestoclear = array(
4763 'log' => 'course', // Course logs (NOTE: this might be changed in the future)
4764 'backup_courses' => 'courseid', // Scheduled backup stuff
4765 'user_lastaccess' => 'courseid', // User access info
4767 foreach ($tablestoclear as $table => $col) {
4768 $DB->delete_records($table, array($col=>$course->id));
4771 // delete all course backup files
4772 $fs->delete_area_files($coursecontext->id, 'backup');
4774 // cleanup course record - remove links to deleted stuff
4775 $oldcourse = new stdClass();
4776 $oldcourse->id = $course->id;
4777 $oldcourse->summary = '';
4778 $oldcourse->modinfo = NULL;
4779 $oldcourse->legacyfiles = 0;
4780 $oldcourse->enablecompletion = 0;
4781 if (!empty($options['keep_groups_and_groupings'])) {
4782 $oldcourse->defaultgroupingid = 0;
4784 $DB->update_record('course', $oldcourse);
4786 // Delete course sections and availability options.
4787 $DB->delete_records_select('course_sections_availability',
4788 'coursesectionid IN (SELECT id from {course_sections} WHERE course=?)',
4789 array($course->id));
4790 $DB->delete_records('course_sections', array('course'=>$course->id));
4792 // delete legacy, section and any other course files
4793 $fs->delete_area_files($coursecontext->id, 'course'); // files from summary and section
4795 // Delete all remaining stuff linked to context such as files, comments, ratings, etc.
4796 if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
4797 // Easy, do not delete the context itself...
4798 $coursecontext->delete_content();
4800 } else {
4801 // Hack alert!!!!
4802 // We can not drop all context stuff because it would bork enrolments and roles,
4803 // there might be also files used by enrol plugins...
4806 // Delete legacy files - just in case some files are still left there after conversion to new file api,
4807 // also some non-standard unsupported plugins may try to store something there
4808 fulldelete($CFG->dataroot.'/'.$course->id);
4810 // Finally trigger the event
4811 $course->context = $coursecontext; // you can not access context in cron event later after course is deleted
4812 $course->options = $options; // not empty if we used any crazy hack
4813 events_trigger('course_content_removed', $course);
4815 return true;
4819 * Change dates in module - used from course reset.
4821 * @global object
4822 * @global object
4823 * @param string $modname forum, assignment, etc
4824 * @param array $fields array of date fields from mod table
4825 * @param int $timeshift time difference
4826 * @param int $courseid
4827 * @return bool success
4829 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4830 global $CFG, $DB;
4831 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
4833 $return = true;
4834 foreach ($fields as $field) {
4835 $updatesql = "UPDATE {".$modname."}
4836 SET $field = $field + ?
4837 WHERE course=? AND $field<>0";
4838 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4841 $refreshfunction = $modname.'_refresh_events';
4842 if (function_exists($refreshfunction)) {
4843 $refreshfunction($courseid);
4846 return $return;
4850 * This function will empty a course of user data.
4851 * It will retain the activities and the structure of the course.
4853 * @param object $data an object containing all the settings including courseid (without magic quotes)
4854 * @return array status array of array component, item, error
4856 function reset_course_userdata($data) {
4857 global $CFG, $USER, $DB;
4858 require_once($CFG->libdir.'/gradelib.php');
4859 require_once($CFG->libdir.'/completionlib.php');
4860 require_once($CFG->dirroot.'/group/lib.php');
4862 $data->courseid = $data->id;
4863 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
4865 // calculate the time shift of dates
4866 if (!empty($data->reset_start_date)) {
4867 // time part of course startdate should be zero
4868 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
4869 } else {
4870 $data->timeshift = 0;
4873 // result array: component, item, error
4874 $status = array();
4876 // start the resetting
4877 $componentstr = get_string('general');
4879 // move the course start time
4880 if (!empty($data->reset_start_date) and $data->timeshift) {
4881 // change course start data
4882 $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
4883 // update all course and group events - do not move activity events
4884 $updatesql = "UPDATE {event}
4885 SET timestart = timestart + ?
4886 WHERE courseid=? AND instance=0";
4887 $DB->execute($updatesql, array($data->timeshift, $data->courseid));
4889 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4892 if (!empty($data->reset_logs)) {
4893 $DB->delete_records('log', array('course'=>$data->courseid));
4894 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4897 if (!empty($data->reset_events)) {
4898 $DB->delete_records('event', array('courseid'=>$data->courseid));
4899 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4902 if (!empty($data->reset_notes)) {
4903 require_once($CFG->dirroot.'/notes/lib.php');
4904 note_delete_all($data->courseid);
4905 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4908 if (!empty($data->delete_blog_associations)) {
4909 require_once($CFG->dirroot.'/blog/lib.php');
4910 blog_remove_associations_for_course($data->courseid);
4911 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4914 if (!empty($data->reset_completion)) {
4915 // Delete course and activity completion information.
4916 $course = $DB->get_record('course', array('id'=>$data->courseid));
4917 $cc = new completion_info($course);
4918 $cc->delete_all_completion_data();
4919 $status[] = array('component' => $componentstr,
4920 'item' => get_string('deletecompletiondata', 'completion'), 'error' => false);
4923 $componentstr = get_string('roles');
4925 if (!empty($data->reset_roles_overrides)) {
4926 $children = get_child_contexts($context);
4927 foreach ($children as $child) {
4928 $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
4930 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
4931 //force refresh for logged in users
4932 mark_context_dirty($context->path);
4933 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4936 if (!empty($data->reset_roles_local)) {
4937 $children = get_child_contexts($context);
4938 foreach ($children as $child) {
4939 role_unassign_all(array('contextid'=>$child->id));
4941 //force refresh for logged in users
4942 mark_context_dirty($context->path);
4943 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4946 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4947 $data->unenrolled = array();
4948 if (!empty($data->unenrol_users)) {
4949 $plugins = enrol_get_plugins(true);
4950 $instances = enrol_get_instances($data->courseid, true);
4951 foreach ($instances as $key=>$instance) {
4952 if (!isset($plugins[$instance->enrol])) {
4953 unset($instances[$key]);
4954 continue;
4958 foreach($data->unenrol_users as $withroleid) {
4959 if ($withroleid) {
4960 $sql = "SELECT ue.*
4961 FROM {user_enrolments} ue
4962 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4963 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4964 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4965 $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
4967 } else {
4968 // without any role assigned at course context
4969 $sql = "SELECT ue.*
4970 FROM {user_enrolments} ue
4971 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4972 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4973 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)
4974 WHERE ra.id IS NULL";
4975 $params = array('courseid'=>$data->courseid, 'courselevel'=>CONTEXT_COURSE);
4978 $rs = $DB->get_recordset_sql($sql, $params);
4979 foreach ($rs as $ue) {
4980 if (!isset($instances[$ue->enrolid])) {
4981 continue;
4983 $instance = $instances[$ue->enrolid];
4984 $plugin = $plugins[$instance->enrol];
4985 if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
4986 continue;
4989 $plugin->unenrol_user($instance, $ue->userid);
4990 $data->unenrolled[$ue->userid] = $ue->userid;
4992 $rs->close();
4995 if (!empty($data->unenrolled)) {
4996 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
5000 $componentstr = get_string('groups');
5002 // remove all group members
5003 if (!empty($data->reset_groups_members)) {
5004 groups_delete_group_members($data->courseid);
5005 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
5008 // remove all groups
5009 if (!empty($data->reset_groups_remove)) {
5010 groups_delete_groups($data->courseid, false);
5011 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
5014 // remove all grouping members
5015 if (!empty($data->reset_groupings_members)) {
5016 groups_delete_groupings_groups($data->courseid, false);
5017 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
5020 // remove all groupings
5021 if (!empty($data->reset_groupings_remove)) {
5022 groups_delete_groupings($data->courseid, false);
5023 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
5026 // Look in every instance of every module for data to delete
5027 $unsupported_mods = array();
5028 if ($allmods = $DB->get_records('modules') ) {
5029 foreach ($allmods as $mod) {
5030 $modname = $mod->name;
5031 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
5032 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
5033 if (file_exists($modfile)) {
5034 if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
5035 continue; // Skip mods with no instances
5037 include_once($modfile);
5038 if (function_exists($moddeleteuserdata)) {
5039 $modstatus = $moddeleteuserdata($data);
5040 if (is_array($modstatus)) {
5041 $status = array_merge($status, $modstatus);
5042 } else {
5043 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
5045 } else {
5046 $unsupported_mods[] = $mod;
5048 } else {
5049 debugging('Missing lib.php in '.$modname.' module!');
5054 // mention unsupported mods
5055 if (!empty($unsupported_mods)) {
5056 foreach($unsupported_mods as $mod) {
5057 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
5062 $componentstr = get_string('gradebook', 'grades');
5063 // reset gradebook
5064 if (!empty($data->reset_gradebook_items)) {
5065 remove_course_grades($data->courseid, false);
5066 grade_grab_course_grades($data->courseid);
5067 grade_regrade_final_grades($data->courseid);
5068 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
5070 } else if (!empty($data->reset_gradebook_grades)) {
5071 grade_course_reset($data->courseid);
5072 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
5074 // reset comments
5075 if (!empty($data->reset_comments)) {
5076 require_once($CFG->dirroot.'/comment/lib.php');
5077 comment::reset_course_page_comments($context);
5080 return $status;
5084 * Generate an email processing address
5086 * @param int $modid
5087 * @param string $modargs
5088 * @return string Returns email processing address
5090 function generate_email_processing_address($modid,$modargs) {
5091 global $CFG;
5093 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
5094 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
5100 * @todo Finish documenting this function
5102 * @global object
5103 * @param string $modargs
5104 * @param string $body Currently unused
5106 function moodle_process_email($modargs,$body) {
5107 global $DB;
5109 // the first char should be an unencoded letter. We'll take this as an action
5110 switch ($modargs{0}) {
5111 case 'B': { // bounce
5112 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
5113 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
5114 // check the half md5 of their email
5115 $md5check = substr(md5($user->email),0,16);
5116 if ($md5check == substr($modargs, -16)) {
5117 set_bounce_count($user);
5119 // else maybe they've already changed it?
5122 break;
5123 // maybe more later?
5127 /// CORRESPONDENCE ////////////////////////////////////////////////
5130 * Get mailer instance, enable buffering, flush buffer or disable buffering.
5132 * @param string $action 'get', 'buffer', 'close' or 'flush'
5133 * @return moodle_phpmailer|null mailer instance if 'get' used or nothing
5135 function get_mailer($action='get') {
5136 global $CFG;
5138 static $mailer = null;
5139 static $counter = 0;
5141 if (!isset($CFG->smtpmaxbulk)) {
5142 $CFG->smtpmaxbulk = 1;
5145 if ($action == 'get') {
5146 $prevkeepalive = false;
5148 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5149 if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
5150 $counter++;
5151 // reset the mailer
5152 $mailer->Priority = 3;
5153 $mailer->CharSet = 'UTF-8'; // our default
5154 $mailer->ContentType = "text/plain";
5155 $mailer->Encoding = "8bit";
5156 $mailer->From = "root@localhost";
5157 $mailer->FromName = "Root User";
5158 $mailer->Sender = "";
5159 $mailer->Subject = "";
5160 $mailer->Body = "";
5161 $mailer->AltBody = "";
5162 $mailer->ConfirmReadingTo = "";
5164 $mailer->ClearAllRecipients();
5165 $mailer->ClearReplyTos();
5166 $mailer->ClearAttachments();
5167 $mailer->ClearCustomHeaders();
5168 return $mailer;
5171 $prevkeepalive = $mailer->SMTPKeepAlive;
5172 get_mailer('flush');
5175 include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
5176 $mailer = new moodle_phpmailer();
5178 $counter = 1;
5180 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
5181 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
5182 $mailer->CharSet = 'UTF-8';
5184 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
5185 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
5186 $mailer->LE = "\r\n";
5187 } else {
5188 $mailer->LE = "\n";
5191 if ($CFG->smtphosts == 'qmail') {
5192 $mailer->IsQmail(); // use Qmail system
5194 } else if (empty($CFG->smtphosts)) {
5195 $mailer->IsMail(); // use PHP mail() = sendmail
5197 } else {
5198 $mailer->IsSMTP(); // use SMTP directly
5199 if (!empty($CFG->debugsmtp)) {
5200 $mailer->SMTPDebug = true;
5202 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
5203 $mailer->SMTPSecure = $CFG->smtpsecure; // specify secure connection protocol
5204 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
5206 if ($CFG->smtpuser) { // Use SMTP authentication
5207 $mailer->SMTPAuth = true;
5208 $mailer->Username = $CFG->smtpuser;
5209 $mailer->Password = $CFG->smtppass;
5213 return $mailer;
5216 $nothing = null;
5218 // keep smtp session open after sending
5219 if ($action == 'buffer') {
5220 if (!empty($CFG->smtpmaxbulk)) {
5221 get_mailer('flush');
5222 $m = get_mailer();
5223 if ($m->Mailer == 'smtp') {
5224 $m->SMTPKeepAlive = true;
5227 return $nothing;
5230 // close smtp session, but continue buffering
5231 if ($action == 'flush') {
5232 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5233 if (!empty($mailer->SMTPDebug)) {
5234 echo '<pre>'."\n";
5236 $mailer->SmtpClose();
5237 if (!empty($mailer->SMTPDebug)) {
5238 echo '</pre>';
5241 return $nothing;
5244 // close smtp session, do not buffer anymore
5245 if ($action == 'close') {
5246 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5247 get_mailer('flush');
5248 $mailer->SMTPKeepAlive = false;
5250 $mailer = null; // better force new instance
5251 return $nothing;
5256 * Send an email to a specified user
5258 * @global object
5259 * @global string
5260 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
5261 * @uses SITEID
5262 * @param stdClass $user A {@link $USER} object
5263 * @param stdClass $from A {@link $USER} object
5264 * @param string $subject plain text subject line of the email
5265 * @param string $messagetext plain text version of the message
5266 * @param string $messagehtml complete html version of the message (optional)
5267 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
5268 * @param string $attachname the name of the file (extension indicates MIME)
5269 * @param bool $usetrueaddress determines whether $from email address should
5270 * be sent out. Will be overruled by user profile setting for maildisplay
5271 * @param string $replyto Email address to reply to
5272 * @param string $replytoname Name of reply to recipient
5273 * @param int $wordwrapwidth custom word wrap width, default 79
5274 * @return bool Returns true if mail was sent OK and false if there was an error.
5276 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
5278 global $CFG;
5280 if (empty($user) || empty($user->email)) {
5281 $nulluser = 'User is null or has no email';
5282 error_log($nulluser);
5283 if (CLI_SCRIPT) {
5284 mtrace('Error: lib/moodlelib.php email_to_user(): '.$nulluser);
5286 return false;
5289 if (!empty($user->deleted)) {
5290 // do not mail deleted users
5291 $userdeleted = 'User is deleted';
5292 error_log($userdeleted);
5293 if (CLI_SCRIPT) {
5294 mtrace('Error: lib/moodlelib.php email_to_user(): '.$userdeleted);
5296 return false;
5299 if (!empty($CFG->noemailever)) {
5300 // hidden setting for development sites, set in config.php if needed
5301 $noemail = 'Not sending email due to noemailever config setting';
5302 error_log($noemail);
5303 if (CLI_SCRIPT) {
5304 mtrace('Error: lib/moodlelib.php email_to_user(): '.$noemail);
5306 return true;
5309 if (!empty($CFG->divertallemailsto)) {
5310 $subject = "[DIVERTED {$user->email}] $subject";
5311 $user = clone($user);
5312 $user->email = $CFG->divertallemailsto;
5315 // skip mail to suspended users
5316 if ((isset($user->auth) && $user->auth=='nologin') or (isset($user->suspended) && $user->suspended)) {
5317 return true;
5320 if (!validate_email($user->email)) {
5321 // we can not send emails to invalid addresses - it might create security issue or confuse the mailer
5322 $invalidemail = "User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.";
5323 error_log($invalidemail);
5324 if (CLI_SCRIPT) {
5325 mtrace('Error: lib/moodlelib.php email_to_user(): '.$invalidemail);
5327 return false;
5330 if (over_bounce_threshold($user)) {
5331 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
5332 error_log($bouncemsg);
5333 if (CLI_SCRIPT) {
5334 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
5336 return false;
5339 // If the user is a remote mnet user, parse the email text for URL to the
5340 // wwwroot and modify the url to direct the user's browser to login at their
5341 // home site (identity provider - idp) before hitting the link itself
5342 if (is_mnet_remote_user($user)) {
5343 require_once($CFG->dirroot.'/mnet/lib.php');
5345 $jumpurl = mnet_get_idp_jump_url($user);
5346 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
5348 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
5349 $callback,
5350 $messagetext);
5351 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
5352 $callback,
5353 $messagehtml);
5355 $mail = get_mailer();
5357 if (!empty($mail->SMTPDebug)) {
5358 echo '<pre>' . "\n";
5361 $temprecipients = array();
5362 $tempreplyto = array();
5364 $supportuser = generate_email_supportuser();
5366 // make up an email address for handling bounces
5367 if (!empty($CFG->handlebounces)) {
5368 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
5369 $mail->Sender = generate_email_processing_address(0,$modargs);
5370 } else {
5371 $mail->Sender = $supportuser->email;
5374 if (is_string($from)) { // So we can pass whatever we want if there is need
5375 $mail->From = $CFG->noreplyaddress;
5376 $mail->FromName = $from;
5377 } else if ($usetrueaddress and $from->maildisplay) {
5378 $mail->From = $from->email;
5379 $mail->FromName = fullname($from);
5380 } else {
5381 $mail->From = $CFG->noreplyaddress;
5382 $mail->FromName = fullname($from);
5383 if (empty($replyto)) {
5384 $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
5388 if (!empty($replyto)) {
5389 $tempreplyto[] = array($replyto, $replytoname);
5392 $mail->Subject = substr($subject, 0, 900);
5394 $temprecipients[] = array($user->email, fullname($user));
5396 $mail->WordWrap = $wordwrapwidth; // set word wrap
5398 if (!empty($from->customheaders)) { // Add custom headers
5399 if (is_array($from->customheaders)) {
5400 foreach ($from->customheaders as $customheader) {
5401 $mail->AddCustomHeader($customheader);
5403 } else {
5404 $mail->AddCustomHeader($from->customheaders);
5408 if (!empty($from->priority)) {
5409 $mail->Priority = $from->priority;
5412 if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
5413 $mail->IsHTML(true);
5414 $mail->Encoding = 'quoted-printable'; // Encoding to use
5415 $mail->Body = $messagehtml;
5416 $mail->AltBody = "\n$messagetext\n";
5417 } else {
5418 $mail->IsHTML(false);
5419 $mail->Body = "\n$messagetext\n";
5422 if ($attachment && $attachname) {
5423 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
5424 $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
5425 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
5426 } else {
5427 require_once($CFG->libdir.'/filelib.php');
5428 $mimetype = mimeinfo('type', $attachname);
5429 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
5433 // Check if the email should be sent in an other charset then the default UTF-8
5434 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
5436 // use the defined site mail charset or eventually the one preferred by the recipient
5437 $charset = $CFG->sitemailcharset;
5438 if (!empty($CFG->allowusermailcharset)) {
5439 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
5440 $charset = $useremailcharset;
5444 // convert all the necessary strings if the charset is supported
5445 $charsets = get_list_of_charsets();
5446 unset($charsets['UTF-8']);
5447 if (in_array($charset, $charsets)) {
5448 $mail->CharSet = $charset;
5449 $mail->FromName = textlib::convert($mail->FromName, 'utf-8', strtolower($charset));
5450 $mail->Subject = textlib::convert($mail->Subject, 'utf-8', strtolower($charset));
5451 $mail->Body = textlib::convert($mail->Body, 'utf-8', strtolower($charset));
5452 $mail->AltBody = textlib::convert($mail->AltBody, 'utf-8', strtolower($charset));
5454 foreach ($temprecipients as $key => $values) {
5455 $temprecipients[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5457 foreach ($tempreplyto as $key => $values) {
5458 $tempreplyto[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5463 foreach ($temprecipients as $values) {
5464 $mail->AddAddress($values[0], $values[1]);
5466 foreach ($tempreplyto as $values) {
5467 $mail->AddReplyTo($values[0], $values[1]);
5470 if ($mail->Send()) {
5471 set_send_count($user);
5472 if (!empty($mail->SMTPDebug)) {
5473 echo '</pre>';
5475 return true;
5476 } else {
5477 add_to_log(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: '. $mail->ErrorInfo);
5478 if (CLI_SCRIPT) {
5479 mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
5481 if (!empty($mail->SMTPDebug)) {
5482 echo '</pre>';
5484 return false;
5489 * Generate a signoff for emails based on support settings
5491 * @global object
5492 * @return string
5494 function generate_email_signoff() {
5495 global $CFG;
5497 $signoff = "\n";
5498 if (!empty($CFG->supportname)) {
5499 $signoff .= $CFG->supportname."\n";
5501 if (!empty($CFG->supportemail)) {
5502 $signoff .= $CFG->supportemail."\n";
5504 if (!empty($CFG->supportpage)) {
5505 $signoff .= $CFG->supportpage."\n";
5507 return $signoff;
5511 * Generate a fake user for emails based on support settings
5512 * @global object
5513 * @return object user info
5515 function generate_email_supportuser() {
5516 global $CFG;
5518 static $supportuser;
5520 if (!empty($supportuser)) {
5521 return $supportuser;
5524 $supportuser = new stdClass();
5525 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
5526 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
5527 $supportuser->lastname = '';
5528 $supportuser->maildisplay = true;
5530 return $supportuser;
5535 * Sets specified user's password and send the new password to the user via email.
5537 * @global object
5538 * @global object
5539 * @param user $user A {@link $USER} object
5540 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
5542 function setnew_password_and_mail($user) {
5543 global $CFG, $DB;
5545 // we try to send the mail in language the user understands,
5546 // unfortunately the filter_string() does not support alternative langs yet
5547 // so multilang will not work properly for site->fullname
5548 $lang = empty($user->lang) ? $CFG->lang : $user->lang;
5550 $site = get_site();
5552 $supportuser = generate_email_supportuser();
5554 $newpassword = generate_password();
5556 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
5558 $a = new stdClass();
5559 $a->firstname = fullname($user, true);
5560 $a->sitename = format_string($site->fullname);
5561 $a->username = $user->username;
5562 $a->newpassword = $newpassword;
5563 $a->link = $CFG->wwwroot .'/login/';
5564 $a->signoff = generate_email_signoff();
5566 $message = (string)new lang_string('newusernewpasswordtext', '', $a, $lang);
5568 $subject = format_string($site->fullname) .': '. (string)new lang_string('newusernewpasswordsubj', '', $a, $lang);
5570 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5571 return email_to_user($user, $supportuser, $subject, $message);
5576 * Resets specified user's password and send the new password to the user via email.
5578 * @param stdClass $user A {@link $USER} object
5579 * @return bool Returns true if mail was sent OK and false if there was an error.
5581 function reset_password_and_mail($user) {
5582 global $CFG;
5584 $site = get_site();
5585 $supportuser = generate_email_supportuser();
5587 $userauth = get_auth_plugin($user->auth);
5588 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
5589 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
5590 return false;
5593 $newpassword = generate_password();
5595 if (!$userauth->user_update_password($user, $newpassword)) {
5596 print_error("cannotsetpassword");
5599 $a = new stdClass();
5600 $a->firstname = $user->firstname;
5601 $a->lastname = $user->lastname;
5602 $a->sitename = format_string($site->fullname);
5603 $a->username = $user->username;
5604 $a->newpassword = $newpassword;
5605 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
5606 $a->signoff = generate_email_signoff();
5608 $message = get_string('newpasswordtext', '', $a);
5610 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
5612 unset_user_preference('create_password', $user); // prevent cron from generating the password
5614 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5615 return email_to_user($user, $supportuser, $subject, $message);
5620 * Send email to specified user with confirmation text and activation link.
5622 * @global object
5623 * @param user $user A {@link $USER} object
5624 * @return bool Returns true if mail was sent OK and false if there was an error.
5626 function send_confirmation_email($user) {
5627 global $CFG;
5629 $site = get_site();
5630 $supportuser = generate_email_supportuser();
5632 $data = new stdClass();
5633 $data->firstname = fullname($user);
5634 $data->sitename = format_string($site->fullname);
5635 $data->admin = generate_email_signoff();
5637 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
5639 $username = urlencode($user->username);
5640 $username = str_replace('.', '%2E', $username); // prevent problems with trailing dots
5641 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $username;
5642 $message = get_string('emailconfirmation', '', $data);
5643 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
5645 $user->mailformat = 1; // Always send HTML version as well
5647 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5648 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
5653 * send_password_change_confirmation_email.
5655 * @global object
5656 * @param user $user A {@link $USER} object
5657 * @return bool Returns true if mail was sent OK and false if there was an error.
5659 function send_password_change_confirmation_email($user) {
5660 global $CFG;
5662 $site = get_site();
5663 $supportuser = generate_email_supportuser();
5665 $data = new stdClass();
5666 $data->firstname = $user->firstname;
5667 $data->lastname = $user->lastname;
5668 $data->sitename = format_string($site->fullname);
5669 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
5670 $data->admin = generate_email_signoff();
5672 $message = get_string('emailpasswordconfirmation', '', $data);
5673 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
5675 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5676 return email_to_user($user, $supportuser, $subject, $message);
5681 * send_password_change_info.
5683 * @global object
5684 * @param user $user A {@link $USER} object
5685 * @return bool Returns true if mail was sent OK and false if there was an error.
5687 function send_password_change_info($user) {
5688 global $CFG;
5690 $site = get_site();
5691 $supportuser = generate_email_supportuser();
5692 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
5694 $data = new stdClass();
5695 $data->firstname = $user->firstname;
5696 $data->lastname = $user->lastname;
5697 $data->sitename = format_string($site->fullname);
5698 $data->admin = generate_email_signoff();
5700 $userauth = get_auth_plugin($user->auth);
5702 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
5703 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
5704 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5705 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5706 return email_to_user($user, $supportuser, $subject, $message);
5709 if ($userauth->can_change_password() and $userauth->change_password_url()) {
5710 // we have some external url for password changing
5711 $data->link .= $userauth->change_password_url();
5713 } else {
5714 //no way to change password, sorry
5715 $data->link = '';
5718 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
5719 $message = get_string('emailpasswordchangeinfo', '', $data);
5720 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5721 } else {
5722 $message = get_string('emailpasswordchangeinfofail', '', $data);
5723 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5726 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5727 return email_to_user($user, $supportuser, $subject, $message);
5732 * Check that an email is allowed. It returns an error message if there
5733 * was a problem.
5735 * @global object
5736 * @param string $email Content of email
5737 * @return string|false
5739 function email_is_not_allowed($email) {
5740 global $CFG;
5742 if (!empty($CFG->allowemailaddresses)) {
5743 $allowed = explode(' ', $CFG->allowemailaddresses);
5744 foreach ($allowed as $allowedpattern) {
5745 $allowedpattern = trim($allowedpattern);
5746 if (!$allowedpattern) {
5747 continue;
5749 if (strpos($allowedpattern, '.') === 0) {
5750 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5751 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5752 return false;
5755 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5756 return false;
5759 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5761 } else if (!empty($CFG->denyemailaddresses)) {
5762 $denied = explode(' ', $CFG->denyemailaddresses);
5763 foreach ($denied as $deniedpattern) {
5764 $deniedpattern = trim($deniedpattern);
5765 if (!$deniedpattern) {
5766 continue;
5768 if (strpos($deniedpattern, '.') === 0) {
5769 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5770 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5771 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5774 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5775 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5780 return false;
5783 /// FILE HANDLING /////////////////////////////////////////////
5786 * Returns local file storage instance
5788 * @return file_storage
5790 function get_file_storage() {
5791 global $CFG;
5793 static $fs = null;
5795 if ($fs) {
5796 return $fs;
5799 require_once("$CFG->libdir/filelib.php");
5801 if (isset($CFG->filedir)) {
5802 $filedir = $CFG->filedir;
5803 } else {
5804 $filedir = $CFG->dataroot.'/filedir';
5807 if (isset($CFG->trashdir)) {
5808 $trashdirdir = $CFG->trashdir;
5809 } else {
5810 $trashdirdir = $CFG->dataroot.'/trashdir';
5813 $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5815 return $fs;
5819 * Returns local file storage instance
5821 * @return file_browser
5823 function get_file_browser() {
5824 global $CFG;
5826 static $fb = null;
5828 if ($fb) {
5829 return $fb;
5832 require_once("$CFG->libdir/filelib.php");
5834 $fb = new file_browser();
5836 return $fb;
5840 * Returns file packer
5842 * @param string $mimetype default application/zip
5843 * @return file_packer
5845 function get_file_packer($mimetype='application/zip') {
5846 global $CFG;
5848 static $fp = array();;
5850 if (isset($fp[$mimetype])) {
5851 return $fp[$mimetype];
5854 switch ($mimetype) {
5855 case 'application/zip':
5856 case 'application/vnd.moodle.backup':
5857 $classname = 'zip_packer';
5858 break;
5859 case 'application/x-tar':
5860 // $classname = 'tar_packer';
5861 // break;
5862 default:
5863 return false;
5866 require_once("$CFG->libdir/filestorage/$classname.php");
5867 $fp[$mimetype] = new $classname();
5869 return $fp[$mimetype];
5873 * Returns current name of file on disk if it exists.
5875 * @param string $newfile File to be verified
5876 * @return string Current name of file on disk if true
5878 function valid_uploaded_file($newfile) {
5879 if (empty($newfile)) {
5880 return '';
5882 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5883 return $newfile['tmp_name'];
5884 } else {
5885 return '';
5890 * Returns the maximum size for uploading files.
5892 * There are seven possible upload limits:
5893 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5894 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5895 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5896 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5897 * 5. by the Moodle admin in $CFG->maxbytes
5898 * 6. by the teacher in the current course $course->maxbytes
5899 * 7. by the teacher for the current module, eg $assignment->maxbytes
5901 * These last two are passed to this function as arguments (in bytes).
5902 * Anything defined as 0 is ignored.
5903 * The smallest of all the non-zero numbers is returned.
5905 * @todo Finish documenting this function
5907 * @param int $sizebytes Set maximum size
5908 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5909 * @param int $modulebytes Current module ->maxbytes (in bytes)
5910 * @return int The maximum size for uploading files.
5912 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5914 if (! $filesize = ini_get('upload_max_filesize')) {
5915 $filesize = '5M';
5917 $minimumsize = get_real_size($filesize);
5919 if ($postsize = ini_get('post_max_size')) {
5920 $postsize = get_real_size($postsize);
5921 if ($postsize < $minimumsize) {
5922 $minimumsize = $postsize;
5926 if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
5927 $minimumsize = $sitebytes;
5930 if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
5931 $minimumsize = $coursebytes;
5934 if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
5935 $minimumsize = $modulebytes;
5938 return $minimumsize;
5942 * Returns the maximum size for uploading files for the current user
5944 * This function takes in account @see:get_max_upload_file_size() the user's capabilities
5946 * @param context $context The context in which to check user capabilities
5947 * @param int $sizebytes Set maximum size
5948 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5949 * @param int $modulebytes Current module ->maxbytes (in bytes)
5950 * @param stdClass The user
5951 * @return int The maximum size for uploading files.
5953 function get_user_max_upload_file_size($context, $sitebytes=0, $coursebytes=0, $modulebytes=0, $user=null) {
5954 global $USER;
5956 if (empty($user)) {
5957 $user = $USER;
5960 if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
5961 return USER_CAN_IGNORE_FILE_SIZE_LIMITS;
5964 return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes);
5968 * Returns an array of possible sizes in local language
5970 * Related to {@link get_max_upload_file_size()} - this function returns an
5971 * array of possible sizes in an array, translated to the
5972 * local language.
5974 * @todo Finish documenting this function
5976 * @global object
5977 * @uses SORT_NUMERIC
5978 * @param int $sizebytes Set maximum size
5979 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5980 * @param int $modulebytes Current module ->maxbytes (in bytes)
5981 * @param int|array $custombytes custom upload size/s which will be added to list,
5982 * Only value/s smaller then maxsize will be added to list.
5983 * @return array
5985 function get_max_upload_sizes($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $custombytes = null) {
5986 global $CFG;
5988 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5989 return array();
5992 $filesize = array();
5993 $filesize[intval($maxsize)] = display_size($maxsize);
5995 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5996 5242880, 10485760, 20971520, 52428800, 104857600);
5998 // If custombytes is given and is valid then add it to the list.
5999 if (is_number($custombytes) and $custombytes > 0) {
6000 $custombytes = (int)$custombytes;
6001 if (!in_array($custombytes, $sizelist)) {
6002 $sizelist[] = $custombytes;
6004 } else if (is_array($custombytes)) {
6005 $sizelist = array_unique(array_merge($sizelist, $custombytes));
6008 // Allow maxbytes to be selected if it falls outside the above boundaries
6009 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
6010 // note: get_real_size() is used in order to prevent problems with invalid values
6011 $sizelist[] = get_real_size($CFG->maxbytes);
6014 foreach ($sizelist as $sizebytes) {
6015 if ($sizebytes < $maxsize) {
6016 $filesize[intval($sizebytes)] = display_size($sizebytes);
6020 krsort($filesize, SORT_NUMERIC);
6022 return $filesize;
6026 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
6028 * If excludefiles is defined, then that file/directory is ignored
6029 * If getdirs is true, then (sub)directories are included in the output
6030 * If getfiles is true, then files are included in the output
6031 * (at least one of these must be true!)
6033 * @todo Finish documenting this function. Add examples of $excludefile usage.
6035 * @param string $rootdir A given root directory to start from
6036 * @param string|array $excludefile If defined then the specified file/directory is ignored
6037 * @param bool $descend If true then subdirectories are recursed as well
6038 * @param bool $getdirs If true then (sub)directories are included in the output
6039 * @param bool $getfiles If true then files are included in the output
6040 * @return array An array with all the filenames in
6041 * all subdirectories, relative to the given rootdir
6043 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
6045 $dirs = array();
6047 if (!$getdirs and !$getfiles) { // Nothing to show
6048 return $dirs;
6051 if (!is_dir($rootdir)) { // Must be a directory
6052 return $dirs;
6055 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
6056 return $dirs;
6059 if (!is_array($excludefiles)) {
6060 $excludefiles = array($excludefiles);
6063 while (false !== ($file = readdir($dir))) {
6064 $firstchar = substr($file, 0, 1);
6065 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
6066 continue;
6068 $fullfile = $rootdir .'/'. $file;
6069 if (filetype($fullfile) == 'dir') {
6070 if ($getdirs) {
6071 $dirs[] = $file;
6073 if ($descend) {
6074 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
6075 foreach ($subdirs as $subdir) {
6076 $dirs[] = $file .'/'. $subdir;
6079 } else if ($getfiles) {
6080 $dirs[] = $file;
6083 closedir($dir);
6085 asort($dirs);
6087 return $dirs;
6092 * Adds up all the files in a directory and works out the size.
6094 * @todo Finish documenting this function
6096 * @param string $rootdir The directory to start from
6097 * @param string $excludefile A file to exclude when summing directory size
6098 * @return int The summed size of all files and subfiles within the root directory
6100 function get_directory_size($rootdir, $excludefile='') {
6101 global $CFG;
6103 // do it this way if we can, it's much faster
6104 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
6105 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
6106 $output = null;
6107 $return = null;
6108 exec($command,$output,$return);
6109 if (is_array($output)) {
6110 return get_real_size(intval($output[0]).'k'); // we told it to return k.
6114 if (!is_dir($rootdir)) { // Must be a directory
6115 return 0;
6118 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
6119 return 0;
6122 $size = 0;
6124 while (false !== ($file = readdir($dir))) {
6125 $firstchar = substr($file, 0, 1);
6126 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
6127 continue;
6129 $fullfile = $rootdir .'/'. $file;
6130 if (filetype($fullfile) == 'dir') {
6131 $size += get_directory_size($fullfile, $excludefile);
6132 } else {
6133 $size += filesize($fullfile);
6136 closedir($dir);
6138 return $size;
6142 * Converts bytes into display form
6144 * @todo Finish documenting this function. Verify return type.
6146 * @staticvar string $gb Localized string for size in gigabytes
6147 * @staticvar string $mb Localized string for size in megabytes
6148 * @staticvar string $kb Localized string for size in kilobytes
6149 * @staticvar string $b Localized string for size in bytes
6150 * @param int $size The size to convert to human readable form
6151 * @return string
6153 function display_size($size) {
6155 static $gb, $mb, $kb, $b;
6157 if ($size === USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
6158 return get_string('unlimited');
6161 if (empty($gb)) {
6162 $gb = get_string('sizegb');
6163 $mb = get_string('sizemb');
6164 $kb = get_string('sizekb');
6165 $b = get_string('sizeb');
6168 if ($size >= 1073741824) {
6169 $size = round($size / 1073741824 * 10) / 10 . $gb;
6170 } else if ($size >= 1048576) {
6171 $size = round($size / 1048576 * 10) / 10 . $mb;
6172 } else if ($size >= 1024) {
6173 $size = round($size / 1024 * 10) / 10 . $kb;
6174 } else {
6175 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
6177 return $size;
6181 * Cleans a given filename by removing suspicious or troublesome characters
6182 * @see clean_param()
6184 * @uses PARAM_FILE
6185 * @param string $string file name
6186 * @return string cleaned file name
6188 function clean_filename($string) {
6189 return clean_param($string, PARAM_FILE);
6193 /// STRING TRANSLATION ////////////////////////////////////////
6196 * Returns the code for the current language
6198 * @category string
6199 * @return string
6201 function current_language() {
6202 global $CFG, $USER, $SESSION, $COURSE;
6204 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
6205 $return = $COURSE->lang;
6207 } else if (!empty($SESSION->lang)) { // Session language can override other settings
6208 $return = $SESSION->lang;
6210 } else if (!empty($USER->lang)) {
6211 $return = $USER->lang;
6213 } else if (isset($CFG->lang)) {
6214 $return = $CFG->lang;
6216 } else {
6217 $return = 'en';
6220 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
6222 return $return;
6226 * Returns parent language of current active language if defined
6228 * @category string
6229 * @uses COURSE
6230 * @uses SESSION
6231 * @param string $lang null means current language
6232 * @return string
6234 function get_parent_language($lang=null) {
6235 global $COURSE, $SESSION;
6237 //let's hack around the current language
6238 if (!empty($lang)) {
6239 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
6240 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
6241 $COURSE->lang = '';
6242 $SESSION->lang = $lang;
6245 $parentlang = get_string('parentlanguage', 'langconfig');
6246 if ($parentlang === 'en') {
6247 $parentlang = '';
6250 //let's hack around the current language
6251 if (!empty($lang)) {
6252 $COURSE->lang = $old_course_lang;
6253 $SESSION->lang = $old_session_lang;
6256 return $parentlang;
6260 * Returns current string_manager instance.
6262 * The param $forcereload is needed for CLI installer only where the string_manager instance
6263 * must be replaced during the install.php script life time.
6265 * @category string
6266 * @param bool $forcereload shall the singleton be released and new instance created instead?
6267 * @return string_manager
6269 function get_string_manager($forcereload=false) {
6270 global $CFG;
6272 static $singleton = null;
6274 if ($forcereload) {
6275 $singleton = null;
6277 if ($singleton === null) {
6278 if (empty($CFG->early_install_lang)) {
6280 if (empty($CFG->langcacheroot)) {
6281 $langcacheroot = $CFG->cachedir . '/lang';
6282 } else {
6283 $langcacheroot = $CFG->langcacheroot;
6286 if (empty($CFG->langlist)) {
6287 $translist = array();
6288 } else {
6289 $translist = explode(',', $CFG->langlist);
6292 if (empty($CFG->langmenucachefile)) {
6293 $langmenucache = $CFG->cachedir . '/languages';
6294 } else {
6295 $langmenucache = $CFG->langmenucachefile;
6298 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
6299 !empty($CFG->langstringcache), $translist, $langmenucache);
6301 } else {
6302 $singleton = new install_string_manager();
6306 return $singleton;
6311 * Interface for string manager
6313 * Interface describing class which is responsible for getting
6314 * of localised strings from language packs.
6316 * @package core
6317 * @copyright 2010 Petr Skoda (http://skodak.org)
6318 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6320 interface string_manager {
6322 * Get String returns a requested string
6324 * @param string $identifier The identifier of the string to search for
6325 * @param string $component The module the string is associated with
6326 * @param string|object|array $a An object, string or number that can be used
6327 * within translation strings
6328 * @param string $lang moodle translation language, NULL means use current
6329 * @return string The String !
6331 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
6334 * Does the string actually exist?
6336 * get_string() is throwing debug warnings, sometimes we do not want them
6337 * or we want to display better explanation of the problem.
6339 * Use with care!
6341 * @param string $identifier The identifier of the string to search for
6342 * @param string $component The module the string is associated with
6343 * @return boot true if exists
6345 public function string_exists($identifier, $component);
6348 * Returns a localised list of all country names, sorted by country keys.
6349 * @param bool $returnall return all or just enabled
6350 * @param string $lang moodle translation language, NULL means use current
6351 * @return array two-letter country code => translated name.
6353 public function get_list_of_countries($returnall = false, $lang = NULL);
6356 * Returns a localised list of languages, sorted by code keys.
6358 * @param string $lang moodle translation language, NULL means use current
6359 * @param string $standard language list standard
6360 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6361 * @return array language code => translated name
6363 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
6366 * Checks if the translation exists for the language
6368 * @param string $lang moodle translation language code
6369 * @param bool $includeall include also disabled translations
6370 * @return bool true if exists
6372 public function translation_exists($lang, $includeall = true);
6375 * Returns localised list of installed translations
6376 * @param bool $returnall return all or just enabled
6377 * @return array moodle translation code => localised translation name
6379 public function get_list_of_translations($returnall = false);
6382 * Returns localised list of currencies.
6384 * @param string $lang moodle translation language, NULL means use current
6385 * @return array currency code => localised currency name
6387 public function get_list_of_currencies($lang = NULL);
6390 * Load all strings for one component
6391 * @param string $component The module the string is associated with
6392 * @param string $lang
6393 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6394 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6395 * @return array of all string for given component and lang
6397 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
6400 * Invalidates all caches, should the implementation use any
6402 public function reset_caches();
6407 * Standard string_manager implementation
6409 * Implements string_manager with getting and printing localised strings
6411 * @package core
6412 * @category string
6413 * @copyright 2010 Petr Skoda (http://skodak.org)
6414 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6416 class core_string_manager implements string_manager {
6417 /** @var string location of all packs except 'en' */
6418 protected $otherroot;
6419 /** @var string location of all lang pack local modifications */
6420 protected $localroot;
6421 /** @var string location of on-disk cache of merged strings */
6422 protected $cacheroot;
6423 /** @var array lang string cache - it will be optimised more later */
6424 protected $cache = array();
6425 /** @var int get_string() counter */
6426 protected $countgetstring = 0;
6427 /** @var int in-memory cache hits counter */
6428 protected $countmemcache = 0;
6429 /** @var int on-disk cache hits counter */
6430 protected $countdiskcache = 0;
6431 /** @var bool use disk cache */
6432 protected $usediskcache;
6433 /** @var array limit list of translations */
6434 protected $translist;
6435 /** @var string location of a file that caches the list of available translations */
6436 protected $menucache;
6439 * Create new instance of string manager
6441 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
6442 * @param string $localroot usually the same as $otherroot
6443 * @param string $cacheroot usually lang dir in cache folder
6444 * @param bool $usediskcache use disk cache
6445 * @param array $translist limit list of visible translations
6446 * @param string $menucache the location of a file that caches the list of available translations
6448 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
6449 $this->otherroot = $otherroot;
6450 $this->localroot = $localroot;
6451 $this->cacheroot = $cacheroot;
6452 $this->usediskcache = $usediskcache;
6453 $this->translist = $translist;
6454 $this->menucache = $menucache;
6458 * Returns list of all explicit parent languages for the given language.
6460 * English (en) is considered as the top implicit parent of all language packs
6461 * and is not included in the returned list. The language itself is appended to the
6462 * end of the list. The method is aware of circular dependency risk.
6464 * @see self::populate_parent_languages()
6465 * @param string $lang the code of the language
6466 * @return array all explicit parent languages with the lang itself appended
6468 public function get_language_dependencies($lang) {
6469 return $this->populate_parent_languages($lang);
6473 * Load all strings for one component
6475 * @param string $component The module the string is associated with
6476 * @param string $lang
6477 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6478 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6479 * @return array of all string for given component and lang
6481 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6482 global $CFG;
6484 list($plugintype, $pluginname) = normalize_component($component);
6485 if ($plugintype == 'core' and is_null($pluginname)) {
6486 $component = 'core';
6487 } else {
6488 $component = $plugintype . '_' . $pluginname;
6491 if (!$disablecache and !$disablelocal) {
6492 // try in-memory cache first
6493 if (isset($this->cache[$lang][$component])) {
6494 $this->countmemcache++;
6495 return $this->cache[$lang][$component];
6498 // try on-disk cache then
6499 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
6500 $this->countdiskcache++;
6501 include($this->cacheroot . "/$lang/$component.php");
6502 return $this->cache[$lang][$component];
6506 // no cache found - let us merge all possible sources of the strings
6507 if ($plugintype === 'core') {
6508 $file = $pluginname;
6509 if ($file === null) {
6510 $file = 'moodle';
6512 $string = array();
6513 // first load english pack
6514 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
6515 return array();
6517 include("$CFG->dirroot/lang/en/$file.php");
6518 $originalkeys = array_keys($string);
6519 $originalkeys = array_flip($originalkeys);
6521 // and then corresponding local if present and allowed
6522 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6523 include("$this->localroot/en_local/$file.php");
6525 // now loop through all langs in correct order
6526 $deps = $this->get_language_dependencies($lang);
6527 foreach ($deps as $dep) {
6528 // the main lang string location
6529 if (file_exists("$this->otherroot/$dep/$file.php")) {
6530 include("$this->otherroot/$dep/$file.php");
6532 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6533 include("$this->localroot/{$dep}_local/$file.php");
6537 } else {
6538 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
6539 return array();
6541 if ($plugintype === 'mod') {
6542 // bloody mod hack
6543 $file = $pluginname;
6544 } else {
6545 $file = $plugintype . '_' . $pluginname;
6547 $string = array();
6548 // first load English pack
6549 if (!file_exists("$location/lang/en/$file.php")) {
6550 //English pack does not exist, so do not try to load anything else
6551 return array();
6553 include("$location/lang/en/$file.php");
6554 $originalkeys = array_keys($string);
6555 $originalkeys = array_flip($originalkeys);
6556 // and then corresponding local english if present
6557 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6558 include("$this->localroot/en_local/$file.php");
6561 // now loop through all langs in correct order
6562 $deps = $this->get_language_dependencies($lang);
6563 foreach ($deps as $dep) {
6564 // legacy location - used by contrib only
6565 if (file_exists("$location/lang/$dep/$file.php")) {
6566 include("$location/lang/$dep/$file.php");
6568 // the main lang string location
6569 if (file_exists("$this->otherroot/$dep/$file.php")) {
6570 include("$this->otherroot/$dep/$file.php");
6572 // local customisations
6573 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6574 include("$this->localroot/{$dep}_local/$file.php");
6579 // we do not want any extra strings from other languages - everything must be in en lang pack
6580 $string = array_intersect_key($string, $originalkeys);
6582 if (!$disablelocal) {
6583 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
6584 // caches so we do not need to do all this merging and dependencies resolving again
6585 $this->cache[$lang][$component] = $string;
6586 if ($this->usediskcache) {
6587 check_dir_exists("$this->cacheroot/$lang");
6588 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
6591 return $string;
6595 * Does the string actually exist?
6597 * get_string() is throwing debug warnings, sometimes we do not want them
6598 * or we want to display better explanation of the problem.
6599 * Note: Use with care!
6601 * @param string $identifier The identifier of the string to search for
6602 * @param string $component The module the string is associated with
6603 * @return boot true if exists
6605 public function string_exists($identifier, $component) {
6606 $lang = current_language();
6607 $string = $this->load_component_strings($component, $lang);
6608 return isset($string[$identifier]);
6612 * Get String returns a requested string
6614 * @param string $identifier The identifier of the string to search for
6615 * @param string $component The module the string is associated with
6616 * @param string|object|array $a An object, string or number that can be used
6617 * within translation strings
6618 * @param string $lang moodle translation language, NULL means use current
6619 * @return string The String !
6621 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6622 $this->countgetstring++;
6623 // there are very many uses of these time formating strings without the 'langconfig' component,
6624 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
6625 static $langconfigstrs = array(
6626 'strftimedate' => 1,
6627 'strftimedatefullshort' => 1,
6628 'strftimedateshort' => 1,
6629 'strftimedatetime' => 1,
6630 'strftimedatetimeshort' => 1,
6631 'strftimedaydate' => 1,
6632 'strftimedaydatetime' => 1,
6633 'strftimedayshort' => 1,
6634 'strftimedaytime' => 1,
6635 'strftimemonthyear' => 1,
6636 'strftimerecent' => 1,
6637 'strftimerecentfull' => 1,
6638 'strftimetime' => 1);
6640 if (empty($component)) {
6641 if (isset($langconfigstrs[$identifier])) {
6642 $component = 'langconfig';
6643 } else {
6644 $component = 'moodle';
6648 if ($lang === NULL) {
6649 $lang = current_language();
6652 $string = $this->load_component_strings($component, $lang);
6654 if (!isset($string[$identifier])) {
6655 if ($component === 'pix' or $component === 'core_pix') {
6656 // this component contains only alt tags for emoticons,
6657 // not all of them are supposed to be defined
6658 return '';
6660 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
6661 // parentlanguage is a special string, undefined means use English if not defined
6662 return 'en';
6664 if ($this->usediskcache) {
6665 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources,
6666 // do NOT write the results to disk cache because it may end up in race conditions see MDL-31904
6667 $this->usediskcache = false;
6668 $string = $this->load_component_strings($component, $lang, true);
6669 $this->usediskcache = true;
6671 if (!isset($string[$identifier])) {
6672 // the string is still missing - should be fixed by developer
6673 list($plugintype, $pluginname) = normalize_component($component);
6674 if ($plugintype == 'core') {
6675 $file = "lang/en/{$component}.php";
6676 } else if ($plugintype == 'mod') {
6677 $file = "mod/{$pluginname}/lang/en/{$pluginname}.php";
6678 } else {
6679 $path = get_plugin_directory($plugintype, $pluginname);
6680 $file = "{$path}/lang/en/{$plugintype}_{$pluginname}.php";
6682 debugging("Invalid get_string() identifier: '{$identifier}' or component '{$component}'. " .
6683 "Perhaps you are missing \$string['{$identifier}'] = ''; in {$file}?", DEBUG_DEVELOPER);
6684 return "[[$identifier]]";
6688 $string = $string[$identifier];
6690 if ($a !== NULL) {
6691 // Process array's and objects (except lang_strings)
6692 if (is_array($a) or (is_object($a) && !($a instanceof lang_string))) {
6693 $a = (array)$a;
6694 $search = array();
6695 $replace = array();
6696 foreach ($a as $key=>$value) {
6697 if (is_int($key)) {
6698 // we do not support numeric keys - sorry!
6699 continue;
6701 if (is_array($value) or (is_object($value) && !($value instanceof lang_string))) {
6702 // we support just string or lang_string as value
6703 continue;
6705 $search[] = '{$a->'.$key.'}';
6706 $replace[] = (string)$value;
6708 if ($search) {
6709 $string = str_replace($search, $replace, $string);
6711 } else {
6712 $string = str_replace('{$a}', (string)$a, $string);
6716 return $string;
6720 * Returns information about the string_manager performance
6722 * @return array
6724 public function get_performance_summary() {
6725 return array(array(
6726 'langcountgetstring' => $this->countgetstring,
6727 'langcountmemcache' => $this->countmemcache,
6728 'langcountdiskcache' => $this->countdiskcache,
6729 ), array(
6730 'langcountgetstring' => 'get_string calls',
6731 'langcountmemcache' => 'strings mem cache hits',
6732 'langcountdiskcache' => 'strings disk cache hits',
6737 * Returns a localised list of all country names, sorted by localised name.
6739 * @param bool $returnall return all or just enabled
6740 * @param string $lang moodle translation language, NULL means use current
6741 * @return array two-letter country code => translated name.
6743 public function get_list_of_countries($returnall = false, $lang = NULL) {
6744 global $CFG;
6746 if ($lang === NULL) {
6747 $lang = current_language();
6750 $countries = $this->load_component_strings('core_countries', $lang);
6751 collatorlib::asort($countries);
6752 if (!$returnall and !empty($CFG->allcountrycodes)) {
6753 $enabled = explode(',', $CFG->allcountrycodes);
6754 $return = array();
6755 foreach ($enabled as $c) {
6756 if (isset($countries[$c])) {
6757 $return[$c] = $countries[$c];
6760 return $return;
6763 return $countries;
6767 * Returns a localised list of languages, sorted by code keys.
6769 * @param string $lang moodle translation language, NULL means use current
6770 * @param string $standard language list standard
6771 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
6772 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
6773 * @return array language code => translated name
6775 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
6776 if ($lang === NULL) {
6777 $lang = current_language();
6780 if ($standard === 'iso6392') {
6781 $langs = $this->load_component_strings('core_iso6392', $lang);
6782 ksort($langs);
6783 return $langs;
6785 } else if ($standard === 'iso6391') {
6786 $langs2 = $this->load_component_strings('core_iso6392', $lang);
6787 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
6788 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
6789 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
6790 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
6791 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
6792 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
6793 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
6794 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
6795 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
6796 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
6797 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
6798 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
6799 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
6800 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6801 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6802 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6803 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6804 $langs1 = array();
6805 foreach ($mapping as $c2=>$c1) {
6806 $langs1[$c1] = $langs2[$c2];
6808 ksort($langs1);
6809 return $langs1;
6811 } else {
6812 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6815 return array();
6819 * Checks if the translation exists for the language
6821 * @param string $lang moodle translation language code
6822 * @param bool $includeall include also disabled translations
6823 * @return bool true if exists
6825 public function translation_exists($lang, $includeall = true) {
6827 if (strpos($lang, '_local') !== false) {
6828 // _local packs are not real translations
6829 return false;
6831 if (!$includeall and !empty($this->translist)) {
6832 if (!in_array($lang, $this->translist)) {
6833 return false;
6836 if ($lang === 'en') {
6837 // part of distribution
6838 return true;
6840 return file_exists("$this->otherroot/$lang/langconfig.php");
6844 * Returns localised list of installed translations
6846 * @param bool $returnall return all or just enabled
6847 * @return array moodle translation code => localised translation name
6849 public function get_list_of_translations($returnall = false) {
6850 global $CFG;
6852 $languages = array();
6854 if (!empty($CFG->langcache) and is_readable($this->menucache)) {
6855 // try to re-use the cached list of all available languages
6856 $cachedlist = json_decode(file_get_contents($this->menucache), true);
6858 if (is_array($cachedlist) and !empty($cachedlist)) {
6859 // the cache file is restored correctly
6861 if (!$returnall and !empty($this->translist)) {
6862 // return just enabled translations
6863 foreach ($cachedlist as $langcode => $langname) {
6864 if (in_array($langcode, $this->translist)) {
6865 $languages[$langcode] = $langname;
6868 return $languages;
6870 } else {
6871 // return all translations
6872 return $cachedlist;
6877 // the cached list of languages is not available, let us populate the list
6879 if (!$returnall and !empty($this->translist)) {
6880 // return only some translations
6881 foreach ($this->translist as $lang) {
6882 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6883 if (strstr($lang, '_local') !== false) {
6884 continue;
6886 if (strstr($lang, '_utf8') !== false) {
6887 continue;
6889 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6890 // some broken or missing lang - can not switch to it anyway
6891 continue;
6893 $string = $this->load_component_strings('langconfig', $lang);
6894 if (!empty($string['thislanguage'])) {
6895 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6897 unset($string);
6900 } else {
6901 // return all languages available in system
6902 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6904 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6905 // Sort all
6907 // Loop through all langs and get info
6908 foreach ($langdirs as $lang) {
6909 if (strstr($lang, '_local') !== false) {
6910 continue;
6912 if (strstr($lang, '_utf8') !== false) {
6913 continue;
6915 $string = $this->load_component_strings('langconfig', $lang);
6916 if (!empty($string['thislanguage'])) {
6917 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6919 unset($string);
6922 if (!empty($CFG->langcache) and !empty($this->menucache)) {
6923 // cache the list so that it can be used next time
6924 collatorlib::asort($languages);
6925 check_dir_exists(dirname($this->menucache), true, true);
6926 file_put_contents($this->menucache, json_encode($languages));
6930 collatorlib::asort($languages);
6932 return $languages;
6936 * Returns localised list of currencies.
6938 * @param string $lang moodle translation language, NULL means use current
6939 * @return array currency code => localised currency name
6941 public function get_list_of_currencies($lang = NULL) {
6942 if ($lang === NULL) {
6943 $lang = current_language();
6946 $currencies = $this->load_component_strings('core_currencies', $lang);
6947 asort($currencies);
6949 return $currencies;
6953 * Clears both in-memory and on-disk caches
6955 public function reset_caches() {
6956 global $CFG;
6957 require_once("$CFG->libdir/filelib.php");
6959 // clear the on-disk disk with aggregated string files
6960 fulldelete($this->cacheroot);
6962 // clear the in-memory cache of loaded strings
6963 $this->cache = array();
6965 // clear the cache containing the list of available translations
6966 // and re-populate it again
6967 fulldelete($this->menucache);
6968 $this->get_list_of_translations(true);
6971 /// End of external API ////////////////////////////////////////////////////
6974 * Helper method that recursively loads all parents of the given language.
6976 * @see self::get_language_dependencies()
6977 * @param string $lang language code
6978 * @param array $stack list of parent languages already populated in previous recursive calls
6979 * @return array list of all parents of the given language with the $lang itself added as the last element
6981 protected function populate_parent_languages($lang, array $stack = array()) {
6983 // English does not have a parent language.
6984 if ($lang === 'en') {
6985 return $stack;
6988 // Prevent circular dependency (and thence the infinitive recursion loop).
6989 if (in_array($lang, $stack)) {
6990 return $stack;
6993 // Load language configuration and look for the explicit parent language.
6994 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
6995 return $stack;
6997 $string = array();
6998 include("$this->otherroot/$lang/langconfig.php");
7000 if (empty($string['parentlanguage']) or $string['parentlanguage'] === 'en') {
7001 unset($string);
7002 return array_merge(array($lang), $stack);
7004 } else {
7005 $parentlang = $string['parentlanguage'];
7006 unset($string);
7007 return $this->populate_parent_languages($parentlang, array_merge(array($lang), $stack));
7014 * Fetches minimum strings for installation
7016 * Minimalistic string fetching implementation
7017 * that is used in installer before we fetch the wanted
7018 * language pack from moodle.org lang download site.
7020 * @package core
7021 * @copyright 2010 Petr Skoda (http://skodak.org)
7022 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7024 class install_string_manager implements string_manager {
7025 /** @var string location of pre-install packs for all langs */
7026 protected $installroot;
7029 * Crate new instance of install string manager
7031 public function __construct() {
7032 global $CFG;
7033 $this->installroot = "$CFG->dirroot/install/lang";
7037 * Load all strings for one component
7038 * @param string $component The module the string is associated with
7039 * @param string $lang
7040 * @param bool $disablecache Do not use caches, force fetching the strings from sources
7041 * @param bool $disablelocal Do not use customized strings in xx_local language packs
7042 * @return array of all string for given component and lang
7044 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
7045 // not needed in installer
7046 return array();
7050 * Does the string actually exist?
7052 * get_string() is throwing debug warnings, sometimes we do not want them
7053 * or we want to display better explanation of the problem.
7055 * Use with care!
7057 * @param string $identifier The identifier of the string to search for
7058 * @param string $component The module the string is associated with
7059 * @return boot true if exists
7061 public function string_exists($identifier, $component) {
7062 // simple old style hack ;)
7063 $str = get_string($identifier, $component);
7064 return (strpos($str, '[[') === false);
7068 * Get String returns a requested string
7070 * @param string $identifier The identifier of the string to search for
7071 * @param string $component The module the string is associated with
7072 * @param string|object|array $a An object, string or number that can be used
7073 * within translation strings
7074 * @param string $lang moodle translation language, NULL means use current
7075 * @return string The String !
7077 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
7078 if (!$component) {
7079 $component = 'moodle';
7082 if ($lang === NULL) {
7083 $lang = current_language();
7086 //get parent lang
7087 $parent = '';
7088 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
7089 if (file_exists("$this->installroot/$lang/langconfig.php")) {
7090 $string = array();
7091 include("$this->installroot/$lang/langconfig.php");
7092 if (isset($string['parentlanguage'])) {
7093 $parent = $string['parentlanguage'];
7095 unset($string);
7099 // include en string first
7100 if (!file_exists("$this->installroot/en/$component.php")) {
7101 return "[[$identifier]]";
7103 $string = array();
7104 include("$this->installroot/en/$component.php");
7106 // now override en with parent if defined
7107 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
7108 include("$this->installroot/$parent/$component.php");
7111 // finally override with requested language
7112 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
7113 include("$this->installroot/$lang/$component.php");
7116 if (!isset($string[$identifier])) {
7117 return "[[$identifier]]";
7120 $string = $string[$identifier];
7122 if ($a !== NULL) {
7123 if (is_object($a) or is_array($a)) {
7124 $a = (array)$a;
7125 $search = array();
7126 $replace = array();
7127 foreach ($a as $key=>$value) {
7128 if (is_int($key)) {
7129 // we do not support numeric keys - sorry!
7130 continue;
7132 $search[] = '{$a->'.$key.'}';
7133 $replace[] = (string)$value;
7135 if ($search) {
7136 $string = str_replace($search, $replace, $string);
7138 } else {
7139 $string = str_replace('{$a}', (string)$a, $string);
7143 return $string;
7147 * Returns a localised list of all country names, sorted by country keys.
7149 * @param bool $returnall return all or just enabled
7150 * @param string $lang moodle translation language, NULL means use current
7151 * @return array two-letter country code => translated name.
7153 public function get_list_of_countries($returnall = false, $lang = NULL) {
7154 //not used in installer
7155 return array();
7159 * Returns a localised list of languages, sorted by code keys.
7161 * @param string $lang moodle translation language, NULL means use current
7162 * @param string $standard language list standard
7163 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
7164 * @return array language code => translated name
7166 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
7167 //not used in installer
7168 return array();
7172 * Checks if the translation exists for the language
7174 * @param string $lang moodle translation language code
7175 * @param bool $includeall include also disabled translations
7176 * @return bool true if exists
7178 public function translation_exists($lang, $includeall = true) {
7179 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
7183 * Returns localised list of installed translations
7184 * @param bool $returnall return all or just enabled
7185 * @return array moodle translation code => localised translation name
7187 public function get_list_of_translations($returnall = false) {
7188 // return all is ignored here - we need to know all langs in installer
7189 $languages = array();
7190 // Get raw list of lang directories
7191 $langdirs = get_list_of_plugins('install/lang');
7192 asort($langdirs);
7193 // Get some info from each lang
7194 foreach ($langdirs as $lang) {
7195 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
7196 $string = array();
7197 include($this->installroot.'/'.$lang.'/langconfig.php');
7198 if (!empty($string['thislanguage'])) {
7199 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
7203 // Return array
7204 return $languages;
7208 * Returns localised list of currencies.
7210 * @param string $lang moodle translation language, NULL means use current
7211 * @return array currency code => localised currency name
7213 public function get_list_of_currencies($lang = NULL) {
7214 // not used in installer
7215 return array();
7219 * This implementation does not use any caches
7221 public function reset_caches() {}
7226 * Returns a localized string.
7228 * Returns the translated string specified by $identifier as
7229 * for $module. Uses the same format files as STphp.
7230 * $a is an object, string or number that can be used
7231 * within translation strings
7233 * eg 'hello {$a->firstname} {$a->lastname}'
7234 * or 'hello {$a}'
7236 * If you would like to directly echo the localized string use
7237 * the function {@link print_string()}
7239 * Example usage of this function involves finding the string you would
7240 * like a local equivalent of and using its identifier and module information
7241 * to retrieve it.<br/>
7242 * If you open moodle/lang/en/moodle.php and look near line 278
7243 * you will find a string to prompt a user for their word for 'course'
7244 * <code>
7245 * $string['course'] = 'Course';
7246 * </code>
7247 * So if you want to display the string 'Course'
7248 * in any language that supports it on your site
7249 * you just need to use the identifier 'course'
7250 * <code>
7251 * $mystring = '<strong>'. get_string('course') .'</strong>';
7252 * or
7253 * </code>
7254 * If the string you want is in another file you'd take a slightly
7255 * different approach. Looking in moodle/lang/en/calendar.php you find
7256 * around line 75:
7257 * <code>
7258 * $string['typecourse'] = 'Course event';
7259 * </code>
7260 * If you want to display the string "Course event" in any language
7261 * supported you would use the identifier 'typecourse' and the module 'calendar'
7262 * (because it is in the file calendar.php):
7263 * <code>
7264 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
7265 * </code>
7267 * As a last resort, should the identifier fail to map to a string
7268 * the returned string will be [[ $identifier ]]
7270 * In Moodle 2.3 there is a new argument to this function $lazyload.
7271 * Setting $lazyload to true causes get_string to return a lang_string object
7272 * rather than the string itself. The fetching of the string is then put off until
7273 * the string object is first used. The object can be used by calling it's out
7274 * method or by casting the object to a string, either directly e.g.
7275 * (string)$stringobject
7276 * or indirectly by using the string within another string or echoing it out e.g.
7277 * echo $stringobject
7278 * return "<p>{$stringobject}</p>";
7279 * It is worth noting that using $lazyload and attempting to use the string as an
7280 * array key will cause a fatal error as objects cannot be used as array keys.
7281 * But you should never do that anyway!
7282 * For more information {@see lang_string}
7284 * @category string
7285 * @param string $identifier The key identifier for the localized string
7286 * @param string $component The module where the key identifier is stored,
7287 * usually expressed as the filename in the language pack without the
7288 * .php on the end but can also be written as mod/forum or grade/export/xls.
7289 * If none is specified then moodle.php is used.
7290 * @param string|object|array $a An object, string or number that can be used
7291 * within translation strings
7292 * @param bool $lazyload If set to true a string object is returned instead of
7293 * the string itself. The string then isn't calculated until it is first used.
7294 * @return string The localized string.
7296 function get_string($identifier, $component = '', $a = NULL, $lazyload = false) {
7297 global $CFG;
7299 // If the lazy load argument has been supplied return a lang_string object
7300 // instead.
7301 // We need to make sure it is true (and a bool) as you will see below there
7302 // used to be a forth argument at one point.
7303 if ($lazyload === true) {
7304 return new lang_string($identifier, $component, $a);
7307 if (debugging('', DEBUG_DEVELOPER) && clean_param($identifier, PARAM_STRINGID) === '') {
7308 throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.');
7311 // There is now a forth argument again, this time it is a boolean however so
7312 // we can still check for the old extralocations parameter.
7313 if (!is_bool($lazyload) && !empty($lazyload)) {
7314 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
7317 if (strpos($component, '/') !== false) {
7318 debugging('The module name you passed to get_string is the deprecated format ' .
7319 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
7320 $componentpath = explode('/', $component);
7322 switch ($componentpath[0]) {
7323 case 'mod':
7324 $component = $componentpath[1];
7325 break;
7326 case 'blocks':
7327 case 'block':
7328 $component = 'block_'.$componentpath[1];
7329 break;
7330 case 'enrol':
7331 $component = 'enrol_'.$componentpath[1];
7332 break;
7333 case 'format':
7334 $component = 'format_'.$componentpath[1];
7335 break;
7336 case 'grade':
7337 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
7338 break;
7342 $result = get_string_manager()->get_string($identifier, $component, $a);
7344 // Debugging feature lets you display string identifier and component
7345 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
7346 $result .= ' {' . $identifier . '/' . $component . '}';
7348 return $result;
7352 * Converts an array of strings to their localized value.
7354 * @param array $array An array of strings
7355 * @param string $component The language module that these strings can be found in.
7356 * @return stdClass translated strings.
7358 function get_strings($array, $component = '') {
7359 $string = new stdClass;
7360 foreach ($array as $item) {
7361 $string->$item = get_string($item, $component);
7363 return $string;
7367 * Prints out a translated string.
7369 * Prints out a translated string using the return value from the {@link get_string()} function.
7371 * Example usage of this function when the string is in the moodle.php file:<br/>
7372 * <code>
7373 * echo '<strong>';
7374 * print_string('course');
7375 * echo '</strong>';
7376 * </code>
7378 * Example usage of this function when the string is not in the moodle.php file:<br/>
7379 * <code>
7380 * echo '<h1>';
7381 * print_string('typecourse', 'calendar');
7382 * echo '</h1>';
7383 * </code>
7385 * @category string
7386 * @param string $identifier The key identifier for the localized string
7387 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
7388 * @param string|object|array $a An object, string or number that can be used within translation strings
7390 function print_string($identifier, $component = '', $a = NULL) {
7391 echo get_string($identifier, $component, $a);
7395 * Returns a list of charset codes
7397 * Returns a list of charset codes. It's hardcoded, so they should be added manually
7398 * (checking that such charset is supported by the texlib library!)
7400 * @return array And associative array with contents in the form of charset => charset
7402 function get_list_of_charsets() {
7404 $charsets = array(
7405 'EUC-JP' => 'EUC-JP',
7406 'ISO-2022-JP'=> 'ISO-2022-JP',
7407 'ISO-8859-1' => 'ISO-8859-1',
7408 'SHIFT-JIS' => 'SHIFT-JIS',
7409 'GB2312' => 'GB2312',
7410 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
7411 'UTF-8' => 'UTF-8');
7413 asort($charsets);
7415 return $charsets;
7419 * Returns a list of valid and compatible themes
7421 * @return array
7423 function get_list_of_themes() {
7424 global $CFG;
7426 $themes = array();
7428 if (!empty($CFG->themelist)) { // use admin's list of themes
7429 $themelist = explode(',', $CFG->themelist);
7430 } else {
7431 $themelist = array_keys(get_plugin_list("theme"));
7434 foreach ($themelist as $key => $themename) {
7435 $theme = theme_config::load($themename);
7436 $themes[$themename] = $theme;
7439 collatorlib::asort_objects_by_method($themes, 'get_theme_name');
7441 return $themes;
7445 * Returns a list of timezones in the current language
7447 * @global object
7448 * @global object
7449 * @return array
7451 function get_list_of_timezones() {
7452 global $CFG, $DB;
7454 static $timezones;
7456 if (!empty($timezones)) { // This function has been called recently
7457 return $timezones;
7460 $timezones = array();
7462 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
7463 foreach($rawtimezones as $timezone) {
7464 if (!empty($timezone->name)) {
7465 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
7466 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
7467 } else {
7468 $timezones[$timezone->name] = $timezone->name;
7470 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
7471 $timezones[$timezone->name] = $timezone->name;
7477 asort($timezones);
7479 for ($i = -13; $i <= 13; $i += .5) {
7480 $tzstring = 'UTC';
7481 if ($i < 0) {
7482 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
7483 } else if ($i > 0) {
7484 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
7485 } else {
7486 $timezones[sprintf("%.1f", $i)] = $tzstring;
7490 return $timezones;
7494 * Factory function for emoticon_manager
7496 * @return emoticon_manager singleton
7498 function get_emoticon_manager() {
7499 static $singleton = null;
7501 if (is_null($singleton)) {
7502 $singleton = new emoticon_manager();
7505 return $singleton;
7509 * Provides core support for plugins that have to deal with
7510 * emoticons (like HTML editor or emoticon filter).
7512 * Whenever this manager mentiones 'emoticon object', the following data
7513 * structure is expected: stdClass with properties text, imagename, imagecomponent,
7514 * altidentifier and altcomponent
7516 * @see admin_setting_emoticons
7518 class emoticon_manager {
7521 * Returns the currently enabled emoticons
7523 * @return array of emoticon objects
7525 public function get_emoticons() {
7526 global $CFG;
7528 if (empty($CFG->emoticons)) {
7529 return array();
7532 $emoticons = $this->decode_stored_config($CFG->emoticons);
7534 if (!is_array($emoticons)) {
7535 // something is wrong with the format of stored setting
7536 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
7537 return array();
7540 return $emoticons;
7544 * Converts emoticon object into renderable pix_emoticon object
7546 * @param stdClass $emoticon emoticon object
7547 * @param array $attributes explicit HTML attributes to set
7548 * @return pix_emoticon
7550 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
7551 $stringmanager = get_string_manager();
7552 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
7553 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
7554 } else {
7555 $alt = s($emoticon->text);
7557 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
7561 * Encodes the array of emoticon objects into a string storable in config table
7563 * @see self::decode_stored_config()
7564 * @param array $emoticons array of emtocion objects
7565 * @return string
7567 public function encode_stored_config(array $emoticons) {
7568 return json_encode($emoticons);
7572 * Decodes the string into an array of emoticon objects
7574 * @see self::encode_stored_config()
7575 * @param string $encoded
7576 * @return string|null
7578 public function decode_stored_config($encoded) {
7579 $decoded = json_decode($encoded);
7580 if (!is_array($decoded)) {
7581 return null;
7583 return $decoded;
7587 * Returns default set of emoticons supported by Moodle
7589 * @return array of sdtClasses
7591 public function default_emoticons() {
7592 return array(
7593 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
7594 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
7595 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
7596 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
7597 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
7598 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
7599 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
7600 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
7601 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
7602 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
7603 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
7604 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
7605 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
7606 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
7607 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
7608 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
7609 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
7610 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
7611 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
7612 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
7613 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
7614 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
7615 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
7616 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
7617 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
7618 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
7619 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
7620 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
7621 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
7622 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
7627 * Helper method preparing the stdClass with the emoticon properties
7629 * @param string|array $text or array of strings
7630 * @param string $imagename to be used by {@see pix_emoticon}
7631 * @param string $altidentifier alternative string identifier, null for no alt
7632 * @param array $altcomponent where the alternative string is defined
7633 * @param string $imagecomponent to be used by {@see pix_emoticon}
7634 * @return stdClass
7636 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
7637 return (object)array(
7638 'text' => $text,
7639 'imagename' => $imagename,
7640 'imagecomponent' => $imagecomponent,
7641 'altidentifier' => $altidentifier,
7642 'altcomponent' => $altcomponent,
7647 /// ENCRYPTION ////////////////////////////////////////////////
7650 * rc4encrypt
7652 * Please note that in this version of moodle that the default for rc4encryption is
7653 * using the slightly more secure password key. There may be an issue when upgrading
7654 * from an older version of moodle.
7656 * @todo MDL-31836 Remove the old password key in version 2.4
7657 * Code also needs to be changed in sessionlib.php
7658 * @see get_moodle_cookie()
7659 * @see set_moodle_cookie()
7661 * @param string $data Data to encrypt.
7662 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7663 * @return string The now encrypted data.
7665 function rc4encrypt($data, $usesecurekey = true) {
7666 if (!$usesecurekey) {
7667 $passwordkey = 'nfgjeingjk';
7668 } else {
7669 $passwordkey = get_site_identifier();
7671 return endecrypt($passwordkey, $data, '');
7675 * rc4decrypt
7677 * Please note that in this version of moodle that the default for rc4encryption is
7678 * using the slightly more secure password key. There may be an issue when upgrading
7679 * from an older version of moodle.
7681 * @todo MDL-31836 Remove the old password key in version 2.4
7682 * Code also needs to be changed in sessionlib.php
7683 * @see get_moodle_cookie()
7684 * @see set_moodle_cookie()
7686 * @param string $data Data to decrypt.
7687 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7688 * @return string The now decrypted data.
7690 function rc4decrypt($data, $usesecurekey = true) {
7691 if (!$usesecurekey) {
7692 $passwordkey = 'nfgjeingjk';
7693 } else {
7694 $passwordkey = get_site_identifier();
7696 return endecrypt($passwordkey, $data, 'de');
7700 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
7702 * @todo Finish documenting this function
7704 * @param string $pwd The password to use when encrypting or decrypting
7705 * @param string $data The data to be decrypted/encrypted
7706 * @param string $case Either 'de' for decrypt or '' for encrypt
7707 * @return string
7709 function endecrypt ($pwd, $data, $case) {
7711 if ($case == 'de') {
7712 $data = urldecode($data);
7715 $key[] = '';
7716 $box[] = '';
7717 $temp_swap = '';
7718 $pwd_length = 0;
7720 $pwd_length = strlen($pwd);
7722 for ($i = 0; $i <= 255; $i++) {
7723 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
7724 $box[$i] = $i;
7727 $x = 0;
7729 for ($i = 0; $i <= 255; $i++) {
7730 $x = ($x + $box[$i] + $key[$i]) % 256;
7731 $temp_swap = $box[$i];
7732 $box[$i] = $box[$x];
7733 $box[$x] = $temp_swap;
7736 $temp = '';
7737 $k = '';
7739 $cipherby = '';
7740 $cipher = '';
7742 $a = 0;
7743 $j = 0;
7745 for ($i = 0; $i < strlen($data); $i++) {
7746 $a = ($a + 1) % 256;
7747 $j = ($j + $box[$a]) % 256;
7748 $temp = $box[$a];
7749 $box[$a] = $box[$j];
7750 $box[$j] = $temp;
7751 $k = $box[(($box[$a] + $box[$j]) % 256)];
7752 $cipherby = ord(substr($data, $i, 1)) ^ $k;
7753 $cipher .= chr($cipherby);
7756 if ($case == 'de') {
7757 $cipher = urldecode(urlencode($cipher));
7758 } else {
7759 $cipher = urlencode($cipher);
7762 return $cipher;
7765 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
7768 * Returns the exact absolute path to plugin directory.
7770 * @param string $plugintype type of plugin
7771 * @param string $name name of the plugin
7772 * @return string full path to plugin directory; NULL if not found
7774 function get_plugin_directory($plugintype, $name) {
7775 global $CFG;
7777 if ($plugintype === '') {
7778 $plugintype = 'mod';
7781 $types = get_plugin_types(true);
7782 if (!array_key_exists($plugintype, $types)) {
7783 return NULL;
7785 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
7787 if (!empty($CFG->themedir) and $plugintype === 'theme') {
7788 if (!is_dir($types['theme'] . '/' . $name)) {
7789 // ok, so the theme is supposed to be in the $CFG->themedir
7790 return $CFG->themedir . '/' . $name;
7794 return $types[$plugintype].'/'.$name;
7798 * Return exact absolute path to a plugin directory.
7800 * @param string $component name such as 'moodle', 'mod_forum'
7801 * @return string full path to component directory; NULL if not found
7803 function get_component_directory($component) {
7804 global $CFG;
7806 list($type, $plugin) = normalize_component($component);
7808 if ($type === 'core') {
7809 if ($plugin === NULL ) {
7810 $path = $CFG->libdir;
7811 } else {
7812 $subsystems = get_core_subsystems();
7813 if (isset($subsystems[$plugin])) {
7814 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
7815 } else {
7816 $path = NULL;
7820 } else {
7821 $path = get_plugin_directory($type, $plugin);
7824 return $path;
7828 * Normalize the component name using the "frankenstyle" names.
7829 * @param string $component
7830 * @return array $type+$plugin elements
7832 function normalize_component($component) {
7833 if ($component === 'moodle' or $component === 'core') {
7834 $type = 'core';
7835 $plugin = NULL;
7837 } else if (strpos($component, '_') === false) {
7838 $subsystems = get_core_subsystems();
7839 if (array_key_exists($component, $subsystems)) {
7840 $type = 'core';
7841 $plugin = $component;
7842 } else {
7843 // everything else is a module
7844 $type = 'mod';
7845 $plugin = $component;
7848 } else {
7849 list($type, $plugin) = explode('_', $component, 2);
7850 $plugintypes = get_plugin_types(false);
7851 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
7852 $type = 'mod';
7853 $plugin = $component;
7857 return array($type, $plugin);
7861 * List all core subsystems and their location
7863 * This is a whitelist of components that are part of the core and their
7864 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
7865 * plugin is not listed here and it does not have proper plugintype prefix,
7866 * then it is considered as course activity module.
7868 * The location is dirroot relative path. NULL means there is no special
7869 * directory for this subsystem. If the location is set, the subsystem's
7870 * renderer.php is expected to be there.
7872 * @return array of (string)name => (string|null)location
7874 function get_core_subsystems() {
7875 global $CFG;
7877 static $info = null;
7879 if (!$info) {
7880 $info = array(
7881 'access' => NULL,
7882 'admin' => $CFG->admin,
7883 'auth' => 'auth',
7884 'backup' => 'backup/util/ui',
7885 'block' => 'blocks',
7886 'blog' => 'blog',
7887 'bulkusers' => NULL,
7888 'calendar' => 'calendar',
7889 'cohort' => 'cohort',
7890 'condition' => NULL,
7891 'completion' => NULL,
7892 'countries' => NULL,
7893 'course' => 'course',
7894 'currencies' => NULL,
7895 'dbtransfer' => NULL,
7896 'debug' => NULL,
7897 'dock' => NULL,
7898 'editor' => 'lib/editor',
7899 'edufields' => NULL,
7900 'enrol' => 'enrol',
7901 'error' => NULL,
7902 'filepicker' => NULL,
7903 'files' => 'files',
7904 'filters' => NULL,
7905 'fonts' => NULL,
7906 'form' => 'lib/form',
7907 'grades' => 'grade',
7908 'grading' => 'grade/grading',
7909 'group' => 'group',
7910 'help' => NULL,
7911 'hub' => NULL,
7912 'imscc' => NULL,
7913 'install' => NULL,
7914 'iso6392' => NULL,
7915 'langconfig' => NULL,
7916 'license' => NULL,
7917 'mathslib' => NULL,
7918 'media' => 'media',
7919 'message' => 'message',
7920 'mimetypes' => NULL,
7921 'mnet' => 'mnet',
7922 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7923 'my' => 'my',
7924 'notes' => 'notes',
7925 'pagetype' => NULL,
7926 'pix' => NULL,
7927 'plagiarism' => 'plagiarism',
7928 'plugin' => NULL,
7929 'portfolio' => 'portfolio',
7930 'publish' => 'course/publish',
7931 'question' => 'question',
7932 'rating' => 'rating',
7933 'register' => 'admin/registration', //TODO: this is wrong, unfortunately we would need to modify hub code to pass around the correct url
7934 'repository' => 'repository',
7935 'rss' => 'rss',
7936 'role' => $CFG->admin.'/role',
7937 'search' => 'search',
7938 'table' => NULL,
7939 'tag' => 'tag',
7940 'timezones' => NULL,
7941 'user' => 'user',
7942 'userkey' => NULL,
7943 'webservice' => 'webservice',
7947 return $info;
7951 * Lists all plugin types
7952 * @param bool $fullpaths false means relative paths from dirroot
7953 * @return array Array of strings - name=>location
7955 function get_plugin_types($fullpaths=true) {
7956 global $CFG;
7958 static $info = null;
7959 static $fullinfo = null;
7961 if (!$info) {
7962 $info = array('qtype' => 'question/type',
7963 'mod' => 'mod',
7964 'auth' => 'auth',
7965 'enrol' => 'enrol',
7966 'message' => 'message/output',
7967 'block' => 'blocks',
7968 'filter' => 'filter',
7969 'editor' => 'lib/editor',
7970 'format' => 'course/format',
7971 'profilefield' => 'user/profile/field',
7972 'report' => 'report',
7973 'coursereport' => 'course/report', // must be after system reports
7974 'gradeexport' => 'grade/export',
7975 'gradeimport' => 'grade/import',
7976 'gradereport' => 'grade/report',
7977 'gradingform' => 'grade/grading/form',
7978 'mnetservice' => 'mnet/service',
7979 'webservice' => 'webservice',
7980 'repository' => 'repository',
7981 'portfolio' => 'portfolio',
7982 'qbehaviour' => 'question/behaviour',
7983 'qformat' => 'question/format',
7984 'plagiarism' => 'plagiarism',
7985 'tool' => $CFG->admin.'/tool',
7986 'theme' => 'theme', // this is a bit hacky, themes may be in $CFG->themedir too
7989 $mods = get_plugin_list('mod');
7990 foreach ($mods as $mod => $moddir) {
7991 if (file_exists("$moddir/db/subplugins.php")) {
7992 $subplugins = array();
7993 include("$moddir/db/subplugins.php");
7994 foreach ($subplugins as $subtype=>$dir) {
7995 $info[$subtype] = $dir;
8000 // local is always last!
8001 $info['local'] = 'local';
8003 $fullinfo = array();
8004 foreach ($info as $type => $dir) {
8005 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
8009 return ($fullpaths ? $fullinfo : $info);
8013 * Simplified version of get_list_of_plugins()
8014 * @param string $plugintype type of plugin
8015 * @return array name=>fulllocation pairs of plugins of given type
8017 function get_plugin_list($plugintype) {
8018 global $CFG;
8020 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
8021 if ($plugintype == 'auth') {
8022 // Historically we have had an auth plugin called 'db', so allow a special case.
8023 $key = array_search('db', $ignored);
8024 if ($key !== false) {
8025 unset($ignored[$key]);
8029 if ($plugintype === '') {
8030 $plugintype = 'mod';
8033 $fulldirs = array();
8035 if ($plugintype === 'mod') {
8036 // mod is an exception because we have to call this function from get_plugin_types()
8037 $fulldirs[] = $CFG->dirroot.'/mod';
8039 } else if ($plugintype === 'theme') {
8040 $fulldirs[] = $CFG->dirroot.'/theme';
8041 // themes are special because they may be stored also in separate directory
8042 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
8043 $fulldirs[] = $CFG->themedir;
8046 } else {
8047 $types = get_plugin_types(true);
8048 if (!array_key_exists($plugintype, $types)) {
8049 return array();
8051 $fulldir = $types[$plugintype];
8052 if (!file_exists($fulldir)) {
8053 return array();
8055 $fulldirs[] = $fulldir;
8058 $result = array();
8060 foreach ($fulldirs as $fulldir) {
8061 if (!is_dir($fulldir)) {
8062 continue;
8064 $items = new DirectoryIterator($fulldir);
8065 foreach ($items as $item) {
8066 if ($item->isDot() or !$item->isDir()) {
8067 continue;
8069 $pluginname = $item->getFilename();
8070 if (in_array($pluginname, $ignored)) {
8071 continue;
8073 $pluginname = clean_param($pluginname, PARAM_PLUGIN);
8074 if (empty($pluginname)) {
8075 // better ignore plugins with problematic names here
8076 continue;
8078 $result[$pluginname] = $fulldir.'/'.$pluginname;
8079 unset($item);
8081 unset($items);
8084 //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!
8085 ksort($result);
8086 return $result;
8090 * Get a list of all the plugins of a given type that contain a particular file.
8091 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8092 * @param string $file the name of file that must be present in the plugin.
8093 * (e.g. 'view.php', 'db/install.xml').
8094 * @param bool $include if true (default false), the file will be include_once-ed if found.
8095 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
8096 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
8098 function get_plugin_list_with_file($plugintype, $file, $include = false) {
8099 global $CFG; // Necessary in case it is referenced by include()d PHP scripts.
8101 $plugins = array();
8103 foreach(get_plugin_list($plugintype) as $plugin => $dir) {
8104 $path = $dir . '/' . $file;
8105 if (file_exists($path)) {
8106 if ($include) {
8107 include_once($path);
8109 $plugins[$plugin] = $path;
8113 return $plugins;
8117 * Get a list of all the plugins of a given type that define a certain API function
8118 * in a certain file. The plugin component names and function names are returned.
8120 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8121 * @param string $function the part of the name of the function after the
8122 * frankenstyle prefix. e.g 'hook' if you are looking for functions with
8123 * names like report_courselist_hook.
8124 * @param string $file the name of file within the plugin that defines the
8125 * function. Defaults to lib.php.
8126 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8127 * and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
8129 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
8130 $pluginfunctions = array();
8131 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8132 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
8134 if (function_exists($fullfunction)) {
8135 // Function exists with standard name. Store, indexed by
8136 // frankenstyle name of plugin
8137 $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
8139 } else if ($plugintype === 'mod') {
8140 // For modules, we also allow plugin without full frankenstyle
8141 // but just starting with the module name
8142 $shortfunction = $plugin . '_' . $function;
8143 if (function_exists($shortfunction)) {
8144 $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
8148 return $pluginfunctions;
8152 * Get a list of all the plugins of a given type that define a certain class
8153 * in a certain file. The plugin component names and class names are returned.
8155 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8156 * @param string $class the part of the name of the class after the
8157 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
8158 * names like report_courselist_thing. If you are looking for classes with
8159 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
8160 * @param string $file the name of file within the plugin that defines the class.
8161 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8162 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
8164 function get_plugin_list_with_class($plugintype, $class, $file) {
8165 if ($class) {
8166 $suffix = '_' . $class;
8167 } else {
8168 $suffix = '';
8171 $pluginclasses = array();
8172 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8173 $classname = $plugintype . '_' . $plugin . $suffix;
8174 if (class_exists($classname)) {
8175 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
8179 return $pluginclasses;
8183 * Lists plugin-like directories within specified directory
8185 * This function was originally used for standard Moodle plugins, please use
8186 * new get_plugin_list() now.
8188 * This function is used for general directory listing and backwards compatility.
8190 * @param string $directory relative directory from root
8191 * @param string $exclude dir name to exclude from the list (defaults to none)
8192 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
8193 * @return array Sorted array of directory names found under the requested parameters
8195 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
8196 global $CFG;
8198 $plugins = array();
8200 if (empty($basedir)) {
8201 $basedir = $CFG->dirroot .'/'. $directory;
8203 } else {
8204 $basedir = $basedir .'/'. $directory;
8207 if (file_exists($basedir) && filetype($basedir) == 'dir') {
8208 if (!$dirhandle = opendir($basedir)) {
8209 debugging("Directory permission error for plugin ({$directory}). Directory exists but cannot be read.", DEBUG_DEVELOPER);
8210 return array();
8212 while (false !== ($dir = readdir($dirhandle))) {
8213 $firstchar = substr($dir, 0, 1);
8214 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
8215 continue;
8217 if (filetype($basedir .'/'. $dir) != 'dir') {
8218 continue;
8220 $plugins[] = $dir;
8222 closedir($dirhandle);
8224 if ($plugins) {
8225 asort($plugins);
8227 return $plugins;
8231 * Invoke plugin's callback functions
8233 * @param string $type plugin type e.g. 'mod'
8234 * @param string $name plugin name
8235 * @param string $feature feature name
8236 * @param string $action feature's action
8237 * @param array $params parameters of callback function, should be an array
8238 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8239 * @return mixed
8241 * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
8243 function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
8244 return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
8248 * Invoke component's callback functions
8250 * @param string $component frankenstyle component name, e.g. 'mod_quiz'
8251 * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
8252 * @param array $params parameters of callback function
8253 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8254 * @return mixed
8256 function component_callback($component, $function, array $params = array(), $default = null) {
8257 global $CFG; // this is needed for require_once() below
8259 $cleancomponent = clean_param($component, PARAM_COMPONENT);
8260 if (empty($cleancomponent)) {
8261 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8263 $component = $cleancomponent;
8265 list($type, $name) = normalize_component($component);
8266 $component = $type . '_' . $name;
8268 $oldfunction = $name.'_'.$function;
8269 $function = $component.'_'.$function;
8271 $dir = get_component_directory($component);
8272 if (empty($dir)) {
8273 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8276 // Load library and look for function
8277 if (file_exists($dir.'/lib.php')) {
8278 require_once($dir.'/lib.php');
8281 if (!function_exists($function) and function_exists($oldfunction)) {
8282 if ($type !== 'mod' and $type !== 'core') {
8283 debugging("Please use new function name $function instead of legacy $oldfunction");
8285 $function = $oldfunction;
8288 if (function_exists($function)) {
8289 // Function exists, so just return function result
8290 $ret = call_user_func_array($function, $params);
8291 if (is_null($ret)) {
8292 return $default;
8293 } else {
8294 return $ret;
8297 return $default;
8301 * Checks whether a plugin supports a specified feature.
8303 * @param string $type Plugin type e.g. 'mod'
8304 * @param string $name Plugin name e.g. 'forum'
8305 * @param string $feature Feature code (FEATURE_xx constant)
8306 * @param mixed $default default value if feature support unknown
8307 * @return mixed Feature result (false if not supported, null if feature is unknown,
8308 * otherwise usually true but may have other feature-specific value such as array)
8310 function plugin_supports($type, $name, $feature, $default = NULL) {
8311 global $CFG;
8313 if ($type === 'mod' and $name === 'NEWMODULE') {
8314 //somebody forgot to rename the module template
8315 return false;
8318 $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
8319 if (empty($component)) {
8320 throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
8323 $function = null;
8325 if ($type === 'mod') {
8326 // we need this special case because we support subplugins in modules,
8327 // otherwise it would end up in infinite loop
8328 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
8329 include_once("$CFG->dirroot/mod/$name/lib.php");
8330 $function = $component.'_supports';
8331 if (!function_exists($function)) {
8332 // legacy non-frankenstyle function name
8333 $function = $name.'_supports';
8335 } else {
8336 // invalid module
8339 } else {
8340 if (!$path = get_plugin_directory($type, $name)) {
8341 // non existent plugin type
8342 return false;
8344 if (file_exists("$path/lib.php")) {
8345 include_once("$path/lib.php");
8346 $function = $component.'_supports';
8350 if ($function and function_exists($function)) {
8351 $supports = $function($feature);
8352 if (is_null($supports)) {
8353 // plugin does not know - use default
8354 return $default;
8355 } else {
8356 return $supports;
8360 //plugin does not care, so use default
8361 return $default;
8365 * Returns true if the current version of PHP is greater that the specified one.
8367 * @todo Check PHP version being required here is it too low?
8369 * @param string $version The version of php being tested.
8370 * @return bool
8372 function check_php_version($version='5.2.4') {
8373 return (version_compare(phpversion(), $version) >= 0);
8377 * Checks to see if is the browser operating system matches the specified
8378 * brand.
8380 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
8382 * @uses $_SERVER
8383 * @param string $brand The operating system identifier being tested
8384 * @return bool true if the given brand below to the detected operating system
8386 function check_browser_operating_system($brand) {
8387 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8388 return false;
8391 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
8392 return true;
8395 return false;
8399 * Checks to see if is a browser matches the specified
8400 * brand and is equal or better version.
8402 * @uses $_SERVER
8403 * @param string $brand The browser identifier being tested
8404 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
8405 * @return bool true if the given version is below that of the detected browser
8407 function check_browser_version($brand, $version = null) {
8408 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8409 return false;
8412 $agent = $_SERVER['HTTP_USER_AGENT'];
8414 switch ($brand) {
8416 case 'Camino': /// OSX browser using Gecke engine
8417 if (strpos($agent, 'Camino') === false) {
8418 return false;
8420 if (empty($version)) {
8421 return true; // no version specified
8423 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
8424 if (version_compare($match[1], $version) >= 0) {
8425 return true;
8428 break;
8431 case 'Firefox': /// Mozilla Firefox browsers
8432 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
8433 return false;
8435 if (empty($version)) {
8436 return true; // no version specified
8438 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8439 if (version_compare($match[2], $version) >= 0) {
8440 return true;
8443 break;
8446 case 'Gecko': /// Gecko based browsers
8447 // Do not look for dates any more, we expect real Firefox version here.
8448 if (empty($version)) {
8449 $version = 1;
8450 } else if ($version > 20000000) {
8451 // This is just a guess, it is not supposed to be 100% accurate!
8452 if (preg_match('/^201/', $version)) {
8453 $version = 3.6;
8454 } else if (preg_match('/^200[7-9]/', $version)) {
8455 $version = 3;
8456 } else if (preg_match('/^2006/', $version)) {
8457 $version = 2;
8458 } else {
8459 $version = 1.5;
8462 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8463 // Use real Firefox version if specified in user agent string.
8464 if (version_compare($match[2], $version) >= 0) {
8465 return true;
8467 } else if (preg_match("/Gecko\/([0-9\.]+)/i", $agent, $match)) {
8468 // Gecko might contain date or Firefox revision, let's just guess the Firefox version from the date.
8469 $browserver = $match[1];
8470 if ($browserver > 20000000) {
8471 // This is just a guess, it is not supposed to be 100% accurate!
8472 if (preg_match('/^201/', $browserver)) {
8473 $browserver = 3.6;
8474 } else if (preg_match('/^200[7-9]/', $browserver)) {
8475 $browserver = 3;
8476 } else if (preg_match('/^2006/', $version)) {
8477 $browserver = 2;
8478 } else {
8479 $browserver = 1.5;
8482 if (version_compare($browserver, $version) >= 0) {
8483 return true;
8486 break;
8489 case 'MSIE': /// Internet Explorer
8490 if (strpos($agent, 'Opera') !== false) { // Reject Opera
8491 return false;
8493 // in case of IE we have to deal with BC of the version parameter
8494 if (is_null($version)) {
8495 $version = 5.5; // anything older is not considered a browser at all!
8498 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
8499 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
8500 if (version_compare($match[1], $version) >= 0) {
8501 return true;
8504 break;
8507 case 'Opera': /// Opera
8508 if (strpos($agent, 'Opera') === false) {
8509 return false;
8511 if (empty($version)) {
8512 return true; // no version specified
8514 // Recent Opera useragents have Version/ with the actual version, e.g.:
8515 // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
8516 // That's Opera 12.01, not 9.8.
8517 if (preg_match("/Version\/([0-9\.]+)/i", $agent, $match)) {
8518 if (version_compare($match[1], $version) >= 0) {
8519 return true;
8521 } else if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
8522 if (version_compare($match[1], $version) >= 0) {
8523 return true;
8526 break;
8529 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
8530 if (strpos($agent, 'AppleWebKit') === false) {
8531 return false;
8533 if (empty($version)) {
8534 return true; // no version specified
8536 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8537 if (version_compare($match[1], $version) >= 0) {
8538 return true;
8541 break;
8544 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
8545 if (strpos($agent, 'AppleWebKit') === false) {
8546 return false;
8548 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
8549 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
8550 return false;
8552 if (strpos($agent, 'Shiira')) { // Reject Shiira
8553 return false;
8555 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
8556 return false;
8558 if (strpos($agent, 'Android')) { // Reject Androids too
8559 return false;
8561 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
8562 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
8563 return false;
8565 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
8566 return false;
8569 if (empty($version)) {
8570 return true; // no version specified
8572 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8573 if (version_compare($match[1], $version) >= 0) {
8574 return true;
8577 break;
8580 case 'Chrome':
8581 if (strpos($agent, 'Chrome') === false) {
8582 return false;
8584 if (empty($version)) {
8585 return true; // no version specified
8587 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
8588 if (version_compare($match[1], $version) >= 0) {
8589 return true;
8592 break;
8595 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
8596 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
8597 return false;
8599 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
8600 return false;
8602 if (empty($version)) {
8603 return true; // no version specified
8605 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8606 if (version_compare($match[1], $version) >= 0) {
8607 return true;
8610 break;
8613 case 'WebKit Android': /// WebKit browser on Android
8614 if (strpos($agent, 'Linux; U; Android') === false) {
8615 return false;
8617 if (empty($version)) {
8618 return true; // no version specified
8620 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8621 if (version_compare($match[1], $version) >= 0) {
8622 return true;
8625 break;
8629 return false;
8633 * Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
8634 * an optional admin specified regular expression. If enabledevicedetection is set to no or not set
8635 * it returns default
8637 * @return string device type
8639 function get_device_type() {
8640 global $CFG;
8642 if (empty($CFG->enabledevicedetection) || empty($_SERVER['HTTP_USER_AGENT'])) {
8643 return 'default';
8646 $useragent = $_SERVER['HTTP_USER_AGENT'];
8648 if (!empty($CFG->devicedetectregex)) {
8649 $regexes = json_decode($CFG->devicedetectregex);
8651 foreach ($regexes as $value=>$regex) {
8652 if (preg_match($regex, $useragent)) {
8653 return $value;
8658 //mobile detection PHP direct copy from open source detectmobilebrowser.com
8659 $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';
8660 $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';
8661 if (preg_match($phonesregex,$useragent) || preg_match($modelsregex,substr($useragent, 0, 4))){
8662 return 'mobile';
8665 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
8666 if (preg_match($tabletregex, $useragent)) {
8667 return 'tablet';
8670 // Safe way to check for IE6 and not get false positives for some IE 7/8 users
8671 if (substr($_SERVER['HTTP_USER_AGENT'], 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
8672 return 'legacy';
8675 return 'default';
8679 * Returns a list of the device types supporting by Moodle
8681 * @param boolean $incusertypes includes types specified using the devicedetectregex admin setting
8682 * @return array $types
8684 function get_device_type_list($incusertypes = true) {
8685 global $CFG;
8687 $types = array('default', 'legacy', 'mobile', 'tablet');
8689 if ($incusertypes && !empty($CFG->devicedetectregex)) {
8690 $regexes = json_decode($CFG->devicedetectregex);
8692 foreach ($regexes as $value => $regex) {
8693 $types[] = $value;
8697 return $types;
8701 * Returns the theme selected for a particular device or false if none selected.
8703 * @param string $devicetype
8704 * @return string|false The name of the theme to use for the device or the false if not set
8706 function get_selected_theme_for_device_type($devicetype = null) {
8707 global $CFG;
8709 if (empty($devicetype)) {
8710 $devicetype = get_user_device_type();
8713 $themevarname = get_device_cfg_var_name($devicetype);
8714 if (empty($CFG->$themevarname)) {
8715 return false;
8718 return $CFG->$themevarname;
8722 * Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
8724 * @param string $devicetype
8725 * @return string The config variable to use to determine the theme
8727 function get_device_cfg_var_name($devicetype = null) {
8728 if ($devicetype == 'default' || empty($devicetype)) {
8729 return 'theme';
8732 return 'theme' . $devicetype;
8736 * Allows the user to switch the device they are seeing the theme for.
8737 * This allows mobile users to switch back to the default theme, or theme for any other device.
8739 * @param string $newdevice The device the user is currently using.
8740 * @return string The device the user has switched to
8742 function set_user_device_type($newdevice) {
8743 global $USER;
8745 $devicetype = get_device_type();
8746 $devicetypes = get_device_type_list();
8748 if ($newdevice == $devicetype) {
8749 unset_user_preference('switchdevice'.$devicetype);
8750 } else if (in_array($newdevice, $devicetypes)) {
8751 set_user_preference('switchdevice'.$devicetype, $newdevice);
8756 * Returns the device the user is currently using, or if the user has chosen to switch devices
8757 * for the current device type the type they have switched to.
8759 * @return string The device the user is currently using or wishes to use
8761 function get_user_device_type() {
8762 $device = get_device_type();
8763 $switched = get_user_preferences('switchdevice'.$device, false);
8764 if ($switched != false) {
8765 return $switched;
8767 return $device;
8771 * Returns one or several CSS class names that match the user's browser. These can be put
8772 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
8774 * @return array An array of browser version classes
8776 function get_browser_version_classes() {
8777 $classes = array();
8779 if (check_browser_version("MSIE", "0")) {
8780 $classes[] = 'ie';
8781 if (check_browser_version("MSIE", 9)) {
8782 $classes[] = 'ie9';
8783 } else if (check_browser_version("MSIE", 8)) {
8784 $classes[] = 'ie8';
8785 } elseif (check_browser_version("MSIE", 7)) {
8786 $classes[] = 'ie7';
8787 } elseif (check_browser_version("MSIE", 6)) {
8788 $classes[] = 'ie6';
8791 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
8792 $classes[] = 'gecko';
8793 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
8794 $classes[] = "gecko{$matches[1]}{$matches[2]}";
8797 } else if (check_browser_version("WebKit")) {
8798 $classes[] = 'safari';
8799 if (check_browser_version("Safari iOS")) {
8800 $classes[] = 'ios';
8802 } else if (check_browser_version("WebKit Android")) {
8803 $classes[] = 'android';
8806 } else if (check_browser_version("Opera")) {
8807 $classes[] = 'opera';
8811 return $classes;
8815 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
8817 * @return bool True for yes, false for no
8819 function can_use_rotated_text() {
8820 global $USER;
8821 return (check_browser_version('MSIE', 9) || check_browser_version('Firefox', 2) ||
8822 check_browser_version('Chrome', 21) || check_browser_version('Safari', 536.25) ||
8823 check_browser_version('Opera', 12) || check_browser_version('Safari iOS', 533)) &&
8824 !$USER->screenreader;
8828 * Hack to find out the GD version by parsing phpinfo output
8830 * @return int GD version (1, 2, or 0)
8832 function check_gd_version() {
8833 $gdversion = 0;
8835 if (function_exists('gd_info')){
8836 $gd_info = gd_info();
8837 if (substr_count($gd_info['GD Version'], '2.')) {
8838 $gdversion = 2;
8839 } else if (substr_count($gd_info['GD Version'], '1.')) {
8840 $gdversion = 1;
8843 } else {
8844 ob_start();
8845 phpinfo(INFO_MODULES);
8846 $phpinfo = ob_get_contents();
8847 ob_end_clean();
8849 $phpinfo = explode("\n", $phpinfo);
8852 foreach ($phpinfo as $text) {
8853 $parts = explode('</td>', $text);
8854 foreach ($parts as $key => $val) {
8855 $parts[$key] = trim(strip_tags($val));
8857 if ($parts[0] == 'GD Version') {
8858 if (substr_count($parts[1], '2.0')) {
8859 $parts[1] = '2.0';
8861 $gdversion = intval($parts[1]);
8866 return $gdversion; // 1, 2 or 0
8870 * Determine if moodle installation requires update
8872 * Checks version numbers of main code and all modules to see
8873 * if there are any mismatches
8875 * @global moodle_database $DB
8876 * @return bool
8878 function moodle_needs_upgrading() {
8879 global $CFG, $DB, $OUTPUT;
8881 if (empty($CFG->version)) {
8882 return true;
8885 // main versio nfirst
8886 $version = null;
8887 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
8888 if ($version > $CFG->version) {
8889 return true;
8892 // modules
8893 $mods = get_plugin_list('mod');
8894 $installed = $DB->get_records('modules', array(), '', 'name, version');
8895 foreach ($mods as $mod => $fullmod) {
8896 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
8897 continue;
8899 $module = new stdClass();
8900 if (!is_readable($fullmod.'/version.php')) {
8901 continue;
8903 include($fullmod.'/version.php'); // defines $module with version etc
8904 if (empty($installed[$mod])) {
8905 return true;
8906 } else if ($module->version > $installed[$mod]->version) {
8907 return true;
8910 unset($installed);
8912 // blocks
8913 $blocks = get_plugin_list('block');
8914 $installed = $DB->get_records('block', array(), '', 'name, version');
8915 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
8916 foreach ($blocks as $blockname=>$fullblock) {
8917 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
8918 continue;
8920 if (!is_readable($fullblock.'/version.php')) {
8921 continue;
8923 $plugin = new stdClass();
8924 $plugin->version = NULL;
8925 include($fullblock.'/version.php');
8926 if (empty($installed[$blockname])) {
8927 return true;
8928 } else if ($plugin->version > $installed[$blockname]->version) {
8929 return true;
8932 unset($installed);
8934 // now the rest of plugins
8935 $plugintypes = get_plugin_types();
8936 unset($plugintypes['mod']);
8937 unset($plugintypes['block']);
8939 $versions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin, value');
8940 foreach ($plugintypes as $type=>$unused) {
8941 $plugs = get_plugin_list($type);
8942 foreach ($plugs as $plug=>$fullplug) {
8943 $component = $type.'_'.$plug;
8944 if (!is_readable($fullplug.'/version.php')) {
8945 continue;
8947 $plugin = new stdClass();
8948 include($fullplug.'/version.php'); // defines $plugin with version etc
8949 if (array_key_exists($component, $versions)) {
8950 $installedversion = $versions[$component];
8951 } else {
8952 $installedversion = get_config($component, 'version');
8954 if (empty($installedversion)) { // new installation
8955 return true;
8956 } else if ($installedversion < $plugin->version) { // upgrade
8957 return true;
8962 return false;
8966 * Returns the major version of this site
8968 * Moodle version numbers consist of three numbers separated by a dot, for
8969 * example 1.9.11 or 2.0.2. The first two numbers, like 1.9 or 2.0, represent so
8970 * called major version. This function extracts the major version from either
8971 * $CFG->release (default) or eventually from the $release variable defined in
8972 * the main version.php.
8974 * @param bool $fromdisk should the version if source code files be used
8975 * @return string|false the major version like '2.3', false if could not be determined
8977 function moodle_major_version($fromdisk = false) {
8978 global $CFG;
8980 if ($fromdisk) {
8981 $release = null;
8982 require($CFG->dirroot.'/version.php');
8983 if (empty($release)) {
8984 return false;
8987 } else {
8988 if (empty($CFG->release)) {
8989 return false;
8991 $release = $CFG->release;
8994 if (preg_match('/^[0-9]+\.[0-9]+/', $release, $matches)) {
8995 return $matches[0];
8996 } else {
8997 return false;
9001 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
9004 * Sets the system locale
9006 * @category string
9007 * @param string $locale Can be used to force a locale
9009 function moodle_setlocale($locale='') {
9010 global $CFG;
9012 static $currentlocale = ''; // last locale caching
9014 $oldlocale = $currentlocale;
9016 /// Fetch the correct locale based on ostype
9017 if ($CFG->ostype == 'WINDOWS') {
9018 $stringtofetch = 'localewin';
9019 } else {
9020 $stringtofetch = 'locale';
9023 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
9024 if (!empty($locale)) {
9025 $currentlocale = $locale;
9026 } else if (!empty($CFG->locale)) { // override locale for all language packs
9027 $currentlocale = $CFG->locale;
9028 } else {
9029 $currentlocale = get_string($stringtofetch, 'langconfig');
9032 /// do nothing if locale already set up
9033 if ($oldlocale == $currentlocale) {
9034 return;
9037 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
9038 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
9039 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
9041 /// Get current values
9042 $monetary= setlocale (LC_MONETARY, 0);
9043 $numeric = setlocale (LC_NUMERIC, 0);
9044 $ctype = setlocale (LC_CTYPE, 0);
9045 if ($CFG->ostype != 'WINDOWS') {
9046 $messages= setlocale (LC_MESSAGES, 0);
9048 /// Set locale to all
9049 setlocale (LC_ALL, $currentlocale);
9050 /// Set old values
9051 setlocale (LC_MONETARY, $monetary);
9052 setlocale (LC_NUMERIC, $numeric);
9053 if ($CFG->ostype != 'WINDOWS') {
9054 setlocale (LC_MESSAGES, $messages);
9056 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
9057 setlocale (LC_CTYPE, $ctype);
9062 * Count words in a string.
9064 * Words are defined as things between whitespace.
9066 * @category string
9067 * @param string $string The text to be searched for words.
9068 * @return int The count of words in the specified string
9070 function count_words($string) {
9071 $string = strip_tags($string);
9072 return count(preg_split("/\w\b/", $string)) - 1;
9075 /** Count letters in a string.
9077 * Letters are defined as chars not in tags and different from whitespace.
9079 * @category string
9080 * @param string $string The text to be searched for letters.
9081 * @return int The count of letters in the specified text.
9083 function count_letters($string) {
9084 /// Loading the textlib singleton instance. We are going to need it.
9085 $string = strip_tags($string); // Tags are out now
9086 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
9088 return textlib::strlen($string);
9092 * Generate and return a random string of the specified length.
9094 * @param int $length The length of the string to be created.
9095 * @return string
9097 function random_string ($length=15) {
9098 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
9099 $pool .= 'abcdefghijklmnopqrstuvwxyz';
9100 $pool .= '0123456789';
9101 $poollen = strlen($pool);
9102 mt_srand ((double) microtime() * 1000000);
9103 $string = '';
9104 for ($i = 0; $i < $length; $i++) {
9105 $string .= substr($pool, (mt_rand()%($poollen)), 1);
9107 return $string;
9111 * Generate a complex random string (useful for md5 salts)
9113 * This function is based on the above {@link random_string()} however it uses a
9114 * larger pool of characters and generates a string between 24 and 32 characters
9116 * @param int $length Optional if set generates a string to exactly this length
9117 * @return string
9119 function complex_random_string($length=null) {
9120 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
9121 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
9122 $poollen = strlen($pool);
9123 mt_srand ((double) microtime() * 1000000);
9124 if ($length===null) {
9125 $length = floor(rand(24,32));
9127 $string = '';
9128 for ($i = 0; $i < $length; $i++) {
9129 $string .= $pool[(mt_rand()%$poollen)];
9131 return $string;
9135 * Given some text (which may contain HTML) and an ideal length,
9136 * this function truncates the text neatly on a word boundary if possible
9138 * @category string
9139 * @global stdClass $CFG
9140 * @param string $text text to be shortened
9141 * @param int $ideal ideal string length
9142 * @param boolean $exact if false, $text will not be cut mid-word
9143 * @param string $ending The string to append if the passed string is truncated
9144 * @return string $truncate shortened string
9146 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
9148 global $CFG;
9150 // If the plain text is shorter than the maximum length, return the whole text.
9151 if (textlib::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
9152 return $text;
9155 // Splits on HTML tags. Each open/close/empty tag will be the first thing
9156 // and only tag in its 'line'.
9157 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
9159 $total_length = textlib::strlen($ending);
9160 $truncate = '';
9162 // This array stores information about open and close tags and their position
9163 // in the truncated string. Each item in the array is an object with fields
9164 // ->open (true if open), ->tag (tag name in lower case), and ->pos
9165 // (byte position in truncated text).
9166 $tagdetails = array();
9168 foreach ($lines as $line_matchings) {
9169 // If there is any html-tag in this line, handle it and add it (uncounted) to the output.
9170 if (!empty($line_matchings[1])) {
9171 // If it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>).
9172 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
9173 // Do nothing.
9175 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
9176 // Record closing tag.
9177 $tagdetails[] = (object) array(
9178 'open' => false,
9179 'tag' => textlib::strtolower($tag_matchings[1]),
9180 'pos' => textlib::strlen($truncate),
9183 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
9184 // Record opening tag.
9185 $tagdetails[] = (object) array(
9186 'open' => true,
9187 'tag' => textlib::strtolower($tag_matchings[1]),
9188 'pos' => textlib::strlen($truncate),
9191 // Add html-tag to $truncate'd text.
9192 $truncate .= $line_matchings[1];
9195 // Calculate the length of the plain text part of the line; handle entities as one character.
9196 $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]));
9197 if ($total_length + $content_length > $ideal) {
9198 // The number of characters which are left.
9199 $left = $ideal - $total_length;
9200 $entities_length = 0;
9201 // Search for html entities.
9202 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)) {
9203 // calculate the real length of all entities in the legal range
9204 foreach ($entities[0] as $entity) {
9205 if ($entity[1]+1-$entities_length <= $left) {
9206 $left--;
9207 $entities_length += textlib::strlen($entity[0]);
9208 } else {
9209 // no more characters left
9210 break;
9214 $breakpos = $left + $entities_length;
9216 // if the words shouldn't be cut in the middle...
9217 if (!$exact) {
9218 // ...search the last occurence of a space...
9219 for (; $breakpos > 0; $breakpos--) {
9220 if ($char = textlib::substr($line_matchings[2], $breakpos, 1)) {
9221 if ($char === '.' or $char === ' ') {
9222 $breakpos += 1;
9223 break;
9224 } else if (strlen($char) > 2) { // Chinese/Japanese/Korean text
9225 $breakpos += 1; // can be truncated at any UTF-8
9226 break; // character boundary.
9231 if ($breakpos == 0) {
9232 // This deals with the test_shorten_text_no_spaces case.
9233 $breakpos = $left + $entities_length;
9234 } else if ($breakpos > $left + $entities_length) {
9235 // This deals with the previous for loop breaking on the first char.
9236 $breakpos = $left + $entities_length;
9239 $truncate .= textlib::substr($line_matchings[2], 0, $breakpos);
9240 // maximum length is reached, so get off the loop
9241 break;
9242 } else {
9243 $truncate .= $line_matchings[2];
9244 $total_length += $content_length;
9247 // If the maximum length is reached, get off the loop.
9248 if($total_length >= $ideal) {
9249 break;
9253 // Add the defined ending to the text.
9254 $truncate .= $ending;
9256 // Now calculate the list of open html tags based on the truncate position.
9257 $open_tags = array();
9258 foreach ($tagdetails as $taginfo) {
9259 if ($taginfo->open) {
9260 // Add tag to the beginning of $open_tags list.
9261 array_unshift($open_tags, $taginfo->tag);
9262 } else {
9263 // Can have multiple exact same open tags, close the last one.
9264 $pos = array_search($taginfo->tag, array_reverse($open_tags, true));
9265 if ($pos !== false) {
9266 unset($open_tags[$pos]);
9271 // Close all unclosed html-tags.
9272 foreach ($open_tags as $tag) {
9273 $truncate .= '</' . $tag . '>';
9276 return $truncate;
9281 * Given dates in seconds, how many weeks is the date from startdate
9282 * The first week is 1, the second 2 etc ...
9284 * @todo Finish documenting this function
9286 * @uses WEEKSECS
9287 * @param int $startdate Timestamp for the start date
9288 * @param int $thedate Timestamp for the end date
9289 * @return string
9291 function getweek ($startdate, $thedate) {
9292 if ($thedate < $startdate) { // error
9293 return 0;
9296 return floor(($thedate - $startdate) / WEEKSECS) + 1;
9300 * returns a randomly generated password of length $maxlen. inspired by
9302 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
9303 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
9305 * @global stdClass $CFG
9306 * @param int $maxlen The maximum size of the password being generated.
9307 * @return string
9309 function generate_password($maxlen=10) {
9310 global $CFG;
9312 if (empty($CFG->passwordpolicy)) {
9313 $fillers = PASSWORD_DIGITS;
9314 $wordlist = file($CFG->wordlist);
9315 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9316 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9317 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
9318 $password = $word1 . $filler1 . $word2;
9319 } else {
9320 $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
9321 $digits = $CFG->minpassworddigits;
9322 $lower = $CFG->minpasswordlower;
9323 $upper = $CFG->minpasswordupper;
9324 $nonalphanum = $CFG->minpasswordnonalphanum;
9325 $total = $lower + $upper + $digits + $nonalphanum;
9326 // minlength should be the greater one of the two ( $minlen and $total )
9327 $minlen = $minlen < $total ? $total : $minlen;
9328 // maxlen can never be smaller than minlen
9329 $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
9330 $additional = $maxlen - $total;
9332 // Make sure we have enough characters to fulfill
9333 // complexity requirements
9334 $passworddigits = PASSWORD_DIGITS;
9335 while ($digits > strlen($passworddigits)) {
9336 $passworddigits .= PASSWORD_DIGITS;
9338 $passwordlower = PASSWORD_LOWER;
9339 while ($lower > strlen($passwordlower)) {
9340 $passwordlower .= PASSWORD_LOWER;
9342 $passwordupper = PASSWORD_UPPER;
9343 while ($upper > strlen($passwordupper)) {
9344 $passwordupper .= PASSWORD_UPPER;
9346 $passwordnonalphanum = PASSWORD_NONALPHANUM;
9347 while ($nonalphanum > strlen($passwordnonalphanum)) {
9348 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
9351 // Now mix and shuffle it all
9352 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
9353 substr(str_shuffle ($passwordupper), 0, $upper) .
9354 substr(str_shuffle ($passworddigits), 0, $digits) .
9355 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
9356 substr(str_shuffle ($passwordlower .
9357 $passwordupper .
9358 $passworddigits .
9359 $passwordnonalphanum), 0 , $additional));
9362 return substr ($password, 0, $maxlen);
9366 * Given a float, prints it nicely.
9367 * Localized floats must not be used in calculations!
9369 * The stripzeros feature is intended for making numbers look nicer in small
9370 * areas where it is not necessary to indicate the degree of accuracy by showing
9371 * ending zeros. If you turn it on with $decimalpoints set to 3, for example,
9372 * then it will display '5.4' instead of '5.400' or '5' instead of '5.000'.
9374 * @param float $float The float to print
9375 * @param int $decimalpoints The number of decimal places to print.
9376 * @param bool $localized use localized decimal separator
9377 * @param bool $stripzeros If true, removes final zeros after decimal point
9378 * @return string locale float
9380 function format_float($float, $decimalpoints=1, $localized=true, $stripzeros=false) {
9381 if (is_null($float)) {
9382 return '';
9384 if ($localized) {
9385 $separator = get_string('decsep', 'langconfig');
9386 } else {
9387 $separator = '.';
9389 $result = number_format($float, $decimalpoints, $separator, '');
9390 if ($stripzeros) {
9391 // Remove zeros and final dot if not needed
9392 $result = preg_replace('~(' . preg_quote($separator) . ')?0+$~', '', $result);
9394 return $result;
9398 * Converts locale specific floating point/comma number back to standard PHP float value
9399 * Do NOT try to do any math operations before this conversion on any user submitted floats!
9401 * @param string $locale_float locale aware float representation
9402 * @return float
9404 function unformat_float($locale_float) {
9405 $locale_float = trim($locale_float);
9407 if ($locale_float == '') {
9408 return null;
9411 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
9413 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
9417 * Given a simple array, this shuffles it up just like shuffle()
9418 * Unlike PHP's shuffle() this function works on any machine.
9420 * @param array $array The array to be rearranged
9421 * @return array
9423 function swapshuffle($array) {
9425 srand ((double) microtime() * 10000000);
9426 $last = count($array) - 1;
9427 for ($i=0;$i<=$last;$i++) {
9428 $from = rand(0,$last);
9429 $curr = $array[$i];
9430 $array[$i] = $array[$from];
9431 $array[$from] = $curr;
9433 return $array;
9437 * Like {@link swapshuffle()}, but works on associative arrays
9439 * @param array $array The associative array to be rearranged
9440 * @return array
9442 function swapshuffle_assoc($array) {
9444 $newarray = array();
9445 $newkeys = swapshuffle(array_keys($array));
9447 foreach ($newkeys as $newkey) {
9448 $newarray[$newkey] = $array[$newkey];
9450 return $newarray;
9454 * Given an arbitrary array, and a number of draws,
9455 * this function returns an array with that amount
9456 * of items. The indexes are retained.
9458 * @todo Finish documenting this function
9460 * @param array $array
9461 * @param int $draws
9462 * @return array
9464 function draw_rand_array($array, $draws) {
9465 srand ((double) microtime() * 10000000);
9467 $return = array();
9469 $last = count($array);
9471 if ($draws > $last) {
9472 $draws = $last;
9475 while ($draws > 0) {
9476 $last--;
9478 $keys = array_keys($array);
9479 $rand = rand(0, $last);
9481 $return[$keys[$rand]] = $array[$keys[$rand]];
9482 unset($array[$keys[$rand]]);
9484 $draws--;
9487 return $return;
9491 * Calculate the difference between two microtimes
9493 * @param string $a The first Microtime
9494 * @param string $b The second Microtime
9495 * @return string
9497 function microtime_diff($a, $b) {
9498 list($a_dec, $a_sec) = explode(' ', $a);
9499 list($b_dec, $b_sec) = explode(' ', $b);
9500 return $b_sec - $a_sec + $b_dec - $a_dec;
9504 * Given a list (eg a,b,c,d,e) this function returns
9505 * an array of 1->a, 2->b, 3->c etc
9507 * @param string $list The string to explode into array bits
9508 * @param string $separator The separator used within the list string
9509 * @return array The now assembled array
9511 function make_menu_from_list($list, $separator=',') {
9513 $array = array_reverse(explode($separator, $list), true);
9514 foreach ($array as $key => $item) {
9515 $outarray[$key+1] = trim($item);
9517 return $outarray;
9521 * Creates an array that represents all the current grades that
9522 * can be chosen using the given grading type.
9524 * Negative numbers
9525 * are scales, zero is no grade, and positive numbers are maximum
9526 * grades.
9528 * @todo Finish documenting this function or better deprecated this completely!
9530 * @param int $gradingtype
9531 * @return array
9533 function make_grades_menu($gradingtype) {
9534 global $DB;
9536 $grades = array();
9537 if ($gradingtype < 0) {
9538 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
9539 return make_menu_from_list($scale->scale);
9541 } else if ($gradingtype > 0) {
9542 for ($i=$gradingtype; $i>=0; $i--) {
9543 $grades[$i] = $i .' / '. $gradingtype;
9545 return $grades;
9547 return $grades;
9551 * This function returns the number of activities
9552 * using scaleid in a courseid
9554 * @todo Finish documenting this function
9556 * @global object
9557 * @global object
9558 * @param int $courseid ?
9559 * @param int $scaleid ?
9560 * @return int
9562 function course_scale_used($courseid, $scaleid) {
9563 global $CFG, $DB;
9565 $return = 0;
9567 if (!empty($scaleid)) {
9568 if ($cms = get_course_mods($courseid)) {
9569 foreach ($cms as $cm) {
9570 //Check cm->name/lib.php exists
9571 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
9572 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
9573 $function_name = $cm->modname.'_scale_used';
9574 if (function_exists($function_name)) {
9575 if ($function_name($cm->instance,$scaleid)) {
9576 $return++;
9583 // check if any course grade item makes use of the scale
9584 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
9586 // check if any outcome in the course makes use of the scale
9587 $return += $DB->count_records_sql("SELECT COUNT('x')
9588 FROM {grade_outcomes_courses} goc,
9589 {grade_outcomes} go
9590 WHERE go.id = goc.outcomeid
9591 AND go.scaleid = ? AND goc.courseid = ?",
9592 array($scaleid, $courseid));
9594 return $return;
9598 * This function returns the number of activities
9599 * using scaleid in the entire site
9601 * @param int $scaleid
9602 * @param array $courses
9603 * @return int
9605 function site_scale_used($scaleid, &$courses) {
9606 $return = 0;
9608 if (!is_array($courses) || count($courses) == 0) {
9609 $courses = get_courses("all",false,"c.id,c.shortname");
9612 if (!empty($scaleid)) {
9613 if (is_array($courses) && count($courses) > 0) {
9614 foreach ($courses as $course) {
9615 $return += course_scale_used($course->id,$scaleid);
9619 return $return;
9623 * make_unique_id_code
9625 * @todo Finish documenting this function
9627 * @uses $_SERVER
9628 * @param string $extra Extra string to append to the end of the code
9629 * @return string
9631 function make_unique_id_code($extra='') {
9633 $hostname = 'unknownhost';
9634 if (!empty($_SERVER['HTTP_HOST'])) {
9635 $hostname = $_SERVER['HTTP_HOST'];
9636 } else if (!empty($_ENV['HTTP_HOST'])) {
9637 $hostname = $_ENV['HTTP_HOST'];
9638 } else if (!empty($_SERVER['SERVER_NAME'])) {
9639 $hostname = $_SERVER['SERVER_NAME'];
9640 } else if (!empty($_ENV['SERVER_NAME'])) {
9641 $hostname = $_ENV['SERVER_NAME'];
9644 $date = gmdate("ymdHis");
9646 $random = random_string(6);
9648 if ($extra) {
9649 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
9650 } else {
9651 return $hostname .'+'. $date .'+'. $random;
9657 * Function to check the passed address is within the passed subnet
9659 * The parameter is a comma separated string of subnet definitions.
9660 * Subnet strings can be in one of three formats:
9661 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
9662 * 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)
9663 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
9664 * Code for type 1 modified from user posted comments by mediator at
9665 * {@link http://au.php.net/manual/en/function.ip2long.php}
9667 * @param string $addr The address you are checking
9668 * @param string $subnetstr The string of subnet addresses
9669 * @return bool
9671 function address_in_subnet($addr, $subnetstr) {
9673 if ($addr == '0.0.0.0') {
9674 return false;
9676 $subnets = explode(',', $subnetstr);
9677 $found = false;
9678 $addr = trim($addr);
9679 $addr = cleanremoteaddr($addr, false); // normalise
9680 if ($addr === null) {
9681 return false;
9683 $addrparts = explode(':', $addr);
9685 $ipv6 = strpos($addr, ':');
9687 foreach ($subnets as $subnet) {
9688 $subnet = trim($subnet);
9689 if ($subnet === '') {
9690 continue;
9693 if (strpos($subnet, '/') !== false) {
9694 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
9695 list($ip, $mask) = explode('/', $subnet);
9696 $mask = trim($mask);
9697 if (!is_number($mask)) {
9698 continue; // incorect mask number, eh?
9700 $ip = cleanremoteaddr($ip, false); // normalise
9701 if ($ip === null) {
9702 continue;
9704 if (strpos($ip, ':') !== false) {
9705 // IPv6
9706 if (!$ipv6) {
9707 continue;
9709 if ($mask > 128 or $mask < 0) {
9710 continue; // nonsense
9712 if ($mask == 0) {
9713 return true; // any address
9715 if ($mask == 128) {
9716 if ($ip === $addr) {
9717 return true;
9719 continue;
9721 $ipparts = explode(':', $ip);
9722 $modulo = $mask % 16;
9723 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
9724 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
9725 if (implode(':', $ipnet) === implode(':', $addrnet)) {
9726 if ($modulo == 0) {
9727 return true;
9729 $pos = ($mask-$modulo)/16;
9730 $ipnet = hexdec($ipparts[$pos]);
9731 $addrnet = hexdec($addrparts[$pos]);
9732 $mask = 0xffff << (16 - $modulo);
9733 if (($addrnet & $mask) == ($ipnet & $mask)) {
9734 return true;
9738 } else {
9739 // IPv4
9740 if ($ipv6) {
9741 continue;
9743 if ($mask > 32 or $mask < 0) {
9744 continue; // nonsense
9746 if ($mask == 0) {
9747 return true;
9749 if ($mask == 32) {
9750 if ($ip === $addr) {
9751 return true;
9753 continue;
9755 $mask = 0xffffffff << (32 - $mask);
9756 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
9757 return true;
9761 } else if (strpos($subnet, '-') !== false) {
9762 /// 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.
9763 $parts = explode('-', $subnet);
9764 if (count($parts) != 2) {
9765 continue;
9768 if (strpos($subnet, ':') !== false) {
9769 // IPv6
9770 if (!$ipv6) {
9771 continue;
9773 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9774 if ($ipstart === null) {
9775 continue;
9777 $ipparts = explode(':', $ipstart);
9778 $start = hexdec(array_pop($ipparts));
9779 $ipparts[] = trim($parts[1]);
9780 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
9781 if ($ipend === null) {
9782 continue;
9784 $ipparts[7] = '';
9785 $ipnet = implode(':', $ipparts);
9786 if (strpos($addr, $ipnet) !== 0) {
9787 continue;
9789 $ipparts = explode(':', $ipend);
9790 $end = hexdec($ipparts[7]);
9792 $addrend = hexdec($addrparts[7]);
9794 if (($addrend >= $start) and ($addrend <= $end)) {
9795 return true;
9798 } else {
9799 // IPv4
9800 if ($ipv6) {
9801 continue;
9803 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9804 if ($ipstart === null) {
9805 continue;
9807 $ipparts = explode('.', $ipstart);
9808 $ipparts[3] = trim($parts[1]);
9809 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
9810 if ($ipend === null) {
9811 continue;
9814 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
9815 return true;
9819 } else {
9820 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
9821 if (strpos($subnet, ':') !== false) {
9822 // IPv6
9823 if (!$ipv6) {
9824 continue;
9826 $parts = explode(':', $subnet);
9827 $count = count($parts);
9828 if ($parts[$count-1] === '') {
9829 unset($parts[$count-1]); // trim trailing :
9830 $count--;
9831 $subnet = implode('.', $parts);
9833 $isip = cleanremoteaddr($subnet, false); // normalise
9834 if ($isip !== null) {
9835 if ($isip === $addr) {
9836 return true;
9838 continue;
9839 } else if ($count > 8) {
9840 continue;
9842 $zeros = array_fill(0, 8-$count, '0');
9843 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
9844 if (address_in_subnet($addr, $subnet)) {
9845 return true;
9848 } else {
9849 // IPv4
9850 if ($ipv6) {
9851 continue;
9853 $parts = explode('.', $subnet);
9854 $count = count($parts);
9855 if ($parts[$count-1] === '') {
9856 unset($parts[$count-1]); // trim trailing .
9857 $count--;
9858 $subnet = implode('.', $parts);
9860 if ($count == 4) {
9861 $subnet = cleanremoteaddr($subnet, false); // normalise
9862 if ($subnet === $addr) {
9863 return true;
9865 continue;
9866 } else if ($count > 4) {
9867 continue;
9869 $zeros = array_fill(0, 4-$count, '0');
9870 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
9871 if (address_in_subnet($addr, $subnet)) {
9872 return true;
9878 return false;
9882 * For outputting debugging info
9884 * @uses STDOUT
9885 * @param string $string The string to write
9886 * @param string $eol The end of line char(s) to use
9887 * @param string $sleep Period to make the application sleep
9888 * This ensures any messages have time to display before redirect
9890 function mtrace($string, $eol="\n", $sleep=0) {
9892 if (defined('STDOUT') and !PHPUNIT_TEST) {
9893 fwrite(STDOUT, $string.$eol);
9894 } else {
9895 echo $string . $eol;
9898 flush();
9900 //delay to keep message on user's screen in case of subsequent redirect
9901 if ($sleep) {
9902 sleep($sleep);
9907 * Replace 1 or more slashes or backslashes to 1 slash
9909 * @param string $path The path to strip
9910 * @return string the path with double slashes removed
9912 function cleardoubleslashes ($path) {
9913 return preg_replace('/(\/|\\\){1,}/','/',$path);
9917 * Is current ip in give list?
9919 * @param string $list
9920 * @return bool
9922 function remoteip_in_list($list){
9923 $inlist = false;
9924 $client_ip = getremoteaddr(null);
9926 if(!$client_ip){
9927 // ensure access on cli
9928 return true;
9931 $list = explode("\n", $list);
9932 foreach($list as $subnet) {
9933 $subnet = trim($subnet);
9934 if (address_in_subnet($client_ip, $subnet)) {
9935 $inlist = true;
9936 break;
9939 return $inlist;
9943 * Returns most reliable client address
9945 * @global object
9946 * @param string $default If an address can't be determined, then return this
9947 * @return string The remote IP address
9949 function getremoteaddr($default='0.0.0.0') {
9950 global $CFG;
9952 if (empty($CFG->getremoteaddrconf)) {
9953 // This will happen, for example, before just after the upgrade, as the
9954 // user is redirected to the admin screen.
9955 $variablestoskip = 0;
9956 } else {
9957 $variablestoskip = $CFG->getremoteaddrconf;
9959 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
9960 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
9961 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
9962 return $address ? $address : $default;
9965 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
9966 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
9967 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
9968 return $address ? $address : $default;
9971 if (!empty($_SERVER['REMOTE_ADDR'])) {
9972 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
9973 return $address ? $address : $default;
9974 } else {
9975 return $default;
9980 * Cleans an ip address. Internal addresses are now allowed.
9981 * (Originally local addresses were not allowed.)
9983 * @param string $addr IPv4 or IPv6 address
9984 * @param bool $compress use IPv6 address compression
9985 * @return string normalised ip address string, null if error
9987 function cleanremoteaddr($addr, $compress=false) {
9988 $addr = trim($addr);
9990 //TODO: maybe add a separate function is_addr_public() or something like this
9992 if (strpos($addr, ':') !== false) {
9993 // can be only IPv6
9994 $parts = explode(':', $addr);
9995 $count = count($parts);
9997 if (strpos($parts[$count-1], '.') !== false) {
9998 //legacy ipv4 notation
9999 $last = array_pop($parts);
10000 $ipv4 = cleanremoteaddr($last, true);
10001 if ($ipv4 === null) {
10002 return null;
10004 $bits = explode('.', $ipv4);
10005 $parts[] = dechex($bits[0]).dechex($bits[1]);
10006 $parts[] = dechex($bits[2]).dechex($bits[3]);
10007 $count = count($parts);
10008 $addr = implode(':', $parts);
10011 if ($count < 3 or $count > 8) {
10012 return null; // severly malformed
10015 if ($count != 8) {
10016 if (strpos($addr, '::') === false) {
10017 return null; // malformed
10019 // uncompress ::
10020 $insertat = array_search('', $parts, true);
10021 $missing = array_fill(0, 1 + 8 - $count, '0');
10022 array_splice($parts, $insertat, 1, $missing);
10023 foreach ($parts as $key=>$part) {
10024 if ($part === '') {
10025 $parts[$key] = '0';
10030 $adr = implode(':', $parts);
10031 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
10032 return null; // incorrect format - sorry
10035 // normalise 0s and case
10036 $parts = array_map('hexdec', $parts);
10037 $parts = array_map('dechex', $parts);
10039 $result = implode(':', $parts);
10041 if (!$compress) {
10042 return $result;
10045 if ($result === '0:0:0:0:0:0:0:0') {
10046 return '::'; // all addresses
10049 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
10050 if ($compressed !== $result) {
10051 return $compressed;
10054 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
10055 if ($compressed !== $result) {
10056 return $compressed;
10059 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
10060 if ($compressed !== $result) {
10061 return $compressed;
10064 return $result;
10067 // first get all things that look like IPv4 addresses
10068 $parts = array();
10069 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
10070 return null;
10072 unset($parts[0]);
10074 foreach ($parts as $key=>$match) {
10075 if ($match > 255) {
10076 return null;
10078 $parts[$key] = (int)$match; // normalise 0s
10081 return implode('.', $parts);
10085 * This function will make a complete copy of anything it's given,
10086 * regardless of whether it's an object or not.
10088 * @param mixed $thing Something you want cloned
10089 * @return mixed What ever it is you passed it
10091 function fullclone($thing) {
10092 return unserialize(serialize($thing));
10097 * This function expects to called during shutdown
10098 * should be set via register_shutdown_function()
10099 * in lib/setup.php .
10101 * @return void
10103 function moodle_request_shutdown() {
10104 global $CFG;
10106 // help apache server if possible
10107 $apachereleasemem = false;
10108 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
10109 && ini_get_bool('child_terminate')) {
10111 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
10112 if (memory_get_usage() > get_real_size($limit)) {
10113 $apachereleasemem = $limit;
10114 @apache_child_terminate();
10118 // deal with perf logging
10119 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
10120 if ($apachereleasemem) {
10121 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
10123 if (defined('MDL_PERFTOLOG')) {
10124 $perf = get_performance_info();
10125 error_log("PERF: " . $perf['txt']);
10127 if (defined('MDL_PERFINC')) {
10128 $inc = get_included_files();
10129 $ts = 0;
10130 foreach($inc as $f) {
10131 if (preg_match(':^/:', $f)) {
10132 $fs = filesize($f);
10133 $ts += $fs;
10134 $hfs = display_size($fs);
10135 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
10136 , NULL, NULL, 0);
10137 } else {
10138 error_log($f , NULL, NULL, 0);
10141 if ($ts > 0 ) {
10142 $hts = display_size($ts);
10143 error_log("Total size of files included: $ts ($hts)");
10150 * If new messages are waiting for the current user, then insert
10151 * JavaScript to pop up the messaging window into the page
10153 * @global moodle_page $PAGE
10154 * @return void
10156 function message_popup_window() {
10157 global $USER, $DB, $PAGE, $CFG, $SITE;
10159 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
10160 return;
10163 if (!isloggedin() || isguestuser()) {
10164 return;
10167 if (!isset($USER->message_lastpopup)) {
10168 $USER->message_lastpopup = 0;
10169 } else if ($USER->message_lastpopup > (time()-120)) {
10170 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
10171 return;
10174 //a quick query to check whether the user has new messages
10175 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
10176 if ($messagecount<1) {
10177 return;
10180 //got unread messages so now do another query that joins with the user table
10181 $messagesql = "SELECT m.id, m.smallmessage, m.fullmessageformat, m.notification, u.firstname, u.lastname
10182 FROM {message} m
10183 JOIN {message_working} mw ON m.id=mw.unreadmessageid
10184 JOIN {message_processors} p ON mw.processorid=p.id
10185 JOIN {user} u ON m.useridfrom=u.id
10186 WHERE m.useridto = :userid
10187 AND p.name='popup'";
10189 //if the user was last notified over an hour ago we can renotify them of old messages
10190 //so don't worry about when the new message was sent
10191 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
10192 if (!$lastnotifiedlongago) {
10193 $messagesql .= 'AND m.timecreated > :lastpopuptime';
10196 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
10198 //if we have new messages to notify the user about
10199 if (!empty($message_users)) {
10201 $strmessages = '';
10202 if (count($message_users)>1) {
10203 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
10204 } else {
10205 $message_users = reset($message_users);
10207 //show who the message is from if its not a notification
10208 if (!$message_users->notification) {
10209 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
10212 //try to display the small version of the message
10213 $smallmessage = null;
10214 if (!empty($message_users->smallmessage)) {
10215 //display the first 200 chars of the message in the popup
10216 $smallmessage = null;
10217 if (textlib::strlen($message_users->smallmessage) > 200) {
10218 $smallmessage = textlib::substr($message_users->smallmessage,0,200).'...';
10219 } else {
10220 $smallmessage = $message_users->smallmessage;
10223 //prevent html symbols being displayed
10224 if ($message_users->fullmessageformat == FORMAT_HTML) {
10225 $smallmessage = html_to_text($smallmessage);
10226 } else {
10227 $smallmessage = s($smallmessage);
10229 } else if ($message_users->notification) {
10230 //its a notification with no smallmessage so just say they have a notification
10231 $smallmessage = get_string('unreadnewnotification', 'message');
10233 if (!empty($smallmessage)) {
10234 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
10238 $strgomessage = get_string('gotomessages', 'message');
10239 $strstaymessage = get_string('ignore','admin');
10241 $url = $CFG->wwwroot.'/message/index.php';
10242 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
10243 html_writer::start_tag('div', array('id'=>'newmessagetext')).
10244 $strmessages.
10245 html_writer::end_tag('div').
10247 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
10248 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
10249 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
10250 html_writer::end_tag('div');
10251 html_writer::end_tag('div');
10253 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
10255 $USER->message_lastpopup = time();
10260 * Used to make sure that $min <= $value <= $max
10262 * Make sure that value is between min, and max
10264 * @param int $min The minimum value
10265 * @param int $value The value to check
10266 * @param int $max The maximum value
10268 function bounded_number($min, $value, $max) {
10269 if($value < $min) {
10270 return $min;
10272 if($value > $max) {
10273 return $max;
10275 return $value;
10279 * Check if there is a nested array within the passed array
10281 * @param array $array
10282 * @return bool true if there is a nested array false otherwise
10284 function array_is_nested($array) {
10285 foreach ($array as $value) {
10286 if (is_array($value)) {
10287 return true;
10290 return false;
10294 * get_performance_info() pairs up with init_performance_info()
10295 * loaded in setup.php. Returns an array with 'html' and 'txt'
10296 * values ready for use, and each of the individual stats provided
10297 * separately as well.
10299 * @global object
10300 * @global object
10301 * @global object
10302 * @return array
10304 function get_performance_info() {
10305 global $CFG, $PERF, $DB, $PAGE;
10307 $info = array();
10308 $info['html'] = ''; // holds userfriendly HTML representation
10309 $info['txt'] = me() . ' '; // holds log-friendly representation
10311 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
10313 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
10314 $info['txt'] .= 'time: '.$info['realtime'].'s ';
10316 if (function_exists('memory_get_usage')) {
10317 $info['memory_total'] = memory_get_usage();
10318 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
10319 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
10320 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
10323 if (function_exists('memory_get_peak_usage')) {
10324 $info['memory_peak'] = memory_get_peak_usage();
10325 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
10326 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
10329 $inc = get_included_files();
10330 //error_log(print_r($inc,1));
10331 $info['includecount'] = count($inc);
10332 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
10333 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
10335 $filtermanager = filter_manager::instance();
10336 if (method_exists($filtermanager, 'get_performance_summary')) {
10337 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
10338 $info = array_merge($filterinfo, $info);
10339 foreach ($filterinfo as $key => $value) {
10340 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10341 $info['txt'] .= "$key: $value ";
10345 $stringmanager = get_string_manager();
10346 if (method_exists($stringmanager, 'get_performance_summary')) {
10347 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
10348 $info = array_merge($filterinfo, $info);
10349 foreach ($filterinfo as $key => $value) {
10350 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10351 $info['txt'] .= "$key: $value ";
10355 $jsmodules = $PAGE->requires->get_loaded_modules();
10356 if ($jsmodules) {
10357 $yuicount = 0;
10358 $othercount = 0;
10359 $details = '';
10360 foreach ($jsmodules as $module => $backtraces) {
10361 if (strpos($module, 'yui') === 0) {
10362 $yuicount += 1;
10363 } else {
10364 $othercount += 1;
10366 if (!empty($CFG->yuimoduledebug)) {
10367 // hidden feature for developers working on YUI module infrastructure
10368 $details .= "<div class='yui-module'><p>$module</p>";
10369 foreach ($backtraces as $backtrace) {
10370 $details .= "<div class='backtrace'>$backtrace</div>";
10372 $details .= '</div>';
10375 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
10376 $info['txt'] .= "includedyuimodules: $yuicount ";
10377 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
10378 $info['txt'] .= "includedjsmodules: $othercount ";
10379 if ($details) {
10380 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
10384 if (!empty($PERF->logwrites)) {
10385 $info['logwrites'] = $PERF->logwrites;
10386 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
10387 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
10390 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
10391 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
10392 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
10394 if (function_exists('posix_times')) {
10395 $ptimes = posix_times();
10396 if (is_array($ptimes)) {
10397 foreach ($ptimes as $key => $val) {
10398 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
10400 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
10401 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
10405 // Grab the load average for the last minute
10406 // /proc will only work under some linux configurations
10407 // while uptime is there under MacOSX/Darwin and other unices
10408 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
10409 list($server_load) = explode(' ', $loadavg[0]);
10410 unset($loadavg);
10411 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
10412 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
10413 $server_load = $matches[1];
10414 } else {
10415 trigger_error('Could not parse uptime output!');
10418 if (!empty($server_load)) {
10419 $info['serverload'] = $server_load;
10420 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
10421 $info['txt'] .= "serverload: {$info['serverload']} ";
10424 // Display size of session if session started
10425 if (session_id()) {
10426 $info['sessionsize'] = display_size(strlen(session_encode()));
10427 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
10428 $info['txt'] .= "Session: {$info['sessionsize']} ";
10431 /* if (isset($rcache->hits) && isset($rcache->misses)) {
10432 $info['rcachehits'] = $rcache->hits;
10433 $info['rcachemisses'] = $rcache->misses;
10434 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
10435 "{$rcache->hits}/{$rcache->misses}</span> ";
10436 $info['txt'] .= 'rcache: '.
10437 "{$rcache->hits}/{$rcache->misses} ";
10439 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
10440 return $info;
10444 * @todo Document this function linux people
10446 function apd_get_profiling() {
10447 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
10451 * Delete directory or only its content
10453 * @param string $dir directory path
10454 * @param bool $content_only
10455 * @return bool success, true also if dir does not exist
10457 function remove_dir($dir, $content_only=false) {
10458 if (!file_exists($dir)) {
10459 // nothing to do
10460 return true;
10462 if (!$handle = opendir($dir)) {
10463 return false;
10465 $result = true;
10466 while (false!==($item = readdir($handle))) {
10467 if($item != '.' && $item != '..') {
10468 if(is_dir($dir.'/'.$item)) {
10469 $result = remove_dir($dir.'/'.$item) && $result;
10470 }else{
10471 $result = unlink($dir.'/'.$item) && $result;
10475 closedir($handle);
10476 if ($content_only) {
10477 clearstatcache(); // make sure file stat cache is properly invalidated
10478 return $result;
10480 $result = rmdir($dir); // if anything left the result will be false, no need for && $result
10481 clearstatcache(); // make sure file stat cache is properly invalidated
10482 return $result;
10486 * Detect if an object or a class contains a given property
10487 * will take an actual object or the name of a class
10489 * @param mix $obj Name of class or real object to test
10490 * @param string $property name of property to find
10491 * @return bool true if property exists
10493 function object_property_exists( $obj, $property ) {
10494 if (is_string( $obj )) {
10495 $properties = get_class_vars( $obj );
10497 else {
10498 $properties = get_object_vars( $obj );
10500 return array_key_exists( $property, $properties );
10504 * Converts an object into an associative array
10506 * This function converts an object into an associative array by iterating
10507 * over its public properties. Because this function uses the foreach
10508 * construct, Iterators are respected. It works recursively on arrays of objects.
10509 * Arrays and simple values are returned as is.
10511 * If class has magic properties, it can implement IteratorAggregate
10512 * and return all available properties in getIterator()
10514 * @param mixed $var
10515 * @return array
10517 function convert_to_array($var) {
10518 $result = array();
10520 // loop over elements/properties
10521 foreach ($var as $key => $value) {
10522 // recursively convert objects
10523 if (is_object($value) || is_array($value)) {
10524 $result[$key] = convert_to_array($value);
10525 } else {
10526 // simple values are untouched
10527 $result[$key] = $value;
10530 return $result;
10534 * Detect a custom script replacement in the data directory that will
10535 * replace an existing moodle script
10537 * @return string|bool full path name if a custom script exists, false if no custom script exists
10539 function custom_script_path() {
10540 global $CFG, $SCRIPT;
10542 if ($SCRIPT === null) {
10543 // Probably some weird external script
10544 return false;
10547 $scriptpath = $CFG->customscripts . $SCRIPT;
10549 // check the custom script exists
10550 if (file_exists($scriptpath) and is_file($scriptpath)) {
10551 return $scriptpath;
10552 } else {
10553 return false;
10558 * Returns whether or not the user object is a remote MNET user. This function
10559 * is in moodlelib because it does not rely on loading any of the MNET code.
10561 * @global object
10562 * @param object $user A valid user object
10563 * @return bool True if the user is from a remote Moodle.
10565 function is_mnet_remote_user($user) {
10566 global $CFG;
10568 if (!isset($CFG->mnet_localhost_id)) {
10569 include_once $CFG->dirroot . '/mnet/lib.php';
10570 $env = new mnet_environment();
10571 $env->init();
10572 unset($env);
10575 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
10579 * This function will search for browser prefereed languages, setting Moodle
10580 * to use the best one available if $SESSION->lang is undefined
10582 * @global object
10583 * @global object
10584 * @global object
10586 function setup_lang_from_browser() {
10588 global $CFG, $SESSION, $USER;
10590 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
10591 // Lang is defined in session or user profile, nothing to do
10592 return;
10595 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
10596 return;
10599 /// Extract and clean langs from headers
10600 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
10601 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
10602 $rawlangs = explode(',', $rawlangs); // Convert to array
10603 $langs = array();
10605 $order = 1.0;
10606 foreach ($rawlangs as $lang) {
10607 if (strpos($lang, ';') === false) {
10608 $langs[(string)$order] = $lang;
10609 $order = $order-0.01;
10610 } else {
10611 $parts = explode(';', $lang);
10612 $pos = strpos($parts[1], '=');
10613 $langs[substr($parts[1], $pos+1)] = $parts[0];
10616 krsort($langs, SORT_NUMERIC);
10618 /// Look for such langs under standard locations
10619 foreach ($langs as $lang) {
10620 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
10621 if (get_string_manager()->translation_exists($lang, false)) {
10622 $SESSION->lang = $lang; /// Lang exists, set it in session
10623 break; /// We have finished. Go out
10626 return;
10630 * check if $url matches anything in proxybypass list
10632 * any errors just result in the proxy being used (least bad)
10634 * @global object
10635 * @param string $url url to check
10636 * @return boolean true if we should bypass the proxy
10638 function is_proxybypass( $url ) {
10639 global $CFG;
10641 // sanity check
10642 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
10643 return false;
10646 // get the host part out of the url
10647 if (!$host = parse_url( $url, PHP_URL_HOST )) {
10648 return false;
10651 // get the possible bypass hosts into an array
10652 $matches = explode( ',', $CFG->proxybypass );
10654 // check for a match
10655 // (IPs need to match the left hand side and hosts the right of the url,
10656 // but we can recklessly check both as there can't be a false +ve)
10657 $bypass = false;
10658 foreach ($matches as $match) {
10659 $match = trim($match);
10661 // try for IP match (Left side)
10662 $lhs = substr($host,0,strlen($match));
10663 if (strcasecmp($match,$lhs)==0) {
10664 return true;
10667 // try for host match (Right side)
10668 $rhs = substr($host,-strlen($match));
10669 if (strcasecmp($match,$rhs)==0) {
10670 return true;
10674 // nothing matched.
10675 return false;
10679 ////////////////////////////////////////////////////////////////////////////////
10682 * Check if the passed navigation is of the new style
10684 * @param mixed $navigation
10685 * @return bool true for yes false for no
10687 function is_newnav($navigation) {
10688 if (is_array($navigation) && !empty($navigation['newnav'])) {
10689 return true;
10690 } else {
10691 return false;
10696 * Checks whether the given variable name is defined as a variable within the given object.
10698 * This will NOT work with stdClass objects, which have no class variables.
10700 * @param string $var The variable name
10701 * @param object $object The object to check
10702 * @return boolean
10704 function in_object_vars($var, $object) {
10705 $class_vars = get_class_vars(get_class($object));
10706 $class_vars = array_keys($class_vars);
10707 return in_array($var, $class_vars);
10711 * Returns an array without repeated objects.
10712 * This function is similar to array_unique, but for arrays that have objects as values
10714 * @param array $array
10715 * @param bool $keep_key_assoc
10716 * @return array
10718 function object_array_unique($array, $keep_key_assoc = true) {
10719 $duplicate_keys = array();
10720 $tmp = array();
10722 foreach ($array as $key=>$val) {
10723 // convert objects to arrays, in_array() does not support objects
10724 if (is_object($val)) {
10725 $val = (array)$val;
10728 if (!in_array($val, $tmp)) {
10729 $tmp[] = $val;
10730 } else {
10731 $duplicate_keys[] = $key;
10735 foreach ($duplicate_keys as $key) {
10736 unset($array[$key]);
10739 return $keep_key_assoc ? $array : array_values($array);
10743 * Is a userid the primary administrator?
10745 * @param int $userid int id of user to check
10746 * @return boolean
10748 function is_primary_admin($userid){
10749 $primaryadmin = get_admin();
10751 if($userid == $primaryadmin->id){
10752 return true;
10753 }else{
10754 return false;
10759 * Returns the site identifier
10761 * @global object
10762 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
10764 function get_site_identifier() {
10765 global $CFG;
10766 // Check to see if it is missing. If so, initialise it.
10767 if (empty($CFG->siteidentifier)) {
10768 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
10770 // Return it.
10771 return $CFG->siteidentifier;
10775 * Check whether the given password has no more than the specified
10776 * number of consecutive identical characters.
10778 * @param string $password password to be checked against the password policy
10779 * @param integer $maxchars maximum number of consecutive identical characters
10781 function check_consecutive_identical_characters($password, $maxchars) {
10783 if ($maxchars < 1) {
10784 return true; // 0 is to disable this check
10786 if (strlen($password) <= $maxchars) {
10787 return true; // too short to fail this test
10790 $previouschar = '';
10791 $consecutivecount = 1;
10792 foreach (str_split($password) as $char) {
10793 if ($char != $previouschar) {
10794 $consecutivecount = 1;
10796 else {
10797 $consecutivecount++;
10798 if ($consecutivecount > $maxchars) {
10799 return false; // check failed already
10803 $previouschar = $char;
10806 return true;
10810 * helper function to do partial function binding
10811 * so we can use it for preg_replace_callback, for example
10812 * this works with php functions, user functions, static methods and class methods
10813 * it returns you a callback that you can pass on like so:
10815 * $callback = partial('somefunction', $arg1, $arg2);
10816 * or
10817 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
10818 * or even
10819 * $obj = new someclass();
10820 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
10822 * and then the arguments that are passed through at calltime are appended to the argument list.
10824 * @param mixed $function a php callback
10825 * $param mixed $arg1.. $argv arguments to partially bind with
10827 * @return callback
10829 function partial() {
10830 if (!class_exists('partial')) {
10831 class partial{
10832 var $values = array();
10833 var $func;
10835 function __construct($func, $args) {
10836 $this->values = $args;
10837 $this->func = $func;
10840 function method() {
10841 $args = func_get_args();
10842 return call_user_func_array($this->func, array_merge($this->values, $args));
10846 $args = func_get_args();
10847 $func = array_shift($args);
10848 $p = new partial($func, $args);
10849 return array($p, 'method');
10853 * helper function to load up and initialise the mnet environment
10854 * this must be called before you use mnet functions.
10856 * @return mnet_environment the equivalent of old $MNET global
10858 function get_mnet_environment() {
10859 global $CFG;
10860 require_once($CFG->dirroot . '/mnet/lib.php');
10861 static $instance = null;
10862 if (empty($instance)) {
10863 $instance = new mnet_environment();
10864 $instance->init();
10866 return $instance;
10870 * during xmlrpc server code execution, any code wishing to access
10871 * information about the remote peer must use this to get it.
10873 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
10875 function get_mnet_remote_client() {
10876 if (!defined('MNET_SERVER')) {
10877 debugging(get_string('notinxmlrpcserver', 'mnet'));
10878 return false;
10880 global $MNET_REMOTE_CLIENT;
10881 if (isset($MNET_REMOTE_CLIENT)) {
10882 return $MNET_REMOTE_CLIENT;
10884 return false;
10888 * during the xmlrpc server code execution, this will be called
10889 * to setup the object returned by {@see get_mnet_remote_client}
10891 * @param mnet_remote_client $client the client to set up
10893 function set_mnet_remote_client($client) {
10894 if (!defined('MNET_SERVER')) {
10895 throw new moodle_exception('notinxmlrpcserver', 'mnet');
10897 global $MNET_REMOTE_CLIENT;
10898 $MNET_REMOTE_CLIENT = $client;
10902 * return the jump url for a given remote user
10903 * this is used for rewriting forum post links in emails, etc
10905 * @param stdclass $user the user to get the idp url for
10907 function mnet_get_idp_jump_url($user) {
10908 global $CFG;
10910 static $mnetjumps = array();
10911 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
10912 $idp = mnet_get_peer_host($user->mnethostid);
10913 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
10914 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
10916 return $mnetjumps[$user->mnethostid];
10920 * Gets the homepage to use for the current user
10922 * @return int One of HOMEPAGE_*
10924 function get_home_page() {
10925 global $CFG;
10927 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
10928 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
10929 return HOMEPAGE_MY;
10930 } else {
10931 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
10934 return HOMEPAGE_SITE;
10938 * Gets the name of a course to be displayed when showing a list of courses.
10939 * By default this is just $course->fullname but user can configure it. The
10940 * result of this function should be passed through print_string.
10941 * @param object $course Moodle course object
10942 * @return string Display name of course (either fullname or short + fullname)
10944 function get_course_display_name_for_list($course) {
10945 global $CFG;
10946 if (!empty($CFG->courselistshortnames)) {
10947 return get_string('courseextendednamedisplay', '', $course);
10948 } else {
10949 return $course->fullname;
10954 * The lang_string class
10956 * This special class is used to create an object representation of a string request.
10957 * It is special because processing doesn't occur until the object is first used.
10958 * The class was created especially to aid performance in areas where strings were
10959 * required to be generated but were not necessarily used.
10960 * As an example the admin tree when generated uses over 1500 strings, of which
10961 * normally only 1/3 are ever actually printed at any time.
10962 * The performance advantage is achieved by not actually processing strings that
10963 * arn't being used, as such reducing the processing required for the page.
10965 * How to use the lang_string class?
10966 * There are two methods of using the lang_string class, first through the
10967 * forth argument of the get_string function, and secondly directly.
10968 * The following are examples of both.
10969 * 1. Through get_string calls e.g.
10970 * $string = get_string($identifier, $component, $a, true);
10971 * $string = get_string('yes', 'moodle', null, true);
10972 * 2. Direct instantiation
10973 * $string = new lang_string($identifier, $component, $a, $lang);
10974 * $string = new lang_string('yes');
10976 * How do I use a lang_string object?
10977 * The lang_string object makes use of a magic __toString method so that you
10978 * are able to use the object exactly as you would use a string in most cases.
10979 * This means you are able to collect it into a variable and then directly
10980 * echo it, or concatenate it into another string, or similar.
10981 * The other thing you can do is manually get the string by calling the
10982 * lang_strings out method e.g.
10983 * $string = new lang_string('yes');
10984 * $string->out();
10985 * Also worth noting is that the out method can take one argument, $lang which
10986 * allows the developer to change the language on the fly.
10988 * When should I use a lang_string object?
10989 * The lang_string object is designed to be used in any situation where a
10990 * string may not be needed, but needs to be generated.
10991 * The admin tree is a good example of where lang_string objects should be
10992 * used.
10993 * A more practical example would be any class that requries strings that may
10994 * not be printed (after all classes get renderer by renderers and who knows
10995 * what they will do ;))
10997 * When should I not use a lang_string object?
10998 * Don't use lang_strings when you are going to use a string immediately.
10999 * There is no need as it will be processed immediately and there will be no
11000 * advantage, and in fact perhaps a negative hit as a class has to be
11001 * instantiated for a lang_string object, however get_string won't require
11002 * that.
11004 * Limitations:
11005 * 1. You cannot use a lang_string object as an array offset. Doing so will
11006 * result in PHP throwing an error. (You can use it as an object property!)
11008 * @package core
11009 * @category string
11010 * @copyright 2011 Sam Hemelryk
11011 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
11013 class lang_string {
11015 /** @var string The strings identifier */
11016 protected $identifier;
11017 /** @var string The strings component. Default '' */
11018 protected $component = '';
11019 /** @var array|stdClass Any arguments required for the string. Default null */
11020 protected $a = null;
11021 /** @var string The language to use when processing the string. Default null */
11022 protected $lang = null;
11024 /** @var string The processed string (once processed) */
11025 protected $string = null;
11028 * A special boolean. If set to true then the object has been woken up and
11029 * cannot be regenerated. If this is set then $this->string MUST be used.
11030 * @var bool
11032 protected $forcedstring = false;
11035 * Constructs a lang_string object
11037 * This function should do as little processing as possible to ensure the best
11038 * performance for strings that won't be used.
11040 * @param string $identifier The strings identifier
11041 * @param string $component The strings component
11042 * @param stdClass|array $a Any arguments the string requires
11043 * @param string $lang The language to use when processing the string.
11045 public function __construct($identifier, $component = '', $a = null, $lang = null) {
11046 if (empty($component)) {
11047 $component = 'moodle';
11050 $this->identifier = $identifier;
11051 $this->component = $component;
11052 $this->lang = $lang;
11054 // We MUST duplicate $a to ensure that it if it changes by reference those
11055 // changes are not carried across.
11056 // To do this we always ensure $a or its properties/values are strings
11057 // and that any properties/values that arn't convertable are forgotten.
11058 if (!empty($a)) {
11059 if (is_scalar($a)) {
11060 $this->a = $a;
11061 } else if ($a instanceof lang_string) {
11062 $this->a = $a->out();
11063 } else if (is_object($a) or is_array($a)) {
11064 $a = (array)$a;
11065 $this->a = array();
11066 foreach ($a as $key => $value) {
11067 // Make sure conversion errors don't get displayed (results in '')
11068 if (is_array($value)) {
11069 $this->a[$key] = '';
11070 } else if (is_object($value)) {
11071 if (method_exists($value, '__toString')) {
11072 $this->a[$key] = $value->__toString();
11073 } else {
11074 $this->a[$key] = '';
11076 } else {
11077 $this->a[$key] = (string)$value;
11083 if (debugging(false, DEBUG_DEVELOPER)) {
11084 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
11085 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11087 if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') {
11088 throw new coding_exception('Invalid string compontent. Please check your string definition');
11090 if (!get_string_manager()->string_exists($this->identifier, $this->component)) {
11091 debugging('String does not exist. Please check your string definition for '.$this->identifier.'/'.$this->component, DEBUG_DEVELOPER);
11097 * Processes the string.
11099 * This function actually processes the string, stores it in the string property
11100 * and then returns it.
11101 * You will notice that this function is VERY similar to the get_string method.
11102 * That is because it is pretty much doing the same thing.
11103 * However as this function is an upgrade it isn't as tolerant to backwards
11104 * compatability.
11106 * @return string
11108 protected function get_string() {
11109 global $CFG;
11111 // Check if we need to process the string
11112 if ($this->string === null) {
11113 // Check the quality of the identifier.
11114 if (debugging('', DEBUG_DEVELOPER) && clean_param($this->identifier, PARAM_STRINGID) === '') {
11115 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11118 // Process the string
11119 $this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
11120 // Debugging feature lets you display string identifier and component
11121 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
11122 $this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
11125 // Return the string
11126 return $this->string;
11130 * Returns the string
11132 * @param string $lang The langauge to use when processing the string
11133 * @return string
11135 public function out($lang = null) {
11136 if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
11137 if ($this->forcedstring) {
11138 debugging('lang_string objects that have been serialised and unserialised cannot be printed in another language. ('.$this->lang.' used)', DEBUG_DEVELOPER);
11139 return $this->get_string();
11141 $translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
11142 return $translatedstring->out();
11144 return $this->get_string();
11148 * Magic __toString method for printing a string
11150 * @return string
11152 public function __toString() {
11153 return $this->get_string();
11157 * Magic __set_state method used for var_export
11159 * @return string
11161 public function __set_state() {
11162 return $this->get_string();
11166 * Prepares the lang_string for sleep and stores only the forcedstring and
11167 * string properties... the string cannot be regenerated so we need to ensure
11168 * it is generated for this.
11170 * @return string
11172 public function __sleep() {
11173 $this->get_string();
11174 $this->forcedstring = true;
11175 return array('forcedstring', 'string', 'lang');