Merge branch 'MDL-37781-workshop-schedule_23' of git://github.com/mudrd8mz/moodle...
[moodle.git] / lib / moodlelib.php
blob0d298a67fd28a2c6ce99461de01a55cf603d09e9
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * moodlelib.php - Moodle main library
20 * Main library file of miscellaneous general-purpose Moodle functions.
21 * Other main libraries:
22 * - weblib.php - functions that produce web output
23 * - datalib.php - functions that access the database
25 * @package core
26 * @subpackage lib
27 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31 defined('MOODLE_INTERNAL') || die();
33 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
35 /// Date and time constants ///
36 /**
37 * Time constant - the number of seconds in a year
39 define('YEARSECS', 31536000);
41 /**
42 * Time constant - the number of seconds in a week
44 define('WEEKSECS', 604800);
46 /**
47 * Time constant - the number of seconds in a day
49 define('DAYSECS', 86400);
51 /**
52 * Time constant - the number of seconds in an hour
54 define('HOURSECS', 3600);
56 /**
57 * Time constant - the number of seconds in a minute
59 define('MINSECS', 60);
61 /**
62 * Time constant - the number of minutes in a day
64 define('DAYMINS', 1440);
66 /**
67 * Time constant - the number of minutes in an hour
69 define('HOURMINS', 60);
71 /// Parameter constants - every call to optional_param(), required_param() ///
72 /// or clean_param() should have a specified type of parameter. //////////////
76 /**
77 * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
79 define('PARAM_ALPHA', 'alpha');
81 /**
82 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "_-" allowed
83 * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
85 define('PARAM_ALPHAEXT', 'alphaext');
87 /**
88 * PARAM_ALPHANUM - expected numbers and letters only.
90 define('PARAM_ALPHANUM', 'alphanum');
92 /**
93 * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
95 define('PARAM_ALPHANUMEXT', 'alphanumext');
97 /**
98 * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
100 define('PARAM_AUTH', 'auth');
103 * PARAM_BASE64 - Base 64 encoded format
105 define('PARAM_BASE64', 'base64');
108 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
110 define('PARAM_BOOL', 'bool');
113 * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
114 * checked against the list of capabilities in the database.
116 define('PARAM_CAPABILITY', 'capability');
119 * PARAM_CLEANHTML - cleans submitted HTML code. use only for text in HTML format. This cleaning may fix xhtml strictness too.
121 define('PARAM_CLEANHTML', 'cleanhtml');
124 * PARAM_EMAIL - an email address following the RFC
126 define('PARAM_EMAIL', 'email');
129 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
131 define('PARAM_FILE', 'file');
134 * PARAM_FLOAT - a real/floating point number.
136 * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
137 * It does not work for languages that use , as a decimal separator.
138 * Instead, do something like
139 * $rawvalue = required_param('name', PARAM_RAW);
140 * // ... other code including require_login, which sets current lang ...
141 * $realvalue = unformat_float($rawvalue);
142 * // ... then use $realvalue
144 define('PARAM_FLOAT', 'float');
147 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
149 define('PARAM_HOST', 'host');
152 * PARAM_INT - integers only, use when expecting only numbers.
154 define('PARAM_INT', 'int');
157 * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
159 define('PARAM_LANG', 'lang');
162 * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the others! Implies PARAM_URL!)
164 define('PARAM_LOCALURL', 'localurl');
167 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
169 define('PARAM_NOTAGS', 'notags');
172 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
173 * note: the leading slash is not removed, window drive letter is not allowed
175 define('PARAM_PATH', 'path');
178 * PARAM_PEM - Privacy Enhanced Mail format
180 define('PARAM_PEM', 'pem');
183 * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
185 define('PARAM_PERMISSION', 'permission');
188 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
190 define('PARAM_RAW', 'raw');
193 * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
195 define('PARAM_RAW_TRIMMED', 'raw_trimmed');
198 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
200 define('PARAM_SAFEDIR', 'safedir');
203 * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
205 define('PARAM_SAFEPATH', 'safepath');
208 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
210 define('PARAM_SEQUENCE', 'sequence');
213 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
215 define('PARAM_TAG', 'tag');
218 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
220 define('PARAM_TAGLIST', 'taglist');
223 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
225 define('PARAM_TEXT', 'text');
228 * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
230 define('PARAM_THEME', 'theme');
233 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
235 define('PARAM_URL', 'url');
238 * PARAM_USERNAME - Clean username to only contains allowed characters. This is to be used ONLY when manually creating user accounts, do NOT use when syncing with external systems!!
240 define('PARAM_USERNAME', 'username');
243 * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
245 define('PARAM_STRINGID', 'stringid');
247 ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
249 * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
250 * It was one of the first types, that is why it is abused so much ;-)
251 * @deprecated since 2.0
253 define('PARAM_CLEAN', 'clean');
256 * PARAM_INTEGER - deprecated alias for PARAM_INT
258 define('PARAM_INTEGER', 'int');
261 * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
263 define('PARAM_NUMBER', 'float');
266 * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
267 * NOTE: originally alias for PARAM_APLHA
269 define('PARAM_ACTION', 'alphanumext');
272 * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
273 * NOTE: originally alias for PARAM_APLHA
275 define('PARAM_FORMAT', 'alphanumext');
278 * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
280 define('PARAM_MULTILANG', 'text');
283 * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
284 * string seperated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
285 * America/Port-au-Prince)
287 define('PARAM_TIMEZONE', 'timezone');
290 * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
292 define('PARAM_CLEANFILE', 'file');
295 * PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
296 * Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
297 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
298 * NOTE: numbers and underscores are strongly discouraged in plugin names!
300 define('PARAM_COMPONENT', 'component');
303 * PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
304 * It is usually used together with context id and component.
305 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
307 define('PARAM_AREA', 'area');
310 * PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'radius', 'paypal', 'completionstatus'.
311 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
312 * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
314 define('PARAM_PLUGIN', 'plugin');
317 /// Web Services ///
320 * VALUE_REQUIRED - if the parameter is not supplied, there is an error
322 define('VALUE_REQUIRED', 1);
325 * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
327 define('VALUE_OPTIONAL', 2);
330 * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
332 define('VALUE_DEFAULT', 0);
335 * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
337 define('NULL_NOT_ALLOWED', false);
340 * NULL_ALLOWED - the parameter can be set to null in the database
342 define('NULL_ALLOWED', true);
344 /// Page types ///
346 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
348 define('PAGE_COURSE_VIEW', 'course-view');
350 /** Get remote addr constant */
351 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
352 /** Get remote addr constant */
353 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
355 /// Blog access level constant declaration ///
356 define ('BLOG_USER_LEVEL', 1);
357 define ('BLOG_GROUP_LEVEL', 2);
358 define ('BLOG_COURSE_LEVEL', 3);
359 define ('BLOG_SITE_LEVEL', 4);
360 define ('BLOG_GLOBAL_LEVEL', 5);
363 ///Tag constants///
365 * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
366 * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
367 * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
369 * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
371 define('TAG_MAX_LENGTH', 50);
373 /// Password policy constants ///
374 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
375 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
376 define ('PASSWORD_DIGITS', '0123456789');
377 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
379 /// Feature constants ///
380 // Used for plugin_supports() to report features that are, or are not, supported by a module.
382 /** True if module can provide a grade */
383 define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
384 /** True if module supports outcomes */
385 define('FEATURE_GRADE_OUTCOMES', 'outcomes');
386 /** True if module supports advanced grading methods */
387 define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
388 /** True if module controls the grade visibility over the gradebook */
389 define('FEATURE_CONTROLS_GRADE_VISIBILITY', 'controlsgradevisbility');
391 /** True if module has code to track whether somebody viewed it */
392 define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
393 /** True if module has custom completion rules */
394 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
396 /** True if module has no 'view' page (like label) */
397 define('FEATURE_NO_VIEW_LINK', 'viewlink');
398 /** True if module supports outcomes */
399 define('FEATURE_IDNUMBER', 'idnumber');
400 /** True if module supports groups */
401 define('FEATURE_GROUPS', 'groups');
402 /** True if module supports groupings */
403 define('FEATURE_GROUPINGS', 'groupings');
404 /** True if module supports groupmembersonly */
405 define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
407 /** Type of module */
408 define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
409 /** True if module supports intro editor */
410 define('FEATURE_MOD_INTRO', 'mod_intro');
411 /** True if module has default completion */
412 define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
414 define('FEATURE_COMMENT', 'comment');
416 define('FEATURE_RATE', 'rate');
417 /** True if module supports backup/restore of moodle2 format */
418 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
420 /** True if module can show description on course main page */
421 define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
423 /** Unspecified module archetype */
424 define('MOD_ARCHETYPE_OTHER', 0);
425 /** Resource-like type module */
426 define('MOD_ARCHETYPE_RESOURCE', 1);
427 /** Assignment module archetype */
428 define('MOD_ARCHETYPE_ASSIGNMENT', 2);
429 /** System (not user-addable) module archetype */
430 define('MOD_ARCHETYPE_SYSTEM', 3);
433 * Security token used for allowing access
434 * from external application such as web services.
435 * Scripts do not use any session, performance is relatively
436 * low because we need to load access info in each request.
437 * Scripts are executed in parallel.
439 define('EXTERNAL_TOKEN_PERMANENT', 0);
442 * Security token used for allowing access
443 * of embedded applications, the code is executed in the
444 * active user session. Token is invalidated after user logs out.
445 * Scripts are executed serially - normal session locking is used.
447 define('EXTERNAL_TOKEN_EMBEDDED', 1);
450 * The home page should be the site home
452 define('HOMEPAGE_SITE', 0);
454 * The home page should be the users my page
456 define('HOMEPAGE_MY', 1);
458 * The home page can be chosen by the user
460 define('HOMEPAGE_USER', 2);
463 * Hub directory url (should be moodle.org)
465 define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
469 * Moodle.org url (should be moodle.org)
471 define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
474 * Moodle mobile app service name
476 define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
479 * Indicates the user has the capabilities required to ignore activity and course file size restrictions
481 define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1);
484 * Course display settings
486 define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page
487 define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section
489 /// PARAMETER HANDLING ////////////////////////////////////////////////////
492 * Returns a particular value for the named variable, taken from
493 * POST or GET. If the parameter doesn't exist then an error is
494 * thrown because we require this variable.
496 * This function should be used to initialise all required values
497 * in a script that are based on parameters. Usually it will be
498 * used like this:
499 * $id = required_param('id', PARAM_INT);
501 * Please note the $type parameter is now required and the value can not be array.
503 * @param string $parname the name of the page parameter we want
504 * @param string $type expected type of parameter
505 * @return mixed
507 function required_param($parname, $type) {
508 if (func_num_args() != 2 or empty($parname) or empty($type)) {
509 throw new coding_exception('required_param() requires $parname and $type to be specified (parameter: '.$parname.')');
511 if (isset($_POST[$parname])) { // POST has precedence
512 $param = $_POST[$parname];
513 } else if (isset($_GET[$parname])) {
514 $param = $_GET[$parname];
515 } else {
516 print_error('missingparam', '', '', $parname);
519 if (is_array($param)) {
520 debugging('Invalid array parameter detected in required_param(): '.$parname);
521 // TODO: switch to fatal error in Moodle 2.3
522 //print_error('missingparam', '', '', $parname);
523 return required_param_array($parname, $type);
526 return clean_param($param, $type);
530 * Returns a particular array value for the named variable, taken from
531 * POST or GET. If the parameter doesn't exist then an error is
532 * thrown because we require this variable.
534 * This function should be used to initialise all required values
535 * in a script that are based on parameters. Usually it will be
536 * used like this:
537 * $ids = required_param_array('ids', PARAM_INT);
539 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
541 * @param string $parname the name of the page parameter we want
542 * @param string $type expected type of parameter
543 * @return array
545 function required_param_array($parname, $type) {
546 if (func_num_args() != 2 or empty($parname) or empty($type)) {
547 throw new coding_exception('required_param_array() requires $parname and $type to be specified (parameter: '.$parname.')');
549 if (isset($_POST[$parname])) { // POST has precedence
550 $param = $_POST[$parname];
551 } else if (isset($_GET[$parname])) {
552 $param = $_GET[$parname];
553 } else {
554 print_error('missingparam', '', '', $parname);
556 if (!is_array($param)) {
557 print_error('missingparam', '', '', $parname);
560 $result = array();
561 foreach($param as $key=>$value) {
562 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
563 debugging('Invalid key name in required_param_array() detected: '.$key.', parameter: '.$parname);
564 continue;
566 $result[$key] = clean_param($value, $type);
569 return $result;
573 * Returns a particular value for the named variable, taken from
574 * POST or GET, otherwise returning a given default.
576 * This function should be used to initialise all optional values
577 * in a script that are based on parameters. Usually it will be
578 * used like this:
579 * $name = optional_param('name', 'Fred', PARAM_TEXT);
581 * Please note the $type parameter is now required and the value can not be array.
583 * @param string $parname the name of the page parameter we want
584 * @param mixed $default the default value to return if nothing is found
585 * @param string $type expected type of parameter
586 * @return mixed
588 function optional_param($parname, $default, $type) {
589 if (func_num_args() != 3 or empty($parname) or empty($type)) {
590 throw new coding_exception('optional_param() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
592 if (!isset($default)) {
593 $default = null;
596 if (isset($_POST[$parname])) { // POST has precedence
597 $param = $_POST[$parname];
598 } else if (isset($_GET[$parname])) {
599 $param = $_GET[$parname];
600 } else {
601 return $default;
604 if (is_array($param)) {
605 debugging('Invalid array parameter detected in required_param(): '.$parname);
606 // TODO: switch to $default in Moodle 2.3
607 //return $default;
608 return optional_param_array($parname, $default, $type);
611 return clean_param($param, $type);
615 * Returns a particular array value for the named variable, taken from
616 * POST or GET, otherwise returning a given default.
618 * This function should be used to initialise all optional values
619 * in a script that are based on parameters. Usually it will be
620 * used like this:
621 * $ids = optional_param('id', array(), PARAM_INT);
623 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
625 * @param string $parname the name of the page parameter we want
626 * @param mixed $default the default value to return if nothing is found
627 * @param string $type expected type of parameter
628 * @return array
630 function optional_param_array($parname, $default, $type) {
631 if (func_num_args() != 3 or empty($parname) or empty($type)) {
632 throw new coding_exception('optional_param_array() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
635 if (isset($_POST[$parname])) { // POST has precedence
636 $param = $_POST[$parname];
637 } else if (isset($_GET[$parname])) {
638 $param = $_GET[$parname];
639 } else {
640 return $default;
642 if (!is_array($param)) {
643 debugging('optional_param_array() expects array parameters only: '.$parname);
644 return $default;
647 $result = array();
648 foreach($param as $key=>$value) {
649 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
650 debugging('Invalid key name in optional_param_array() detected: '.$key.', parameter: '.$parname);
651 continue;
653 $result[$key] = clean_param($value, $type);
656 return $result;
660 * Strict validation of parameter values, the values are only converted
661 * to requested PHP type. Internally it is using clean_param, the values
662 * before and after cleaning must be equal - otherwise
663 * an invalid_parameter_exception is thrown.
664 * Objects and classes are not accepted.
666 * @param mixed $param
667 * @param string $type PARAM_ constant
668 * @param bool $allownull are nulls valid value?
669 * @param string $debuginfo optional debug information
670 * @return mixed the $param value converted to PHP type
671 * @throws invalid_parameter_exception if $param is not of given type
673 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
674 if (is_null($param)) {
675 if ($allownull == NULL_ALLOWED) {
676 return null;
677 } else {
678 throw new invalid_parameter_exception($debuginfo);
681 if (is_array($param) or is_object($param)) {
682 throw new invalid_parameter_exception($debuginfo);
685 $cleaned = clean_param($param, $type);
687 if ($type == PARAM_FLOAT) {
688 // Do not detect precision loss here.
689 if (is_float($param) or is_int($param)) {
690 // These always fit.
691 } else if (!is_numeric($param) or !preg_match('/^[\+-]?[0-9]*\.?[0-9]*(e[-+]?[0-9]+)?$/i', (string)$param)) {
692 throw new invalid_parameter_exception($debuginfo);
694 } else if ((string)$param !== (string)$cleaned) {
695 // conversion to string is usually lossless
696 throw new invalid_parameter_exception($debuginfo);
699 return $cleaned;
703 * Makes sure array contains only the allowed types,
704 * this function does not validate array key names!
705 * <code>
706 * $options = clean_param($options, PARAM_INT);
707 * </code>
709 * @param array $param the variable array we are cleaning
710 * @param string $type expected format of param after cleaning.
711 * @param bool $recursive clean recursive arrays
712 * @return array
714 function clean_param_array(array $param = null, $type, $recursive = false) {
715 $param = (array)$param; // convert null to empty array
716 foreach ($param as $key => $value) {
717 if (is_array($value)) {
718 if ($recursive) {
719 $param[$key] = clean_param_array($value, $type, true);
720 } else {
721 throw new coding_exception('clean_param_array() can not process multidimensional arrays when $recursive is false.');
723 } else {
724 $param[$key] = clean_param($value, $type);
727 return $param;
731 * Used by {@link optional_param()} and {@link required_param()} to
732 * clean the variables and/or cast to specific types, based on
733 * an options field.
734 * <code>
735 * $course->format = clean_param($course->format, PARAM_ALPHA);
736 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
737 * </code>
739 * @param mixed $param the variable we are cleaning
740 * @param string $type expected format of param after cleaning.
741 * @return mixed
743 function clean_param($param, $type) {
745 global $CFG;
747 if (is_array($param)) {
748 throw new coding_exception('clean_param() can not process arrays, please use clean_param_array() instead.');
749 } else if (is_object($param)) {
750 if (method_exists($param, '__toString')) {
751 $param = $param->__toString();
752 } else {
753 throw new coding_exception('clean_param() can not process objects, please use clean_param_array() instead.');
757 switch ($type) {
758 case PARAM_RAW: // no cleaning at all
759 $param = fix_utf8($param);
760 return $param;
762 case PARAM_RAW_TRIMMED: // no cleaning, but strip leading and trailing whitespace.
763 $param = fix_utf8($param);
764 return trim($param);
766 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
767 // this is deprecated!, please use more specific type instead
768 if (is_numeric($param)) {
769 return $param;
771 $param = fix_utf8($param);
772 return clean_text($param); // Sweep for scripts, etc
774 case PARAM_CLEANHTML: // clean html fragment
775 $param = fix_utf8($param);
776 $param = clean_text($param, FORMAT_HTML); // Sweep for scripts, etc
777 return trim($param);
779 case PARAM_INT:
780 return (int)$param; // Convert to integer
782 case PARAM_FLOAT:
783 case PARAM_NUMBER:
784 return (float)$param; // Convert to float
786 case PARAM_ALPHA: // Remove everything not a-z
787 return preg_replace('/[^a-zA-Z]/i', '', $param);
789 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z_- (originally allowed "/" too)
790 return preg_replace('/[^a-zA-Z_-]/i', '', $param);
792 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
793 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
795 case PARAM_ALPHANUMEXT: // Remove everything not a-zA-Z0-9_-
796 return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
798 case PARAM_SEQUENCE: // Remove everything not 0-9,
799 return preg_replace('/[^0-9,]/i', '', $param);
801 case PARAM_BOOL: // Convert to 1 or 0
802 $tempstr = strtolower($param);
803 if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
804 $param = 1;
805 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
806 $param = 0;
807 } else {
808 $param = empty($param) ? 0 : 1;
810 return $param;
812 case PARAM_NOTAGS: // Strip all tags
813 $param = fix_utf8($param);
814 return strip_tags($param);
816 case PARAM_TEXT: // leave only tags needed for multilang
817 $param = fix_utf8($param);
818 // if the multilang syntax is not correct we strip all tags
819 // because it would break xhtml strict which is required for accessibility standards
820 // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
821 do {
822 if (strpos($param, '</lang>') !== false) {
823 // old and future mutilang syntax
824 $param = strip_tags($param, '<lang>');
825 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
826 break;
828 $open = false;
829 foreach ($matches[0] as $match) {
830 if ($match === '</lang>') {
831 if ($open) {
832 $open = false;
833 continue;
834 } else {
835 break 2;
838 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
839 break 2;
840 } else {
841 $open = true;
844 if ($open) {
845 break;
847 return $param;
849 } else if (strpos($param, '</span>') !== false) {
850 // current problematic multilang syntax
851 $param = strip_tags($param, '<span>');
852 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
853 break;
855 $open = false;
856 foreach ($matches[0] as $match) {
857 if ($match === '</span>') {
858 if ($open) {
859 $open = false;
860 continue;
861 } else {
862 break 2;
865 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
866 break 2;
867 } else {
868 $open = true;
871 if ($open) {
872 break;
874 return $param;
876 } while (false);
877 // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
878 return strip_tags($param);
880 case PARAM_COMPONENT:
881 // we do not want any guessing here, either the name is correct or not
882 // please note only normalised component names are accepted
883 if (!preg_match('/^[a-z]+(_[a-z][a-z0-9_]*)?[a-z0-9]$/', $param)) {
884 return '';
886 if (strpos($param, '__') !== false) {
887 return '';
889 if (strpos($param, 'mod_') === 0) {
890 // module names must not contain underscores because we need to differentiate them from invalid plugin types
891 if (substr_count($param, '_') != 1) {
892 return '';
895 return $param;
897 case PARAM_PLUGIN:
898 case PARAM_AREA:
899 // we do not want any guessing here, either the name is correct or not
900 if (!preg_match('/^[a-z][a-z0-9_]*[a-z0-9]$/', $param)) {
901 return '';
903 if (strpos($param, '__') !== false) {
904 return '';
906 return $param;
908 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
909 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
911 case PARAM_SAFEPATH: // Remove everything not a-zA-Z0-9/_-
912 return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
914 case PARAM_FILE: // Strip all suspicious characters from filename
915 $param = fix_utf8($param);
916 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
917 $param = preg_replace('~\.\.+~', '', $param);
918 if ($param === '.') {
919 $param = '';
921 return $param;
923 case PARAM_PATH: // Strip all suspicious characters from file path
924 $param = fix_utf8($param);
925 $param = str_replace('\\', '/', $param);
926 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
927 $param = preg_replace('~\.\.+~', '', $param);
928 $param = preg_replace('~//+~', '/', $param);
929 return preg_replace('~/(\./)+~', '/', $param);
931 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
932 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
933 // match ipv4 dotted quad
934 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
935 // confirm values are ok
936 if ( $match[0] > 255
937 || $match[1] > 255
938 || $match[3] > 255
939 || $match[4] > 255 ) {
940 // hmmm, what kind of dotted quad is this?
941 $param = '';
943 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
944 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
945 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
947 // all is ok - $param is respected
948 } else {
949 // all is not ok...
950 $param='';
952 return $param;
954 case PARAM_URL: // allow safe ftp, http, mailto urls
955 $param = fix_utf8($param);
956 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
957 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
958 // all is ok, param is respected
959 } else {
960 $param =''; // not really ok
962 return $param;
964 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
965 $param = clean_param($param, PARAM_URL);
966 if (!empty($param)) {
967 if (preg_match(':^/:', $param)) {
968 // root-relative, ok!
969 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
970 // absolute, and matches our wwwroot
971 } else {
972 // relative - let's make sure there are no tricks
973 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
974 // looks ok.
975 } else {
976 $param = '';
980 return $param;
982 case PARAM_PEM:
983 $param = trim($param);
984 // PEM formatted strings may contain letters/numbers and the symbols
985 // forward slash: /
986 // plus sign: +
987 // equal sign: =
988 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
989 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
990 list($wholething, $body) = $matches;
991 unset($wholething, $matches);
992 $b64 = clean_param($body, PARAM_BASE64);
993 if (!empty($b64)) {
994 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
995 } else {
996 return '';
999 return '';
1001 case PARAM_BASE64:
1002 if (!empty($param)) {
1003 // PEM formatted strings may contain letters/numbers and the symbols
1004 // forward slash: /
1005 // plus sign: +
1006 // equal sign: =
1007 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
1008 return '';
1010 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
1011 // Each line of base64 encoded data must be 64 characters in
1012 // length, except for the last line which may be less than (or
1013 // equal to) 64 characters long.
1014 for ($i=0, $j=count($lines); $i < $j; $i++) {
1015 if ($i + 1 == $j) {
1016 if (64 < strlen($lines[$i])) {
1017 return '';
1019 continue;
1022 if (64 != strlen($lines[$i])) {
1023 return '';
1026 return implode("\n",$lines);
1027 } else {
1028 return '';
1031 case PARAM_TAG:
1032 $param = fix_utf8($param);
1033 // Please note it is not safe to use the tag name directly anywhere,
1034 // it must be processed with s(), urlencode() before embedding anywhere.
1035 // remove some nasties
1036 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
1037 //convert many whitespace chars into one
1038 $param = preg_replace('/\s+/', ' ', $param);
1039 $param = textlib::substr(trim($param), 0, TAG_MAX_LENGTH);
1040 return $param;
1042 case PARAM_TAGLIST:
1043 $param = fix_utf8($param);
1044 $tags = explode(',', $param);
1045 $result = array();
1046 foreach ($tags as $tag) {
1047 $res = clean_param($tag, PARAM_TAG);
1048 if ($res !== '') {
1049 $result[] = $res;
1052 if ($result) {
1053 return implode(',', $result);
1054 } else {
1055 return '';
1058 case PARAM_CAPABILITY:
1059 if (get_capability_info($param)) {
1060 return $param;
1061 } else {
1062 return '';
1065 case PARAM_PERMISSION:
1066 $param = (int)$param;
1067 if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
1068 return $param;
1069 } else {
1070 return CAP_INHERIT;
1073 case PARAM_AUTH:
1074 $param = clean_param($param, PARAM_PLUGIN);
1075 if (empty($param)) {
1076 return '';
1077 } else if (exists_auth_plugin($param)) {
1078 return $param;
1079 } else {
1080 return '';
1083 case PARAM_LANG:
1084 $param = clean_param($param, PARAM_SAFEDIR);
1085 if (get_string_manager()->translation_exists($param)) {
1086 return $param;
1087 } else {
1088 return ''; // Specified language is not installed or param malformed
1091 case PARAM_THEME:
1092 $param = clean_param($param, PARAM_PLUGIN);
1093 if (empty($param)) {
1094 return '';
1095 } else if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
1096 return $param;
1097 } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
1098 return $param;
1099 } else {
1100 return ''; // Specified theme is not installed
1103 case PARAM_USERNAME:
1104 $param = fix_utf8($param);
1105 $param = str_replace(" " , "", $param);
1106 $param = textlib::strtolower($param); // Convert uppercase to lowercase MDL-16919
1107 if (empty($CFG->extendedusernamechars)) {
1108 // regular expression, eliminate all chars EXCEPT:
1109 // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
1110 $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
1112 return $param;
1114 case PARAM_EMAIL:
1115 $param = fix_utf8($param);
1116 if (validate_email($param)) {
1117 return $param;
1118 } else {
1119 return '';
1122 case PARAM_STRINGID:
1123 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
1124 return $param;
1125 } else {
1126 return '';
1129 case PARAM_TIMEZONE: //can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'
1130 $param = fix_utf8($param);
1131 $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3](\.0)?|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
1132 if (preg_match($timezonepattern, $param)) {
1133 return $param;
1134 } else {
1135 return '';
1138 default: // throw error, switched parameters in optional_param or another serious problem
1139 print_error("unknownparamtype", '', '', $type);
1144 * Makes sure the data is using valid utf8, invalid characters are discarded.
1146 * Note: this function is not intended for full objects with methods and private properties.
1148 * @param mixed $value
1149 * @return mixed with proper utf-8 encoding
1151 function fix_utf8($value) {
1152 if (is_null($value) or $value === '') {
1153 return $value;
1155 } else if (is_string($value)) {
1156 if ((string)(int)$value === $value) {
1157 // shortcut
1158 return $value;
1161 // Lower error reporting because glibc throws bogus notices.
1162 $olderror = error_reporting();
1163 if ($olderror & E_NOTICE) {
1164 error_reporting($olderror ^ E_NOTICE);
1167 // Note: this duplicates min_fix_utf8() intentionally.
1168 static $buggyiconv = null;
1169 if ($buggyiconv === null) {
1170 $buggyiconv = (!function_exists('iconv') or iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
1173 if ($buggyiconv) {
1174 if (function_exists('mb_convert_encoding')) {
1175 $subst = mb_substitute_character();
1176 mb_substitute_character('');
1177 $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
1178 mb_substitute_character($subst);
1180 } else {
1181 // Warn admins on admin/index.php page.
1182 $result = $value;
1185 } else {
1186 $result = iconv('UTF-8', 'UTF-8//IGNORE', $value);
1189 if ($olderror & E_NOTICE) {
1190 error_reporting($olderror);
1193 return $result;
1195 } else if (is_array($value)) {
1196 foreach ($value as $k=>$v) {
1197 $value[$k] = fix_utf8($v);
1199 return $value;
1201 } else if (is_object($value)) {
1202 $value = clone($value); // do not modify original
1203 foreach ($value as $k=>$v) {
1204 $value->$k = fix_utf8($v);
1206 return $value;
1208 } else {
1209 // this is some other type, no utf-8 here
1210 return $value;
1215 * Return true if given value is integer or string with integer value
1217 * @param mixed $value String or Int
1218 * @return bool true if number, false if not
1220 function is_number($value) {
1221 if (is_int($value)) {
1222 return true;
1223 } else if (is_string($value)) {
1224 return ((string)(int)$value) === $value;
1225 } else {
1226 return false;
1231 * Returns host part from url
1232 * @param string $url full url
1233 * @return string host, null if not found
1235 function get_host_from_url($url) {
1236 preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
1237 if ($matches) {
1238 return $matches[1];
1240 return null;
1244 * Tests whether anything was returned by text editor
1246 * This function is useful for testing whether something you got back from
1247 * the HTML editor actually contains anything. Sometimes the HTML editor
1248 * appear to be empty, but actually you get back a <br> tag or something.
1250 * @param string $string a string containing HTML.
1251 * @return boolean does the string contain any actual content - that is text,
1252 * images, objects, etc.
1254 function html_is_blank($string) {
1255 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
1259 * Set a key in global configuration
1261 * Set a key/value pair in both this session's {@link $CFG} global variable
1262 * and in the 'config' database table for future sessions.
1264 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
1265 * In that case it doesn't affect $CFG.
1267 * A NULL value will delete the entry.
1269 * @global object
1270 * @global object
1271 * @param string $name the key to set
1272 * @param string $value the value to set (without magic quotes)
1273 * @param string $plugin (optional) the plugin scope, default NULL
1274 * @return bool true or exception
1276 function set_config($name, $value, $plugin=NULL) {
1277 global $CFG, $DB;
1279 if (empty($plugin)) {
1280 if (!array_key_exists($name, $CFG->config_php_settings)) {
1281 // So it's defined for this invocation at least
1282 if (is_null($value)) {
1283 unset($CFG->$name);
1284 } else {
1285 $CFG->$name = (string)$value; // settings from db are always strings
1289 if ($DB->get_field('config', 'name', array('name'=>$name))) {
1290 if ($value === null) {
1291 $DB->delete_records('config', array('name'=>$name));
1292 } else {
1293 $DB->set_field('config', 'value', $value, array('name'=>$name));
1295 } else {
1296 if ($value !== null) {
1297 $config = new stdClass();
1298 $config->name = $name;
1299 $config->value = $value;
1300 $DB->insert_record('config', $config, false);
1304 } else { // plugin scope
1305 if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
1306 if ($value===null) {
1307 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1308 } else {
1309 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
1311 } else {
1312 if ($value !== null) {
1313 $config = new stdClass();
1314 $config->plugin = $plugin;
1315 $config->name = $name;
1316 $config->value = $value;
1317 $DB->insert_record('config_plugins', $config, false);
1322 return true;
1326 * Get configuration values from the global config table
1327 * or the config_plugins table.
1329 * If called with one parameter, it will load all the config
1330 * variables for one plugin, and return them as an object.
1332 * If called with 2 parameters it will return a string single
1333 * value or false if the value is not found.
1335 * @param string $plugin full component name
1336 * @param string $name default NULL
1337 * @return mixed hash-like object or single value, return false no config found
1339 function get_config($plugin, $name = NULL) {
1340 global $CFG, $DB;
1342 // normalise component name
1343 if ($plugin === 'moodle' or $plugin === 'core') {
1344 $plugin = NULL;
1347 if (!empty($name)) { // the user is asking for a specific value
1348 if (!empty($plugin)) {
1349 if (isset($CFG->forced_plugin_settings[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings[$plugin])) {
1350 // setting forced in config file
1351 return $CFG->forced_plugin_settings[$plugin][$name];
1352 } else {
1353 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1355 } else {
1356 if (array_key_exists($name, $CFG->config_php_settings)) {
1357 // setting force in config file
1358 return $CFG->config_php_settings[$name];
1359 } else {
1360 return $DB->get_field('config', 'value', array('name'=>$name));
1365 // the user is after a recordset
1366 if ($plugin) {
1367 $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
1368 if (isset($CFG->forced_plugin_settings[$plugin])) {
1369 foreach($CFG->forced_plugin_settings[$plugin] as $n=>$v) {
1370 if (is_null($v) or is_array($v) or is_object($v)) {
1371 // we do not want any extra mess here, just real settings that could be saved in db
1372 unset($localcfg[$n]);
1373 } else {
1374 //convert to string as if it went through the DB
1375 $localcfg[$n] = (string)$v;
1379 if ($localcfg) {
1380 return (object)$localcfg;
1381 } else {
1382 return new stdClass();
1385 } else {
1386 // this part is not really used any more, but anyway...
1387 $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
1388 foreach($CFG->config_php_settings as $n=>$v) {
1389 if (is_null($v) or is_array($v) or is_object($v)) {
1390 // we do not want any extra mess here, just real settings that could be saved in db
1391 unset($localcfg[$n]);
1392 } else {
1393 //convert to string as if it went through the DB
1394 $localcfg[$n] = (string)$v;
1397 return (object)$localcfg;
1402 * Removes a key from global configuration
1404 * @param string $name the key to set
1405 * @param string $plugin (optional) the plugin scope
1406 * @global object
1407 * @return boolean whether the operation succeeded.
1409 function unset_config($name, $plugin=NULL) {
1410 global $CFG, $DB;
1412 if (empty($plugin)) {
1413 unset($CFG->$name);
1414 $DB->delete_records('config', array('name'=>$name));
1415 } else {
1416 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1419 return true;
1423 * Remove all the config variables for a given plugin.
1425 * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1426 * @return boolean whether the operation succeeded.
1428 function unset_all_config_for_plugin($plugin) {
1429 global $DB;
1430 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1431 $like = $DB->sql_like('name', '?', true, true, false, '|');
1432 $params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
1433 $DB->delete_records_select('config', $like, $params);
1434 return true;
1438 * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1440 * All users are verified if they still have the necessary capability.
1442 * @param string $value the value of the config setting.
1443 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1444 * @param bool $include admins, include administrators
1445 * @return array of user objects.
1447 function get_users_from_config($value, $capability, $includeadmins = true) {
1448 global $CFG, $DB;
1450 if (empty($value) or $value === '$@NONE@$') {
1451 return array();
1454 // we have to make sure that users still have the necessary capability,
1455 // it should be faster to fetch them all first and then test if they are present
1456 // instead of validating them one-by-one
1457 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
1458 if ($includeadmins) {
1459 $admins = get_admins();
1460 foreach ($admins as $admin) {
1461 $users[$admin->id] = $admin;
1465 if ($value === '$@ALL@$') {
1466 return $users;
1469 $result = array(); // result in correct order
1470 $allowed = explode(',', $value);
1471 foreach ($allowed as $uid) {
1472 if (isset($users[$uid])) {
1473 $user = $users[$uid];
1474 $result[$user->id] = $user;
1478 return $result;
1483 * Invalidates browser caches and cached data in temp
1484 * @return void
1486 function purge_all_caches() {
1487 global $CFG;
1489 reset_text_filters_cache();
1490 js_reset_all_caches();
1491 theme_reset_all_caches();
1492 get_string_manager()->reset_caches();
1493 textlib::reset_caches();
1495 // purge all other caches: rss, simplepie, etc.
1496 remove_dir($CFG->cachedir.'', true);
1498 // make sure cache dir is writable, throws exception if not
1499 make_cache_directory('');
1501 // hack: this script may get called after the purifier was initialised,
1502 // but we do not want to verify repeatedly this exists in each call
1503 make_cache_directory('htmlpurifier');
1507 * Get volatile flags
1509 * @param string $type
1510 * @param int $changedsince default null
1511 * @return records array
1513 function get_cache_flags($type, $changedsince=NULL) {
1514 global $DB;
1516 $params = array('type'=>$type, 'expiry'=>time());
1517 $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1518 if ($changedsince !== NULL) {
1519 $params['changedsince'] = $changedsince;
1520 $sqlwhere .= " AND timemodified > :changedsince";
1522 $cf = array();
1524 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1525 foreach ($flags as $flag) {
1526 $cf[$flag->name] = $flag->value;
1529 return $cf;
1533 * Get volatile flags
1535 * @param string $type
1536 * @param string $name
1537 * @param int $changedsince default null
1538 * @return records array
1540 function get_cache_flag($type, $name, $changedsince=NULL) {
1541 global $DB;
1543 $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
1545 $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1546 if ($changedsince !== NULL) {
1547 $params['changedsince'] = $changedsince;
1548 $sqlwhere .= " AND timemodified > :changedsince";
1551 return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1555 * Set a volatile flag
1557 * @param string $type the "type" namespace for the key
1558 * @param string $name the key to set
1559 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
1560 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1561 * @return bool Always returns true
1563 function set_cache_flag($type, $name, $value, $expiry=NULL) {
1564 global $DB;
1566 $timemodified = time();
1567 if ($expiry===NULL || $expiry < $timemodified) {
1568 $expiry = $timemodified + 24 * 60 * 60;
1569 } else {
1570 $expiry = (int)$expiry;
1573 if ($value === NULL) {
1574 unset_cache_flag($type,$name);
1575 return true;
1578 if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE)) { // this is a potential problem in DEBUG_DEVELOPER
1579 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
1580 return true; //no need to update; helps rcache too
1582 $f->value = $value;
1583 $f->expiry = $expiry;
1584 $f->timemodified = $timemodified;
1585 $DB->update_record('cache_flags', $f);
1586 } else {
1587 $f = new stdClass();
1588 $f->flagtype = $type;
1589 $f->name = $name;
1590 $f->value = $value;
1591 $f->expiry = $expiry;
1592 $f->timemodified = $timemodified;
1593 $DB->insert_record('cache_flags', $f);
1595 return true;
1599 * Removes a single volatile flag
1601 * @global object
1602 * @param string $type the "type" namespace for the key
1603 * @param string $name the key to set
1604 * @return bool
1606 function unset_cache_flag($type, $name) {
1607 global $DB;
1608 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1609 return true;
1613 * Garbage-collect volatile flags
1615 * @return bool Always returns true
1617 function gc_cache_flags() {
1618 global $DB;
1619 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1620 return true;
1623 // USER PREFERENCE API
1626 * Refresh user preference cache. This is used most often for $USER
1627 * object that is stored in session, but it also helps with performance in cron script.
1629 * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1631 * @package core
1632 * @category preference
1633 * @access public
1634 * @param stdClass $user User object. Preferences are preloaded into 'preference' property
1635 * @param int $cachelifetime Cache life time on the current page (in seconds)
1636 * @throws coding_exception
1637 * @return null
1639 function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
1640 global $DB;
1641 static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
1643 if (!isset($user->id)) {
1644 throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1647 if (empty($user->id) or isguestuser($user->id)) {
1648 // No permanent storage for not-logged-in users and guest
1649 if (!isset($user->preference)) {
1650 $user->preference = array();
1652 return;
1655 $timenow = time();
1657 if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
1658 // Already loaded at least once on this page. Are we up to date?
1659 if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
1660 // no need to reload - we are on the same page and we loaded prefs just a moment ago
1661 return;
1663 } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
1664 // no change since the lastcheck on this page
1665 $user->preference['_lastloaded'] = $timenow;
1666 return;
1670 // OK, so we have to reload all preferences
1671 $loadedusers[$user->id] = true;
1672 $user->preference = $DB->get_records_menu('user_preferences', array('userid'=>$user->id), '', 'name,value'); // All values
1673 $user->preference['_lastloaded'] = $timenow;
1677 * Called from set/unset_user_preferences, so that the prefs can
1678 * be correctly reloaded in different sessions.
1680 * NOTE: internal function, do not call from other code.
1682 * @package core
1683 * @access private
1684 * @param integer $userid the user whose prefs were changed.
1686 function mark_user_preferences_changed($userid) {
1687 global $CFG;
1689 if (empty($userid) or isguestuser($userid)) {
1690 // no cache flags for guest and not-logged-in users
1691 return;
1694 set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
1698 * Sets a preference for the specified user.
1700 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1702 * @package core
1703 * @category preference
1704 * @access public
1705 * @param string $name The key to set as preference for the specified user
1706 * @param string $value The value to set for the $name key in the specified user's
1707 * record, null means delete current value.
1708 * @param stdClass|int|null $user A moodle user object or id, null means current user
1709 * @throws coding_exception
1710 * @return bool Always true or exception
1712 function set_user_preference($name, $value, $user = null) {
1713 global $USER, $DB;
1715 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1716 throw new coding_exception('Invalid preference name in set_user_preference() call');
1719 if (is_null($value)) {
1720 // null means delete current
1721 return unset_user_preference($name, $user);
1722 } else if (is_object($value)) {
1723 throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1724 } else if (is_array($value)) {
1725 throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1727 $value = (string)$value;
1728 if (textlib::strlen($value) > 1333) { //value column maximum length is 1333 characters
1729 throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
1732 if (is_null($user)) {
1733 $user = $USER;
1734 } else if (isset($user->id)) {
1735 // $user is valid object
1736 } else if (is_numeric($user)) {
1737 $user = (object)array('id'=>(int)$user);
1738 } else {
1739 throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1742 check_user_preferences_loaded($user);
1744 if (empty($user->id) or isguestuser($user->id)) {
1745 // no permanent storage for not-logged-in users and guest
1746 $user->preference[$name] = $value;
1747 return true;
1750 if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>$name))) {
1751 if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
1752 // preference already set to this value
1753 return true;
1755 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id));
1757 } else {
1758 $preference = new stdClass();
1759 $preference->userid = $user->id;
1760 $preference->name = $name;
1761 $preference->value = $value;
1762 $DB->insert_record('user_preferences', $preference);
1765 // update value in cache
1766 $user->preference[$name] = $value;
1768 // set reload flag for other sessions
1769 mark_user_preferences_changed($user->id);
1771 return true;
1775 * Sets a whole array of preferences for the current user
1777 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1779 * @package core
1780 * @category preference
1781 * @access public
1782 * @param array $prefarray An array of key/value pairs to be set
1783 * @param stdClass|int|null $user A moodle user object or id, null means current user
1784 * @return bool Always true or exception
1786 function set_user_preferences(array $prefarray, $user = null) {
1787 foreach ($prefarray as $name => $value) {
1788 set_user_preference($name, $value, $user);
1790 return true;
1794 * Unsets a preference completely by deleting it from the database
1796 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1798 * @package core
1799 * @category preference
1800 * @access public
1801 * @param string $name The key to unset as preference for the specified user
1802 * @param stdClass|int|null $user A moodle user object or id, null means current user
1803 * @throws coding_exception
1804 * @return bool Always true or exception
1806 function unset_user_preference($name, $user = null) {
1807 global $USER, $DB;
1809 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1810 throw new coding_exception('Invalid preference name in unset_user_preference() call');
1813 if (is_null($user)) {
1814 $user = $USER;
1815 } else if (isset($user->id)) {
1816 // $user is valid object
1817 } else if (is_numeric($user)) {
1818 $user = (object)array('id'=>(int)$user);
1819 } else {
1820 throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1823 check_user_preferences_loaded($user);
1825 if (empty($user->id) or isguestuser($user->id)) {
1826 // no permanent storage for not-logged-in user and guest
1827 unset($user->preference[$name]);
1828 return true;
1831 // delete from DB
1832 $DB->delete_records('user_preferences', array('userid'=>$user->id, 'name'=>$name));
1834 // delete the preference from cache
1835 unset($user->preference[$name]);
1837 // set reload flag for other sessions
1838 mark_user_preferences_changed($user->id);
1840 return true;
1844 * Used to fetch user preference(s)
1846 * If no arguments are supplied this function will return
1847 * all of the current user preferences as an array.
1849 * If a name is specified then this function
1850 * attempts to return that particular preference value. If
1851 * none is found, then the optional value $default is returned,
1852 * otherwise NULL.
1854 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1856 * @package core
1857 * @category preference
1858 * @access public
1859 * @param string $name Name of the key to use in finding a preference value
1860 * @param mixed|null $default Value to be returned if the $name key is not set in the user preferences
1861 * @param stdClass|int|null $user A moodle user object or id, null means current user
1862 * @throws coding_exception
1863 * @return string|mixed|null A string containing the value of a single preference. An
1864 * array with all of the preferences or null
1866 function get_user_preferences($name = null, $default = null, $user = null) {
1867 global $USER;
1869 if (is_null($name)) {
1870 // all prefs
1871 } else if (is_numeric($name) or $name === '_lastloaded') {
1872 throw new coding_exception('Invalid preference name in get_user_preferences() call');
1875 if (is_null($user)) {
1876 $user = $USER;
1877 } else if (isset($user->id)) {
1878 // $user is valid object
1879 } else if (is_numeric($user)) {
1880 $user = (object)array('id'=>(int)$user);
1881 } else {
1882 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1885 check_user_preferences_loaded($user);
1887 if (empty($name)) {
1888 return $user->preference; // All values
1889 } else if (isset($user->preference[$name])) {
1890 return $user->preference[$name]; // The single string value
1891 } else {
1892 return $default; // Default value (null if not specified)
1896 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1899 * Given date parts in user time produce a GMT timestamp.
1901 * @package core
1902 * @category time
1903 * @param int $year The year part to create timestamp of
1904 * @param int $month The month part to create timestamp of
1905 * @param int $day The day part to create timestamp of
1906 * @param int $hour The hour part to create timestamp of
1907 * @param int $minute The minute part to create timestamp of
1908 * @param int $second The second part to create timestamp of
1909 * @param int|float|string $timezone Timezone modifier, used to calculate GMT time offset.
1910 * if 99 then default user's timezone is used {@link http://docs.moodle.org/dev/Time_API#Timezone}
1911 * @param bool $applydst Toggle Daylight Saving Time, default true, will be
1912 * applied only if timezone is 99 or string.
1913 * @return int GMT timestamp
1915 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1917 //save input timezone, required for dst offset check.
1918 $passedtimezone = $timezone;
1920 $timezone = get_user_timezone_offset($timezone);
1922 if (abs($timezone) > 13) { //server time
1923 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1924 } else {
1925 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1926 $time = usertime($time, $timezone);
1928 //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
1929 if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
1930 $time -= dst_offset_on($time, $passedtimezone);
1934 return $time;
1939 * Format a date/time (seconds) as weeks, days, hours etc as needed
1941 * Given an amount of time in seconds, returns string
1942 * formatted nicely as weeks, days, hours etc as needed
1944 * @package core
1945 * @category time
1946 * @uses MINSECS
1947 * @uses HOURSECS
1948 * @uses DAYSECS
1949 * @uses YEARSECS
1950 * @param int $totalsecs Time in seconds
1951 * @param object $str Should be a time object
1952 * @return string A nicely formatted date/time string
1954 function format_time($totalsecs, $str=NULL) {
1956 $totalsecs = abs($totalsecs);
1958 if (!$str) { // Create the str structure the slow way
1959 $str = new stdClass();
1960 $str->day = get_string('day');
1961 $str->days = get_string('days');
1962 $str->hour = get_string('hour');
1963 $str->hours = get_string('hours');
1964 $str->min = get_string('min');
1965 $str->mins = get_string('mins');
1966 $str->sec = get_string('sec');
1967 $str->secs = get_string('secs');
1968 $str->year = get_string('year');
1969 $str->years = get_string('years');
1973 $years = floor($totalsecs/YEARSECS);
1974 $remainder = $totalsecs - ($years*YEARSECS);
1975 $days = floor($remainder/DAYSECS);
1976 $remainder = $totalsecs - ($days*DAYSECS);
1977 $hours = floor($remainder/HOURSECS);
1978 $remainder = $remainder - ($hours*HOURSECS);
1979 $mins = floor($remainder/MINSECS);
1980 $secs = $remainder - ($mins*MINSECS);
1982 $ss = ($secs == 1) ? $str->sec : $str->secs;
1983 $sm = ($mins == 1) ? $str->min : $str->mins;
1984 $sh = ($hours == 1) ? $str->hour : $str->hours;
1985 $sd = ($days == 1) ? $str->day : $str->days;
1986 $sy = ($years == 1) ? $str->year : $str->years;
1988 $oyears = '';
1989 $odays = '';
1990 $ohours = '';
1991 $omins = '';
1992 $osecs = '';
1994 if ($years) $oyears = $years .' '. $sy;
1995 if ($days) $odays = $days .' '. $sd;
1996 if ($hours) $ohours = $hours .' '. $sh;
1997 if ($mins) $omins = $mins .' '. $sm;
1998 if ($secs) $osecs = $secs .' '. $ss;
2000 if ($years) return trim($oyears .' '. $odays);
2001 if ($days) return trim($odays .' '. $ohours);
2002 if ($hours) return trim($ohours .' '. $omins);
2003 if ($mins) return trim($omins .' '. $osecs);
2004 if ($secs) return $osecs;
2005 return get_string('now');
2009 * Returns a formatted string that represents a date in user time
2011 * Returns a formatted string that represents a date in user time
2012 * <b>WARNING: note that the format is for strftime(), not date().</b>
2013 * Because of a bug in most Windows time libraries, we can't use
2014 * the nicer %e, so we have to use %d which has leading zeroes.
2015 * A lot of the fuss in the function is just getting rid of these leading
2016 * zeroes as efficiently as possible.
2018 * If parameter fixday = true (default), then take off leading
2019 * zero from %d, else maintain it.
2021 * @package core
2022 * @category time
2023 * @param int $date the timestamp in UTC, as obtained from the database.
2024 * @param string $format strftime format. You should probably get this using
2025 * get_string('strftime...', 'langconfig');
2026 * @param int|float|string $timezone by default, uses the user's time zone. if numeric and
2027 * not 99 then daylight saving will not be added.
2028 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2029 * @param bool $fixday If true (default) then the leading zero from %d is removed.
2030 * If false then the leading zero is maintained.
2031 * @param bool $fixhour If true (default) then the leading zero from %I is removed.
2032 * @return string the formatted date/time.
2034 function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true) {
2036 global $CFG;
2038 if (empty($format)) {
2039 $format = get_string('strftimedaydatetime', 'langconfig');
2042 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
2043 $fixday = false;
2044 } else if ($fixday) {
2045 $formatnoday = str_replace('%d', 'DD', $format);
2046 $fixday = ($formatnoday != $format);
2047 $format = $formatnoday;
2050 // Note: This logic about fixing 12-hour time to remove unnecessary leading
2051 // zero is required because on Windows, PHP strftime function does not
2052 // support the correct 'hour without leading zero' parameter (%l).
2053 if (!empty($CFG->nofixhour)) {
2054 // Config.php can force %I not to be fixed.
2055 $fixhour = false;
2056 } else if ($fixhour) {
2057 $formatnohour = str_replace('%I', 'HH', $format);
2058 $fixhour = ($formatnohour != $format);
2059 $format = $formatnohour;
2062 //add daylight saving offset for string timezones only, as we can't get dst for
2063 //float values. if timezone is 99 (user default timezone), then try update dst.
2064 if ((99 == $timezone) || !is_numeric($timezone)) {
2065 $date += dst_offset_on($date, $timezone);
2068 $timezone = get_user_timezone_offset($timezone);
2070 // If we are running under Windows convert to windows encoding and then back to UTF-8
2071 // (because it's impossible to specify UTF-8 to fetch locale info in Win32)
2073 if (abs($timezone) > 13) { /// Server time
2074 $datestring = date_format_string($date, $format, $timezone);
2075 if ($fixday) {
2076 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
2077 $datestring = str_replace('DD', $daystring, $datestring);
2079 if ($fixhour) {
2080 $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
2081 $datestring = str_replace('HH', $hourstring, $datestring);
2084 } else {
2085 $date += (int)($timezone * 3600);
2086 $datestring = date_format_string($date, $format, $timezone);
2087 if ($fixday) {
2088 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
2089 $datestring = str_replace('DD', $daystring, $datestring);
2091 if ($fixhour) {
2092 $hourstring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %I', $date)));
2093 $datestring = str_replace('HH', $hourstring, $datestring);
2097 return $datestring;
2101 * Returns a formatted date ensuring it is UTF-8.
2103 * If we are running under Windows convert to Windows encoding and then back to UTF-8
2104 * (because it's impossible to specify UTF-8 to fetch locale info in Win32).
2106 * This function does not do any calculation regarding the user preferences and should
2107 * therefore receive the final date timestamp, format and timezone. Timezone being only used
2108 * to differenciate the use of server time or not (strftime() against gmstrftime()).
2110 * @param int $date the timestamp.
2111 * @param string $format strftime format.
2112 * @param int|float $timezone the numerical timezone, typically returned by {@link get_user_timezone_offset()}.
2113 * @return string the formatted date/time.
2114 * @since 2.3.3
2116 function date_format_string($date, $format, $tz = 99) {
2117 global $CFG;
2118 if (abs($tz) > 13) {
2119 if ($CFG->ostype == 'WINDOWS') {
2120 $localewincharset = get_string('localewincharset', 'langconfig');
2121 $format = textlib::convert($format, 'utf-8', $localewincharset);
2122 $datestring = strftime($format, $date);
2123 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2124 } else {
2125 $datestring = strftime($format, $date);
2127 } else {
2128 if ($CFG->ostype == 'WINDOWS') {
2129 $localewincharset = get_string('localewincharset', 'langconfig');
2130 $format = textlib::convert($format, 'utf-8', $localewincharset);
2131 $datestring = gmstrftime($format, $date);
2132 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2133 } else {
2134 $datestring = gmstrftime($format, $date);
2137 return $datestring;
2141 * Given a $time timestamp in GMT (seconds since epoch),
2142 * returns an array that represents the date in user time
2144 * @package core
2145 * @category time
2146 * @uses HOURSECS
2147 * @param int $time Timestamp in GMT
2148 * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
2149 * dst offset is applyed {@link http://docs.moodle.org/dev/Time_API#Timezone}
2150 * @return array An array that represents the date in user time
2152 function usergetdate($time, $timezone=99) {
2154 //save input timezone, required for dst offset check.
2155 $passedtimezone = $timezone;
2157 $timezone = get_user_timezone_offset($timezone);
2159 if (abs($timezone) > 13) { // Server time
2160 return getdate($time);
2163 //add daylight saving offset for string timezones only, as we can't get dst for
2164 //float values. if timezone is 99 (user default timezone), then try update dst.
2165 if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
2166 $time += dst_offset_on($time, $passedtimezone);
2169 $time += intval((float)$timezone * HOURSECS);
2171 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
2173 //be careful to ensure the returned array matches that produced by getdate() above
2174 list(
2175 $getdate['month'],
2176 $getdate['weekday'],
2177 $getdate['yday'],
2178 $getdate['year'],
2179 $getdate['mon'],
2180 $getdate['wday'],
2181 $getdate['mday'],
2182 $getdate['hours'],
2183 $getdate['minutes'],
2184 $getdate['seconds']
2185 ) = explode('_', $datestring);
2187 // set correct datatype to match with getdate()
2188 $getdate['seconds'] = (int)$getdate['seconds'];
2189 $getdate['yday'] = (int)$getdate['yday'] - 1; // gettime returns 0 through 365
2190 $getdate['year'] = (int)$getdate['year'];
2191 $getdate['mon'] = (int)$getdate['mon'];
2192 $getdate['wday'] = (int)$getdate['wday'];
2193 $getdate['mday'] = (int)$getdate['mday'];
2194 $getdate['hours'] = (int)$getdate['hours'];
2195 $getdate['minutes'] = (int)$getdate['minutes'];
2196 return $getdate;
2200 * Given a GMT timestamp (seconds since epoch), offsets it by
2201 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
2203 * @package core
2204 * @category time
2205 * @uses HOURSECS
2206 * @param int $date Timestamp in GMT
2207 * @param float|int|string $timezone timezone to calculate GMT time offset before
2208 * calculating user time, 99 is default user timezone
2209 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2210 * @return int
2212 function usertime($date, $timezone=99) {
2214 $timezone = get_user_timezone_offset($timezone);
2216 if (abs($timezone) > 13) {
2217 return $date;
2219 return $date - (int)($timezone * HOURSECS);
2223 * Given a time, return the GMT timestamp of the most recent midnight
2224 * for the current user.
2226 * @package core
2227 * @category time
2228 * @param int $date Timestamp in GMT
2229 * @param float|int|string $timezone timezone to calculate GMT time offset before
2230 * calculating user midnight time, 99 is default user timezone
2231 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2232 * @return int Returns a GMT timestamp
2234 function usergetmidnight($date, $timezone=99) {
2236 $userdate = usergetdate($date, $timezone);
2238 // Time of midnight of this user's day, in GMT
2239 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
2244 * Returns a string that prints the user's timezone
2246 * @package core
2247 * @category time
2248 * @param float|int|string $timezone timezone to calculate GMT time offset before
2249 * calculating user timezone, 99 is default user timezone
2250 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2251 * @return string
2253 function usertimezone($timezone=99) {
2255 $tz = get_user_timezone($timezone);
2257 if (!is_float($tz)) {
2258 return $tz;
2261 if(abs($tz) > 13) { // Server time
2262 return get_string('serverlocaltime');
2265 if($tz == intval($tz)) {
2266 // Don't show .0 for whole hours
2267 $tz = intval($tz);
2270 if($tz == 0) {
2271 return 'UTC';
2273 else if($tz > 0) {
2274 return 'UTC+'.$tz;
2276 else {
2277 return 'UTC'.$tz;
2283 * Returns a float which represents the user's timezone difference from GMT in hours
2284 * Checks various settings and picks the most dominant of those which have a value
2286 * @package core
2287 * @category time
2288 * @param float|int|string $tz timezone to calculate GMT time offset for user,
2289 * 99 is default user timezone
2290 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2291 * @return float
2293 function get_user_timezone_offset($tz = 99) {
2295 global $USER, $CFG;
2297 $tz = get_user_timezone($tz);
2299 if (is_float($tz)) {
2300 return $tz;
2301 } else {
2302 $tzrecord = get_timezone_record($tz);
2303 if (empty($tzrecord)) {
2304 return 99.0;
2306 return (float)$tzrecord->gmtoff / HOURMINS;
2311 * Returns an int which represents the systems's timezone difference from GMT in seconds
2313 * @package core
2314 * @category time
2315 * @param float|int|string $tz timezone for which offset is required.
2316 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2317 * @return int|bool if found, false is timezone 99 or error
2319 function get_timezone_offset($tz) {
2320 global $CFG;
2322 if ($tz == 99) {
2323 return false;
2326 if (is_numeric($tz)) {
2327 return intval($tz * 60*60);
2330 if (!$tzrecord = get_timezone_record($tz)) {
2331 return false;
2333 return intval($tzrecord->gmtoff * 60);
2337 * Returns a float or a string which denotes the user's timezone
2338 * 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)
2339 * means that for this timezone there are also DST rules to be taken into account
2340 * Checks various settings and picks the most dominant of those which have a value
2342 * @package core
2343 * @category time
2344 * @param float|int|string $tz timezone to calculate GMT time offset before
2345 * calculating user timezone, 99 is default user timezone
2346 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2347 * @return float|string
2349 function get_user_timezone($tz = 99) {
2350 global $USER, $CFG;
2352 $timezones = array(
2353 $tz,
2354 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
2355 isset($USER->timezone) ? $USER->timezone : 99,
2356 isset($CFG->timezone) ? $CFG->timezone : 99,
2359 $tz = 99;
2361 // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array
2362 while(((empty($tz) && !is_numeric($tz)) || $tz == 99) && $next = each($timezones)) {
2363 $tz = $next['value'];
2365 return is_numeric($tz) ? (float) $tz : $tz;
2369 * Returns cached timezone record for given $timezonename
2371 * @package core
2372 * @param string $timezonename name of the timezone
2373 * @return stdClass|bool timezonerecord or false
2375 function get_timezone_record($timezonename) {
2376 global $CFG, $DB;
2377 static $cache = NULL;
2379 if ($cache === NULL) {
2380 $cache = array();
2383 if (isset($cache[$timezonename])) {
2384 return $cache[$timezonename];
2387 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
2388 WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
2392 * Build and store the users Daylight Saving Time (DST) table
2394 * @package core
2395 * @param int $from_year Start year for the table, defaults to 1971
2396 * @param int $to_year End year for the table, defaults to 2035
2397 * @param int|float|string $strtimezone, timezone to check if dst should be applyed.
2398 * @return bool
2400 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
2401 global $CFG, $SESSION, $DB;
2403 $usertz = get_user_timezone($strtimezone);
2405 if (is_float($usertz)) {
2406 // Trivial timezone, no DST
2407 return false;
2410 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
2411 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2412 unset($SESSION->dst_offsets);
2413 unset($SESSION->dst_range);
2416 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
2417 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
2418 // This will be the return path most of the time, pretty light computationally
2419 return true;
2422 // Reaching here means we either need to extend our table or create it from scratch
2424 // Remember which TZ we calculated these changes for
2425 $SESSION->dst_offsettz = $usertz;
2427 if(empty($SESSION->dst_offsets)) {
2428 // If we 're creating from scratch, put the two guard elements in there
2429 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
2431 if(empty($SESSION->dst_range)) {
2432 // If creating from scratch
2433 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
2434 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
2436 // Fill in the array with the extra years we need to process
2437 $yearstoprocess = array();
2438 for($i = $from; $i <= $to; ++$i) {
2439 $yearstoprocess[] = $i;
2442 // Take note of which years we have processed for future calls
2443 $SESSION->dst_range = array($from, $to);
2445 else {
2446 // If needing to extend the table, do the same
2447 $yearstoprocess = array();
2449 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
2450 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
2452 if($from < $SESSION->dst_range[0]) {
2453 // Take note of which years we need to process and then note that we have processed them for future calls
2454 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2455 $yearstoprocess[] = $i;
2457 $SESSION->dst_range[0] = $from;
2459 if($to > $SESSION->dst_range[1]) {
2460 // Take note of which years we need to process and then note that we have processed them for future calls
2461 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2462 $yearstoprocess[] = $i;
2464 $SESSION->dst_range[1] = $to;
2468 if(empty($yearstoprocess)) {
2469 // This means that there was a call requesting a SMALLER range than we have already calculated
2470 return true;
2473 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2474 // Also, the array is sorted in descending timestamp order!
2476 // Get DB data
2478 static $presets_cache = array();
2479 if (!isset($presets_cache[$usertz])) {
2480 $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');
2482 if(empty($presets_cache[$usertz])) {
2483 return false;
2486 // Remove ending guard (first element of the array)
2487 reset($SESSION->dst_offsets);
2488 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2490 // Add all required change timestamps
2491 foreach($yearstoprocess as $y) {
2492 // Find the record which is in effect for the year $y
2493 foreach($presets_cache[$usertz] as $year => $preset) {
2494 if($year <= $y) {
2495 break;
2499 $changes = dst_changes_for_year($y, $preset);
2501 if($changes === NULL) {
2502 continue;
2504 if($changes['dst'] != 0) {
2505 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2507 if($changes['std'] != 0) {
2508 $SESSION->dst_offsets[$changes['std']] = 0;
2512 // Put in a guard element at the top
2513 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2514 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2516 // Sort again
2517 krsort($SESSION->dst_offsets);
2519 return true;
2523 * Calculates the required DST change and returns a Timestamp Array
2525 * @package core
2526 * @category time
2527 * @uses HOURSECS
2528 * @uses MINSECS
2529 * @param int|string $year Int or String Year to focus on
2530 * @param object $timezone Instatiated Timezone object
2531 * @return array|null Array dst=>xx, 0=>xx, std=>yy, 1=>yy or NULL
2533 function dst_changes_for_year($year, $timezone) {
2535 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2536 return NULL;
2539 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2540 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2542 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
2543 list($std_hour, $std_min) = explode(':', $timezone->std_time);
2545 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2546 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2548 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2549 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2550 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2552 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
2553 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
2555 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2559 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2560 * - Note: Daylight saving only works for string timezones and not for float.
2562 * @package core
2563 * @category time
2564 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2565 * @param int|float|string $strtimezone timezone for which offset is expected, if 99 or null
2566 * then user's default timezone is used. {@link http://docs.moodle.org/dev/Time_API#Timezone}
2567 * @return int
2569 function dst_offset_on($time, $strtimezone = NULL) {
2570 global $SESSION;
2572 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
2573 return 0;
2576 reset($SESSION->dst_offsets);
2577 while(list($from, $offset) = each($SESSION->dst_offsets)) {
2578 if($from <= $time) {
2579 break;
2583 // This is the normal return path
2584 if($offset !== NULL) {
2585 return $offset;
2588 // Reaching this point means we haven't calculated far enough, do it now:
2589 // Calculate extra DST changes if needed and recurse. The recursion always
2590 // moves toward the stopping condition, so will always end.
2592 if($from == 0) {
2593 // We need a year smaller than $SESSION->dst_range[0]
2594 if($SESSION->dst_range[0] == 1971) {
2595 return 0;
2597 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
2598 return dst_offset_on($time, $strtimezone);
2600 else {
2601 // We need a year larger than $SESSION->dst_range[1]
2602 if($SESSION->dst_range[1] == 2035) {
2603 return 0;
2605 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
2606 return dst_offset_on($time, $strtimezone);
2611 * Calculates when the day appears in specific month
2613 * @package core
2614 * @category time
2615 * @param int $startday starting day of the month
2616 * @param int $weekday The day when week starts (normally taken from user preferences)
2617 * @param int $month The month whose day is sought
2618 * @param int $year The year of the month whose day is sought
2619 * @return int
2621 function find_day_in_month($startday, $weekday, $month, $year) {
2623 $daysinmonth = days_in_month($month, $year);
2625 if($weekday == -1) {
2626 // Don't care about weekday, so return:
2627 // abs($startday) if $startday != -1
2628 // $daysinmonth otherwise
2629 return ($startday == -1) ? $daysinmonth : abs($startday);
2632 // From now on we 're looking for a specific weekday
2634 // Give "end of month" its actual value, since we know it
2635 if($startday == -1) {
2636 $startday = -1 * $daysinmonth;
2639 // Starting from day $startday, the sign is the direction
2641 if($startday < 1) {
2643 $startday = abs($startday);
2644 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2646 // This is the last such weekday of the month
2647 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2648 if($lastinmonth > $daysinmonth) {
2649 $lastinmonth -= 7;
2652 // Find the first such weekday <= $startday
2653 while($lastinmonth > $startday) {
2654 $lastinmonth -= 7;
2657 return $lastinmonth;
2660 else {
2662 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2664 $diff = $weekday - $indexweekday;
2665 if($diff < 0) {
2666 $diff += 7;
2669 // This is the first such weekday of the month equal to or after $startday
2670 $firstfromindex = $startday + $diff;
2672 return $firstfromindex;
2678 * Calculate the number of days in a given month
2680 * @package core
2681 * @category time
2682 * @param int $month The month whose day count is sought
2683 * @param int $year The year of the month whose day count is sought
2684 * @return int
2686 function days_in_month($month, $year) {
2687 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2691 * Calculate the position in the week of a specific calendar day
2693 * @package core
2694 * @category time
2695 * @param int $day The day of the date whose position in the week is sought
2696 * @param int $month The month of the date whose position in the week is sought
2697 * @param int $year The year of the date whose position in the week is sought
2698 * @return int
2700 function dayofweek($day, $month, $year) {
2701 // I wonder if this is any different from
2702 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2703 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2706 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2709 * Returns full login url.
2711 * @return string login url
2713 function get_login_url() {
2714 global $CFG;
2716 $url = "$CFG->wwwroot/login/index.php";
2718 if (!empty($CFG->loginhttps)) {
2719 $url = str_replace('http:', 'https:', $url);
2722 return $url;
2726 * This function checks that the current user is logged in and has the
2727 * required privileges
2729 * This function checks that the current user is logged in, and optionally
2730 * whether they are allowed to be in a particular course and view a particular
2731 * course module.
2732 * If they are not logged in, then it redirects them to the site login unless
2733 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2734 * case they are automatically logged in as guests.
2735 * If $courseid is given and the user is not enrolled in that course then the
2736 * user is redirected to the course enrolment page.
2737 * If $cm is given and the course module is hidden and the user is not a teacher
2738 * in the course then the user is redirected to the course home page.
2740 * When $cm parameter specified, this function sets page layout to 'module'.
2741 * You need to change it manually later if some other layout needed.
2743 * @package core_access
2744 * @category access
2746 * @param mixed $courseorid id of the course or course object
2747 * @param bool $autologinguest default true
2748 * @param object $cm course module object
2749 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2750 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2751 * in order to keep redirects working properly. MDL-14495
2752 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2753 * @return mixed Void, exit, and die depending on path
2755 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2756 global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
2758 // setup global $COURSE, themes, language and locale
2759 if (!empty($courseorid)) {
2760 if (is_object($courseorid)) {
2761 $course = $courseorid;
2762 } else if ($courseorid == SITEID) {
2763 $course = clone($SITE);
2764 } else {
2765 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2767 if ($cm) {
2768 if ($cm->course != $course->id) {
2769 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2771 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2772 if (!($cm instanceof cm_info)) {
2773 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2774 // db queries so this is not really a performance concern, however it is obviously
2775 // better if you use get_fast_modinfo to get the cm before calling this.
2776 $modinfo = get_fast_modinfo($course);
2777 $cm = $modinfo->get_cm($cm->id);
2779 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2780 $PAGE->set_pagelayout('incourse');
2781 } else {
2782 $PAGE->set_course($course); // set's up global $COURSE
2784 } else {
2785 // do not touch global $COURSE via $PAGE->set_course(),
2786 // the reasons is we need to be able to call require_login() at any time!!
2787 $course = $SITE;
2788 if ($cm) {
2789 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2793 // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2794 // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2795 // risk leading the user back to the AJAX request URL.
2796 if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
2797 $setwantsurltome = false;
2800 // If the user is not even logged in yet then make sure they are
2801 if (!isloggedin()) {
2802 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2803 if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2804 // misconfigured site guest, just redirect to login page
2805 redirect(get_login_url());
2806 exit; // never reached
2808 $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2809 complete_user_login($guest);
2810 $USER->autologinguest = true;
2811 $SESSION->lang = $lang;
2812 } else {
2813 //NOTE: $USER->site check was obsoleted by session test cookie,
2814 // $USER->confirmed test is in login/index.php
2815 if ($preventredirect) {
2816 throw new require_login_exception('You are not logged in');
2819 if ($setwantsurltome) {
2820 $SESSION->wantsurl = qualified_me();
2822 if (!empty($_SERVER['HTTP_REFERER'])) {
2823 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
2825 redirect(get_login_url());
2826 exit; // never reached
2830 // loginas as redirection if needed
2831 if ($course->id != SITEID and session_is_loggedinas()) {
2832 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2833 if ($USER->loginascontext->instanceid != $course->id) {
2834 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2839 // check whether the user should be changing password (but only if it is REALLY them)
2840 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2841 $userauth = get_auth_plugin($USER->auth);
2842 if ($userauth->can_change_password() and !$preventredirect) {
2843 if ($setwantsurltome) {
2844 $SESSION->wantsurl = qualified_me();
2846 if ($changeurl = $userauth->change_password_url()) {
2847 //use plugin custom url
2848 redirect($changeurl);
2849 } else {
2850 //use moodle internal method
2851 if (empty($CFG->loginhttps)) {
2852 redirect($CFG->wwwroot .'/login/change_password.php');
2853 } else {
2854 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
2855 redirect($wwwroot .'/login/change_password.php');
2858 } else {
2859 print_error('nopasswordchangeforced', 'auth');
2863 // Check that the user account is properly set up
2864 if (user_not_fully_set_up($USER)) {
2865 if ($preventredirect) {
2866 throw new require_login_exception('User not fully set-up');
2868 if ($setwantsurltome) {
2869 $SESSION->wantsurl = qualified_me();
2871 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2874 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2875 sesskey();
2877 // Do not bother admins with any formalities
2878 if (is_siteadmin()) {
2879 //set accesstime or the user will appear offline which messes up messaging
2880 user_accesstime_log($course->id);
2881 return;
2884 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2885 if (!$USER->policyagreed and !is_siteadmin()) {
2886 if (!empty($CFG->sitepolicy) and !isguestuser()) {
2887 if ($preventredirect) {
2888 throw new require_login_exception('Policy not agreed');
2890 if ($setwantsurltome) {
2891 $SESSION->wantsurl = qualified_me();
2893 redirect($CFG->wwwroot .'/user/policy.php');
2894 } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
2895 if ($preventredirect) {
2896 throw new require_login_exception('Policy not agreed');
2898 if ($setwantsurltome) {
2899 $SESSION->wantsurl = qualified_me();
2901 redirect($CFG->wwwroot .'/user/policy.php');
2905 // Fetch the system context, the course context, and prefetch its child contexts
2906 $sysctx = get_context_instance(CONTEXT_SYSTEM);
2907 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
2908 if ($cm) {
2909 $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
2910 } else {
2911 $cmcontext = null;
2914 // If the site is currently under maintenance, then print a message
2915 if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
2916 if ($preventredirect) {
2917 throw new require_login_exception('Maintenance in progress');
2920 print_maintenance_message();
2923 // make sure the course itself is not hidden
2924 if ($course->id == SITEID) {
2925 // frontpage can not be hidden
2926 } else {
2927 if (is_role_switched($course->id)) {
2928 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2929 } else {
2930 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2931 // originally there was also test of parent category visibility,
2932 // BUT is was very slow in complex queries involving "my courses"
2933 // now it is also possible to simply hide all courses user is not enrolled in :-)
2934 if ($preventredirect) {
2935 throw new require_login_exception('Course is hidden');
2937 // We need to override the navigation URL as the course won't have
2938 // been added to the navigation and thus the navigation will mess up
2939 // when trying to find it.
2940 navigation_node::override_active_url(new moodle_url('/'));
2941 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2946 // is the user enrolled?
2947 if ($course->id == SITEID) {
2948 // everybody is enrolled on the frontpage
2950 } else {
2951 if (session_is_loggedinas()) {
2952 // Make sure the REAL person can access this course first
2953 $realuser = session_get_realuser();
2954 if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
2955 if ($preventredirect) {
2956 throw new require_login_exception('Invalid course login-as access');
2958 echo $OUTPUT->header();
2959 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2963 $access = false;
2965 if (is_role_switched($course->id)) {
2966 // ok, user had to be inside this course before the switch
2967 $access = true;
2969 } else if (is_viewing($coursecontext, $USER)) {
2970 // ok, no need to mess with enrol
2971 $access = true;
2973 } else {
2974 if (isset($USER->enrol['enrolled'][$course->id])) {
2975 if ($USER->enrol['enrolled'][$course->id] > time()) {
2976 $access = true;
2977 if (isset($USER->enrol['tempguest'][$course->id])) {
2978 unset($USER->enrol['tempguest'][$course->id]);
2979 remove_temp_course_roles($coursecontext);
2981 } else {
2982 //expired
2983 unset($USER->enrol['enrolled'][$course->id]);
2986 if (isset($USER->enrol['tempguest'][$course->id])) {
2987 if ($USER->enrol['tempguest'][$course->id] == 0) {
2988 $access = true;
2989 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
2990 $access = true;
2991 } else {
2992 //expired
2993 unset($USER->enrol['tempguest'][$course->id]);
2994 remove_temp_course_roles($coursecontext);
2998 if ($access) {
2999 // cache ok
3000 } else {
3001 $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
3002 if ($until !== false) {
3003 // active participants may always access, a timestamp in the future, 0 (always) or false.
3004 if ($until == 0) {
3005 $until = ENROL_MAX_TIMESTAMP;
3007 $USER->enrol['enrolled'][$course->id] = $until;
3008 $access = true;
3010 } else {
3011 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
3012 $enrols = enrol_get_plugins(true);
3013 // first ask all enabled enrol instances in course if they want to auto enrol user
3014 foreach($instances as $instance) {
3015 if (!isset($enrols[$instance->enrol])) {
3016 continue;
3018 // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
3019 $until = $enrols[$instance->enrol]->try_autoenrol($instance);
3020 if ($until !== false) {
3021 if ($until == 0) {
3022 $until = ENROL_MAX_TIMESTAMP;
3024 $USER->enrol['enrolled'][$course->id] = $until;
3025 $access = true;
3026 break;
3029 // if not enrolled yet try to gain temporary guest access
3030 if (!$access) {
3031 foreach($instances as $instance) {
3032 if (!isset($enrols[$instance->enrol])) {
3033 continue;
3035 // Get a duration for the guest access, a timestamp in the future or false.
3036 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
3037 if ($until !== false and $until > time()) {
3038 $USER->enrol['tempguest'][$course->id] = $until;
3039 $access = true;
3040 break;
3048 if (!$access) {
3049 if ($preventredirect) {
3050 throw new require_login_exception('Not enrolled');
3052 if ($setwantsurltome) {
3053 $SESSION->wantsurl = qualified_me();
3055 redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
3059 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
3060 // conditional availability, etc
3061 if ($cm && !$cm->uservisible) {
3062 if ($preventredirect) {
3063 throw new require_login_exception('Activity is hidden');
3065 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
3068 // Finally access granted, update lastaccess times
3069 user_accesstime_log($course->id);
3074 * This function just makes sure a user is logged out.
3076 * @package core_access
3078 function require_logout() {
3079 global $USER;
3081 $params = $USER;
3083 if (isloggedin()) {
3084 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
3086 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
3087 foreach($authsequence as $authname) {
3088 $authplugin = get_auth_plugin($authname);
3089 $authplugin->prelogout_hook();
3093 events_trigger('user_logout', $params);
3094 session_get_instance()->terminate_current();
3095 unset($params);
3099 * Weaker version of require_login()
3101 * This is a weaker version of {@link require_login()} which only requires login
3102 * when called from within a course rather than the site page, unless
3103 * the forcelogin option is turned on.
3104 * @see require_login()
3106 * @package core_access
3107 * @category access
3109 * @param mixed $courseorid The course object or id in question
3110 * @param bool $autologinguest Allow autologin guests if that is wanted
3111 * @param object $cm Course activity module if known
3112 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
3113 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
3114 * in order to keep redirects working properly. MDL-14495
3115 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
3116 * @return void
3118 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
3119 global $CFG, $PAGE, $SITE;
3120 $issite = (is_object($courseorid) and $courseorid->id == SITEID)
3121 or (!is_object($courseorid) and $courseorid == SITEID);
3122 if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
3123 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
3124 // db queries so this is not really a performance concern, however it is obviously
3125 // better if you use get_fast_modinfo to get the cm before calling this.
3126 if (is_object($courseorid)) {
3127 $course = $courseorid;
3128 } else {
3129 $course = clone($SITE);
3131 $modinfo = get_fast_modinfo($course);
3132 $cm = $modinfo->get_cm($cm->id);
3134 if (!empty($CFG->forcelogin)) {
3135 // login required for both SITE and courses
3136 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3138 } else if ($issite && !empty($cm) and !$cm->uservisible) {
3139 // always login for hidden activities
3140 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3142 } else if ($issite) {
3143 //login for SITE not required
3144 if ($cm and empty($cm->visible)) {
3145 // hidden activities are not accessible without login
3146 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3147 } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
3148 // not-logged-in users do not have any group membership
3149 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3150 } else {
3151 // We still need to instatiate PAGE vars properly so that things
3152 // that rely on it like navigation function correctly.
3153 if (!empty($courseorid)) {
3154 if (is_object($courseorid)) {
3155 $course = $courseorid;
3156 } else {
3157 $course = clone($SITE);
3159 if ($cm) {
3160 if ($cm->course != $course->id) {
3161 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
3163 $PAGE->set_cm($cm, $course);
3164 $PAGE->set_pagelayout('incourse');
3165 } else {
3166 $PAGE->set_course($course);
3168 } else {
3169 // If $PAGE->course, and hence $PAGE->context, have not already been set
3170 // up properly, set them up now.
3171 $PAGE->set_course($PAGE->course);
3173 //TODO: verify conditional activities here
3174 user_accesstime_log(SITEID);
3175 return;
3178 } else {
3179 // course login always required
3180 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3185 * Require key login. Function terminates with error if key not found or incorrect.
3187 * @global object
3188 * @global object
3189 * @global object
3190 * @global object
3191 * @uses NO_MOODLE_COOKIES
3192 * @uses PARAM_ALPHANUM
3193 * @param string $script unique script identifier
3194 * @param int $instance optional instance id
3195 * @return int Instance ID
3197 function require_user_key_login($script, $instance=null) {
3198 global $USER, $SESSION, $CFG, $DB;
3200 if (!NO_MOODLE_COOKIES) {
3201 print_error('sessioncookiesdisable');
3204 /// extra safety
3205 @session_write_close();
3207 $keyvalue = required_param('key', PARAM_ALPHANUM);
3209 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
3210 print_error('invalidkey');
3213 if (!empty($key->validuntil) and $key->validuntil < time()) {
3214 print_error('expiredkey');
3217 if ($key->iprestriction) {
3218 $remoteaddr = getremoteaddr(null);
3219 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
3220 print_error('ipmismatch');
3224 if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
3225 print_error('invaliduserid');
3228 /// emulate normal session
3229 enrol_check_plugins($user);
3230 session_set_user($user);
3232 /// note we are not using normal login
3233 if (!defined('USER_KEY_LOGIN')) {
3234 define('USER_KEY_LOGIN', true);
3237 /// return instance id - it might be empty
3238 return $key->instance;
3242 * Creates a new private user access key.
3244 * @global object
3245 * @param string $script unique target identifier
3246 * @param int $userid
3247 * @param int $instance optional instance id
3248 * @param string $iprestriction optional ip restricted access
3249 * @param timestamp $validuntil key valid only until given data
3250 * @return string access key value
3252 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3253 global $DB;
3255 $key = new stdClass();
3256 $key->script = $script;
3257 $key->userid = $userid;
3258 $key->instance = $instance;
3259 $key->iprestriction = $iprestriction;
3260 $key->validuntil = $validuntil;
3261 $key->timecreated = time();
3263 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
3264 while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
3265 // must be unique
3266 $key->value = md5($userid.'_'.time().random_string(40));
3268 $DB->insert_record('user_private_key', $key);
3269 return $key->value;
3273 * Delete the user's new private user access keys for a particular script.
3275 * @global object
3276 * @param string $script unique target identifier
3277 * @param int $userid
3278 * @return void
3280 function delete_user_key($script,$userid) {
3281 global $DB;
3282 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
3286 * Gets a private user access key (and creates one if one doesn't exist).
3288 * @global object
3289 * @param string $script unique target identifier
3290 * @param int $userid
3291 * @param int $instance optional instance id
3292 * @param string $iprestriction optional ip restricted access
3293 * @param timestamp $validuntil key valid only until given data
3294 * @return string access key value
3296 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3297 global $DB;
3299 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
3300 'instance'=>$instance, 'iprestriction'=>$iprestriction,
3301 'validuntil'=>$validuntil))) {
3302 return $key->value;
3303 } else {
3304 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
3310 * Modify the user table by setting the currently logged in user's
3311 * last login to now.
3313 * @global object
3314 * @global object
3315 * @return bool Always returns true
3317 function update_user_login_times() {
3318 global $USER, $DB;
3320 if (isguestuser()) {
3321 // Do not update guest access times/ips for performance.
3322 return true;
3325 $now = time();
3327 $user = new stdClass();
3328 $user->id = $USER->id;
3330 // Make sure all users that logged in have some firstaccess.
3331 if ($USER->firstaccess == 0) {
3332 $USER->firstaccess = $user->firstaccess = $now;
3335 // Store the previous current as lastlogin.
3336 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
3338 $USER->currentlogin = $user->currentlogin = $now;
3340 // Function user_accesstime_log() may not update immediately, better do it here.
3341 $USER->lastaccess = $user->lastaccess = $now;
3342 $USER->lastip = $user->lastip = getremoteaddr();
3344 $DB->update_record('user', $user);
3345 return true;
3349 * Determines if a user has completed setting up their account.
3351 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
3352 * @return bool
3354 function user_not_fully_set_up($user) {
3355 if (isguestuser($user)) {
3356 return false;
3358 return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
3362 * Check whether the user has exceeded the bounce threshold
3364 * @global object
3365 * @global object
3366 * @param user $user A {@link $USER} object
3367 * @return bool true=>User has exceeded bounce threshold
3369 function over_bounce_threshold($user) {
3370 global $CFG, $DB;
3372 if (empty($CFG->handlebounces)) {
3373 return false;
3376 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3377 return false;
3380 // set sensible defaults
3381 if (empty($CFG->minbounces)) {
3382 $CFG->minbounces = 10;
3384 if (empty($CFG->bounceratio)) {
3385 $CFG->bounceratio = .20;
3387 $bouncecount = 0;
3388 $sendcount = 0;
3389 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3390 $bouncecount = $bounce->value;
3392 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3393 $sendcount = $send->value;
3395 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
3399 * Used to increment or reset email sent count
3401 * @global object
3402 * @param user $user object containing an id
3403 * @param bool $reset will reset the count to 0
3404 * @return void
3406 function set_send_count($user,$reset=false) {
3407 global $DB;
3409 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3410 return;
3413 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3414 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3415 $DB->update_record('user_preferences', $pref);
3417 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3418 // make a new one
3419 $pref = new stdClass();
3420 $pref->name = 'email_send_count';
3421 $pref->value = 1;
3422 $pref->userid = $user->id;
3423 $DB->insert_record('user_preferences', $pref, false);
3428 * Increment or reset user's email bounce count
3430 * @global object
3431 * @param user $user object containing an id
3432 * @param bool $reset will reset the count to 0
3434 function set_bounce_count($user,$reset=false) {
3435 global $DB;
3437 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3438 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3439 $DB->update_record('user_preferences', $pref);
3441 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3442 // make a new one
3443 $pref = new stdClass();
3444 $pref->name = 'email_bounce_count';
3445 $pref->value = 1;
3446 $pref->userid = $user->id;
3447 $DB->insert_record('user_preferences', $pref, false);
3452 * Keeps track of login attempts
3454 * @global object
3456 function update_login_count() {
3457 global $SESSION;
3459 $max_logins = 10;
3461 if (empty($SESSION->logincount)) {
3462 $SESSION->logincount = 1;
3463 } else {
3464 $SESSION->logincount++;
3467 if ($SESSION->logincount > $max_logins) {
3468 unset($SESSION->wantsurl);
3469 print_error('errortoomanylogins');
3474 * Resets login attempts
3476 * @global object
3478 function reset_login_count() {
3479 global $SESSION;
3481 $SESSION->logincount = 0;
3485 * Determines if the currently logged in user is in editing mode.
3486 * Note: originally this function had $userid parameter - it was not usable anyway
3488 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3489 * @todo Deprecated function remove when ready
3491 * @global object
3492 * @uses DEBUG_DEVELOPER
3493 * @return bool
3495 function isediting() {
3496 global $PAGE;
3497 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
3498 return $PAGE->user_is_editing();
3502 * Determines if the logged in user is currently moving an activity
3504 * @global object
3505 * @param int $courseid The id of the course being tested
3506 * @return bool
3508 function ismoving($courseid) {
3509 global $USER;
3511 if (!empty($USER->activitycopy)) {
3512 return ($USER->activitycopycourse == $courseid);
3514 return false;
3518 * Returns a persons full name
3520 * Given an object containing firstname and lastname
3521 * values, this function returns a string with the
3522 * full name of the person.
3523 * The result may depend on system settings
3524 * or language. 'override' will force both names
3525 * to be used even if system settings specify one.
3527 * @global object
3528 * @global object
3529 * @param object $user A {@link $USER} object to get full name of
3530 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3531 * @return string
3533 function fullname($user, $override=false) {
3534 global $CFG, $SESSION;
3536 if (!isset($user->firstname) and !isset($user->lastname)) {
3537 return '';
3540 if (!$override) {
3541 if (!empty($CFG->forcefirstname)) {
3542 $user->firstname = $CFG->forcefirstname;
3544 if (!empty($CFG->forcelastname)) {
3545 $user->lastname = $CFG->forcelastname;
3549 if (!empty($SESSION->fullnamedisplay)) {
3550 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3553 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
3554 return $user->firstname .' '. $user->lastname;
3556 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
3557 return $user->lastname .' '. $user->firstname;
3559 } else if ($CFG->fullnamedisplay == 'firstname') {
3560 if ($override) {
3561 return get_string('fullnamedisplay', '', $user);
3562 } else {
3563 return $user->firstname;
3567 return get_string('fullnamedisplay', '', $user);
3571 * Checks if current user is shown any extra fields when listing users.
3572 * @param object $context Context
3573 * @param array $already Array of fields that we're going to show anyway
3574 * so don't bother listing them
3575 * @return array Array of field names from user table, not including anything
3576 * listed in $already
3578 function get_extra_user_fields($context, $already = array()) {
3579 global $CFG;
3581 // Only users with permission get the extra fields
3582 if (!has_capability('moodle/site:viewuseridentity', $context)) {
3583 return array();
3586 // Split showuseridentity on comma
3587 if (empty($CFG->showuseridentity)) {
3588 // Explode gives wrong result with empty string
3589 $extra = array();
3590 } else {
3591 $extra = explode(',', $CFG->showuseridentity);
3593 $renumber = false;
3594 foreach ($extra as $key => $field) {
3595 if (in_array($field, $already)) {
3596 unset($extra[$key]);
3597 $renumber = true;
3600 if ($renumber) {
3601 // For consistency, if entries are removed from array, renumber it
3602 // so they are numbered as you would expect
3603 $extra = array_merge($extra);
3605 return $extra;
3609 * If the current user is to be shown extra user fields when listing or
3610 * selecting users, returns a string suitable for including in an SQL select
3611 * clause to retrieve those fields.
3612 * @param object $context Context
3613 * @param string $alias Alias of user table, e.g. 'u' (default none)
3614 * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none)
3615 * @param array $already Array of fields that we're going to include anyway
3616 * so don't list them (default none)
3617 * @return string Partial SQL select clause, beginning with comma, for example
3618 * ',u.idnumber,u.department' unless it is blank
3620 function get_extra_user_fields_sql($context, $alias='', $prefix='',
3621 $already = array()) {
3622 $fields = get_extra_user_fields($context, $already);
3623 $result = '';
3624 // Add punctuation for alias
3625 if ($alias !== '') {
3626 $alias .= '.';
3628 foreach ($fields as $field) {
3629 $result .= ', ' . $alias . $field;
3630 if ($prefix) {
3631 $result .= ' AS ' . $prefix . $field;
3634 return $result;
3638 * Returns the display name of a field in the user table. Works for most fields
3639 * that are commonly displayed to users.
3640 * @param string $field Field name, e.g. 'phone1'
3641 * @return string Text description taken from language file, e.g. 'Phone number'
3643 function get_user_field_name($field) {
3644 // Some fields have language strings which are not the same as field name
3645 switch ($field) {
3646 case 'phone1' : return get_string('phone');
3648 // Otherwise just use the same lang string
3649 return get_string($field);
3653 * Returns whether a given authentication plugin exists.
3655 * @global object
3656 * @param string $auth Form of authentication to check for. Defaults to the
3657 * global setting in {@link $CFG}.
3658 * @return boolean Whether the plugin is available.
3660 function exists_auth_plugin($auth) {
3661 global $CFG;
3663 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3664 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3666 return false;
3670 * Checks if a given plugin is in the list of enabled authentication plugins.
3672 * @param string $auth Authentication plugin.
3673 * @return boolean Whether the plugin is enabled.
3675 function is_enabled_auth($auth) {
3676 if (empty($auth)) {
3677 return false;
3680 $enabled = get_enabled_auth_plugins();
3682 return in_array($auth, $enabled);
3686 * Returns an authentication plugin instance.
3688 * @global object
3689 * @param string $auth name of authentication plugin
3690 * @return auth_plugin_base An instance of the required authentication plugin.
3692 function get_auth_plugin($auth) {
3693 global $CFG;
3695 // check the plugin exists first
3696 if (! exists_auth_plugin($auth)) {
3697 print_error('authpluginnotfound', 'debug', '', $auth);
3700 // return auth plugin instance
3701 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3702 $class = "auth_plugin_$auth";
3703 return new $class;
3707 * Returns array of active auth plugins.
3709 * @param bool $fix fix $CFG->auth if needed
3710 * @return array
3712 function get_enabled_auth_plugins($fix=false) {
3713 global $CFG;
3715 $default = array('manual', 'nologin');
3717 if (empty($CFG->auth)) {
3718 $auths = array();
3719 } else {
3720 $auths = explode(',', $CFG->auth);
3723 if ($fix) {
3724 $auths = array_unique($auths);
3725 foreach($auths as $k=>$authname) {
3726 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3727 unset($auths[$k]);
3730 $newconfig = implode(',', $auths);
3731 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
3732 set_config('auth', $newconfig);
3736 return (array_merge($default, $auths));
3740 * Returns true if an internal authentication method is being used.
3741 * if method not specified then, global default is assumed
3743 * @param string $auth Form of authentication required
3744 * @return bool
3746 function is_internal_auth($auth) {
3747 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3748 return $authplugin->is_internal();
3752 * Returns true if the user is a 'restored' one
3754 * Used in the login process to inform the user
3755 * and allow him/her to reset the password
3757 * @uses $CFG
3758 * @uses $DB
3759 * @param string $username username to be checked
3760 * @return bool
3762 function is_restored_user($username) {
3763 global $CFG, $DB;
3765 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
3769 * Returns an array of user fields
3771 * @return array User field/column names
3773 function get_user_fieldnames() {
3774 global $DB;
3776 $fieldarray = $DB->get_columns('user');
3777 unset($fieldarray['id']);
3778 $fieldarray = array_keys($fieldarray);
3780 return $fieldarray;
3784 * Creates a bare-bones user record
3786 * @todo Outline auth types and provide code example
3788 * @param string $username New user's username to add to record
3789 * @param string $password New user's password to add to record
3790 * @param string $auth Form of authentication required
3791 * @return stdClass A complete user object
3793 function create_user_record($username, $password, $auth = 'manual') {
3794 global $CFG, $DB;
3796 //just in case check text case
3797 $username = trim(textlib::strtolower($username));
3799 $authplugin = get_auth_plugin($auth);
3801 $newuser = new stdClass();
3803 if ($newinfo = $authplugin->get_userinfo($username)) {
3804 $newinfo = truncate_userinfo($newinfo);
3805 foreach ($newinfo as $key => $value){
3806 $newuser->$key = $value;
3810 if (!empty($newuser->email)) {
3811 if (email_is_not_allowed($newuser->email)) {
3812 unset($newuser->email);
3816 if (!isset($newuser->city)) {
3817 $newuser->city = '';
3820 $newuser->auth = $auth;
3821 $newuser->username = $username;
3823 // fix for MDL-8480
3824 // user CFG lang for user if $newuser->lang is empty
3825 // or $user->lang is not an installed language
3826 if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
3827 $newuser->lang = $CFG->lang;
3829 $newuser->confirmed = 1;
3830 $newuser->lastip = getremoteaddr();
3831 $newuser->timecreated = time();
3832 $newuser->timemodified = $newuser->timecreated;
3833 $newuser->mnethostid = $CFG->mnet_localhost_id;
3835 $newuser->id = $DB->insert_record('user', $newuser);
3836 $user = get_complete_user_data('id', $newuser->id);
3837 if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
3838 set_user_preference('auth_forcepasswordchange', 1, $user);
3840 update_internal_user_password($user, $password);
3842 // fetch full user record for the event, the complete user data contains too much info
3843 // and we want to be consistent with other places that trigger this event
3844 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
3846 return $user;
3850 * Will update a local user record from an external source.
3851 * (MNET users can not be updated using this method!)
3853 * @param string $username user's username to update the record
3854 * @return stdClass A complete user object
3856 function update_user_record($username) {
3857 global $DB, $CFG;
3859 $username = trim(textlib::strtolower($username)); /// just in case check text case
3861 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
3862 $newuser = array();
3863 $userauth = get_auth_plugin($oldinfo->auth);
3865 if ($newinfo = $userauth->get_userinfo($username)) {
3866 $newinfo = truncate_userinfo($newinfo);
3867 foreach ($newinfo as $key => $value){
3868 $key = strtolower($key);
3869 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3870 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3871 // unknown or must not be changed
3872 continue;
3874 $confval = $userauth->config->{'field_updatelocal_' . $key};
3875 $lockval = $userauth->config->{'field_lock_' . $key};
3876 if (empty($confval) || empty($lockval)) {
3877 continue;
3879 if ($confval === 'onlogin') {
3880 // MDL-4207 Don't overwrite modified user profile values with
3881 // empty LDAP values when 'unlocked if empty' is set. The purpose
3882 // of the setting 'unlocked if empty' is to allow the user to fill
3883 // in a value for the selected field _if LDAP is giving
3884 // nothing_ for this field. Thus it makes sense to let this value
3885 // stand in until LDAP is giving a value for this field.
3886 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3887 if ((string)$oldinfo->$key !== (string)$value) {
3888 $newuser[$key] = (string)$value;
3893 if ($newuser) {
3894 $newuser['id'] = $oldinfo->id;
3895 $newuser['timemodified'] = time();
3896 $DB->update_record('user', $newuser);
3897 // fetch full user record for the event, the complete user data contains too much info
3898 // and we want to be consistent with other places that trigger this event
3899 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
3903 return get_complete_user_data('id', $oldinfo->id);
3907 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3908 * which may have large fields
3910 * @todo Add vartype handling to ensure $info is an array
3912 * @param array $info Array of user properties to truncate if needed
3913 * @return array The now truncated information that was passed in
3915 function truncate_userinfo($info) {
3916 // define the limits
3917 $limit = array(
3918 'username' => 100,
3919 'idnumber' => 255,
3920 'firstname' => 100,
3921 'lastname' => 100,
3922 'email' => 100,
3923 'icq' => 15,
3924 'phone1' => 20,
3925 'phone2' => 20,
3926 'institution' => 40,
3927 'department' => 30,
3928 'address' => 70,
3929 'city' => 120,
3930 'country' => 2,
3931 'url' => 255,
3934 // apply where needed
3935 foreach (array_keys($info) as $key) {
3936 if (!empty($limit[$key])) {
3937 $info[$key] = trim(textlib::substr($info[$key],0, $limit[$key]));
3941 return $info;
3945 * Marks user deleted in internal user database and notifies the auth plugin.
3946 * Also unenrols user from all roles and does other cleanup.
3948 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3950 * @param stdClass $user full user object before delete
3951 * @return boolean success
3952 * @throws coding_exception if invalid $user parameter detected
3954 function delete_user(stdClass $user) {
3955 global $CFG, $DB;
3956 require_once($CFG->libdir.'/grouplib.php');
3957 require_once($CFG->libdir.'/gradelib.php');
3958 require_once($CFG->dirroot.'/message/lib.php');
3959 require_once($CFG->dirroot.'/tag/lib.php');
3961 // Make sure nobody sends bogus record type as parameter.
3962 if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
3963 throw new coding_exception('Invalid $user parameter in delete_user() detected');
3966 // Better not trust the parameter and fetch the latest info,
3967 // this will be very expensive anyway.
3968 if (!$user = $DB->get_record('user', array('id'=>$user->id))) {
3969 debugging('Attempt to delete unknown user account.');
3970 return false;
3973 // There must be always exactly one guest record,
3974 // originally the guest account was identified by username only,
3975 // now we use $CFG->siteguest for performance reasons.
3976 if ($user->username === 'guest' or isguestuser($user)) {
3977 debugging('Guest user account can not be deleted.');
3978 return false;
3981 // Admin can be theoretically from different auth plugin,
3982 // but we want to prevent deletion of internal accoutns only,
3983 // if anything goes wrong ppl may force somebody to be admin via
3984 // config.php setting $CFG->siteadmins.
3985 if ($user->auth === 'manual' and is_siteadmin($user)) {
3986 debugging('Local administrator accounts can not be deleted.');
3987 return false;
3990 // delete all grades - backup is kept in grade_grades_history table
3991 grade_user_delete($user->id);
3993 //move unread messages from this user to read
3994 message_move_userfrom_unread2read($user->id);
3996 // TODO: remove from cohorts using standard API here
3998 // remove user tags
3999 tag_set('user', $user->id, array());
4001 // unconditionally unenrol from all courses
4002 enrol_user_delete($user);
4004 // unenrol from all roles in all contexts
4005 role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
4007 //now do a brute force cleanup
4009 // remove from all cohorts
4010 $DB->delete_records('cohort_members', array('userid'=>$user->id));
4012 // remove from all groups
4013 $DB->delete_records('groups_members', array('userid'=>$user->id));
4015 // brute force unenrol from all courses
4016 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
4018 // purge user preferences
4019 $DB->delete_records('user_preferences', array('userid'=>$user->id));
4021 // purge user extra profile info
4022 $DB->delete_records('user_info_data', array('userid'=>$user->id));
4024 // last course access not necessary either
4025 $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
4027 // remove all user tokens
4028 $DB->delete_records('external_tokens', array('userid'=>$user->id));
4030 // unauthorise the user for all services
4031 $DB->delete_records('external_services_users', array('userid'=>$user->id));
4033 // force logout - may fail if file based sessions used, sorry
4034 session_kill_user($user->id);
4036 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
4037 delete_context(CONTEXT_USER, $user->id);
4039 // workaround for bulk deletes of users with the same email address
4040 $delname = "$user->email.".time();
4041 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
4042 $delname++;
4045 // mark internal user record as "deleted"
4046 $updateuser = new stdClass();
4047 $updateuser->id = $user->id;
4048 $updateuser->deleted = 1;
4049 $updateuser->username = $delname; // Remember it just in case
4050 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
4051 $updateuser->idnumber = ''; // Clear this field to free it up
4052 $updateuser->picture = 0;
4053 $updateuser->timemodified = time();
4055 $DB->update_record('user', $updateuser);
4056 // Add this action to log
4057 add_to_log(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
4060 // We will update the user's timemodified, as it will be passed to the user_deleted event, which
4061 // should know about this updated property persisted to the user's table.
4062 $user->timemodified = $updateuser->timemodified;
4064 // notify auth plugin - do not block the delete even when plugin fails
4065 $authplugin = get_auth_plugin($user->auth);
4066 $authplugin->user_delete($user);
4068 // any plugin that needs to cleanup should register this event
4069 events_trigger('user_deleted', $user);
4071 return true;
4075 * Retrieve the guest user object
4077 * @global object
4078 * @global object
4079 * @return user A {@link $USER} object
4081 function guest_user() {
4082 global $CFG, $DB;
4084 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
4085 $newuser->confirmed = 1;
4086 $newuser->lang = $CFG->lang;
4087 $newuser->lastip = getremoteaddr();
4090 return $newuser;
4094 * Authenticates a user against the chosen authentication mechanism
4096 * Given a username and password, this function looks them
4097 * up using the currently selected authentication mechanism,
4098 * and if the authentication is successful, it returns a
4099 * valid $user object from the 'user' table.
4101 * Uses auth_ functions from the currently active auth module
4103 * After authenticate_user_login() returns success, you will need to
4104 * log that the user has logged in, and call complete_user_login() to set
4105 * the session up.
4107 * Note: this function works only with non-mnet accounts!
4109 * @param string $username User's username
4110 * @param string $password User's password
4111 * @return user|flase A {@link $USER} object or false if error
4113 function authenticate_user_login($username, $password) {
4114 global $CFG, $DB;
4116 $authsenabled = get_enabled_auth_plugins();
4118 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
4119 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
4120 if (!empty($user->suspended)) {
4121 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4122 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4123 return false;
4125 if ($auth=='nologin' or !is_enabled_auth($auth)) {
4126 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4127 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4128 return false;
4130 $auths = array($auth);
4132 } else {
4133 // check if there's a deleted record (cheaply)
4134 if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
4135 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4136 return false;
4139 // User does not exist
4140 $auths = $authsenabled;
4141 $user = new stdClass();
4142 $user->id = 0;
4145 foreach ($auths as $auth) {
4146 $authplugin = get_auth_plugin($auth);
4148 // on auth fail fall through to the next plugin
4149 if (!$authplugin->user_login($username, $password)) {
4150 continue;
4153 // successful authentication
4154 if ($user->id) { // User already exists in database
4155 if (empty($user->auth)) { // For some reason auth isn't set yet
4156 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
4157 $user->auth = $auth;
4160 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
4162 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
4163 $user = update_user_record($username);
4165 } else {
4166 // if user not found and user creation is not disabled, create it
4167 if (empty($CFG->authpreventaccountcreation)) {
4168 $user = create_user_record($username, $password, $auth);
4169 } else {
4170 continue;
4174 $authplugin->sync_roles($user);
4176 foreach ($authsenabled as $hau) {
4177 $hauth = get_auth_plugin($hau);
4178 $hauth->user_authenticated_hook($user, $username, $password);
4181 if (empty($user->id)) {
4182 return false;
4185 if (!empty($user->suspended)) {
4186 // just in case some auth plugin suspended account
4187 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4188 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4189 return false;
4192 return $user;
4195 // failed if all the plugins have failed
4196 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4197 if (debugging('', DEBUG_ALL)) {
4198 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4200 return false;
4204 * Call to complete the user login process after authenticate_user_login()
4205 * has succeeded. It will setup the $USER variable and other required bits
4206 * and pieces.
4208 * NOTE:
4209 * - It will NOT log anything -- up to the caller to decide what to log.
4210 * - this function does not set any cookies any more!
4212 * @param object $user
4213 * @return object A {@link $USER} object - BC only, do not use
4215 function complete_user_login($user) {
4216 global $CFG, $USER;
4218 // regenerate session id and delete old session,
4219 // this helps prevent session fixation attacks from the same domain
4220 session_regenerate_id(true);
4222 // let enrol plugins deal with new enrolments if necessary
4223 enrol_check_plugins($user);
4225 // check enrolments, load caps and setup $USER object
4226 session_set_user($user);
4228 // reload preferences from DB
4229 unset($USER->preference);
4230 check_user_preferences_loaded($USER);
4232 // update login times
4233 update_user_login_times();
4235 // extra session prefs init
4236 set_login_session_preferences();
4238 if (isguestuser()) {
4239 // no need to continue when user is THE guest
4240 return $USER;
4243 /// Select password change url
4244 $userauth = get_auth_plugin($USER->auth);
4246 /// check whether the user should be changing password
4247 if (get_user_preferences('auth_forcepasswordchange', false)){
4248 if ($userauth->can_change_password()) {
4249 if ($changeurl = $userauth->change_password_url()) {
4250 redirect($changeurl);
4251 } else {
4252 redirect($CFG->httpswwwroot.'/login/change_password.php');
4254 } else {
4255 print_error('nopasswordchangeforced', 'auth');
4258 return $USER;
4262 * Compare password against hash stored in internal user table.
4263 * If necessary it also updates the stored hash to new format.
4265 * @param stdClass $user (password property may be updated)
4266 * @param string $password plain text password
4267 * @return bool is password valid?
4269 function validate_internal_user_password($user, $password) {
4270 global $CFG;
4272 if (!isset($CFG->passwordsaltmain)) {
4273 $CFG->passwordsaltmain = '';
4276 $validated = false;
4278 if ($user->password === 'not cached') {
4279 // internal password is not used at all, it can not validate
4281 } else if ($user->password === md5($password.$CFG->passwordsaltmain)
4282 or $user->password === md5($password)
4283 or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
4284 or $user->password === md5(addslashes($password))) {
4285 // note: we are intentionally using the addslashes() here because we
4286 // need to accept old password hashes of passwords with magic quotes
4287 $validated = true;
4289 } else {
4290 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
4291 $alt = 'passwordsaltalt'.$i;
4292 if (!empty($CFG->$alt)) {
4293 if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
4294 $validated = true;
4295 break;
4301 if ($validated) {
4302 // force update of password hash using latest main password salt and encoding if needed
4303 update_internal_user_password($user, $password);
4306 return $validated;
4310 * Calculate hashed value from password using current hash mechanism.
4312 * @param string $password
4313 * @return string password hash
4315 function hash_internal_user_password($password) {
4316 global $CFG;
4318 if (isset($CFG->passwordsaltmain)) {
4319 return md5($password.$CFG->passwordsaltmain);
4320 } else {
4321 return md5($password);
4326 * Update password hash in user object.
4328 * @param stdClass $user (password property may be updated)
4329 * @param string $password plain text password
4330 * @return bool always returns true
4332 function update_internal_user_password($user, $password) {
4333 global $DB;
4335 $authplugin = get_auth_plugin($user->auth);
4336 if ($authplugin->prevent_local_passwords()) {
4337 $hashedpassword = 'not cached';
4338 } else {
4339 $hashedpassword = hash_internal_user_password($password);
4342 if ($user->password !== $hashedpassword) {
4343 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
4344 $user->password = $hashedpassword;
4347 return true;
4351 * Get a complete user record, which includes all the info
4352 * in the user record.
4354 * Intended for setting as $USER session variable
4356 * @param string $field The user field to be checked for a given value.
4357 * @param string $value The value to match for $field.
4358 * @param int $mnethostid
4359 * @return mixed False, or A {@link $USER} object.
4361 function get_complete_user_data($field, $value, $mnethostid = null) {
4362 global $CFG, $DB;
4364 if (!$field || !$value) {
4365 return false;
4368 /// Build the WHERE clause for an SQL query
4369 $params = array('fieldval'=>$value);
4370 $constraints = "$field = :fieldval AND deleted <> 1";
4372 // If we are loading user data based on anything other than id,
4373 // we must also restrict our search based on mnet host.
4374 if ($field != 'id') {
4375 if (empty($mnethostid)) {
4376 // if empty, we restrict to local users
4377 $mnethostid = $CFG->mnet_localhost_id;
4380 if (!empty($mnethostid)) {
4381 $params['mnethostid'] = $mnethostid;
4382 $constraints .= " AND mnethostid = :mnethostid";
4385 /// Get all the basic user data
4387 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
4388 return false;
4391 /// Get various settings and preferences
4393 // preload preference cache
4394 check_user_preferences_loaded($user);
4396 // load course enrolment related stuff
4397 $user->lastcourseaccess = array(); // during last session
4398 $user->currentcourseaccess = array(); // during current session
4399 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
4400 foreach ($lastaccesses as $lastaccess) {
4401 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
4405 $sql = "SELECT g.id, g.courseid
4406 FROM {groups} g, {groups_members} gm
4407 WHERE gm.groupid=g.id AND gm.userid=?";
4409 // this is a special hack to speedup calendar display
4410 $user->groupmember = array();
4411 if (!isguestuser($user)) {
4412 if ($groups = $DB->get_records_sql($sql, array($user->id))) {
4413 foreach ($groups as $group) {
4414 if (!array_key_exists($group->courseid, $user->groupmember)) {
4415 $user->groupmember[$group->courseid] = array();
4417 $user->groupmember[$group->courseid][$group->id] = $group->id;
4422 /// Add the custom profile fields to the user record
4423 $user->profile = array();
4424 if (!isguestuser($user)) {
4425 require_once($CFG->dirroot.'/user/profile/lib.php');
4426 profile_load_custom_fields($user);
4429 /// Rewrite some variables if necessary
4430 if (!empty($user->description)) {
4431 $user->description = true; // No need to cart all of it around
4433 if (isguestuser($user)) {
4434 $user->lang = $CFG->lang; // Guest language always same as site
4435 $user->firstname = get_string('guestuser'); // Name always in current language
4436 $user->lastname = ' ';
4439 return $user;
4443 * Validate a password against the configured password policy
4445 * @global object
4446 * @param string $password the password to be checked against the password policy
4447 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
4448 * @return bool true if the password is valid according to the policy. false otherwise.
4450 function check_password_policy($password, &$errmsg) {
4451 global $CFG;
4453 if (empty($CFG->passwordpolicy)) {
4454 return true;
4457 $errmsg = '';
4458 if (textlib::strlen($password) < $CFG->minpasswordlength) {
4459 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
4462 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
4463 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
4466 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
4467 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
4470 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
4471 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
4474 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
4475 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
4477 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
4478 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
4481 if ($errmsg == '') {
4482 return true;
4483 } else {
4484 return false;
4490 * When logging in, this function is run to set certain preferences
4491 * for the current SESSION
4493 * @global object
4494 * @global object
4496 function set_login_session_preferences() {
4497 global $SESSION, $CFG;
4499 $SESSION->justloggedin = true;
4501 unset($SESSION->lang);
4506 * Delete a course, including all related data from the database,
4507 * and any associated files.
4509 * @global object
4510 * @global object
4511 * @param mixed $courseorid The id of the course or course object to delete.
4512 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4513 * @return bool true if all the removals succeeded. false if there were any failures. If this
4514 * method returns false, some of the removals will probably have succeeded, and others
4515 * failed, but you have no way of knowing which.
4517 function delete_course($courseorid, $showfeedback = true) {
4518 global $DB;
4520 if (is_object($courseorid)) {
4521 $courseid = $courseorid->id;
4522 $course = $courseorid;
4523 } else {
4524 $courseid = $courseorid;
4525 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
4526 return false;
4529 $context = get_context_instance(CONTEXT_COURSE, $courseid);
4531 // frontpage course can not be deleted!!
4532 if ($courseid == SITEID) {
4533 return false;
4536 // make the course completely empty
4537 remove_course_contents($courseid, $showfeedback);
4539 // delete the course and related context instance
4540 delete_context(CONTEXT_COURSE, $courseid);
4542 // We will update the course's timemodified, as it will be passed to the course_deleted event,
4543 // which should know about this updated property, as this event is meant to pass the full course record
4544 $course->timemodified = time();
4546 $DB->delete_records("course", array("id"=>$courseid));
4548 //trigger events
4549 $course->context = $context; // you can not fetch context in the event because it was already deleted
4550 events_trigger('course_deleted', $course);
4552 return true;
4556 * Clear a course out completely, deleting all content
4557 * but don't delete the course itself.
4558 * This function does not verify any permissions.
4560 * Please note this function also deletes all user enrolments,
4561 * enrolment instances and role assignments by default.
4563 * $options:
4564 * - 'keep_roles_and_enrolments' - false by default
4565 * - 'keep_groups_and_groupings' - false by default
4567 * @param int $courseid The id of the course that is being deleted
4568 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4569 * @param array $options extra options
4570 * @return bool true if all the removals succeeded. false if there were any failures. If this
4571 * method returns false, some of the removals will probably have succeeded, and others
4572 * failed, but you have no way of knowing which.
4574 function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
4575 global $CFG, $DB, $OUTPUT;
4576 require_once($CFG->libdir.'/completionlib.php');
4577 require_once($CFG->libdir.'/questionlib.php');
4578 require_once($CFG->libdir.'/gradelib.php');
4579 require_once($CFG->dirroot.'/group/lib.php');
4580 require_once($CFG->dirroot.'/tag/coursetagslib.php');
4581 require_once($CFG->dirroot.'/comment/lib.php');
4582 require_once($CFG->dirroot.'/rating/lib.php');
4584 // NOTE: these concatenated strings are suboptimal, but it is just extra info...
4585 $strdeleted = get_string('deleted').' - ';
4587 // Some crazy wishlist of stuff we should skip during purging of course content
4588 $options = (array)$options;
4590 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
4591 $coursecontext = context_course::instance($courseid);
4592 $fs = get_file_storage();
4594 // Delete course completion information, this has to be done before grades and enrols
4595 $cc = new completion_info($course);
4596 $cc->clear_criteria();
4597 if ($showfeedback) {
4598 echo $OUTPUT->notification($strdeleted.get_string('completion', 'completion'), 'notifysuccess');
4601 // Remove all data from gradebook - this needs to be done before course modules
4602 // because while deleting this information, the system may need to reference
4603 // the course modules that own the grades.
4604 remove_course_grades($courseid, $showfeedback);
4605 remove_grade_letters($coursecontext, $showfeedback);
4607 // Delete course blocks in any all child contexts,
4608 // they may depend on modules so delete them first
4609 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4610 foreach ($childcontexts as $childcontext) {
4611 blocks_delete_all_for_context($childcontext->id);
4613 unset($childcontexts);
4614 blocks_delete_all_for_context($coursecontext->id);
4615 if ($showfeedback) {
4616 echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
4619 // Delete every instance of every module,
4620 // this has to be done before deleting of course level stuff
4621 $locations = get_plugin_list('mod');
4622 foreach ($locations as $modname=>$moddir) {
4623 if ($modname === 'NEWMODULE') {
4624 continue;
4626 if ($module = $DB->get_record('modules', array('name'=>$modname))) {
4627 include_once("$moddir/lib.php"); // Shows php warning only if plugin defective
4628 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4629 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4631 if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
4632 foreach ($instances as $instance) {
4633 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
4634 /// Delete activity context questions and question categories
4635 question_delete_activity($cm, $showfeedback);
4637 if (function_exists($moddelete)) {
4638 // This purges all module data in related tables, extra user prefs, settings, etc.
4639 $moddelete($instance->id);
4640 } else {
4641 // NOTE: we should not allow installation of modules with missing delete support!
4642 debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
4643 $DB->delete_records($modname, array('id'=>$instance->id));
4646 if ($cm) {
4647 // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition
4648 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4649 $DB->delete_records('course_modules', array('id'=>$cm->id));
4653 if (function_exists($moddeletecourse)) {
4654 // Execute ptional course cleanup callback
4655 $moddeletecourse($course, $showfeedback);
4657 if ($instances and $showfeedback) {
4658 echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
4660 } else {
4661 // Ooops, this module is not properly installed, force-delete it in the next block
4665 // We have tried to delete everything the nice way - now let's force-delete any remaining module data
4667 // Remove all data from availability and completion tables that is associated
4668 // with course-modules belonging to this course. Note this is done even if the
4669 // features are not enabled now, in case they were enabled previously.
4670 $DB->delete_records_select('course_modules_completion',
4671 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4672 array($courseid));
4673 $DB->delete_records_select('course_modules_availability',
4674 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4675 array($courseid));
4677 // Remove course-module data.
4678 $cms = $DB->get_records('course_modules', array('course'=>$course->id));
4679 foreach ($cms as $cm) {
4680 if ($module = $DB->get_record('modules', array('id'=>$cm->module))) {
4681 try {
4682 $DB->delete_records($module->name, array('id'=>$cm->instance));
4683 } catch (Exception $e) {
4684 // Ignore weird or missing table problems
4687 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4688 $DB->delete_records('course_modules', array('id'=>$cm->id));
4691 if ($showfeedback) {
4692 echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
4695 // Cleanup the rest of plugins
4696 $cleanuplugintypes = array('report', 'coursereport', 'format');
4697 foreach ($cleanuplugintypes as $type) {
4698 $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
4699 foreach ($plugins as $plugin=>$pluginfunction) {
4700 $pluginfunction($course->id, $showfeedback);
4702 if ($showfeedback) {
4703 echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
4707 // Delete questions and question categories
4708 question_delete_course($course, $showfeedback);
4709 if ($showfeedback) {
4710 echo $OUTPUT->notification($strdeleted.get_string('questions', 'question'), 'notifysuccess');
4713 // Make sure there are no subcontexts left - all valid blocks and modules should be already gone
4714 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4715 foreach ($childcontexts as $childcontext) {
4716 $childcontext->delete();
4718 unset($childcontexts);
4720 // Remove all roles and enrolments by default
4721 if (empty($options['keep_roles_and_enrolments'])) {
4722 // this hack is used in restore when deleting contents of existing course
4723 role_unassign_all(array('contextid'=>$coursecontext->id, 'component'=>''), true);
4724 enrol_course_delete($course);
4725 if ($showfeedback) {
4726 echo $OUTPUT->notification($strdeleted.get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
4730 // Delete any groups, removing members and grouping/course links first.
4731 if (empty($options['keep_groups_and_groupings'])) {
4732 groups_delete_groupings($course->id, $showfeedback);
4733 groups_delete_groups($course->id, $showfeedback);
4736 // filters be gone!
4737 filter_delete_all_for_context($coursecontext->id);
4739 // die comments!
4740 comment::delete_comments($coursecontext->id);
4742 // ratings are history too
4743 $delopt = new stdclass();
4744 $delopt->contextid = $coursecontext->id;
4745 $rm = new rating_manager();
4746 $rm->delete_ratings($delopt);
4748 // Delete course tags
4749 coursetag_delete_course_tags($course->id, $showfeedback);
4751 // Delete calendar events
4752 $DB->delete_records('event', array('courseid'=>$course->id));
4753 $fs->delete_area_files($coursecontext->id, 'calendar');
4755 // Delete all related records in other core tables that may have a courseid
4756 // This array stores the tables that need to be cleared, as
4757 // table_name => column_name that contains the course id.
4758 $tablestoclear = array(
4759 'log' => 'course', // Course logs (NOTE: this might be changed in the future)
4760 'backup_courses' => 'courseid', // Scheduled backup stuff
4761 'user_lastaccess' => 'courseid', // User access info
4763 foreach ($tablestoclear as $table => $col) {
4764 $DB->delete_records($table, array($col=>$course->id));
4767 // delete all course backup files
4768 $fs->delete_area_files($coursecontext->id, 'backup');
4770 // cleanup course record - remove links to deleted stuff
4771 $oldcourse = new stdClass();
4772 $oldcourse->id = $course->id;
4773 $oldcourse->summary = '';
4774 $oldcourse->modinfo = NULL;
4775 $oldcourse->legacyfiles = 0;
4776 $oldcourse->enablecompletion = 0;
4777 if (!empty($options['keep_groups_and_groupings'])) {
4778 $oldcourse->defaultgroupingid = 0;
4780 $DB->update_record('course', $oldcourse);
4782 // Delete course sections and availability options.
4783 $DB->delete_records_select('course_sections_availability',
4784 'coursesectionid IN (SELECT id from {course_sections} WHERE course=?)',
4785 array($course->id));
4786 $DB->delete_records('course_sections', array('course'=>$course->id));
4788 // delete legacy, section and any other course files
4789 $fs->delete_area_files($coursecontext->id, 'course'); // files from summary and section
4791 // Delete all remaining stuff linked to context such as files, comments, ratings, etc.
4792 if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
4793 // Easy, do not delete the context itself...
4794 $coursecontext->delete_content();
4796 } else {
4797 // Hack alert!!!!
4798 // We can not drop all context stuff because it would bork enrolments and roles,
4799 // there might be also files used by enrol plugins...
4802 // Delete legacy files - just in case some files are still left there after conversion to new file api,
4803 // also some non-standard unsupported plugins may try to store something there
4804 fulldelete($CFG->dataroot.'/'.$course->id);
4806 // Finally trigger the event
4807 $course->context = $coursecontext; // you can not access context in cron event later after course is deleted
4808 $course->options = $options; // not empty if we used any crazy hack
4809 events_trigger('course_content_removed', $course);
4811 return true;
4815 * Change dates in module - used from course reset.
4817 * @global object
4818 * @global object
4819 * @param string $modname forum, assignment, etc
4820 * @param array $fields array of date fields from mod table
4821 * @param int $timeshift time difference
4822 * @param int $courseid
4823 * @return bool success
4825 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4826 global $CFG, $DB;
4827 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
4829 $return = true;
4830 foreach ($fields as $field) {
4831 $updatesql = "UPDATE {".$modname."}
4832 SET $field = $field + ?
4833 WHERE course=? AND $field<>0";
4834 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4837 $refreshfunction = $modname.'_refresh_events';
4838 if (function_exists($refreshfunction)) {
4839 $refreshfunction($courseid);
4842 return $return;
4846 * This function will empty a course of user data.
4847 * It will retain the activities and the structure of the course.
4849 * @param object $data an object containing all the settings including courseid (without magic quotes)
4850 * @return array status array of array component, item, error
4852 function reset_course_userdata($data) {
4853 global $CFG, $USER, $DB;
4854 require_once($CFG->libdir.'/gradelib.php');
4855 require_once($CFG->libdir.'/completionlib.php');
4856 require_once($CFG->dirroot.'/group/lib.php');
4858 $data->courseid = $data->id;
4859 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
4861 // calculate the time shift of dates
4862 if (!empty($data->reset_start_date)) {
4863 // time part of course startdate should be zero
4864 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
4865 } else {
4866 $data->timeshift = 0;
4869 // result array: component, item, error
4870 $status = array();
4872 // start the resetting
4873 $componentstr = get_string('general');
4875 // move the course start time
4876 if (!empty($data->reset_start_date) and $data->timeshift) {
4877 // change course start data
4878 $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
4879 // update all course and group events - do not move activity events
4880 $updatesql = "UPDATE {event}
4881 SET timestart = timestart + ?
4882 WHERE courseid=? AND instance=0";
4883 $DB->execute($updatesql, array($data->timeshift, $data->courseid));
4885 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4888 if (!empty($data->reset_logs)) {
4889 $DB->delete_records('log', array('course'=>$data->courseid));
4890 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4893 if (!empty($data->reset_events)) {
4894 $DB->delete_records('event', array('courseid'=>$data->courseid));
4895 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4898 if (!empty($data->reset_notes)) {
4899 require_once($CFG->dirroot.'/notes/lib.php');
4900 note_delete_all($data->courseid);
4901 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4904 if (!empty($data->delete_blog_associations)) {
4905 require_once($CFG->dirroot.'/blog/lib.php');
4906 blog_remove_associations_for_course($data->courseid);
4907 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4910 if (!empty($data->reset_completion)) {
4911 // Delete course and activity completion information.
4912 $course = $DB->get_record('course', array('id'=>$data->courseid));
4913 $cc = new completion_info($course);
4914 $cc->delete_all_completion_data();
4915 $status[] = array('component' => $componentstr,
4916 'item' => get_string('deletecompletiondata', 'completion'), 'error' => false);
4919 $componentstr = get_string('roles');
4921 if (!empty($data->reset_roles_overrides)) {
4922 $children = get_child_contexts($context);
4923 foreach ($children as $child) {
4924 $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
4926 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
4927 //force refresh for logged in users
4928 mark_context_dirty($context->path);
4929 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4932 if (!empty($data->reset_roles_local)) {
4933 $children = get_child_contexts($context);
4934 foreach ($children as $child) {
4935 role_unassign_all(array('contextid'=>$child->id));
4937 //force refresh for logged in users
4938 mark_context_dirty($context->path);
4939 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4942 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4943 $data->unenrolled = array();
4944 if (!empty($data->unenrol_users)) {
4945 $plugins = enrol_get_plugins(true);
4946 $instances = enrol_get_instances($data->courseid, true);
4947 foreach ($instances as $key=>$instance) {
4948 if (!isset($plugins[$instance->enrol])) {
4949 unset($instances[$key]);
4950 continue;
4954 foreach($data->unenrol_users as $withroleid) {
4955 if ($withroleid) {
4956 $sql = "SELECT ue.*
4957 FROM {user_enrolments} ue
4958 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4959 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4960 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4961 $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
4963 } else {
4964 // without any role assigned at course context
4965 $sql = "SELECT ue.*
4966 FROM {user_enrolments} ue
4967 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4968 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4969 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)
4970 WHERE ra.id IS NULL";
4971 $params = array('courseid'=>$data->courseid, 'courselevel'=>CONTEXT_COURSE);
4974 $rs = $DB->get_recordset_sql($sql, $params);
4975 foreach ($rs as $ue) {
4976 if (!isset($instances[$ue->enrolid])) {
4977 continue;
4979 $instance = $instances[$ue->enrolid];
4980 $plugin = $plugins[$instance->enrol];
4981 if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
4982 continue;
4985 $plugin->unenrol_user($instance, $ue->userid);
4986 $data->unenrolled[$ue->userid] = $ue->userid;
4988 $rs->close();
4991 if (!empty($data->unenrolled)) {
4992 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
4996 $componentstr = get_string('groups');
4998 // remove all group members
4999 if (!empty($data->reset_groups_members)) {
5000 groups_delete_group_members($data->courseid);
5001 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
5004 // remove all groups
5005 if (!empty($data->reset_groups_remove)) {
5006 groups_delete_groups($data->courseid, false);
5007 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
5010 // remove all grouping members
5011 if (!empty($data->reset_groupings_members)) {
5012 groups_delete_groupings_groups($data->courseid, false);
5013 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
5016 // remove all groupings
5017 if (!empty($data->reset_groupings_remove)) {
5018 groups_delete_groupings($data->courseid, false);
5019 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
5022 // Look in every instance of every module for data to delete
5023 $unsupported_mods = array();
5024 if ($allmods = $DB->get_records('modules') ) {
5025 foreach ($allmods as $mod) {
5026 $modname = $mod->name;
5027 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
5028 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
5029 if (file_exists($modfile)) {
5030 if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
5031 continue; // Skip mods with no instances
5033 include_once($modfile);
5034 if (function_exists($moddeleteuserdata)) {
5035 $modstatus = $moddeleteuserdata($data);
5036 if (is_array($modstatus)) {
5037 $status = array_merge($status, $modstatus);
5038 } else {
5039 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
5041 } else {
5042 $unsupported_mods[] = $mod;
5044 } else {
5045 debugging('Missing lib.php in '.$modname.' module!');
5050 // mention unsupported mods
5051 if (!empty($unsupported_mods)) {
5052 foreach($unsupported_mods as $mod) {
5053 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
5058 $componentstr = get_string('gradebook', 'grades');
5059 // reset gradebook
5060 if (!empty($data->reset_gradebook_items)) {
5061 remove_course_grades($data->courseid, false);
5062 grade_grab_course_grades($data->courseid);
5063 grade_regrade_final_grades($data->courseid);
5064 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
5066 } else if (!empty($data->reset_gradebook_grades)) {
5067 grade_course_reset($data->courseid);
5068 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
5070 // reset comments
5071 if (!empty($data->reset_comments)) {
5072 require_once($CFG->dirroot.'/comment/lib.php');
5073 comment::reset_course_page_comments($context);
5076 return $status;
5080 * Generate an email processing address
5082 * @param int $modid
5083 * @param string $modargs
5084 * @return string Returns email processing address
5086 function generate_email_processing_address($modid,$modargs) {
5087 global $CFG;
5089 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
5090 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
5096 * @todo Finish documenting this function
5098 * @global object
5099 * @param string $modargs
5100 * @param string $body Currently unused
5102 function moodle_process_email($modargs,$body) {
5103 global $DB;
5105 // the first char should be an unencoded letter. We'll take this as an action
5106 switch ($modargs{0}) {
5107 case 'B': { // bounce
5108 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
5109 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
5110 // check the half md5 of their email
5111 $md5check = substr(md5($user->email),0,16);
5112 if ($md5check == substr($modargs, -16)) {
5113 set_bounce_count($user);
5115 // else maybe they've already changed it?
5118 break;
5119 // maybe more later?
5123 /// CORRESPONDENCE ////////////////////////////////////////////////
5126 * Get mailer instance, enable buffering, flush buffer or disable buffering.
5128 * @param string $action 'get', 'buffer', 'close' or 'flush'
5129 * @return moodle_phpmailer|null mailer instance if 'get' used or nothing
5131 function get_mailer($action='get') {
5132 global $CFG;
5134 static $mailer = null;
5135 static $counter = 0;
5137 if (!isset($CFG->smtpmaxbulk)) {
5138 $CFG->smtpmaxbulk = 1;
5141 if ($action == 'get') {
5142 $prevkeepalive = false;
5144 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5145 if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
5146 $counter++;
5147 // reset the mailer
5148 $mailer->Priority = 3;
5149 $mailer->CharSet = 'UTF-8'; // our default
5150 $mailer->ContentType = "text/plain";
5151 $mailer->Encoding = "8bit";
5152 $mailer->From = "root@localhost";
5153 $mailer->FromName = "Root User";
5154 $mailer->Sender = "";
5155 $mailer->Subject = "";
5156 $mailer->Body = "";
5157 $mailer->AltBody = "";
5158 $mailer->ConfirmReadingTo = "";
5160 $mailer->ClearAllRecipients();
5161 $mailer->ClearReplyTos();
5162 $mailer->ClearAttachments();
5163 $mailer->ClearCustomHeaders();
5164 return $mailer;
5167 $prevkeepalive = $mailer->SMTPKeepAlive;
5168 get_mailer('flush');
5171 include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
5172 $mailer = new moodle_phpmailer();
5174 $counter = 1;
5176 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
5177 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
5178 $mailer->CharSet = 'UTF-8';
5180 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
5181 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
5182 $mailer->LE = "\r\n";
5183 } else {
5184 $mailer->LE = "\n";
5187 if ($CFG->smtphosts == 'qmail') {
5188 $mailer->IsQmail(); // use Qmail system
5190 } else if (empty($CFG->smtphosts)) {
5191 $mailer->IsMail(); // use PHP mail() = sendmail
5193 } else {
5194 $mailer->IsSMTP(); // use SMTP directly
5195 if (!empty($CFG->debugsmtp)) {
5196 $mailer->SMTPDebug = true;
5198 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
5199 $mailer->SMTPSecure = $CFG->smtpsecure; // specify secure connection protocol
5200 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
5202 if ($CFG->smtpuser) { // Use SMTP authentication
5203 $mailer->SMTPAuth = true;
5204 $mailer->Username = $CFG->smtpuser;
5205 $mailer->Password = $CFG->smtppass;
5209 return $mailer;
5212 $nothing = null;
5214 // keep smtp session open after sending
5215 if ($action == 'buffer') {
5216 if (!empty($CFG->smtpmaxbulk)) {
5217 get_mailer('flush');
5218 $m = get_mailer();
5219 if ($m->Mailer == 'smtp') {
5220 $m->SMTPKeepAlive = true;
5223 return $nothing;
5226 // close smtp session, but continue buffering
5227 if ($action == 'flush') {
5228 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5229 if (!empty($mailer->SMTPDebug)) {
5230 echo '<pre>'."\n";
5232 $mailer->SmtpClose();
5233 if (!empty($mailer->SMTPDebug)) {
5234 echo '</pre>';
5237 return $nothing;
5240 // close smtp session, do not buffer anymore
5241 if ($action == 'close') {
5242 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5243 get_mailer('flush');
5244 $mailer->SMTPKeepAlive = false;
5246 $mailer = null; // better force new instance
5247 return $nothing;
5252 * Send an email to a specified user
5254 * @global object
5255 * @global string
5256 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
5257 * @uses SITEID
5258 * @param stdClass $user A {@link $USER} object
5259 * @param stdClass $from A {@link $USER} object
5260 * @param string $subject plain text subject line of the email
5261 * @param string $messagetext plain text version of the message
5262 * @param string $messagehtml complete html version of the message (optional)
5263 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
5264 * @param string $attachname the name of the file (extension indicates MIME)
5265 * @param bool $usetrueaddress determines whether $from email address should
5266 * be sent out. Will be overruled by user profile setting for maildisplay
5267 * @param string $replyto Email address to reply to
5268 * @param string $replytoname Name of reply to recipient
5269 * @param int $wordwrapwidth custom word wrap width, default 79
5270 * @return bool Returns true if mail was sent OK and false if there was an error.
5272 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
5274 global $CFG;
5276 if (empty($user) || empty($user->email)) {
5277 $nulluser = 'User is null or has no email';
5278 error_log($nulluser);
5279 if (CLI_SCRIPT) {
5280 mtrace('Error: lib/moodlelib.php email_to_user(): '.$nulluser);
5282 return false;
5285 if (!empty($user->deleted)) {
5286 // do not mail deleted users
5287 $userdeleted = 'User is deleted';
5288 error_log($userdeleted);
5289 if (CLI_SCRIPT) {
5290 mtrace('Error: lib/moodlelib.php email_to_user(): '.$userdeleted);
5292 return false;
5295 if (!empty($CFG->noemailever)) {
5296 // hidden setting for development sites, set in config.php if needed
5297 $noemail = 'Not sending email due to noemailever config setting';
5298 error_log($noemail);
5299 if (CLI_SCRIPT) {
5300 mtrace('Error: lib/moodlelib.php email_to_user(): '.$noemail);
5302 return true;
5305 if (!empty($CFG->divertallemailsto)) {
5306 $subject = "[DIVERTED {$user->email}] $subject";
5307 $user = clone($user);
5308 $user->email = $CFG->divertallemailsto;
5311 // skip mail to suspended users
5312 if ((isset($user->auth) && $user->auth=='nologin') or (isset($user->suspended) && $user->suspended)) {
5313 return true;
5316 if (!validate_email($user->email)) {
5317 // we can not send emails to invalid addresses - it might create security issue or confuse the mailer
5318 $invalidemail = "User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.";
5319 error_log($invalidemail);
5320 if (CLI_SCRIPT) {
5321 mtrace('Error: lib/moodlelib.php email_to_user(): '.$invalidemail);
5323 return false;
5326 if (over_bounce_threshold($user)) {
5327 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
5328 error_log($bouncemsg);
5329 if (CLI_SCRIPT) {
5330 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
5332 return false;
5335 // If the user is a remote mnet user, parse the email text for URL to the
5336 // wwwroot and modify the url to direct the user's browser to login at their
5337 // home site (identity provider - idp) before hitting the link itself
5338 if (is_mnet_remote_user($user)) {
5339 require_once($CFG->dirroot.'/mnet/lib.php');
5341 $jumpurl = mnet_get_idp_jump_url($user);
5342 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
5344 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
5345 $callback,
5346 $messagetext);
5347 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
5348 $callback,
5349 $messagehtml);
5351 $mail = get_mailer();
5353 if (!empty($mail->SMTPDebug)) {
5354 echo '<pre>' . "\n";
5357 $temprecipients = array();
5358 $tempreplyto = array();
5360 $supportuser = generate_email_supportuser();
5362 // make up an email address for handling bounces
5363 if (!empty($CFG->handlebounces)) {
5364 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
5365 $mail->Sender = generate_email_processing_address(0,$modargs);
5366 } else {
5367 $mail->Sender = $supportuser->email;
5370 if (is_string($from)) { // So we can pass whatever we want if there is need
5371 $mail->From = $CFG->noreplyaddress;
5372 $mail->FromName = $from;
5373 } else if ($usetrueaddress and $from->maildisplay) {
5374 $mail->From = $from->email;
5375 $mail->FromName = fullname($from);
5376 } else {
5377 $mail->From = $CFG->noreplyaddress;
5378 $mail->FromName = fullname($from);
5379 if (empty($replyto)) {
5380 $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
5384 if (!empty($replyto)) {
5385 $tempreplyto[] = array($replyto, $replytoname);
5388 $mail->Subject = substr($subject, 0, 900);
5390 $temprecipients[] = array($user->email, fullname($user));
5392 $mail->WordWrap = $wordwrapwidth; // set word wrap
5394 if (!empty($from->customheaders)) { // Add custom headers
5395 if (is_array($from->customheaders)) {
5396 foreach ($from->customheaders as $customheader) {
5397 $mail->AddCustomHeader($customheader);
5399 } else {
5400 $mail->AddCustomHeader($from->customheaders);
5404 if (!empty($from->priority)) {
5405 $mail->Priority = $from->priority;
5408 if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
5409 $mail->IsHTML(true);
5410 $mail->Encoding = 'quoted-printable'; // Encoding to use
5411 $mail->Body = $messagehtml;
5412 $mail->AltBody = "\n$messagetext\n";
5413 } else {
5414 $mail->IsHTML(false);
5415 $mail->Body = "\n$messagetext\n";
5418 if ($attachment && $attachname) {
5419 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
5420 $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
5421 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
5422 } else {
5423 require_once($CFG->libdir.'/filelib.php');
5424 $mimetype = mimeinfo('type', $attachname);
5425 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
5429 // Check if the email should be sent in an other charset then the default UTF-8
5430 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
5432 // use the defined site mail charset or eventually the one preferred by the recipient
5433 $charset = $CFG->sitemailcharset;
5434 if (!empty($CFG->allowusermailcharset)) {
5435 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
5436 $charset = $useremailcharset;
5440 // convert all the necessary strings if the charset is supported
5441 $charsets = get_list_of_charsets();
5442 unset($charsets['UTF-8']);
5443 if (in_array($charset, $charsets)) {
5444 $mail->CharSet = $charset;
5445 $mail->FromName = textlib::convert($mail->FromName, 'utf-8', strtolower($charset));
5446 $mail->Subject = textlib::convert($mail->Subject, 'utf-8', strtolower($charset));
5447 $mail->Body = textlib::convert($mail->Body, 'utf-8', strtolower($charset));
5448 $mail->AltBody = textlib::convert($mail->AltBody, 'utf-8', strtolower($charset));
5450 foreach ($temprecipients as $key => $values) {
5451 $temprecipients[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5453 foreach ($tempreplyto as $key => $values) {
5454 $tempreplyto[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5459 foreach ($temprecipients as $values) {
5460 $mail->AddAddress($values[0], $values[1]);
5462 foreach ($tempreplyto as $values) {
5463 $mail->AddReplyTo($values[0], $values[1]);
5466 if ($mail->Send()) {
5467 set_send_count($user);
5468 if (!empty($mail->SMTPDebug)) {
5469 echo '</pre>';
5471 return true;
5472 } else {
5473 add_to_log(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: '. $mail->ErrorInfo);
5474 if (CLI_SCRIPT) {
5475 mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
5477 if (!empty($mail->SMTPDebug)) {
5478 echo '</pre>';
5480 return false;
5485 * Generate a signoff for emails based on support settings
5487 * @global object
5488 * @return string
5490 function generate_email_signoff() {
5491 global $CFG;
5493 $signoff = "\n";
5494 if (!empty($CFG->supportname)) {
5495 $signoff .= $CFG->supportname."\n";
5497 if (!empty($CFG->supportemail)) {
5498 $signoff .= $CFG->supportemail."\n";
5500 if (!empty($CFG->supportpage)) {
5501 $signoff .= $CFG->supportpage."\n";
5503 return $signoff;
5507 * Generate a fake user for emails based on support settings
5508 * @global object
5509 * @return object user info
5511 function generate_email_supportuser() {
5512 global $CFG;
5514 static $supportuser;
5516 if (!empty($supportuser)) {
5517 return $supportuser;
5520 $supportuser = new stdClass();
5521 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
5522 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
5523 $supportuser->lastname = '';
5524 $supportuser->maildisplay = true;
5526 return $supportuser;
5531 * Sets specified user's password and send the new password to the user via email.
5533 * @global object
5534 * @global object
5535 * @param user $user A {@link $USER} object
5536 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
5538 function setnew_password_and_mail($user) {
5539 global $CFG, $DB;
5541 // we try to send the mail in language the user understands,
5542 // unfortunately the filter_string() does not support alternative langs yet
5543 // so multilang will not work properly for site->fullname
5544 $lang = empty($user->lang) ? $CFG->lang : $user->lang;
5546 $site = get_site();
5548 $supportuser = generate_email_supportuser();
5550 $newpassword = generate_password();
5552 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
5554 $a = new stdClass();
5555 $a->firstname = fullname($user, true);
5556 $a->sitename = format_string($site->fullname);
5557 $a->username = $user->username;
5558 $a->newpassword = $newpassword;
5559 $a->link = $CFG->wwwroot .'/login/';
5560 $a->signoff = generate_email_signoff();
5562 $message = (string)new lang_string('newusernewpasswordtext', '', $a, $lang);
5564 $subject = format_string($site->fullname) .': '. (string)new lang_string('newusernewpasswordsubj', '', $a, $lang);
5566 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5567 return email_to_user($user, $supportuser, $subject, $message);
5572 * Resets specified user's password and send the new password to the user via email.
5574 * @param stdClass $user A {@link $USER} object
5575 * @return bool Returns true if mail was sent OK and false if there was an error.
5577 function reset_password_and_mail($user) {
5578 global $CFG;
5580 $site = get_site();
5581 $supportuser = generate_email_supportuser();
5583 $userauth = get_auth_plugin($user->auth);
5584 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
5585 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
5586 return false;
5589 $newpassword = generate_password();
5591 if (!$userauth->user_update_password($user, $newpassword)) {
5592 print_error("cannotsetpassword");
5595 $a = new stdClass();
5596 $a->firstname = $user->firstname;
5597 $a->lastname = $user->lastname;
5598 $a->sitename = format_string($site->fullname);
5599 $a->username = $user->username;
5600 $a->newpassword = $newpassword;
5601 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
5602 $a->signoff = generate_email_signoff();
5604 $message = get_string('newpasswordtext', '', $a);
5606 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
5608 unset_user_preference('create_password', $user); // prevent cron from generating the password
5610 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5611 return email_to_user($user, $supportuser, $subject, $message);
5616 * Send email to specified user with confirmation text and activation link.
5618 * @global object
5619 * @param user $user A {@link $USER} object
5620 * @return bool Returns true if mail was sent OK and false if there was an error.
5622 function send_confirmation_email($user) {
5623 global $CFG;
5625 $site = get_site();
5626 $supportuser = generate_email_supportuser();
5628 $data = new stdClass();
5629 $data->firstname = fullname($user);
5630 $data->sitename = format_string($site->fullname);
5631 $data->admin = generate_email_signoff();
5633 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
5635 $username = urlencode($user->username);
5636 $username = str_replace('.', '%2E', $username); // prevent problems with trailing dots
5637 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $username;
5638 $message = get_string('emailconfirmation', '', $data);
5639 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
5641 $user->mailformat = 1; // Always send HTML version as well
5643 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5644 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
5649 * send_password_change_confirmation_email.
5651 * @global object
5652 * @param user $user A {@link $USER} object
5653 * @return bool Returns true if mail was sent OK and false if there was an error.
5655 function send_password_change_confirmation_email($user) {
5656 global $CFG;
5658 $site = get_site();
5659 $supportuser = generate_email_supportuser();
5661 $data = new stdClass();
5662 $data->firstname = $user->firstname;
5663 $data->lastname = $user->lastname;
5664 $data->sitename = format_string($site->fullname);
5665 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
5666 $data->admin = generate_email_signoff();
5668 $message = get_string('emailpasswordconfirmation', '', $data);
5669 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
5671 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5672 return email_to_user($user, $supportuser, $subject, $message);
5677 * send_password_change_info.
5679 * @global object
5680 * @param user $user A {@link $USER} object
5681 * @return bool Returns true if mail was sent OK and false if there was an error.
5683 function send_password_change_info($user) {
5684 global $CFG;
5686 $site = get_site();
5687 $supportuser = generate_email_supportuser();
5688 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
5690 $data = new stdClass();
5691 $data->firstname = $user->firstname;
5692 $data->lastname = $user->lastname;
5693 $data->sitename = format_string($site->fullname);
5694 $data->admin = generate_email_signoff();
5696 $userauth = get_auth_plugin($user->auth);
5698 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
5699 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
5700 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5701 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5702 return email_to_user($user, $supportuser, $subject, $message);
5705 if ($userauth->can_change_password() and $userauth->change_password_url()) {
5706 // we have some external url for password changing
5707 $data->link .= $userauth->change_password_url();
5709 } else {
5710 //no way to change password, sorry
5711 $data->link = '';
5714 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
5715 $message = get_string('emailpasswordchangeinfo', '', $data);
5716 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5717 } else {
5718 $message = get_string('emailpasswordchangeinfofail', '', $data);
5719 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5722 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5723 return email_to_user($user, $supportuser, $subject, $message);
5728 * Check that an email is allowed. It returns an error message if there
5729 * was a problem.
5731 * @global object
5732 * @param string $email Content of email
5733 * @return string|false
5735 function email_is_not_allowed($email) {
5736 global $CFG;
5738 if (!empty($CFG->allowemailaddresses)) {
5739 $allowed = explode(' ', $CFG->allowemailaddresses);
5740 foreach ($allowed as $allowedpattern) {
5741 $allowedpattern = trim($allowedpattern);
5742 if (!$allowedpattern) {
5743 continue;
5745 if (strpos($allowedpattern, '.') === 0) {
5746 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5747 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5748 return false;
5751 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5752 return false;
5755 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5757 } else if (!empty($CFG->denyemailaddresses)) {
5758 $denied = explode(' ', $CFG->denyemailaddresses);
5759 foreach ($denied as $deniedpattern) {
5760 $deniedpattern = trim($deniedpattern);
5761 if (!$deniedpattern) {
5762 continue;
5764 if (strpos($deniedpattern, '.') === 0) {
5765 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5766 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5767 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5770 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5771 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5776 return false;
5779 /// FILE HANDLING /////////////////////////////////////////////
5782 * Returns local file storage instance
5784 * @return file_storage
5786 function get_file_storage() {
5787 global $CFG;
5789 static $fs = null;
5791 if ($fs) {
5792 return $fs;
5795 require_once("$CFG->libdir/filelib.php");
5797 if (isset($CFG->filedir)) {
5798 $filedir = $CFG->filedir;
5799 } else {
5800 $filedir = $CFG->dataroot.'/filedir';
5803 if (isset($CFG->trashdir)) {
5804 $trashdirdir = $CFG->trashdir;
5805 } else {
5806 $trashdirdir = $CFG->dataroot.'/trashdir';
5809 $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5811 return $fs;
5815 * Returns local file storage instance
5817 * @return file_browser
5819 function get_file_browser() {
5820 global $CFG;
5822 static $fb = null;
5824 if ($fb) {
5825 return $fb;
5828 require_once("$CFG->libdir/filelib.php");
5830 $fb = new file_browser();
5832 return $fb;
5836 * Returns file packer
5838 * @param string $mimetype default application/zip
5839 * @return file_packer
5841 function get_file_packer($mimetype='application/zip') {
5842 global $CFG;
5844 static $fp = array();;
5846 if (isset($fp[$mimetype])) {
5847 return $fp[$mimetype];
5850 switch ($mimetype) {
5851 case 'application/zip':
5852 case 'application/vnd.moodle.backup':
5853 $classname = 'zip_packer';
5854 break;
5855 case 'application/x-tar':
5856 // $classname = 'tar_packer';
5857 // break;
5858 default:
5859 return false;
5862 require_once("$CFG->libdir/filestorage/$classname.php");
5863 $fp[$mimetype] = new $classname();
5865 return $fp[$mimetype];
5869 * Returns current name of file on disk if it exists.
5871 * @param string $newfile File to be verified
5872 * @return string Current name of file on disk if true
5874 function valid_uploaded_file($newfile) {
5875 if (empty($newfile)) {
5876 return '';
5878 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5879 return $newfile['tmp_name'];
5880 } else {
5881 return '';
5886 * Returns the maximum size for uploading files.
5888 * There are seven possible upload limits:
5889 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5890 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5891 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5892 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5893 * 5. by the Moodle admin in $CFG->maxbytes
5894 * 6. by the teacher in the current course $course->maxbytes
5895 * 7. by the teacher for the current module, eg $assignment->maxbytes
5897 * These last two are passed to this function as arguments (in bytes).
5898 * Anything defined as 0 is ignored.
5899 * The smallest of all the non-zero numbers is returned.
5901 * @todo Finish documenting this function
5903 * @param int $sizebytes Set maximum size
5904 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5905 * @param int $modulebytes Current module ->maxbytes (in bytes)
5906 * @return int The maximum size for uploading files.
5908 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5910 if (! $filesize = ini_get('upload_max_filesize')) {
5911 $filesize = '5M';
5913 $minimumsize = get_real_size($filesize);
5915 if ($postsize = ini_get('post_max_size')) {
5916 $postsize = get_real_size($postsize);
5917 if ($postsize < $minimumsize) {
5918 $minimumsize = $postsize;
5922 if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
5923 $minimumsize = $sitebytes;
5926 if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
5927 $minimumsize = $coursebytes;
5930 if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
5931 $minimumsize = $modulebytes;
5934 return $minimumsize;
5938 * Returns the maximum size for uploading files for the current user
5940 * This function takes in account @see:get_max_upload_file_size() the user's capabilities
5942 * @param context $context The context in which to check user capabilities
5943 * @param int $sizebytes Set maximum size
5944 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5945 * @param int $modulebytes Current module ->maxbytes (in bytes)
5946 * @param stdClass The user
5947 * @return int The maximum size for uploading files.
5949 function get_user_max_upload_file_size($context, $sitebytes=0, $coursebytes=0, $modulebytes=0, $user=null) {
5950 global $USER;
5952 if (empty($user)) {
5953 $user = $USER;
5956 if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
5957 return USER_CAN_IGNORE_FILE_SIZE_LIMITS;
5960 return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes);
5964 * Returns an array of possible sizes in local language
5966 * Related to {@link get_max_upload_file_size()} - this function returns an
5967 * array of possible sizes in an array, translated to the
5968 * local language.
5970 * @todo Finish documenting this function
5972 * @global object
5973 * @uses SORT_NUMERIC
5974 * @param int $sizebytes Set maximum size
5975 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5976 * @param int $modulebytes Current module ->maxbytes (in bytes)
5977 * @param int|array $custombytes custom upload size/s which will be added to list,
5978 * Only value/s smaller then maxsize will be added to list.
5979 * @return array
5981 function get_max_upload_sizes($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $custombytes = null) {
5982 global $CFG;
5984 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5985 return array();
5988 $filesize = array();
5989 $filesize[intval($maxsize)] = display_size($maxsize);
5991 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5992 5242880, 10485760, 20971520, 52428800, 104857600);
5994 // If custombytes is given and is valid then add it to the list.
5995 if (is_number($custombytes) and $custombytes > 0) {
5996 $custombytes = (int)$custombytes;
5997 if (!in_array($custombytes, $sizelist)) {
5998 $sizelist[] = $custombytes;
6000 } else if (is_array($custombytes)) {
6001 $sizelist = array_unique(array_merge($sizelist, $custombytes));
6004 // Allow maxbytes to be selected if it falls outside the above boundaries
6005 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
6006 // note: get_real_size() is used in order to prevent problems with invalid values
6007 $sizelist[] = get_real_size($CFG->maxbytes);
6010 foreach ($sizelist as $sizebytes) {
6011 if ($sizebytes < $maxsize) {
6012 $filesize[intval($sizebytes)] = display_size($sizebytes);
6016 krsort($filesize, SORT_NUMERIC);
6018 return $filesize;
6022 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
6024 * If excludefiles is defined, then that file/directory is ignored
6025 * If getdirs is true, then (sub)directories are included in the output
6026 * If getfiles is true, then files are included in the output
6027 * (at least one of these must be true!)
6029 * @todo Finish documenting this function. Add examples of $excludefile usage.
6031 * @param string $rootdir A given root directory to start from
6032 * @param string|array $excludefile If defined then the specified file/directory is ignored
6033 * @param bool $descend If true then subdirectories are recursed as well
6034 * @param bool $getdirs If true then (sub)directories are included in the output
6035 * @param bool $getfiles If true then files are included in the output
6036 * @return array An array with all the filenames in
6037 * all subdirectories, relative to the given rootdir
6039 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
6041 $dirs = array();
6043 if (!$getdirs and !$getfiles) { // Nothing to show
6044 return $dirs;
6047 if (!is_dir($rootdir)) { // Must be a directory
6048 return $dirs;
6051 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
6052 return $dirs;
6055 if (!is_array($excludefiles)) {
6056 $excludefiles = array($excludefiles);
6059 while (false !== ($file = readdir($dir))) {
6060 $firstchar = substr($file, 0, 1);
6061 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
6062 continue;
6064 $fullfile = $rootdir .'/'. $file;
6065 if (filetype($fullfile) == 'dir') {
6066 if ($getdirs) {
6067 $dirs[] = $file;
6069 if ($descend) {
6070 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
6071 foreach ($subdirs as $subdir) {
6072 $dirs[] = $file .'/'. $subdir;
6075 } else if ($getfiles) {
6076 $dirs[] = $file;
6079 closedir($dir);
6081 asort($dirs);
6083 return $dirs;
6088 * Adds up all the files in a directory and works out the size.
6090 * @todo Finish documenting this function
6092 * @param string $rootdir The directory to start from
6093 * @param string $excludefile A file to exclude when summing directory size
6094 * @return int The summed size of all files and subfiles within the root directory
6096 function get_directory_size($rootdir, $excludefile='') {
6097 global $CFG;
6099 // do it this way if we can, it's much faster
6100 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
6101 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
6102 $output = null;
6103 $return = null;
6104 exec($command,$output,$return);
6105 if (is_array($output)) {
6106 return get_real_size(intval($output[0]).'k'); // we told it to return k.
6110 if (!is_dir($rootdir)) { // Must be a directory
6111 return 0;
6114 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
6115 return 0;
6118 $size = 0;
6120 while (false !== ($file = readdir($dir))) {
6121 $firstchar = substr($file, 0, 1);
6122 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
6123 continue;
6125 $fullfile = $rootdir .'/'. $file;
6126 if (filetype($fullfile) == 'dir') {
6127 $size += get_directory_size($fullfile, $excludefile);
6128 } else {
6129 $size += filesize($fullfile);
6132 closedir($dir);
6134 return $size;
6138 * Converts bytes into display form
6140 * @todo Finish documenting this function. Verify return type.
6142 * @staticvar string $gb Localized string for size in gigabytes
6143 * @staticvar string $mb Localized string for size in megabytes
6144 * @staticvar string $kb Localized string for size in kilobytes
6145 * @staticvar string $b Localized string for size in bytes
6146 * @param int $size The size to convert to human readable form
6147 * @return string
6149 function display_size($size) {
6151 static $gb, $mb, $kb, $b;
6153 if ($size === USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
6154 return get_string('unlimited');
6157 if (empty($gb)) {
6158 $gb = get_string('sizegb');
6159 $mb = get_string('sizemb');
6160 $kb = get_string('sizekb');
6161 $b = get_string('sizeb');
6164 if ($size >= 1073741824) {
6165 $size = round($size / 1073741824 * 10) / 10 . $gb;
6166 } else if ($size >= 1048576) {
6167 $size = round($size / 1048576 * 10) / 10 . $mb;
6168 } else if ($size >= 1024) {
6169 $size = round($size / 1024 * 10) / 10 . $kb;
6170 } else {
6171 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
6173 return $size;
6177 * Cleans a given filename by removing suspicious or troublesome characters
6178 * @see clean_param()
6180 * @uses PARAM_FILE
6181 * @param string $string file name
6182 * @return string cleaned file name
6184 function clean_filename($string) {
6185 return clean_param($string, PARAM_FILE);
6189 /// STRING TRANSLATION ////////////////////////////////////////
6192 * Returns the code for the current language
6194 * @category string
6195 * @return string
6197 function current_language() {
6198 global $CFG, $USER, $SESSION, $COURSE;
6200 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
6201 $return = $COURSE->lang;
6203 } else if (!empty($SESSION->lang)) { // Session language can override other settings
6204 $return = $SESSION->lang;
6206 } else if (!empty($USER->lang)) {
6207 $return = $USER->lang;
6209 } else if (isset($CFG->lang)) {
6210 $return = $CFG->lang;
6212 } else {
6213 $return = 'en';
6216 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
6218 return $return;
6222 * Returns parent language of current active language if defined
6224 * @category string
6225 * @uses COURSE
6226 * @uses SESSION
6227 * @param string $lang null means current language
6228 * @return string
6230 function get_parent_language($lang=null) {
6231 global $COURSE, $SESSION;
6233 //let's hack around the current language
6234 if (!empty($lang)) {
6235 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
6236 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
6237 $COURSE->lang = '';
6238 $SESSION->lang = $lang;
6241 $parentlang = get_string('parentlanguage', 'langconfig');
6242 if ($parentlang === 'en') {
6243 $parentlang = '';
6246 //let's hack around the current language
6247 if (!empty($lang)) {
6248 $COURSE->lang = $old_course_lang;
6249 $SESSION->lang = $old_session_lang;
6252 return $parentlang;
6256 * Returns current string_manager instance.
6258 * The param $forcereload is needed for CLI installer only where the string_manager instance
6259 * must be replaced during the install.php script life time.
6261 * @category string
6262 * @param bool $forcereload shall the singleton be released and new instance created instead?
6263 * @return string_manager
6265 function get_string_manager($forcereload=false) {
6266 global $CFG;
6268 static $singleton = null;
6270 if ($forcereload) {
6271 $singleton = null;
6273 if ($singleton === null) {
6274 if (empty($CFG->early_install_lang)) {
6276 if (empty($CFG->langcacheroot)) {
6277 $langcacheroot = $CFG->cachedir . '/lang';
6278 } else {
6279 $langcacheroot = $CFG->langcacheroot;
6282 if (empty($CFG->langlist)) {
6283 $translist = array();
6284 } else {
6285 $translist = explode(',', $CFG->langlist);
6288 if (empty($CFG->langmenucachefile)) {
6289 $langmenucache = $CFG->cachedir . '/languages';
6290 } else {
6291 $langmenucache = $CFG->langmenucachefile;
6294 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
6295 !empty($CFG->langstringcache), $translist, $langmenucache);
6297 } else {
6298 $singleton = new install_string_manager();
6302 return $singleton;
6307 * Interface for string manager
6309 * Interface describing class which is responsible for getting
6310 * of localised strings from language packs.
6312 * @package core
6313 * @copyright 2010 Petr Skoda (http://skodak.org)
6314 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6316 interface string_manager {
6318 * Get String returns a requested string
6320 * @param string $identifier The identifier of the string to search for
6321 * @param string $component The module the string is associated with
6322 * @param string|object|array $a An object, string or number that can be used
6323 * within translation strings
6324 * @param string $lang moodle translation language, NULL means use current
6325 * @return string The String !
6327 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
6330 * Does the string actually exist?
6332 * get_string() is throwing debug warnings, sometimes we do not want them
6333 * or we want to display better explanation of the problem.
6335 * Use with care!
6337 * @param string $identifier The identifier of the string to search for
6338 * @param string $component The module the string is associated with
6339 * @return boot true if exists
6341 public function string_exists($identifier, $component);
6344 * Returns a localised list of all country names, sorted by country keys.
6345 * @param bool $returnall return all or just enabled
6346 * @param string $lang moodle translation language, NULL means use current
6347 * @return array two-letter country code => translated name.
6349 public function get_list_of_countries($returnall = false, $lang = NULL);
6352 * Returns a localised list of languages, sorted by code keys.
6354 * @param string $lang moodle translation language, NULL means use current
6355 * @param string $standard language list standard
6356 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6357 * @return array language code => translated name
6359 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
6362 * Checks if the translation exists for the language
6364 * @param string $lang moodle translation language code
6365 * @param bool $includeall include also disabled translations
6366 * @return bool true if exists
6368 public function translation_exists($lang, $includeall = true);
6371 * Returns localised list of installed translations
6372 * @param bool $returnall return all or just enabled
6373 * @return array moodle translation code => localised translation name
6375 public function get_list_of_translations($returnall = false);
6378 * Returns localised list of currencies.
6380 * @param string $lang moodle translation language, NULL means use current
6381 * @return array currency code => localised currency name
6383 public function get_list_of_currencies($lang = NULL);
6386 * Load all strings for one component
6387 * @param string $component The module the string is associated with
6388 * @param string $lang
6389 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6390 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6391 * @return array of all string for given component and lang
6393 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
6396 * Invalidates all caches, should the implementation use any
6398 public function reset_caches();
6403 * Standard string_manager implementation
6405 * Implements string_manager with getting and printing localised strings
6407 * @package core
6408 * @category string
6409 * @copyright 2010 Petr Skoda (http://skodak.org)
6410 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6412 class core_string_manager implements string_manager {
6413 /** @var string location of all packs except 'en' */
6414 protected $otherroot;
6415 /** @var string location of all lang pack local modifications */
6416 protected $localroot;
6417 /** @var string location of on-disk cache of merged strings */
6418 protected $cacheroot;
6419 /** @var array lang string cache - it will be optimised more later */
6420 protected $cache = array();
6421 /** @var int get_string() counter */
6422 protected $countgetstring = 0;
6423 /** @var int in-memory cache hits counter */
6424 protected $countmemcache = 0;
6425 /** @var int on-disk cache hits counter */
6426 protected $countdiskcache = 0;
6427 /** @var bool use disk cache */
6428 protected $usediskcache;
6429 /** @var array limit list of translations */
6430 protected $translist;
6431 /** @var string location of a file that caches the list of available translations */
6432 protected $menucache;
6435 * Create new instance of string manager
6437 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
6438 * @param string $localroot usually the same as $otherroot
6439 * @param string $cacheroot usually lang dir in cache folder
6440 * @param bool $usediskcache use disk cache
6441 * @param array $translist limit list of visible translations
6442 * @param string $menucache the location of a file that caches the list of available translations
6444 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
6445 $this->otherroot = $otherroot;
6446 $this->localroot = $localroot;
6447 $this->cacheroot = $cacheroot;
6448 $this->usediskcache = $usediskcache;
6449 $this->translist = $translist;
6450 $this->menucache = $menucache;
6454 * Returns dependencies of current language, en is not included.
6456 * @param string $lang
6457 * @return array all parents, the lang itself is last
6459 public function get_language_dependencies($lang) {
6460 if ($lang === 'en') {
6461 return array();
6463 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
6464 return array();
6466 $string = array();
6467 include("$this->otherroot/$lang/langconfig.php");
6469 if (empty($string['parentlanguage'])) {
6470 return array($lang);
6471 } else {
6472 $parentlang = $string['parentlanguage'];
6473 unset($string);
6474 return array_merge($this->get_language_dependencies($parentlang), array($lang));
6479 * Load all strings for one component
6481 * @param string $component The module the string is associated with
6482 * @param string $lang
6483 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6484 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6485 * @return array of all string for given component and lang
6487 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6488 global $CFG;
6490 list($plugintype, $pluginname) = normalize_component($component);
6491 if ($plugintype == 'core' and is_null($pluginname)) {
6492 $component = 'core';
6493 } else {
6494 $component = $plugintype . '_' . $pluginname;
6497 if (!$disablecache and !$disablelocal) {
6498 // try in-memory cache first
6499 if (isset($this->cache[$lang][$component])) {
6500 $this->countmemcache++;
6501 return $this->cache[$lang][$component];
6504 // try on-disk cache then
6505 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
6506 $this->countdiskcache++;
6507 include($this->cacheroot . "/$lang/$component.php");
6508 return $this->cache[$lang][$component];
6512 // no cache found - let us merge all possible sources of the strings
6513 if ($plugintype === 'core') {
6514 $file = $pluginname;
6515 if ($file === null) {
6516 $file = 'moodle';
6518 $string = array();
6519 // first load english pack
6520 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
6521 return array();
6523 include("$CFG->dirroot/lang/en/$file.php");
6524 $originalkeys = array_keys($string);
6525 $originalkeys = array_flip($originalkeys);
6527 // and then corresponding local if present and allowed
6528 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6529 include("$this->localroot/en_local/$file.php");
6531 // now loop through all langs in correct order
6532 $deps = $this->get_language_dependencies($lang);
6533 foreach ($deps as $dep) {
6534 // the main lang string location
6535 if (file_exists("$this->otherroot/$dep/$file.php")) {
6536 include("$this->otherroot/$dep/$file.php");
6538 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6539 include("$this->localroot/{$dep}_local/$file.php");
6543 } else {
6544 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
6545 return array();
6547 if ($plugintype === 'mod') {
6548 // bloody mod hack
6549 $file = $pluginname;
6550 } else {
6551 $file = $plugintype . '_' . $pluginname;
6553 $string = array();
6554 // first load English pack
6555 if (!file_exists("$location/lang/en/$file.php")) {
6556 //English pack does not exist, so do not try to load anything else
6557 return array();
6559 include("$location/lang/en/$file.php");
6560 $originalkeys = array_keys($string);
6561 $originalkeys = array_flip($originalkeys);
6562 // and then corresponding local english if present
6563 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6564 include("$this->localroot/en_local/$file.php");
6567 // now loop through all langs in correct order
6568 $deps = $this->get_language_dependencies($lang);
6569 foreach ($deps as $dep) {
6570 // legacy location - used by contrib only
6571 if (file_exists("$location/lang/$dep/$file.php")) {
6572 include("$location/lang/$dep/$file.php");
6574 // the main lang string location
6575 if (file_exists("$this->otherroot/$dep/$file.php")) {
6576 include("$this->otherroot/$dep/$file.php");
6578 // local customisations
6579 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6580 include("$this->localroot/{$dep}_local/$file.php");
6585 // we do not want any extra strings from other languages - everything must be in en lang pack
6586 $string = array_intersect_key($string, $originalkeys);
6588 if (!$disablelocal) {
6589 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
6590 // caches so we do not need to do all this merging and dependencies resolving again
6591 $this->cache[$lang][$component] = $string;
6592 if ($this->usediskcache) {
6593 check_dir_exists("$this->cacheroot/$lang");
6594 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
6597 return $string;
6601 * Does the string actually exist?
6603 * get_string() is throwing debug warnings, sometimes we do not want them
6604 * or we want to display better explanation of the problem.
6605 * Note: Use with care!
6607 * @param string $identifier The identifier of the string to search for
6608 * @param string $component The module the string is associated with
6609 * @return boot true if exists
6611 public function string_exists($identifier, $component) {
6612 $identifier = clean_param($identifier, PARAM_STRINGID);
6613 if (empty($identifier)) {
6614 return false;
6616 $lang = current_language();
6617 $string = $this->load_component_strings($component, $lang);
6618 return isset($string[$identifier]);
6622 * Get String returns a requested string
6624 * @param string $identifier The identifier of the string to search for
6625 * @param string $component The module the string is associated with
6626 * @param string|object|array $a An object, string or number that can be used
6627 * within translation strings
6628 * @param string $lang moodle translation language, NULL means use current
6629 * @return string The String !
6631 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6632 $this->countgetstring++;
6633 // there are very many uses of these time formating strings without the 'langconfig' component,
6634 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
6635 static $langconfigstrs = array(
6636 'strftimedate' => 1,
6637 'strftimedatefullshort' => 1,
6638 'strftimedateshort' => 1,
6639 'strftimedatetime' => 1,
6640 'strftimedatetimeshort' => 1,
6641 'strftimedaydate' => 1,
6642 'strftimedaydatetime' => 1,
6643 'strftimedayshort' => 1,
6644 'strftimedaytime' => 1,
6645 'strftimemonthyear' => 1,
6646 'strftimerecent' => 1,
6647 'strftimerecentfull' => 1,
6648 'strftimetime' => 1);
6650 if (empty($component)) {
6651 if (isset($langconfigstrs[$identifier])) {
6652 $component = 'langconfig';
6653 } else {
6654 $component = 'moodle';
6658 if ($lang === NULL) {
6659 $lang = current_language();
6662 $string = $this->load_component_strings($component, $lang);
6664 if (!isset($string[$identifier])) {
6665 if ($component === 'pix' or $component === 'core_pix') {
6666 // this component contains only alt tags for emoticons,
6667 // not all of them are supposed to be defined
6668 return '';
6670 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
6671 // parentlanguage is a special string, undefined means use English if not defined
6672 return 'en';
6674 if ($this->usediskcache) {
6675 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources,
6676 // do NOT write the results to disk cache because it may end up in race conditions see MDL-31904
6677 $this->usediskcache = false;
6678 $string = $this->load_component_strings($component, $lang, true);
6679 $this->usediskcache = true;
6681 if (!isset($string[$identifier])) {
6682 // the string is still missing - should be fixed by developer
6683 list($plugintype, $pluginname) = normalize_component($component);
6684 if ($plugintype == 'core') {
6685 $file = "lang/en/{$component}.php";
6686 } else if ($plugintype == 'mod') {
6687 $file = "mod/{$pluginname}/lang/en/{$pluginname}.php";
6688 } else {
6689 $path = get_plugin_directory($plugintype, $pluginname);
6690 $file = "{$path}/lang/en/{$plugintype}_{$pluginname}.php";
6692 debugging("Invalid get_string() identifier: '{$identifier}' or component '{$component}'. " .
6693 "Perhaps you are missing \$string['{$identifier}'] = ''; in {$file}?", DEBUG_DEVELOPER);
6694 return "[[$identifier]]";
6698 $string = $string[$identifier];
6700 if ($a !== NULL) {
6701 // Process array's and objects (except lang_strings)
6702 if (is_array($a) or (is_object($a) && !($a instanceof lang_string))) {
6703 $a = (array)$a;
6704 $search = array();
6705 $replace = array();
6706 foreach ($a as $key=>$value) {
6707 if (is_int($key)) {
6708 // we do not support numeric keys - sorry!
6709 continue;
6711 if (is_array($value) or (is_object($value) && !($value instanceof lang_string))) {
6712 // we support just string or lang_string as value
6713 continue;
6715 $search[] = '{$a->'.$key.'}';
6716 $replace[] = (string)$value;
6718 if ($search) {
6719 $string = str_replace($search, $replace, $string);
6721 } else {
6722 $string = str_replace('{$a}', (string)$a, $string);
6726 return $string;
6730 * Returns information about the string_manager performance
6732 * @return array
6734 public function get_performance_summary() {
6735 return array(array(
6736 'langcountgetstring' => $this->countgetstring,
6737 'langcountmemcache' => $this->countmemcache,
6738 'langcountdiskcache' => $this->countdiskcache,
6739 ), array(
6740 'langcountgetstring' => 'get_string calls',
6741 'langcountmemcache' => 'strings mem cache hits',
6742 'langcountdiskcache' => 'strings disk cache hits',
6747 * Returns a localised list of all country names, sorted by localised name.
6749 * @param bool $returnall return all or just enabled
6750 * @param string $lang moodle translation language, NULL means use current
6751 * @return array two-letter country code => translated name.
6753 public function get_list_of_countries($returnall = false, $lang = NULL) {
6754 global $CFG;
6756 if ($lang === NULL) {
6757 $lang = current_language();
6760 $countries = $this->load_component_strings('core_countries', $lang);
6761 collatorlib::asort($countries);
6762 if (!$returnall and !empty($CFG->allcountrycodes)) {
6763 $enabled = explode(',', $CFG->allcountrycodes);
6764 $return = array();
6765 foreach ($enabled as $c) {
6766 if (isset($countries[$c])) {
6767 $return[$c] = $countries[$c];
6770 return $return;
6773 return $countries;
6777 * Returns a localised list of languages, sorted by code keys.
6779 * @param string $lang moodle translation language, NULL means use current
6780 * @param string $standard language list standard
6781 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
6782 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
6783 * @return array language code => translated name
6785 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
6786 if ($lang === NULL) {
6787 $lang = current_language();
6790 if ($standard === 'iso6392') {
6791 $langs = $this->load_component_strings('core_iso6392', $lang);
6792 ksort($langs);
6793 return $langs;
6795 } else if ($standard === 'iso6391') {
6796 $langs2 = $this->load_component_strings('core_iso6392', $lang);
6797 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
6798 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
6799 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
6800 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
6801 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
6802 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
6803 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
6804 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
6805 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
6806 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
6807 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
6808 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
6809 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
6810 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6811 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6812 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6813 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6814 $langs1 = array();
6815 foreach ($mapping as $c2=>$c1) {
6816 $langs1[$c1] = $langs2[$c2];
6818 ksort($langs1);
6819 return $langs1;
6821 } else {
6822 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6825 return array();
6829 * Checks if the translation exists for the language
6831 * @param string $lang moodle translation language code
6832 * @param bool $includeall include also disabled translations
6833 * @return bool true if exists
6835 public function translation_exists($lang, $includeall = true) {
6837 if (strpos($lang, '_local') !== false) {
6838 // _local packs are not real translations
6839 return false;
6841 if (!$includeall and !empty($this->translist)) {
6842 if (!in_array($lang, $this->translist)) {
6843 return false;
6846 if ($lang === 'en') {
6847 // part of distribution
6848 return true;
6850 return file_exists("$this->otherroot/$lang/langconfig.php");
6854 * Returns localised list of installed translations
6856 * @param bool $returnall return all or just enabled
6857 * @return array moodle translation code => localised translation name
6859 public function get_list_of_translations($returnall = false) {
6860 global $CFG;
6862 $languages = array();
6864 if (!empty($CFG->langcache) and is_readable($this->menucache)) {
6865 // try to re-use the cached list of all available languages
6866 $cachedlist = json_decode(file_get_contents($this->menucache), true);
6868 if (is_array($cachedlist) and !empty($cachedlist)) {
6869 // the cache file is restored correctly
6871 if (!$returnall and !empty($this->translist)) {
6872 // return just enabled translations
6873 foreach ($cachedlist as $langcode => $langname) {
6874 if (in_array($langcode, $this->translist)) {
6875 $languages[$langcode] = $langname;
6878 return $languages;
6880 } else {
6881 // return all translations
6882 return $cachedlist;
6887 // the cached list of languages is not available, let us populate the list
6889 if (!$returnall and !empty($this->translist)) {
6890 // return only some translations
6891 foreach ($this->translist as $lang) {
6892 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6893 if (strstr($lang, '_local') !== false) {
6894 continue;
6896 if (strstr($lang, '_utf8') !== false) {
6897 continue;
6899 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6900 // some broken or missing lang - can not switch to it anyway
6901 continue;
6903 $string = $this->load_component_strings('langconfig', $lang);
6904 if (!empty($string['thislanguage'])) {
6905 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6907 unset($string);
6910 } else {
6911 // return all languages available in system
6912 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6914 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6915 // Sort all
6917 // Loop through all langs and get info
6918 foreach ($langdirs as $lang) {
6919 if (strstr($lang, '_local') !== false) {
6920 continue;
6922 if (strstr($lang, '_utf8') !== false) {
6923 continue;
6925 $string = $this->load_component_strings('langconfig', $lang);
6926 if (!empty($string['thislanguage'])) {
6927 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6929 unset($string);
6932 if (!empty($CFG->langcache) and !empty($this->menucache)) {
6933 // cache the list so that it can be used next time
6934 collatorlib::asort($languages);
6935 check_dir_exists(dirname($this->menucache), true, true);
6936 file_put_contents($this->menucache, json_encode($languages));
6940 collatorlib::asort($languages);
6942 return $languages;
6946 * Returns localised list of currencies.
6948 * @param string $lang moodle translation language, NULL means use current
6949 * @return array currency code => localised currency name
6951 public function get_list_of_currencies($lang = NULL) {
6952 if ($lang === NULL) {
6953 $lang = current_language();
6956 $currencies = $this->load_component_strings('core_currencies', $lang);
6957 asort($currencies);
6959 return $currencies;
6963 * Clears both in-memory and on-disk caches
6965 public function reset_caches() {
6966 global $CFG;
6967 require_once("$CFG->libdir/filelib.php");
6969 // clear the on-disk disk with aggregated string files
6970 fulldelete($this->cacheroot);
6972 // clear the in-memory cache of loaded strings
6973 $this->cache = array();
6975 // clear the cache containing the list of available translations
6976 // and re-populate it again
6977 fulldelete($this->menucache);
6978 $this->get_list_of_translations(true);
6984 * Fetches minimum strings for installation
6986 * Minimalistic string fetching implementation
6987 * that is used in installer before we fetch the wanted
6988 * language pack from moodle.org lang download site.
6990 * @package core
6991 * @copyright 2010 Petr Skoda (http://skodak.org)
6992 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6994 class install_string_manager implements string_manager {
6995 /** @var string location of pre-install packs for all langs */
6996 protected $installroot;
6999 * Crate new instance of install string manager
7001 public function __construct() {
7002 global $CFG;
7003 $this->installroot = "$CFG->dirroot/install/lang";
7007 * Load all strings for one component
7008 * @param string $component The module the string is associated with
7009 * @param string $lang
7010 * @param bool $disablecache Do not use caches, force fetching the strings from sources
7011 * @param bool $disablelocal Do not use customized strings in xx_local language packs
7012 * @return array of all string for given component and lang
7014 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
7015 // not needed in installer
7016 return array();
7020 * Does the string actually exist?
7022 * get_string() is throwing debug warnings, sometimes we do not want them
7023 * or we want to display better explanation of the problem.
7025 * Use with care!
7027 * @param string $identifier The identifier of the string to search for
7028 * @param string $component The module the string is associated with
7029 * @return boot true if exists
7031 public function string_exists($identifier, $component) {
7032 $identifier = clean_param($identifier, PARAM_STRINGID);
7033 if (empty($identifier)) {
7034 return false;
7036 // simple old style hack ;)
7037 $str = get_string($identifier, $component);
7038 return (strpos($str, '[[') === false);
7042 * Get String returns a requested string
7044 * @param string $identifier The identifier of the string to search for
7045 * @param string $component The module the string is associated with
7046 * @param string|object|array $a An object, string or number that can be used
7047 * within translation strings
7048 * @param string $lang moodle translation language, NULL means use current
7049 * @return string The String !
7051 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
7052 if (!$component) {
7053 $component = 'moodle';
7056 if ($lang === NULL) {
7057 $lang = current_language();
7060 //get parent lang
7061 $parent = '';
7062 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
7063 if (file_exists("$this->installroot/$lang/langconfig.php")) {
7064 $string = array();
7065 include("$this->installroot/$lang/langconfig.php");
7066 if (isset($string['parentlanguage'])) {
7067 $parent = $string['parentlanguage'];
7069 unset($string);
7073 // include en string first
7074 if (!file_exists("$this->installroot/en/$component.php")) {
7075 return "[[$identifier]]";
7077 $string = array();
7078 include("$this->installroot/en/$component.php");
7080 // now override en with parent if defined
7081 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
7082 include("$this->installroot/$parent/$component.php");
7085 // finally override with requested language
7086 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
7087 include("$this->installroot/$lang/$component.php");
7090 if (!isset($string[$identifier])) {
7091 return "[[$identifier]]";
7094 $string = $string[$identifier];
7096 if ($a !== NULL) {
7097 if (is_object($a) or is_array($a)) {
7098 $a = (array)$a;
7099 $search = array();
7100 $replace = array();
7101 foreach ($a as $key=>$value) {
7102 if (is_int($key)) {
7103 // we do not support numeric keys - sorry!
7104 continue;
7106 $search[] = '{$a->'.$key.'}';
7107 $replace[] = (string)$value;
7109 if ($search) {
7110 $string = str_replace($search, $replace, $string);
7112 } else {
7113 $string = str_replace('{$a}', (string)$a, $string);
7117 return $string;
7121 * Returns a localised list of all country names, sorted by country keys.
7123 * @param bool $returnall return all or just enabled
7124 * @param string $lang moodle translation language, NULL means use current
7125 * @return array two-letter country code => translated name.
7127 public function get_list_of_countries($returnall = false, $lang = NULL) {
7128 //not used in installer
7129 return array();
7133 * Returns a localised list of languages, sorted by code keys.
7135 * @param string $lang moodle translation language, NULL means use current
7136 * @param string $standard language list standard
7137 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
7138 * @return array language code => translated name
7140 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
7141 //not used in installer
7142 return array();
7146 * Checks if the translation exists for the language
7148 * @param string $lang moodle translation language code
7149 * @param bool $includeall include also disabled translations
7150 * @return bool true if exists
7152 public function translation_exists($lang, $includeall = true) {
7153 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
7157 * Returns localised list of installed translations
7158 * @param bool $returnall return all or just enabled
7159 * @return array moodle translation code => localised translation name
7161 public function get_list_of_translations($returnall = false) {
7162 // return all is ignored here - we need to know all langs in installer
7163 $languages = array();
7164 // Get raw list of lang directories
7165 $langdirs = get_list_of_plugins('install/lang');
7166 asort($langdirs);
7167 // Get some info from each lang
7168 foreach ($langdirs as $lang) {
7169 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
7170 $string = array();
7171 include($this->installroot.'/'.$lang.'/langconfig.php');
7172 if (!empty($string['thislanguage'])) {
7173 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
7177 // Return array
7178 return $languages;
7182 * Returns localised list of currencies.
7184 * @param string $lang moodle translation language, NULL means use current
7185 * @return array currency code => localised currency name
7187 public function get_list_of_currencies($lang = NULL) {
7188 // not used in installer
7189 return array();
7193 * This implementation does not use any caches
7195 public function reset_caches() {}
7200 * Returns a localized string.
7202 * Returns the translated string specified by $identifier as
7203 * for $module. Uses the same format files as STphp.
7204 * $a is an object, string or number that can be used
7205 * within translation strings
7207 * eg 'hello {$a->firstname} {$a->lastname}'
7208 * or 'hello {$a}'
7210 * If you would like to directly echo the localized string use
7211 * the function {@link print_string()}
7213 * Example usage of this function involves finding the string you would
7214 * like a local equivalent of and using its identifier and module information
7215 * to retrieve it.<br/>
7216 * If you open moodle/lang/en/moodle.php and look near line 278
7217 * you will find a string to prompt a user for their word for 'course'
7218 * <code>
7219 * $string['course'] = 'Course';
7220 * </code>
7221 * So if you want to display the string 'Course'
7222 * in any language that supports it on your site
7223 * you just need to use the identifier 'course'
7224 * <code>
7225 * $mystring = '<strong>'. get_string('course') .'</strong>';
7226 * or
7227 * </code>
7228 * If the string you want is in another file you'd take a slightly
7229 * different approach. Looking in moodle/lang/en/calendar.php you find
7230 * around line 75:
7231 * <code>
7232 * $string['typecourse'] = 'Course event';
7233 * </code>
7234 * If you want to display the string "Course event" in any language
7235 * supported you would use the identifier 'typecourse' and the module 'calendar'
7236 * (because it is in the file calendar.php):
7237 * <code>
7238 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
7239 * </code>
7241 * As a last resort, should the identifier fail to map to a string
7242 * the returned string will be [[ $identifier ]]
7244 * In Moodle 2.3 there is a new argument to this function $lazyload.
7245 * Setting $lazyload to true causes get_string to return a lang_string object
7246 * rather than the string itself. The fetching of the string is then put off until
7247 * the string object is first used. The object can be used by calling it's out
7248 * method or by casting the object to a string, either directly e.g.
7249 * (string)$stringobject
7250 * or indirectly by using the string within another string or echoing it out e.g.
7251 * echo $stringobject
7252 * return "<p>{$stringobject}</p>";
7253 * It is worth noting that using $lazyload and attempting to use the string as an
7254 * array key will cause a fatal error as objects cannot be used as array keys.
7255 * But you should never do that anyway!
7256 * For more information {@see lang_string}
7258 * @category string
7259 * @param string $identifier The key identifier for the localized string
7260 * @param string $component The module where the key identifier is stored,
7261 * usually expressed as the filename in the language pack without the
7262 * .php on the end but can also be written as mod/forum or grade/export/xls.
7263 * If none is specified then moodle.php is used.
7264 * @param string|object|array $a An object, string or number that can be used
7265 * within translation strings
7266 * @param bool $lazyload If set to true a string object is returned instead of
7267 * the string itself. The string then isn't calculated until it is first used.
7268 * @return string The localized string.
7270 function get_string($identifier, $component = '', $a = NULL, $lazyload = false) {
7271 global $CFG;
7273 // If the lazy load argument has been supplied return a lang_string object
7274 // instead.
7275 // We need to make sure it is true (and a bool) as you will see below there
7276 // used to be a forth argument at one point.
7277 if ($lazyload === true) {
7278 return new lang_string($identifier, $component, $a);
7281 $identifier = clean_param($identifier, PARAM_STRINGID);
7282 if (empty($identifier)) {
7283 throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.');
7286 // There is now a forth argument again, this time it is a boolean however so
7287 // we can still check for the old extralocations parameter.
7288 if (!is_bool($lazyload) && !empty($lazyload)) {
7289 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
7292 if (strpos($component, '/') !== false) {
7293 debugging('The module name you passed to get_string is the deprecated format ' .
7294 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
7295 $componentpath = explode('/', $component);
7297 switch ($componentpath[0]) {
7298 case 'mod':
7299 $component = $componentpath[1];
7300 break;
7301 case 'blocks':
7302 case 'block':
7303 $component = 'block_'.$componentpath[1];
7304 break;
7305 case 'enrol':
7306 $component = 'enrol_'.$componentpath[1];
7307 break;
7308 case 'format':
7309 $component = 'format_'.$componentpath[1];
7310 break;
7311 case 'grade':
7312 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
7313 break;
7317 $result = get_string_manager()->get_string($identifier, $component, $a);
7319 // Debugging feature lets you display string identifier and component
7320 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
7321 $result .= ' {' . $identifier . '/' . $component . '}';
7323 return $result;
7327 * Converts an array of strings to their localized value.
7329 * @param array $array An array of strings
7330 * @param string $component The language module that these strings can be found in.
7331 * @return stdClass translated strings.
7333 function get_strings($array, $component = '') {
7334 $string = new stdClass;
7335 foreach ($array as $item) {
7336 $string->$item = get_string($item, $component);
7338 return $string;
7342 * Prints out a translated string.
7344 * Prints out a translated string using the return value from the {@link get_string()} function.
7346 * Example usage of this function when the string is in the moodle.php file:<br/>
7347 * <code>
7348 * echo '<strong>';
7349 * print_string('course');
7350 * echo '</strong>';
7351 * </code>
7353 * Example usage of this function when the string is not in the moodle.php file:<br/>
7354 * <code>
7355 * echo '<h1>';
7356 * print_string('typecourse', 'calendar');
7357 * echo '</h1>';
7358 * </code>
7360 * @category string
7361 * @param string $identifier The key identifier for the localized string
7362 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
7363 * @param string|object|array $a An object, string or number that can be used within translation strings
7365 function print_string($identifier, $component = '', $a = NULL) {
7366 echo get_string($identifier, $component, $a);
7370 * Returns a list of charset codes
7372 * Returns a list of charset codes. It's hardcoded, so they should be added manually
7373 * (checking that such charset is supported by the texlib library!)
7375 * @return array And associative array with contents in the form of charset => charset
7377 function get_list_of_charsets() {
7379 $charsets = array(
7380 'EUC-JP' => 'EUC-JP',
7381 'ISO-2022-JP'=> 'ISO-2022-JP',
7382 'ISO-8859-1' => 'ISO-8859-1',
7383 'SHIFT-JIS' => 'SHIFT-JIS',
7384 'GB2312' => 'GB2312',
7385 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
7386 'UTF-8' => 'UTF-8');
7388 asort($charsets);
7390 return $charsets;
7394 * Returns a list of valid and compatible themes
7396 * @return array
7398 function get_list_of_themes() {
7399 global $CFG;
7401 $themes = array();
7403 if (!empty($CFG->themelist)) { // use admin's list of themes
7404 $themelist = explode(',', $CFG->themelist);
7405 } else {
7406 $themelist = array_keys(get_plugin_list("theme"));
7409 foreach ($themelist as $key => $themename) {
7410 $theme = theme_config::load($themename);
7411 $themes[$themename] = $theme;
7414 collatorlib::asort_objects_by_method($themes, 'get_theme_name');
7416 return $themes;
7420 * Returns a list of timezones in the current language
7422 * @global object
7423 * @global object
7424 * @return array
7426 function get_list_of_timezones() {
7427 global $CFG, $DB;
7429 static $timezones;
7431 if (!empty($timezones)) { // This function has been called recently
7432 return $timezones;
7435 $timezones = array();
7437 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
7438 foreach($rawtimezones as $timezone) {
7439 if (!empty($timezone->name)) {
7440 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
7441 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
7442 } else {
7443 $timezones[$timezone->name] = $timezone->name;
7445 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
7446 $timezones[$timezone->name] = $timezone->name;
7452 asort($timezones);
7454 for ($i = -13; $i <= 13; $i += .5) {
7455 $tzstring = 'UTC';
7456 if ($i < 0) {
7457 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
7458 } else if ($i > 0) {
7459 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
7460 } else {
7461 $timezones[sprintf("%.1f", $i)] = $tzstring;
7465 return $timezones;
7469 * Factory function for emoticon_manager
7471 * @return emoticon_manager singleton
7473 function get_emoticon_manager() {
7474 static $singleton = null;
7476 if (is_null($singleton)) {
7477 $singleton = new emoticon_manager();
7480 return $singleton;
7484 * Provides core support for plugins that have to deal with
7485 * emoticons (like HTML editor or emoticon filter).
7487 * Whenever this manager mentiones 'emoticon object', the following data
7488 * structure is expected: stdClass with properties text, imagename, imagecomponent,
7489 * altidentifier and altcomponent
7491 * @see admin_setting_emoticons
7493 class emoticon_manager {
7496 * Returns the currently enabled emoticons
7498 * @return array of emoticon objects
7500 public function get_emoticons() {
7501 global $CFG;
7503 if (empty($CFG->emoticons)) {
7504 return array();
7507 $emoticons = $this->decode_stored_config($CFG->emoticons);
7509 if (!is_array($emoticons)) {
7510 // something is wrong with the format of stored setting
7511 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
7512 return array();
7515 return $emoticons;
7519 * Converts emoticon object into renderable pix_emoticon object
7521 * @param stdClass $emoticon emoticon object
7522 * @param array $attributes explicit HTML attributes to set
7523 * @return pix_emoticon
7525 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
7526 $stringmanager = get_string_manager();
7527 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
7528 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
7529 } else {
7530 $alt = s($emoticon->text);
7532 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
7536 * Encodes the array of emoticon objects into a string storable in config table
7538 * @see self::decode_stored_config()
7539 * @param array $emoticons array of emtocion objects
7540 * @return string
7542 public function encode_stored_config(array $emoticons) {
7543 return json_encode($emoticons);
7547 * Decodes the string into an array of emoticon objects
7549 * @see self::encode_stored_config()
7550 * @param string $encoded
7551 * @return string|null
7553 public function decode_stored_config($encoded) {
7554 $decoded = json_decode($encoded);
7555 if (!is_array($decoded)) {
7556 return null;
7558 return $decoded;
7562 * Returns default set of emoticons supported by Moodle
7564 * @return array of sdtClasses
7566 public function default_emoticons() {
7567 return array(
7568 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
7569 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
7570 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
7571 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
7572 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
7573 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
7574 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
7575 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
7576 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
7577 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
7578 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
7579 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
7580 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
7581 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
7582 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
7583 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
7584 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
7585 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
7586 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
7587 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
7588 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
7589 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
7590 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
7591 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
7592 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
7593 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
7594 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
7595 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
7596 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
7597 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
7602 * Helper method preparing the stdClass with the emoticon properties
7604 * @param string|array $text or array of strings
7605 * @param string $imagename to be used by {@see pix_emoticon}
7606 * @param string $altidentifier alternative string identifier, null for no alt
7607 * @param array $altcomponent where the alternative string is defined
7608 * @param string $imagecomponent to be used by {@see pix_emoticon}
7609 * @return stdClass
7611 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
7612 return (object)array(
7613 'text' => $text,
7614 'imagename' => $imagename,
7615 'imagecomponent' => $imagecomponent,
7616 'altidentifier' => $altidentifier,
7617 'altcomponent' => $altcomponent,
7622 /// ENCRYPTION ////////////////////////////////////////////////
7625 * rc4encrypt
7627 * Please note that in this version of moodle that the default for rc4encryption is
7628 * using the slightly more secure password key. There may be an issue when upgrading
7629 * from an older version of moodle.
7631 * @todo MDL-31836 Remove the old password key in version 2.4
7632 * Code also needs to be changed in sessionlib.php
7633 * @see get_moodle_cookie()
7634 * @see set_moodle_cookie()
7636 * @param string $data Data to encrypt.
7637 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7638 * @return string The now encrypted data.
7640 function rc4encrypt($data, $usesecurekey = true) {
7641 if (!$usesecurekey) {
7642 $passwordkey = 'nfgjeingjk';
7643 } else {
7644 $passwordkey = get_site_identifier();
7646 return endecrypt($passwordkey, $data, '');
7650 * rc4decrypt
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 decrypt.
7662 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7663 * @return string The now decrypted data.
7665 function rc4decrypt($data, $usesecurekey = true) {
7666 if (!$usesecurekey) {
7667 $passwordkey = 'nfgjeingjk';
7668 } else {
7669 $passwordkey = get_site_identifier();
7671 return endecrypt($passwordkey, $data, 'de');
7675 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
7677 * @todo Finish documenting this function
7679 * @param string $pwd The password to use when encrypting or decrypting
7680 * @param string $data The data to be decrypted/encrypted
7681 * @param string $case Either 'de' for decrypt or '' for encrypt
7682 * @return string
7684 function endecrypt ($pwd, $data, $case) {
7686 if ($case == 'de') {
7687 $data = urldecode($data);
7690 $key[] = '';
7691 $box[] = '';
7692 $temp_swap = '';
7693 $pwd_length = 0;
7695 $pwd_length = strlen($pwd);
7697 for ($i = 0; $i <= 255; $i++) {
7698 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
7699 $box[$i] = $i;
7702 $x = 0;
7704 for ($i = 0; $i <= 255; $i++) {
7705 $x = ($x + $box[$i] + $key[$i]) % 256;
7706 $temp_swap = $box[$i];
7707 $box[$i] = $box[$x];
7708 $box[$x] = $temp_swap;
7711 $temp = '';
7712 $k = '';
7714 $cipherby = '';
7715 $cipher = '';
7717 $a = 0;
7718 $j = 0;
7720 for ($i = 0; $i < strlen($data); $i++) {
7721 $a = ($a + 1) % 256;
7722 $j = ($j + $box[$a]) % 256;
7723 $temp = $box[$a];
7724 $box[$a] = $box[$j];
7725 $box[$j] = $temp;
7726 $k = $box[(($box[$a] + $box[$j]) % 256)];
7727 $cipherby = ord(substr($data, $i, 1)) ^ $k;
7728 $cipher .= chr($cipherby);
7731 if ($case == 'de') {
7732 $cipher = urldecode(urlencode($cipher));
7733 } else {
7734 $cipher = urlencode($cipher);
7737 return $cipher;
7740 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
7743 * Returns the exact absolute path to plugin directory.
7745 * @param string $plugintype type of plugin
7746 * @param string $name name of the plugin
7747 * @return string full path to plugin directory; NULL if not found
7749 function get_plugin_directory($plugintype, $name) {
7750 global $CFG;
7752 if ($plugintype === '') {
7753 $plugintype = 'mod';
7756 $types = get_plugin_types(true);
7757 if (!array_key_exists($plugintype, $types)) {
7758 return NULL;
7760 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
7762 if (!empty($CFG->themedir) and $plugintype === 'theme') {
7763 if (!is_dir($types['theme'] . '/' . $name)) {
7764 // ok, so the theme is supposed to be in the $CFG->themedir
7765 return $CFG->themedir . '/' . $name;
7769 return $types[$plugintype].'/'.$name;
7773 * Return exact absolute path to a plugin directory.
7775 * @param string $component name such as 'moodle', 'mod_forum'
7776 * @return string full path to component directory; NULL if not found
7778 function get_component_directory($component) {
7779 global $CFG;
7781 list($type, $plugin) = normalize_component($component);
7783 if ($type === 'core') {
7784 if ($plugin === NULL ) {
7785 $path = $CFG->libdir;
7786 } else {
7787 $subsystems = get_core_subsystems();
7788 if (isset($subsystems[$plugin])) {
7789 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
7790 } else {
7791 $path = NULL;
7795 } else {
7796 $path = get_plugin_directory($type, $plugin);
7799 return $path;
7803 * Normalize the component name using the "frankenstyle" names.
7804 * @param string $component
7805 * @return array $type+$plugin elements
7807 function normalize_component($component) {
7808 if ($component === 'moodle' or $component === 'core') {
7809 $type = 'core';
7810 $plugin = NULL;
7812 } else if (strpos($component, '_') === false) {
7813 $subsystems = get_core_subsystems();
7814 if (array_key_exists($component, $subsystems)) {
7815 $type = 'core';
7816 $plugin = $component;
7817 } else {
7818 // everything else is a module
7819 $type = 'mod';
7820 $plugin = $component;
7823 } else {
7824 list($type, $plugin) = explode('_', $component, 2);
7825 $plugintypes = get_plugin_types(false);
7826 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
7827 $type = 'mod';
7828 $plugin = $component;
7832 return array($type, $plugin);
7836 * List all core subsystems and their location
7838 * This is a whitelist of components that are part of the core and their
7839 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
7840 * plugin is not listed here and it does not have proper plugintype prefix,
7841 * then it is considered as course activity module.
7843 * The location is dirroot relative path. NULL means there is no special
7844 * directory for this subsystem. If the location is set, the subsystem's
7845 * renderer.php is expected to be there.
7847 * @return array of (string)name => (string|null)location
7849 function get_core_subsystems() {
7850 global $CFG;
7852 static $info = null;
7854 if (!$info) {
7855 $info = array(
7856 'access' => NULL,
7857 'admin' => $CFG->admin,
7858 'auth' => 'auth',
7859 'backup' => 'backup/util/ui',
7860 'block' => 'blocks',
7861 'blog' => 'blog',
7862 'bulkusers' => NULL,
7863 'calendar' => 'calendar',
7864 'cohort' => 'cohort',
7865 'condition' => NULL,
7866 'completion' => NULL,
7867 'countries' => NULL,
7868 'course' => 'course',
7869 'currencies' => NULL,
7870 'dbtransfer' => NULL,
7871 'debug' => NULL,
7872 'dock' => NULL,
7873 'editor' => 'lib/editor',
7874 'edufields' => NULL,
7875 'enrol' => 'enrol',
7876 'error' => NULL,
7877 'filepicker' => NULL,
7878 'files' => 'files',
7879 'filters' => NULL,
7880 'fonts' => NULL,
7881 'form' => 'lib/form',
7882 'grades' => 'grade',
7883 'grading' => 'grade/grading',
7884 'group' => 'group',
7885 'help' => NULL,
7886 'hub' => NULL,
7887 'imscc' => NULL,
7888 'install' => NULL,
7889 'iso6392' => NULL,
7890 'langconfig' => NULL,
7891 'license' => NULL,
7892 'mathslib' => NULL,
7893 'media' => 'media',
7894 'message' => 'message',
7895 'mimetypes' => NULL,
7896 'mnet' => 'mnet',
7897 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7898 'my' => 'my',
7899 'notes' => 'notes',
7900 'pagetype' => NULL,
7901 'pix' => NULL,
7902 'plagiarism' => 'plagiarism',
7903 'plugin' => NULL,
7904 'portfolio' => 'portfolio',
7905 'publish' => 'course/publish',
7906 'question' => 'question',
7907 'rating' => 'rating',
7908 'register' => 'admin/registration', //TODO: this is wrong, unfortunately we would need to modify hub code to pass around the correct url
7909 'repository' => 'repository',
7910 'rss' => 'rss',
7911 'role' => $CFG->admin.'/role',
7912 'search' => 'search',
7913 'table' => NULL,
7914 'tag' => 'tag',
7915 'timezones' => NULL,
7916 'user' => 'user',
7917 'userkey' => NULL,
7918 'webservice' => 'webservice',
7922 return $info;
7926 * Lists all plugin types
7927 * @param bool $fullpaths false means relative paths from dirroot
7928 * @return array Array of strings - name=>location
7930 function get_plugin_types($fullpaths=true) {
7931 global $CFG;
7933 static $info = null;
7934 static $fullinfo = null;
7936 if (!$info) {
7937 $info = array('qtype' => 'question/type',
7938 'mod' => 'mod',
7939 'auth' => 'auth',
7940 'enrol' => 'enrol',
7941 'message' => 'message/output',
7942 'block' => 'blocks',
7943 'filter' => 'filter',
7944 'editor' => 'lib/editor',
7945 'format' => 'course/format',
7946 'profilefield' => 'user/profile/field',
7947 'report' => 'report',
7948 'coursereport' => 'course/report', // must be after system reports
7949 'gradeexport' => 'grade/export',
7950 'gradeimport' => 'grade/import',
7951 'gradereport' => 'grade/report',
7952 'gradingform' => 'grade/grading/form',
7953 'mnetservice' => 'mnet/service',
7954 'webservice' => 'webservice',
7955 'repository' => 'repository',
7956 'portfolio' => 'portfolio',
7957 'qbehaviour' => 'question/behaviour',
7958 'qformat' => 'question/format',
7959 'plagiarism' => 'plagiarism',
7960 'tool' => $CFG->admin.'/tool',
7961 'theme' => 'theme', // this is a bit hacky, themes may be in $CFG->themedir too
7964 $mods = get_plugin_list('mod');
7965 foreach ($mods as $mod => $moddir) {
7966 if (file_exists("$moddir/db/subplugins.php")) {
7967 $subplugins = array();
7968 include("$moddir/db/subplugins.php");
7969 foreach ($subplugins as $subtype=>$dir) {
7970 $info[$subtype] = $dir;
7975 // local is always last!
7976 $info['local'] = 'local';
7978 $fullinfo = array();
7979 foreach ($info as $type => $dir) {
7980 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
7984 return ($fullpaths ? $fullinfo : $info);
7988 * Simplified version of get_list_of_plugins()
7989 * @param string $plugintype type of plugin
7990 * @return array name=>fulllocation pairs of plugins of given type
7992 function get_plugin_list($plugintype) {
7993 global $CFG;
7995 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
7996 if ($plugintype == 'auth') {
7997 // Historically we have had an auth plugin called 'db', so allow a special case.
7998 $key = array_search('db', $ignored);
7999 if ($key !== false) {
8000 unset($ignored[$key]);
8004 if ($plugintype === '') {
8005 $plugintype = 'mod';
8008 $fulldirs = array();
8010 if ($plugintype === 'mod') {
8011 // mod is an exception because we have to call this function from get_plugin_types()
8012 $fulldirs[] = $CFG->dirroot.'/mod';
8014 } else if ($plugintype === 'theme') {
8015 $fulldirs[] = $CFG->dirroot.'/theme';
8016 // themes are special because they may be stored also in separate directory
8017 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
8018 $fulldirs[] = $CFG->themedir;
8021 } else {
8022 $types = get_plugin_types(true);
8023 if (!array_key_exists($plugintype, $types)) {
8024 return array();
8026 $fulldir = $types[$plugintype];
8027 if (!file_exists($fulldir)) {
8028 return array();
8030 $fulldirs[] = $fulldir;
8033 $result = array();
8035 foreach ($fulldirs as $fulldir) {
8036 if (!is_dir($fulldir)) {
8037 continue;
8039 $items = new DirectoryIterator($fulldir);
8040 foreach ($items as $item) {
8041 if ($item->isDot() or !$item->isDir()) {
8042 continue;
8044 $pluginname = $item->getFilename();
8045 if (in_array($pluginname, $ignored)) {
8046 continue;
8048 $pluginname = clean_param($pluginname, PARAM_PLUGIN);
8049 if (empty($pluginname)) {
8050 // better ignore plugins with problematic names here
8051 continue;
8053 $result[$pluginname] = $fulldir.'/'.$pluginname;
8054 unset($item);
8056 unset($items);
8059 //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!
8060 ksort($result);
8061 return $result;
8065 * Get a list of all the plugins of a given type that contain a particular file.
8066 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8067 * @param string $file the name of file that must be present in the plugin.
8068 * (e.g. 'view.php', 'db/install.xml').
8069 * @param bool $include if true (default false), the file will be include_once-ed if found.
8070 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
8071 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
8073 function get_plugin_list_with_file($plugintype, $file, $include = false) {
8074 global $CFG; // Necessary in case it is referenced by include()d PHP scripts.
8076 $plugins = array();
8078 foreach(get_plugin_list($plugintype) as $plugin => $dir) {
8079 $path = $dir . '/' . $file;
8080 if (file_exists($path)) {
8081 if ($include) {
8082 include_once($path);
8084 $plugins[$plugin] = $path;
8088 return $plugins;
8092 * Get a list of all the plugins of a given type that define a certain API function
8093 * in a certain file. The plugin component names and function names are returned.
8095 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8096 * @param string $function the part of the name of the function after the
8097 * frankenstyle prefix. e.g 'hook' if you are looking for functions with
8098 * names like report_courselist_hook.
8099 * @param string $file the name of file within the plugin that defines the
8100 * function. Defaults to lib.php.
8101 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8102 * and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
8104 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
8105 $pluginfunctions = array();
8106 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8107 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
8109 if (function_exists($fullfunction)) {
8110 // Function exists with standard name. Store, indexed by
8111 // frankenstyle name of plugin
8112 $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
8114 } else if ($plugintype === 'mod') {
8115 // For modules, we also allow plugin without full frankenstyle
8116 // but just starting with the module name
8117 $shortfunction = $plugin . '_' . $function;
8118 if (function_exists($shortfunction)) {
8119 $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
8123 return $pluginfunctions;
8127 * Get a list of all the plugins of a given type that define a certain class
8128 * in a certain file. The plugin component names and class names are returned.
8130 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8131 * @param string $class the part of the name of the class after the
8132 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
8133 * names like report_courselist_thing. If you are looking for classes with
8134 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
8135 * @param string $file the name of file within the plugin that defines the class.
8136 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8137 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
8139 function get_plugin_list_with_class($plugintype, $class, $file) {
8140 if ($class) {
8141 $suffix = '_' . $class;
8142 } else {
8143 $suffix = '';
8146 $pluginclasses = array();
8147 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8148 $classname = $plugintype . '_' . $plugin . $suffix;
8149 if (class_exists($classname)) {
8150 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
8154 return $pluginclasses;
8158 * Lists plugin-like directories within specified directory
8160 * This function was originally used for standard Moodle plugins, please use
8161 * new get_plugin_list() now.
8163 * This function is used for general directory listing and backwards compatility.
8165 * @param string $directory relative directory from root
8166 * @param string $exclude dir name to exclude from the list (defaults to none)
8167 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
8168 * @return array Sorted array of directory names found under the requested parameters
8170 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
8171 global $CFG;
8173 $plugins = array();
8175 if (empty($basedir)) {
8176 $basedir = $CFG->dirroot .'/'. $directory;
8178 } else {
8179 $basedir = $basedir .'/'. $directory;
8182 if (file_exists($basedir) && filetype($basedir) == 'dir') {
8183 if (!$dirhandle = opendir($basedir)) {
8184 debugging("Directory permission error for plugin ({$directory}). Directory exists but cannot be read.", DEBUG_DEVELOPER);
8185 return array();
8187 while (false !== ($dir = readdir($dirhandle))) {
8188 $firstchar = substr($dir, 0, 1);
8189 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
8190 continue;
8192 if (filetype($basedir .'/'. $dir) != 'dir') {
8193 continue;
8195 $plugins[] = $dir;
8197 closedir($dirhandle);
8199 if ($plugins) {
8200 asort($plugins);
8202 return $plugins;
8206 * Invoke plugin's callback functions
8208 * @param string $type plugin type e.g. 'mod'
8209 * @param string $name plugin name
8210 * @param string $feature feature name
8211 * @param string $action feature's action
8212 * @param array $params parameters of callback function, should be an array
8213 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8214 * @return mixed
8216 * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
8218 function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
8219 return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
8223 * Invoke component's callback functions
8225 * @param string $component frankenstyle component name, e.g. 'mod_quiz'
8226 * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
8227 * @param array $params parameters of callback function
8228 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8229 * @return mixed
8231 function component_callback($component, $function, array $params = array(), $default = null) {
8232 global $CFG; // this is needed for require_once() below
8234 $cleancomponent = clean_param($component, PARAM_COMPONENT);
8235 if (empty($cleancomponent)) {
8236 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8238 $component = $cleancomponent;
8240 list($type, $name) = normalize_component($component);
8241 $component = $type . '_' . $name;
8243 $oldfunction = $name.'_'.$function;
8244 $function = $component.'_'.$function;
8246 $dir = get_component_directory($component);
8247 if (empty($dir)) {
8248 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8251 // Load library and look for function
8252 if (file_exists($dir.'/lib.php')) {
8253 require_once($dir.'/lib.php');
8256 if (!function_exists($function) and function_exists($oldfunction)) {
8257 if ($type !== 'mod' and $type !== 'core') {
8258 debugging("Please use new function name $function instead of legacy $oldfunction");
8260 $function = $oldfunction;
8263 if (function_exists($function)) {
8264 // Function exists, so just return function result
8265 $ret = call_user_func_array($function, $params);
8266 if (is_null($ret)) {
8267 return $default;
8268 } else {
8269 return $ret;
8272 return $default;
8276 * Checks whether a plugin supports a specified feature.
8278 * @param string $type Plugin type e.g. 'mod'
8279 * @param string $name Plugin name e.g. 'forum'
8280 * @param string $feature Feature code (FEATURE_xx constant)
8281 * @param mixed $default default value if feature support unknown
8282 * @return mixed Feature result (false if not supported, null if feature is unknown,
8283 * otherwise usually true but may have other feature-specific value such as array)
8285 function plugin_supports($type, $name, $feature, $default = NULL) {
8286 global $CFG;
8288 if ($type === 'mod' and $name === 'NEWMODULE') {
8289 //somebody forgot to rename the module template
8290 return false;
8293 $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
8294 if (empty($component)) {
8295 throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
8298 $function = null;
8300 if ($type === 'mod') {
8301 // we need this special case because we support subplugins in modules,
8302 // otherwise it would end up in infinite loop
8303 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
8304 include_once("$CFG->dirroot/mod/$name/lib.php");
8305 $function = $component.'_supports';
8306 if (!function_exists($function)) {
8307 // legacy non-frankenstyle function name
8308 $function = $name.'_supports';
8310 } else {
8311 // invalid module
8314 } else {
8315 if (!$path = get_plugin_directory($type, $name)) {
8316 // non existent plugin type
8317 return false;
8319 if (file_exists("$path/lib.php")) {
8320 include_once("$path/lib.php");
8321 $function = $component.'_supports';
8325 if ($function and function_exists($function)) {
8326 $supports = $function($feature);
8327 if (is_null($supports)) {
8328 // plugin does not know - use default
8329 return $default;
8330 } else {
8331 return $supports;
8335 //plugin does not care, so use default
8336 return $default;
8340 * Returns true if the current version of PHP is greater that the specified one.
8342 * @todo Check PHP version being required here is it too low?
8344 * @param string $version The version of php being tested.
8345 * @return bool
8347 function check_php_version($version='5.2.4') {
8348 return (version_compare(phpversion(), $version) >= 0);
8352 * Checks to see if is the browser operating system matches the specified
8353 * brand.
8355 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
8357 * @uses $_SERVER
8358 * @param string $brand The operating system identifier being tested
8359 * @return bool true if the given brand below to the detected operating system
8361 function check_browser_operating_system($brand) {
8362 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8363 return false;
8366 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
8367 return true;
8370 return false;
8374 * Checks to see if is a browser matches the specified
8375 * brand and is equal or better version.
8377 * @uses $_SERVER
8378 * @param string $brand The browser identifier being tested
8379 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
8380 * @return bool true if the given version is below that of the detected browser
8382 function check_browser_version($brand, $version = null) {
8383 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8384 return false;
8387 $agent = $_SERVER['HTTP_USER_AGENT'];
8389 switch ($brand) {
8391 case 'Camino': /// OSX browser using Gecke engine
8392 if (strpos($agent, 'Camino') === false) {
8393 return false;
8395 if (empty($version)) {
8396 return true; // no version specified
8398 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
8399 if (version_compare($match[1], $version) >= 0) {
8400 return true;
8403 break;
8406 case 'Firefox': /// Mozilla Firefox browsers
8407 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
8408 return false;
8410 if (empty($version)) {
8411 return true; // no version specified
8413 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8414 if (version_compare($match[2], $version) >= 0) {
8415 return true;
8418 break;
8421 case 'Gecko': /// Gecko based browsers
8422 // Do not look for dates any more, we expect real Firefox version here.
8423 if (empty($version)) {
8424 $version = 1;
8425 } else if ($version > 20000000) {
8426 // This is just a guess, it is not supposed to be 100% accurate!
8427 if (preg_match('/^201/', $version)) {
8428 $version = 3.6;
8429 } else if (preg_match('/^200[7-9]/', $version)) {
8430 $version = 3;
8431 } else if (preg_match('/^2006/', $version)) {
8432 $version = 2;
8433 } else {
8434 $version = 1.5;
8437 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8438 // Use real Firefox version if specified in user agent string.
8439 if (version_compare($match[2], $version) >= 0) {
8440 return true;
8442 } else if (preg_match("/Gecko\/([0-9\.]+)/i", $agent, $match)) {
8443 // Gecko might contain date or Firefox revision, let's just guess the Firefox version from the date.
8444 $browserver = $match[1];
8445 if ($browserver > 20000000) {
8446 // This is just a guess, it is not supposed to be 100% accurate!
8447 if (preg_match('/^201/', $browserver)) {
8448 $browserver = 3.6;
8449 } else if (preg_match('/^200[7-9]/', $browserver)) {
8450 $browserver = 3;
8451 } else if (preg_match('/^2006/', $version)) {
8452 $browserver = 2;
8453 } else {
8454 $browserver = 1.5;
8457 if (version_compare($browserver, $version) >= 0) {
8458 return true;
8461 break;
8464 case 'MSIE': /// Internet Explorer
8465 if (strpos($agent, 'Opera') !== false) { // Reject Opera
8466 return false;
8468 // in case of IE we have to deal with BC of the version parameter
8469 if (is_null($version)) {
8470 $version = 5.5; // anything older is not considered a browser at all!
8473 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
8474 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
8475 if (version_compare($match[1], $version) >= 0) {
8476 return true;
8479 break;
8482 case 'Opera': /// Opera
8483 if (strpos($agent, 'Opera') === false) {
8484 return false;
8486 if (empty($version)) {
8487 return true; // no version specified
8489 // Recent Opera useragents have Version/ with the actual version, e.g.:
8490 // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
8491 // That's Opera 12.01, not 9.8.
8492 if (preg_match("/Version\/([0-9\.]+)/i", $agent, $match)) {
8493 if (version_compare($match[1], $version) >= 0) {
8494 return true;
8496 } else if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
8497 if (version_compare($match[1], $version) >= 0) {
8498 return true;
8501 break;
8504 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
8505 if (strpos($agent, 'AppleWebKit') === false) {
8506 return false;
8508 if (empty($version)) {
8509 return true; // no version specified
8511 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8512 if (version_compare($match[1], $version) >= 0) {
8513 return true;
8516 break;
8519 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
8520 if (strpos($agent, 'AppleWebKit') === false) {
8521 return false;
8523 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
8524 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
8525 return false;
8527 if (strpos($agent, 'Shiira')) { // Reject Shiira
8528 return false;
8530 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
8531 return false;
8533 if (strpos($agent, 'Android')) { // Reject Androids too
8534 return false;
8536 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
8537 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
8538 return false;
8540 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
8541 return false;
8544 if (empty($version)) {
8545 return true; // no version specified
8547 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8548 if (version_compare($match[1], $version) >= 0) {
8549 return true;
8552 break;
8555 case 'Chrome':
8556 if (strpos($agent, 'Chrome') === false) {
8557 return false;
8559 if (empty($version)) {
8560 return true; // no version specified
8562 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
8563 if (version_compare($match[1], $version) >= 0) {
8564 return true;
8567 break;
8570 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
8571 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
8572 return false;
8574 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
8575 return false;
8577 if (empty($version)) {
8578 return true; // no version specified
8580 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8581 if (version_compare($match[1], $version) >= 0) {
8582 return true;
8585 break;
8588 case 'WebKit Android': /// WebKit browser on Android
8589 if (strpos($agent, 'Linux; U; Android') === false) {
8590 return false;
8592 if (empty($version)) {
8593 return true; // no version specified
8595 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8596 if (version_compare($match[1], $version) >= 0) {
8597 return true;
8600 break;
8604 return false;
8608 * Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
8609 * an optional admin specified regular expression. If enabledevicedetection is set to no or not set
8610 * it returns default
8612 * @return string device type
8614 function get_device_type() {
8615 global $CFG;
8617 if (empty($CFG->enabledevicedetection) || empty($_SERVER['HTTP_USER_AGENT'])) {
8618 return 'default';
8621 $useragent = $_SERVER['HTTP_USER_AGENT'];
8623 if (!empty($CFG->devicedetectregex)) {
8624 $regexes = json_decode($CFG->devicedetectregex);
8626 foreach ($regexes as $value=>$regex) {
8627 if (preg_match($regex, $useragent)) {
8628 return $value;
8633 //mobile detection PHP direct copy from open source detectmobilebrowser.com
8634 $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';
8635 $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';
8636 if (preg_match($phonesregex,$useragent) || preg_match($modelsregex,substr($useragent, 0, 4))){
8637 return 'mobile';
8640 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
8641 if (preg_match($tabletregex, $useragent)) {
8642 return 'tablet';
8645 // Safe way to check for IE6 and not get false positives for some IE 7/8 users
8646 if (substr($_SERVER['HTTP_USER_AGENT'], 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
8647 return 'legacy';
8650 return 'default';
8654 * Returns a list of the device types supporting by Moodle
8656 * @param boolean $incusertypes includes types specified using the devicedetectregex admin setting
8657 * @return array $types
8659 function get_device_type_list($incusertypes = true) {
8660 global $CFG;
8662 $types = array('default', 'legacy', 'mobile', 'tablet');
8664 if ($incusertypes && !empty($CFG->devicedetectregex)) {
8665 $regexes = json_decode($CFG->devicedetectregex);
8667 foreach ($regexes as $value => $regex) {
8668 $types[] = $value;
8672 return $types;
8676 * Returns the theme selected for a particular device or false if none selected.
8678 * @param string $devicetype
8679 * @return string|false The name of the theme to use for the device or the false if not set
8681 function get_selected_theme_for_device_type($devicetype = null) {
8682 global $CFG;
8684 if (empty($devicetype)) {
8685 $devicetype = get_user_device_type();
8688 $themevarname = get_device_cfg_var_name($devicetype);
8689 if (empty($CFG->$themevarname)) {
8690 return false;
8693 return $CFG->$themevarname;
8697 * Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
8699 * @param string $devicetype
8700 * @return string The config variable to use to determine the theme
8702 function get_device_cfg_var_name($devicetype = null) {
8703 if ($devicetype == 'default' || empty($devicetype)) {
8704 return 'theme';
8707 return 'theme' . $devicetype;
8711 * Allows the user to switch the device they are seeing the theme for.
8712 * This allows mobile users to switch back to the default theme, or theme for any other device.
8714 * @param string $newdevice The device the user is currently using.
8715 * @return string The device the user has switched to
8717 function set_user_device_type($newdevice) {
8718 global $USER;
8720 $devicetype = get_device_type();
8721 $devicetypes = get_device_type_list();
8723 if ($newdevice == $devicetype) {
8724 unset_user_preference('switchdevice'.$devicetype);
8725 } else if (in_array($newdevice, $devicetypes)) {
8726 set_user_preference('switchdevice'.$devicetype, $newdevice);
8731 * Returns the device the user is currently using, or if the user has chosen to switch devices
8732 * for the current device type the type they have switched to.
8734 * @return string The device the user is currently using or wishes to use
8736 function get_user_device_type() {
8737 $device = get_device_type();
8738 $switched = get_user_preferences('switchdevice'.$device, false);
8739 if ($switched != false) {
8740 return $switched;
8742 return $device;
8746 * Returns one or several CSS class names that match the user's browser. These can be put
8747 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
8749 * @return array An array of browser version classes
8751 function get_browser_version_classes() {
8752 $classes = array();
8754 if (check_browser_version("MSIE", "0")) {
8755 $classes[] = 'ie';
8756 if (check_browser_version("MSIE", 9)) {
8757 $classes[] = 'ie9';
8758 } else if (check_browser_version("MSIE", 8)) {
8759 $classes[] = 'ie8';
8760 } elseif (check_browser_version("MSIE", 7)) {
8761 $classes[] = 'ie7';
8762 } elseif (check_browser_version("MSIE", 6)) {
8763 $classes[] = 'ie6';
8766 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
8767 $classes[] = 'gecko';
8768 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
8769 $classes[] = "gecko{$matches[1]}{$matches[2]}";
8772 } else if (check_browser_version("WebKit")) {
8773 $classes[] = 'safari';
8774 if (check_browser_version("Safari iOS")) {
8775 $classes[] = 'ios';
8777 } else if (check_browser_version("WebKit Android")) {
8778 $classes[] = 'android';
8781 } else if (check_browser_version("Opera")) {
8782 $classes[] = 'opera';
8786 return $classes;
8790 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
8792 * @return bool True for yes, false for no
8794 function can_use_rotated_text() {
8795 global $USER;
8796 return (check_browser_version('MSIE', 9) || check_browser_version('Firefox', 2) ||
8797 check_browser_version('Chrome', 21) || check_browser_version('Safari', 536.25) ||
8798 check_browser_version('Opera', 12) || check_browser_version('Safari iOS', 533)) &&
8799 !$USER->screenreader;
8803 * Hack to find out the GD version by parsing phpinfo output
8805 * @return int GD version (1, 2, or 0)
8807 function check_gd_version() {
8808 $gdversion = 0;
8810 if (function_exists('gd_info')){
8811 $gd_info = gd_info();
8812 if (substr_count($gd_info['GD Version'], '2.')) {
8813 $gdversion = 2;
8814 } else if (substr_count($gd_info['GD Version'], '1.')) {
8815 $gdversion = 1;
8818 } else {
8819 ob_start();
8820 phpinfo(INFO_MODULES);
8821 $phpinfo = ob_get_contents();
8822 ob_end_clean();
8824 $phpinfo = explode("\n", $phpinfo);
8827 foreach ($phpinfo as $text) {
8828 $parts = explode('</td>', $text);
8829 foreach ($parts as $key => $val) {
8830 $parts[$key] = trim(strip_tags($val));
8832 if ($parts[0] == 'GD Version') {
8833 if (substr_count($parts[1], '2.0')) {
8834 $parts[1] = '2.0';
8836 $gdversion = intval($parts[1]);
8841 return $gdversion; // 1, 2 or 0
8845 * Determine if moodle installation requires update
8847 * Checks version numbers of main code and all modules to see
8848 * if there are any mismatches
8850 * @global moodle_database $DB
8851 * @return bool
8853 function moodle_needs_upgrading() {
8854 global $CFG, $DB, $OUTPUT;
8856 if (empty($CFG->version)) {
8857 return true;
8860 // main versio nfirst
8861 $version = null;
8862 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
8863 if ($version > $CFG->version) {
8864 return true;
8867 // modules
8868 $mods = get_plugin_list('mod');
8869 $installed = $DB->get_records('modules', array(), '', 'name, version');
8870 foreach ($mods as $mod => $fullmod) {
8871 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
8872 continue;
8874 $module = new stdClass();
8875 if (!is_readable($fullmod.'/version.php')) {
8876 continue;
8878 include($fullmod.'/version.php'); // defines $module with version etc
8879 if (empty($installed[$mod])) {
8880 return true;
8881 } else if ($module->version > $installed[$mod]->version) {
8882 return true;
8885 unset($installed);
8887 // blocks
8888 $blocks = get_plugin_list('block');
8889 $installed = $DB->get_records('block', array(), '', 'name, version');
8890 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
8891 foreach ($blocks as $blockname=>$fullblock) {
8892 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
8893 continue;
8895 if (!is_readable($fullblock.'/version.php')) {
8896 continue;
8898 $plugin = new stdClass();
8899 $plugin->version = NULL;
8900 include($fullblock.'/version.php');
8901 if (empty($installed[$blockname])) {
8902 return true;
8903 } else if ($plugin->version > $installed[$blockname]->version) {
8904 return true;
8907 unset($installed);
8909 // now the rest of plugins
8910 $plugintypes = get_plugin_types();
8911 unset($plugintypes['mod']);
8912 unset($plugintypes['block']);
8914 $versions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin, value');
8915 foreach ($plugintypes as $type=>$unused) {
8916 $plugs = get_plugin_list($type);
8917 foreach ($plugs as $plug=>$fullplug) {
8918 $component = $type.'_'.$plug;
8919 if (!is_readable($fullplug.'/version.php')) {
8920 continue;
8922 $plugin = new stdClass();
8923 include($fullplug.'/version.php'); // defines $plugin with version etc
8924 if (array_key_exists($component, $versions)) {
8925 $installedversion = $versions[$component];
8926 } else {
8927 $installedversion = get_config($component, 'version');
8929 if (empty($installedversion)) { // new installation
8930 return true;
8931 } else if ($installedversion < $plugin->version) { // upgrade
8932 return true;
8937 return false;
8941 * Returns the major version of this site
8943 * Moodle version numbers consist of three numbers separated by a dot, for
8944 * example 1.9.11 or 2.0.2. The first two numbers, like 1.9 or 2.0, represent so
8945 * called major version. This function extracts the major version from either
8946 * $CFG->release (default) or eventually from the $release variable defined in
8947 * the main version.php.
8949 * @param bool $fromdisk should the version if source code files be used
8950 * @return string|false the major version like '2.3', false if could not be determined
8952 function moodle_major_version($fromdisk = false) {
8953 global $CFG;
8955 if ($fromdisk) {
8956 $release = null;
8957 require($CFG->dirroot.'/version.php');
8958 if (empty($release)) {
8959 return false;
8962 } else {
8963 if (empty($CFG->release)) {
8964 return false;
8966 $release = $CFG->release;
8969 if (preg_match('/^[0-9]+\.[0-9]+/', $release, $matches)) {
8970 return $matches[0];
8971 } else {
8972 return false;
8976 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
8979 * Sets the system locale
8981 * @category string
8982 * @param string $locale Can be used to force a locale
8984 function moodle_setlocale($locale='') {
8985 global $CFG;
8987 static $currentlocale = ''; // last locale caching
8989 $oldlocale = $currentlocale;
8991 /// Fetch the correct locale based on ostype
8992 if ($CFG->ostype == 'WINDOWS') {
8993 $stringtofetch = 'localewin';
8994 } else {
8995 $stringtofetch = 'locale';
8998 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
8999 if (!empty($locale)) {
9000 $currentlocale = $locale;
9001 } else if (!empty($CFG->locale)) { // override locale for all language packs
9002 $currentlocale = $CFG->locale;
9003 } else {
9004 $currentlocale = get_string($stringtofetch, 'langconfig');
9007 /// do nothing if locale already set up
9008 if ($oldlocale == $currentlocale) {
9009 return;
9012 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
9013 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
9014 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
9016 /// Get current values
9017 $monetary= setlocale (LC_MONETARY, 0);
9018 $numeric = setlocale (LC_NUMERIC, 0);
9019 $ctype = setlocale (LC_CTYPE, 0);
9020 if ($CFG->ostype != 'WINDOWS') {
9021 $messages= setlocale (LC_MESSAGES, 0);
9023 /// Set locale to all
9024 setlocale (LC_ALL, $currentlocale);
9025 /// Set old values
9026 setlocale (LC_MONETARY, $monetary);
9027 setlocale (LC_NUMERIC, $numeric);
9028 if ($CFG->ostype != 'WINDOWS') {
9029 setlocale (LC_MESSAGES, $messages);
9031 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
9032 setlocale (LC_CTYPE, $ctype);
9037 * Count words in a string.
9039 * Words are defined as things between whitespace.
9041 * @category string
9042 * @param string $string The text to be searched for words.
9043 * @return int The count of words in the specified string
9045 function count_words($string) {
9046 $string = strip_tags($string);
9047 return count(preg_split("/\w\b/", $string)) - 1;
9050 /** Count letters in a string.
9052 * Letters are defined as chars not in tags and different from whitespace.
9054 * @category string
9055 * @param string $string The text to be searched for letters.
9056 * @return int The count of letters in the specified text.
9058 function count_letters($string) {
9059 /// Loading the textlib singleton instance. We are going to need it.
9060 $string = strip_tags($string); // Tags are out now
9061 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
9063 return textlib::strlen($string);
9067 * Generate and return a random string of the specified length.
9069 * @param int $length The length of the string to be created.
9070 * @return string
9072 function random_string ($length=15) {
9073 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
9074 $pool .= 'abcdefghijklmnopqrstuvwxyz';
9075 $pool .= '0123456789';
9076 $poollen = strlen($pool);
9077 mt_srand ((double) microtime() * 1000000);
9078 $string = '';
9079 for ($i = 0; $i < $length; $i++) {
9080 $string .= substr($pool, (mt_rand()%($poollen)), 1);
9082 return $string;
9086 * Generate a complex random string (useful for md5 salts)
9088 * This function is based on the above {@link random_string()} however it uses a
9089 * larger pool of characters and generates a string between 24 and 32 characters
9091 * @param int $length Optional if set generates a string to exactly this length
9092 * @return string
9094 function complex_random_string($length=null) {
9095 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
9096 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
9097 $poollen = strlen($pool);
9098 mt_srand ((double) microtime() * 1000000);
9099 if ($length===null) {
9100 $length = floor(rand(24,32));
9102 $string = '';
9103 for ($i = 0; $i < $length; $i++) {
9104 $string .= $pool[(mt_rand()%$poollen)];
9106 return $string;
9110 * Given some text (which may contain HTML) and an ideal length,
9111 * this function truncates the text neatly on a word boundary if possible
9113 * @category string
9114 * @global stdClass $CFG
9115 * @param string $text text to be shortened
9116 * @param int $ideal ideal string length
9117 * @param boolean $exact if false, $text will not be cut mid-word
9118 * @param string $ending The string to append if the passed string is truncated
9119 * @return string $truncate shortened string
9121 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
9123 global $CFG;
9125 // if the plain text is shorter than the maximum length, return the whole text
9126 if (textlib::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
9127 return $text;
9130 // Splits on HTML tags. Each open/close/empty tag will be the first thing
9131 // and only tag in its 'line'
9132 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
9134 $total_length = textlib::strlen($ending);
9135 $truncate = '';
9137 // This array stores information about open and close tags and their position
9138 // in the truncated string. Each item in the array is an object with fields
9139 // ->open (true if open), ->tag (tag name in lower case), and ->pos
9140 // (byte position in truncated text)
9141 $tagdetails = array();
9143 foreach ($lines as $line_matchings) {
9144 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
9145 if (!empty($line_matchings[1])) {
9146 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
9147 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
9148 // do nothing
9149 // if tag is a closing tag (f.e. </b>)
9150 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
9151 // record closing tag
9152 $tagdetails[] = (object)array('open'=>false,
9153 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
9154 // if tag is an opening tag (f.e. <b>)
9155 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
9156 // record opening tag
9157 $tagdetails[] = (object)array('open'=>true,
9158 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
9160 // add html-tag to $truncate'd text
9161 $truncate .= $line_matchings[1];
9164 // calculate the length of the plain text part of the line; handle entities as one character
9165 $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]));
9166 if ($total_length+$content_length > $ideal) {
9167 // the number of characters which are left
9168 $left = $ideal - $total_length;
9169 $entities_length = 0;
9170 // search for html entities
9171 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)) {
9172 // calculate the real length of all entities in the legal range
9173 foreach ($entities[0] as $entity) {
9174 if ($entity[1]+1-$entities_length <= $left) {
9175 $left--;
9176 $entities_length += textlib::strlen($entity[0]);
9177 } else {
9178 // no more characters left
9179 break;
9183 $truncate .= textlib::substr($line_matchings[2], 0, $left+$entities_length);
9184 // maximum length is reached, so get off the loop
9185 break;
9186 } else {
9187 $truncate .= $line_matchings[2];
9188 $total_length += $content_length;
9191 // if the maximum length is reached, get off the loop
9192 if($total_length >= $ideal) {
9193 break;
9197 // if the words shouldn't be cut in the middle...
9198 if (!$exact) {
9199 // ...search the last occurence of a space...
9200 for ($k=textlib::strlen($truncate);$k>0;$k--) {
9201 if ($char = textlib::substr($truncate, $k, 1)) {
9202 if ($char === '.' or $char === ' ') {
9203 $breakpos = $k+1;
9204 break;
9205 } else if (strlen($char) > 2) { // Chinese/Japanese/Korean text
9206 $breakpos = $k+1; // can be truncated at any UTF-8
9207 break; // character boundary.
9212 if (isset($breakpos)) {
9213 // ...and cut the text in this position
9214 $truncate = textlib::substr($truncate, 0, $breakpos);
9218 // add the defined ending to the text
9219 $truncate .= $ending;
9221 // Now calculate the list of open html tags based on the truncate position
9222 $open_tags = array();
9223 foreach ($tagdetails as $taginfo) {
9224 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
9225 // Don't include tags after we made the break!
9226 break;
9228 if($taginfo->open) {
9229 // add tag to the beginning of $open_tags list
9230 array_unshift($open_tags, $taginfo->tag);
9231 } else {
9232 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
9233 if ($pos !== false) {
9234 unset($open_tags[$pos]);
9239 // close all unclosed html-tags
9240 foreach ($open_tags as $tag) {
9241 $truncate .= '</' . $tag . '>';
9244 return $truncate;
9249 * Given dates in seconds, how many weeks is the date from startdate
9250 * The first week is 1, the second 2 etc ...
9252 * @todo Finish documenting this function
9254 * @uses WEEKSECS
9255 * @param int $startdate Timestamp for the start date
9256 * @param int $thedate Timestamp for the end date
9257 * @return string
9259 function getweek ($startdate, $thedate) {
9260 if ($thedate < $startdate) { // error
9261 return 0;
9264 return floor(($thedate - $startdate) / WEEKSECS) + 1;
9268 * returns a randomly generated password of length $maxlen. inspired by
9270 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
9271 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
9273 * @global stdClass $CFG
9274 * @param int $maxlen The maximum size of the password being generated.
9275 * @return string
9277 function generate_password($maxlen=10) {
9278 global $CFG;
9280 if (empty($CFG->passwordpolicy)) {
9281 $fillers = PASSWORD_DIGITS;
9282 $wordlist = file($CFG->wordlist);
9283 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9284 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9285 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
9286 $password = $word1 . $filler1 . $word2;
9287 } else {
9288 $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
9289 $digits = $CFG->minpassworddigits;
9290 $lower = $CFG->minpasswordlower;
9291 $upper = $CFG->minpasswordupper;
9292 $nonalphanum = $CFG->minpasswordnonalphanum;
9293 $total = $lower + $upper + $digits + $nonalphanum;
9294 // minlength should be the greater one of the two ( $minlen and $total )
9295 $minlen = $minlen < $total ? $total : $minlen;
9296 // maxlen can never be smaller than minlen
9297 $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
9298 $additional = $maxlen - $total;
9300 // Make sure we have enough characters to fulfill
9301 // complexity requirements
9302 $passworddigits = PASSWORD_DIGITS;
9303 while ($digits > strlen($passworddigits)) {
9304 $passworddigits .= PASSWORD_DIGITS;
9306 $passwordlower = PASSWORD_LOWER;
9307 while ($lower > strlen($passwordlower)) {
9308 $passwordlower .= PASSWORD_LOWER;
9310 $passwordupper = PASSWORD_UPPER;
9311 while ($upper > strlen($passwordupper)) {
9312 $passwordupper .= PASSWORD_UPPER;
9314 $passwordnonalphanum = PASSWORD_NONALPHANUM;
9315 while ($nonalphanum > strlen($passwordnonalphanum)) {
9316 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
9319 // Now mix and shuffle it all
9320 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
9321 substr(str_shuffle ($passwordupper), 0, $upper) .
9322 substr(str_shuffle ($passworddigits), 0, $digits) .
9323 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
9324 substr(str_shuffle ($passwordlower .
9325 $passwordupper .
9326 $passworddigits .
9327 $passwordnonalphanum), 0 , $additional));
9330 return substr ($password, 0, $maxlen);
9334 * Given a float, prints it nicely.
9335 * Localized floats must not be used in calculations!
9337 * The stripzeros feature is intended for making numbers look nicer in small
9338 * areas where it is not necessary to indicate the degree of accuracy by showing
9339 * ending zeros. If you turn it on with $decimalpoints set to 3, for example,
9340 * then it will display '5.4' instead of '5.400' or '5' instead of '5.000'.
9342 * @param float $float The float to print
9343 * @param int $decimalpoints The number of decimal places to print.
9344 * @param bool $localized use localized decimal separator
9345 * @param bool $stripzeros If true, removes final zeros after decimal point
9346 * @return string locale float
9348 function format_float($float, $decimalpoints=1, $localized=true, $stripzeros=false) {
9349 if (is_null($float)) {
9350 return '';
9352 if ($localized) {
9353 $separator = get_string('decsep', 'langconfig');
9354 } else {
9355 $separator = '.';
9357 $result = number_format($float, $decimalpoints, $separator, '');
9358 if ($stripzeros) {
9359 // Remove zeros and final dot if not needed
9360 $result = preg_replace('~(' . preg_quote($separator) . ')?0+$~', '', $result);
9362 return $result;
9366 * Converts locale specific floating point/comma number back to standard PHP float value
9367 * Do NOT try to do any math operations before this conversion on any user submitted floats!
9369 * @param string $locale_float locale aware float representation
9370 * @return float
9372 function unformat_float($locale_float) {
9373 $locale_float = trim($locale_float);
9375 if ($locale_float == '') {
9376 return null;
9379 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
9381 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
9385 * Given a simple array, this shuffles it up just like shuffle()
9386 * Unlike PHP's shuffle() this function works on any machine.
9388 * @param array $array The array to be rearranged
9389 * @return array
9391 function swapshuffle($array) {
9393 srand ((double) microtime() * 10000000);
9394 $last = count($array) - 1;
9395 for ($i=0;$i<=$last;$i++) {
9396 $from = rand(0,$last);
9397 $curr = $array[$i];
9398 $array[$i] = $array[$from];
9399 $array[$from] = $curr;
9401 return $array;
9405 * Like {@link swapshuffle()}, but works on associative arrays
9407 * @param array $array The associative array to be rearranged
9408 * @return array
9410 function swapshuffle_assoc($array) {
9412 $newarray = array();
9413 $newkeys = swapshuffle(array_keys($array));
9415 foreach ($newkeys as $newkey) {
9416 $newarray[$newkey] = $array[$newkey];
9418 return $newarray;
9422 * Given an arbitrary array, and a number of draws,
9423 * this function returns an array with that amount
9424 * of items. The indexes are retained.
9426 * @todo Finish documenting this function
9428 * @param array $array
9429 * @param int $draws
9430 * @return array
9432 function draw_rand_array($array, $draws) {
9433 srand ((double) microtime() * 10000000);
9435 $return = array();
9437 $last = count($array);
9439 if ($draws > $last) {
9440 $draws = $last;
9443 while ($draws > 0) {
9444 $last--;
9446 $keys = array_keys($array);
9447 $rand = rand(0, $last);
9449 $return[$keys[$rand]] = $array[$keys[$rand]];
9450 unset($array[$keys[$rand]]);
9452 $draws--;
9455 return $return;
9459 * Calculate the difference between two microtimes
9461 * @param string $a The first Microtime
9462 * @param string $b The second Microtime
9463 * @return string
9465 function microtime_diff($a, $b) {
9466 list($a_dec, $a_sec) = explode(' ', $a);
9467 list($b_dec, $b_sec) = explode(' ', $b);
9468 return $b_sec - $a_sec + $b_dec - $a_dec;
9472 * Given a list (eg a,b,c,d,e) this function returns
9473 * an array of 1->a, 2->b, 3->c etc
9475 * @param string $list The string to explode into array bits
9476 * @param string $separator The separator used within the list string
9477 * @return array The now assembled array
9479 function make_menu_from_list($list, $separator=',') {
9481 $array = array_reverse(explode($separator, $list), true);
9482 foreach ($array as $key => $item) {
9483 $outarray[$key+1] = trim($item);
9485 return $outarray;
9489 * Creates an array that represents all the current grades that
9490 * can be chosen using the given grading type.
9492 * Negative numbers
9493 * are scales, zero is no grade, and positive numbers are maximum
9494 * grades.
9496 * @todo Finish documenting this function or better deprecated this completely!
9498 * @param int $gradingtype
9499 * @return array
9501 function make_grades_menu($gradingtype) {
9502 global $DB;
9504 $grades = array();
9505 if ($gradingtype < 0) {
9506 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
9507 return make_menu_from_list($scale->scale);
9509 } else if ($gradingtype > 0) {
9510 for ($i=$gradingtype; $i>=0; $i--) {
9511 $grades[$i] = $i .' / '. $gradingtype;
9513 return $grades;
9515 return $grades;
9519 * This function returns the number of activities
9520 * using scaleid in a courseid
9522 * @todo Finish documenting this function
9524 * @global object
9525 * @global object
9526 * @param int $courseid ?
9527 * @param int $scaleid ?
9528 * @return int
9530 function course_scale_used($courseid, $scaleid) {
9531 global $CFG, $DB;
9533 $return = 0;
9535 if (!empty($scaleid)) {
9536 if ($cms = get_course_mods($courseid)) {
9537 foreach ($cms as $cm) {
9538 //Check cm->name/lib.php exists
9539 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
9540 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
9541 $function_name = $cm->modname.'_scale_used';
9542 if (function_exists($function_name)) {
9543 if ($function_name($cm->instance,$scaleid)) {
9544 $return++;
9551 // check if any course grade item makes use of the scale
9552 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
9554 // check if any outcome in the course makes use of the scale
9555 $return += $DB->count_records_sql("SELECT COUNT('x')
9556 FROM {grade_outcomes_courses} goc,
9557 {grade_outcomes} go
9558 WHERE go.id = goc.outcomeid
9559 AND go.scaleid = ? AND goc.courseid = ?",
9560 array($scaleid, $courseid));
9562 return $return;
9566 * This function returns the number of activities
9567 * using scaleid in the entire site
9569 * @param int $scaleid
9570 * @param array $courses
9571 * @return int
9573 function site_scale_used($scaleid, &$courses) {
9574 $return = 0;
9576 if (!is_array($courses) || count($courses) == 0) {
9577 $courses = get_courses("all",false,"c.id,c.shortname");
9580 if (!empty($scaleid)) {
9581 if (is_array($courses) && count($courses) > 0) {
9582 foreach ($courses as $course) {
9583 $return += course_scale_used($course->id,$scaleid);
9587 return $return;
9591 * make_unique_id_code
9593 * @todo Finish documenting this function
9595 * @uses $_SERVER
9596 * @param string $extra Extra string to append to the end of the code
9597 * @return string
9599 function make_unique_id_code($extra='') {
9601 $hostname = 'unknownhost';
9602 if (!empty($_SERVER['HTTP_HOST'])) {
9603 $hostname = $_SERVER['HTTP_HOST'];
9604 } else if (!empty($_ENV['HTTP_HOST'])) {
9605 $hostname = $_ENV['HTTP_HOST'];
9606 } else if (!empty($_SERVER['SERVER_NAME'])) {
9607 $hostname = $_SERVER['SERVER_NAME'];
9608 } else if (!empty($_ENV['SERVER_NAME'])) {
9609 $hostname = $_ENV['SERVER_NAME'];
9612 $date = gmdate("ymdHis");
9614 $random = random_string(6);
9616 if ($extra) {
9617 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
9618 } else {
9619 return $hostname .'+'. $date .'+'. $random;
9625 * Function to check the passed address is within the passed subnet
9627 * The parameter is a comma separated string of subnet definitions.
9628 * Subnet strings can be in one of three formats:
9629 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
9630 * 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)
9631 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
9632 * Code for type 1 modified from user posted comments by mediator at
9633 * {@link http://au.php.net/manual/en/function.ip2long.php}
9635 * @param string $addr The address you are checking
9636 * @param string $subnetstr The string of subnet addresses
9637 * @return bool
9639 function address_in_subnet($addr, $subnetstr) {
9641 if ($addr == '0.0.0.0') {
9642 return false;
9644 $subnets = explode(',', $subnetstr);
9645 $found = false;
9646 $addr = trim($addr);
9647 $addr = cleanremoteaddr($addr, false); // normalise
9648 if ($addr === null) {
9649 return false;
9651 $addrparts = explode(':', $addr);
9653 $ipv6 = strpos($addr, ':');
9655 foreach ($subnets as $subnet) {
9656 $subnet = trim($subnet);
9657 if ($subnet === '') {
9658 continue;
9661 if (strpos($subnet, '/') !== false) {
9662 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
9663 list($ip, $mask) = explode('/', $subnet);
9664 $mask = trim($mask);
9665 if (!is_number($mask)) {
9666 continue; // incorect mask number, eh?
9668 $ip = cleanremoteaddr($ip, false); // normalise
9669 if ($ip === null) {
9670 continue;
9672 if (strpos($ip, ':') !== false) {
9673 // IPv6
9674 if (!$ipv6) {
9675 continue;
9677 if ($mask > 128 or $mask < 0) {
9678 continue; // nonsense
9680 if ($mask == 0) {
9681 return true; // any address
9683 if ($mask == 128) {
9684 if ($ip === $addr) {
9685 return true;
9687 continue;
9689 $ipparts = explode(':', $ip);
9690 $modulo = $mask % 16;
9691 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
9692 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
9693 if (implode(':', $ipnet) === implode(':', $addrnet)) {
9694 if ($modulo == 0) {
9695 return true;
9697 $pos = ($mask-$modulo)/16;
9698 $ipnet = hexdec($ipparts[$pos]);
9699 $addrnet = hexdec($addrparts[$pos]);
9700 $mask = 0xffff << (16 - $modulo);
9701 if (($addrnet & $mask) == ($ipnet & $mask)) {
9702 return true;
9706 } else {
9707 // IPv4
9708 if ($ipv6) {
9709 continue;
9711 if ($mask > 32 or $mask < 0) {
9712 continue; // nonsense
9714 if ($mask == 0) {
9715 return true;
9717 if ($mask == 32) {
9718 if ($ip === $addr) {
9719 return true;
9721 continue;
9723 $mask = 0xffffffff << (32 - $mask);
9724 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
9725 return true;
9729 } else if (strpos($subnet, '-') !== false) {
9730 /// 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.
9731 $parts = explode('-', $subnet);
9732 if (count($parts) != 2) {
9733 continue;
9736 if (strpos($subnet, ':') !== false) {
9737 // IPv6
9738 if (!$ipv6) {
9739 continue;
9741 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9742 if ($ipstart === null) {
9743 continue;
9745 $ipparts = explode(':', $ipstart);
9746 $start = hexdec(array_pop($ipparts));
9747 $ipparts[] = trim($parts[1]);
9748 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
9749 if ($ipend === null) {
9750 continue;
9752 $ipparts[7] = '';
9753 $ipnet = implode(':', $ipparts);
9754 if (strpos($addr, $ipnet) !== 0) {
9755 continue;
9757 $ipparts = explode(':', $ipend);
9758 $end = hexdec($ipparts[7]);
9760 $addrend = hexdec($addrparts[7]);
9762 if (($addrend >= $start) and ($addrend <= $end)) {
9763 return true;
9766 } else {
9767 // IPv4
9768 if ($ipv6) {
9769 continue;
9771 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9772 if ($ipstart === null) {
9773 continue;
9775 $ipparts = explode('.', $ipstart);
9776 $ipparts[3] = trim($parts[1]);
9777 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
9778 if ($ipend === null) {
9779 continue;
9782 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
9783 return true;
9787 } else {
9788 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
9789 if (strpos($subnet, ':') !== false) {
9790 // IPv6
9791 if (!$ipv6) {
9792 continue;
9794 $parts = explode(':', $subnet);
9795 $count = count($parts);
9796 if ($parts[$count-1] === '') {
9797 unset($parts[$count-1]); // trim trailing :
9798 $count--;
9799 $subnet = implode('.', $parts);
9801 $isip = cleanremoteaddr($subnet, false); // normalise
9802 if ($isip !== null) {
9803 if ($isip === $addr) {
9804 return true;
9806 continue;
9807 } else if ($count > 8) {
9808 continue;
9810 $zeros = array_fill(0, 8-$count, '0');
9811 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
9812 if (address_in_subnet($addr, $subnet)) {
9813 return true;
9816 } else {
9817 // IPv4
9818 if ($ipv6) {
9819 continue;
9821 $parts = explode('.', $subnet);
9822 $count = count($parts);
9823 if ($parts[$count-1] === '') {
9824 unset($parts[$count-1]); // trim trailing .
9825 $count--;
9826 $subnet = implode('.', $parts);
9828 if ($count == 4) {
9829 $subnet = cleanremoteaddr($subnet, false); // normalise
9830 if ($subnet === $addr) {
9831 return true;
9833 continue;
9834 } else if ($count > 4) {
9835 continue;
9837 $zeros = array_fill(0, 4-$count, '0');
9838 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
9839 if (address_in_subnet($addr, $subnet)) {
9840 return true;
9846 return false;
9850 * For outputting debugging info
9852 * @uses STDOUT
9853 * @param string $string The string to write
9854 * @param string $eol The end of line char(s) to use
9855 * @param string $sleep Period to make the application sleep
9856 * This ensures any messages have time to display before redirect
9858 function mtrace($string, $eol="\n", $sleep=0) {
9860 if (defined('STDOUT') and !PHPUNIT_TEST) {
9861 fwrite(STDOUT, $string.$eol);
9862 } else {
9863 echo $string . $eol;
9866 flush();
9868 //delay to keep message on user's screen in case of subsequent redirect
9869 if ($sleep) {
9870 sleep($sleep);
9875 * Replace 1 or more slashes or backslashes to 1 slash
9877 * @param string $path The path to strip
9878 * @return string the path with double slashes removed
9880 function cleardoubleslashes ($path) {
9881 return preg_replace('/(\/|\\\){1,}/','/',$path);
9885 * Is current ip in give list?
9887 * @param string $list
9888 * @return bool
9890 function remoteip_in_list($list){
9891 $inlist = false;
9892 $client_ip = getremoteaddr(null);
9894 if(!$client_ip){
9895 // ensure access on cli
9896 return true;
9899 $list = explode("\n", $list);
9900 foreach($list as $subnet) {
9901 $subnet = trim($subnet);
9902 if (address_in_subnet($client_ip, $subnet)) {
9903 $inlist = true;
9904 break;
9907 return $inlist;
9911 * Returns most reliable client address
9913 * @global object
9914 * @param string $default If an address can't be determined, then return this
9915 * @return string The remote IP address
9917 function getremoteaddr($default='0.0.0.0') {
9918 global $CFG;
9920 if (empty($CFG->getremoteaddrconf)) {
9921 // This will happen, for example, before just after the upgrade, as the
9922 // user is redirected to the admin screen.
9923 $variablestoskip = 0;
9924 } else {
9925 $variablestoskip = $CFG->getremoteaddrconf;
9927 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
9928 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
9929 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
9930 return $address ? $address : $default;
9933 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
9934 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
9935 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
9936 return $address ? $address : $default;
9939 if (!empty($_SERVER['REMOTE_ADDR'])) {
9940 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
9941 return $address ? $address : $default;
9942 } else {
9943 return $default;
9948 * Cleans an ip address. Internal addresses are now allowed.
9949 * (Originally local addresses were not allowed.)
9951 * @param string $addr IPv4 or IPv6 address
9952 * @param bool $compress use IPv6 address compression
9953 * @return string normalised ip address string, null if error
9955 function cleanremoteaddr($addr, $compress=false) {
9956 $addr = trim($addr);
9958 //TODO: maybe add a separate function is_addr_public() or something like this
9960 if (strpos($addr, ':') !== false) {
9961 // can be only IPv6
9962 $parts = explode(':', $addr);
9963 $count = count($parts);
9965 if (strpos($parts[$count-1], '.') !== false) {
9966 //legacy ipv4 notation
9967 $last = array_pop($parts);
9968 $ipv4 = cleanremoteaddr($last, true);
9969 if ($ipv4 === null) {
9970 return null;
9972 $bits = explode('.', $ipv4);
9973 $parts[] = dechex($bits[0]).dechex($bits[1]);
9974 $parts[] = dechex($bits[2]).dechex($bits[3]);
9975 $count = count($parts);
9976 $addr = implode(':', $parts);
9979 if ($count < 3 or $count > 8) {
9980 return null; // severly malformed
9983 if ($count != 8) {
9984 if (strpos($addr, '::') === false) {
9985 return null; // malformed
9987 // uncompress ::
9988 $insertat = array_search('', $parts, true);
9989 $missing = array_fill(0, 1 + 8 - $count, '0');
9990 array_splice($parts, $insertat, 1, $missing);
9991 foreach ($parts as $key=>$part) {
9992 if ($part === '') {
9993 $parts[$key] = '0';
9998 $adr = implode(':', $parts);
9999 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
10000 return null; // incorrect format - sorry
10003 // normalise 0s and case
10004 $parts = array_map('hexdec', $parts);
10005 $parts = array_map('dechex', $parts);
10007 $result = implode(':', $parts);
10009 if (!$compress) {
10010 return $result;
10013 if ($result === '0:0:0:0:0:0:0:0') {
10014 return '::'; // all addresses
10017 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
10018 if ($compressed !== $result) {
10019 return $compressed;
10022 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
10023 if ($compressed !== $result) {
10024 return $compressed;
10027 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
10028 if ($compressed !== $result) {
10029 return $compressed;
10032 return $result;
10035 // first get all things that look like IPv4 addresses
10036 $parts = array();
10037 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
10038 return null;
10040 unset($parts[0]);
10042 foreach ($parts as $key=>$match) {
10043 if ($match > 255) {
10044 return null;
10046 $parts[$key] = (int)$match; // normalise 0s
10049 return implode('.', $parts);
10053 * This function will make a complete copy of anything it's given,
10054 * regardless of whether it's an object or not.
10056 * @param mixed $thing Something you want cloned
10057 * @return mixed What ever it is you passed it
10059 function fullclone($thing) {
10060 return unserialize(serialize($thing));
10065 * This function expects to called during shutdown
10066 * should be set via register_shutdown_function()
10067 * in lib/setup.php .
10069 * @return void
10071 function moodle_request_shutdown() {
10072 global $CFG;
10074 // help apache server if possible
10075 $apachereleasemem = false;
10076 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
10077 && ini_get_bool('child_terminate')) {
10079 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
10080 if (memory_get_usage() > get_real_size($limit)) {
10081 $apachereleasemem = $limit;
10082 @apache_child_terminate();
10086 // deal with perf logging
10087 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
10088 if ($apachereleasemem) {
10089 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
10091 if (defined('MDL_PERFTOLOG')) {
10092 $perf = get_performance_info();
10093 error_log("PERF: " . $perf['txt']);
10095 if (defined('MDL_PERFINC')) {
10096 $inc = get_included_files();
10097 $ts = 0;
10098 foreach($inc as $f) {
10099 if (preg_match(':^/:', $f)) {
10100 $fs = filesize($f);
10101 $ts += $fs;
10102 $hfs = display_size($fs);
10103 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
10104 , NULL, NULL, 0);
10105 } else {
10106 error_log($f , NULL, NULL, 0);
10109 if ($ts > 0 ) {
10110 $hts = display_size($ts);
10111 error_log("Total size of files included: $ts ($hts)");
10118 * If new messages are waiting for the current user, then insert
10119 * JavaScript to pop up the messaging window into the page
10121 * @global moodle_page $PAGE
10122 * @return void
10124 function message_popup_window() {
10125 global $USER, $DB, $PAGE, $CFG, $SITE;
10127 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
10128 return;
10131 if (!isloggedin() || isguestuser()) {
10132 return;
10135 if (!isset($USER->message_lastpopup)) {
10136 $USER->message_lastpopup = 0;
10137 } else if ($USER->message_lastpopup > (time()-120)) {
10138 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
10139 return;
10142 //a quick query to check whether the user has new messages
10143 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
10144 if ($messagecount<1) {
10145 return;
10148 //got unread messages so now do another query that joins with the user table
10149 $messagesql = "SELECT m.id, m.smallmessage, m.fullmessageformat, m.notification, u.firstname, u.lastname
10150 FROM {message} m
10151 JOIN {message_working} mw ON m.id=mw.unreadmessageid
10152 JOIN {message_processors} p ON mw.processorid=p.id
10153 JOIN {user} u ON m.useridfrom=u.id
10154 WHERE m.useridto = :userid
10155 AND p.name='popup'";
10157 //if the user was last notified over an hour ago we can renotify them of old messages
10158 //so don't worry about when the new message was sent
10159 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
10160 if (!$lastnotifiedlongago) {
10161 $messagesql .= 'AND m.timecreated > :lastpopuptime';
10164 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
10166 //if we have new messages to notify the user about
10167 if (!empty($message_users)) {
10169 $strmessages = '';
10170 if (count($message_users)>1) {
10171 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
10172 } else {
10173 $message_users = reset($message_users);
10175 //show who the message is from if its not a notification
10176 if (!$message_users->notification) {
10177 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
10180 //try to display the small version of the message
10181 $smallmessage = null;
10182 if (!empty($message_users->smallmessage)) {
10183 //display the first 200 chars of the message in the popup
10184 $smallmessage = null;
10185 if (textlib::strlen($message_users->smallmessage) > 200) {
10186 $smallmessage = textlib::substr($message_users->smallmessage,0,200).'...';
10187 } else {
10188 $smallmessage = $message_users->smallmessage;
10191 //prevent html symbols being displayed
10192 if ($message_users->fullmessageformat == FORMAT_HTML) {
10193 $smallmessage = html_to_text($smallmessage);
10194 } else {
10195 $smallmessage = s($smallmessage);
10197 } else if ($message_users->notification) {
10198 //its a notification with no smallmessage so just say they have a notification
10199 $smallmessage = get_string('unreadnewnotification', 'message');
10201 if (!empty($smallmessage)) {
10202 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
10206 $strgomessage = get_string('gotomessages', 'message');
10207 $strstaymessage = get_string('ignore','admin');
10209 $url = $CFG->wwwroot.'/message/index.php';
10210 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
10211 html_writer::start_tag('div', array('id'=>'newmessagetext')).
10212 $strmessages.
10213 html_writer::end_tag('div').
10215 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
10216 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
10217 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
10218 html_writer::end_tag('div');
10219 html_writer::end_tag('div');
10221 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
10223 $USER->message_lastpopup = time();
10228 * Used to make sure that $min <= $value <= $max
10230 * Make sure that value is between min, and max
10232 * @param int $min The minimum value
10233 * @param int $value The value to check
10234 * @param int $max The maximum value
10236 function bounded_number($min, $value, $max) {
10237 if($value < $min) {
10238 return $min;
10240 if($value > $max) {
10241 return $max;
10243 return $value;
10247 * Check if there is a nested array within the passed array
10249 * @param array $array
10250 * @return bool true if there is a nested array false otherwise
10252 function array_is_nested($array) {
10253 foreach ($array as $value) {
10254 if (is_array($value)) {
10255 return true;
10258 return false;
10262 * get_performance_info() pairs up with init_performance_info()
10263 * loaded in setup.php. Returns an array with 'html' and 'txt'
10264 * values ready for use, and each of the individual stats provided
10265 * separately as well.
10267 * @global object
10268 * @global object
10269 * @global object
10270 * @return array
10272 function get_performance_info() {
10273 global $CFG, $PERF, $DB, $PAGE;
10275 $info = array();
10276 $info['html'] = ''; // holds userfriendly HTML representation
10277 $info['txt'] = me() . ' '; // holds log-friendly representation
10279 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
10281 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
10282 $info['txt'] .= 'time: '.$info['realtime'].'s ';
10284 if (function_exists('memory_get_usage')) {
10285 $info['memory_total'] = memory_get_usage();
10286 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
10287 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
10288 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
10291 if (function_exists('memory_get_peak_usage')) {
10292 $info['memory_peak'] = memory_get_peak_usage();
10293 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
10294 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
10297 $inc = get_included_files();
10298 //error_log(print_r($inc,1));
10299 $info['includecount'] = count($inc);
10300 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
10301 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
10303 $filtermanager = filter_manager::instance();
10304 if (method_exists($filtermanager, 'get_performance_summary')) {
10305 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
10306 $info = array_merge($filterinfo, $info);
10307 foreach ($filterinfo as $key => $value) {
10308 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10309 $info['txt'] .= "$key: $value ";
10313 $stringmanager = get_string_manager();
10314 if (method_exists($stringmanager, 'get_performance_summary')) {
10315 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
10316 $info = array_merge($filterinfo, $info);
10317 foreach ($filterinfo as $key => $value) {
10318 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10319 $info['txt'] .= "$key: $value ";
10323 $jsmodules = $PAGE->requires->get_loaded_modules();
10324 if ($jsmodules) {
10325 $yuicount = 0;
10326 $othercount = 0;
10327 $details = '';
10328 foreach ($jsmodules as $module => $backtraces) {
10329 if (strpos($module, 'yui') === 0) {
10330 $yuicount += 1;
10331 } else {
10332 $othercount += 1;
10334 if (!empty($CFG->yuimoduledebug)) {
10335 // hidden feature for developers working on YUI module infrastructure
10336 $details .= "<div class='yui-module'><p>$module</p>";
10337 foreach ($backtraces as $backtrace) {
10338 $details .= "<div class='backtrace'>$backtrace</div>";
10340 $details .= '</div>';
10343 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
10344 $info['txt'] .= "includedyuimodules: $yuicount ";
10345 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
10346 $info['txt'] .= "includedjsmodules: $othercount ";
10347 if ($details) {
10348 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
10352 if (!empty($PERF->logwrites)) {
10353 $info['logwrites'] = $PERF->logwrites;
10354 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
10355 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
10358 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
10359 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
10360 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
10362 if (function_exists('posix_times')) {
10363 $ptimes = posix_times();
10364 if (is_array($ptimes)) {
10365 foreach ($ptimes as $key => $val) {
10366 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
10368 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
10369 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
10373 // Grab the load average for the last minute
10374 // /proc will only work under some linux configurations
10375 // while uptime is there under MacOSX/Darwin and other unices
10376 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
10377 list($server_load) = explode(' ', $loadavg[0]);
10378 unset($loadavg);
10379 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
10380 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
10381 $server_load = $matches[1];
10382 } else {
10383 trigger_error('Could not parse uptime output!');
10386 if (!empty($server_load)) {
10387 $info['serverload'] = $server_load;
10388 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
10389 $info['txt'] .= "serverload: {$info['serverload']} ";
10392 // Display size of session if session started
10393 if (session_id()) {
10394 $info['sessionsize'] = display_size(strlen(session_encode()));
10395 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
10396 $info['txt'] .= "Session: {$info['sessionsize']} ";
10399 /* if (isset($rcache->hits) && isset($rcache->misses)) {
10400 $info['rcachehits'] = $rcache->hits;
10401 $info['rcachemisses'] = $rcache->misses;
10402 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
10403 "{$rcache->hits}/{$rcache->misses}</span> ";
10404 $info['txt'] .= 'rcache: '.
10405 "{$rcache->hits}/{$rcache->misses} ";
10407 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
10408 return $info;
10412 * @todo Document this function linux people
10414 function apd_get_profiling() {
10415 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
10419 * Delete directory or only its content
10421 * @param string $dir directory path
10422 * @param bool $content_only
10423 * @return bool success, true also if dir does not exist
10425 function remove_dir($dir, $content_only=false) {
10426 if (!file_exists($dir)) {
10427 // nothing to do
10428 return true;
10430 if (!$handle = opendir($dir)) {
10431 return false;
10433 $result = true;
10434 while (false!==($item = readdir($handle))) {
10435 if($item != '.' && $item != '..') {
10436 if(is_dir($dir.'/'.$item)) {
10437 $result = remove_dir($dir.'/'.$item) && $result;
10438 }else{
10439 $result = unlink($dir.'/'.$item) && $result;
10443 closedir($handle);
10444 if ($content_only) {
10445 clearstatcache(); // make sure file stat cache is properly invalidated
10446 return $result;
10448 $result = rmdir($dir); // if anything left the result will be false, no need for && $result
10449 clearstatcache(); // make sure file stat cache is properly invalidated
10450 return $result;
10454 * Detect if an object or a class contains a given property
10455 * will take an actual object or the name of a class
10457 * @param mix $obj Name of class or real object to test
10458 * @param string $property name of property to find
10459 * @return bool true if property exists
10461 function object_property_exists( $obj, $property ) {
10462 if (is_string( $obj )) {
10463 $properties = get_class_vars( $obj );
10465 else {
10466 $properties = get_object_vars( $obj );
10468 return array_key_exists( $property, $properties );
10472 * Converts an object into an associative array
10474 * This function converts an object into an associative array by iterating
10475 * over its public properties. Because this function uses the foreach
10476 * construct, Iterators are respected. It works recursively on arrays of objects.
10477 * Arrays and simple values are returned as is.
10479 * If class has magic properties, it can implement IteratorAggregate
10480 * and return all available properties in getIterator()
10482 * @param mixed $var
10483 * @return array
10485 function convert_to_array($var) {
10486 $result = array();
10488 // loop over elements/properties
10489 foreach ($var as $key => $value) {
10490 // recursively convert objects
10491 if (is_object($value) || is_array($value)) {
10492 $result[$key] = convert_to_array($value);
10493 } else {
10494 // simple values are untouched
10495 $result[$key] = $value;
10498 return $result;
10502 * Detect a custom script replacement in the data directory that will
10503 * replace an existing moodle script
10505 * @return string|bool full path name if a custom script exists, false if no custom script exists
10507 function custom_script_path() {
10508 global $CFG, $SCRIPT;
10510 if ($SCRIPT === null) {
10511 // Probably some weird external script
10512 return false;
10515 $scriptpath = $CFG->customscripts . $SCRIPT;
10517 // check the custom script exists
10518 if (file_exists($scriptpath) and is_file($scriptpath)) {
10519 return $scriptpath;
10520 } else {
10521 return false;
10526 * Returns whether or not the user object is a remote MNET user. This function
10527 * is in moodlelib because it does not rely on loading any of the MNET code.
10529 * @global object
10530 * @param object $user A valid user object
10531 * @return bool True if the user is from a remote Moodle.
10533 function is_mnet_remote_user($user) {
10534 global $CFG;
10536 if (!isset($CFG->mnet_localhost_id)) {
10537 include_once $CFG->dirroot . '/mnet/lib.php';
10538 $env = new mnet_environment();
10539 $env->init();
10540 unset($env);
10543 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
10547 * This function will search for browser prefereed languages, setting Moodle
10548 * to use the best one available if $SESSION->lang is undefined
10550 * @global object
10551 * @global object
10552 * @global object
10554 function setup_lang_from_browser() {
10556 global $CFG, $SESSION, $USER;
10558 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
10559 // Lang is defined in session or user profile, nothing to do
10560 return;
10563 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
10564 return;
10567 /// Extract and clean langs from headers
10568 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
10569 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
10570 $rawlangs = explode(',', $rawlangs); // Convert to array
10571 $langs = array();
10573 $order = 1.0;
10574 foreach ($rawlangs as $lang) {
10575 if (strpos($lang, ';') === false) {
10576 $langs[(string)$order] = $lang;
10577 $order = $order-0.01;
10578 } else {
10579 $parts = explode(';', $lang);
10580 $pos = strpos($parts[1], '=');
10581 $langs[substr($parts[1], $pos+1)] = $parts[0];
10584 krsort($langs, SORT_NUMERIC);
10586 /// Look for such langs under standard locations
10587 foreach ($langs as $lang) {
10588 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
10589 if (get_string_manager()->translation_exists($lang, false)) {
10590 $SESSION->lang = $lang; /// Lang exists, set it in session
10591 break; /// We have finished. Go out
10594 return;
10598 * check if $url matches anything in proxybypass list
10600 * any errors just result in the proxy being used (least bad)
10602 * @global object
10603 * @param string $url url to check
10604 * @return boolean true if we should bypass the proxy
10606 function is_proxybypass( $url ) {
10607 global $CFG;
10609 // sanity check
10610 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
10611 return false;
10614 // get the host part out of the url
10615 if (!$host = parse_url( $url, PHP_URL_HOST )) {
10616 return false;
10619 // get the possible bypass hosts into an array
10620 $matches = explode( ',', $CFG->proxybypass );
10622 // check for a match
10623 // (IPs need to match the left hand side and hosts the right of the url,
10624 // but we can recklessly check both as there can't be a false +ve)
10625 $bypass = false;
10626 foreach ($matches as $match) {
10627 $match = trim($match);
10629 // try for IP match (Left side)
10630 $lhs = substr($host,0,strlen($match));
10631 if (strcasecmp($match,$lhs)==0) {
10632 return true;
10635 // try for host match (Right side)
10636 $rhs = substr($host,-strlen($match));
10637 if (strcasecmp($match,$rhs)==0) {
10638 return true;
10642 // nothing matched.
10643 return false;
10647 ////////////////////////////////////////////////////////////////////////////////
10650 * Check if the passed navigation is of the new style
10652 * @param mixed $navigation
10653 * @return bool true for yes false for no
10655 function is_newnav($navigation) {
10656 if (is_array($navigation) && !empty($navigation['newnav'])) {
10657 return true;
10658 } else {
10659 return false;
10664 * Checks whether the given variable name is defined as a variable within the given object.
10666 * This will NOT work with stdClass objects, which have no class variables.
10668 * @param string $var The variable name
10669 * @param object $object The object to check
10670 * @return boolean
10672 function in_object_vars($var, $object) {
10673 $class_vars = get_class_vars(get_class($object));
10674 $class_vars = array_keys($class_vars);
10675 return in_array($var, $class_vars);
10679 * Returns an array without repeated objects.
10680 * This function is similar to array_unique, but for arrays that have objects as values
10682 * @param array $array
10683 * @param bool $keep_key_assoc
10684 * @return array
10686 function object_array_unique($array, $keep_key_assoc = true) {
10687 $duplicate_keys = array();
10688 $tmp = array();
10690 foreach ($array as $key=>$val) {
10691 // convert objects to arrays, in_array() does not support objects
10692 if (is_object($val)) {
10693 $val = (array)$val;
10696 if (!in_array($val, $tmp)) {
10697 $tmp[] = $val;
10698 } else {
10699 $duplicate_keys[] = $key;
10703 foreach ($duplicate_keys as $key) {
10704 unset($array[$key]);
10707 return $keep_key_assoc ? $array : array_values($array);
10711 * Is a userid the primary administrator?
10713 * @param int $userid int id of user to check
10714 * @return boolean
10716 function is_primary_admin($userid){
10717 $primaryadmin = get_admin();
10719 if($userid == $primaryadmin->id){
10720 return true;
10721 }else{
10722 return false;
10727 * Returns the site identifier
10729 * @global object
10730 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
10732 function get_site_identifier() {
10733 global $CFG;
10734 // Check to see if it is missing. If so, initialise it.
10735 if (empty($CFG->siteidentifier)) {
10736 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
10738 // Return it.
10739 return $CFG->siteidentifier;
10743 * Check whether the given password has no more than the specified
10744 * number of consecutive identical characters.
10746 * @param string $password password to be checked against the password policy
10747 * @param integer $maxchars maximum number of consecutive identical characters
10749 function check_consecutive_identical_characters($password, $maxchars) {
10751 if ($maxchars < 1) {
10752 return true; // 0 is to disable this check
10754 if (strlen($password) <= $maxchars) {
10755 return true; // too short to fail this test
10758 $previouschar = '';
10759 $consecutivecount = 1;
10760 foreach (str_split($password) as $char) {
10761 if ($char != $previouschar) {
10762 $consecutivecount = 1;
10764 else {
10765 $consecutivecount++;
10766 if ($consecutivecount > $maxchars) {
10767 return false; // check failed already
10771 $previouschar = $char;
10774 return true;
10778 * helper function to do partial function binding
10779 * so we can use it for preg_replace_callback, for example
10780 * this works with php functions, user functions, static methods and class methods
10781 * it returns you a callback that you can pass on like so:
10783 * $callback = partial('somefunction', $arg1, $arg2);
10784 * or
10785 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
10786 * or even
10787 * $obj = new someclass();
10788 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
10790 * and then the arguments that are passed through at calltime are appended to the argument list.
10792 * @param mixed $function a php callback
10793 * $param mixed $arg1.. $argv arguments to partially bind with
10795 * @return callback
10797 function partial() {
10798 if (!class_exists('partial')) {
10799 class partial{
10800 var $values = array();
10801 var $func;
10803 function __construct($func, $args) {
10804 $this->values = $args;
10805 $this->func = $func;
10808 function method() {
10809 $args = func_get_args();
10810 return call_user_func_array($this->func, array_merge($this->values, $args));
10814 $args = func_get_args();
10815 $func = array_shift($args);
10816 $p = new partial($func, $args);
10817 return array($p, 'method');
10821 * helper function to load up and initialise the mnet environment
10822 * this must be called before you use mnet functions.
10824 * @return mnet_environment the equivalent of old $MNET global
10826 function get_mnet_environment() {
10827 global $CFG;
10828 require_once($CFG->dirroot . '/mnet/lib.php');
10829 static $instance = null;
10830 if (empty($instance)) {
10831 $instance = new mnet_environment();
10832 $instance->init();
10834 return $instance;
10838 * during xmlrpc server code execution, any code wishing to access
10839 * information about the remote peer must use this to get it.
10841 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
10843 function get_mnet_remote_client() {
10844 if (!defined('MNET_SERVER')) {
10845 debugging(get_string('notinxmlrpcserver', 'mnet'));
10846 return false;
10848 global $MNET_REMOTE_CLIENT;
10849 if (isset($MNET_REMOTE_CLIENT)) {
10850 return $MNET_REMOTE_CLIENT;
10852 return false;
10856 * during the xmlrpc server code execution, this will be called
10857 * to setup the object returned by {@see get_mnet_remote_client}
10859 * @param mnet_remote_client $client the client to set up
10861 function set_mnet_remote_client($client) {
10862 if (!defined('MNET_SERVER')) {
10863 throw new moodle_exception('notinxmlrpcserver', 'mnet');
10865 global $MNET_REMOTE_CLIENT;
10866 $MNET_REMOTE_CLIENT = $client;
10870 * return the jump url for a given remote user
10871 * this is used for rewriting forum post links in emails, etc
10873 * @param stdclass $user the user to get the idp url for
10875 function mnet_get_idp_jump_url($user) {
10876 global $CFG;
10878 static $mnetjumps = array();
10879 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
10880 $idp = mnet_get_peer_host($user->mnethostid);
10881 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
10882 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
10884 return $mnetjumps[$user->mnethostid];
10888 * Gets the homepage to use for the current user
10890 * @return int One of HOMEPAGE_*
10892 function get_home_page() {
10893 global $CFG;
10895 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
10896 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
10897 return HOMEPAGE_MY;
10898 } else {
10899 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
10902 return HOMEPAGE_SITE;
10906 * Gets the name of a course to be displayed when showing a list of courses.
10907 * By default this is just $course->fullname but user can configure it. The
10908 * result of this function should be passed through print_string.
10909 * @param object $course Moodle course object
10910 * @return string Display name of course (either fullname or short + fullname)
10912 function get_course_display_name_for_list($course) {
10913 global $CFG;
10914 if (!empty($CFG->courselistshortnames)) {
10915 return get_string('courseextendednamedisplay', '', $course);
10916 } else {
10917 return $course->fullname;
10922 * The lang_string class
10924 * This special class is used to create an object representation of a string request.
10925 * It is special because processing doesn't occur until the object is first used.
10926 * The class was created especially to aid performance in areas where strings were
10927 * required to be generated but were not necessarily used.
10928 * As an example the admin tree when generated uses over 1500 strings, of which
10929 * normally only 1/3 are ever actually printed at any time.
10930 * The performance advantage is achieved by not actually processing strings that
10931 * arn't being used, as such reducing the processing required for the page.
10933 * How to use the lang_string class?
10934 * There are two methods of using the lang_string class, first through the
10935 * forth argument of the get_string function, and secondly directly.
10936 * The following are examples of both.
10937 * 1. Through get_string calls e.g.
10938 * $string = get_string($identifier, $component, $a, true);
10939 * $string = get_string('yes', 'moodle', null, true);
10940 * 2. Direct instantiation
10941 * $string = new lang_string($identifier, $component, $a, $lang);
10942 * $string = new lang_string('yes');
10944 * How do I use a lang_string object?
10945 * The lang_string object makes use of a magic __toString method so that you
10946 * are able to use the object exactly as you would use a string in most cases.
10947 * This means you are able to collect it into a variable and then directly
10948 * echo it, or concatenate it into another string, or similar.
10949 * The other thing you can do is manually get the string by calling the
10950 * lang_strings out method e.g.
10951 * $string = new lang_string('yes');
10952 * $string->out();
10953 * Also worth noting is that the out method can take one argument, $lang which
10954 * allows the developer to change the language on the fly.
10956 * When should I use a lang_string object?
10957 * The lang_string object is designed to be used in any situation where a
10958 * string may not be needed, but needs to be generated.
10959 * The admin tree is a good example of where lang_string objects should be
10960 * used.
10961 * A more practical example would be any class that requries strings that may
10962 * not be printed (after all classes get renderer by renderers and who knows
10963 * what they will do ;))
10965 * When should I not use a lang_string object?
10966 * Don't use lang_strings when you are going to use a string immediately.
10967 * There is no need as it will be processed immediately and there will be no
10968 * advantage, and in fact perhaps a negative hit as a class has to be
10969 * instantiated for a lang_string object, however get_string won't require
10970 * that.
10972 * Limitations:
10973 * 1. You cannot use a lang_string object as an array offset. Doing so will
10974 * result in PHP throwing an error. (You can use it as an object property!)
10976 * @package core
10977 * @category string
10978 * @copyright 2011 Sam Hemelryk
10979 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10981 class lang_string {
10983 /** @var string The strings identifier */
10984 protected $identifier;
10985 /** @var string The strings component. Default '' */
10986 protected $component = '';
10987 /** @var array|stdClass Any arguments required for the string. Default null */
10988 protected $a = null;
10989 /** @var string The language to use when processing the string. Default null */
10990 protected $lang = null;
10992 /** @var string The processed string (once processed) */
10993 protected $string = null;
10996 * A special boolean. If set to true then the object has been woken up and
10997 * cannot be regenerated. If this is set then $this->string MUST be used.
10998 * @var bool
11000 protected $forcedstring = false;
11003 * Constructs a lang_string object
11005 * This function should do as little processing as possible to ensure the best
11006 * performance for strings that won't be used.
11008 * @param string $identifier The strings identifier
11009 * @param string $component The strings component
11010 * @param stdClass|array $a Any arguments the string requires
11011 * @param string $lang The language to use when processing the string.
11013 public function __construct($identifier, $component = '', $a = null, $lang = null) {
11014 if (empty($component)) {
11015 $component = 'moodle';
11018 $this->identifier = $identifier;
11019 $this->component = $component;
11020 $this->lang = $lang;
11022 // We MUST duplicate $a to ensure that it if it changes by reference those
11023 // changes are not carried across.
11024 // To do this we always ensure $a or its properties/values are strings
11025 // and that any properties/values that arn't convertable are forgotten.
11026 if (!empty($a)) {
11027 if (is_scalar($a)) {
11028 $this->a = $a;
11029 } else if ($a instanceof lang_string) {
11030 $this->a = $a->out();
11031 } else if (is_object($a) or is_array($a)) {
11032 $a = (array)$a;
11033 $this->a = array();
11034 foreach ($a as $key => $value) {
11035 // Make sure conversion errors don't get displayed (results in '')
11036 if (is_array($value)) {
11037 $this->a[$key] = '';
11038 } else if (is_object($value)) {
11039 if (method_exists($value, '__toString')) {
11040 $this->a[$key] = $value->__toString();
11041 } else {
11042 $this->a[$key] = '';
11044 } else {
11045 $this->a[$key] = (string)$value;
11051 if (debugging(false, DEBUG_DEVELOPER)) {
11052 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
11053 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11055 if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') {
11056 throw new coding_exception('Invalid string compontent. Please check your string definition');
11058 if (!get_string_manager()->string_exists($this->identifier, $this->component)) {
11059 debugging('String does not exist. Please check your string definition for '.$this->identifier.'/'.$this->component, DEBUG_DEVELOPER);
11065 * Processes the string.
11067 * This function actually processes the string, stores it in the string property
11068 * and then returns it.
11069 * You will notice that this function is VERY similar to the get_string method.
11070 * That is because it is pretty much doing the same thing.
11071 * However as this function is an upgrade it isn't as tolerant to backwards
11072 * compatability.
11074 * @return string
11076 protected function get_string() {
11077 global $CFG;
11079 // Check if we need to process the string
11080 if ($this->string === null) {
11081 // Check the quality of the identifier.
11082 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
11083 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11086 // Process the string
11087 $this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
11088 // Debugging feature lets you display string identifier and component
11089 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
11090 $this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
11093 // Return the string
11094 return $this->string;
11098 * Returns the string
11100 * @param string $lang The langauge to use when processing the string
11101 * @return string
11103 public function out($lang = null) {
11104 if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
11105 if ($this->forcedstring) {
11106 debugging('lang_string objects that have been serialised and unserialised cannot be printed in another language. ('.$this->lang.' used)', DEBUG_DEVELOPER);
11107 return $this->get_string();
11109 $translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
11110 return $translatedstring->out();
11112 return $this->get_string();
11116 * Magic __toString method for printing a string
11118 * @return string
11120 public function __toString() {
11121 return $this->get_string();
11125 * Magic __set_state method used for var_export
11127 * @return string
11129 public function __set_state() {
11130 return $this->get_string();
11134 * Prepares the lang_string for sleep and stores only the forcedstring and
11135 * string properties... the string cannot be regenerated so we need to ensure
11136 * it is generated for this.
11138 * @return string
11140 public function __sleep() {
11141 $this->get_string();
11142 $this->forcedstring = true;
11143 return array('forcedstring', 'string', 'lang');