MDL-35129 add missing recordset closing in db transfer
[moodle.git] / lib / moodlelib.php
blobce654997f1cae0c8d5f69b3f237610472f09bf81
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]|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 if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
2075 $format = textlib::convert($format, 'utf-8', $localewincharset);
2076 $datestring = strftime($format, $date);
2077 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2078 } else {
2079 $datestring = strftime($format, $date);
2081 if ($fixday) {
2082 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
2083 $datestring = str_replace('DD', $daystring, $datestring);
2085 if ($fixhour) {
2086 $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
2087 $datestring = str_replace('HH', $hourstring, $datestring);
2090 } else {
2091 $date += (int)($timezone * 3600);
2092 if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
2093 $format = textlib::convert($format, 'utf-8', $localewincharset);
2094 $datestring = gmstrftime($format, $date);
2095 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2096 } else {
2097 $datestring = gmstrftime($format, $date);
2099 if ($fixday) {
2100 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
2101 $datestring = str_replace('DD', $daystring, $datestring);
2103 if ($fixhour) {
2104 $hourstring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %I', $date)));
2105 $datestring = str_replace('HH', $hourstring, $datestring);
2109 return $datestring;
2113 * Given a $time timestamp in GMT (seconds since epoch),
2114 * returns an array that represents the date in user time
2116 * @package core
2117 * @category time
2118 * @uses HOURSECS
2119 * @param int $time Timestamp in GMT
2120 * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
2121 * dst offset is applyed {@link http://docs.moodle.org/dev/Time_API#Timezone}
2122 * @return array An array that represents the date in user time
2124 function usergetdate($time, $timezone=99) {
2126 //save input timezone, required for dst offset check.
2127 $passedtimezone = $timezone;
2129 $timezone = get_user_timezone_offset($timezone);
2131 if (abs($timezone) > 13) { // Server time
2132 return getdate($time);
2135 //add daylight saving offset for string timezones only, as we can't get dst for
2136 //float values. if timezone is 99 (user default timezone), then try update dst.
2137 if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
2138 $time += dst_offset_on($time, $passedtimezone);
2141 $time += intval((float)$timezone * HOURSECS);
2143 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
2145 //be careful to ensure the returned array matches that produced by getdate() above
2146 list(
2147 $getdate['month'],
2148 $getdate['weekday'],
2149 $getdate['yday'],
2150 $getdate['year'],
2151 $getdate['mon'],
2152 $getdate['wday'],
2153 $getdate['mday'],
2154 $getdate['hours'],
2155 $getdate['minutes'],
2156 $getdate['seconds']
2157 ) = explode('_', $datestring);
2159 // set correct datatype to match with getdate()
2160 $getdate['seconds'] = (int)$getdate['seconds'];
2161 $getdate['yday'] = (int)$getdate['yday'] - 1; // gettime returns 0 through 365
2162 $getdate['year'] = (int)$getdate['year'];
2163 $getdate['mon'] = (int)$getdate['mon'];
2164 $getdate['wday'] = (int)$getdate['wday'];
2165 $getdate['mday'] = (int)$getdate['mday'];
2166 $getdate['hours'] = (int)$getdate['hours'];
2167 $getdate['minutes'] = (int)$getdate['minutes'];
2168 return $getdate;
2172 * Given a GMT timestamp (seconds since epoch), offsets it by
2173 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
2175 * @package core
2176 * @category time
2177 * @uses HOURSECS
2178 * @param int $date Timestamp in GMT
2179 * @param float|int|string $timezone timezone to calculate GMT time offset before
2180 * calculating user time, 99 is default user timezone
2181 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2182 * @return int
2184 function usertime($date, $timezone=99) {
2186 $timezone = get_user_timezone_offset($timezone);
2188 if (abs($timezone) > 13) {
2189 return $date;
2191 return $date - (int)($timezone * HOURSECS);
2195 * Given a time, return the GMT timestamp of the most recent midnight
2196 * for the current user.
2198 * @package core
2199 * @category time
2200 * @param int $date Timestamp in GMT
2201 * @param float|int|string $timezone timezone to calculate GMT time offset before
2202 * calculating user midnight time, 99 is default user timezone
2203 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2204 * @return int Returns a GMT timestamp
2206 function usergetmidnight($date, $timezone=99) {
2208 $userdate = usergetdate($date, $timezone);
2210 // Time of midnight of this user's day, in GMT
2211 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
2216 * Returns a string that prints the user's timezone
2218 * @package core
2219 * @category time
2220 * @param float|int|string $timezone timezone to calculate GMT time offset before
2221 * calculating user timezone, 99 is default user timezone
2222 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2223 * @return string
2225 function usertimezone($timezone=99) {
2227 $tz = get_user_timezone($timezone);
2229 if (!is_float($tz)) {
2230 return $tz;
2233 if(abs($tz) > 13) { // Server time
2234 return get_string('serverlocaltime');
2237 if($tz == intval($tz)) {
2238 // Don't show .0 for whole hours
2239 $tz = intval($tz);
2242 if($tz == 0) {
2243 return 'UTC';
2245 else if($tz > 0) {
2246 return 'UTC+'.$tz;
2248 else {
2249 return 'UTC'.$tz;
2255 * Returns a float which represents the user's timezone difference from GMT in hours
2256 * Checks various settings and picks the most dominant of those which have a value
2258 * @package core
2259 * @category time
2260 * @param float|int|string $tz timezone to calculate GMT time offset for user,
2261 * 99 is default user timezone
2262 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2263 * @return float
2265 function get_user_timezone_offset($tz = 99) {
2267 global $USER, $CFG;
2269 $tz = get_user_timezone($tz);
2271 if (is_float($tz)) {
2272 return $tz;
2273 } else {
2274 $tzrecord = get_timezone_record($tz);
2275 if (empty($tzrecord)) {
2276 return 99.0;
2278 return (float)$tzrecord->gmtoff / HOURMINS;
2283 * Returns an int which represents the systems's timezone difference from GMT in seconds
2285 * @package core
2286 * @category time
2287 * @param float|int|string $tz timezone for which offset is required.
2288 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2289 * @return int|bool if found, false is timezone 99 or error
2291 function get_timezone_offset($tz) {
2292 global $CFG;
2294 if ($tz == 99) {
2295 return false;
2298 if (is_numeric($tz)) {
2299 return intval($tz * 60*60);
2302 if (!$tzrecord = get_timezone_record($tz)) {
2303 return false;
2305 return intval($tzrecord->gmtoff * 60);
2309 * Returns a float or a string which denotes the user's timezone
2310 * 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)
2311 * means that for this timezone there are also DST rules to be taken into account
2312 * Checks various settings and picks the most dominant of those which have a value
2314 * @package core
2315 * @category time
2316 * @param float|int|string $tz timezone to calculate GMT time offset before
2317 * calculating user timezone, 99 is default user timezone
2318 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2319 * @return float|string
2321 function get_user_timezone($tz = 99) {
2322 global $USER, $CFG;
2324 $timezones = array(
2325 $tz,
2326 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
2327 isset($USER->timezone) ? $USER->timezone : 99,
2328 isset($CFG->timezone) ? $CFG->timezone : 99,
2331 $tz = 99;
2333 // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array
2334 while(((empty($tz) && !is_numeric($tz)) || $tz == 99) && $next = each($timezones)) {
2335 $tz = $next['value'];
2337 return is_numeric($tz) ? (float) $tz : $tz;
2341 * Returns cached timezone record for given $timezonename
2343 * @package core
2344 * @param string $timezonename name of the timezone
2345 * @return stdClass|bool timezonerecord or false
2347 function get_timezone_record($timezonename) {
2348 global $CFG, $DB;
2349 static $cache = NULL;
2351 if ($cache === NULL) {
2352 $cache = array();
2355 if (isset($cache[$timezonename])) {
2356 return $cache[$timezonename];
2359 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
2360 WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
2364 * Build and store the users Daylight Saving Time (DST) table
2366 * @package core
2367 * @param int $from_year Start year for the table, defaults to 1971
2368 * @param int $to_year End year for the table, defaults to 2035
2369 * @param int|float|string $strtimezone, timezone to check if dst should be applyed.
2370 * @return bool
2372 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
2373 global $CFG, $SESSION, $DB;
2375 $usertz = get_user_timezone($strtimezone);
2377 if (is_float($usertz)) {
2378 // Trivial timezone, no DST
2379 return false;
2382 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
2383 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2384 unset($SESSION->dst_offsets);
2385 unset($SESSION->dst_range);
2388 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
2389 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
2390 // This will be the return path most of the time, pretty light computationally
2391 return true;
2394 // Reaching here means we either need to extend our table or create it from scratch
2396 // Remember which TZ we calculated these changes for
2397 $SESSION->dst_offsettz = $usertz;
2399 if(empty($SESSION->dst_offsets)) {
2400 // If we 're creating from scratch, put the two guard elements in there
2401 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
2403 if(empty($SESSION->dst_range)) {
2404 // If creating from scratch
2405 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
2406 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
2408 // Fill in the array with the extra years we need to process
2409 $yearstoprocess = array();
2410 for($i = $from; $i <= $to; ++$i) {
2411 $yearstoprocess[] = $i;
2414 // Take note of which years we have processed for future calls
2415 $SESSION->dst_range = array($from, $to);
2417 else {
2418 // If needing to extend the table, do the same
2419 $yearstoprocess = array();
2421 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
2422 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
2424 if($from < $SESSION->dst_range[0]) {
2425 // Take note of which years we need to process and then note that we have processed them for future calls
2426 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2427 $yearstoprocess[] = $i;
2429 $SESSION->dst_range[0] = $from;
2431 if($to > $SESSION->dst_range[1]) {
2432 // Take note of which years we need to process and then note that we have processed them for future calls
2433 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2434 $yearstoprocess[] = $i;
2436 $SESSION->dst_range[1] = $to;
2440 if(empty($yearstoprocess)) {
2441 // This means that there was a call requesting a SMALLER range than we have already calculated
2442 return true;
2445 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2446 // Also, the array is sorted in descending timestamp order!
2448 // Get DB data
2450 static $presets_cache = array();
2451 if (!isset($presets_cache[$usertz])) {
2452 $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');
2454 if(empty($presets_cache[$usertz])) {
2455 return false;
2458 // Remove ending guard (first element of the array)
2459 reset($SESSION->dst_offsets);
2460 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2462 // Add all required change timestamps
2463 foreach($yearstoprocess as $y) {
2464 // Find the record which is in effect for the year $y
2465 foreach($presets_cache[$usertz] as $year => $preset) {
2466 if($year <= $y) {
2467 break;
2471 $changes = dst_changes_for_year($y, $preset);
2473 if($changes === NULL) {
2474 continue;
2476 if($changes['dst'] != 0) {
2477 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2479 if($changes['std'] != 0) {
2480 $SESSION->dst_offsets[$changes['std']] = 0;
2484 // Put in a guard element at the top
2485 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2486 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2488 // Sort again
2489 krsort($SESSION->dst_offsets);
2491 return true;
2495 * Calculates the required DST change and returns a Timestamp Array
2497 * @package core
2498 * @category time
2499 * @uses HOURSECS
2500 * @uses MINSECS
2501 * @param int|string $year Int or String Year to focus on
2502 * @param object $timezone Instatiated Timezone object
2503 * @return array|null Array dst=>xx, 0=>xx, std=>yy, 1=>yy or NULL
2505 function dst_changes_for_year($year, $timezone) {
2507 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2508 return NULL;
2511 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2512 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2514 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
2515 list($std_hour, $std_min) = explode(':', $timezone->std_time);
2517 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2518 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2520 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2521 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2522 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2524 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
2525 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
2527 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2531 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2532 * - Note: Daylight saving only works for string timezones and not for float.
2534 * @package core
2535 * @category time
2536 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2537 * @param int|float|string $strtimezone timezone for which offset is expected, if 99 or null
2538 * then user's default timezone is used. {@link http://docs.moodle.org/dev/Time_API#Timezone}
2539 * @return int
2541 function dst_offset_on($time, $strtimezone = NULL) {
2542 global $SESSION;
2544 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
2545 return 0;
2548 reset($SESSION->dst_offsets);
2549 while(list($from, $offset) = each($SESSION->dst_offsets)) {
2550 if($from <= $time) {
2551 break;
2555 // This is the normal return path
2556 if($offset !== NULL) {
2557 return $offset;
2560 // Reaching this point means we haven't calculated far enough, do it now:
2561 // Calculate extra DST changes if needed and recurse. The recursion always
2562 // moves toward the stopping condition, so will always end.
2564 if($from == 0) {
2565 // We need a year smaller than $SESSION->dst_range[0]
2566 if($SESSION->dst_range[0] == 1971) {
2567 return 0;
2569 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
2570 return dst_offset_on($time, $strtimezone);
2572 else {
2573 // We need a year larger than $SESSION->dst_range[1]
2574 if($SESSION->dst_range[1] == 2035) {
2575 return 0;
2577 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
2578 return dst_offset_on($time, $strtimezone);
2583 * Calculates when the day appears in specific month
2585 * @package core
2586 * @category time
2587 * @param int $startday starting day of the month
2588 * @param int $weekday The day when week starts (normally taken from user preferences)
2589 * @param int $month The month whose day is sought
2590 * @param int $year The year of the month whose day is sought
2591 * @return int
2593 function find_day_in_month($startday, $weekday, $month, $year) {
2595 $daysinmonth = days_in_month($month, $year);
2597 if($weekday == -1) {
2598 // Don't care about weekday, so return:
2599 // abs($startday) if $startday != -1
2600 // $daysinmonth otherwise
2601 return ($startday == -1) ? $daysinmonth : abs($startday);
2604 // From now on we 're looking for a specific weekday
2606 // Give "end of month" its actual value, since we know it
2607 if($startday == -1) {
2608 $startday = -1 * $daysinmonth;
2611 // Starting from day $startday, the sign is the direction
2613 if($startday < 1) {
2615 $startday = abs($startday);
2616 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2618 // This is the last such weekday of the month
2619 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2620 if($lastinmonth > $daysinmonth) {
2621 $lastinmonth -= 7;
2624 // Find the first such weekday <= $startday
2625 while($lastinmonth > $startday) {
2626 $lastinmonth -= 7;
2629 return $lastinmonth;
2632 else {
2634 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2636 $diff = $weekday - $indexweekday;
2637 if($diff < 0) {
2638 $diff += 7;
2641 // This is the first such weekday of the month equal to or after $startday
2642 $firstfromindex = $startday + $diff;
2644 return $firstfromindex;
2650 * Calculate the number of days in a given month
2652 * @package core
2653 * @category time
2654 * @param int $month The month whose day count is sought
2655 * @param int $year The year of the month whose day count is sought
2656 * @return int
2658 function days_in_month($month, $year) {
2659 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2663 * Calculate the position in the week of a specific calendar day
2665 * @package core
2666 * @category time
2667 * @param int $day The day of the date whose position in the week is sought
2668 * @param int $month The month of the date whose position in the week is sought
2669 * @param int $year The year of the date whose position in the week is sought
2670 * @return int
2672 function dayofweek($day, $month, $year) {
2673 // I wonder if this is any different from
2674 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2675 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2678 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2681 * Returns full login url.
2683 * @return string login url
2685 function get_login_url() {
2686 global $CFG;
2688 $url = "$CFG->wwwroot/login/index.php";
2690 if (!empty($CFG->loginhttps)) {
2691 $url = str_replace('http:', 'https:', $url);
2694 return $url;
2698 * This function checks that the current user is logged in and has the
2699 * required privileges
2701 * This function checks that the current user is logged in, and optionally
2702 * whether they are allowed to be in a particular course and view a particular
2703 * course module.
2704 * If they are not logged in, then it redirects them to the site login unless
2705 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2706 * case they are automatically logged in as guests.
2707 * If $courseid is given and the user is not enrolled in that course then the
2708 * user is redirected to the course enrolment page.
2709 * If $cm is given and the course module is hidden and the user is not a teacher
2710 * in the course then the user is redirected to the course home page.
2712 * When $cm parameter specified, this function sets page layout to 'module'.
2713 * You need to change it manually later if some other layout needed.
2715 * @package core_access
2716 * @category access
2718 * @param mixed $courseorid id of the course or course object
2719 * @param bool $autologinguest default true
2720 * @param object $cm course module object
2721 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2722 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2723 * in order to keep redirects working properly. MDL-14495
2724 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2725 * @return mixed Void, exit, and die depending on path
2727 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2728 global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
2730 // setup global $COURSE, themes, language and locale
2731 if (!empty($courseorid)) {
2732 if (is_object($courseorid)) {
2733 $course = $courseorid;
2734 } else if ($courseorid == SITEID) {
2735 $course = clone($SITE);
2736 } else {
2737 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2739 if ($cm) {
2740 if ($cm->course != $course->id) {
2741 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2743 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2744 if (!($cm instanceof cm_info)) {
2745 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2746 // db queries so this is not really a performance concern, however it is obviously
2747 // better if you use get_fast_modinfo to get the cm before calling this.
2748 $modinfo = get_fast_modinfo($course);
2749 $cm = $modinfo->get_cm($cm->id);
2751 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2752 $PAGE->set_pagelayout('incourse');
2753 } else {
2754 $PAGE->set_course($course); // set's up global $COURSE
2756 } else {
2757 // do not touch global $COURSE via $PAGE->set_course(),
2758 // the reasons is we need to be able to call require_login() at any time!!
2759 $course = $SITE;
2760 if ($cm) {
2761 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2765 // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2766 // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2767 // risk leading the user back to the AJAX request URL.
2768 if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
2769 $setwantsurltome = false;
2772 // If the user is not even logged in yet then make sure they are
2773 if (!isloggedin()) {
2774 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2775 if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2776 // misconfigured site guest, just redirect to login page
2777 redirect(get_login_url());
2778 exit; // never reached
2780 $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2781 complete_user_login($guest);
2782 $USER->autologinguest = true;
2783 $SESSION->lang = $lang;
2784 } else {
2785 //NOTE: $USER->site check was obsoleted by session test cookie,
2786 // $USER->confirmed test is in login/index.php
2787 if ($preventredirect) {
2788 throw new require_login_exception('You are not logged in');
2791 if ($setwantsurltome) {
2792 $SESSION->wantsurl = qualified_me();
2794 if (!empty($_SERVER['HTTP_REFERER'])) {
2795 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
2797 redirect(get_login_url());
2798 exit; // never reached
2802 // loginas as redirection if needed
2803 if ($course->id != SITEID and session_is_loggedinas()) {
2804 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2805 if ($USER->loginascontext->instanceid != $course->id) {
2806 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2811 // check whether the user should be changing password (but only if it is REALLY them)
2812 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2813 $userauth = get_auth_plugin($USER->auth);
2814 if ($userauth->can_change_password() and !$preventredirect) {
2815 if ($setwantsurltome) {
2816 $SESSION->wantsurl = qualified_me();
2818 if ($changeurl = $userauth->change_password_url()) {
2819 //use plugin custom url
2820 redirect($changeurl);
2821 } else {
2822 //use moodle internal method
2823 if (empty($CFG->loginhttps)) {
2824 redirect($CFG->wwwroot .'/login/change_password.php');
2825 } else {
2826 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
2827 redirect($wwwroot .'/login/change_password.php');
2830 } else {
2831 print_error('nopasswordchangeforced', 'auth');
2835 // Check that the user account is properly set up
2836 if (user_not_fully_set_up($USER)) {
2837 if ($preventredirect) {
2838 throw new require_login_exception('User not fully set-up');
2840 if ($setwantsurltome) {
2841 $SESSION->wantsurl = qualified_me();
2843 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2846 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2847 sesskey();
2849 // Do not bother admins with any formalities
2850 if (is_siteadmin()) {
2851 //set accesstime or the user will appear offline which messes up messaging
2852 user_accesstime_log($course->id);
2853 return;
2856 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2857 if (!$USER->policyagreed and !is_siteadmin()) {
2858 if (!empty($CFG->sitepolicy) and !isguestuser()) {
2859 if ($preventredirect) {
2860 throw new require_login_exception('Policy not agreed');
2862 if ($setwantsurltome) {
2863 $SESSION->wantsurl = qualified_me();
2865 redirect($CFG->wwwroot .'/user/policy.php');
2866 } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
2867 if ($preventredirect) {
2868 throw new require_login_exception('Policy not agreed');
2870 if ($setwantsurltome) {
2871 $SESSION->wantsurl = qualified_me();
2873 redirect($CFG->wwwroot .'/user/policy.php');
2877 // Fetch the system context, the course context, and prefetch its child contexts
2878 $sysctx = get_context_instance(CONTEXT_SYSTEM);
2879 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
2880 if ($cm) {
2881 $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
2882 } else {
2883 $cmcontext = null;
2886 // If the site is currently under maintenance, then print a message
2887 if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
2888 if ($preventredirect) {
2889 throw new require_login_exception('Maintenance in progress');
2892 print_maintenance_message();
2895 // make sure the course itself is not hidden
2896 if ($course->id == SITEID) {
2897 // frontpage can not be hidden
2898 } else {
2899 if (is_role_switched($course->id)) {
2900 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2901 } else {
2902 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2903 // originally there was also test of parent category visibility,
2904 // BUT is was very slow in complex queries involving "my courses"
2905 // now it is also possible to simply hide all courses user is not enrolled in :-)
2906 if ($preventredirect) {
2907 throw new require_login_exception('Course is hidden');
2909 // We need to override the navigation URL as the course won't have
2910 // been added to the navigation and thus the navigation will mess up
2911 // when trying to find it.
2912 navigation_node::override_active_url(new moodle_url('/'));
2913 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2918 // is the user enrolled?
2919 if ($course->id == SITEID) {
2920 // everybody is enrolled on the frontpage
2922 } else {
2923 if (session_is_loggedinas()) {
2924 // Make sure the REAL person can access this course first
2925 $realuser = session_get_realuser();
2926 if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
2927 if ($preventredirect) {
2928 throw new require_login_exception('Invalid course login-as access');
2930 echo $OUTPUT->header();
2931 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2935 $access = false;
2937 if (is_role_switched($course->id)) {
2938 // ok, user had to be inside this course before the switch
2939 $access = true;
2941 } else if (is_viewing($coursecontext, $USER)) {
2942 // ok, no need to mess with enrol
2943 $access = true;
2945 } else {
2946 if (isset($USER->enrol['enrolled'][$course->id])) {
2947 if ($USER->enrol['enrolled'][$course->id] > time()) {
2948 $access = true;
2949 if (isset($USER->enrol['tempguest'][$course->id])) {
2950 unset($USER->enrol['tempguest'][$course->id]);
2951 remove_temp_course_roles($coursecontext);
2953 } else {
2954 //expired
2955 unset($USER->enrol['enrolled'][$course->id]);
2958 if (isset($USER->enrol['tempguest'][$course->id])) {
2959 if ($USER->enrol['tempguest'][$course->id] == 0) {
2960 $access = true;
2961 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
2962 $access = true;
2963 } else {
2964 //expired
2965 unset($USER->enrol['tempguest'][$course->id]);
2966 remove_temp_course_roles($coursecontext);
2970 if ($access) {
2971 // cache ok
2972 } else {
2973 $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
2974 if ($until !== false) {
2975 // active participants may always access, a timestamp in the future, 0 (always) or false.
2976 if ($until == 0) {
2977 $until = ENROL_MAX_TIMESTAMP;
2979 $USER->enrol['enrolled'][$course->id] = $until;
2980 $access = true;
2982 } else {
2983 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2984 $enrols = enrol_get_plugins(true);
2985 // first ask all enabled enrol instances in course if they want to auto enrol user
2986 foreach($instances as $instance) {
2987 if (!isset($enrols[$instance->enrol])) {
2988 continue;
2990 // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
2991 $until = $enrols[$instance->enrol]->try_autoenrol($instance);
2992 if ($until !== false) {
2993 if ($until == 0) {
2994 $until = ENROL_MAX_TIMESTAMP;
2996 $USER->enrol['enrolled'][$course->id] = $until;
2997 $access = true;
2998 break;
3001 // if not enrolled yet try to gain temporary guest access
3002 if (!$access) {
3003 foreach($instances as $instance) {
3004 if (!isset($enrols[$instance->enrol])) {
3005 continue;
3007 // Get a duration for the guest access, a timestamp in the future or false.
3008 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
3009 if ($until !== false and $until > time()) {
3010 $USER->enrol['tempguest'][$course->id] = $until;
3011 $access = true;
3012 break;
3020 if (!$access) {
3021 if ($preventredirect) {
3022 throw new require_login_exception('Not enrolled');
3024 if ($setwantsurltome) {
3025 $SESSION->wantsurl = qualified_me();
3027 redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
3031 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
3032 // conditional availability, etc
3033 if ($cm && !$cm->uservisible) {
3034 if ($preventredirect) {
3035 throw new require_login_exception('Activity is hidden');
3037 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
3040 // Finally access granted, update lastaccess times
3041 user_accesstime_log($course->id);
3046 * This function just makes sure a user is logged out.
3048 * @package core_access
3050 function require_logout() {
3051 global $USER;
3053 $params = $USER;
3055 if (isloggedin()) {
3056 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
3058 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
3059 foreach($authsequence as $authname) {
3060 $authplugin = get_auth_plugin($authname);
3061 $authplugin->prelogout_hook();
3065 events_trigger('user_logout', $params);
3066 session_get_instance()->terminate_current();
3067 unset($params);
3071 * Weaker version of require_login()
3073 * This is a weaker version of {@link require_login()} which only requires login
3074 * when called from within a course rather than the site page, unless
3075 * the forcelogin option is turned on.
3076 * @see require_login()
3078 * @package core_access
3079 * @category access
3081 * @param mixed $courseorid The course object or id in question
3082 * @param bool $autologinguest Allow autologin guests if that is wanted
3083 * @param object $cm Course activity module if known
3084 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
3085 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
3086 * in order to keep redirects working properly. MDL-14495
3087 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
3088 * @return void
3090 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
3091 global $CFG, $PAGE, $SITE;
3092 $issite = (is_object($courseorid) and $courseorid->id == SITEID)
3093 or (!is_object($courseorid) and $courseorid == SITEID);
3094 if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
3095 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
3096 // db queries so this is not really a performance concern, however it is obviously
3097 // better if you use get_fast_modinfo to get the cm before calling this.
3098 if (is_object($courseorid)) {
3099 $course = $courseorid;
3100 } else {
3101 $course = clone($SITE);
3103 $modinfo = get_fast_modinfo($course);
3104 $cm = $modinfo->get_cm($cm->id);
3106 if (!empty($CFG->forcelogin)) {
3107 // login required for both SITE and courses
3108 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3110 } else if ($issite && !empty($cm) and !$cm->uservisible) {
3111 // always login for hidden activities
3112 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3114 } else if ($issite) {
3115 //login for SITE not required
3116 if ($cm and empty($cm->visible)) {
3117 // hidden activities are not accessible without login
3118 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3119 } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
3120 // not-logged-in users do not have any group membership
3121 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3122 } else {
3123 // We still need to instatiate PAGE vars properly so that things
3124 // that rely on it like navigation function correctly.
3125 if (!empty($courseorid)) {
3126 if (is_object($courseorid)) {
3127 $course = $courseorid;
3128 } else {
3129 $course = clone($SITE);
3131 if ($cm) {
3132 if ($cm->course != $course->id) {
3133 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
3135 $PAGE->set_cm($cm, $course);
3136 $PAGE->set_pagelayout('incourse');
3137 } else {
3138 $PAGE->set_course($course);
3140 } else {
3141 // If $PAGE->course, and hence $PAGE->context, have not already been set
3142 // up properly, set them up now.
3143 $PAGE->set_course($PAGE->course);
3145 //TODO: verify conditional activities here
3146 user_accesstime_log(SITEID);
3147 return;
3150 } else {
3151 // course login always required
3152 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3157 * Require key login. Function terminates with error if key not found or incorrect.
3159 * @global object
3160 * @global object
3161 * @global object
3162 * @global object
3163 * @uses NO_MOODLE_COOKIES
3164 * @uses PARAM_ALPHANUM
3165 * @param string $script unique script identifier
3166 * @param int $instance optional instance id
3167 * @return int Instance ID
3169 function require_user_key_login($script, $instance=null) {
3170 global $USER, $SESSION, $CFG, $DB;
3172 if (!NO_MOODLE_COOKIES) {
3173 print_error('sessioncookiesdisable');
3176 /// extra safety
3177 @session_write_close();
3179 $keyvalue = required_param('key', PARAM_ALPHANUM);
3181 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
3182 print_error('invalidkey');
3185 if (!empty($key->validuntil) and $key->validuntil < time()) {
3186 print_error('expiredkey');
3189 if ($key->iprestriction) {
3190 $remoteaddr = getremoteaddr(null);
3191 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
3192 print_error('ipmismatch');
3196 if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
3197 print_error('invaliduserid');
3200 /// emulate normal session
3201 enrol_check_plugins($user);
3202 session_set_user($user);
3204 /// note we are not using normal login
3205 if (!defined('USER_KEY_LOGIN')) {
3206 define('USER_KEY_LOGIN', true);
3209 /// return instance id - it might be empty
3210 return $key->instance;
3214 * Creates a new private user access key.
3216 * @global object
3217 * @param string $script unique target identifier
3218 * @param int $userid
3219 * @param int $instance optional instance id
3220 * @param string $iprestriction optional ip restricted access
3221 * @param timestamp $validuntil key valid only until given data
3222 * @return string access key value
3224 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3225 global $DB;
3227 $key = new stdClass();
3228 $key->script = $script;
3229 $key->userid = $userid;
3230 $key->instance = $instance;
3231 $key->iprestriction = $iprestriction;
3232 $key->validuntil = $validuntil;
3233 $key->timecreated = time();
3235 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
3236 while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
3237 // must be unique
3238 $key->value = md5($userid.'_'.time().random_string(40));
3240 $DB->insert_record('user_private_key', $key);
3241 return $key->value;
3245 * Delete the user's new private user access keys for a particular script.
3247 * @global object
3248 * @param string $script unique target identifier
3249 * @param int $userid
3250 * @return void
3252 function delete_user_key($script,$userid) {
3253 global $DB;
3254 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
3258 * Gets a private user access key (and creates one if one doesn't exist).
3260 * @global object
3261 * @param string $script unique target identifier
3262 * @param int $userid
3263 * @param int $instance optional instance id
3264 * @param string $iprestriction optional ip restricted access
3265 * @param timestamp $validuntil key valid only until given data
3266 * @return string access key value
3268 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3269 global $DB;
3271 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
3272 'instance'=>$instance, 'iprestriction'=>$iprestriction,
3273 'validuntil'=>$validuntil))) {
3274 return $key->value;
3275 } else {
3276 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
3282 * Modify the user table by setting the currently logged in user's
3283 * last login to now.
3285 * @global object
3286 * @global object
3287 * @return bool Always returns true
3289 function update_user_login_times() {
3290 global $USER, $DB;
3292 if (isguestuser()) {
3293 // Do not update guest access times/ips for performance.
3294 return true;
3297 $now = time();
3299 $user = new stdClass();
3300 $user->id = $USER->id;
3302 // Make sure all users that logged in have some firstaccess.
3303 if ($USER->firstaccess == 0) {
3304 $USER->firstaccess = $user->firstaccess = $now;
3307 // Store the previous current as lastlogin.
3308 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
3310 $USER->currentlogin = $user->currentlogin = $now;
3312 // Function user_accesstime_log() may not update immediately, better do it here.
3313 $USER->lastaccess = $user->lastaccess = $now;
3314 $USER->lastip = $user->lastip = getremoteaddr();
3316 $DB->update_record('user', $user);
3317 return true;
3321 * Determines if a user has completed setting up their account.
3323 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
3324 * @return bool
3326 function user_not_fully_set_up($user) {
3327 if (isguestuser($user)) {
3328 return false;
3330 return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
3334 * Check whether the user has exceeded the bounce threshold
3336 * @global object
3337 * @global object
3338 * @param user $user A {@link $USER} object
3339 * @return bool true=>User has exceeded bounce threshold
3341 function over_bounce_threshold($user) {
3342 global $CFG, $DB;
3344 if (empty($CFG->handlebounces)) {
3345 return false;
3348 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3349 return false;
3352 // set sensible defaults
3353 if (empty($CFG->minbounces)) {
3354 $CFG->minbounces = 10;
3356 if (empty($CFG->bounceratio)) {
3357 $CFG->bounceratio = .20;
3359 $bouncecount = 0;
3360 $sendcount = 0;
3361 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3362 $bouncecount = $bounce->value;
3364 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3365 $sendcount = $send->value;
3367 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
3371 * Used to increment or reset email sent count
3373 * @global object
3374 * @param user $user object containing an id
3375 * @param bool $reset will reset the count to 0
3376 * @return void
3378 function set_send_count($user,$reset=false) {
3379 global $DB;
3381 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3382 return;
3385 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3386 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3387 $DB->update_record('user_preferences', $pref);
3389 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3390 // make a new one
3391 $pref = new stdClass();
3392 $pref->name = 'email_send_count';
3393 $pref->value = 1;
3394 $pref->userid = $user->id;
3395 $DB->insert_record('user_preferences', $pref, false);
3400 * Increment or reset user's email bounce count
3402 * @global object
3403 * @param user $user object containing an id
3404 * @param bool $reset will reset the count to 0
3406 function set_bounce_count($user,$reset=false) {
3407 global $DB;
3409 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3410 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3411 $DB->update_record('user_preferences', $pref);
3413 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3414 // make a new one
3415 $pref = new stdClass();
3416 $pref->name = 'email_bounce_count';
3417 $pref->value = 1;
3418 $pref->userid = $user->id;
3419 $DB->insert_record('user_preferences', $pref, false);
3424 * Keeps track of login attempts
3426 * @global object
3428 function update_login_count() {
3429 global $SESSION;
3431 $max_logins = 10;
3433 if (empty($SESSION->logincount)) {
3434 $SESSION->logincount = 1;
3435 } else {
3436 $SESSION->logincount++;
3439 if ($SESSION->logincount > $max_logins) {
3440 unset($SESSION->wantsurl);
3441 print_error('errortoomanylogins');
3446 * Resets login attempts
3448 * @global object
3450 function reset_login_count() {
3451 global $SESSION;
3453 $SESSION->logincount = 0;
3457 * Determines if the currently logged in user is in editing mode.
3458 * Note: originally this function had $userid parameter - it was not usable anyway
3460 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3461 * @todo Deprecated function remove when ready
3463 * @global object
3464 * @uses DEBUG_DEVELOPER
3465 * @return bool
3467 function isediting() {
3468 global $PAGE;
3469 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
3470 return $PAGE->user_is_editing();
3474 * Determines if the logged in user is currently moving an activity
3476 * @global object
3477 * @param int $courseid The id of the course being tested
3478 * @return bool
3480 function ismoving($courseid) {
3481 global $USER;
3483 if (!empty($USER->activitycopy)) {
3484 return ($USER->activitycopycourse == $courseid);
3486 return false;
3490 * Returns a persons full name
3492 * Given an object containing firstname and lastname
3493 * values, this function returns a string with the
3494 * full name of the person.
3495 * The result may depend on system settings
3496 * or language. 'override' will force both names
3497 * to be used even if system settings specify one.
3499 * @global object
3500 * @global object
3501 * @param object $user A {@link $USER} object to get full name of
3502 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3503 * @return string
3505 function fullname($user, $override=false) {
3506 global $CFG, $SESSION;
3508 if (!isset($user->firstname) and !isset($user->lastname)) {
3509 return '';
3512 if (!$override) {
3513 if (!empty($CFG->forcefirstname)) {
3514 $user->firstname = $CFG->forcefirstname;
3516 if (!empty($CFG->forcelastname)) {
3517 $user->lastname = $CFG->forcelastname;
3521 if (!empty($SESSION->fullnamedisplay)) {
3522 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3525 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
3526 return $user->firstname .' '. $user->lastname;
3528 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
3529 return $user->lastname .' '. $user->firstname;
3531 } else if ($CFG->fullnamedisplay == 'firstname') {
3532 if ($override) {
3533 return get_string('fullnamedisplay', '', $user);
3534 } else {
3535 return $user->firstname;
3539 return get_string('fullnamedisplay', '', $user);
3543 * Checks if current user is shown any extra fields when listing users.
3544 * @param object $context Context
3545 * @param array $already Array of fields that we're going to show anyway
3546 * so don't bother listing them
3547 * @return array Array of field names from user table, not including anything
3548 * listed in $already
3550 function get_extra_user_fields($context, $already = array()) {
3551 global $CFG;
3553 // Only users with permission get the extra fields
3554 if (!has_capability('moodle/site:viewuseridentity', $context)) {
3555 return array();
3558 // Split showuseridentity on comma
3559 if (empty($CFG->showuseridentity)) {
3560 // Explode gives wrong result with empty string
3561 $extra = array();
3562 } else {
3563 $extra = explode(',', $CFG->showuseridentity);
3565 $renumber = false;
3566 foreach ($extra as $key => $field) {
3567 if (in_array($field, $already)) {
3568 unset($extra[$key]);
3569 $renumber = true;
3572 if ($renumber) {
3573 // For consistency, if entries are removed from array, renumber it
3574 // so they are numbered as you would expect
3575 $extra = array_merge($extra);
3577 return $extra;
3581 * If the current user is to be shown extra user fields when listing or
3582 * selecting users, returns a string suitable for including in an SQL select
3583 * clause to retrieve those fields.
3584 * @param object $context Context
3585 * @param string $alias Alias of user table, e.g. 'u' (default none)
3586 * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none)
3587 * @param array $already Array of fields that we're going to include anyway
3588 * so don't list them (default none)
3589 * @return string Partial SQL select clause, beginning with comma, for example
3590 * ',u.idnumber,u.department' unless it is blank
3592 function get_extra_user_fields_sql($context, $alias='', $prefix='',
3593 $already = array()) {
3594 $fields = get_extra_user_fields($context, $already);
3595 $result = '';
3596 // Add punctuation for alias
3597 if ($alias !== '') {
3598 $alias .= '.';
3600 foreach ($fields as $field) {
3601 $result .= ', ' . $alias . $field;
3602 if ($prefix) {
3603 $result .= ' AS ' . $prefix . $field;
3606 return $result;
3610 * Returns the display name of a field in the user table. Works for most fields
3611 * that are commonly displayed to users.
3612 * @param string $field Field name, e.g. 'phone1'
3613 * @return string Text description taken from language file, e.g. 'Phone number'
3615 function get_user_field_name($field) {
3616 // Some fields have language strings which are not the same as field name
3617 switch ($field) {
3618 case 'phone1' : return get_string('phone');
3620 // Otherwise just use the same lang string
3621 return get_string($field);
3625 * Returns whether a given authentication plugin exists.
3627 * @global object
3628 * @param string $auth Form of authentication to check for. Defaults to the
3629 * global setting in {@link $CFG}.
3630 * @return boolean Whether the plugin is available.
3632 function exists_auth_plugin($auth) {
3633 global $CFG;
3635 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3636 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3638 return false;
3642 * Checks if a given plugin is in the list of enabled authentication plugins.
3644 * @param string $auth Authentication plugin.
3645 * @return boolean Whether the plugin is enabled.
3647 function is_enabled_auth($auth) {
3648 if (empty($auth)) {
3649 return false;
3652 $enabled = get_enabled_auth_plugins();
3654 return in_array($auth, $enabled);
3658 * Returns an authentication plugin instance.
3660 * @global object
3661 * @param string $auth name of authentication plugin
3662 * @return auth_plugin_base An instance of the required authentication plugin.
3664 function get_auth_plugin($auth) {
3665 global $CFG;
3667 // check the plugin exists first
3668 if (! exists_auth_plugin($auth)) {
3669 print_error('authpluginnotfound', 'debug', '', $auth);
3672 // return auth plugin instance
3673 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3674 $class = "auth_plugin_$auth";
3675 return new $class;
3679 * Returns array of active auth plugins.
3681 * @param bool $fix fix $CFG->auth if needed
3682 * @return array
3684 function get_enabled_auth_plugins($fix=false) {
3685 global $CFG;
3687 $default = array('manual', 'nologin');
3689 if (empty($CFG->auth)) {
3690 $auths = array();
3691 } else {
3692 $auths = explode(',', $CFG->auth);
3695 if ($fix) {
3696 $auths = array_unique($auths);
3697 foreach($auths as $k=>$authname) {
3698 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3699 unset($auths[$k]);
3702 $newconfig = implode(',', $auths);
3703 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
3704 set_config('auth', $newconfig);
3708 return (array_merge($default, $auths));
3712 * Returns true if an internal authentication method is being used.
3713 * if method not specified then, global default is assumed
3715 * @param string $auth Form of authentication required
3716 * @return bool
3718 function is_internal_auth($auth) {
3719 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3720 return $authplugin->is_internal();
3724 * Returns true if the user is a 'restored' one
3726 * Used in the login process to inform the user
3727 * and allow him/her to reset the password
3729 * @uses $CFG
3730 * @uses $DB
3731 * @param string $username username to be checked
3732 * @return bool
3734 function is_restored_user($username) {
3735 global $CFG, $DB;
3737 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
3741 * Returns an array of user fields
3743 * @return array User field/column names
3745 function get_user_fieldnames() {
3746 global $DB;
3748 $fieldarray = $DB->get_columns('user');
3749 unset($fieldarray['id']);
3750 $fieldarray = array_keys($fieldarray);
3752 return $fieldarray;
3756 * Creates a bare-bones user record
3758 * @todo Outline auth types and provide code example
3760 * @param string $username New user's username to add to record
3761 * @param string $password New user's password to add to record
3762 * @param string $auth Form of authentication required
3763 * @return stdClass A complete user object
3765 function create_user_record($username, $password, $auth = 'manual') {
3766 global $CFG, $DB;
3768 //just in case check text case
3769 $username = trim(textlib::strtolower($username));
3771 $authplugin = get_auth_plugin($auth);
3773 $newuser = new stdClass();
3775 if ($newinfo = $authplugin->get_userinfo($username)) {
3776 $newinfo = truncate_userinfo($newinfo);
3777 foreach ($newinfo as $key => $value){
3778 $newuser->$key = $value;
3782 if (!empty($newuser->email)) {
3783 if (email_is_not_allowed($newuser->email)) {
3784 unset($newuser->email);
3788 if (!isset($newuser->city)) {
3789 $newuser->city = '';
3792 $newuser->auth = $auth;
3793 $newuser->username = $username;
3795 // fix for MDL-8480
3796 // user CFG lang for user if $newuser->lang is empty
3797 // or $user->lang is not an installed language
3798 if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
3799 $newuser->lang = $CFG->lang;
3801 $newuser->confirmed = 1;
3802 $newuser->lastip = getremoteaddr();
3803 $newuser->timecreated = time();
3804 $newuser->timemodified = $newuser->timecreated;
3805 $newuser->mnethostid = $CFG->mnet_localhost_id;
3807 $newuser->id = $DB->insert_record('user', $newuser);
3808 $user = get_complete_user_data('id', $newuser->id);
3809 if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
3810 set_user_preference('auth_forcepasswordchange', 1, $user);
3812 update_internal_user_password($user, $password);
3814 // fetch full user record for the event, the complete user data contains too much info
3815 // and we want to be consistent with other places that trigger this event
3816 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
3818 return $user;
3822 * Will update a local user record from an external source.
3823 * (MNET users can not be updated using this method!)
3825 * @param string $username user's username to update the record
3826 * @return stdClass A complete user object
3828 function update_user_record($username) {
3829 global $DB, $CFG;
3831 $username = trim(textlib::strtolower($username)); /// just in case check text case
3833 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
3834 $newuser = array();
3835 $userauth = get_auth_plugin($oldinfo->auth);
3837 if ($newinfo = $userauth->get_userinfo($username)) {
3838 $newinfo = truncate_userinfo($newinfo);
3839 foreach ($newinfo as $key => $value){
3840 $key = strtolower($key);
3841 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3842 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3843 // unknown or must not be changed
3844 continue;
3846 $confval = $userauth->config->{'field_updatelocal_' . $key};
3847 $lockval = $userauth->config->{'field_lock_' . $key};
3848 if (empty($confval) || empty($lockval)) {
3849 continue;
3851 if ($confval === 'onlogin') {
3852 // MDL-4207 Don't overwrite modified user profile values with
3853 // empty LDAP values when 'unlocked if empty' is set. The purpose
3854 // of the setting 'unlocked if empty' is to allow the user to fill
3855 // in a value for the selected field _if LDAP is giving
3856 // nothing_ for this field. Thus it makes sense to let this value
3857 // stand in until LDAP is giving a value for this field.
3858 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3859 if ((string)$oldinfo->$key !== (string)$value) {
3860 $newuser[$key] = (string)$value;
3865 if ($newuser) {
3866 $newuser['id'] = $oldinfo->id;
3867 $newuser['timemodified'] = time();
3868 $DB->update_record('user', $newuser);
3869 // fetch full user record for the event, the complete user data contains too much info
3870 // and we want to be consistent with other places that trigger this event
3871 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
3875 return get_complete_user_data('id', $oldinfo->id);
3879 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3880 * which may have large fields
3882 * @todo Add vartype handling to ensure $info is an array
3884 * @param array $info Array of user properties to truncate if needed
3885 * @return array The now truncated information that was passed in
3887 function truncate_userinfo($info) {
3888 // define the limits
3889 $limit = array(
3890 'username' => 100,
3891 'idnumber' => 255,
3892 'firstname' => 100,
3893 'lastname' => 100,
3894 'email' => 100,
3895 'icq' => 15,
3896 'phone1' => 20,
3897 'phone2' => 20,
3898 'institution' => 40,
3899 'department' => 30,
3900 'address' => 70,
3901 'city' => 120,
3902 'country' => 2,
3903 'url' => 255,
3906 // apply where needed
3907 foreach (array_keys($info) as $key) {
3908 if (!empty($limit[$key])) {
3909 $info[$key] = trim(textlib::substr($info[$key],0, $limit[$key]));
3913 return $info;
3917 * Marks user deleted in internal user database and notifies the auth plugin.
3918 * Also unenrols user from all roles and does other cleanup.
3920 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3922 * @param stdClass $user full user object before delete
3923 * @return boolean success
3924 * @throws coding_exception if invalid $user parameter detected
3926 function delete_user(stdClass $user) {
3927 global $CFG, $DB;
3928 require_once($CFG->libdir.'/grouplib.php');
3929 require_once($CFG->libdir.'/gradelib.php');
3930 require_once($CFG->dirroot.'/message/lib.php');
3931 require_once($CFG->dirroot.'/tag/lib.php');
3933 // Make sure nobody sends bogus record type as parameter.
3934 if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
3935 throw new coding_exception('Invalid $user parameter in delete_user() detected');
3938 // Better not trust the parameter and fetch the latest info,
3939 // this will be very expensive anyway.
3940 if (!$user = $DB->get_record('user', array('id'=>$user->id))) {
3941 debugging('Attempt to delete unknown user account.');
3942 return false;
3945 // There must be always exactly one guest record,
3946 // originally the guest account was identified by username only,
3947 // now we use $CFG->siteguest for performance reasons.
3948 if ($user->username === 'guest' or isguestuser($user)) {
3949 debugging('Guest user account can not be deleted.');
3950 return false;
3953 // Admin can be theoretically from different auth plugin,
3954 // but we want to prevent deletion of internal accoutns only,
3955 // if anything goes wrong ppl may force somebody to be admin via
3956 // config.php setting $CFG->siteadmins.
3957 if ($user->auth === 'manual' and is_siteadmin($user)) {
3958 debugging('Local administrator accounts can not be deleted.');
3959 return false;
3962 // delete all grades - backup is kept in grade_grades_history table
3963 grade_user_delete($user->id);
3965 //move unread messages from this user to read
3966 message_move_userfrom_unread2read($user->id);
3968 // TODO: remove from cohorts using standard API here
3970 // remove user tags
3971 tag_set('user', $user->id, array());
3973 // unconditionally unenrol from all courses
3974 enrol_user_delete($user);
3976 // unenrol from all roles in all contexts
3977 role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
3979 //now do a brute force cleanup
3981 // remove from all cohorts
3982 $DB->delete_records('cohort_members', array('userid'=>$user->id));
3984 // remove from all groups
3985 $DB->delete_records('groups_members', array('userid'=>$user->id));
3987 // brute force unenrol from all courses
3988 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
3990 // purge user preferences
3991 $DB->delete_records('user_preferences', array('userid'=>$user->id));
3993 // purge user extra profile info
3994 $DB->delete_records('user_info_data', array('userid'=>$user->id));
3996 // last course access not necessary either
3997 $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
3999 // remove all user tokens
4000 $DB->delete_records('external_tokens', array('userid'=>$user->id));
4002 // unauthorise the user for all services
4003 $DB->delete_records('external_services_users', array('userid'=>$user->id));
4005 // force logout - may fail if file based sessions used, sorry
4006 session_kill_user($user->id);
4008 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
4009 delete_context(CONTEXT_USER, $user->id);
4011 // workaround for bulk deletes of users with the same email address
4012 $delname = "$user->email.".time();
4013 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
4014 $delname++;
4017 // mark internal user record as "deleted"
4018 $updateuser = new stdClass();
4019 $updateuser->id = $user->id;
4020 $updateuser->deleted = 1;
4021 $updateuser->username = $delname; // Remember it just in case
4022 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
4023 $updateuser->idnumber = ''; // Clear this field to free it up
4024 $updateuser->picture = 0;
4025 $updateuser->timemodified = time();
4027 $DB->update_record('user', $updateuser);
4028 // Add this action to log
4029 add_to_log(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
4032 // We will update the user's timemodified, as it will be passed to the user_deleted event, which
4033 // should know about this updated property persisted to the user's table.
4034 $user->timemodified = $updateuser->timemodified;
4036 // notify auth plugin - do not block the delete even when plugin fails
4037 $authplugin = get_auth_plugin($user->auth);
4038 $authplugin->user_delete($user);
4040 // any plugin that needs to cleanup should register this event
4041 events_trigger('user_deleted', $user);
4043 return true;
4047 * Retrieve the guest user object
4049 * @global object
4050 * @global object
4051 * @return user A {@link $USER} object
4053 function guest_user() {
4054 global $CFG, $DB;
4056 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
4057 $newuser->confirmed = 1;
4058 $newuser->lang = $CFG->lang;
4059 $newuser->lastip = getremoteaddr();
4062 return $newuser;
4066 * Authenticates a user against the chosen authentication mechanism
4068 * Given a username and password, this function looks them
4069 * up using the currently selected authentication mechanism,
4070 * and if the authentication is successful, it returns a
4071 * valid $user object from the 'user' table.
4073 * Uses auth_ functions from the currently active auth module
4075 * After authenticate_user_login() returns success, you will need to
4076 * log that the user has logged in, and call complete_user_login() to set
4077 * the session up.
4079 * Note: this function works only with non-mnet accounts!
4081 * @param string $username User's username
4082 * @param string $password User's password
4083 * @return user|flase A {@link $USER} object or false if error
4085 function authenticate_user_login($username, $password) {
4086 global $CFG, $DB;
4088 $authsenabled = get_enabled_auth_plugins();
4090 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
4091 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
4092 if (!empty($user->suspended)) {
4093 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4094 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4095 return false;
4097 if ($auth=='nologin' or !is_enabled_auth($auth)) {
4098 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4099 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4100 return false;
4102 $auths = array($auth);
4104 } else {
4105 // check if there's a deleted record (cheaply)
4106 if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
4107 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4108 return false;
4111 // User does not exist
4112 $auths = $authsenabled;
4113 $user = new stdClass();
4114 $user->id = 0;
4117 foreach ($auths as $auth) {
4118 $authplugin = get_auth_plugin($auth);
4120 // on auth fail fall through to the next plugin
4121 if (!$authplugin->user_login($username, $password)) {
4122 continue;
4125 // successful authentication
4126 if ($user->id) { // User already exists in database
4127 if (empty($user->auth)) { // For some reason auth isn't set yet
4128 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
4129 $user->auth = $auth;
4132 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
4134 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
4135 $user = update_user_record($username);
4137 } else {
4138 // if user not found and user creation is not disabled, create it
4139 if (empty($CFG->authpreventaccountcreation)) {
4140 $user = create_user_record($username, $password, $auth);
4141 } else {
4142 continue;
4146 $authplugin->sync_roles($user);
4148 foreach ($authsenabled as $hau) {
4149 $hauth = get_auth_plugin($hau);
4150 $hauth->user_authenticated_hook($user, $username, $password);
4153 if (empty($user->id)) {
4154 return false;
4157 if (!empty($user->suspended)) {
4158 // just in case some auth plugin suspended account
4159 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4160 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4161 return false;
4164 return $user;
4167 // failed if all the plugins have failed
4168 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4169 if (debugging('', DEBUG_ALL)) {
4170 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4172 return false;
4176 * Call to complete the user login process after authenticate_user_login()
4177 * has succeeded. It will setup the $USER variable and other required bits
4178 * and pieces.
4180 * NOTE:
4181 * - It will NOT log anything -- up to the caller to decide what to log.
4182 * - this function does not set any cookies any more!
4184 * @param object $user
4185 * @return object A {@link $USER} object - BC only, do not use
4187 function complete_user_login($user) {
4188 global $CFG, $USER;
4190 // regenerate session id and delete old session,
4191 // this helps prevent session fixation attacks from the same domain
4192 session_regenerate_id(true);
4194 // let enrol plugins deal with new enrolments if necessary
4195 enrol_check_plugins($user);
4197 // check enrolments, load caps and setup $USER object
4198 session_set_user($user);
4200 // reload preferences from DB
4201 unset($USER->preference);
4202 check_user_preferences_loaded($USER);
4204 // update login times
4205 update_user_login_times();
4207 // extra session prefs init
4208 set_login_session_preferences();
4210 if (isguestuser()) {
4211 // no need to continue when user is THE guest
4212 return $USER;
4215 /// Select password change url
4216 $userauth = get_auth_plugin($USER->auth);
4218 /// check whether the user should be changing password
4219 if (get_user_preferences('auth_forcepasswordchange', false)){
4220 if ($userauth->can_change_password()) {
4221 if ($changeurl = $userauth->change_password_url()) {
4222 redirect($changeurl);
4223 } else {
4224 redirect($CFG->httpswwwroot.'/login/change_password.php');
4226 } else {
4227 print_error('nopasswordchangeforced', 'auth');
4230 return $USER;
4234 * Compare password against hash stored in internal user table.
4235 * If necessary it also updates the stored hash to new format.
4237 * @param stdClass $user (password property may be updated)
4238 * @param string $password plain text password
4239 * @return bool is password valid?
4241 function validate_internal_user_password($user, $password) {
4242 global $CFG;
4244 if (!isset($CFG->passwordsaltmain)) {
4245 $CFG->passwordsaltmain = '';
4248 $validated = false;
4250 if ($user->password === 'not cached') {
4251 // internal password is not used at all, it can not validate
4253 } else if ($user->password === md5($password.$CFG->passwordsaltmain)
4254 or $user->password === md5($password)
4255 or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
4256 or $user->password === md5(addslashes($password))) {
4257 // note: we are intentionally using the addslashes() here because we
4258 // need to accept old password hashes of passwords with magic quotes
4259 $validated = true;
4261 } else {
4262 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
4263 $alt = 'passwordsaltalt'.$i;
4264 if (!empty($CFG->$alt)) {
4265 if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
4266 $validated = true;
4267 break;
4273 if ($validated) {
4274 // force update of password hash using latest main password salt and encoding if needed
4275 update_internal_user_password($user, $password);
4278 return $validated;
4282 * Calculate hashed value from password using current hash mechanism.
4284 * @param string $password
4285 * @return string password hash
4287 function hash_internal_user_password($password) {
4288 global $CFG;
4290 if (isset($CFG->passwordsaltmain)) {
4291 return md5($password.$CFG->passwordsaltmain);
4292 } else {
4293 return md5($password);
4298 * Update password hash in user object.
4300 * @param stdClass $user (password property may be updated)
4301 * @param string $password plain text password
4302 * @return bool always returns true
4304 function update_internal_user_password($user, $password) {
4305 global $DB;
4307 $authplugin = get_auth_plugin($user->auth);
4308 if ($authplugin->prevent_local_passwords()) {
4309 $hashedpassword = 'not cached';
4310 } else {
4311 $hashedpassword = hash_internal_user_password($password);
4314 if ($user->password !== $hashedpassword) {
4315 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
4316 $user->password = $hashedpassword;
4319 return true;
4323 * Get a complete user record, which includes all the info
4324 * in the user record.
4326 * Intended for setting as $USER session variable
4328 * @param string $field The user field to be checked for a given value.
4329 * @param string $value The value to match for $field.
4330 * @param int $mnethostid
4331 * @return mixed False, or A {@link $USER} object.
4333 function get_complete_user_data($field, $value, $mnethostid = null) {
4334 global $CFG, $DB;
4336 if (!$field || !$value) {
4337 return false;
4340 /// Build the WHERE clause for an SQL query
4341 $params = array('fieldval'=>$value);
4342 $constraints = "$field = :fieldval AND deleted <> 1";
4344 // If we are loading user data based on anything other than id,
4345 // we must also restrict our search based on mnet host.
4346 if ($field != 'id') {
4347 if (empty($mnethostid)) {
4348 // if empty, we restrict to local users
4349 $mnethostid = $CFG->mnet_localhost_id;
4352 if (!empty($mnethostid)) {
4353 $params['mnethostid'] = $mnethostid;
4354 $constraints .= " AND mnethostid = :mnethostid";
4357 /// Get all the basic user data
4359 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
4360 return false;
4363 /// Get various settings and preferences
4365 // preload preference cache
4366 check_user_preferences_loaded($user);
4368 // load course enrolment related stuff
4369 $user->lastcourseaccess = array(); // during last session
4370 $user->currentcourseaccess = array(); // during current session
4371 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
4372 foreach ($lastaccesses as $lastaccess) {
4373 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
4377 $sql = "SELECT g.id, g.courseid
4378 FROM {groups} g, {groups_members} gm
4379 WHERE gm.groupid=g.id AND gm.userid=?";
4381 // this is a special hack to speedup calendar display
4382 $user->groupmember = array();
4383 if (!isguestuser($user)) {
4384 if ($groups = $DB->get_records_sql($sql, array($user->id))) {
4385 foreach ($groups as $group) {
4386 if (!array_key_exists($group->courseid, $user->groupmember)) {
4387 $user->groupmember[$group->courseid] = array();
4389 $user->groupmember[$group->courseid][$group->id] = $group->id;
4394 /// Add the custom profile fields to the user record
4395 $user->profile = array();
4396 if (!isguestuser($user)) {
4397 require_once($CFG->dirroot.'/user/profile/lib.php');
4398 profile_load_custom_fields($user);
4401 /// Rewrite some variables if necessary
4402 if (!empty($user->description)) {
4403 $user->description = true; // No need to cart all of it around
4405 if (isguestuser($user)) {
4406 $user->lang = $CFG->lang; // Guest language always same as site
4407 $user->firstname = get_string('guestuser'); // Name always in current language
4408 $user->lastname = ' ';
4411 return $user;
4415 * Validate a password against the configured password policy
4417 * @global object
4418 * @param string $password the password to be checked against the password policy
4419 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
4420 * @return bool true if the password is valid according to the policy. false otherwise.
4422 function check_password_policy($password, &$errmsg) {
4423 global $CFG;
4425 if (empty($CFG->passwordpolicy)) {
4426 return true;
4429 $errmsg = '';
4430 if (textlib::strlen($password) < $CFG->minpasswordlength) {
4431 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
4434 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
4435 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
4438 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
4439 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
4442 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
4443 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
4446 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
4447 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
4449 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
4450 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
4453 if ($errmsg == '') {
4454 return true;
4455 } else {
4456 return false;
4462 * When logging in, this function is run to set certain preferences
4463 * for the current SESSION
4465 * @global object
4466 * @global object
4468 function set_login_session_preferences() {
4469 global $SESSION, $CFG;
4471 $SESSION->justloggedin = true;
4473 unset($SESSION->lang);
4478 * Delete a course, including all related data from the database,
4479 * and any associated files.
4481 * @global object
4482 * @global object
4483 * @param mixed $courseorid The id of the course or course object to delete.
4484 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4485 * @return bool true if all the removals succeeded. false if there were any failures. If this
4486 * method returns false, some of the removals will probably have succeeded, and others
4487 * failed, but you have no way of knowing which.
4489 function delete_course($courseorid, $showfeedback = true) {
4490 global $DB;
4492 if (is_object($courseorid)) {
4493 $courseid = $courseorid->id;
4494 $course = $courseorid;
4495 } else {
4496 $courseid = $courseorid;
4497 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
4498 return false;
4501 $context = get_context_instance(CONTEXT_COURSE, $courseid);
4503 // frontpage course can not be deleted!!
4504 if ($courseid == SITEID) {
4505 return false;
4508 // make the course completely empty
4509 remove_course_contents($courseid, $showfeedback);
4511 // delete the course and related context instance
4512 delete_context(CONTEXT_COURSE, $courseid);
4514 // We will update the course's timemodified, as it will be passed to the course_deleted event,
4515 // which should know about this updated property, as this event is meant to pass the full course record
4516 $course->timemodified = time();
4518 $DB->delete_records("course", array("id"=>$courseid));
4520 //trigger events
4521 $course->context = $context; // you can not fetch context in the event because it was already deleted
4522 events_trigger('course_deleted', $course);
4524 return true;
4528 * Clear a course out completely, deleting all content
4529 * but don't delete the course itself.
4530 * This function does not verify any permissions.
4532 * Please note this function also deletes all user enrolments,
4533 * enrolment instances and role assignments by default.
4535 * $options:
4536 * - 'keep_roles_and_enrolments' - false by default
4537 * - 'keep_groups_and_groupings' - false by default
4539 * @param int $courseid The id of the course that is being deleted
4540 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4541 * @param array $options extra options
4542 * @return bool true if all the removals succeeded. false if there were any failures. If this
4543 * method returns false, some of the removals will probably have succeeded, and others
4544 * failed, but you have no way of knowing which.
4546 function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
4547 global $CFG, $DB, $OUTPUT;
4548 require_once($CFG->libdir.'/completionlib.php');
4549 require_once($CFG->libdir.'/questionlib.php');
4550 require_once($CFG->libdir.'/gradelib.php');
4551 require_once($CFG->dirroot.'/group/lib.php');
4552 require_once($CFG->dirroot.'/tag/coursetagslib.php');
4553 require_once($CFG->dirroot.'/comment/lib.php');
4554 require_once($CFG->dirroot.'/rating/lib.php');
4556 // NOTE: these concatenated strings are suboptimal, but it is just extra info...
4557 $strdeleted = get_string('deleted').' - ';
4559 // Some crazy wishlist of stuff we should skip during purging of course content
4560 $options = (array)$options;
4562 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
4563 $coursecontext = context_course::instance($courseid);
4564 $fs = get_file_storage();
4566 // Delete course completion information, this has to be done before grades and enrols
4567 $cc = new completion_info($course);
4568 $cc->clear_criteria();
4569 if ($showfeedback) {
4570 echo $OUTPUT->notification($strdeleted.get_string('completion', 'completion'), 'notifysuccess');
4573 // Remove all data from gradebook - this needs to be done before course modules
4574 // because while deleting this information, the system may need to reference
4575 // the course modules that own the grades.
4576 remove_course_grades($courseid, $showfeedback);
4577 remove_grade_letters($coursecontext, $showfeedback);
4579 // Delete course blocks in any all child contexts,
4580 // they may depend on modules so delete them first
4581 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4582 foreach ($childcontexts as $childcontext) {
4583 blocks_delete_all_for_context($childcontext->id);
4585 unset($childcontexts);
4586 blocks_delete_all_for_context($coursecontext->id);
4587 if ($showfeedback) {
4588 echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
4591 // Delete every instance of every module,
4592 // this has to be done before deleting of course level stuff
4593 $locations = get_plugin_list('mod');
4594 foreach ($locations as $modname=>$moddir) {
4595 if ($modname === 'NEWMODULE') {
4596 continue;
4598 if ($module = $DB->get_record('modules', array('name'=>$modname))) {
4599 include_once("$moddir/lib.php"); // Shows php warning only if plugin defective
4600 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4601 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4603 if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
4604 foreach ($instances as $instance) {
4605 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
4606 /// Delete activity context questions and question categories
4607 question_delete_activity($cm, $showfeedback);
4609 if (function_exists($moddelete)) {
4610 // This purges all module data in related tables, extra user prefs, settings, etc.
4611 $moddelete($instance->id);
4612 } else {
4613 // NOTE: we should not allow installation of modules with missing delete support!
4614 debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
4615 $DB->delete_records($modname, array('id'=>$instance->id));
4618 if ($cm) {
4619 // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition
4620 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4621 $DB->delete_records('course_modules', array('id'=>$cm->id));
4625 if (function_exists($moddeletecourse)) {
4626 // Execute ptional course cleanup callback
4627 $moddeletecourse($course, $showfeedback);
4629 if ($instances and $showfeedback) {
4630 echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
4632 } else {
4633 // Ooops, this module is not properly installed, force-delete it in the next block
4637 // We have tried to delete everything the nice way - now let's force-delete any remaining module data
4639 // Remove all data from availability and completion tables that is associated
4640 // with course-modules belonging to this course. Note this is done even if the
4641 // features are not enabled now, in case they were enabled previously.
4642 $DB->delete_records_select('course_modules_completion',
4643 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4644 array($courseid));
4645 $DB->delete_records_select('course_modules_availability',
4646 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4647 array($courseid));
4649 // Remove course-module data.
4650 $cms = $DB->get_records('course_modules', array('course'=>$course->id));
4651 foreach ($cms as $cm) {
4652 if ($module = $DB->get_record('modules', array('id'=>$cm->module))) {
4653 try {
4654 $DB->delete_records($module->name, array('id'=>$cm->instance));
4655 } catch (Exception $e) {
4656 // Ignore weird or missing table problems
4659 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4660 $DB->delete_records('course_modules', array('id'=>$cm->id));
4663 if ($showfeedback) {
4664 echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
4667 // Cleanup the rest of plugins
4668 $cleanuplugintypes = array('report', 'coursereport', 'format');
4669 foreach ($cleanuplugintypes as $type) {
4670 $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
4671 foreach ($plugins as $plugin=>$pluginfunction) {
4672 $pluginfunction($course->id, $showfeedback);
4674 if ($showfeedback) {
4675 echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
4679 // Delete questions and question categories
4680 question_delete_course($course, $showfeedback);
4681 if ($showfeedback) {
4682 echo $OUTPUT->notification($strdeleted.get_string('questions', 'question'), 'notifysuccess');
4685 // Make sure there are no subcontexts left - all valid blocks and modules should be already gone
4686 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4687 foreach ($childcontexts as $childcontext) {
4688 $childcontext->delete();
4690 unset($childcontexts);
4692 // Remove all roles and enrolments by default
4693 if (empty($options['keep_roles_and_enrolments'])) {
4694 // this hack is used in restore when deleting contents of existing course
4695 role_unassign_all(array('contextid'=>$coursecontext->id, 'component'=>''), true);
4696 enrol_course_delete($course);
4697 if ($showfeedback) {
4698 echo $OUTPUT->notification($strdeleted.get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
4702 // Delete any groups, removing members and grouping/course links first.
4703 if (empty($options['keep_groups_and_groupings'])) {
4704 groups_delete_groupings($course->id, $showfeedback);
4705 groups_delete_groups($course->id, $showfeedback);
4708 // filters be gone!
4709 filter_delete_all_for_context($coursecontext->id);
4711 // die comments!
4712 comment::delete_comments($coursecontext->id);
4714 // ratings are history too
4715 $delopt = new stdclass();
4716 $delopt->contextid = $coursecontext->id;
4717 $rm = new rating_manager();
4718 $rm->delete_ratings($delopt);
4720 // Delete course tags
4721 coursetag_delete_course_tags($course->id, $showfeedback);
4723 // Delete calendar events
4724 $DB->delete_records('event', array('courseid'=>$course->id));
4725 $fs->delete_area_files($coursecontext->id, 'calendar');
4727 // Delete all related records in other core tables that may have a courseid
4728 // This array stores the tables that need to be cleared, as
4729 // table_name => column_name that contains the course id.
4730 $tablestoclear = array(
4731 'log' => 'course', // Course logs (NOTE: this might be changed in the future)
4732 'backup_courses' => 'courseid', // Scheduled backup stuff
4733 'user_lastaccess' => 'courseid', // User access info
4735 foreach ($tablestoclear as $table => $col) {
4736 $DB->delete_records($table, array($col=>$course->id));
4739 // delete all course backup files
4740 $fs->delete_area_files($coursecontext->id, 'backup');
4742 // cleanup course record - remove links to deleted stuff
4743 $oldcourse = new stdClass();
4744 $oldcourse->id = $course->id;
4745 $oldcourse->summary = '';
4746 $oldcourse->modinfo = NULL;
4747 $oldcourse->legacyfiles = 0;
4748 $oldcourse->enablecompletion = 0;
4749 if (!empty($options['keep_groups_and_groupings'])) {
4750 $oldcourse->defaultgroupingid = 0;
4752 $DB->update_record('course', $oldcourse);
4754 // Delete course sections and availability options.
4755 $DB->delete_records_select('course_sections_availability',
4756 'coursesectionid IN (SELECT id from {course_sections} WHERE course=?)',
4757 array($course->id));
4758 $DB->delete_records('course_sections', array('course'=>$course->id));
4760 // delete legacy, section and any other course files
4761 $fs->delete_area_files($coursecontext->id, 'course'); // files from summary and section
4763 // Delete all remaining stuff linked to context such as files, comments, ratings, etc.
4764 if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
4765 // Easy, do not delete the context itself...
4766 $coursecontext->delete_content();
4768 } else {
4769 // Hack alert!!!!
4770 // We can not drop all context stuff because it would bork enrolments and roles,
4771 // there might be also files used by enrol plugins...
4774 // Delete legacy files - just in case some files are still left there after conversion to new file api,
4775 // also some non-standard unsupported plugins may try to store something there
4776 fulldelete($CFG->dataroot.'/'.$course->id);
4778 // Finally trigger the event
4779 $course->context = $coursecontext; // you can not access context in cron event later after course is deleted
4780 $course->options = $options; // not empty if we used any crazy hack
4781 events_trigger('course_content_removed', $course);
4783 return true;
4787 * Change dates in module - used from course reset.
4789 * @global object
4790 * @global object
4791 * @param string $modname forum, assignment, etc
4792 * @param array $fields array of date fields from mod table
4793 * @param int $timeshift time difference
4794 * @param int $courseid
4795 * @return bool success
4797 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4798 global $CFG, $DB;
4799 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
4801 $return = true;
4802 foreach ($fields as $field) {
4803 $updatesql = "UPDATE {".$modname."}
4804 SET $field = $field + ?
4805 WHERE course=? AND $field<>0";
4806 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4809 $refreshfunction = $modname.'_refresh_events';
4810 if (function_exists($refreshfunction)) {
4811 $refreshfunction($courseid);
4814 return $return;
4818 * This function will empty a course of user data.
4819 * It will retain the activities and the structure of the course.
4821 * @param object $data an object containing all the settings including courseid (without magic quotes)
4822 * @return array status array of array component, item, error
4824 function reset_course_userdata($data) {
4825 global $CFG, $USER, $DB;
4826 require_once($CFG->libdir.'/gradelib.php');
4827 require_once($CFG->libdir.'/completionlib.php');
4828 require_once($CFG->dirroot.'/group/lib.php');
4830 $data->courseid = $data->id;
4831 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
4833 // calculate the time shift of dates
4834 if (!empty($data->reset_start_date)) {
4835 // time part of course startdate should be zero
4836 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
4837 } else {
4838 $data->timeshift = 0;
4841 // result array: component, item, error
4842 $status = array();
4844 // start the resetting
4845 $componentstr = get_string('general');
4847 // move the course start time
4848 if (!empty($data->reset_start_date) and $data->timeshift) {
4849 // change course start data
4850 $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
4851 // update all course and group events - do not move activity events
4852 $updatesql = "UPDATE {event}
4853 SET timestart = timestart + ?
4854 WHERE courseid=? AND instance=0";
4855 $DB->execute($updatesql, array($data->timeshift, $data->courseid));
4857 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4860 if (!empty($data->reset_logs)) {
4861 $DB->delete_records('log', array('course'=>$data->courseid));
4862 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4865 if (!empty($data->reset_events)) {
4866 $DB->delete_records('event', array('courseid'=>$data->courseid));
4867 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4870 if (!empty($data->reset_notes)) {
4871 require_once($CFG->dirroot.'/notes/lib.php');
4872 note_delete_all($data->courseid);
4873 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4876 if (!empty($data->delete_blog_associations)) {
4877 require_once($CFG->dirroot.'/blog/lib.php');
4878 blog_remove_associations_for_course($data->courseid);
4879 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4882 if (!empty($data->reset_completion)) {
4883 // Delete course and activity completion information.
4884 $course = $DB->get_record('course', array('id'=>$data->courseid));
4885 $cc = new completion_info($course);
4886 $cc->delete_all_completion_data();
4887 $status[] = array('component' => $componentstr,
4888 'item' => get_string('deletecompletiondata', 'completion'), 'error' => false);
4891 $componentstr = get_string('roles');
4893 if (!empty($data->reset_roles_overrides)) {
4894 $children = get_child_contexts($context);
4895 foreach ($children as $child) {
4896 $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
4898 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
4899 //force refresh for logged in users
4900 mark_context_dirty($context->path);
4901 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4904 if (!empty($data->reset_roles_local)) {
4905 $children = get_child_contexts($context);
4906 foreach ($children as $child) {
4907 role_unassign_all(array('contextid'=>$child->id));
4909 //force refresh for logged in users
4910 mark_context_dirty($context->path);
4911 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4914 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4915 $data->unenrolled = array();
4916 if (!empty($data->unenrol_users)) {
4917 $plugins = enrol_get_plugins(true);
4918 $instances = enrol_get_instances($data->courseid, true);
4919 foreach ($instances as $key=>$instance) {
4920 if (!isset($plugins[$instance->enrol])) {
4921 unset($instances[$key]);
4922 continue;
4926 foreach($data->unenrol_users as $withroleid) {
4927 if ($withroleid) {
4928 $sql = "SELECT ue.*
4929 FROM {user_enrolments} ue
4930 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4931 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4932 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4933 $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
4935 } else {
4936 // without any role assigned at course context
4937 $sql = "SELECT ue.*
4938 FROM {user_enrolments} ue
4939 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4940 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4941 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)
4942 WHERE ra.id IS NULL";
4943 $params = array('courseid'=>$data->courseid, 'courselevel'=>CONTEXT_COURSE);
4946 $rs = $DB->get_recordset_sql($sql, $params);
4947 foreach ($rs as $ue) {
4948 if (!isset($instances[$ue->enrolid])) {
4949 continue;
4951 $instance = $instances[$ue->enrolid];
4952 $plugin = $plugins[$instance->enrol];
4953 if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
4954 continue;
4957 $plugin->unenrol_user($instance, $ue->userid);
4958 $data->unenrolled[$ue->userid] = $ue->userid;
4960 $rs->close();
4963 if (!empty($data->unenrolled)) {
4964 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
4968 $componentstr = get_string('groups');
4970 // remove all group members
4971 if (!empty($data->reset_groups_members)) {
4972 groups_delete_group_members($data->courseid);
4973 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
4976 // remove all groups
4977 if (!empty($data->reset_groups_remove)) {
4978 groups_delete_groups($data->courseid, false);
4979 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
4982 // remove all grouping members
4983 if (!empty($data->reset_groupings_members)) {
4984 groups_delete_groupings_groups($data->courseid, false);
4985 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
4988 // remove all groupings
4989 if (!empty($data->reset_groupings_remove)) {
4990 groups_delete_groupings($data->courseid, false);
4991 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
4994 // Look in every instance of every module for data to delete
4995 $unsupported_mods = array();
4996 if ($allmods = $DB->get_records('modules') ) {
4997 foreach ($allmods as $mod) {
4998 $modname = $mod->name;
4999 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
5000 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
5001 if (file_exists($modfile)) {
5002 if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
5003 continue; // Skip mods with no instances
5005 include_once($modfile);
5006 if (function_exists($moddeleteuserdata)) {
5007 $modstatus = $moddeleteuserdata($data);
5008 if (is_array($modstatus)) {
5009 $status = array_merge($status, $modstatus);
5010 } else {
5011 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
5013 } else {
5014 $unsupported_mods[] = $mod;
5016 } else {
5017 debugging('Missing lib.php in '.$modname.' module!');
5022 // mention unsupported mods
5023 if (!empty($unsupported_mods)) {
5024 foreach($unsupported_mods as $mod) {
5025 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
5030 $componentstr = get_string('gradebook', 'grades');
5031 // reset gradebook
5032 if (!empty($data->reset_gradebook_items)) {
5033 remove_course_grades($data->courseid, false);
5034 grade_grab_course_grades($data->courseid);
5035 grade_regrade_final_grades($data->courseid);
5036 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
5038 } else if (!empty($data->reset_gradebook_grades)) {
5039 grade_course_reset($data->courseid);
5040 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
5042 // reset comments
5043 if (!empty($data->reset_comments)) {
5044 require_once($CFG->dirroot.'/comment/lib.php');
5045 comment::reset_course_page_comments($context);
5048 return $status;
5052 * Generate an email processing address
5054 * @param int $modid
5055 * @param string $modargs
5056 * @return string Returns email processing address
5058 function generate_email_processing_address($modid,$modargs) {
5059 global $CFG;
5061 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
5062 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
5068 * @todo Finish documenting this function
5070 * @global object
5071 * @param string $modargs
5072 * @param string $body Currently unused
5074 function moodle_process_email($modargs,$body) {
5075 global $DB;
5077 // the first char should be an unencoded letter. We'll take this as an action
5078 switch ($modargs{0}) {
5079 case 'B': { // bounce
5080 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
5081 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
5082 // check the half md5 of their email
5083 $md5check = substr(md5($user->email),0,16);
5084 if ($md5check == substr($modargs, -16)) {
5085 set_bounce_count($user);
5087 // else maybe they've already changed it?
5090 break;
5091 // maybe more later?
5095 /// CORRESPONDENCE ////////////////////////////////////////////////
5098 * Get mailer instance, enable buffering, flush buffer or disable buffering.
5100 * @global object
5101 * @param string $action 'get', 'buffer', 'close' or 'flush'
5102 * @return object|null mailer instance if 'get' used or nothing
5104 function get_mailer($action='get') {
5105 global $CFG;
5107 static $mailer = null;
5108 static $counter = 0;
5110 if (!isset($CFG->smtpmaxbulk)) {
5111 $CFG->smtpmaxbulk = 1;
5114 if ($action == 'get') {
5115 $prevkeepalive = false;
5117 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5118 if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
5119 $counter++;
5120 // reset the mailer
5121 $mailer->Priority = 3;
5122 $mailer->CharSet = 'UTF-8'; // our default
5123 $mailer->ContentType = "text/plain";
5124 $mailer->Encoding = "8bit";
5125 $mailer->From = "root@localhost";
5126 $mailer->FromName = "Root User";
5127 $mailer->Sender = "";
5128 $mailer->Subject = "";
5129 $mailer->Body = "";
5130 $mailer->AltBody = "";
5131 $mailer->ConfirmReadingTo = "";
5133 $mailer->ClearAllRecipients();
5134 $mailer->ClearReplyTos();
5135 $mailer->ClearAttachments();
5136 $mailer->ClearCustomHeaders();
5137 return $mailer;
5140 $prevkeepalive = $mailer->SMTPKeepAlive;
5141 get_mailer('flush');
5144 include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
5145 $mailer = new moodle_phpmailer();
5147 $counter = 1;
5149 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
5150 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
5151 $mailer->CharSet = 'UTF-8';
5153 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
5154 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
5155 $mailer->LE = "\r\n";
5156 } else {
5157 $mailer->LE = "\n";
5160 if ($CFG->smtphosts == 'qmail') {
5161 $mailer->IsQmail(); // use Qmail system
5163 } else if (empty($CFG->smtphosts)) {
5164 $mailer->IsMail(); // use PHP mail() = sendmail
5166 } else {
5167 $mailer->IsSMTP(); // use SMTP directly
5168 if (!empty($CFG->debugsmtp)) {
5169 $mailer->SMTPDebug = true;
5171 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
5172 $mailer->SMTPSecure = $CFG->smtpsecure; // specify secure connection protocol
5173 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
5175 if ($CFG->smtpuser) { // Use SMTP authentication
5176 $mailer->SMTPAuth = true;
5177 $mailer->Username = $CFG->smtpuser;
5178 $mailer->Password = $CFG->smtppass;
5182 return $mailer;
5185 $nothing = null;
5187 // keep smtp session open after sending
5188 if ($action == 'buffer') {
5189 if (!empty($CFG->smtpmaxbulk)) {
5190 get_mailer('flush');
5191 $m = get_mailer();
5192 if ($m->Mailer == 'smtp') {
5193 $m->SMTPKeepAlive = true;
5196 return $nothing;
5199 // close smtp session, but continue buffering
5200 if ($action == 'flush') {
5201 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5202 if (!empty($mailer->SMTPDebug)) {
5203 echo '<pre>'."\n";
5205 $mailer->SmtpClose();
5206 if (!empty($mailer->SMTPDebug)) {
5207 echo '</pre>';
5210 return $nothing;
5213 // close smtp session, do not buffer anymore
5214 if ($action == 'close') {
5215 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5216 get_mailer('flush');
5217 $mailer->SMTPKeepAlive = false;
5219 $mailer = null; // better force new instance
5220 return $nothing;
5225 * Send an email to a specified user
5227 * @global object
5228 * @global string
5229 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
5230 * @uses SITEID
5231 * @param stdClass $user A {@link $USER} object
5232 * @param stdClass $from A {@link $USER} object
5233 * @param string $subject plain text subject line of the email
5234 * @param string $messagetext plain text version of the message
5235 * @param string $messagehtml complete html version of the message (optional)
5236 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
5237 * @param string $attachname the name of the file (extension indicates MIME)
5238 * @param bool $usetrueaddress determines whether $from email address should
5239 * be sent out. Will be overruled by user profile setting for maildisplay
5240 * @param string $replyto Email address to reply to
5241 * @param string $replytoname Name of reply to recipient
5242 * @param int $wordwrapwidth custom word wrap width, default 79
5243 * @return bool Returns true if mail was sent OK and false if there was an error.
5245 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
5247 global $CFG;
5249 if (empty($user) || empty($user->email)) {
5250 $nulluser = 'User is null or has no email';
5251 error_log($nulluser);
5252 if (CLI_SCRIPT) {
5253 mtrace('Error: lib/moodlelib.php email_to_user(): '.$nulluser);
5255 return false;
5258 if (!empty($user->deleted)) {
5259 // do not mail deleted users
5260 $userdeleted = 'User is deleted';
5261 error_log($userdeleted);
5262 if (CLI_SCRIPT) {
5263 mtrace('Error: lib/moodlelib.php email_to_user(): '.$userdeleted);
5265 return false;
5268 if (!empty($CFG->noemailever)) {
5269 // hidden setting for development sites, set in config.php if needed
5270 $noemail = 'Not sending email due to noemailever config setting';
5271 error_log($noemail);
5272 if (CLI_SCRIPT) {
5273 mtrace('Error: lib/moodlelib.php email_to_user(): '.$noemail);
5275 return true;
5278 if (!empty($CFG->divertallemailsto)) {
5279 $subject = "[DIVERTED {$user->email}] $subject";
5280 $user = clone($user);
5281 $user->email = $CFG->divertallemailsto;
5284 // skip mail to suspended users
5285 if ((isset($user->auth) && $user->auth=='nologin') or (isset($user->suspended) && $user->suspended)) {
5286 return true;
5289 if (!validate_email($user->email)) {
5290 // we can not send emails to invalid addresses - it might create security issue or confuse the mailer
5291 $invalidemail = "User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.";
5292 error_log($invalidemail);
5293 if (CLI_SCRIPT) {
5294 mtrace('Error: lib/moodlelib.php email_to_user(): '.$invalidemail);
5296 return false;
5299 if (over_bounce_threshold($user)) {
5300 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
5301 error_log($bouncemsg);
5302 if (CLI_SCRIPT) {
5303 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
5305 return false;
5308 // If the user is a remote mnet user, parse the email text for URL to the
5309 // wwwroot and modify the url to direct the user's browser to login at their
5310 // home site (identity provider - idp) before hitting the link itself
5311 if (is_mnet_remote_user($user)) {
5312 require_once($CFG->dirroot.'/mnet/lib.php');
5314 $jumpurl = mnet_get_idp_jump_url($user);
5315 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
5317 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
5318 $callback,
5319 $messagetext);
5320 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
5321 $callback,
5322 $messagehtml);
5324 $mail = get_mailer();
5326 if (!empty($mail->SMTPDebug)) {
5327 echo '<pre>' . "\n";
5330 $temprecipients = array();
5331 $tempreplyto = array();
5333 $supportuser = generate_email_supportuser();
5335 // make up an email address for handling bounces
5336 if (!empty($CFG->handlebounces)) {
5337 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
5338 $mail->Sender = generate_email_processing_address(0,$modargs);
5339 } else {
5340 $mail->Sender = $supportuser->email;
5343 if (is_string($from)) { // So we can pass whatever we want if there is need
5344 $mail->From = $CFG->noreplyaddress;
5345 $mail->FromName = $from;
5346 } else if ($usetrueaddress and $from->maildisplay) {
5347 $mail->From = $from->email;
5348 $mail->FromName = fullname($from);
5349 } else {
5350 $mail->From = $CFG->noreplyaddress;
5351 $mail->FromName = fullname($from);
5352 if (empty($replyto)) {
5353 $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
5357 if (!empty($replyto)) {
5358 $tempreplyto[] = array($replyto, $replytoname);
5361 $mail->Subject = substr($subject, 0, 900);
5363 $temprecipients[] = array($user->email, fullname($user));
5365 $mail->WordWrap = $wordwrapwidth; // set word wrap
5367 if (!empty($from->customheaders)) { // Add custom headers
5368 if (is_array($from->customheaders)) {
5369 foreach ($from->customheaders as $customheader) {
5370 $mail->AddCustomHeader($customheader);
5372 } else {
5373 $mail->AddCustomHeader($from->customheaders);
5377 if (!empty($from->priority)) {
5378 $mail->Priority = $from->priority;
5381 if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
5382 $mail->IsHTML(true);
5383 $mail->Encoding = 'quoted-printable'; // Encoding to use
5384 $mail->Body = $messagehtml;
5385 $mail->AltBody = "\n$messagetext\n";
5386 } else {
5387 $mail->IsHTML(false);
5388 $mail->Body = "\n$messagetext\n";
5391 if ($attachment && $attachname) {
5392 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
5393 $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
5394 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
5395 } else {
5396 require_once($CFG->libdir.'/filelib.php');
5397 $mimetype = mimeinfo('type', $attachname);
5398 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
5402 // Check if the email should be sent in an other charset then the default UTF-8
5403 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
5405 // use the defined site mail charset or eventually the one preferred by the recipient
5406 $charset = $CFG->sitemailcharset;
5407 if (!empty($CFG->allowusermailcharset)) {
5408 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
5409 $charset = $useremailcharset;
5413 // convert all the necessary strings if the charset is supported
5414 $charsets = get_list_of_charsets();
5415 unset($charsets['UTF-8']);
5416 if (in_array($charset, $charsets)) {
5417 $mail->CharSet = $charset;
5418 $mail->FromName = textlib::convert($mail->FromName, 'utf-8', strtolower($charset));
5419 $mail->Subject = textlib::convert($mail->Subject, 'utf-8', strtolower($charset));
5420 $mail->Body = textlib::convert($mail->Body, 'utf-8', strtolower($charset));
5421 $mail->AltBody = textlib::convert($mail->AltBody, 'utf-8', strtolower($charset));
5423 foreach ($temprecipients as $key => $values) {
5424 $temprecipients[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5426 foreach ($tempreplyto as $key => $values) {
5427 $tempreplyto[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5432 foreach ($temprecipients as $values) {
5433 $mail->AddAddress($values[0], $values[1]);
5435 foreach ($tempreplyto as $values) {
5436 $mail->AddReplyTo($values[0], $values[1]);
5439 if ($mail->Send()) {
5440 set_send_count($user);
5441 $mail->IsSMTP(); // use SMTP directly
5442 if (!empty($mail->SMTPDebug)) {
5443 echo '</pre>';
5445 return true;
5446 } else {
5447 add_to_log(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: '. $mail->ErrorInfo);
5448 if (CLI_SCRIPT) {
5449 mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
5451 if (!empty($mail->SMTPDebug)) {
5452 echo '</pre>';
5454 return false;
5459 * Generate a signoff for emails based on support settings
5461 * @global object
5462 * @return string
5464 function generate_email_signoff() {
5465 global $CFG;
5467 $signoff = "\n";
5468 if (!empty($CFG->supportname)) {
5469 $signoff .= $CFG->supportname."\n";
5471 if (!empty($CFG->supportemail)) {
5472 $signoff .= $CFG->supportemail."\n";
5474 if (!empty($CFG->supportpage)) {
5475 $signoff .= $CFG->supportpage."\n";
5477 return $signoff;
5481 * Generate a fake user for emails based on support settings
5482 * @global object
5483 * @return object user info
5485 function generate_email_supportuser() {
5486 global $CFG;
5488 static $supportuser;
5490 if (!empty($supportuser)) {
5491 return $supportuser;
5494 $supportuser = new stdClass();
5495 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
5496 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
5497 $supportuser->lastname = '';
5498 $supportuser->maildisplay = true;
5500 return $supportuser;
5505 * Sets specified user's password and send the new password to the user via email.
5507 * @global object
5508 * @global object
5509 * @param user $user A {@link $USER} object
5510 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
5512 function setnew_password_and_mail($user) {
5513 global $CFG, $DB;
5515 // we try to send the mail in language the user understands,
5516 // unfortunately the filter_string() does not support alternative langs yet
5517 // so multilang will not work properly for site->fullname
5518 $lang = empty($user->lang) ? $CFG->lang : $user->lang;
5520 $site = get_site();
5522 $supportuser = generate_email_supportuser();
5524 $newpassword = generate_password();
5526 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
5528 $a = new stdClass();
5529 $a->firstname = fullname($user, true);
5530 $a->sitename = format_string($site->fullname);
5531 $a->username = $user->username;
5532 $a->newpassword = $newpassword;
5533 $a->link = $CFG->wwwroot .'/login/';
5534 $a->signoff = generate_email_signoff();
5536 $message = (string)new lang_string('newusernewpasswordtext', '', $a, $lang);
5538 $subject = format_string($site->fullname) .': '. (string)new lang_string('newusernewpasswordsubj', '', $a, $lang);
5540 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5541 return email_to_user($user, $supportuser, $subject, $message);
5546 * Resets specified user's password and send the new password to the user via email.
5548 * @param stdClass $user A {@link $USER} object
5549 * @return bool Returns true if mail was sent OK and false if there was an error.
5551 function reset_password_and_mail($user) {
5552 global $CFG;
5554 $site = get_site();
5555 $supportuser = generate_email_supportuser();
5557 $userauth = get_auth_plugin($user->auth);
5558 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
5559 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
5560 return false;
5563 $newpassword = generate_password();
5565 if (!$userauth->user_update_password($user, $newpassword)) {
5566 print_error("cannotsetpassword");
5569 $a = new stdClass();
5570 $a->firstname = $user->firstname;
5571 $a->lastname = $user->lastname;
5572 $a->sitename = format_string($site->fullname);
5573 $a->username = $user->username;
5574 $a->newpassword = $newpassword;
5575 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
5576 $a->signoff = generate_email_signoff();
5578 $message = get_string('newpasswordtext', '', $a);
5580 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
5582 unset_user_preference('create_password', $user); // prevent cron from generating the password
5584 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5585 return email_to_user($user, $supportuser, $subject, $message);
5590 * Send email to specified user with confirmation text and activation link.
5592 * @global object
5593 * @param user $user A {@link $USER} object
5594 * @return bool Returns true if mail was sent OK and false if there was an error.
5596 function send_confirmation_email($user) {
5597 global $CFG;
5599 $site = get_site();
5600 $supportuser = generate_email_supportuser();
5602 $data = new stdClass();
5603 $data->firstname = fullname($user);
5604 $data->sitename = format_string($site->fullname);
5605 $data->admin = generate_email_signoff();
5607 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
5609 $username = urlencode($user->username);
5610 $username = str_replace('.', '%2E', $username); // prevent problems with trailing dots
5611 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $username;
5612 $message = get_string('emailconfirmation', '', $data);
5613 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
5615 $user->mailformat = 1; // Always send HTML version as well
5617 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5618 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
5623 * send_password_change_confirmation_email.
5625 * @global object
5626 * @param user $user A {@link $USER} object
5627 * @return bool Returns true if mail was sent OK and false if there was an error.
5629 function send_password_change_confirmation_email($user) {
5630 global $CFG;
5632 $site = get_site();
5633 $supportuser = generate_email_supportuser();
5635 $data = new stdClass();
5636 $data->firstname = $user->firstname;
5637 $data->lastname = $user->lastname;
5638 $data->sitename = format_string($site->fullname);
5639 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
5640 $data->admin = generate_email_signoff();
5642 $message = get_string('emailpasswordconfirmation', '', $data);
5643 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
5645 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5646 return email_to_user($user, $supportuser, $subject, $message);
5651 * send_password_change_info.
5653 * @global object
5654 * @param user $user A {@link $USER} object
5655 * @return bool Returns true if mail was sent OK and false if there was an error.
5657 function send_password_change_info($user) {
5658 global $CFG;
5660 $site = get_site();
5661 $supportuser = generate_email_supportuser();
5662 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
5664 $data = new stdClass();
5665 $data->firstname = $user->firstname;
5666 $data->lastname = $user->lastname;
5667 $data->sitename = format_string($site->fullname);
5668 $data->admin = generate_email_signoff();
5670 $userauth = get_auth_plugin($user->auth);
5672 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
5673 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
5674 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5675 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5676 return email_to_user($user, $supportuser, $subject, $message);
5679 if ($userauth->can_change_password() and $userauth->change_password_url()) {
5680 // we have some external url for password changing
5681 $data->link .= $userauth->change_password_url();
5683 } else {
5684 //no way to change password, sorry
5685 $data->link = '';
5688 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
5689 $message = get_string('emailpasswordchangeinfo', '', $data);
5690 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5691 } else {
5692 $message = get_string('emailpasswordchangeinfofail', '', $data);
5693 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5696 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5697 return email_to_user($user, $supportuser, $subject, $message);
5702 * Check that an email is allowed. It returns an error message if there
5703 * was a problem.
5705 * @global object
5706 * @param string $email Content of email
5707 * @return string|false
5709 function email_is_not_allowed($email) {
5710 global $CFG;
5712 if (!empty($CFG->allowemailaddresses)) {
5713 $allowed = explode(' ', $CFG->allowemailaddresses);
5714 foreach ($allowed as $allowedpattern) {
5715 $allowedpattern = trim($allowedpattern);
5716 if (!$allowedpattern) {
5717 continue;
5719 if (strpos($allowedpattern, '.') === 0) {
5720 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5721 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5722 return false;
5725 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5726 return false;
5729 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5731 } else if (!empty($CFG->denyemailaddresses)) {
5732 $denied = explode(' ', $CFG->denyemailaddresses);
5733 foreach ($denied as $deniedpattern) {
5734 $deniedpattern = trim($deniedpattern);
5735 if (!$deniedpattern) {
5736 continue;
5738 if (strpos($deniedpattern, '.') === 0) {
5739 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5740 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5741 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5744 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5745 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5750 return false;
5753 /// FILE HANDLING /////////////////////////////////////////////
5756 * Returns local file storage instance
5758 * @return file_storage
5760 function get_file_storage() {
5761 global $CFG;
5763 static $fs = null;
5765 if ($fs) {
5766 return $fs;
5769 require_once("$CFG->libdir/filelib.php");
5771 if (isset($CFG->filedir)) {
5772 $filedir = $CFG->filedir;
5773 } else {
5774 $filedir = $CFG->dataroot.'/filedir';
5777 if (isset($CFG->trashdir)) {
5778 $trashdirdir = $CFG->trashdir;
5779 } else {
5780 $trashdirdir = $CFG->dataroot.'/trashdir';
5783 $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5785 return $fs;
5789 * Returns local file storage instance
5791 * @return file_browser
5793 function get_file_browser() {
5794 global $CFG;
5796 static $fb = null;
5798 if ($fb) {
5799 return $fb;
5802 require_once("$CFG->libdir/filelib.php");
5804 $fb = new file_browser();
5806 return $fb;
5810 * Returns file packer
5812 * @param string $mimetype default application/zip
5813 * @return file_packer
5815 function get_file_packer($mimetype='application/zip') {
5816 global $CFG;
5818 static $fp = array();;
5820 if (isset($fp[$mimetype])) {
5821 return $fp[$mimetype];
5824 switch ($mimetype) {
5825 case 'application/zip':
5826 case 'application/vnd.moodle.backup':
5827 $classname = 'zip_packer';
5828 break;
5829 case 'application/x-tar':
5830 // $classname = 'tar_packer';
5831 // break;
5832 default:
5833 return false;
5836 require_once("$CFG->libdir/filestorage/$classname.php");
5837 $fp[$mimetype] = new $classname();
5839 return $fp[$mimetype];
5843 * Returns current name of file on disk if it exists.
5845 * @param string $newfile File to be verified
5846 * @return string Current name of file on disk if true
5848 function valid_uploaded_file($newfile) {
5849 if (empty($newfile)) {
5850 return '';
5852 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5853 return $newfile['tmp_name'];
5854 } else {
5855 return '';
5860 * Returns the maximum size for uploading files.
5862 * There are seven possible upload limits:
5863 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5864 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5865 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5866 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5867 * 5. by the Moodle admin in $CFG->maxbytes
5868 * 6. by the teacher in the current course $course->maxbytes
5869 * 7. by the teacher for the current module, eg $assignment->maxbytes
5871 * These last two are passed to this function as arguments (in bytes).
5872 * Anything defined as 0 is ignored.
5873 * The smallest of all the non-zero numbers is returned.
5875 * @todo Finish documenting this function
5877 * @param int $sizebytes Set maximum size
5878 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5879 * @param int $modulebytes Current module ->maxbytes (in bytes)
5880 * @return int The maximum size for uploading files.
5882 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5884 if (! $filesize = ini_get('upload_max_filesize')) {
5885 $filesize = '5M';
5887 $minimumsize = get_real_size($filesize);
5889 if ($postsize = ini_get('post_max_size')) {
5890 $postsize = get_real_size($postsize);
5891 if ($postsize < $minimumsize) {
5892 $minimumsize = $postsize;
5896 if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
5897 $minimumsize = $sitebytes;
5900 if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
5901 $minimumsize = $coursebytes;
5904 if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
5905 $minimumsize = $modulebytes;
5908 return $minimumsize;
5912 * Returns the maximum size for uploading files for the current user
5914 * This function takes in account @see:get_max_upload_file_size() the user's capabilities
5916 * @param context $context The context in which to check user capabilities
5917 * @param int $sizebytes Set maximum size
5918 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5919 * @param int $modulebytes Current module ->maxbytes (in bytes)
5920 * @param stdClass The user
5921 * @return int The maximum size for uploading files.
5923 function get_user_max_upload_file_size($context, $sitebytes=0, $coursebytes=0, $modulebytes=0, $user=null) {
5924 global $USER;
5926 if (empty($user)) {
5927 $user = $USER;
5930 if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
5931 return USER_CAN_IGNORE_FILE_SIZE_LIMITS;
5934 return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes);
5938 * Returns an array of possible sizes in local language
5940 * Related to {@link get_max_upload_file_size()} - this function returns an
5941 * array of possible sizes in an array, translated to the
5942 * local language.
5944 * @todo Finish documenting this function
5946 * @global object
5947 * @uses SORT_NUMERIC
5948 * @param int $sizebytes Set maximum size
5949 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5950 * @param int $modulebytes Current module ->maxbytes (in bytes)
5951 * @param int|array $custombytes custom upload size/s which will be added to list,
5952 * Only value/s smaller then maxsize will be added to list.
5953 * @return array
5955 function get_max_upload_sizes($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $custombytes = null) {
5956 global $CFG;
5958 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5959 return array();
5962 $filesize[intval($maxsize)] = display_size($maxsize);
5964 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5965 5242880, 10485760, 20971520, 52428800, 104857600);
5967 // If custombytes is given then add it to the list.
5968 if (!is_null($custombytes)) {
5969 if (is_number($custombytes)) {
5970 $custombytes = array((int)$custombytes);
5972 $sizelist = array_unique(array_merge($sizelist, $custombytes));
5975 // Allow maxbytes to be selected if it falls outside the above boundaries
5976 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
5977 // note: get_real_size() is used in order to prevent problems with invalid values
5978 $sizelist[] = get_real_size($CFG->maxbytes);
5981 foreach ($sizelist as $sizebytes) {
5982 if ($sizebytes < $maxsize) {
5983 $filesize[intval($sizebytes)] = display_size($sizebytes);
5987 krsort($filesize, SORT_NUMERIC);
5989 return $filesize;
5993 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
5995 * If excludefiles is defined, then that file/directory is ignored
5996 * If getdirs is true, then (sub)directories are included in the output
5997 * If getfiles is true, then files are included in the output
5998 * (at least one of these must be true!)
6000 * @todo Finish documenting this function. Add examples of $excludefile usage.
6002 * @param string $rootdir A given root directory to start from
6003 * @param string|array $excludefile If defined then the specified file/directory is ignored
6004 * @param bool $descend If true then subdirectories are recursed as well
6005 * @param bool $getdirs If true then (sub)directories are included in the output
6006 * @param bool $getfiles If true then files are included in the output
6007 * @return array An array with all the filenames in
6008 * all subdirectories, relative to the given rootdir
6010 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
6012 $dirs = array();
6014 if (!$getdirs and !$getfiles) { // Nothing to show
6015 return $dirs;
6018 if (!is_dir($rootdir)) { // Must be a directory
6019 return $dirs;
6022 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
6023 return $dirs;
6026 if (!is_array($excludefiles)) {
6027 $excludefiles = array($excludefiles);
6030 while (false !== ($file = readdir($dir))) {
6031 $firstchar = substr($file, 0, 1);
6032 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
6033 continue;
6035 $fullfile = $rootdir .'/'. $file;
6036 if (filetype($fullfile) == 'dir') {
6037 if ($getdirs) {
6038 $dirs[] = $file;
6040 if ($descend) {
6041 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
6042 foreach ($subdirs as $subdir) {
6043 $dirs[] = $file .'/'. $subdir;
6046 } else if ($getfiles) {
6047 $dirs[] = $file;
6050 closedir($dir);
6052 asort($dirs);
6054 return $dirs;
6059 * Adds up all the files in a directory and works out the size.
6061 * @todo Finish documenting this function
6063 * @param string $rootdir The directory to start from
6064 * @param string $excludefile A file to exclude when summing directory size
6065 * @return int The summed size of all files and subfiles within the root directory
6067 function get_directory_size($rootdir, $excludefile='') {
6068 global $CFG;
6070 // do it this way if we can, it's much faster
6071 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
6072 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
6073 $output = null;
6074 $return = null;
6075 exec($command,$output,$return);
6076 if (is_array($output)) {
6077 return get_real_size(intval($output[0]).'k'); // we told it to return k.
6081 if (!is_dir($rootdir)) { // Must be a directory
6082 return 0;
6085 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
6086 return 0;
6089 $size = 0;
6091 while (false !== ($file = readdir($dir))) {
6092 $firstchar = substr($file, 0, 1);
6093 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
6094 continue;
6096 $fullfile = $rootdir .'/'. $file;
6097 if (filetype($fullfile) == 'dir') {
6098 $size += get_directory_size($fullfile, $excludefile);
6099 } else {
6100 $size += filesize($fullfile);
6103 closedir($dir);
6105 return $size;
6109 * Converts bytes into display form
6111 * @todo Finish documenting this function. Verify return type.
6113 * @staticvar string $gb Localized string for size in gigabytes
6114 * @staticvar string $mb Localized string for size in megabytes
6115 * @staticvar string $kb Localized string for size in kilobytes
6116 * @staticvar string $b Localized string for size in bytes
6117 * @param int $size The size to convert to human readable form
6118 * @return string
6120 function display_size($size) {
6122 static $gb, $mb, $kb, $b;
6124 if ($size === USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
6125 return get_string('unlimited');
6128 if (empty($gb)) {
6129 $gb = get_string('sizegb');
6130 $mb = get_string('sizemb');
6131 $kb = get_string('sizekb');
6132 $b = get_string('sizeb');
6135 if ($size >= 1073741824) {
6136 $size = round($size / 1073741824 * 10) / 10 . $gb;
6137 } else if ($size >= 1048576) {
6138 $size = round($size / 1048576 * 10) / 10 . $mb;
6139 } else if ($size >= 1024) {
6140 $size = round($size / 1024 * 10) / 10 . $kb;
6141 } else {
6142 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
6144 return $size;
6148 * Cleans a given filename by removing suspicious or troublesome characters
6149 * @see clean_param()
6151 * @uses PARAM_FILE
6152 * @param string $string file name
6153 * @return string cleaned file name
6155 function clean_filename($string) {
6156 return clean_param($string, PARAM_FILE);
6160 /// STRING TRANSLATION ////////////////////////////////////////
6163 * Returns the code for the current language
6165 * @category string
6166 * @return string
6168 function current_language() {
6169 global $CFG, $USER, $SESSION, $COURSE;
6171 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
6172 $return = $COURSE->lang;
6174 } else if (!empty($SESSION->lang)) { // Session language can override other settings
6175 $return = $SESSION->lang;
6177 } else if (!empty($USER->lang)) {
6178 $return = $USER->lang;
6180 } else if (isset($CFG->lang)) {
6181 $return = $CFG->lang;
6183 } else {
6184 $return = 'en';
6187 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
6189 return $return;
6193 * Returns parent language of current active language if defined
6195 * @category string
6196 * @uses COURSE
6197 * @uses SESSION
6198 * @param string $lang null means current language
6199 * @return string
6201 function get_parent_language($lang=null) {
6202 global $COURSE, $SESSION;
6204 //let's hack around the current language
6205 if (!empty($lang)) {
6206 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
6207 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
6208 $COURSE->lang = '';
6209 $SESSION->lang = $lang;
6212 $parentlang = get_string('parentlanguage', 'langconfig');
6213 if ($parentlang === 'en') {
6214 $parentlang = '';
6217 //let's hack around the current language
6218 if (!empty($lang)) {
6219 $COURSE->lang = $old_course_lang;
6220 $SESSION->lang = $old_session_lang;
6223 return $parentlang;
6227 * Returns current string_manager instance.
6229 * The param $forcereload is needed for CLI installer only where the string_manager instance
6230 * must be replaced during the install.php script life time.
6232 * @category string
6233 * @param bool $forcereload shall the singleton be released and new instance created instead?
6234 * @return string_manager
6236 function get_string_manager($forcereload=false) {
6237 global $CFG;
6239 static $singleton = null;
6241 if ($forcereload) {
6242 $singleton = null;
6244 if ($singleton === null) {
6245 if (empty($CFG->early_install_lang)) {
6247 if (empty($CFG->langcacheroot)) {
6248 $langcacheroot = $CFG->cachedir . '/lang';
6249 } else {
6250 $langcacheroot = $CFG->langcacheroot;
6253 if (empty($CFG->langlist)) {
6254 $translist = array();
6255 } else {
6256 $translist = explode(',', $CFG->langlist);
6259 if (empty($CFG->langmenucachefile)) {
6260 $langmenucache = $CFG->cachedir . '/languages';
6261 } else {
6262 $langmenucache = $CFG->langmenucachefile;
6265 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
6266 !empty($CFG->langstringcache), $translist, $langmenucache);
6268 } else {
6269 $singleton = new install_string_manager();
6273 return $singleton;
6278 * Interface for string manager
6280 * Interface describing class which is responsible for getting
6281 * of localised strings from language packs.
6283 * @package core
6284 * @copyright 2010 Petr Skoda (http://skodak.org)
6285 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6287 interface string_manager {
6289 * Get String returns a requested string
6291 * @param string $identifier The identifier of the string to search for
6292 * @param string $component The module the string is associated with
6293 * @param string|object|array $a An object, string or number that can be used
6294 * within translation strings
6295 * @param string $lang moodle translation language, NULL means use current
6296 * @return string The String !
6298 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
6301 * Does the string actually exist?
6303 * get_string() is throwing debug warnings, sometimes we do not want them
6304 * or we want to display better explanation of the problem.
6306 * Use with care!
6308 * @param string $identifier The identifier of the string to search for
6309 * @param string $component The module the string is associated with
6310 * @return boot true if exists
6312 public function string_exists($identifier, $component);
6315 * Returns a localised list of all country names, sorted by country keys.
6316 * @param bool $returnall return all or just enabled
6317 * @param string $lang moodle translation language, NULL means use current
6318 * @return array two-letter country code => translated name.
6320 public function get_list_of_countries($returnall = false, $lang = NULL);
6323 * Returns a localised list of languages, sorted by code keys.
6325 * @param string $lang moodle translation language, NULL means use current
6326 * @param string $standard language list standard
6327 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6328 * @return array language code => translated name
6330 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
6333 * Checks if the translation exists for the language
6335 * @param string $lang moodle translation language code
6336 * @param bool $includeall include also disabled translations
6337 * @return bool true if exists
6339 public function translation_exists($lang, $includeall = true);
6342 * Returns localised list of installed translations
6343 * @param bool $returnall return all or just enabled
6344 * @return array moodle translation code => localised translation name
6346 public function get_list_of_translations($returnall = false);
6349 * Returns localised list of currencies.
6351 * @param string $lang moodle translation language, NULL means use current
6352 * @return array currency code => localised currency name
6354 public function get_list_of_currencies($lang = NULL);
6357 * Load all strings for one component
6358 * @param string $component The module the string is associated with
6359 * @param string $lang
6360 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6361 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6362 * @return array of all string for given component and lang
6364 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
6367 * Invalidates all caches, should the implementation use any
6369 public function reset_caches();
6374 * Standard string_manager implementation
6376 * Implements string_manager with getting and printing localised strings
6378 * @package core
6379 * @category string
6380 * @copyright 2010 Petr Skoda (http://skodak.org)
6381 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6383 class core_string_manager implements string_manager {
6384 /** @var string location of all packs except 'en' */
6385 protected $otherroot;
6386 /** @var string location of all lang pack local modifications */
6387 protected $localroot;
6388 /** @var string location of on-disk cache of merged strings */
6389 protected $cacheroot;
6390 /** @var array lang string cache - it will be optimised more later */
6391 protected $cache = array();
6392 /** @var int get_string() counter */
6393 protected $countgetstring = 0;
6394 /** @var int in-memory cache hits counter */
6395 protected $countmemcache = 0;
6396 /** @var int on-disk cache hits counter */
6397 protected $countdiskcache = 0;
6398 /** @var bool use disk cache */
6399 protected $usediskcache;
6400 /** @var array limit list of translations */
6401 protected $translist;
6402 /** @var string location of a file that caches the list of available translations */
6403 protected $menucache;
6406 * Create new instance of string manager
6408 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
6409 * @param string $localroot usually the same as $otherroot
6410 * @param string $cacheroot usually lang dir in cache folder
6411 * @param bool $usediskcache use disk cache
6412 * @param array $translist limit list of visible translations
6413 * @param string $menucache the location of a file that caches the list of available translations
6415 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
6416 $this->otherroot = $otherroot;
6417 $this->localroot = $localroot;
6418 $this->cacheroot = $cacheroot;
6419 $this->usediskcache = $usediskcache;
6420 $this->translist = $translist;
6421 $this->menucache = $menucache;
6425 * Returns dependencies of current language, en is not included.
6427 * @param string $lang
6428 * @return array all parents, the lang itself is last
6430 public function get_language_dependencies($lang) {
6431 if ($lang === 'en') {
6432 return array();
6434 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
6435 return array();
6437 $string = array();
6438 include("$this->otherroot/$lang/langconfig.php");
6440 if (empty($string['parentlanguage'])) {
6441 return array($lang);
6442 } else {
6443 $parentlang = $string['parentlanguage'];
6444 unset($string);
6445 return array_merge($this->get_language_dependencies($parentlang), array($lang));
6450 * Load all strings for one component
6452 * @param string $component The module the string is associated with
6453 * @param string $lang
6454 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6455 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6456 * @return array of all string for given component and lang
6458 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6459 global $CFG;
6461 list($plugintype, $pluginname) = normalize_component($component);
6462 if ($plugintype == 'core' and is_null($pluginname)) {
6463 $component = 'core';
6464 } else {
6465 $component = $plugintype . '_' . $pluginname;
6468 if (!$disablecache and !$disablelocal) {
6469 // try in-memory cache first
6470 if (isset($this->cache[$lang][$component])) {
6471 $this->countmemcache++;
6472 return $this->cache[$lang][$component];
6475 // try on-disk cache then
6476 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
6477 $this->countdiskcache++;
6478 include($this->cacheroot . "/$lang/$component.php");
6479 return $this->cache[$lang][$component];
6483 // no cache found - let us merge all possible sources of the strings
6484 if ($plugintype === 'core') {
6485 $file = $pluginname;
6486 if ($file === null) {
6487 $file = 'moodle';
6489 $string = array();
6490 // first load english pack
6491 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
6492 return array();
6494 include("$CFG->dirroot/lang/en/$file.php");
6495 $originalkeys = array_keys($string);
6496 $originalkeys = array_flip($originalkeys);
6498 // and then corresponding local if present and allowed
6499 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6500 include("$this->localroot/en_local/$file.php");
6502 // now loop through all langs in correct order
6503 $deps = $this->get_language_dependencies($lang);
6504 foreach ($deps as $dep) {
6505 // the main lang string location
6506 if (file_exists("$this->otherroot/$dep/$file.php")) {
6507 include("$this->otherroot/$dep/$file.php");
6509 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6510 include("$this->localroot/{$dep}_local/$file.php");
6514 } else {
6515 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
6516 return array();
6518 if ($plugintype === 'mod') {
6519 // bloody mod hack
6520 $file = $pluginname;
6521 } else {
6522 $file = $plugintype . '_' . $pluginname;
6524 $string = array();
6525 // first load English pack
6526 if (!file_exists("$location/lang/en/$file.php")) {
6527 //English pack does not exist, so do not try to load anything else
6528 return array();
6530 include("$location/lang/en/$file.php");
6531 $originalkeys = array_keys($string);
6532 $originalkeys = array_flip($originalkeys);
6533 // and then corresponding local english if present
6534 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6535 include("$this->localroot/en_local/$file.php");
6538 // now loop through all langs in correct order
6539 $deps = $this->get_language_dependencies($lang);
6540 foreach ($deps as $dep) {
6541 // legacy location - used by contrib only
6542 if (file_exists("$location/lang/$dep/$file.php")) {
6543 include("$location/lang/$dep/$file.php");
6545 // the main lang string location
6546 if (file_exists("$this->otherroot/$dep/$file.php")) {
6547 include("$this->otherroot/$dep/$file.php");
6549 // local customisations
6550 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6551 include("$this->localroot/{$dep}_local/$file.php");
6556 // we do not want any extra strings from other languages - everything must be in en lang pack
6557 $string = array_intersect_key($string, $originalkeys);
6559 if (!$disablelocal) {
6560 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
6561 // caches so we do not need to do all this merging and dependencies resolving again
6562 $this->cache[$lang][$component] = $string;
6563 if ($this->usediskcache) {
6564 check_dir_exists("$this->cacheroot/$lang");
6565 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
6568 return $string;
6572 * Does the string actually exist?
6574 * get_string() is throwing debug warnings, sometimes we do not want them
6575 * or we want to display better explanation of the problem.
6576 * Note: Use with care!
6578 * @param string $identifier The identifier of the string to search for
6579 * @param string $component The module the string is associated with
6580 * @return boot true if exists
6582 public function string_exists($identifier, $component) {
6583 $identifier = clean_param($identifier, PARAM_STRINGID);
6584 if (empty($identifier)) {
6585 return false;
6587 $lang = current_language();
6588 $string = $this->load_component_strings($component, $lang);
6589 return isset($string[$identifier]);
6593 * Get String returns a requested string
6595 * @param string $identifier The identifier of the string to search for
6596 * @param string $component The module the string is associated with
6597 * @param string|object|array $a An object, string or number that can be used
6598 * within translation strings
6599 * @param string $lang moodle translation language, NULL means use current
6600 * @return string The String !
6602 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6603 $this->countgetstring++;
6604 // there are very many uses of these time formating strings without the 'langconfig' component,
6605 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
6606 static $langconfigstrs = array(
6607 'strftimedate' => 1,
6608 'strftimedatefullshort' => 1,
6609 'strftimedateshort' => 1,
6610 'strftimedatetime' => 1,
6611 'strftimedatetimeshort' => 1,
6612 'strftimedaydate' => 1,
6613 'strftimedaydatetime' => 1,
6614 'strftimedayshort' => 1,
6615 'strftimedaytime' => 1,
6616 'strftimemonthyear' => 1,
6617 'strftimerecent' => 1,
6618 'strftimerecentfull' => 1,
6619 'strftimetime' => 1);
6621 if (empty($component)) {
6622 if (isset($langconfigstrs[$identifier])) {
6623 $component = 'langconfig';
6624 } else {
6625 $component = 'moodle';
6629 if ($lang === NULL) {
6630 $lang = current_language();
6633 $string = $this->load_component_strings($component, $lang);
6635 if (!isset($string[$identifier])) {
6636 if ($component === 'pix' or $component === 'core_pix') {
6637 // this component contains only alt tags for emoticons,
6638 // not all of them are supposed to be defined
6639 return '';
6641 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
6642 // parentlanguage is a special string, undefined means use English if not defined
6643 return 'en';
6645 if ($this->usediskcache) {
6646 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources,
6647 // do NOT write the results to disk cache because it may end up in race conditions see MDL-31904
6648 $this->usediskcache = false;
6649 $string = $this->load_component_strings($component, $lang, true);
6650 $this->usediskcache = true;
6652 if (!isset($string[$identifier])) {
6653 // the string is still missing - should be fixed by developer
6654 list($plugintype, $pluginname) = normalize_component($component);
6655 if ($plugintype == 'core') {
6656 $file = "lang/en/{$component}.php";
6657 } else if ($plugintype == 'mod') {
6658 $file = "mod/{$pluginname}/lang/en/{$pluginname}.php";
6659 } else {
6660 $path = get_plugin_directory($plugintype, $pluginname);
6661 $file = "{$path}/lang/en/{$plugintype}_{$pluginname}.php";
6663 debugging("Invalid get_string() identifier: '{$identifier}' or component '{$component}'. " .
6664 "Perhaps you are missing \$string['{$identifier}'] = ''; in {$file}?", DEBUG_DEVELOPER);
6665 return "[[$identifier]]";
6669 $string = $string[$identifier];
6671 if ($a !== NULL) {
6672 // Process array's and objects (except lang_strings)
6673 if (is_array($a) or (is_object($a) && !($a instanceof lang_string))) {
6674 $a = (array)$a;
6675 $search = array();
6676 $replace = array();
6677 foreach ($a as $key=>$value) {
6678 if (is_int($key)) {
6679 // we do not support numeric keys - sorry!
6680 continue;
6682 if (is_array($value) or (is_object($value) && !($value instanceof lang_string))) {
6683 // we support just string or lang_string as value
6684 continue;
6686 $search[] = '{$a->'.$key.'}';
6687 $replace[] = (string)$value;
6689 if ($search) {
6690 $string = str_replace($search, $replace, $string);
6692 } else {
6693 $string = str_replace('{$a}', (string)$a, $string);
6697 return $string;
6701 * Returns information about the string_manager performance
6703 * @return array
6705 public function get_performance_summary() {
6706 return array(array(
6707 'langcountgetstring' => $this->countgetstring,
6708 'langcountmemcache' => $this->countmemcache,
6709 'langcountdiskcache' => $this->countdiskcache,
6710 ), array(
6711 'langcountgetstring' => 'get_string calls',
6712 'langcountmemcache' => 'strings mem cache hits',
6713 'langcountdiskcache' => 'strings disk cache hits',
6718 * Returns a localised list of all country names, sorted by localised name.
6720 * @param bool $returnall return all or just enabled
6721 * @param string $lang moodle translation language, NULL means use current
6722 * @return array two-letter country code => translated name.
6724 public function get_list_of_countries($returnall = false, $lang = NULL) {
6725 global $CFG;
6727 if ($lang === NULL) {
6728 $lang = current_language();
6731 $countries = $this->load_component_strings('core_countries', $lang);
6732 collatorlib::asort($countries);
6733 if (!$returnall and !empty($CFG->allcountrycodes)) {
6734 $enabled = explode(',', $CFG->allcountrycodes);
6735 $return = array();
6736 foreach ($enabled as $c) {
6737 if (isset($countries[$c])) {
6738 $return[$c] = $countries[$c];
6741 return $return;
6744 return $countries;
6748 * Returns a localised list of languages, sorted by code keys.
6750 * @param string $lang moodle translation language, NULL means use current
6751 * @param string $standard language list standard
6752 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
6753 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
6754 * @return array language code => translated name
6756 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
6757 if ($lang === NULL) {
6758 $lang = current_language();
6761 if ($standard === 'iso6392') {
6762 $langs = $this->load_component_strings('core_iso6392', $lang);
6763 ksort($langs);
6764 return $langs;
6766 } else if ($standard === 'iso6391') {
6767 $langs2 = $this->load_component_strings('core_iso6392', $lang);
6768 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
6769 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
6770 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
6771 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
6772 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
6773 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
6774 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
6775 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
6776 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
6777 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
6778 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
6779 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
6780 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
6781 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6782 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6783 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6784 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6785 $langs1 = array();
6786 foreach ($mapping as $c2=>$c1) {
6787 $langs1[$c1] = $langs2[$c2];
6789 ksort($langs1);
6790 return $langs1;
6792 } else {
6793 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6796 return array();
6800 * Checks if the translation exists for the language
6802 * @param string $lang moodle translation language code
6803 * @param bool $includeall include also disabled translations
6804 * @return bool true if exists
6806 public function translation_exists($lang, $includeall = true) {
6808 if (strpos($lang, '_local') !== false) {
6809 // _local packs are not real translations
6810 return false;
6812 if (!$includeall and !empty($this->translist)) {
6813 if (!in_array($lang, $this->translist)) {
6814 return false;
6817 if ($lang === 'en') {
6818 // part of distribution
6819 return true;
6821 return file_exists("$this->otherroot/$lang/langconfig.php");
6825 * Returns localised list of installed translations
6827 * @param bool $returnall return all or just enabled
6828 * @return array moodle translation code => localised translation name
6830 public function get_list_of_translations($returnall = false) {
6831 global $CFG;
6833 $languages = array();
6835 if (!empty($CFG->langcache) and is_readable($this->menucache)) {
6836 // try to re-use the cached list of all available languages
6837 $cachedlist = json_decode(file_get_contents($this->menucache), true);
6839 if (is_array($cachedlist) and !empty($cachedlist)) {
6840 // the cache file is restored correctly
6842 if (!$returnall and !empty($this->translist)) {
6843 // return just enabled translations
6844 foreach ($cachedlist as $langcode => $langname) {
6845 if (in_array($langcode, $this->translist)) {
6846 $languages[$langcode] = $langname;
6849 return $languages;
6851 } else {
6852 // return all translations
6853 return $cachedlist;
6858 // the cached list of languages is not available, let us populate the list
6860 if (!$returnall and !empty($this->translist)) {
6861 // return only some translations
6862 foreach ($this->translist as $lang) {
6863 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6864 if (strstr($lang, '_local') !== false) {
6865 continue;
6867 if (strstr($lang, '_utf8') !== false) {
6868 continue;
6870 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6871 // some broken or missing lang - can not switch to it anyway
6872 continue;
6874 $string = $this->load_component_strings('langconfig', $lang);
6875 if (!empty($string['thislanguage'])) {
6876 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6878 unset($string);
6881 } else {
6882 // return all languages available in system
6883 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6885 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6886 // Sort all
6888 // Loop through all langs and get info
6889 foreach ($langdirs as $lang) {
6890 if (strstr($lang, '_local') !== false) {
6891 continue;
6893 if (strstr($lang, '_utf8') !== false) {
6894 continue;
6896 $string = $this->load_component_strings('langconfig', $lang);
6897 if (!empty($string['thislanguage'])) {
6898 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6900 unset($string);
6903 if (!empty($CFG->langcache) and !empty($this->menucache)) {
6904 // cache the list so that it can be used next time
6905 collatorlib::asort($languages);
6906 check_dir_exists(dirname($this->menucache), true, true);
6907 file_put_contents($this->menucache, json_encode($languages));
6911 collatorlib::asort($languages);
6913 return $languages;
6917 * Returns localised list of currencies.
6919 * @param string $lang moodle translation language, NULL means use current
6920 * @return array currency code => localised currency name
6922 public function get_list_of_currencies($lang = NULL) {
6923 if ($lang === NULL) {
6924 $lang = current_language();
6927 $currencies = $this->load_component_strings('core_currencies', $lang);
6928 asort($currencies);
6930 return $currencies;
6934 * Clears both in-memory and on-disk caches
6936 public function reset_caches() {
6937 global $CFG;
6938 require_once("$CFG->libdir/filelib.php");
6940 // clear the on-disk disk with aggregated string files
6941 fulldelete($this->cacheroot);
6943 // clear the in-memory cache of loaded strings
6944 $this->cache = array();
6946 // clear the cache containing the list of available translations
6947 // and re-populate it again
6948 fulldelete($this->menucache);
6949 $this->get_list_of_translations(true);
6955 * Fetches minimum strings for installation
6957 * Minimalistic string fetching implementation
6958 * that is used in installer before we fetch the wanted
6959 * language pack from moodle.org lang download site.
6961 * @package core
6962 * @copyright 2010 Petr Skoda (http://skodak.org)
6963 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6965 class install_string_manager implements string_manager {
6966 /** @var string location of pre-install packs for all langs */
6967 protected $installroot;
6970 * Crate new instance of install string manager
6972 public function __construct() {
6973 global $CFG;
6974 $this->installroot = "$CFG->dirroot/install/lang";
6978 * Load all strings for one component
6979 * @param string $component The module the string is associated with
6980 * @param string $lang
6981 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6982 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6983 * @return array of all string for given component and lang
6985 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6986 // not needed in installer
6987 return array();
6991 * Does the string actually exist?
6993 * get_string() is throwing debug warnings, sometimes we do not want them
6994 * or we want to display better explanation of the problem.
6996 * Use with care!
6998 * @param string $identifier The identifier of the string to search for
6999 * @param string $component The module the string is associated with
7000 * @return boot true if exists
7002 public function string_exists($identifier, $component) {
7003 $identifier = clean_param($identifier, PARAM_STRINGID);
7004 if (empty($identifier)) {
7005 return false;
7007 // simple old style hack ;)
7008 $str = get_string($identifier, $component);
7009 return (strpos($str, '[[') === false);
7013 * Get String returns a requested string
7015 * @param string $identifier The identifier of the string to search for
7016 * @param string $component The module the string is associated with
7017 * @param string|object|array $a An object, string or number that can be used
7018 * within translation strings
7019 * @param string $lang moodle translation language, NULL means use current
7020 * @return string The String !
7022 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
7023 if (!$component) {
7024 $component = 'moodle';
7027 if ($lang === NULL) {
7028 $lang = current_language();
7031 //get parent lang
7032 $parent = '';
7033 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
7034 if (file_exists("$this->installroot/$lang/langconfig.php")) {
7035 $string = array();
7036 include("$this->installroot/$lang/langconfig.php");
7037 if (isset($string['parentlanguage'])) {
7038 $parent = $string['parentlanguage'];
7040 unset($string);
7044 // include en string first
7045 if (!file_exists("$this->installroot/en/$component.php")) {
7046 return "[[$identifier]]";
7048 $string = array();
7049 include("$this->installroot/en/$component.php");
7051 // now override en with parent if defined
7052 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
7053 include("$this->installroot/$parent/$component.php");
7056 // finally override with requested language
7057 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
7058 include("$this->installroot/$lang/$component.php");
7061 if (!isset($string[$identifier])) {
7062 return "[[$identifier]]";
7065 $string = $string[$identifier];
7067 if ($a !== NULL) {
7068 if (is_object($a) or is_array($a)) {
7069 $a = (array)$a;
7070 $search = array();
7071 $replace = array();
7072 foreach ($a as $key=>$value) {
7073 if (is_int($key)) {
7074 // we do not support numeric keys - sorry!
7075 continue;
7077 $search[] = '{$a->'.$key.'}';
7078 $replace[] = (string)$value;
7080 if ($search) {
7081 $string = str_replace($search, $replace, $string);
7083 } else {
7084 $string = str_replace('{$a}', (string)$a, $string);
7088 return $string;
7092 * Returns a localised list of all country names, sorted by country keys.
7094 * @param bool $returnall return all or just enabled
7095 * @param string $lang moodle translation language, NULL means use current
7096 * @return array two-letter country code => translated name.
7098 public function get_list_of_countries($returnall = false, $lang = NULL) {
7099 //not used in installer
7100 return array();
7104 * Returns a localised list of languages, sorted by code keys.
7106 * @param string $lang moodle translation language, NULL means use current
7107 * @param string $standard language list standard
7108 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
7109 * @return array language code => translated name
7111 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
7112 //not used in installer
7113 return array();
7117 * Checks if the translation exists for the language
7119 * @param string $lang moodle translation language code
7120 * @param bool $includeall include also disabled translations
7121 * @return bool true if exists
7123 public function translation_exists($lang, $includeall = true) {
7124 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
7128 * Returns localised list of installed translations
7129 * @param bool $returnall return all or just enabled
7130 * @return array moodle translation code => localised translation name
7132 public function get_list_of_translations($returnall = false) {
7133 // return all is ignored here - we need to know all langs in installer
7134 $languages = array();
7135 // Get raw list of lang directories
7136 $langdirs = get_list_of_plugins('install/lang');
7137 asort($langdirs);
7138 // Get some info from each lang
7139 foreach ($langdirs as $lang) {
7140 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
7141 $string = array();
7142 include($this->installroot.'/'.$lang.'/langconfig.php');
7143 if (!empty($string['thislanguage'])) {
7144 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
7148 // Return array
7149 return $languages;
7153 * Returns localised list of currencies.
7155 * @param string $lang moodle translation language, NULL means use current
7156 * @return array currency code => localised currency name
7158 public function get_list_of_currencies($lang = NULL) {
7159 // not used in installer
7160 return array();
7164 * This implementation does not use any caches
7166 public function reset_caches() {}
7171 * Returns a localized string.
7173 * Returns the translated string specified by $identifier as
7174 * for $module. Uses the same format files as STphp.
7175 * $a is an object, string or number that can be used
7176 * within translation strings
7178 * eg 'hello {$a->firstname} {$a->lastname}'
7179 * or 'hello {$a}'
7181 * If you would like to directly echo the localized string use
7182 * the function {@link print_string()}
7184 * Example usage of this function involves finding the string you would
7185 * like a local equivalent of and using its identifier and module information
7186 * to retrieve it.<br/>
7187 * If you open moodle/lang/en/moodle.php and look near line 278
7188 * you will find a string to prompt a user for their word for 'course'
7189 * <code>
7190 * $string['course'] = 'Course';
7191 * </code>
7192 * So if you want to display the string 'Course'
7193 * in any language that supports it on your site
7194 * you just need to use the identifier 'course'
7195 * <code>
7196 * $mystring = '<strong>'. get_string('course') .'</strong>';
7197 * or
7198 * </code>
7199 * If the string you want is in another file you'd take a slightly
7200 * different approach. Looking in moodle/lang/en/calendar.php you find
7201 * around line 75:
7202 * <code>
7203 * $string['typecourse'] = 'Course event';
7204 * </code>
7205 * If you want to display the string "Course event" in any language
7206 * supported you would use the identifier 'typecourse' and the module 'calendar'
7207 * (because it is in the file calendar.php):
7208 * <code>
7209 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
7210 * </code>
7212 * As a last resort, should the identifier fail to map to a string
7213 * the returned string will be [[ $identifier ]]
7215 * In Moodle 2.3 there is a new argument to this function $lazyload.
7216 * Setting $lazyload to true causes get_string to return a lang_string object
7217 * rather than the string itself. The fetching of the string is then put off until
7218 * the string object is first used. The object can be used by calling it's out
7219 * method or by casting the object to a string, either directly e.g.
7220 * (string)$stringobject
7221 * or indirectly by using the string within another string or echoing it out e.g.
7222 * echo $stringobject
7223 * return "<p>{$stringobject}</p>";
7224 * It is worth noting that using $lazyload and attempting to use the string as an
7225 * array key will cause a fatal error as objects cannot be used as array keys.
7226 * But you should never do that anyway!
7227 * For more information {@see lang_string}
7229 * @category string
7230 * @param string $identifier The key identifier for the localized string
7231 * @param string $component The module where the key identifier is stored,
7232 * usually expressed as the filename in the language pack without the
7233 * .php on the end but can also be written as mod/forum or grade/export/xls.
7234 * If none is specified then moodle.php is used.
7235 * @param string|object|array $a An object, string or number that can be used
7236 * within translation strings
7237 * @param bool $lazyload If set to true a string object is returned instead of
7238 * the string itself. The string then isn't calculated until it is first used.
7239 * @return string The localized string.
7241 function get_string($identifier, $component = '', $a = NULL, $lazyload = false) {
7242 global $CFG;
7244 // If the lazy load argument has been supplied return a lang_string object
7245 // instead.
7246 // We need to make sure it is true (and a bool) as you will see below there
7247 // used to be a forth argument at one point.
7248 if ($lazyload === true) {
7249 return new lang_string($identifier, $component, $a);
7252 $identifier = clean_param($identifier, PARAM_STRINGID);
7253 if (empty($identifier)) {
7254 throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.');
7257 // There is now a forth argument again, this time it is a boolean however so
7258 // we can still check for the old extralocations parameter.
7259 if (!is_bool($lazyload) && !empty($lazyload)) {
7260 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
7263 if (strpos($component, '/') !== false) {
7264 debugging('The module name you passed to get_string is the deprecated format ' .
7265 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
7266 $componentpath = explode('/', $component);
7268 switch ($componentpath[0]) {
7269 case 'mod':
7270 $component = $componentpath[1];
7271 break;
7272 case 'blocks':
7273 case 'block':
7274 $component = 'block_'.$componentpath[1];
7275 break;
7276 case 'enrol':
7277 $component = 'enrol_'.$componentpath[1];
7278 break;
7279 case 'format':
7280 $component = 'format_'.$componentpath[1];
7281 break;
7282 case 'grade':
7283 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
7284 break;
7288 $result = get_string_manager()->get_string($identifier, $component, $a);
7290 // Debugging feature lets you display string identifier and component
7291 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
7292 $result .= ' {' . $identifier . '/' . $component . '}';
7294 return $result;
7298 * Converts an array of strings to their localized value.
7300 * @param array $array An array of strings
7301 * @param string $component The language module that these strings can be found in.
7302 * @return stdClass translated strings.
7304 function get_strings($array, $component = '') {
7305 $string = new stdClass;
7306 foreach ($array as $item) {
7307 $string->$item = get_string($item, $component);
7309 return $string;
7313 * Prints out a translated string.
7315 * Prints out a translated string using the return value from the {@link get_string()} function.
7317 * Example usage of this function when the string is in the moodle.php file:<br/>
7318 * <code>
7319 * echo '<strong>';
7320 * print_string('course');
7321 * echo '</strong>';
7322 * </code>
7324 * Example usage of this function when the string is not in the moodle.php file:<br/>
7325 * <code>
7326 * echo '<h1>';
7327 * print_string('typecourse', 'calendar');
7328 * echo '</h1>';
7329 * </code>
7331 * @category string
7332 * @param string $identifier The key identifier for the localized string
7333 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
7334 * @param string|object|array $a An object, string or number that can be used within translation strings
7336 function print_string($identifier, $component = '', $a = NULL) {
7337 echo get_string($identifier, $component, $a);
7341 * Returns a list of charset codes
7343 * Returns a list of charset codes. It's hardcoded, so they should be added manually
7344 * (checking that such charset is supported by the texlib library!)
7346 * @return array And associative array with contents in the form of charset => charset
7348 function get_list_of_charsets() {
7350 $charsets = array(
7351 'EUC-JP' => 'EUC-JP',
7352 'ISO-2022-JP'=> 'ISO-2022-JP',
7353 'ISO-8859-1' => 'ISO-8859-1',
7354 'SHIFT-JIS' => 'SHIFT-JIS',
7355 'GB2312' => 'GB2312',
7356 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
7357 'UTF-8' => 'UTF-8');
7359 asort($charsets);
7361 return $charsets;
7365 * Returns a list of valid and compatible themes
7367 * @return array
7369 function get_list_of_themes() {
7370 global $CFG;
7372 $themes = array();
7374 if (!empty($CFG->themelist)) { // use admin's list of themes
7375 $themelist = explode(',', $CFG->themelist);
7376 } else {
7377 $themelist = array_keys(get_plugin_list("theme"));
7380 foreach ($themelist as $key => $themename) {
7381 $theme = theme_config::load($themename);
7382 $themes[$themename] = $theme;
7385 collatorlib::asort_objects_by_method($themes, 'get_theme_name');
7387 return $themes;
7391 * Returns a list of timezones in the current language
7393 * @global object
7394 * @global object
7395 * @return array
7397 function get_list_of_timezones() {
7398 global $CFG, $DB;
7400 static $timezones;
7402 if (!empty($timezones)) { // This function has been called recently
7403 return $timezones;
7406 $timezones = array();
7408 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
7409 foreach($rawtimezones as $timezone) {
7410 if (!empty($timezone->name)) {
7411 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
7412 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
7413 } else {
7414 $timezones[$timezone->name] = $timezone->name;
7416 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
7417 $timezones[$timezone->name] = $timezone->name;
7423 asort($timezones);
7425 for ($i = -13; $i <= 13; $i += .5) {
7426 $tzstring = 'UTC';
7427 if ($i < 0) {
7428 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
7429 } else if ($i > 0) {
7430 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
7431 } else {
7432 $timezones[sprintf("%.1f", $i)] = $tzstring;
7436 return $timezones;
7440 * Factory function for emoticon_manager
7442 * @return emoticon_manager singleton
7444 function get_emoticon_manager() {
7445 static $singleton = null;
7447 if (is_null($singleton)) {
7448 $singleton = new emoticon_manager();
7451 return $singleton;
7455 * Provides core support for plugins that have to deal with
7456 * emoticons (like HTML editor or emoticon filter).
7458 * Whenever this manager mentiones 'emoticon object', the following data
7459 * structure is expected: stdClass with properties text, imagename, imagecomponent,
7460 * altidentifier and altcomponent
7462 * @see admin_setting_emoticons
7464 class emoticon_manager {
7467 * Returns the currently enabled emoticons
7469 * @return array of emoticon objects
7471 public function get_emoticons() {
7472 global $CFG;
7474 if (empty($CFG->emoticons)) {
7475 return array();
7478 $emoticons = $this->decode_stored_config($CFG->emoticons);
7480 if (!is_array($emoticons)) {
7481 // something is wrong with the format of stored setting
7482 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
7483 return array();
7486 return $emoticons;
7490 * Converts emoticon object into renderable pix_emoticon object
7492 * @param stdClass $emoticon emoticon object
7493 * @param array $attributes explicit HTML attributes to set
7494 * @return pix_emoticon
7496 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
7497 $stringmanager = get_string_manager();
7498 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
7499 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
7500 } else {
7501 $alt = s($emoticon->text);
7503 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
7507 * Encodes the array of emoticon objects into a string storable in config table
7509 * @see self::decode_stored_config()
7510 * @param array $emoticons array of emtocion objects
7511 * @return string
7513 public function encode_stored_config(array $emoticons) {
7514 return json_encode($emoticons);
7518 * Decodes the string into an array of emoticon objects
7520 * @see self::encode_stored_config()
7521 * @param string $encoded
7522 * @return string|null
7524 public function decode_stored_config($encoded) {
7525 $decoded = json_decode($encoded);
7526 if (!is_array($decoded)) {
7527 return null;
7529 return $decoded;
7533 * Returns default set of emoticons supported by Moodle
7535 * @return array of sdtClasses
7537 public function default_emoticons() {
7538 return array(
7539 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
7540 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
7541 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
7542 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
7543 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
7544 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
7545 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
7546 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
7547 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
7548 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
7549 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
7550 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
7551 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
7552 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
7553 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
7554 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
7555 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
7556 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
7557 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
7558 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
7559 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
7560 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
7561 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
7562 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
7563 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
7564 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
7565 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
7566 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
7567 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
7568 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
7573 * Helper method preparing the stdClass with the emoticon properties
7575 * @param string|array $text or array of strings
7576 * @param string $imagename to be used by {@see pix_emoticon}
7577 * @param string $altidentifier alternative string identifier, null for no alt
7578 * @param array $altcomponent where the alternative string is defined
7579 * @param string $imagecomponent to be used by {@see pix_emoticon}
7580 * @return stdClass
7582 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
7583 return (object)array(
7584 'text' => $text,
7585 'imagename' => $imagename,
7586 'imagecomponent' => $imagecomponent,
7587 'altidentifier' => $altidentifier,
7588 'altcomponent' => $altcomponent,
7593 /// ENCRYPTION ////////////////////////////////////////////////
7596 * rc4encrypt
7598 * Please note that in this version of moodle that the default for rc4encryption is
7599 * using the slightly more secure password key. There may be an issue when upgrading
7600 * from an older version of moodle.
7602 * @todo MDL-31836 Remove the old password key in version 2.4
7603 * Code also needs to be changed in sessionlib.php
7604 * @see get_moodle_cookie()
7605 * @see set_moodle_cookie()
7607 * @param string $data Data to encrypt.
7608 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7609 * @return string The now encrypted data.
7611 function rc4encrypt($data, $usesecurekey = true) {
7612 if (!$usesecurekey) {
7613 $passwordkey = 'nfgjeingjk';
7614 } else {
7615 $passwordkey = get_site_identifier();
7617 return endecrypt($passwordkey, $data, '');
7621 * rc4decrypt
7623 * Please note that in this version of moodle that the default for rc4encryption is
7624 * using the slightly more secure password key. There may be an issue when upgrading
7625 * from an older version of moodle.
7627 * @todo MDL-31836 Remove the old password key in version 2.4
7628 * Code also needs to be changed in sessionlib.php
7629 * @see get_moodle_cookie()
7630 * @see set_moodle_cookie()
7632 * @param string $data Data to decrypt.
7633 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7634 * @return string The now decrypted data.
7636 function rc4decrypt($data, $usesecurekey = true) {
7637 if (!$usesecurekey) {
7638 $passwordkey = 'nfgjeingjk';
7639 } else {
7640 $passwordkey = get_site_identifier();
7642 return endecrypt($passwordkey, $data, 'de');
7646 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
7648 * @todo Finish documenting this function
7650 * @param string $pwd The password to use when encrypting or decrypting
7651 * @param string $data The data to be decrypted/encrypted
7652 * @param string $case Either 'de' for decrypt or '' for encrypt
7653 * @return string
7655 function endecrypt ($pwd, $data, $case) {
7657 if ($case == 'de') {
7658 $data = urldecode($data);
7661 $key[] = '';
7662 $box[] = '';
7663 $temp_swap = '';
7664 $pwd_length = 0;
7666 $pwd_length = strlen($pwd);
7668 for ($i = 0; $i <= 255; $i++) {
7669 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
7670 $box[$i] = $i;
7673 $x = 0;
7675 for ($i = 0; $i <= 255; $i++) {
7676 $x = ($x + $box[$i] + $key[$i]) % 256;
7677 $temp_swap = $box[$i];
7678 $box[$i] = $box[$x];
7679 $box[$x] = $temp_swap;
7682 $temp = '';
7683 $k = '';
7685 $cipherby = '';
7686 $cipher = '';
7688 $a = 0;
7689 $j = 0;
7691 for ($i = 0; $i < strlen($data); $i++) {
7692 $a = ($a + 1) % 256;
7693 $j = ($j + $box[$a]) % 256;
7694 $temp = $box[$a];
7695 $box[$a] = $box[$j];
7696 $box[$j] = $temp;
7697 $k = $box[(($box[$a] + $box[$j]) % 256)];
7698 $cipherby = ord(substr($data, $i, 1)) ^ $k;
7699 $cipher .= chr($cipherby);
7702 if ($case == 'de') {
7703 $cipher = urldecode(urlencode($cipher));
7704 } else {
7705 $cipher = urlencode($cipher);
7708 return $cipher;
7711 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
7714 * Returns the exact absolute path to plugin directory.
7716 * @param string $plugintype type of plugin
7717 * @param string $name name of the plugin
7718 * @return string full path to plugin directory; NULL if not found
7720 function get_plugin_directory($plugintype, $name) {
7721 global $CFG;
7723 if ($plugintype === '') {
7724 $plugintype = 'mod';
7727 $types = get_plugin_types(true);
7728 if (!array_key_exists($plugintype, $types)) {
7729 return NULL;
7731 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
7733 if (!empty($CFG->themedir) and $plugintype === 'theme') {
7734 if (!is_dir($types['theme'] . '/' . $name)) {
7735 // ok, so the theme is supposed to be in the $CFG->themedir
7736 return $CFG->themedir . '/' . $name;
7740 return $types[$plugintype].'/'.$name;
7744 * Return exact absolute path to a plugin directory.
7746 * @param string $component name such as 'moodle', 'mod_forum'
7747 * @return string full path to component directory; NULL if not found
7749 function get_component_directory($component) {
7750 global $CFG;
7752 list($type, $plugin) = normalize_component($component);
7754 if ($type === 'core') {
7755 if ($plugin === NULL ) {
7756 $path = $CFG->libdir;
7757 } else {
7758 $subsystems = get_core_subsystems();
7759 if (isset($subsystems[$plugin])) {
7760 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
7761 } else {
7762 $path = NULL;
7766 } else {
7767 $path = get_plugin_directory($type, $plugin);
7770 return $path;
7774 * Normalize the component name using the "frankenstyle" names.
7775 * @param string $component
7776 * @return array $type+$plugin elements
7778 function normalize_component($component) {
7779 if ($component === 'moodle' or $component === 'core') {
7780 $type = 'core';
7781 $plugin = NULL;
7783 } else if (strpos($component, '_') === false) {
7784 $subsystems = get_core_subsystems();
7785 if (array_key_exists($component, $subsystems)) {
7786 $type = 'core';
7787 $plugin = $component;
7788 } else {
7789 // everything else is a module
7790 $type = 'mod';
7791 $plugin = $component;
7794 } else {
7795 list($type, $plugin) = explode('_', $component, 2);
7796 $plugintypes = get_plugin_types(false);
7797 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
7798 $type = 'mod';
7799 $plugin = $component;
7803 return array($type, $plugin);
7807 * List all core subsystems and their location
7809 * This is a whitelist of components that are part of the core and their
7810 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
7811 * plugin is not listed here and it does not have proper plugintype prefix,
7812 * then it is considered as course activity module.
7814 * The location is dirroot relative path. NULL means there is no special
7815 * directory for this subsystem. If the location is set, the subsystem's
7816 * renderer.php is expected to be there.
7818 * @return array of (string)name => (string|null)location
7820 function get_core_subsystems() {
7821 global $CFG;
7823 static $info = null;
7825 if (!$info) {
7826 $info = array(
7827 'access' => NULL,
7828 'admin' => $CFG->admin,
7829 'auth' => 'auth',
7830 'backup' => 'backup/util/ui',
7831 'block' => 'blocks',
7832 'blog' => 'blog',
7833 'bulkusers' => NULL,
7834 'calendar' => 'calendar',
7835 'cohort' => 'cohort',
7836 'condition' => NULL,
7837 'completion' => NULL,
7838 'countries' => NULL,
7839 'course' => 'course',
7840 'currencies' => NULL,
7841 'dbtransfer' => NULL,
7842 'debug' => NULL,
7843 'dock' => NULL,
7844 'editor' => 'lib/editor',
7845 'edufields' => NULL,
7846 'enrol' => 'enrol',
7847 'error' => NULL,
7848 'filepicker' => NULL,
7849 'files' => 'files',
7850 'filters' => NULL,
7851 'fonts' => NULL,
7852 'form' => 'lib/form',
7853 'grades' => 'grade',
7854 'grading' => 'grade/grading',
7855 'group' => 'group',
7856 'help' => NULL,
7857 'hub' => NULL,
7858 'imscc' => NULL,
7859 'install' => NULL,
7860 'iso6392' => NULL,
7861 'langconfig' => NULL,
7862 'license' => NULL,
7863 'mathslib' => NULL,
7864 'media' => 'media',
7865 'message' => 'message',
7866 'mimetypes' => NULL,
7867 'mnet' => 'mnet',
7868 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7869 'my' => 'my',
7870 'notes' => 'notes',
7871 'pagetype' => NULL,
7872 'pix' => NULL,
7873 'plagiarism' => 'plagiarism',
7874 'plugin' => NULL,
7875 'portfolio' => 'portfolio',
7876 'publish' => 'course/publish',
7877 'question' => 'question',
7878 'rating' => 'rating',
7879 'register' => 'admin/registration', //TODO: this is wrong, unfortunately we would need to modify hub code to pass around the correct url
7880 'repository' => 'repository',
7881 'rss' => 'rss',
7882 'role' => $CFG->admin.'/role',
7883 'search' => 'search',
7884 'table' => NULL,
7885 'tag' => 'tag',
7886 'timezones' => NULL,
7887 'user' => 'user',
7888 'userkey' => NULL,
7889 'webservice' => 'webservice',
7893 return $info;
7897 * Lists all plugin types
7898 * @param bool $fullpaths false means relative paths from dirroot
7899 * @return array Array of strings - name=>location
7901 function get_plugin_types($fullpaths=true) {
7902 global $CFG;
7904 static $info = null;
7905 static $fullinfo = null;
7907 if (!$info) {
7908 $info = array('qtype' => 'question/type',
7909 'mod' => 'mod',
7910 'auth' => 'auth',
7911 'enrol' => 'enrol',
7912 'message' => 'message/output',
7913 'block' => 'blocks',
7914 'filter' => 'filter',
7915 'editor' => 'lib/editor',
7916 'format' => 'course/format',
7917 'profilefield' => 'user/profile/field',
7918 'report' => 'report',
7919 'coursereport' => 'course/report', // must be after system reports
7920 'gradeexport' => 'grade/export',
7921 'gradeimport' => 'grade/import',
7922 'gradereport' => 'grade/report',
7923 'gradingform' => 'grade/grading/form',
7924 'mnetservice' => 'mnet/service',
7925 'webservice' => 'webservice',
7926 'repository' => 'repository',
7927 'portfolio' => 'portfolio',
7928 'qbehaviour' => 'question/behaviour',
7929 'qformat' => 'question/format',
7930 'plagiarism' => 'plagiarism',
7931 'tool' => $CFG->admin.'/tool',
7932 'theme' => 'theme', // this is a bit hacky, themes may be in $CFG->themedir too
7935 $mods = get_plugin_list('mod');
7936 foreach ($mods as $mod => $moddir) {
7937 if (file_exists("$moddir/db/subplugins.php")) {
7938 $subplugins = array();
7939 include("$moddir/db/subplugins.php");
7940 foreach ($subplugins as $subtype=>$dir) {
7941 $info[$subtype] = $dir;
7946 // local is always last!
7947 $info['local'] = 'local';
7949 $fullinfo = array();
7950 foreach ($info as $type => $dir) {
7951 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
7955 return ($fullpaths ? $fullinfo : $info);
7959 * Simplified version of get_list_of_plugins()
7960 * @param string $plugintype type of plugin
7961 * @return array name=>fulllocation pairs of plugins of given type
7963 function get_plugin_list($plugintype) {
7964 global $CFG;
7966 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
7967 if ($plugintype == 'auth') {
7968 // Historically we have had an auth plugin called 'db', so allow a special case.
7969 $key = array_search('db', $ignored);
7970 if ($key !== false) {
7971 unset($ignored[$key]);
7975 if ($plugintype === '') {
7976 $plugintype = 'mod';
7979 $fulldirs = array();
7981 if ($plugintype === 'mod') {
7982 // mod is an exception because we have to call this function from get_plugin_types()
7983 $fulldirs[] = $CFG->dirroot.'/mod';
7985 } else if ($plugintype === 'theme') {
7986 $fulldirs[] = $CFG->dirroot.'/theme';
7987 // themes are special because they may be stored also in separate directory
7988 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
7989 $fulldirs[] = $CFG->themedir;
7992 } else {
7993 $types = get_plugin_types(true);
7994 if (!array_key_exists($plugintype, $types)) {
7995 return array();
7997 $fulldir = $types[$plugintype];
7998 if (!file_exists($fulldir)) {
7999 return array();
8001 $fulldirs[] = $fulldir;
8004 $result = array();
8006 foreach ($fulldirs as $fulldir) {
8007 if (!is_dir($fulldir)) {
8008 continue;
8010 $items = new DirectoryIterator($fulldir);
8011 foreach ($items as $item) {
8012 if ($item->isDot() or !$item->isDir()) {
8013 continue;
8015 $pluginname = $item->getFilename();
8016 if (in_array($pluginname, $ignored)) {
8017 continue;
8019 $pluginname = clean_param($pluginname, PARAM_PLUGIN);
8020 if (empty($pluginname)) {
8021 // better ignore plugins with problematic names here
8022 continue;
8024 $result[$pluginname] = $fulldir.'/'.$pluginname;
8025 unset($item);
8027 unset($items);
8030 //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!
8031 ksort($result);
8032 return $result;
8036 * Get a list of all the plugins of a given type that contain a particular file.
8037 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8038 * @param string $file the name of file that must be present in the plugin.
8039 * (e.g. 'view.php', 'db/install.xml').
8040 * @param bool $include if true (default false), the file will be include_once-ed if found.
8041 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
8042 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
8044 function get_plugin_list_with_file($plugintype, $file, $include = false) {
8045 global $CFG; // Necessary in case it is referenced by include()d PHP scripts.
8047 $plugins = array();
8049 foreach(get_plugin_list($plugintype) as $plugin => $dir) {
8050 $path = $dir . '/' . $file;
8051 if (file_exists($path)) {
8052 if ($include) {
8053 include_once($path);
8055 $plugins[$plugin] = $path;
8059 return $plugins;
8063 * Get a list of all the plugins of a given type that define a certain API function
8064 * in a certain file. The plugin component names and function names are returned.
8066 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8067 * @param string $function the part of the name of the function after the
8068 * frankenstyle prefix. e.g 'hook' if you are looking for functions with
8069 * names like report_courselist_hook.
8070 * @param string $file the name of file within the plugin that defines the
8071 * function. Defaults to lib.php.
8072 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8073 * and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
8075 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
8076 $pluginfunctions = array();
8077 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8078 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
8080 if (function_exists($fullfunction)) {
8081 // Function exists with standard name. Store, indexed by
8082 // frankenstyle name of plugin
8083 $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
8085 } else if ($plugintype === 'mod') {
8086 // For modules, we also allow plugin without full frankenstyle
8087 // but just starting with the module name
8088 $shortfunction = $plugin . '_' . $function;
8089 if (function_exists($shortfunction)) {
8090 $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
8094 return $pluginfunctions;
8098 * Get a list of all the plugins of a given type that define a certain class
8099 * in a certain file. The plugin component names and class names are returned.
8101 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
8102 * @param string $class the part of the name of the class after the
8103 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
8104 * names like report_courselist_thing. If you are looking for classes with
8105 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
8106 * @param string $file the name of file within the plugin that defines the class.
8107 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
8108 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
8110 function get_plugin_list_with_class($plugintype, $class, $file) {
8111 if ($class) {
8112 $suffix = '_' . $class;
8113 } else {
8114 $suffix = '';
8117 $pluginclasses = array();
8118 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
8119 $classname = $plugintype . '_' . $plugin . $suffix;
8120 if (class_exists($classname)) {
8121 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
8125 return $pluginclasses;
8129 * Lists plugin-like directories within specified directory
8131 * This function was originally used for standard Moodle plugins, please use
8132 * new get_plugin_list() now.
8134 * This function is used for general directory listing and backwards compatility.
8136 * @param string $directory relative directory from root
8137 * @param string $exclude dir name to exclude from the list (defaults to none)
8138 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
8139 * @return array Sorted array of directory names found under the requested parameters
8141 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
8142 global $CFG;
8144 $plugins = array();
8146 if (empty($basedir)) {
8147 $basedir = $CFG->dirroot .'/'. $directory;
8149 } else {
8150 $basedir = $basedir .'/'. $directory;
8153 if (file_exists($basedir) && filetype($basedir) == 'dir') {
8154 if (!$dirhandle = opendir($basedir)) {
8155 debugging("Directory permission error for plugin ({$directory}). Directory exists but cannot be read.", DEBUG_DEVELOPER);
8156 return array();
8158 while (false !== ($dir = readdir($dirhandle))) {
8159 $firstchar = substr($dir, 0, 1);
8160 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
8161 continue;
8163 if (filetype($basedir .'/'. $dir) != 'dir') {
8164 continue;
8166 $plugins[] = $dir;
8168 closedir($dirhandle);
8170 if ($plugins) {
8171 asort($plugins);
8173 return $plugins;
8177 * Invoke plugin's callback functions
8179 * @param string $type plugin type e.g. 'mod'
8180 * @param string $name plugin name
8181 * @param string $feature feature name
8182 * @param string $action feature's action
8183 * @param array $params parameters of callback function, should be an array
8184 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8185 * @return mixed
8187 * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
8189 function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
8190 return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
8194 * Invoke component's callback functions
8196 * @param string $component frankenstyle component name, e.g. 'mod_quiz'
8197 * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
8198 * @param array $params parameters of callback function
8199 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8200 * @return mixed
8202 function component_callback($component, $function, array $params = array(), $default = null) {
8203 global $CFG; // this is needed for require_once() below
8205 $cleancomponent = clean_param($component, PARAM_COMPONENT);
8206 if (empty($cleancomponent)) {
8207 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8209 $component = $cleancomponent;
8211 list($type, $name) = normalize_component($component);
8212 $component = $type . '_' . $name;
8214 $oldfunction = $name.'_'.$function;
8215 $function = $component.'_'.$function;
8217 $dir = get_component_directory($component);
8218 if (empty($dir)) {
8219 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8222 // Load library and look for function
8223 if (file_exists($dir.'/lib.php')) {
8224 require_once($dir.'/lib.php');
8227 if (!function_exists($function) and function_exists($oldfunction)) {
8228 if ($type !== 'mod' and $type !== 'core') {
8229 debugging("Please use new function name $function instead of legacy $oldfunction");
8231 $function = $oldfunction;
8234 if (function_exists($function)) {
8235 // Function exists, so just return function result
8236 $ret = call_user_func_array($function, $params);
8237 if (is_null($ret)) {
8238 return $default;
8239 } else {
8240 return $ret;
8243 return $default;
8247 * Checks whether a plugin supports a specified feature.
8249 * @param string $type Plugin type e.g. 'mod'
8250 * @param string $name Plugin name e.g. 'forum'
8251 * @param string $feature Feature code (FEATURE_xx constant)
8252 * @param mixed $default default value if feature support unknown
8253 * @return mixed Feature result (false if not supported, null if feature is unknown,
8254 * otherwise usually true but may have other feature-specific value such as array)
8256 function plugin_supports($type, $name, $feature, $default = NULL) {
8257 global $CFG;
8259 if ($type === 'mod' and $name === 'NEWMODULE') {
8260 //somebody forgot to rename the module template
8261 return false;
8264 $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
8265 if (empty($component)) {
8266 throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
8269 $function = null;
8271 if ($type === 'mod') {
8272 // we need this special case because we support subplugins in modules,
8273 // otherwise it would end up in infinite loop
8274 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
8275 include_once("$CFG->dirroot/mod/$name/lib.php");
8276 $function = $component.'_supports';
8277 if (!function_exists($function)) {
8278 // legacy non-frankenstyle function name
8279 $function = $name.'_supports';
8281 } else {
8282 // invalid module
8285 } else {
8286 if (!$path = get_plugin_directory($type, $name)) {
8287 // non existent plugin type
8288 return false;
8290 if (file_exists("$path/lib.php")) {
8291 include_once("$path/lib.php");
8292 $function = $component.'_supports';
8296 if ($function and function_exists($function)) {
8297 $supports = $function($feature);
8298 if (is_null($supports)) {
8299 // plugin does not know - use default
8300 return $default;
8301 } else {
8302 return $supports;
8306 //plugin does not care, so use default
8307 return $default;
8311 * Returns true if the current version of PHP is greater that the specified one.
8313 * @todo Check PHP version being required here is it too low?
8315 * @param string $version The version of php being tested.
8316 * @return bool
8318 function check_php_version($version='5.2.4') {
8319 return (version_compare(phpversion(), $version) >= 0);
8323 * Checks to see if is the browser operating system matches the specified
8324 * brand.
8326 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
8328 * @uses $_SERVER
8329 * @param string $brand The operating system identifier being tested
8330 * @return bool true if the given brand below to the detected operating system
8332 function check_browser_operating_system($brand) {
8333 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8334 return false;
8337 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
8338 return true;
8341 return false;
8345 * Checks to see if is a browser matches the specified
8346 * brand and is equal or better version.
8348 * @uses $_SERVER
8349 * @param string $brand The browser identifier being tested
8350 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
8351 * @return bool true if the given version is below that of the detected browser
8353 function check_browser_version($brand, $version = null) {
8354 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8355 return false;
8358 $agent = $_SERVER['HTTP_USER_AGENT'];
8360 switch ($brand) {
8362 case 'Camino': /// OSX browser using Gecke engine
8363 if (strpos($agent, 'Camino') === false) {
8364 return false;
8366 if (empty($version)) {
8367 return true; // no version specified
8369 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
8370 if (version_compare($match[1], $version) >= 0) {
8371 return true;
8374 break;
8377 case 'Firefox': /// Mozilla Firefox browsers
8378 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
8379 return false;
8381 if (empty($version)) {
8382 return true; // no version specified
8384 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8385 if (version_compare($match[2], $version) >= 0) {
8386 return true;
8389 break;
8392 case 'Gecko': /// Gecko based browsers
8393 if (empty($version) and substr_count($agent, 'Camino')) {
8394 // MacOS X Camino support
8395 $version = 20041110;
8398 // the proper string - Gecko/CCYYMMDD Vendor/Version
8399 // Faster version and work-a-round No IDN problem.
8400 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
8401 if ($match[1] > $version) {
8402 return true;
8405 break;
8408 case 'MSIE': /// Internet Explorer
8409 if (strpos($agent, 'Opera') !== false) { // Reject Opera
8410 return false;
8412 // in case of IE we have to deal with BC of the version parameter
8413 if (is_null($version)) {
8414 $version = 5.5; // anything older is not considered a browser at all!
8417 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
8418 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
8419 if (version_compare($match[1], $version) >= 0) {
8420 return true;
8423 break;
8426 case 'Opera': /// Opera
8427 if (strpos($agent, 'Opera') === false) {
8428 return false;
8430 if (empty($version)) {
8431 return true; // no version specified
8433 // Recent Opera useragents have Version/ with the actual version, e.g.:
8434 // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
8435 // That's Opera 12.01, not 9.8.
8436 if (preg_match("/Version\/([0-9\.]+)/i", $agent, $match)) {
8437 if (version_compare($match[1], $version) >= 0) {
8438 return true;
8440 } else if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
8441 if (version_compare($match[1], $version) >= 0) {
8442 return true;
8445 break;
8448 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
8449 if (strpos($agent, 'AppleWebKit') === false) {
8450 return false;
8452 if (empty($version)) {
8453 return true; // no version specified
8455 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8456 if (version_compare($match[1], $version) >= 0) {
8457 return true;
8460 break;
8463 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
8464 if (strpos($agent, 'AppleWebKit') === false) {
8465 return false;
8467 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
8468 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
8469 return false;
8471 if (strpos($agent, 'Shiira')) { // Reject Shiira
8472 return false;
8474 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
8475 return false;
8477 if (strpos($agent, 'Android')) { // Reject Androids too
8478 return false;
8480 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
8481 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
8482 return false;
8484 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
8485 return false;
8488 if (empty($version)) {
8489 return true; // no version specified
8491 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $agent, $match)) {
8492 if (version_compare($match[1], $version) >= 0) {
8493 return true;
8496 break;
8499 case 'Chrome':
8500 if (strpos($agent, 'Chrome') === false) {
8501 return false;
8503 if (empty($version)) {
8504 return true; // no version specified
8506 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
8507 if (version_compare($match[1], $version) >= 0) {
8508 return true;
8511 break;
8514 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
8515 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
8516 return false;
8518 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
8519 return false;
8521 if (empty($version)) {
8522 return true; // no version specified
8524 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8525 if (version_compare($match[1], $version) >= 0) {
8526 return true;
8529 break;
8532 case 'WebKit Android': /// WebKit browser on Android
8533 if (strpos($agent, 'Linux; U; Android') === false) {
8534 return false;
8536 if (empty($version)) {
8537 return true; // no version specified
8539 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8540 if (version_compare($match[1], $version) >= 0) {
8541 return true;
8544 break;
8548 return false;
8552 * Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
8553 * an optional admin specified regular expression. If enabledevicedetection is set to no or not set
8554 * it returns default
8556 * @return string device type
8558 function get_device_type() {
8559 global $CFG;
8561 if (empty($CFG->enabledevicedetection) || empty($_SERVER['HTTP_USER_AGENT'])) {
8562 return 'default';
8565 $useragent = $_SERVER['HTTP_USER_AGENT'];
8567 if (!empty($CFG->devicedetectregex)) {
8568 $regexes = json_decode($CFG->devicedetectregex);
8570 foreach ($regexes as $value=>$regex) {
8571 if (preg_match($regex, $useragent)) {
8572 return $value;
8577 //mobile detection PHP direct copy from open source detectmobilebrowser.com
8578 $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';
8579 $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';
8580 if (preg_match($phonesregex,$useragent) || preg_match($modelsregex,substr($useragent, 0, 4))){
8581 return 'mobile';
8584 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
8585 if (preg_match($tabletregex, $useragent)) {
8586 return 'tablet';
8589 // Safe way to check for IE6 and not get false positives for some IE 7/8 users
8590 if (substr($_SERVER['HTTP_USER_AGENT'], 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
8591 return 'legacy';
8594 return 'default';
8598 * Returns a list of the device types supporting by Moodle
8600 * @param boolean $incusertypes includes types specified using the devicedetectregex admin setting
8601 * @return array $types
8603 function get_device_type_list($incusertypes = true) {
8604 global $CFG;
8606 $types = array('default', 'legacy', 'mobile', 'tablet');
8608 if ($incusertypes && !empty($CFG->devicedetectregex)) {
8609 $regexes = json_decode($CFG->devicedetectregex);
8611 foreach ($regexes as $value => $regex) {
8612 $types[] = $value;
8616 return $types;
8620 * Returns the theme selected for a particular device or false if none selected.
8622 * @param string $devicetype
8623 * @return string|false The name of the theme to use for the device or the false if not set
8625 function get_selected_theme_for_device_type($devicetype = null) {
8626 global $CFG;
8628 if (empty($devicetype)) {
8629 $devicetype = get_user_device_type();
8632 $themevarname = get_device_cfg_var_name($devicetype);
8633 if (empty($CFG->$themevarname)) {
8634 return false;
8637 return $CFG->$themevarname;
8641 * Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
8643 * @param string $devicetype
8644 * @return string The config variable to use to determine the theme
8646 function get_device_cfg_var_name($devicetype = null) {
8647 if ($devicetype == 'default' || empty($devicetype)) {
8648 return 'theme';
8651 return 'theme' . $devicetype;
8655 * Allows the user to switch the device they are seeing the theme for.
8656 * This allows mobile users to switch back to the default theme, or theme for any other device.
8658 * @param string $newdevice The device the user is currently using.
8659 * @return string The device the user has switched to
8661 function set_user_device_type($newdevice) {
8662 global $USER;
8664 $devicetype = get_device_type();
8665 $devicetypes = get_device_type_list();
8667 if ($newdevice == $devicetype) {
8668 unset_user_preference('switchdevice'.$devicetype);
8669 } else if (in_array($newdevice, $devicetypes)) {
8670 set_user_preference('switchdevice'.$devicetype, $newdevice);
8675 * Returns the device the user is currently using, or if the user has chosen to switch devices
8676 * for the current device type the type they have switched to.
8678 * @return string The device the user is currently using or wishes to use
8680 function get_user_device_type() {
8681 $device = get_device_type();
8682 $switched = get_user_preferences('switchdevice'.$device, false);
8683 if ($switched != false) {
8684 return $switched;
8686 return $device;
8690 * Returns one or several CSS class names that match the user's browser. These can be put
8691 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
8693 * @return array An array of browser version classes
8695 function get_browser_version_classes() {
8696 $classes = array();
8698 if (check_browser_version("MSIE", "0")) {
8699 $classes[] = 'ie';
8700 if (check_browser_version("MSIE", 9)) {
8701 $classes[] = 'ie9';
8702 } else if (check_browser_version("MSIE", 8)) {
8703 $classes[] = 'ie8';
8704 } elseif (check_browser_version("MSIE", 7)) {
8705 $classes[] = 'ie7';
8706 } elseif (check_browser_version("MSIE", 6)) {
8707 $classes[] = 'ie6';
8710 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
8711 $classes[] = 'gecko';
8712 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
8713 $classes[] = "gecko{$matches[1]}{$matches[2]}";
8716 } else if (check_browser_version("WebKit")) {
8717 $classes[] = 'safari';
8718 if (check_browser_version("Safari iOS")) {
8719 $classes[] = 'ios';
8721 } else if (check_browser_version("WebKit Android")) {
8722 $classes[] = 'android';
8725 } else if (check_browser_version("Opera")) {
8726 $classes[] = 'opera';
8730 return $classes;
8734 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
8736 * @return bool True for yes, false for no
8738 function can_use_rotated_text() {
8739 global $USER;
8740 return (check_browser_version('MSIE', 9) || check_browser_version('Firefox', 2) ||
8741 check_browser_version('Chrome', 21) || check_browser_version('Safari', 536.25) ||
8742 check_browser_version('Opera', 12) || check_browser_version('Safari iOS', 533)) &&
8743 !$USER->screenreader;
8747 * Hack to find out the GD version by parsing phpinfo output
8749 * @return int GD version (1, 2, or 0)
8751 function check_gd_version() {
8752 $gdversion = 0;
8754 if (function_exists('gd_info')){
8755 $gd_info = gd_info();
8756 if (substr_count($gd_info['GD Version'], '2.')) {
8757 $gdversion = 2;
8758 } else if (substr_count($gd_info['GD Version'], '1.')) {
8759 $gdversion = 1;
8762 } else {
8763 ob_start();
8764 phpinfo(INFO_MODULES);
8765 $phpinfo = ob_get_contents();
8766 ob_end_clean();
8768 $phpinfo = explode("\n", $phpinfo);
8771 foreach ($phpinfo as $text) {
8772 $parts = explode('</td>', $text);
8773 foreach ($parts as $key => $val) {
8774 $parts[$key] = trim(strip_tags($val));
8776 if ($parts[0] == 'GD Version') {
8777 if (substr_count($parts[1], '2.0')) {
8778 $parts[1] = '2.0';
8780 $gdversion = intval($parts[1]);
8785 return $gdversion; // 1, 2 or 0
8789 * Determine if moodle installation requires update
8791 * Checks version numbers of main code and all modules to see
8792 * if there are any mismatches
8794 * @global moodle_database $DB
8795 * @return bool
8797 function moodle_needs_upgrading() {
8798 global $CFG, $DB, $OUTPUT;
8800 if (empty($CFG->version)) {
8801 return true;
8804 // main versio nfirst
8805 $version = null;
8806 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
8807 if ($version > $CFG->version) {
8808 return true;
8811 // modules
8812 $mods = get_plugin_list('mod');
8813 $installed = $DB->get_records('modules', array(), '', 'name, version');
8814 foreach ($mods as $mod => $fullmod) {
8815 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
8816 continue;
8818 $module = new stdClass();
8819 if (!is_readable($fullmod.'/version.php')) {
8820 continue;
8822 include($fullmod.'/version.php'); // defines $module with version etc
8823 if (empty($installed[$mod])) {
8824 return true;
8825 } else if ($module->version > $installed[$mod]->version) {
8826 return true;
8829 unset($installed);
8831 // blocks
8832 $blocks = get_plugin_list('block');
8833 $installed = $DB->get_records('block', array(), '', 'name, version');
8834 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
8835 foreach ($blocks as $blockname=>$fullblock) {
8836 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
8837 continue;
8839 if (!is_readable($fullblock.'/version.php')) {
8840 continue;
8842 $plugin = new stdClass();
8843 $plugin->version = NULL;
8844 include($fullblock.'/version.php');
8845 if (empty($installed[$blockname])) {
8846 return true;
8847 } else if ($plugin->version > $installed[$blockname]->version) {
8848 return true;
8851 unset($installed);
8853 // now the rest of plugins
8854 $plugintypes = get_plugin_types();
8855 unset($plugintypes['mod']);
8856 unset($plugintypes['block']);
8858 $versions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin, value');
8859 foreach ($plugintypes as $type=>$unused) {
8860 $plugs = get_plugin_list($type);
8861 foreach ($plugs as $plug=>$fullplug) {
8862 $component = $type.'_'.$plug;
8863 if (!is_readable($fullplug.'/version.php')) {
8864 continue;
8866 $plugin = new stdClass();
8867 include($fullplug.'/version.php'); // defines $plugin with version etc
8868 if (array_key_exists($component, $versions)) {
8869 $installedversion = $versions[$component];
8870 } else {
8871 $installedversion = get_config($component, 'version');
8873 if (empty($installedversion)) { // new installation
8874 return true;
8875 } else if ($installedversion < $plugin->version) { // upgrade
8876 return true;
8881 return false;
8885 * Returns the major version of this site
8887 * Moodle version numbers consist of three numbers separated by a dot, for
8888 * example 1.9.11 or 2.0.2. The first two numbers, like 1.9 or 2.0, represent so
8889 * called major version. This function extracts the major version from either
8890 * $CFG->release (default) or eventually from the $release variable defined in
8891 * the main version.php.
8893 * @param bool $fromdisk should the version if source code files be used
8894 * @return string|false the major version like '2.3', false if could not be determined
8896 function moodle_major_version($fromdisk = false) {
8897 global $CFG;
8899 if ($fromdisk) {
8900 $release = null;
8901 require($CFG->dirroot.'/version.php');
8902 if (empty($release)) {
8903 return false;
8906 } else {
8907 if (empty($CFG->release)) {
8908 return false;
8910 $release = $CFG->release;
8913 if (preg_match('/^[0-9]+\.[0-9]+/', $release, $matches)) {
8914 return $matches[0];
8915 } else {
8916 return false;
8920 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
8923 * Sets the system locale
8925 * @category string
8926 * @param string $locale Can be used to force a locale
8928 function moodle_setlocale($locale='') {
8929 global $CFG;
8931 static $currentlocale = ''; // last locale caching
8933 $oldlocale = $currentlocale;
8935 /// Fetch the correct locale based on ostype
8936 if ($CFG->ostype == 'WINDOWS') {
8937 $stringtofetch = 'localewin';
8938 } else {
8939 $stringtofetch = 'locale';
8942 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
8943 if (!empty($locale)) {
8944 $currentlocale = $locale;
8945 } else if (!empty($CFG->locale)) { // override locale for all language packs
8946 $currentlocale = $CFG->locale;
8947 } else {
8948 $currentlocale = get_string($stringtofetch, 'langconfig');
8951 /// do nothing if locale already set up
8952 if ($oldlocale == $currentlocale) {
8953 return;
8956 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
8957 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
8958 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
8960 /// Get current values
8961 $monetary= setlocale (LC_MONETARY, 0);
8962 $numeric = setlocale (LC_NUMERIC, 0);
8963 $ctype = setlocale (LC_CTYPE, 0);
8964 if ($CFG->ostype != 'WINDOWS') {
8965 $messages= setlocale (LC_MESSAGES, 0);
8967 /// Set locale to all
8968 setlocale (LC_ALL, $currentlocale);
8969 /// Set old values
8970 setlocale (LC_MONETARY, $monetary);
8971 setlocale (LC_NUMERIC, $numeric);
8972 if ($CFG->ostype != 'WINDOWS') {
8973 setlocale (LC_MESSAGES, $messages);
8975 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
8976 setlocale (LC_CTYPE, $ctype);
8981 * Count words in a string.
8983 * Words are defined as things between whitespace.
8985 * @category string
8986 * @param string $string The text to be searched for words.
8987 * @return int The count of words in the specified string
8989 function count_words($string) {
8990 $string = strip_tags($string);
8991 return count(preg_split("/\w\b/", $string)) - 1;
8994 /** Count letters in a string.
8996 * Letters are defined as chars not in tags and different from whitespace.
8998 * @category string
8999 * @param string $string The text to be searched for letters.
9000 * @return int The count of letters in the specified text.
9002 function count_letters($string) {
9003 /// Loading the textlib singleton instance. We are going to need it.
9004 $string = strip_tags($string); // Tags are out now
9005 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
9007 return textlib::strlen($string);
9011 * Generate and return a random string of the specified length.
9013 * @param int $length The length of the string to be created.
9014 * @return string
9016 function random_string ($length=15) {
9017 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
9018 $pool .= 'abcdefghijklmnopqrstuvwxyz';
9019 $pool .= '0123456789';
9020 $poollen = strlen($pool);
9021 mt_srand ((double) microtime() * 1000000);
9022 $string = '';
9023 for ($i = 0; $i < $length; $i++) {
9024 $string .= substr($pool, (mt_rand()%($poollen)), 1);
9026 return $string;
9030 * Generate a complex random string (useful for md5 salts)
9032 * This function is based on the above {@link random_string()} however it uses a
9033 * larger pool of characters and generates a string between 24 and 32 characters
9035 * @param int $length Optional if set generates a string to exactly this length
9036 * @return string
9038 function complex_random_string($length=null) {
9039 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
9040 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
9041 $poollen = strlen($pool);
9042 mt_srand ((double) microtime() * 1000000);
9043 if ($length===null) {
9044 $length = floor(rand(24,32));
9046 $string = '';
9047 for ($i = 0; $i < $length; $i++) {
9048 $string .= $pool[(mt_rand()%$poollen)];
9050 return $string;
9054 * Given some text (which may contain HTML) and an ideal length,
9055 * this function truncates the text neatly on a word boundary if possible
9057 * @category string
9058 * @global stdClass $CFG
9059 * @param string $text text to be shortened
9060 * @param int $ideal ideal string length
9061 * @param boolean $exact if false, $text will not be cut mid-word
9062 * @param string $ending The string to append if the passed string is truncated
9063 * @return string $truncate shortened string
9065 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
9067 global $CFG;
9069 // if the plain text is shorter than the maximum length, return the whole text
9070 if (textlib::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
9071 return $text;
9074 // Splits on HTML tags. Each open/close/empty tag will be the first thing
9075 // and only tag in its 'line'
9076 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
9078 $total_length = textlib::strlen($ending);
9079 $truncate = '';
9081 // This array stores information about open and close tags and their position
9082 // in the truncated string. Each item in the array is an object with fields
9083 // ->open (true if open), ->tag (tag name in lower case), and ->pos
9084 // (byte position in truncated text)
9085 $tagdetails = array();
9087 foreach ($lines as $line_matchings) {
9088 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
9089 if (!empty($line_matchings[1])) {
9090 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
9091 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
9092 // do nothing
9093 // if tag is a closing tag (f.e. </b>)
9094 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
9095 // record closing tag
9096 $tagdetails[] = (object)array('open'=>false,
9097 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
9098 // if tag is an opening tag (f.e. <b>)
9099 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
9100 // record opening tag
9101 $tagdetails[] = (object)array('open'=>true,
9102 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
9104 // add html-tag to $truncate'd text
9105 $truncate .= $line_matchings[1];
9108 // calculate the length of the plain text part of the line; handle entities as one character
9109 $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]));
9110 if ($total_length+$content_length > $ideal) {
9111 // the number of characters which are left
9112 $left = $ideal - $total_length;
9113 $entities_length = 0;
9114 // search for html entities
9115 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)) {
9116 // calculate the real length of all entities in the legal range
9117 foreach ($entities[0] as $entity) {
9118 if ($entity[1]+1-$entities_length <= $left) {
9119 $left--;
9120 $entities_length += textlib::strlen($entity[0]);
9121 } else {
9122 // no more characters left
9123 break;
9127 $truncate .= textlib::substr($line_matchings[2], 0, $left+$entities_length);
9128 // maximum length is reached, so get off the loop
9129 break;
9130 } else {
9131 $truncate .= $line_matchings[2];
9132 $total_length += $content_length;
9135 // if the maximum length is reached, get off the loop
9136 if($total_length >= $ideal) {
9137 break;
9141 // if the words shouldn't be cut in the middle...
9142 if (!$exact) {
9143 // ...search the last occurence of a space...
9144 for ($k=textlib::strlen($truncate);$k>0;$k--) {
9145 if ($char = textlib::substr($truncate, $k, 1)) {
9146 if ($char === '.' or $char === ' ') {
9147 $breakpos = $k+1;
9148 break;
9149 } else if (strlen($char) > 2) { // Chinese/Japanese/Korean text
9150 $breakpos = $k+1; // can be truncated at any UTF-8
9151 break; // character boundary.
9156 if (isset($breakpos)) {
9157 // ...and cut the text in this position
9158 $truncate = textlib::substr($truncate, 0, $breakpos);
9162 // add the defined ending to the text
9163 $truncate .= $ending;
9165 // Now calculate the list of open html tags based on the truncate position
9166 $open_tags = array();
9167 foreach ($tagdetails as $taginfo) {
9168 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
9169 // Don't include tags after we made the break!
9170 break;
9172 if($taginfo->open) {
9173 // add tag to the beginning of $open_tags list
9174 array_unshift($open_tags, $taginfo->tag);
9175 } else {
9176 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
9177 if ($pos !== false) {
9178 unset($open_tags[$pos]);
9183 // close all unclosed html-tags
9184 foreach ($open_tags as $tag) {
9185 $truncate .= '</' . $tag . '>';
9188 return $truncate;
9193 * Given dates in seconds, how many weeks is the date from startdate
9194 * The first week is 1, the second 2 etc ...
9196 * @todo Finish documenting this function
9198 * @uses WEEKSECS
9199 * @param int $startdate Timestamp for the start date
9200 * @param int $thedate Timestamp for the end date
9201 * @return string
9203 function getweek ($startdate, $thedate) {
9204 if ($thedate < $startdate) { // error
9205 return 0;
9208 return floor(($thedate - $startdate) / WEEKSECS) + 1;
9212 * returns a randomly generated password of length $maxlen. inspired by
9214 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
9215 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
9217 * @global stdClass $CFG
9218 * @param int $maxlen The maximum size of the password being generated.
9219 * @return string
9221 function generate_password($maxlen=10) {
9222 global $CFG;
9224 if (empty($CFG->passwordpolicy)) {
9225 $fillers = PASSWORD_DIGITS;
9226 $wordlist = file($CFG->wordlist);
9227 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9228 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9229 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
9230 $password = $word1 . $filler1 . $word2;
9231 } else {
9232 $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
9233 $digits = $CFG->minpassworddigits;
9234 $lower = $CFG->minpasswordlower;
9235 $upper = $CFG->minpasswordupper;
9236 $nonalphanum = $CFG->minpasswordnonalphanum;
9237 $total = $lower + $upper + $digits + $nonalphanum;
9238 // minlength should be the greater one of the two ( $minlen and $total )
9239 $minlen = $minlen < $total ? $total : $minlen;
9240 // maxlen can never be smaller than minlen
9241 $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
9242 $additional = $maxlen - $total;
9244 // Make sure we have enough characters to fulfill
9245 // complexity requirements
9246 $passworddigits = PASSWORD_DIGITS;
9247 while ($digits > strlen($passworddigits)) {
9248 $passworddigits .= PASSWORD_DIGITS;
9250 $passwordlower = PASSWORD_LOWER;
9251 while ($lower > strlen($passwordlower)) {
9252 $passwordlower .= PASSWORD_LOWER;
9254 $passwordupper = PASSWORD_UPPER;
9255 while ($upper > strlen($passwordupper)) {
9256 $passwordupper .= PASSWORD_UPPER;
9258 $passwordnonalphanum = PASSWORD_NONALPHANUM;
9259 while ($nonalphanum > strlen($passwordnonalphanum)) {
9260 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
9263 // Now mix and shuffle it all
9264 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
9265 substr(str_shuffle ($passwordupper), 0, $upper) .
9266 substr(str_shuffle ($passworddigits), 0, $digits) .
9267 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
9268 substr(str_shuffle ($passwordlower .
9269 $passwordupper .
9270 $passworddigits .
9271 $passwordnonalphanum), 0 , $additional));
9274 return substr ($password, 0, $maxlen);
9278 * Given a float, prints it nicely.
9279 * Localized floats must not be used in calculations!
9281 * The stripzeros feature is intended for making numbers look nicer in small
9282 * areas where it is not necessary to indicate the degree of accuracy by showing
9283 * ending zeros. If you turn it on with $decimalpoints set to 3, for example,
9284 * then it will display '5.4' instead of '5.400' or '5' instead of '5.000'.
9286 * @param float $float The float to print
9287 * @param int $decimalpoints The number of decimal places to print.
9288 * @param bool $localized use localized decimal separator
9289 * @param bool $stripzeros If true, removes final zeros after decimal point
9290 * @return string locale float
9292 function format_float($float, $decimalpoints=1, $localized=true, $stripzeros=false) {
9293 if (is_null($float)) {
9294 return '';
9296 if ($localized) {
9297 $separator = get_string('decsep', 'langconfig');
9298 } else {
9299 $separator = '.';
9301 $result = number_format($float, $decimalpoints, $separator, '');
9302 if ($stripzeros) {
9303 // Remove zeros and final dot if not needed
9304 $result = preg_replace('~(' . preg_quote($separator) . ')?0+$~', '', $result);
9306 return $result;
9310 * Converts locale specific floating point/comma number back to standard PHP float value
9311 * Do NOT try to do any math operations before this conversion on any user submitted floats!
9313 * @param string $locale_float locale aware float representation
9314 * @return float
9316 function unformat_float($locale_float) {
9317 $locale_float = trim($locale_float);
9319 if ($locale_float == '') {
9320 return null;
9323 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
9325 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
9329 * Given a simple array, this shuffles it up just like shuffle()
9330 * Unlike PHP's shuffle() this function works on any machine.
9332 * @param array $array The array to be rearranged
9333 * @return array
9335 function swapshuffle($array) {
9337 srand ((double) microtime() * 10000000);
9338 $last = count($array) - 1;
9339 for ($i=0;$i<=$last;$i++) {
9340 $from = rand(0,$last);
9341 $curr = $array[$i];
9342 $array[$i] = $array[$from];
9343 $array[$from] = $curr;
9345 return $array;
9349 * Like {@link swapshuffle()}, but works on associative arrays
9351 * @param array $array The associative array to be rearranged
9352 * @return array
9354 function swapshuffle_assoc($array) {
9356 $newarray = array();
9357 $newkeys = swapshuffle(array_keys($array));
9359 foreach ($newkeys as $newkey) {
9360 $newarray[$newkey] = $array[$newkey];
9362 return $newarray;
9366 * Given an arbitrary array, and a number of draws,
9367 * this function returns an array with that amount
9368 * of items. The indexes are retained.
9370 * @todo Finish documenting this function
9372 * @param array $array
9373 * @param int $draws
9374 * @return array
9376 function draw_rand_array($array, $draws) {
9377 srand ((double) microtime() * 10000000);
9379 $return = array();
9381 $last = count($array);
9383 if ($draws > $last) {
9384 $draws = $last;
9387 while ($draws > 0) {
9388 $last--;
9390 $keys = array_keys($array);
9391 $rand = rand(0, $last);
9393 $return[$keys[$rand]] = $array[$keys[$rand]];
9394 unset($array[$keys[$rand]]);
9396 $draws--;
9399 return $return;
9403 * Calculate the difference between two microtimes
9405 * @param string $a The first Microtime
9406 * @param string $b The second Microtime
9407 * @return string
9409 function microtime_diff($a, $b) {
9410 list($a_dec, $a_sec) = explode(' ', $a);
9411 list($b_dec, $b_sec) = explode(' ', $b);
9412 return $b_sec - $a_sec + $b_dec - $a_dec;
9416 * Given a list (eg a,b,c,d,e) this function returns
9417 * an array of 1->a, 2->b, 3->c etc
9419 * @param string $list The string to explode into array bits
9420 * @param string $separator The separator used within the list string
9421 * @return array The now assembled array
9423 function make_menu_from_list($list, $separator=',') {
9425 $array = array_reverse(explode($separator, $list), true);
9426 foreach ($array as $key => $item) {
9427 $outarray[$key+1] = trim($item);
9429 return $outarray;
9433 * Creates an array that represents all the current grades that
9434 * can be chosen using the given grading type.
9436 * Negative numbers
9437 * are scales, zero is no grade, and positive numbers are maximum
9438 * grades.
9440 * @todo Finish documenting this function or better deprecated this completely!
9442 * @param int $gradingtype
9443 * @return array
9445 function make_grades_menu($gradingtype) {
9446 global $DB;
9448 $grades = array();
9449 if ($gradingtype < 0) {
9450 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
9451 return make_menu_from_list($scale->scale);
9453 } else if ($gradingtype > 0) {
9454 for ($i=$gradingtype; $i>=0; $i--) {
9455 $grades[$i] = $i .' / '. $gradingtype;
9457 return $grades;
9459 return $grades;
9463 * This function returns the number of activities
9464 * using scaleid in a courseid
9466 * @todo Finish documenting this function
9468 * @global object
9469 * @global object
9470 * @param int $courseid ?
9471 * @param int $scaleid ?
9472 * @return int
9474 function course_scale_used($courseid, $scaleid) {
9475 global $CFG, $DB;
9477 $return = 0;
9479 if (!empty($scaleid)) {
9480 if ($cms = get_course_mods($courseid)) {
9481 foreach ($cms as $cm) {
9482 //Check cm->name/lib.php exists
9483 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
9484 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
9485 $function_name = $cm->modname.'_scale_used';
9486 if (function_exists($function_name)) {
9487 if ($function_name($cm->instance,$scaleid)) {
9488 $return++;
9495 // check if any course grade item makes use of the scale
9496 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
9498 // check if any outcome in the course makes use of the scale
9499 $return += $DB->count_records_sql("SELECT COUNT('x')
9500 FROM {grade_outcomes_courses} goc,
9501 {grade_outcomes} go
9502 WHERE go.id = goc.outcomeid
9503 AND go.scaleid = ? AND goc.courseid = ?",
9504 array($scaleid, $courseid));
9506 return $return;
9510 * This function returns the number of activities
9511 * using scaleid in the entire site
9513 * @param int $scaleid
9514 * @param array $courses
9515 * @return int
9517 function site_scale_used($scaleid, &$courses) {
9518 $return = 0;
9520 if (!is_array($courses) || count($courses) == 0) {
9521 $courses = get_courses("all",false,"c.id,c.shortname");
9524 if (!empty($scaleid)) {
9525 if (is_array($courses) && count($courses) > 0) {
9526 foreach ($courses as $course) {
9527 $return += course_scale_used($course->id,$scaleid);
9531 return $return;
9535 * make_unique_id_code
9537 * @todo Finish documenting this function
9539 * @uses $_SERVER
9540 * @param string $extra Extra string to append to the end of the code
9541 * @return string
9543 function make_unique_id_code($extra='') {
9545 $hostname = 'unknownhost';
9546 if (!empty($_SERVER['HTTP_HOST'])) {
9547 $hostname = $_SERVER['HTTP_HOST'];
9548 } else if (!empty($_ENV['HTTP_HOST'])) {
9549 $hostname = $_ENV['HTTP_HOST'];
9550 } else if (!empty($_SERVER['SERVER_NAME'])) {
9551 $hostname = $_SERVER['SERVER_NAME'];
9552 } else if (!empty($_ENV['SERVER_NAME'])) {
9553 $hostname = $_ENV['SERVER_NAME'];
9556 $date = gmdate("ymdHis");
9558 $random = random_string(6);
9560 if ($extra) {
9561 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
9562 } else {
9563 return $hostname .'+'. $date .'+'. $random;
9569 * Function to check the passed address is within the passed subnet
9571 * The parameter is a comma separated string of subnet definitions.
9572 * Subnet strings can be in one of three formats:
9573 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
9574 * 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)
9575 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
9576 * Code for type 1 modified from user posted comments by mediator at
9577 * {@link http://au.php.net/manual/en/function.ip2long.php}
9579 * @param string $addr The address you are checking
9580 * @param string $subnetstr The string of subnet addresses
9581 * @return bool
9583 function address_in_subnet($addr, $subnetstr) {
9585 if ($addr == '0.0.0.0') {
9586 return false;
9588 $subnets = explode(',', $subnetstr);
9589 $found = false;
9590 $addr = trim($addr);
9591 $addr = cleanremoteaddr($addr, false); // normalise
9592 if ($addr === null) {
9593 return false;
9595 $addrparts = explode(':', $addr);
9597 $ipv6 = strpos($addr, ':');
9599 foreach ($subnets as $subnet) {
9600 $subnet = trim($subnet);
9601 if ($subnet === '') {
9602 continue;
9605 if (strpos($subnet, '/') !== false) {
9606 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
9607 list($ip, $mask) = explode('/', $subnet);
9608 $mask = trim($mask);
9609 if (!is_number($mask)) {
9610 continue; // incorect mask number, eh?
9612 $ip = cleanremoteaddr($ip, false); // normalise
9613 if ($ip === null) {
9614 continue;
9616 if (strpos($ip, ':') !== false) {
9617 // IPv6
9618 if (!$ipv6) {
9619 continue;
9621 if ($mask > 128 or $mask < 0) {
9622 continue; // nonsense
9624 if ($mask == 0) {
9625 return true; // any address
9627 if ($mask == 128) {
9628 if ($ip === $addr) {
9629 return true;
9631 continue;
9633 $ipparts = explode(':', $ip);
9634 $modulo = $mask % 16;
9635 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
9636 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
9637 if (implode(':', $ipnet) === implode(':', $addrnet)) {
9638 if ($modulo == 0) {
9639 return true;
9641 $pos = ($mask-$modulo)/16;
9642 $ipnet = hexdec($ipparts[$pos]);
9643 $addrnet = hexdec($addrparts[$pos]);
9644 $mask = 0xffff << (16 - $modulo);
9645 if (($addrnet & $mask) == ($ipnet & $mask)) {
9646 return true;
9650 } else {
9651 // IPv4
9652 if ($ipv6) {
9653 continue;
9655 if ($mask > 32 or $mask < 0) {
9656 continue; // nonsense
9658 if ($mask == 0) {
9659 return true;
9661 if ($mask == 32) {
9662 if ($ip === $addr) {
9663 return true;
9665 continue;
9667 $mask = 0xffffffff << (32 - $mask);
9668 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
9669 return true;
9673 } else if (strpos($subnet, '-') !== false) {
9674 /// 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.
9675 $parts = explode('-', $subnet);
9676 if (count($parts) != 2) {
9677 continue;
9680 if (strpos($subnet, ':') !== false) {
9681 // IPv6
9682 if (!$ipv6) {
9683 continue;
9685 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9686 if ($ipstart === null) {
9687 continue;
9689 $ipparts = explode(':', $ipstart);
9690 $start = hexdec(array_pop($ipparts));
9691 $ipparts[] = trim($parts[1]);
9692 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
9693 if ($ipend === null) {
9694 continue;
9696 $ipparts[7] = '';
9697 $ipnet = implode(':', $ipparts);
9698 if (strpos($addr, $ipnet) !== 0) {
9699 continue;
9701 $ipparts = explode(':', $ipend);
9702 $end = hexdec($ipparts[7]);
9704 $addrend = hexdec($addrparts[7]);
9706 if (($addrend >= $start) and ($addrend <= $end)) {
9707 return true;
9710 } else {
9711 // IPv4
9712 if ($ipv6) {
9713 continue;
9715 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9716 if ($ipstart === null) {
9717 continue;
9719 $ipparts = explode('.', $ipstart);
9720 $ipparts[3] = trim($parts[1]);
9721 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
9722 if ($ipend === null) {
9723 continue;
9726 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
9727 return true;
9731 } else {
9732 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
9733 if (strpos($subnet, ':') !== false) {
9734 // IPv6
9735 if (!$ipv6) {
9736 continue;
9738 $parts = explode(':', $subnet);
9739 $count = count($parts);
9740 if ($parts[$count-1] === '') {
9741 unset($parts[$count-1]); // trim trailing :
9742 $count--;
9743 $subnet = implode('.', $parts);
9745 $isip = cleanremoteaddr($subnet, false); // normalise
9746 if ($isip !== null) {
9747 if ($isip === $addr) {
9748 return true;
9750 continue;
9751 } else if ($count > 8) {
9752 continue;
9754 $zeros = array_fill(0, 8-$count, '0');
9755 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
9756 if (address_in_subnet($addr, $subnet)) {
9757 return true;
9760 } else {
9761 // IPv4
9762 if ($ipv6) {
9763 continue;
9765 $parts = explode('.', $subnet);
9766 $count = count($parts);
9767 if ($parts[$count-1] === '') {
9768 unset($parts[$count-1]); // trim trailing .
9769 $count--;
9770 $subnet = implode('.', $parts);
9772 if ($count == 4) {
9773 $subnet = cleanremoteaddr($subnet, false); // normalise
9774 if ($subnet === $addr) {
9775 return true;
9777 continue;
9778 } else if ($count > 4) {
9779 continue;
9781 $zeros = array_fill(0, 4-$count, '0');
9782 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
9783 if (address_in_subnet($addr, $subnet)) {
9784 return true;
9790 return false;
9794 * For outputting debugging info
9796 * @uses STDOUT
9797 * @param string $string The string to write
9798 * @param string $eol The end of line char(s) to use
9799 * @param string $sleep Period to make the application sleep
9800 * This ensures any messages have time to display before redirect
9802 function mtrace($string, $eol="\n", $sleep=0) {
9804 if (defined('STDOUT') and !PHPUNIT_TEST) {
9805 fwrite(STDOUT, $string.$eol);
9806 } else {
9807 echo $string . $eol;
9810 flush();
9812 //delay to keep message on user's screen in case of subsequent redirect
9813 if ($sleep) {
9814 sleep($sleep);
9819 * Replace 1 or more slashes or backslashes to 1 slash
9821 * @param string $path The path to strip
9822 * @return string the path with double slashes removed
9824 function cleardoubleslashes ($path) {
9825 return preg_replace('/(\/|\\\){1,}/','/',$path);
9829 * Is current ip in give list?
9831 * @param string $list
9832 * @return bool
9834 function remoteip_in_list($list){
9835 $inlist = false;
9836 $client_ip = getremoteaddr(null);
9838 if(!$client_ip){
9839 // ensure access on cli
9840 return true;
9843 $list = explode("\n", $list);
9844 foreach($list as $subnet) {
9845 $subnet = trim($subnet);
9846 if (address_in_subnet($client_ip, $subnet)) {
9847 $inlist = true;
9848 break;
9851 return $inlist;
9855 * Returns most reliable client address
9857 * @global object
9858 * @param string $default If an address can't be determined, then return this
9859 * @return string The remote IP address
9861 function getremoteaddr($default='0.0.0.0') {
9862 global $CFG;
9864 if (empty($CFG->getremoteaddrconf)) {
9865 // This will happen, for example, before just after the upgrade, as the
9866 // user is redirected to the admin screen.
9867 $variablestoskip = 0;
9868 } else {
9869 $variablestoskip = $CFG->getremoteaddrconf;
9871 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
9872 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
9873 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
9874 return $address ? $address : $default;
9877 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
9878 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
9879 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
9880 return $address ? $address : $default;
9883 if (!empty($_SERVER['REMOTE_ADDR'])) {
9884 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
9885 return $address ? $address : $default;
9886 } else {
9887 return $default;
9892 * Cleans an ip address. Internal addresses are now allowed.
9893 * (Originally local addresses were not allowed.)
9895 * @param string $addr IPv4 or IPv6 address
9896 * @param bool $compress use IPv6 address compression
9897 * @return string normalised ip address string, null if error
9899 function cleanremoteaddr($addr, $compress=false) {
9900 $addr = trim($addr);
9902 //TODO: maybe add a separate function is_addr_public() or something like this
9904 if (strpos($addr, ':') !== false) {
9905 // can be only IPv6
9906 $parts = explode(':', $addr);
9907 $count = count($parts);
9909 if (strpos($parts[$count-1], '.') !== false) {
9910 //legacy ipv4 notation
9911 $last = array_pop($parts);
9912 $ipv4 = cleanremoteaddr($last, true);
9913 if ($ipv4 === null) {
9914 return null;
9916 $bits = explode('.', $ipv4);
9917 $parts[] = dechex($bits[0]).dechex($bits[1]);
9918 $parts[] = dechex($bits[2]).dechex($bits[3]);
9919 $count = count($parts);
9920 $addr = implode(':', $parts);
9923 if ($count < 3 or $count > 8) {
9924 return null; // severly malformed
9927 if ($count != 8) {
9928 if (strpos($addr, '::') === false) {
9929 return null; // malformed
9931 // uncompress ::
9932 $insertat = array_search('', $parts, true);
9933 $missing = array_fill(0, 1 + 8 - $count, '0');
9934 array_splice($parts, $insertat, 1, $missing);
9935 foreach ($parts as $key=>$part) {
9936 if ($part === '') {
9937 $parts[$key] = '0';
9942 $adr = implode(':', $parts);
9943 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
9944 return null; // incorrect format - sorry
9947 // normalise 0s and case
9948 $parts = array_map('hexdec', $parts);
9949 $parts = array_map('dechex', $parts);
9951 $result = implode(':', $parts);
9953 if (!$compress) {
9954 return $result;
9957 if ($result === '0:0:0:0:0:0:0:0') {
9958 return '::'; // all addresses
9961 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
9962 if ($compressed !== $result) {
9963 return $compressed;
9966 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
9967 if ($compressed !== $result) {
9968 return $compressed;
9971 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
9972 if ($compressed !== $result) {
9973 return $compressed;
9976 return $result;
9979 // first get all things that look like IPv4 addresses
9980 $parts = array();
9981 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
9982 return null;
9984 unset($parts[0]);
9986 foreach ($parts as $key=>$match) {
9987 if ($match > 255) {
9988 return null;
9990 $parts[$key] = (int)$match; // normalise 0s
9993 return implode('.', $parts);
9997 * This function will make a complete copy of anything it's given,
9998 * regardless of whether it's an object or not.
10000 * @param mixed $thing Something you want cloned
10001 * @return mixed What ever it is you passed it
10003 function fullclone($thing) {
10004 return unserialize(serialize($thing));
10009 * This function expects to called during shutdown
10010 * should be set via register_shutdown_function()
10011 * in lib/setup.php .
10013 * @return void
10015 function moodle_request_shutdown() {
10016 global $CFG;
10018 // help apache server if possible
10019 $apachereleasemem = false;
10020 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
10021 && ini_get_bool('child_terminate')) {
10023 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
10024 if (memory_get_usage() > get_real_size($limit)) {
10025 $apachereleasemem = $limit;
10026 @apache_child_terminate();
10030 // deal with perf logging
10031 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
10032 if ($apachereleasemem) {
10033 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
10035 if (defined('MDL_PERFTOLOG')) {
10036 $perf = get_performance_info();
10037 error_log("PERF: " . $perf['txt']);
10039 if (defined('MDL_PERFINC')) {
10040 $inc = get_included_files();
10041 $ts = 0;
10042 foreach($inc as $f) {
10043 if (preg_match(':^/:', $f)) {
10044 $fs = filesize($f);
10045 $ts += $fs;
10046 $hfs = display_size($fs);
10047 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
10048 , NULL, NULL, 0);
10049 } else {
10050 error_log($f , NULL, NULL, 0);
10053 if ($ts > 0 ) {
10054 $hts = display_size($ts);
10055 error_log("Total size of files included: $ts ($hts)");
10062 * If new messages are waiting for the current user, then insert
10063 * JavaScript to pop up the messaging window into the page
10065 * @global moodle_page $PAGE
10066 * @return void
10068 function message_popup_window() {
10069 global $USER, $DB, $PAGE, $CFG, $SITE;
10071 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
10072 return;
10075 if (!isloggedin() || isguestuser()) {
10076 return;
10079 if (!isset($USER->message_lastpopup)) {
10080 $USER->message_lastpopup = 0;
10081 } else if ($USER->message_lastpopup > (time()-120)) {
10082 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
10083 return;
10086 //a quick query to check whether the user has new messages
10087 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
10088 if ($messagecount<1) {
10089 return;
10092 //got unread messages so now do another query that joins with the user table
10093 $messagesql = "SELECT m.id, m.smallmessage, m.fullmessageformat, m.notification, u.firstname, u.lastname
10094 FROM {message} m
10095 JOIN {message_working} mw ON m.id=mw.unreadmessageid
10096 JOIN {message_processors} p ON mw.processorid=p.id
10097 JOIN {user} u ON m.useridfrom=u.id
10098 WHERE m.useridto = :userid
10099 AND p.name='popup'";
10101 //if the user was last notified over an hour ago we can renotify them of old messages
10102 //so don't worry about when the new message was sent
10103 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
10104 if (!$lastnotifiedlongago) {
10105 $messagesql .= 'AND m.timecreated > :lastpopuptime';
10108 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
10110 //if we have new messages to notify the user about
10111 if (!empty($message_users)) {
10113 $strmessages = '';
10114 if (count($message_users)>1) {
10115 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
10116 } else {
10117 $message_users = reset($message_users);
10119 //show who the message is from if its not a notification
10120 if (!$message_users->notification) {
10121 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
10124 //try to display the small version of the message
10125 $smallmessage = null;
10126 if (!empty($message_users->smallmessage)) {
10127 //display the first 200 chars of the message in the popup
10128 $smallmessage = null;
10129 if (textlib::strlen($message_users->smallmessage) > 200) {
10130 $smallmessage = textlib::substr($message_users->smallmessage,0,200).'...';
10131 } else {
10132 $smallmessage = $message_users->smallmessage;
10135 //prevent html symbols being displayed
10136 if ($message_users->fullmessageformat == FORMAT_HTML) {
10137 $smallmessage = html_to_text($smallmessage);
10138 } else {
10139 $smallmessage = s($smallmessage);
10141 } else if ($message_users->notification) {
10142 //its a notification with no smallmessage so just say they have a notification
10143 $smallmessage = get_string('unreadnewnotification', 'message');
10145 if (!empty($smallmessage)) {
10146 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
10150 $strgomessage = get_string('gotomessages', 'message');
10151 $strstaymessage = get_string('ignore','admin');
10153 $url = $CFG->wwwroot.'/message/index.php';
10154 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
10155 html_writer::start_tag('div', array('id'=>'newmessagetext')).
10156 $strmessages.
10157 html_writer::end_tag('div').
10159 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
10160 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
10161 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
10162 html_writer::end_tag('div');
10163 html_writer::end_tag('div');
10165 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
10167 $USER->message_lastpopup = time();
10172 * Used to make sure that $min <= $value <= $max
10174 * Make sure that value is between min, and max
10176 * @param int $min The minimum value
10177 * @param int $value The value to check
10178 * @param int $max The maximum value
10180 function bounded_number($min, $value, $max) {
10181 if($value < $min) {
10182 return $min;
10184 if($value > $max) {
10185 return $max;
10187 return $value;
10191 * Check if there is a nested array within the passed array
10193 * @param array $array
10194 * @return bool true if there is a nested array false otherwise
10196 function array_is_nested($array) {
10197 foreach ($array as $value) {
10198 if (is_array($value)) {
10199 return true;
10202 return false;
10206 * get_performance_info() pairs up with init_performance_info()
10207 * loaded in setup.php. Returns an array with 'html' and 'txt'
10208 * values ready for use, and each of the individual stats provided
10209 * separately as well.
10211 * @global object
10212 * @global object
10213 * @global object
10214 * @return array
10216 function get_performance_info() {
10217 global $CFG, $PERF, $DB, $PAGE;
10219 $info = array();
10220 $info['html'] = ''; // holds userfriendly HTML representation
10221 $info['txt'] = me() . ' '; // holds log-friendly representation
10223 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
10225 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
10226 $info['txt'] .= 'time: '.$info['realtime'].'s ';
10228 if (function_exists('memory_get_usage')) {
10229 $info['memory_total'] = memory_get_usage();
10230 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
10231 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
10232 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
10235 if (function_exists('memory_get_peak_usage')) {
10236 $info['memory_peak'] = memory_get_peak_usage();
10237 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
10238 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
10241 $inc = get_included_files();
10242 //error_log(print_r($inc,1));
10243 $info['includecount'] = count($inc);
10244 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
10245 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
10247 $filtermanager = filter_manager::instance();
10248 if (method_exists($filtermanager, 'get_performance_summary')) {
10249 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
10250 $info = array_merge($filterinfo, $info);
10251 foreach ($filterinfo as $key => $value) {
10252 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10253 $info['txt'] .= "$key: $value ";
10257 $stringmanager = get_string_manager();
10258 if (method_exists($stringmanager, 'get_performance_summary')) {
10259 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
10260 $info = array_merge($filterinfo, $info);
10261 foreach ($filterinfo as $key => $value) {
10262 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10263 $info['txt'] .= "$key: $value ";
10267 $jsmodules = $PAGE->requires->get_loaded_modules();
10268 if ($jsmodules) {
10269 $yuicount = 0;
10270 $othercount = 0;
10271 $details = '';
10272 foreach ($jsmodules as $module => $backtraces) {
10273 if (strpos($module, 'yui') === 0) {
10274 $yuicount += 1;
10275 } else {
10276 $othercount += 1;
10278 if (!empty($CFG->yuimoduledebug)) {
10279 // hidden feature for developers working on YUI module infrastructure
10280 $details .= "<div class='yui-module'><p>$module</p>";
10281 foreach ($backtraces as $backtrace) {
10282 $details .= "<div class='backtrace'>$backtrace</div>";
10284 $details .= '</div>';
10287 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
10288 $info['txt'] .= "includedyuimodules: $yuicount ";
10289 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
10290 $info['txt'] .= "includedjsmodules: $othercount ";
10291 if ($details) {
10292 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
10296 if (!empty($PERF->logwrites)) {
10297 $info['logwrites'] = $PERF->logwrites;
10298 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
10299 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
10302 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
10303 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
10304 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
10306 if (function_exists('posix_times')) {
10307 $ptimes = posix_times();
10308 if (is_array($ptimes)) {
10309 foreach ($ptimes as $key => $val) {
10310 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
10312 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
10313 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
10317 // Grab the load average for the last minute
10318 // /proc will only work under some linux configurations
10319 // while uptime is there under MacOSX/Darwin and other unices
10320 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
10321 list($server_load) = explode(' ', $loadavg[0]);
10322 unset($loadavg);
10323 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
10324 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
10325 $server_load = $matches[1];
10326 } else {
10327 trigger_error('Could not parse uptime output!');
10330 if (!empty($server_load)) {
10331 $info['serverload'] = $server_load;
10332 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
10333 $info['txt'] .= "serverload: {$info['serverload']} ";
10336 // Display size of session if session started
10337 if (session_id()) {
10338 $info['sessionsize'] = display_size(strlen(session_encode()));
10339 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
10340 $info['txt'] .= "Session: {$info['sessionsize']} ";
10343 /* if (isset($rcache->hits) && isset($rcache->misses)) {
10344 $info['rcachehits'] = $rcache->hits;
10345 $info['rcachemisses'] = $rcache->misses;
10346 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
10347 "{$rcache->hits}/{$rcache->misses}</span> ";
10348 $info['txt'] .= 'rcache: '.
10349 "{$rcache->hits}/{$rcache->misses} ";
10351 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
10352 return $info;
10356 * @todo Document this function linux people
10358 function apd_get_profiling() {
10359 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
10363 * Delete directory or only its content
10365 * @param string $dir directory path
10366 * @param bool $content_only
10367 * @return bool success, true also if dir does not exist
10369 function remove_dir($dir, $content_only=false) {
10370 if (!file_exists($dir)) {
10371 // nothing to do
10372 return true;
10374 if (!$handle = opendir($dir)) {
10375 return false;
10377 $result = true;
10378 while (false!==($item = readdir($handle))) {
10379 if($item != '.' && $item != '..') {
10380 if(is_dir($dir.'/'.$item)) {
10381 $result = remove_dir($dir.'/'.$item) && $result;
10382 }else{
10383 $result = unlink($dir.'/'.$item) && $result;
10387 closedir($handle);
10388 if ($content_only) {
10389 clearstatcache(); // make sure file stat cache is properly invalidated
10390 return $result;
10392 $result = rmdir($dir); // if anything left the result will be false, no need for && $result
10393 clearstatcache(); // make sure file stat cache is properly invalidated
10394 return $result;
10398 * Detect if an object or a class contains a given property
10399 * will take an actual object or the name of a class
10401 * @param mix $obj Name of class or real object to test
10402 * @param string $property name of property to find
10403 * @return bool true if property exists
10405 function object_property_exists( $obj, $property ) {
10406 if (is_string( $obj )) {
10407 $properties = get_class_vars( $obj );
10409 else {
10410 $properties = get_object_vars( $obj );
10412 return array_key_exists( $property, $properties );
10416 * Converts an object into an associative array
10418 * This function converts an object into an associative array by iterating
10419 * over its public properties. Because this function uses the foreach
10420 * construct, Iterators are respected. It works recursively on arrays of objects.
10421 * Arrays and simple values are returned as is.
10423 * If class has magic properties, it can implement IteratorAggregate
10424 * and return all available properties in getIterator()
10426 * @param mixed $var
10427 * @return array
10429 function convert_to_array($var) {
10430 $result = array();
10431 $references = array();
10433 // loop over elements/properties
10434 foreach ($var as $key => $value) {
10435 // recursively convert objects
10436 if (is_object($value) || is_array($value)) {
10437 // but prevent cycles
10438 if (!in_array($value, $references)) {
10439 $result[$key] = convert_to_array($value);
10440 $references[] = $value;
10442 } else {
10443 // simple values are untouched
10444 $result[$key] = $value;
10447 return $result;
10451 * Detect a custom script replacement in the data directory that will
10452 * replace an existing moodle script
10454 * @return string|bool full path name if a custom script exists, false if no custom script exists
10456 function custom_script_path() {
10457 global $CFG, $SCRIPT;
10459 if ($SCRIPT === null) {
10460 // Probably some weird external script
10461 return false;
10464 $scriptpath = $CFG->customscripts . $SCRIPT;
10466 // check the custom script exists
10467 if (file_exists($scriptpath) and is_file($scriptpath)) {
10468 return $scriptpath;
10469 } else {
10470 return false;
10475 * Returns whether or not the user object is a remote MNET user. This function
10476 * is in moodlelib because it does not rely on loading any of the MNET code.
10478 * @global object
10479 * @param object $user A valid user object
10480 * @return bool True if the user is from a remote Moodle.
10482 function is_mnet_remote_user($user) {
10483 global $CFG;
10485 if (!isset($CFG->mnet_localhost_id)) {
10486 include_once $CFG->dirroot . '/mnet/lib.php';
10487 $env = new mnet_environment();
10488 $env->init();
10489 unset($env);
10492 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
10496 * This function will search for browser prefereed languages, setting Moodle
10497 * to use the best one available if $SESSION->lang is undefined
10499 * @global object
10500 * @global object
10501 * @global object
10503 function setup_lang_from_browser() {
10505 global $CFG, $SESSION, $USER;
10507 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
10508 // Lang is defined in session or user profile, nothing to do
10509 return;
10512 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
10513 return;
10516 /// Extract and clean langs from headers
10517 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
10518 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
10519 $rawlangs = explode(',', $rawlangs); // Convert to array
10520 $langs = array();
10522 $order = 1.0;
10523 foreach ($rawlangs as $lang) {
10524 if (strpos($lang, ';') === false) {
10525 $langs[(string)$order] = $lang;
10526 $order = $order-0.01;
10527 } else {
10528 $parts = explode(';', $lang);
10529 $pos = strpos($parts[1], '=');
10530 $langs[substr($parts[1], $pos+1)] = $parts[0];
10533 krsort($langs, SORT_NUMERIC);
10535 /// Look for such langs under standard locations
10536 foreach ($langs as $lang) {
10537 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
10538 if (get_string_manager()->translation_exists($lang, false)) {
10539 $SESSION->lang = $lang; /// Lang exists, set it in session
10540 break; /// We have finished. Go out
10543 return;
10547 * check if $url matches anything in proxybypass list
10549 * any errors just result in the proxy being used (least bad)
10551 * @global object
10552 * @param string $url url to check
10553 * @return boolean true if we should bypass the proxy
10555 function is_proxybypass( $url ) {
10556 global $CFG;
10558 // sanity check
10559 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
10560 return false;
10563 // get the host part out of the url
10564 if (!$host = parse_url( $url, PHP_URL_HOST )) {
10565 return false;
10568 // get the possible bypass hosts into an array
10569 $matches = explode( ',', $CFG->proxybypass );
10571 // check for a match
10572 // (IPs need to match the left hand side and hosts the right of the url,
10573 // but we can recklessly check both as there can't be a false +ve)
10574 $bypass = false;
10575 foreach ($matches as $match) {
10576 $match = trim($match);
10578 // try for IP match (Left side)
10579 $lhs = substr($host,0,strlen($match));
10580 if (strcasecmp($match,$lhs)==0) {
10581 return true;
10584 // try for host match (Right side)
10585 $rhs = substr($host,-strlen($match));
10586 if (strcasecmp($match,$rhs)==0) {
10587 return true;
10591 // nothing matched.
10592 return false;
10596 ////////////////////////////////////////////////////////////////////////////////
10599 * Check if the passed navigation is of the new style
10601 * @param mixed $navigation
10602 * @return bool true for yes false for no
10604 function is_newnav($navigation) {
10605 if (is_array($navigation) && !empty($navigation['newnav'])) {
10606 return true;
10607 } else {
10608 return false;
10613 * Checks whether the given variable name is defined as a variable within the given object.
10615 * This will NOT work with stdClass objects, which have no class variables.
10617 * @param string $var The variable name
10618 * @param object $object The object to check
10619 * @return boolean
10621 function in_object_vars($var, $object) {
10622 $class_vars = get_class_vars(get_class($object));
10623 $class_vars = array_keys($class_vars);
10624 return in_array($var, $class_vars);
10628 * Returns an array without repeated objects.
10629 * This function is similar to array_unique, but for arrays that have objects as values
10631 * @param array $array
10632 * @param bool $keep_key_assoc
10633 * @return array
10635 function object_array_unique($array, $keep_key_assoc = true) {
10636 $duplicate_keys = array();
10637 $tmp = array();
10639 foreach ($array as $key=>$val) {
10640 // convert objects to arrays, in_array() does not support objects
10641 if (is_object($val)) {
10642 $val = (array)$val;
10645 if (!in_array($val, $tmp)) {
10646 $tmp[] = $val;
10647 } else {
10648 $duplicate_keys[] = $key;
10652 foreach ($duplicate_keys as $key) {
10653 unset($array[$key]);
10656 return $keep_key_assoc ? $array : array_values($array);
10660 * Is a userid the primary administrator?
10662 * @param int $userid int id of user to check
10663 * @return boolean
10665 function is_primary_admin($userid){
10666 $primaryadmin = get_admin();
10668 if($userid == $primaryadmin->id){
10669 return true;
10670 }else{
10671 return false;
10676 * Returns the site identifier
10678 * @global object
10679 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
10681 function get_site_identifier() {
10682 global $CFG;
10683 // Check to see if it is missing. If so, initialise it.
10684 if (empty($CFG->siteidentifier)) {
10685 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
10687 // Return it.
10688 return $CFG->siteidentifier;
10692 * Check whether the given password has no more than the specified
10693 * number of consecutive identical characters.
10695 * @param string $password password to be checked against the password policy
10696 * @param integer $maxchars maximum number of consecutive identical characters
10698 function check_consecutive_identical_characters($password, $maxchars) {
10700 if ($maxchars < 1) {
10701 return true; // 0 is to disable this check
10703 if (strlen($password) <= $maxchars) {
10704 return true; // too short to fail this test
10707 $previouschar = '';
10708 $consecutivecount = 1;
10709 foreach (str_split($password) as $char) {
10710 if ($char != $previouschar) {
10711 $consecutivecount = 1;
10713 else {
10714 $consecutivecount++;
10715 if ($consecutivecount > $maxchars) {
10716 return false; // check failed already
10720 $previouschar = $char;
10723 return true;
10727 * helper function to do partial function binding
10728 * so we can use it for preg_replace_callback, for example
10729 * this works with php functions, user functions, static methods and class methods
10730 * it returns you a callback that you can pass on like so:
10732 * $callback = partial('somefunction', $arg1, $arg2);
10733 * or
10734 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
10735 * or even
10736 * $obj = new someclass();
10737 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
10739 * and then the arguments that are passed through at calltime are appended to the argument list.
10741 * @param mixed $function a php callback
10742 * $param mixed $arg1.. $argv arguments to partially bind with
10744 * @return callback
10746 function partial() {
10747 if (!class_exists('partial')) {
10748 class partial{
10749 var $values = array();
10750 var $func;
10752 function __construct($func, $args) {
10753 $this->values = $args;
10754 $this->func = $func;
10757 function method() {
10758 $args = func_get_args();
10759 return call_user_func_array($this->func, array_merge($this->values, $args));
10763 $args = func_get_args();
10764 $func = array_shift($args);
10765 $p = new partial($func, $args);
10766 return array($p, 'method');
10770 * helper function to load up and initialise the mnet environment
10771 * this must be called before you use mnet functions.
10773 * @return mnet_environment the equivalent of old $MNET global
10775 function get_mnet_environment() {
10776 global $CFG;
10777 require_once($CFG->dirroot . '/mnet/lib.php');
10778 static $instance = null;
10779 if (empty($instance)) {
10780 $instance = new mnet_environment();
10781 $instance->init();
10783 return $instance;
10787 * during xmlrpc server code execution, any code wishing to access
10788 * information about the remote peer must use this to get it.
10790 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
10792 function get_mnet_remote_client() {
10793 if (!defined('MNET_SERVER')) {
10794 debugging(get_string('notinxmlrpcserver', 'mnet'));
10795 return false;
10797 global $MNET_REMOTE_CLIENT;
10798 if (isset($MNET_REMOTE_CLIENT)) {
10799 return $MNET_REMOTE_CLIENT;
10801 return false;
10805 * during the xmlrpc server code execution, this will be called
10806 * to setup the object returned by {@see get_mnet_remote_client}
10808 * @param mnet_remote_client $client the client to set up
10810 function set_mnet_remote_client($client) {
10811 if (!defined('MNET_SERVER')) {
10812 throw new moodle_exception('notinxmlrpcserver', 'mnet');
10814 global $MNET_REMOTE_CLIENT;
10815 $MNET_REMOTE_CLIENT = $client;
10819 * return the jump url for a given remote user
10820 * this is used for rewriting forum post links in emails, etc
10822 * @param stdclass $user the user to get the idp url for
10824 function mnet_get_idp_jump_url($user) {
10825 global $CFG;
10827 static $mnetjumps = array();
10828 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
10829 $idp = mnet_get_peer_host($user->mnethostid);
10830 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
10831 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
10833 return $mnetjumps[$user->mnethostid];
10837 * Gets the homepage to use for the current user
10839 * @return int One of HOMEPAGE_*
10841 function get_home_page() {
10842 global $CFG;
10844 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
10845 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
10846 return HOMEPAGE_MY;
10847 } else {
10848 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
10851 return HOMEPAGE_SITE;
10855 * Gets the name of a course to be displayed when showing a list of courses.
10856 * By default this is just $course->fullname but user can configure it. The
10857 * result of this function should be passed through print_string.
10858 * @param object $course Moodle course object
10859 * @return string Display name of course (either fullname or short + fullname)
10861 function get_course_display_name_for_list($course) {
10862 global $CFG;
10863 if (!empty($CFG->courselistshortnames)) {
10864 return get_string('courseextendednamedisplay', '', $course);
10865 } else {
10866 return $course->fullname;
10871 * The lang_string class
10873 * This special class is used to create an object representation of a string request.
10874 * It is special because processing doesn't occur until the object is first used.
10875 * The class was created especially to aid performance in areas where strings were
10876 * required to be generated but were not necessarily used.
10877 * As an example the admin tree when generated uses over 1500 strings, of which
10878 * normally only 1/3 are ever actually printed at any time.
10879 * The performance advantage is achieved by not actually processing strings that
10880 * arn't being used, as such reducing the processing required for the page.
10882 * How to use the lang_string class?
10883 * There are two methods of using the lang_string class, first through the
10884 * forth argument of the get_string function, and secondly directly.
10885 * The following are examples of both.
10886 * 1. Through get_string calls e.g.
10887 * $string = get_string($identifier, $component, $a, true);
10888 * $string = get_string('yes', 'moodle', null, true);
10889 * 2. Direct instantiation
10890 * $string = new lang_string($identifier, $component, $a, $lang);
10891 * $string = new lang_string('yes');
10893 * How do I use a lang_string object?
10894 * The lang_string object makes use of a magic __toString method so that you
10895 * are able to use the object exactly as you would use a string in most cases.
10896 * This means you are able to collect it into a variable and then directly
10897 * echo it, or concatenate it into another string, or similar.
10898 * The other thing you can do is manually get the string by calling the
10899 * lang_strings out method e.g.
10900 * $string = new lang_string('yes');
10901 * $string->out();
10902 * Also worth noting is that the out method can take one argument, $lang which
10903 * allows the developer to change the language on the fly.
10905 * When should I use a lang_string object?
10906 * The lang_string object is designed to be used in any situation where a
10907 * string may not be needed, but needs to be generated.
10908 * The admin tree is a good example of where lang_string objects should be
10909 * used.
10910 * A more practical example would be any class that requries strings that may
10911 * not be printed (after all classes get renderer by renderers and who knows
10912 * what they will do ;))
10914 * When should I not use a lang_string object?
10915 * Don't use lang_strings when you are going to use a string immediately.
10916 * There is no need as it will be processed immediately and there will be no
10917 * advantage, and in fact perhaps a negative hit as a class has to be
10918 * instantiated for a lang_string object, however get_string won't require
10919 * that.
10921 * Limitations:
10922 * 1. You cannot use a lang_string object as an array offset. Doing so will
10923 * result in PHP throwing an error. (You can use it as an object property!)
10925 * @package core
10926 * @category string
10927 * @copyright 2011 Sam Hemelryk
10928 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10930 class lang_string {
10932 /** @var string The strings identifier */
10933 protected $identifier;
10934 /** @var string The strings component. Default '' */
10935 protected $component = '';
10936 /** @var array|stdClass Any arguments required for the string. Default null */
10937 protected $a = null;
10938 /** @var string The language to use when processing the string. Default null */
10939 protected $lang = null;
10941 /** @var string The processed string (once processed) */
10942 protected $string = null;
10945 * A special boolean. If set to true then the object has been woken up and
10946 * cannot be regenerated. If this is set then $this->string MUST be used.
10947 * @var bool
10949 protected $forcedstring = false;
10952 * Constructs a lang_string object
10954 * This function should do as little processing as possible to ensure the best
10955 * performance for strings that won't be used.
10957 * @param string $identifier The strings identifier
10958 * @param string $component The strings component
10959 * @param stdClass|array $a Any arguments the string requires
10960 * @param string $lang The language to use when processing the string.
10962 public function __construct($identifier, $component = '', $a = null, $lang = null) {
10963 if (empty($component)) {
10964 $component = 'moodle';
10967 $this->identifier = $identifier;
10968 $this->component = $component;
10969 $this->lang = $lang;
10971 // We MUST duplicate $a to ensure that it if it changes by reference those
10972 // changes are not carried across.
10973 // To do this we always ensure $a or its properties/values are strings
10974 // and that any properties/values that arn't convertable are forgotten.
10975 if (!empty($a)) {
10976 if (is_scalar($a)) {
10977 $this->a = $a;
10978 } else if ($a instanceof lang_string) {
10979 $this->a = $a->out();
10980 } else if (is_object($a) or is_array($a)) {
10981 $a = (array)$a;
10982 $this->a = array();
10983 foreach ($a as $key => $value) {
10984 // Make sure conversion errors don't get displayed (results in '')
10985 if (is_array($value)) {
10986 $this->a[$key] = '';
10987 } else if (is_object($value)) {
10988 if (method_exists($value, '__toString')) {
10989 $this->a[$key] = $value->__toString();
10990 } else {
10991 $this->a[$key] = '';
10993 } else {
10994 $this->a[$key] = (string)$value;
11000 if (debugging(false, DEBUG_DEVELOPER)) {
11001 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
11002 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11004 if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') {
11005 throw new coding_exception('Invalid string compontent. Please check your string definition');
11007 if (!get_string_manager()->string_exists($this->identifier, $this->component)) {
11008 debugging('String does not exist. Please check your string definition for '.$this->identifier.'/'.$this->component, DEBUG_DEVELOPER);
11014 * Processes the string.
11016 * This function actually processes the string, stores it in the string property
11017 * and then returns it.
11018 * You will notice that this function is VERY similar to the get_string method.
11019 * That is because it is pretty much doing the same thing.
11020 * However as this function is an upgrade it isn't as tolerant to backwards
11021 * compatability.
11023 * @return string
11025 protected function get_string() {
11026 global $CFG;
11028 // Check if we need to process the string
11029 if ($this->string === null) {
11030 // Check the quality of the identifier.
11031 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
11032 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
11035 // Process the string
11036 $this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
11037 // Debugging feature lets you display string identifier and component
11038 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
11039 $this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
11042 // Return the string
11043 return $this->string;
11047 * Returns the string
11049 * @param string $lang The langauge to use when processing the string
11050 * @return string
11052 public function out($lang = null) {
11053 if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
11054 if ($this->forcedstring) {
11055 debugging('lang_string objects that have been serialised and unserialised cannot be printed in another language. ('.$this->lang.' used)', DEBUG_DEVELOPER);
11056 return $this->get_string();
11058 $translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
11059 return $translatedstring->out();
11061 return $this->get_string();
11065 * Magic __toString method for printing a string
11067 * @return string
11069 public function __toString() {
11070 return $this->get_string();
11074 * Magic __set_state method used for var_export
11076 * @return string
11078 public function __set_state() {
11079 return $this->get_string();
11083 * Prepares the lang_string for sleep and stores only the forcedstring and
11084 * string properties... the string cannot be regenerated so we need to ensure
11085 * it is generated for this.
11087 * @return string
11089 public function __sleep() {
11090 $this->get_string();
11091 $this->forcedstring = true;
11092 return array('forcedstring', 'string', 'lang');