Merge branch 'MDL-32657-master-1' of git://git.luns.net.uk/moodle
[moodle.git] / lib / moodlelib.php
blob0430c7f29dbc6a52c03d427dcbdeb57ca4cd0e80
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 define('PARAM_FLOAT', 'float');
139 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
141 define('PARAM_HOST', 'host');
144 * PARAM_INT - integers only, use when expecting only numbers.
146 define('PARAM_INT', 'int');
149 * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
151 define('PARAM_LANG', 'lang');
154 * 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!)
156 define('PARAM_LOCALURL', 'localurl');
159 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
161 define('PARAM_NOTAGS', 'notags');
164 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
165 * note: the leading slash is not removed, window drive letter is not allowed
167 define('PARAM_PATH', 'path');
170 * PARAM_PEM - Privacy Enhanced Mail format
172 define('PARAM_PEM', 'pem');
175 * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
177 define('PARAM_PERMISSION', 'permission');
180 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
182 define('PARAM_RAW', 'raw');
185 * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
187 define('PARAM_RAW_TRIMMED', 'raw_trimmed');
190 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
192 define('PARAM_SAFEDIR', 'safedir');
195 * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
197 define('PARAM_SAFEPATH', 'safepath');
200 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
202 define('PARAM_SEQUENCE', 'sequence');
205 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
207 define('PARAM_TAG', 'tag');
210 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
212 define('PARAM_TAGLIST', 'taglist');
215 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
217 define('PARAM_TEXT', 'text');
220 * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
222 define('PARAM_THEME', 'theme');
225 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but http://localhost.localdomain/ is ok.
227 define('PARAM_URL', 'url');
230 * 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!!
232 define('PARAM_USERNAME', 'username');
235 * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
237 define('PARAM_STRINGID', 'stringid');
239 ///// DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE /////
241 * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
242 * It was one of the first types, that is why it is abused so much ;-)
243 * @deprecated since 2.0
245 define('PARAM_CLEAN', 'clean');
248 * PARAM_INTEGER - deprecated alias for PARAM_INT
250 define('PARAM_INTEGER', 'int');
253 * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
255 define('PARAM_NUMBER', 'float');
258 * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
259 * NOTE: originally alias for PARAM_APLHA
261 define('PARAM_ACTION', 'alphanumext');
264 * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
265 * NOTE: originally alias for PARAM_APLHA
267 define('PARAM_FORMAT', 'alphanumext');
270 * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
272 define('PARAM_MULTILANG', 'text');
275 * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
276 * string seperated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
277 * America/Port-au-Prince)
279 define('PARAM_TIMEZONE', 'timezone');
282 * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
284 define('PARAM_CLEANFILE', 'file');
287 * PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
288 * Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
289 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
290 * NOTE: numbers and underscores are strongly discouraged in plugin names!
292 define('PARAM_COMPONENT', 'component');
295 * PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
296 * It is usually used together with context id and component.
297 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
299 define('PARAM_AREA', 'area');
302 * PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'radius', 'paypal', 'completionstatus'.
303 * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
304 * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
306 define('PARAM_PLUGIN', 'plugin');
309 /// Web Services ///
312 * VALUE_REQUIRED - if the parameter is not supplied, there is an error
314 define('VALUE_REQUIRED', 1);
317 * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
319 define('VALUE_OPTIONAL', 2);
322 * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
324 define('VALUE_DEFAULT', 0);
327 * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
329 define('NULL_NOT_ALLOWED', false);
332 * NULL_ALLOWED - the parameter can be set to null in the database
334 define('NULL_ALLOWED', true);
336 /// Page types ///
338 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
340 define('PAGE_COURSE_VIEW', 'course-view');
342 /** Get remote addr constant */
343 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
344 /** Get remote addr constant */
345 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
347 /// Blog access level constant declaration ///
348 define ('BLOG_USER_LEVEL', 1);
349 define ('BLOG_GROUP_LEVEL', 2);
350 define ('BLOG_COURSE_LEVEL', 3);
351 define ('BLOG_SITE_LEVEL', 4);
352 define ('BLOG_GLOBAL_LEVEL', 5);
355 ///Tag constants///
357 * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
358 * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
359 * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
361 * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
363 define('TAG_MAX_LENGTH', 50);
365 /// Password policy constants ///
366 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
367 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
368 define ('PASSWORD_DIGITS', '0123456789');
369 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
371 /// Feature constants ///
372 // Used for plugin_supports() to report features that are, or are not, supported by a module.
374 /** True if module can provide a grade */
375 define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
376 /** True if module supports outcomes */
377 define('FEATURE_GRADE_OUTCOMES', 'outcomes');
378 /** True if module supports advanced grading methods */
379 define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
381 /** True if module has code to track whether somebody viewed it */
382 define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
383 /** True if module has custom completion rules */
384 define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
386 /** True if module has no 'view' page (like label) */
387 define('FEATURE_NO_VIEW_LINK', 'viewlink');
388 /** True if module supports outcomes */
389 define('FEATURE_IDNUMBER', 'idnumber');
390 /** True if module supports groups */
391 define('FEATURE_GROUPS', 'groups');
392 /** True if module supports groupings */
393 define('FEATURE_GROUPINGS', 'groupings');
394 /** True if module supports groupmembersonly */
395 define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
397 /** Type of module */
398 define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
399 /** True if module supports intro editor */
400 define('FEATURE_MOD_INTRO', 'mod_intro');
401 /** True if module has default completion */
402 define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
404 define('FEATURE_COMMENT', 'comment');
406 define('FEATURE_RATE', 'rate');
407 /** True if module supports backup/restore of moodle2 format */
408 define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
410 /** True if module can show description on course main page */
411 define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
413 /** Unspecified module archetype */
414 define('MOD_ARCHETYPE_OTHER', 0);
415 /** Resource-like type module */
416 define('MOD_ARCHETYPE_RESOURCE', 1);
417 /** Assignment module archetype */
418 define('MOD_ARCHETYPE_ASSIGNMENT', 2);
419 /** System (not user-addable) module archetype */
420 define('MOD_ARCHETYPE_SYSTEM', 3);
423 * Security token used for allowing access
424 * from external application such as web services.
425 * Scripts do not use any session, performance is relatively
426 * low because we need to load access info in each request.
427 * Scripts are executed in parallel.
429 define('EXTERNAL_TOKEN_PERMANENT', 0);
432 * Security token used for allowing access
433 * of embedded applications, the code is executed in the
434 * active user session. Token is invalidated after user logs out.
435 * Scripts are executed serially - normal session locking is used.
437 define('EXTERNAL_TOKEN_EMBEDDED', 1);
440 * The home page should be the site home
442 define('HOMEPAGE_SITE', 0);
444 * The home page should be the users my page
446 define('HOMEPAGE_MY', 1);
448 * The home page can be chosen by the user
450 define('HOMEPAGE_USER', 2);
453 * Hub directory url (should be moodle.org)
455 define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
459 * Moodle.org url (should be moodle.org)
461 define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
464 * Moodle mobile app service name
466 define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
468 /// PARAMETER HANDLING ////////////////////////////////////////////////////
471 * Returns a particular value for the named variable, taken from
472 * POST or GET. If the parameter doesn't exist then an error is
473 * thrown because we require this variable.
475 * This function should be used to initialise all required values
476 * in a script that are based on parameters. Usually it will be
477 * used like this:
478 * $id = required_param('id', PARAM_INT);
480 * Please note the $type parameter is now required and the value can not be array.
482 * @param string $parname the name of the page parameter we want
483 * @param string $type expected type of parameter
484 * @return mixed
486 function required_param($parname, $type) {
487 if (func_num_args() != 2 or empty($parname) or empty($type)) {
488 throw new coding_exception('required_param() requires $parname and $type to be specified (parameter: '.$parname.')');
490 if (isset($_POST[$parname])) { // POST has precedence
491 $param = $_POST[$parname];
492 } else if (isset($_GET[$parname])) {
493 $param = $_GET[$parname];
494 } else {
495 print_error('missingparam', '', '', $parname);
498 if (is_array($param)) {
499 debugging('Invalid array parameter detected in required_param(): '.$parname);
500 // TODO: switch to fatal error in Moodle 2.3
501 //print_error('missingparam', '', '', $parname);
502 return required_param_array($parname, $type);
505 return clean_param($param, $type);
509 * Returns a particular array value for the named variable, taken from
510 * POST or GET. If the parameter doesn't exist then an error is
511 * thrown because we require this variable.
513 * This function should be used to initialise all required values
514 * in a script that are based on parameters. Usually it will be
515 * used like this:
516 * $ids = required_param_array('ids', PARAM_INT);
518 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
520 * @param string $parname the name of the page parameter we want
521 * @param string $type expected type of parameter
522 * @return array
524 function required_param_array($parname, $type) {
525 if (func_num_args() != 2 or empty($parname) or empty($type)) {
526 throw new coding_exception('required_param_array() requires $parname and $type to be specified (parameter: '.$parname.')');
528 if (isset($_POST[$parname])) { // POST has precedence
529 $param = $_POST[$parname];
530 } else if (isset($_GET[$parname])) {
531 $param = $_GET[$parname];
532 } else {
533 print_error('missingparam', '', '', $parname);
535 if (!is_array($param)) {
536 print_error('missingparam', '', '', $parname);
539 $result = array();
540 foreach($param as $key=>$value) {
541 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
542 debugging('Invalid key name in required_param_array() detected: '.$key.', parameter: '.$parname);
543 continue;
545 $result[$key] = clean_param($value, $type);
548 return $result;
552 * Returns a particular value for the named variable, taken from
553 * POST or GET, otherwise returning a given default.
555 * This function should be used to initialise all optional values
556 * in a script that are based on parameters. Usually it will be
557 * used like this:
558 * $name = optional_param('name', 'Fred', PARAM_TEXT);
560 * Please note the $type parameter is now required and the value can not be array.
562 * @param string $parname the name of the page parameter we want
563 * @param mixed $default the default value to return if nothing is found
564 * @param string $type expected type of parameter
565 * @return mixed
567 function optional_param($parname, $default, $type) {
568 if (func_num_args() != 3 or empty($parname) or empty($type)) {
569 throw new coding_exception('optional_param() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
571 if (!isset($default)) {
572 $default = null;
575 if (isset($_POST[$parname])) { // POST has precedence
576 $param = $_POST[$parname];
577 } else if (isset($_GET[$parname])) {
578 $param = $_GET[$parname];
579 } else {
580 return $default;
583 if (is_array($param)) {
584 debugging('Invalid array parameter detected in required_param(): '.$parname);
585 // TODO: switch to $default in Moodle 2.3
586 //return $default;
587 return optional_param_array($parname, $default, $type);
590 return clean_param($param, $type);
594 * Returns a particular array value for the named variable, taken from
595 * POST or GET, otherwise returning a given default.
597 * This function should be used to initialise all optional values
598 * in a script that are based on parameters. Usually it will be
599 * used like this:
600 * $ids = optional_param('id', array(), PARAM_INT);
602 * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
604 * @param string $parname the name of the page parameter we want
605 * @param mixed $default the default value to return if nothing is found
606 * @param string $type expected type of parameter
607 * @return array
609 function optional_param_array($parname, $default, $type) {
610 if (func_num_args() != 3 or empty($parname) or empty($type)) {
611 throw new coding_exception('optional_param_array() requires $parname, $default and $type to be specified (parameter: '.$parname.')');
614 if (isset($_POST[$parname])) { // POST has precedence
615 $param = $_POST[$parname];
616 } else if (isset($_GET[$parname])) {
617 $param = $_GET[$parname];
618 } else {
619 return $default;
621 if (!is_array($param)) {
622 debugging('optional_param_array() expects array parameters only: '.$parname);
623 return $default;
626 $result = array();
627 foreach($param as $key=>$value) {
628 if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
629 debugging('Invalid key name in optional_param_array() detected: '.$key.', parameter: '.$parname);
630 continue;
632 $result[$key] = clean_param($value, $type);
635 return $result;
639 * Strict validation of parameter values, the values are only converted
640 * to requested PHP type. Internally it is using clean_param, the values
641 * before and after cleaning must be equal - otherwise
642 * an invalid_parameter_exception is thrown.
643 * Objects and classes are not accepted.
645 * @param mixed $param
646 * @param string $type PARAM_ constant
647 * @param bool $allownull are nulls valid value?
648 * @param string $debuginfo optional debug information
649 * @return mixed the $param value converted to PHP type or invalid_parameter_exception
651 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
652 if (is_null($param)) {
653 if ($allownull == NULL_ALLOWED) {
654 return null;
655 } else {
656 throw new invalid_parameter_exception($debuginfo);
659 if (is_array($param) or is_object($param)) {
660 throw new invalid_parameter_exception($debuginfo);
663 $cleaned = clean_param($param, $type);
664 if ((string)$param !== (string)$cleaned) {
665 // conversion to string is usually lossless
666 throw new invalid_parameter_exception($debuginfo);
669 return $cleaned;
673 * Makes sure array contains only the allowed types,
674 * this function does not validate array key names!
675 * <code>
676 * $options = clean_param($options, PARAM_INT);
677 * </code>
679 * @param array $param the variable array we are cleaning
680 * @param string $type expected format of param after cleaning.
681 * @param bool $recursive clean recursive arrays
682 * @return array
684 function clean_param_array(array $param = null, $type, $recursive = false) {
685 $param = (array)$param; // convert null to empty array
686 foreach ($param as $key => $value) {
687 if (is_array($value)) {
688 if ($recursive) {
689 $param[$key] = clean_param_array($value, $type, true);
690 } else {
691 throw new coding_exception('clean_param_array() can not process multidimensional arrays when $recursive is false.');
693 } else {
694 $param[$key] = clean_param($value, $type);
697 return $param;
701 * Used by {@link optional_param()} and {@link required_param()} to
702 * clean the variables and/or cast to specific types, based on
703 * an options field.
704 * <code>
705 * $course->format = clean_param($course->format, PARAM_ALPHA);
706 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_INT);
707 * </code>
709 * @param mixed $param the variable we are cleaning
710 * @param string $type expected format of param after cleaning.
711 * @return mixed
713 function clean_param($param, $type) {
715 global $CFG;
717 if (is_array($param)) {
718 throw new coding_exception('clean_param() can not process arrays, please use clean_param_array() instead.');
719 } else if (is_object($param)) {
720 if (method_exists($param, '__toString')) {
721 $param = $param->__toString();
722 } else {
723 throw new coding_exception('clean_param() can not process objects, please use clean_param_array() instead.');
727 switch ($type) {
728 case PARAM_RAW: // no cleaning at all
729 $param = fix_utf8($param);
730 return $param;
732 case PARAM_RAW_TRIMMED: // no cleaning, but strip leading and trailing whitespace.
733 $param = fix_utf8($param);
734 return trim($param);
736 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
737 // this is deprecated!, please use more specific type instead
738 if (is_numeric($param)) {
739 return $param;
741 $param = fix_utf8($param);
742 return clean_text($param); // Sweep for scripts, etc
744 case PARAM_CLEANHTML: // clean html fragment
745 $param = fix_utf8($param);
746 $param = clean_text($param, FORMAT_HTML); // Sweep for scripts, etc
747 return trim($param);
749 case PARAM_INT:
750 return (int)$param; // Convert to integer
752 case PARAM_FLOAT:
753 case PARAM_NUMBER:
754 return (float)$param; // Convert to float
756 case PARAM_ALPHA: // Remove everything not a-z
757 return preg_replace('/[^a-zA-Z]/i', '', $param);
759 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z_- (originally allowed "/" too)
760 return preg_replace('/[^a-zA-Z_-]/i', '', $param);
762 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
763 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
765 case PARAM_ALPHANUMEXT: // Remove everything not a-zA-Z0-9_-
766 return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
768 case PARAM_SEQUENCE: // Remove everything not 0-9,
769 return preg_replace('/[^0-9,]/i', '', $param);
771 case PARAM_BOOL: // Convert to 1 or 0
772 $tempstr = strtolower($param);
773 if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
774 $param = 1;
775 } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
776 $param = 0;
777 } else {
778 $param = empty($param) ? 0 : 1;
780 return $param;
782 case PARAM_NOTAGS: // Strip all tags
783 $param = fix_utf8($param);
784 return strip_tags($param);
786 case PARAM_TEXT: // leave only tags needed for multilang
787 $param = fix_utf8($param);
788 // if the multilang syntax is not correct we strip all tags
789 // because it would break xhtml strict which is required for accessibility standards
790 // please note this cleaning does not strip unbalanced '>' for BC compatibility reasons
791 do {
792 if (strpos($param, '</lang>') !== false) {
793 // old and future mutilang syntax
794 $param = strip_tags($param, '<lang>');
795 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
796 break;
798 $open = false;
799 foreach ($matches[0] as $match) {
800 if ($match === '</lang>') {
801 if ($open) {
802 $open = false;
803 continue;
804 } else {
805 break 2;
808 if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
809 break 2;
810 } else {
811 $open = true;
814 if ($open) {
815 break;
817 return $param;
819 } else if (strpos($param, '</span>') !== false) {
820 // current problematic multilang syntax
821 $param = strip_tags($param, '<span>');
822 if (!preg_match_all('/<.*>/suU', $param, $matches)) {
823 break;
825 $open = false;
826 foreach ($matches[0] as $match) {
827 if ($match === '</span>') {
828 if ($open) {
829 $open = false;
830 continue;
831 } else {
832 break 2;
835 if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
836 break 2;
837 } else {
838 $open = true;
841 if ($open) {
842 break;
844 return $param;
846 } while (false);
847 // easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string()
848 return strip_tags($param);
850 case PARAM_COMPONENT:
851 // we do not want any guessing here, either the name is correct or not
852 // please note only normalised component names are accepted
853 if (!preg_match('/^[a-z]+(_[a-z][a-z0-9_]*)?[a-z0-9]$/', $param)) {
854 return '';
856 if (strpos($param, '__') !== false) {
857 return '';
859 if (strpos($param, 'mod_') === 0) {
860 // module names must not contain underscores because we need to differentiate them from invalid plugin types
861 if (substr_count($param, '_') != 1) {
862 return '';
865 return $param;
867 case PARAM_PLUGIN:
868 case PARAM_AREA:
869 // we do not want any guessing here, either the name is correct or not
870 if (!preg_match('/^[a-z][a-z0-9_]*[a-z0-9]$/', $param)) {
871 return '';
873 if (strpos($param, '__') !== false) {
874 return '';
876 return $param;
878 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
879 return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
881 case PARAM_SAFEPATH: // Remove everything not a-zA-Z0-9/_-
882 return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
884 case PARAM_FILE: // Strip all suspicious characters from filename
885 $param = fix_utf8($param);
886 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
887 $param = preg_replace('~\.\.+~', '', $param);
888 if ($param === '.') {
889 $param = '';
891 return $param;
893 case PARAM_PATH: // Strip all suspicious characters from file path
894 $param = fix_utf8($param);
895 $param = str_replace('\\', '/', $param);
896 $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
897 $param = preg_replace('~\.\.+~', '', $param);
898 $param = preg_replace('~//+~', '/', $param);
899 return preg_replace('~/(\./)+~', '/', $param);
901 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
902 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
903 // match ipv4 dotted quad
904 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
905 // confirm values are ok
906 if ( $match[0] > 255
907 || $match[1] > 255
908 || $match[3] > 255
909 || $match[4] > 255 ) {
910 // hmmm, what kind of dotted quad is this?
911 $param = '';
913 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
914 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
915 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
917 // all is ok - $param is respected
918 } else {
919 // all is not ok...
920 $param='';
922 return $param;
924 case PARAM_URL: // allow safe ftp, http, mailto urls
925 $param = fix_utf8($param);
926 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
927 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
928 // all is ok, param is respected
929 } else {
930 $param =''; // not really ok
932 return $param;
934 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
935 $param = clean_param($param, PARAM_URL);
936 if (!empty($param)) {
937 if (preg_match(':^/:', $param)) {
938 // root-relative, ok!
939 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
940 // absolute, and matches our wwwroot
941 } else {
942 // relative - let's make sure there are no tricks
943 if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
944 // looks ok.
945 } else {
946 $param = '';
950 return $param;
952 case PARAM_PEM:
953 $param = trim($param);
954 // PEM formatted strings may contain letters/numbers and the symbols
955 // forward slash: /
956 // plus sign: +
957 // equal sign: =
958 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
959 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
960 list($wholething, $body) = $matches;
961 unset($wholething, $matches);
962 $b64 = clean_param($body, PARAM_BASE64);
963 if (!empty($b64)) {
964 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
965 } else {
966 return '';
969 return '';
971 case PARAM_BASE64:
972 if (!empty($param)) {
973 // PEM formatted strings may contain letters/numbers and the symbols
974 // forward slash: /
975 // plus sign: +
976 // equal sign: =
977 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
978 return '';
980 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
981 // Each line of base64 encoded data must be 64 characters in
982 // length, except for the last line which may be less than (or
983 // equal to) 64 characters long.
984 for ($i=0, $j=count($lines); $i < $j; $i++) {
985 if ($i + 1 == $j) {
986 if (64 < strlen($lines[$i])) {
987 return '';
989 continue;
992 if (64 != strlen($lines[$i])) {
993 return '';
996 return implode("\n",$lines);
997 } else {
998 return '';
1001 case PARAM_TAG:
1002 $param = fix_utf8($param);
1003 // Please note it is not safe to use the tag name directly anywhere,
1004 // it must be processed with s(), urlencode() before embedding anywhere.
1005 // remove some nasties
1006 $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
1007 //convert many whitespace chars into one
1008 $param = preg_replace('/\s+/', ' ', $param);
1009 $param = textlib::substr(trim($param), 0, TAG_MAX_LENGTH);
1010 return $param;
1012 case PARAM_TAGLIST:
1013 $param = fix_utf8($param);
1014 $tags = explode(',', $param);
1015 $result = array();
1016 foreach ($tags as $tag) {
1017 $res = clean_param($tag, PARAM_TAG);
1018 if ($res !== '') {
1019 $result[] = $res;
1022 if ($result) {
1023 return implode(',', $result);
1024 } else {
1025 return '';
1028 case PARAM_CAPABILITY:
1029 if (get_capability_info($param)) {
1030 return $param;
1031 } else {
1032 return '';
1035 case PARAM_PERMISSION:
1036 $param = (int)$param;
1037 if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
1038 return $param;
1039 } else {
1040 return CAP_INHERIT;
1043 case PARAM_AUTH:
1044 $param = clean_param($param, PARAM_PLUGIN);
1045 if (empty($param)) {
1046 return '';
1047 } else if (exists_auth_plugin($param)) {
1048 return $param;
1049 } else {
1050 return '';
1053 case PARAM_LANG:
1054 $param = clean_param($param, PARAM_SAFEDIR);
1055 if (get_string_manager()->translation_exists($param)) {
1056 return $param;
1057 } else {
1058 return ''; // Specified language is not installed or param malformed
1061 case PARAM_THEME:
1062 $param = clean_param($param, PARAM_PLUGIN);
1063 if (empty($param)) {
1064 return '';
1065 } else if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
1066 return $param;
1067 } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
1068 return $param;
1069 } else {
1070 return ''; // Specified theme is not installed
1073 case PARAM_USERNAME:
1074 $param = fix_utf8($param);
1075 $param = str_replace(" " , "", $param);
1076 $param = textlib::strtolower($param); // Convert uppercase to lowercase MDL-16919
1077 if (empty($CFG->extendedusernamechars)) {
1078 // regular expression, eliminate all chars EXCEPT:
1079 // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
1080 $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
1082 return $param;
1084 case PARAM_EMAIL:
1085 $param = fix_utf8($param);
1086 if (validate_email($param)) {
1087 return $param;
1088 } else {
1089 return '';
1092 case PARAM_STRINGID:
1093 if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
1094 return $param;
1095 } else {
1096 return '';
1099 case PARAM_TIMEZONE: //can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'
1100 $param = fix_utf8($param);
1101 $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3]|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
1102 if (preg_match($timezonepattern, $param)) {
1103 return $param;
1104 } else {
1105 return '';
1108 default: // throw error, switched parameters in optional_param or another serious problem
1109 print_error("unknownparamtype", '', '', $type);
1114 * Makes sure the data is using valid utf8, invalid characters are discarded.
1116 * Note: this function is not intended for full objects with methods and private properties.
1118 * @param mixed $value
1119 * @return mixed with proper utf-8 encoding
1121 function fix_utf8($value) {
1122 if (is_null($value) or $value === '') {
1123 return $value;
1125 } else if (is_string($value)) {
1126 if ((string)(int)$value === $value) {
1127 // shortcut
1128 return $value;
1130 // lower error reporting because glibc throws bogus notices
1131 $olderror = error_reporting();
1132 if ($olderror & E_NOTICE) {
1133 error_reporting($olderror ^ E_NOTICE);
1135 $result = iconv('UTF-8', 'UTF-8//IGNORE', $value);
1136 if ($olderror & E_NOTICE) {
1137 error_reporting($olderror);
1139 return $result;
1141 } else if (is_array($value)) {
1142 foreach ($value as $k=>$v) {
1143 $value[$k] = fix_utf8($v);
1145 return $value;
1147 } else if (is_object($value)) {
1148 $value = clone($value); // do not modify original
1149 foreach ($value as $k=>$v) {
1150 $value->$k = fix_utf8($v);
1152 return $value;
1154 } else {
1155 // this is some other type, no utf-8 here
1156 return $value;
1161 * Return true if given value is integer or string with integer value
1163 * @param mixed $value String or Int
1164 * @return bool true if number, false if not
1166 function is_number($value) {
1167 if (is_int($value)) {
1168 return true;
1169 } else if (is_string($value)) {
1170 return ((string)(int)$value) === $value;
1171 } else {
1172 return false;
1177 * Returns host part from url
1178 * @param string $url full url
1179 * @return string host, null if not found
1181 function get_host_from_url($url) {
1182 preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
1183 if ($matches) {
1184 return $matches[1];
1186 return null;
1190 * Tests whether anything was returned by text editor
1192 * This function is useful for testing whether something you got back from
1193 * the HTML editor actually contains anything. Sometimes the HTML editor
1194 * appear to be empty, but actually you get back a <br> tag or something.
1196 * @param string $string a string containing HTML.
1197 * @return boolean does the string contain any actual content - that is text,
1198 * images, objects, etc.
1200 function html_is_blank($string) {
1201 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
1205 * Set a key in global configuration
1207 * Set a key/value pair in both this session's {@link $CFG} global variable
1208 * and in the 'config' database table for future sessions.
1210 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
1211 * In that case it doesn't affect $CFG.
1213 * A NULL value will delete the entry.
1215 * @global object
1216 * @global object
1217 * @param string $name the key to set
1218 * @param string $value the value to set (without magic quotes)
1219 * @param string $plugin (optional) the plugin scope, default NULL
1220 * @return bool true or exception
1222 function set_config($name, $value, $plugin=NULL) {
1223 global $CFG, $DB;
1225 if (empty($plugin)) {
1226 if (!array_key_exists($name, $CFG->config_php_settings)) {
1227 // So it's defined for this invocation at least
1228 if (is_null($value)) {
1229 unset($CFG->$name);
1230 } else {
1231 $CFG->$name = (string)$value; // settings from db are always strings
1235 if ($DB->get_field('config', 'name', array('name'=>$name))) {
1236 if ($value === null) {
1237 $DB->delete_records('config', array('name'=>$name));
1238 } else {
1239 $DB->set_field('config', 'value', $value, array('name'=>$name));
1241 } else {
1242 if ($value !== null) {
1243 $config = new stdClass();
1244 $config->name = $name;
1245 $config->value = $value;
1246 $DB->insert_record('config', $config, false);
1250 } else { // plugin scope
1251 if ($id = $DB->get_field('config_plugins', 'id', array('name'=>$name, 'plugin'=>$plugin))) {
1252 if ($value===null) {
1253 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1254 } else {
1255 $DB->set_field('config_plugins', 'value', $value, array('id'=>$id));
1257 } else {
1258 if ($value !== null) {
1259 $config = new stdClass();
1260 $config->plugin = $plugin;
1261 $config->name = $name;
1262 $config->value = $value;
1263 $DB->insert_record('config_plugins', $config, false);
1268 return true;
1272 * Get configuration values from the global config table
1273 * or the config_plugins table.
1275 * If called with one parameter, it will load all the config
1276 * variables for one plugin, and return them as an object.
1278 * If called with 2 parameters it will return a string single
1279 * value or false if the value is not found.
1281 * @param string $plugin full component name
1282 * @param string $name default NULL
1283 * @return mixed hash-like object or single value, return false no config found
1285 function get_config($plugin, $name = NULL) {
1286 global $CFG, $DB;
1288 // normalise component name
1289 if ($plugin === 'moodle' or $plugin === 'core') {
1290 $plugin = NULL;
1293 if (!empty($name)) { // the user is asking for a specific value
1294 if (!empty($plugin)) {
1295 if (isset($CFG->forced_plugin_settings[$plugin]) and array_key_exists($name, $CFG->forced_plugin_settings[$plugin])) {
1296 // setting forced in config file
1297 return $CFG->forced_plugin_settings[$plugin][$name];
1298 } else {
1299 return $DB->get_field('config_plugins', 'value', array('plugin'=>$plugin, 'name'=>$name));
1301 } else {
1302 if (array_key_exists($name, $CFG->config_php_settings)) {
1303 // setting force in config file
1304 return $CFG->config_php_settings[$name];
1305 } else {
1306 return $DB->get_field('config', 'value', array('name'=>$name));
1311 // the user is after a recordset
1312 if ($plugin) {
1313 $localcfg = $DB->get_records_menu('config_plugins', array('plugin'=>$plugin), '', 'name,value');
1314 if (isset($CFG->forced_plugin_settings[$plugin])) {
1315 foreach($CFG->forced_plugin_settings[$plugin] as $n=>$v) {
1316 if (is_null($v) or is_array($v) or is_object($v)) {
1317 // we do not want any extra mess here, just real settings that could be saved in db
1318 unset($localcfg[$n]);
1319 } else {
1320 //convert to string as if it went through the DB
1321 $localcfg[$n] = (string)$v;
1325 if ($localcfg) {
1326 return (object)$localcfg;
1327 } else {
1328 return new stdClass();
1331 } else {
1332 // this part is not really used any more, but anyway...
1333 $localcfg = $DB->get_records_menu('config', array(), '', 'name,value');
1334 foreach($CFG->config_php_settings as $n=>$v) {
1335 if (is_null($v) or is_array($v) or is_object($v)) {
1336 // we do not want any extra mess here, just real settings that could be saved in db
1337 unset($localcfg[$n]);
1338 } else {
1339 //convert to string as if it went through the DB
1340 $localcfg[$n] = (string)$v;
1343 return (object)$localcfg;
1348 * Removes a key from global configuration
1350 * @param string $name the key to set
1351 * @param string $plugin (optional) the plugin scope
1352 * @global object
1353 * @return boolean whether the operation succeeded.
1355 function unset_config($name, $plugin=NULL) {
1356 global $CFG, $DB;
1358 if (empty($plugin)) {
1359 unset($CFG->$name);
1360 $DB->delete_records('config', array('name'=>$name));
1361 } else {
1362 $DB->delete_records('config_plugins', array('name'=>$name, 'plugin'=>$plugin));
1365 return true;
1369 * Remove all the config variables for a given plugin.
1371 * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1372 * @return boolean whether the operation succeeded.
1374 function unset_all_config_for_plugin($plugin) {
1375 global $DB;
1376 $DB->delete_records('config_plugins', array('plugin' => $plugin));
1377 $like = $DB->sql_like('name', '?', true, true, false, '|');
1378 $params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
1379 $DB->delete_records_select('config', $like, $params);
1380 return true;
1384 * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1386 * All users are verified if they still have the necessary capability.
1388 * @param string $value the value of the config setting.
1389 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1390 * @param bool $include admins, include administrators
1391 * @return array of user objects.
1393 function get_users_from_config($value, $capability, $includeadmins = true) {
1394 global $CFG, $DB;
1396 if (empty($value) or $value === '$@NONE@$') {
1397 return array();
1400 // we have to make sure that users still have the necessary capability,
1401 // it should be faster to fetch them all first and then test if they are present
1402 // instead of validating them one-by-one
1403 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
1404 if ($includeadmins) {
1405 $admins = get_admins();
1406 foreach ($admins as $admin) {
1407 $users[$admin->id] = $admin;
1411 if ($value === '$@ALL@$') {
1412 return $users;
1415 $result = array(); // result in correct order
1416 $allowed = explode(',', $value);
1417 foreach ($allowed as $uid) {
1418 if (isset($users[$uid])) {
1419 $user = $users[$uid];
1420 $result[$user->id] = $user;
1424 return $result;
1429 * Invalidates browser caches and cached data in temp
1430 * @return void
1432 function purge_all_caches() {
1433 global $CFG;
1435 reset_text_filters_cache();
1436 js_reset_all_caches();
1437 theme_reset_all_caches();
1438 get_string_manager()->reset_caches();
1439 textlib::reset_caches();
1441 // purge all other caches: rss, simplepie, etc.
1442 remove_dir($CFG->cachedir.'', true);
1444 // make sure cache dir is writable, throws exception if not
1445 make_cache_directory('');
1447 // hack: this script may get called after the purifier was initialised,
1448 // but we do not want to verify repeatedly this exists in each call
1449 make_cache_directory('htmlpurifier');
1453 * Get volatile flags
1455 * @param string $type
1456 * @param int $changedsince default null
1457 * @return records array
1459 function get_cache_flags($type, $changedsince=NULL) {
1460 global $DB;
1462 $params = array('type'=>$type, 'expiry'=>time());
1463 $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1464 if ($changedsince !== NULL) {
1465 $params['changedsince'] = $changedsince;
1466 $sqlwhere .= " AND timemodified > :changedsince";
1468 $cf = array();
1470 if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1471 foreach ($flags as $flag) {
1472 $cf[$flag->name] = $flag->value;
1475 return $cf;
1479 * Get volatile flags
1481 * @param string $type
1482 * @param string $name
1483 * @param int $changedsince default null
1484 * @return records array
1486 function get_cache_flag($type, $name, $changedsince=NULL) {
1487 global $DB;
1489 $params = array('type'=>$type, 'name'=>$name, 'expiry'=>time());
1491 $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1492 if ($changedsince !== NULL) {
1493 $params['changedsince'] = $changedsince;
1494 $sqlwhere .= " AND timemodified > :changedsince";
1497 return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1501 * Set a volatile flag
1503 * @param string $type the "type" namespace for the key
1504 * @param string $name the key to set
1505 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
1506 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1507 * @return bool Always returns true
1509 function set_cache_flag($type, $name, $value, $expiry=NULL) {
1510 global $DB;
1512 $timemodified = time();
1513 if ($expiry===NULL || $expiry < $timemodified) {
1514 $expiry = $timemodified + 24 * 60 * 60;
1515 } else {
1516 $expiry = (int)$expiry;
1519 if ($value === NULL) {
1520 unset_cache_flag($type,$name);
1521 return true;
1524 if ($f = $DB->get_record('cache_flags', array('name'=>$name, 'flagtype'=>$type), '*', IGNORE_MULTIPLE)) { // this is a potential problem in DEBUG_DEVELOPER
1525 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
1526 return true; //no need to update; helps rcache too
1528 $f->value = $value;
1529 $f->expiry = $expiry;
1530 $f->timemodified = $timemodified;
1531 $DB->update_record('cache_flags', $f);
1532 } else {
1533 $f = new stdClass();
1534 $f->flagtype = $type;
1535 $f->name = $name;
1536 $f->value = $value;
1537 $f->expiry = $expiry;
1538 $f->timemodified = $timemodified;
1539 $DB->insert_record('cache_flags', $f);
1541 return true;
1545 * Removes a single volatile flag
1547 * @global object
1548 * @param string $type the "type" namespace for the key
1549 * @param string $name the key to set
1550 * @return bool
1552 function unset_cache_flag($type, $name) {
1553 global $DB;
1554 $DB->delete_records('cache_flags', array('name'=>$name, 'flagtype'=>$type));
1555 return true;
1559 * Garbage-collect volatile flags
1561 * @return bool Always returns true
1563 function gc_cache_flags() {
1564 global $DB;
1565 $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1566 return true;
1569 // USER PREFERENCE API
1572 * Refresh user preference cache. This is used most often for $USER
1573 * object that is stored in session, but it also helps with performance in cron script.
1575 * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1577 * @package core
1578 * @category preference
1579 * @access public
1580 * @param stdClass $user User object. Preferences are preloaded into 'preference' property
1581 * @param int $cachelifetime Cache life time on the current page (in seconds)
1582 * @throws coding_exception
1583 * @return null
1585 function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
1586 global $DB;
1587 static $loadedusers = array(); // Static cache, we need to check on each page load, not only every 2 minutes.
1589 if (!isset($user->id)) {
1590 throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1593 if (empty($user->id) or isguestuser($user->id)) {
1594 // No permanent storage for not-logged-in users and guest
1595 if (!isset($user->preference)) {
1596 $user->preference = array();
1598 return;
1601 $timenow = time();
1603 if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
1604 // Already loaded at least once on this page. Are we up to date?
1605 if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
1606 // no need to reload - we are on the same page and we loaded prefs just a moment ago
1607 return;
1609 } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
1610 // no change since the lastcheck on this page
1611 $user->preference['_lastloaded'] = $timenow;
1612 return;
1616 // OK, so we have to reload all preferences
1617 $loadedusers[$user->id] = true;
1618 $user->preference = $DB->get_records_menu('user_preferences', array('userid'=>$user->id), '', 'name,value'); // All values
1619 $user->preference['_lastloaded'] = $timenow;
1623 * Called from set/unset_user_preferences, so that the prefs can
1624 * be correctly reloaded in different sessions.
1626 * NOTE: internal function, do not call from other code.
1628 * @package core
1629 * @access private
1630 * @param integer $userid the user whose prefs were changed.
1632 function mark_user_preferences_changed($userid) {
1633 global $CFG;
1635 if (empty($userid) or isguestuser($userid)) {
1636 // no cache flags for guest and not-logged-in users
1637 return;
1640 set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
1644 * Sets a preference for the specified user.
1646 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1648 * @package core
1649 * @category preference
1650 * @access public
1651 * @param string $name The key to set as preference for the specified user
1652 * @param string $value The value to set for the $name key in the specified user's
1653 * record, null means delete current value.
1654 * @param stdClass|int|null $user A moodle user object or id, null means current user
1655 * @throws coding_exception
1656 * @return bool Always true or exception
1658 function set_user_preference($name, $value, $user = null) {
1659 global $USER, $DB;
1661 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1662 throw new coding_exception('Invalid preference name in set_user_preference() call');
1665 if (is_null($value)) {
1666 // null means delete current
1667 return unset_user_preference($name, $user);
1668 } else if (is_object($value)) {
1669 throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1670 } else if (is_array($value)) {
1671 throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1673 $value = (string)$value;
1674 if (textlib::strlen($value) > 1333) { //value column maximum length is 1333 characters
1675 throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
1678 if (is_null($user)) {
1679 $user = $USER;
1680 } else if (isset($user->id)) {
1681 // $user is valid object
1682 } else if (is_numeric($user)) {
1683 $user = (object)array('id'=>(int)$user);
1684 } else {
1685 throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1688 check_user_preferences_loaded($user);
1690 if (empty($user->id) or isguestuser($user->id)) {
1691 // no permanent storage for not-logged-in users and guest
1692 $user->preference[$name] = $value;
1693 return true;
1696 if ($preference = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>$name))) {
1697 if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
1698 // preference already set to this value
1699 return true;
1701 $DB->set_field('user_preferences', 'value', $value, array('id'=>$preference->id));
1703 } else {
1704 $preference = new stdClass();
1705 $preference->userid = $user->id;
1706 $preference->name = $name;
1707 $preference->value = $value;
1708 $DB->insert_record('user_preferences', $preference);
1711 // update value in cache
1712 $user->preference[$name] = $value;
1714 // set reload flag for other sessions
1715 mark_user_preferences_changed($user->id);
1717 return true;
1721 * Sets a whole array of preferences for the current user
1723 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1725 * @package core
1726 * @category preference
1727 * @access public
1728 * @param array $prefarray An array of key/value pairs to be set
1729 * @param stdClass|int|null $user A moodle user object or id, null means current user
1730 * @return bool Always true or exception
1732 function set_user_preferences(array $prefarray, $user = null) {
1733 foreach ($prefarray as $name => $value) {
1734 set_user_preference($name, $value, $user);
1736 return true;
1740 * Unsets a preference completely by deleting it from the database
1742 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1744 * @package core
1745 * @category preference
1746 * @access public
1747 * @param string $name The key to unset as preference for the specified user
1748 * @param stdClass|int|null $user A moodle user object or id, null means current user
1749 * @throws coding_exception
1750 * @return bool Always true or exception
1752 function unset_user_preference($name, $user = null) {
1753 global $USER, $DB;
1755 if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1756 throw new coding_exception('Invalid preference name in unset_user_preference() call');
1759 if (is_null($user)) {
1760 $user = $USER;
1761 } else if (isset($user->id)) {
1762 // $user is valid object
1763 } else if (is_numeric($user)) {
1764 $user = (object)array('id'=>(int)$user);
1765 } else {
1766 throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1769 check_user_preferences_loaded($user);
1771 if (empty($user->id) or isguestuser($user->id)) {
1772 // no permanent storage for not-logged-in user and guest
1773 unset($user->preference[$name]);
1774 return true;
1777 // delete from DB
1778 $DB->delete_records('user_preferences', array('userid'=>$user->id, 'name'=>$name));
1780 // delete the preference from cache
1781 unset($user->preference[$name]);
1783 // set reload flag for other sessions
1784 mark_user_preferences_changed($user->id);
1786 return true;
1790 * Used to fetch user preference(s)
1792 * If no arguments are supplied this function will return
1793 * all of the current user preferences as an array.
1795 * If a name is specified then this function
1796 * attempts to return that particular preference value. If
1797 * none is found, then the optional value $default is returned,
1798 * otherwise NULL.
1800 * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1802 * @package core
1803 * @category preference
1804 * @access public
1805 * @param string $name Name of the key to use in finding a preference value
1806 * @param mixed|null $default Value to be returned if the $name key is not set in the user preferences
1807 * @param stdClass|int|null $user A moodle user object or id, null means current user
1808 * @throws coding_exception
1809 * @return string|mixed|null A string containing the value of a single preference. An
1810 * array with all of the preferences or null
1812 function get_user_preferences($name = null, $default = null, $user = null) {
1813 global $USER;
1815 if (is_null($name)) {
1816 // all prefs
1817 } else if (is_numeric($name) or $name === '_lastloaded') {
1818 throw new coding_exception('Invalid preference name in get_user_preferences() call');
1821 if (is_null($user)) {
1822 $user = $USER;
1823 } else if (isset($user->id)) {
1824 // $user is valid object
1825 } else if (is_numeric($user)) {
1826 $user = (object)array('id'=>(int)$user);
1827 } else {
1828 throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
1831 check_user_preferences_loaded($user);
1833 if (empty($name)) {
1834 return $user->preference; // All values
1835 } else if (isset($user->preference[$name])) {
1836 return $user->preference[$name]; // The single string value
1837 } else {
1838 return $default; // Default value (null if not specified)
1842 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1845 * Given date parts in user time produce a GMT timestamp.
1847 * @package core
1848 * @category time
1849 * @param int $year The year part to create timestamp of
1850 * @param int $month The month part to create timestamp of
1851 * @param int $day The day part to create timestamp of
1852 * @param int $hour The hour part to create timestamp of
1853 * @param int $minute The minute part to create timestamp of
1854 * @param int $second The second part to create timestamp of
1855 * @param int|float|string $timezone Timezone modifier, used to calculate GMT time offset.
1856 * if 99 then default user's timezone is used {@link http://docs.moodle.org/dev/Time_API#Timezone}
1857 * @param bool $applydst Toggle Daylight Saving Time, default true, will be
1858 * applied only if timezone is 99 or string.
1859 * @return int GMT timestamp
1861 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1863 //save input timezone, required for dst offset check.
1864 $passedtimezone = $timezone;
1866 $timezone = get_user_timezone_offset($timezone);
1868 if (abs($timezone) > 13) { //server time
1869 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1870 } else {
1871 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1872 $time = usertime($time, $timezone);
1874 //Apply dst for string timezones or if 99 then try dst offset with user's default timezone
1875 if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
1876 $time -= dst_offset_on($time, $passedtimezone);
1880 return $time;
1885 * Format a date/time (seconds) as weeks, days, hours etc as needed
1887 * Given an amount of time in seconds, returns string
1888 * formatted nicely as weeks, days, hours etc as needed
1890 * @package core
1891 * @category time
1892 * @uses MINSECS
1893 * @uses HOURSECS
1894 * @uses DAYSECS
1895 * @uses YEARSECS
1896 * @param int $totalsecs Time in seconds
1897 * @param object $str Should be a time object
1898 * @return string A nicely formatted date/time string
1900 function format_time($totalsecs, $str=NULL) {
1902 $totalsecs = abs($totalsecs);
1904 if (!$str) { // Create the str structure the slow way
1905 $str = new stdClass();
1906 $str->day = get_string('day');
1907 $str->days = get_string('days');
1908 $str->hour = get_string('hour');
1909 $str->hours = get_string('hours');
1910 $str->min = get_string('min');
1911 $str->mins = get_string('mins');
1912 $str->sec = get_string('sec');
1913 $str->secs = get_string('secs');
1914 $str->year = get_string('year');
1915 $str->years = get_string('years');
1919 $years = floor($totalsecs/YEARSECS);
1920 $remainder = $totalsecs - ($years*YEARSECS);
1921 $days = floor($remainder/DAYSECS);
1922 $remainder = $totalsecs - ($days*DAYSECS);
1923 $hours = floor($remainder/HOURSECS);
1924 $remainder = $remainder - ($hours*HOURSECS);
1925 $mins = floor($remainder/MINSECS);
1926 $secs = $remainder - ($mins*MINSECS);
1928 $ss = ($secs == 1) ? $str->sec : $str->secs;
1929 $sm = ($mins == 1) ? $str->min : $str->mins;
1930 $sh = ($hours == 1) ? $str->hour : $str->hours;
1931 $sd = ($days == 1) ? $str->day : $str->days;
1932 $sy = ($years == 1) ? $str->year : $str->years;
1934 $oyears = '';
1935 $odays = '';
1936 $ohours = '';
1937 $omins = '';
1938 $osecs = '';
1940 if ($years) $oyears = $years .' '. $sy;
1941 if ($days) $odays = $days .' '. $sd;
1942 if ($hours) $ohours = $hours .' '. $sh;
1943 if ($mins) $omins = $mins .' '. $sm;
1944 if ($secs) $osecs = $secs .' '. $ss;
1946 if ($years) return trim($oyears .' '. $odays);
1947 if ($days) return trim($odays .' '. $ohours);
1948 if ($hours) return trim($ohours .' '. $omins);
1949 if ($mins) return trim($omins .' '. $osecs);
1950 if ($secs) return $osecs;
1951 return get_string('now');
1955 * Returns a formatted string that represents a date in user time
1957 * Returns a formatted string that represents a date in user time
1958 * <b>WARNING: note that the format is for strftime(), not date().</b>
1959 * Because of a bug in most Windows time libraries, we can't use
1960 * the nicer %e, so we have to use %d which has leading zeroes.
1961 * A lot of the fuss in the function is just getting rid of these leading
1962 * zeroes as efficiently as possible.
1964 * If parameter fixday = true (default), then take off leading
1965 * zero from %d, else maintain it.
1967 * @package core
1968 * @category time
1969 * @param int $date the timestamp in UTC, as obtained from the database.
1970 * @param string $format strftime format. You should probably get this using
1971 * get_string('strftime...', 'langconfig');
1972 * @param int|float|string $timezone by default, uses the user's time zone. if numeric and
1973 * not 99 then daylight saving will not be added.
1974 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
1975 * @param bool $fixday If true (default) then the leading zero from %d is removed.
1976 * If false then the leading zero is maintained.
1977 * @param bool $fixhour If true (default) then the leading zero from %I is removed.
1978 * @return string the formatted date/time.
1980 function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true) {
1982 global $CFG;
1984 if (empty($format)) {
1985 $format = get_string('strftimedaydatetime', 'langconfig');
1988 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
1989 $fixday = false;
1990 } else if ($fixday) {
1991 $formatnoday = str_replace('%d', 'DD', $format);
1992 $fixday = ($formatnoday != $format);
1993 $format = $formatnoday;
1996 // Note: This logic about fixing 12-hour time to remove unnecessary leading
1997 // zero is required because on Windows, PHP strftime function does not
1998 // support the correct 'hour without leading zero' parameter (%l).
1999 if (!empty($CFG->nofixhour)) {
2000 // Config.php can force %I not to be fixed.
2001 $fixhour = false;
2002 } else if ($fixhour) {
2003 $formatnohour = str_replace('%I', 'HH', $format);
2004 $fixhour = ($formatnohour != $format);
2005 $format = $formatnohour;
2008 //add daylight saving offset for string timezones only, as we can't get dst for
2009 //float values. if timezone is 99 (user default timezone), then try update dst.
2010 if ((99 == $timezone) || !is_numeric($timezone)) {
2011 $date += dst_offset_on($date, $timezone);
2014 $timezone = get_user_timezone_offset($timezone);
2016 if (abs($timezone) > 13) { /// Server time
2017 $datestring = strftime($format, $date);
2018 if ($fixday) {
2019 $daystring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
2020 $datestring = str_replace('DD', $daystring, $datestring);
2022 if ($fixhour) {
2023 $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
2024 $datestring = str_replace('HH', $hourstring, $datestring);
2026 } else {
2027 $date += (int)($timezone * 3600);
2028 $datestring = gmstrftime($format, $date);
2029 if ($fixday) {
2030 $daystring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
2031 $datestring = str_replace('DD', $daystring, $datestring);
2033 if ($fixhour) {
2034 $hourstring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %I', $date)));
2035 $datestring = str_replace('HH', $hourstring, $datestring);
2039 /// If we are running under Windows convert from windows encoding to UTF-8
2040 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
2042 if ($CFG->ostype == 'WINDOWS') {
2043 if ($localewincharset = get_string('localewincharset', 'langconfig')) {
2044 $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
2048 return $datestring;
2052 * Given a $time timestamp in GMT (seconds since epoch),
2053 * returns an array that represents the date in user time
2055 * @package core
2056 * @category time
2057 * @uses HOURSECS
2058 * @param int $time Timestamp in GMT
2059 * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
2060 * dst offset is applyed {@link http://docs.moodle.org/dev/Time_API#Timezone}
2061 * @return array An array that represents the date in user time
2063 function usergetdate($time, $timezone=99) {
2065 //save input timezone, required for dst offset check.
2066 $passedtimezone = $timezone;
2068 $timezone = get_user_timezone_offset($timezone);
2070 if (abs($timezone) > 13) { // Server time
2071 return getdate($time);
2074 //add daylight saving offset for string timezones only, as we can't get dst for
2075 //float values. if timezone is 99 (user default timezone), then try update dst.
2076 if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
2077 $time += dst_offset_on($time, $passedtimezone);
2080 $time += intval((float)$timezone * HOURSECS);
2082 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
2084 //be careful to ensure the returned array matches that produced by getdate() above
2085 list(
2086 $getdate['month'],
2087 $getdate['weekday'],
2088 $getdate['yday'],
2089 $getdate['year'],
2090 $getdate['mon'],
2091 $getdate['wday'],
2092 $getdate['mday'],
2093 $getdate['hours'],
2094 $getdate['minutes'],
2095 $getdate['seconds']
2096 ) = explode('_', $datestring);
2098 return $getdate;
2102 * Given a GMT timestamp (seconds since epoch), offsets it by
2103 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
2105 * @package core
2106 * @category time
2107 * @uses HOURSECS
2108 * @param int $date Timestamp in GMT
2109 * @param float|int|string $timezone timezone to calculate GMT time offset before
2110 * calculating user time, 99 is default user timezone
2111 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2112 * @return int
2114 function usertime($date, $timezone=99) {
2116 $timezone = get_user_timezone_offset($timezone);
2118 if (abs($timezone) > 13) {
2119 return $date;
2121 return $date - (int)($timezone * HOURSECS);
2125 * Given a time, return the GMT timestamp of the most recent midnight
2126 * for the current user.
2128 * @package core
2129 * @category time
2130 * @param int $date Timestamp in GMT
2131 * @param float|int|string $timezone timezone to calculate GMT time offset before
2132 * calculating user midnight time, 99 is default user timezone
2133 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2134 * @return int Returns a GMT timestamp
2136 function usergetmidnight($date, $timezone=99) {
2138 $userdate = usergetdate($date, $timezone);
2140 // Time of midnight of this user's day, in GMT
2141 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
2146 * Returns a string that prints the user's timezone
2148 * @package core
2149 * @category time
2150 * @param float|int|string $timezone timezone to calculate GMT time offset before
2151 * calculating user timezone, 99 is default user timezone
2152 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2153 * @return string
2155 function usertimezone($timezone=99) {
2157 $tz = get_user_timezone($timezone);
2159 if (!is_float($tz)) {
2160 return $tz;
2163 if(abs($tz) > 13) { // Server time
2164 return get_string('serverlocaltime');
2167 if($tz == intval($tz)) {
2168 // Don't show .0 for whole hours
2169 $tz = intval($tz);
2172 if($tz == 0) {
2173 return 'UTC';
2175 else if($tz > 0) {
2176 return 'UTC+'.$tz;
2178 else {
2179 return 'UTC'.$tz;
2185 * Returns a float which represents the user's timezone difference from GMT in hours
2186 * Checks various settings and picks the most dominant of those which have a value
2188 * @package core
2189 * @category time
2190 * @param float|int|string $tz timezone to calculate GMT time offset for user,
2191 * 99 is default user timezone
2192 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2193 * @return float
2195 function get_user_timezone_offset($tz = 99) {
2197 global $USER, $CFG;
2199 $tz = get_user_timezone($tz);
2201 if (is_float($tz)) {
2202 return $tz;
2203 } else {
2204 $tzrecord = get_timezone_record($tz);
2205 if (empty($tzrecord)) {
2206 return 99.0;
2208 return (float)$tzrecord->gmtoff / HOURMINS;
2213 * Returns an int which represents the systems's timezone difference from GMT in seconds
2215 * @package core
2216 * @category time
2217 * @param float|int|string $tz timezone for which offset is required.
2218 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2219 * @return int|bool if found, false is timezone 99 or error
2221 function get_timezone_offset($tz) {
2222 global $CFG;
2224 if ($tz == 99) {
2225 return false;
2228 if (is_numeric($tz)) {
2229 return intval($tz * 60*60);
2232 if (!$tzrecord = get_timezone_record($tz)) {
2233 return false;
2235 return intval($tzrecord->gmtoff * 60);
2239 * Returns a float or a string which denotes the user's timezone
2240 * 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)
2241 * means that for this timezone there are also DST rules to be taken into account
2242 * Checks various settings and picks the most dominant of those which have a value
2244 * @package core
2245 * @category time
2246 * @param float|int|string $tz timezone to calculate GMT time offset before
2247 * calculating user timezone, 99 is default user timezone
2248 * {@link http://docs.moodle.org/dev/Time_API#Timezone}
2249 * @return float|string
2251 function get_user_timezone($tz = 99) {
2252 global $USER, $CFG;
2254 $timezones = array(
2255 $tz,
2256 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
2257 isset($USER->timezone) ? $USER->timezone : 99,
2258 isset($CFG->timezone) ? $CFG->timezone : 99,
2261 $tz = 99;
2263 while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
2264 $tz = $next['value'];
2267 return is_numeric($tz) ? (float) $tz : $tz;
2271 * Returns cached timezone record for given $timezonename
2273 * @package core
2274 * @param string $timezonename name of the timezone
2275 * @return stdClass|bool timezonerecord or false
2277 function get_timezone_record($timezonename) {
2278 global $CFG, $DB;
2279 static $cache = NULL;
2281 if ($cache === NULL) {
2282 $cache = array();
2285 if (isset($cache[$timezonename])) {
2286 return $cache[$timezonename];
2289 return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
2290 WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
2294 * Build and store the users Daylight Saving Time (DST) table
2296 * @package core
2297 * @param int $from_year Start year for the table, defaults to 1971
2298 * @param int $to_year End year for the table, defaults to 2035
2299 * @param int|float|string $strtimezone, timezone to check if dst should be applyed.
2300 * @return bool
2302 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
2303 global $CFG, $SESSION, $DB;
2305 $usertz = get_user_timezone($strtimezone);
2307 if (is_float($usertz)) {
2308 // Trivial timezone, no DST
2309 return false;
2312 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
2313 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
2314 unset($SESSION->dst_offsets);
2315 unset($SESSION->dst_range);
2318 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
2319 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
2320 // This will be the return path most of the time, pretty light computationally
2321 return true;
2324 // Reaching here means we either need to extend our table or create it from scratch
2326 // Remember which TZ we calculated these changes for
2327 $SESSION->dst_offsettz = $usertz;
2329 if(empty($SESSION->dst_offsets)) {
2330 // If we 're creating from scratch, put the two guard elements in there
2331 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
2333 if(empty($SESSION->dst_range)) {
2334 // If creating from scratch
2335 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
2336 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
2338 // Fill in the array with the extra years we need to process
2339 $yearstoprocess = array();
2340 for($i = $from; $i <= $to; ++$i) {
2341 $yearstoprocess[] = $i;
2344 // Take note of which years we have processed for future calls
2345 $SESSION->dst_range = array($from, $to);
2347 else {
2348 // If needing to extend the table, do the same
2349 $yearstoprocess = array();
2351 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
2352 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
2354 if($from < $SESSION->dst_range[0]) {
2355 // Take note of which years we need to process and then note that we have processed them for future calls
2356 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2357 $yearstoprocess[] = $i;
2359 $SESSION->dst_range[0] = $from;
2361 if($to > $SESSION->dst_range[1]) {
2362 // Take note of which years we need to process and then note that we have processed them for future calls
2363 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2364 $yearstoprocess[] = $i;
2366 $SESSION->dst_range[1] = $to;
2370 if(empty($yearstoprocess)) {
2371 // This means that there was a call requesting a SMALLER range than we have already calculated
2372 return true;
2375 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2376 // Also, the array is sorted in descending timestamp order!
2378 // Get DB data
2380 static $presets_cache = array();
2381 if (!isset($presets_cache[$usertz])) {
2382 $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');
2384 if(empty($presets_cache[$usertz])) {
2385 return false;
2388 // Remove ending guard (first element of the array)
2389 reset($SESSION->dst_offsets);
2390 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2392 // Add all required change timestamps
2393 foreach($yearstoprocess as $y) {
2394 // Find the record which is in effect for the year $y
2395 foreach($presets_cache[$usertz] as $year => $preset) {
2396 if($year <= $y) {
2397 break;
2401 $changes = dst_changes_for_year($y, $preset);
2403 if($changes === NULL) {
2404 continue;
2406 if($changes['dst'] != 0) {
2407 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2409 if($changes['std'] != 0) {
2410 $SESSION->dst_offsets[$changes['std']] = 0;
2414 // Put in a guard element at the top
2415 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2416 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
2418 // Sort again
2419 krsort($SESSION->dst_offsets);
2421 return true;
2425 * Calculates the required DST change and returns a Timestamp Array
2427 * @package core
2428 * @category time
2429 * @uses HOURSECS
2430 * @uses MINSECS
2431 * @param int|string $year Int or String Year to focus on
2432 * @param object $timezone Instatiated Timezone object
2433 * @return array|null Array dst=>xx, 0=>xx, std=>yy, 1=>yy or NULL
2435 function dst_changes_for_year($year, $timezone) {
2437 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2438 return NULL;
2441 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2442 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2444 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
2445 list($std_hour, $std_min) = explode(':', $timezone->std_time);
2447 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2448 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2450 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2451 // This has the advantage of being able to have negative values for hour, i.e. for timezones
2452 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2454 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
2455 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
2457 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2461 * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2462 * - Note: Daylight saving only works for string timezones and not for float.
2464 * @package core
2465 * @category time
2466 * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2467 * @param int|float|string $strtimezone timezone for which offset is expected, if 99 or null
2468 * then user's default timezone is used. {@link http://docs.moodle.org/dev/Time_API#Timezone}
2469 * @return int
2471 function dst_offset_on($time, $strtimezone = NULL) {
2472 global $SESSION;
2474 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
2475 return 0;
2478 reset($SESSION->dst_offsets);
2479 while(list($from, $offset) = each($SESSION->dst_offsets)) {
2480 if($from <= $time) {
2481 break;
2485 // This is the normal return path
2486 if($offset !== NULL) {
2487 return $offset;
2490 // Reaching this point means we haven't calculated far enough, do it now:
2491 // Calculate extra DST changes if needed and recurse. The recursion always
2492 // moves toward the stopping condition, so will always end.
2494 if($from == 0) {
2495 // We need a year smaller than $SESSION->dst_range[0]
2496 if($SESSION->dst_range[0] == 1971) {
2497 return 0;
2499 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
2500 return dst_offset_on($time, $strtimezone);
2502 else {
2503 // We need a year larger than $SESSION->dst_range[1]
2504 if($SESSION->dst_range[1] == 2035) {
2505 return 0;
2507 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
2508 return dst_offset_on($time, $strtimezone);
2513 * Calculates when the day appears in specific month
2515 * @package core
2516 * @category time
2517 * @param int $startday starting day of the month
2518 * @param int $weekday The day when week starts (normally taken from user preferences)
2519 * @param int $month The month whose day is sought
2520 * @param int $year The year of the month whose day is sought
2521 * @return int
2523 function find_day_in_month($startday, $weekday, $month, $year) {
2525 $daysinmonth = days_in_month($month, $year);
2527 if($weekday == -1) {
2528 // Don't care about weekday, so return:
2529 // abs($startday) if $startday != -1
2530 // $daysinmonth otherwise
2531 return ($startday == -1) ? $daysinmonth : abs($startday);
2534 // From now on we 're looking for a specific weekday
2536 // Give "end of month" its actual value, since we know it
2537 if($startday == -1) {
2538 $startday = -1 * $daysinmonth;
2541 // Starting from day $startday, the sign is the direction
2543 if($startday < 1) {
2545 $startday = abs($startday);
2546 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year));
2548 // This is the last such weekday of the month
2549 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2550 if($lastinmonth > $daysinmonth) {
2551 $lastinmonth -= 7;
2554 // Find the first such weekday <= $startday
2555 while($lastinmonth > $startday) {
2556 $lastinmonth -= 7;
2559 return $lastinmonth;
2562 else {
2564 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year));
2566 $diff = $weekday - $indexweekday;
2567 if($diff < 0) {
2568 $diff += 7;
2571 // This is the first such weekday of the month equal to or after $startday
2572 $firstfromindex = $startday + $diff;
2574 return $firstfromindex;
2580 * Calculate the number of days in a given month
2582 * @package core
2583 * @category time
2584 * @param int $month The month whose day count is sought
2585 * @param int $year The year of the month whose day count is sought
2586 * @return int
2588 function days_in_month($month, $year) {
2589 return intval(date('t', mktime(12, 0, 0, $month, 1, $year)));
2593 * Calculate the position in the week of a specific calendar day
2595 * @package core
2596 * @category time
2597 * @param int $day The day of the date whose position in the week is sought
2598 * @param int $month The month of the date whose position in the week is sought
2599 * @param int $year The year of the date whose position in the week is sought
2600 * @return int
2602 function dayofweek($day, $month, $year) {
2603 // I wonder if this is any different from
2604 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
2605 return intval(date('w', mktime(12, 0, 0, $month, $day, $year)));
2608 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
2611 * Returns full login url.
2613 * @return string login url
2615 function get_login_url() {
2616 global $CFG;
2618 $url = "$CFG->wwwroot/login/index.php";
2620 if (!empty($CFG->loginhttps)) {
2621 $url = str_replace('http:', 'https:', $url);
2624 return $url;
2628 * This function checks that the current user is logged in and has the
2629 * required privileges
2631 * This function checks that the current user is logged in, and optionally
2632 * whether they are allowed to be in a particular course and view a particular
2633 * course module.
2634 * If they are not logged in, then it redirects them to the site login unless
2635 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2636 * case they are automatically logged in as guests.
2637 * If $courseid is given and the user is not enrolled in that course then the
2638 * user is redirected to the course enrolment page.
2639 * If $cm is given and the course module is hidden and the user is not a teacher
2640 * in the course then the user is redirected to the course home page.
2642 * When $cm parameter specified, this function sets page layout to 'module'.
2643 * You need to change it manually later if some other layout needed.
2645 * @package core_access
2646 * @category access
2648 * @param mixed $courseorid id of the course or course object
2649 * @param bool $autologinguest default true
2650 * @param object $cm course module object
2651 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2652 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2653 * in order to keep redirects working properly. MDL-14495
2654 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2655 * @return mixed Void, exit, and die depending on path
2657 function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
2658 global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
2660 // setup global $COURSE, themes, language and locale
2661 if (!empty($courseorid)) {
2662 if (is_object($courseorid)) {
2663 $course = $courseorid;
2664 } else if ($courseorid == SITEID) {
2665 $course = clone($SITE);
2666 } else {
2667 $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2669 if ($cm) {
2670 if ($cm->course != $course->id) {
2671 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2673 // make sure we have a $cm from get_fast_modinfo as this contains activity access details
2674 if (!($cm instanceof cm_info)) {
2675 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
2676 // db queries so this is not really a performance concern, however it is obviously
2677 // better if you use get_fast_modinfo to get the cm before calling this.
2678 $modinfo = get_fast_modinfo($course);
2679 $cm = $modinfo->get_cm($cm->id);
2681 $PAGE->set_cm($cm, $course); // set's up global $COURSE
2682 $PAGE->set_pagelayout('incourse');
2683 } else {
2684 $PAGE->set_course($course); // set's up global $COURSE
2686 } else {
2687 // do not touch global $COURSE via $PAGE->set_course(),
2688 // the reasons is we need to be able to call require_login() at any time!!
2689 $course = $SITE;
2690 if ($cm) {
2691 throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2695 // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2696 // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2697 // risk leading the user back to the AJAX request URL.
2698 if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
2699 $setwantsurltome = false;
2702 // If the user is not even logged in yet then make sure they are
2703 if (!isloggedin()) {
2704 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2705 if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2706 // misconfigured site guest, just redirect to login page
2707 redirect(get_login_url());
2708 exit; // never reached
2710 $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2711 complete_user_login($guest);
2712 $USER->autologinguest = true;
2713 $SESSION->lang = $lang;
2714 } else {
2715 //NOTE: $USER->site check was obsoleted by session test cookie,
2716 // $USER->confirmed test is in login/index.php
2717 if ($preventredirect) {
2718 throw new require_login_exception('You are not logged in');
2721 if ($setwantsurltome) {
2722 $SESSION->wantsurl = qualified_me();
2724 if (!empty($_SERVER['HTTP_REFERER'])) {
2725 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
2727 redirect(get_login_url());
2728 exit; // never reached
2732 // loginas as redirection if needed
2733 if ($course->id != SITEID and session_is_loggedinas()) {
2734 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2735 if ($USER->loginascontext->instanceid != $course->id) {
2736 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2741 // check whether the user should be changing password (but only if it is REALLY them)
2742 if (get_user_preferences('auth_forcepasswordchange') && !session_is_loggedinas()) {
2743 $userauth = get_auth_plugin($USER->auth);
2744 if ($userauth->can_change_password() and !$preventredirect) {
2745 if ($setwantsurltome) {
2746 $SESSION->wantsurl = qualified_me();
2748 if ($changeurl = $userauth->change_password_url()) {
2749 //use plugin custom url
2750 redirect($changeurl);
2751 } else {
2752 //use moodle internal method
2753 if (empty($CFG->loginhttps)) {
2754 redirect($CFG->wwwroot .'/login/change_password.php');
2755 } else {
2756 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
2757 redirect($wwwroot .'/login/change_password.php');
2760 } else {
2761 print_error('nopasswordchangeforced', 'auth');
2765 // Check that the user account is properly set up
2766 if (user_not_fully_set_up($USER)) {
2767 if ($preventredirect) {
2768 throw new require_login_exception('User not fully set-up');
2770 if ($setwantsurltome) {
2771 $SESSION->wantsurl = qualified_me();
2773 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2776 // Make sure the USER has a sesskey set up. Used for CSRF protection.
2777 sesskey();
2779 // Do not bother admins with any formalities
2780 if (is_siteadmin()) {
2781 //set accesstime or the user will appear offline which messes up messaging
2782 user_accesstime_log($course->id);
2783 return;
2786 // Check that the user has agreed to a site policy if there is one - do not test in case of admins
2787 if (!$USER->policyagreed and !is_siteadmin()) {
2788 if (!empty($CFG->sitepolicy) and !isguestuser()) {
2789 if ($preventredirect) {
2790 throw new require_login_exception('Policy not agreed');
2792 if ($setwantsurltome) {
2793 $SESSION->wantsurl = qualified_me();
2795 redirect($CFG->wwwroot .'/user/policy.php');
2796 } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
2797 if ($preventredirect) {
2798 throw new require_login_exception('Policy not agreed');
2800 if ($setwantsurltome) {
2801 $SESSION->wantsurl = qualified_me();
2803 redirect($CFG->wwwroot .'/user/policy.php');
2807 // Fetch the system context, the course context, and prefetch its child contexts
2808 $sysctx = get_context_instance(CONTEXT_SYSTEM);
2809 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
2810 if ($cm) {
2811 $cmcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
2812 } else {
2813 $cmcontext = null;
2816 // If the site is currently under maintenance, then print a message
2817 if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
2818 if ($preventredirect) {
2819 throw new require_login_exception('Maintenance in progress');
2822 print_maintenance_message();
2825 // make sure the course itself is not hidden
2826 if ($course->id == SITEID) {
2827 // frontpage can not be hidden
2828 } else {
2829 if (is_role_switched($course->id)) {
2830 // when switching roles ignore the hidden flag - user had to be in course to do the switch
2831 } else {
2832 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2833 // originally there was also test of parent category visibility,
2834 // BUT is was very slow in complex queries involving "my courses"
2835 // now it is also possible to simply hide all courses user is not enrolled in :-)
2836 if ($preventredirect) {
2837 throw new require_login_exception('Course is hidden');
2839 // We need to override the navigation URL as the course won't have
2840 // been added to the navigation and thus the navigation will mess up
2841 // when trying to find it.
2842 navigation_node::override_active_url(new moodle_url('/'));
2843 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2848 // is the user enrolled?
2849 if ($course->id == SITEID) {
2850 // everybody is enrolled on the frontpage
2852 } else {
2853 if (session_is_loggedinas()) {
2854 // Make sure the REAL person can access this course first
2855 $realuser = session_get_realuser();
2856 if (!is_enrolled($coursecontext, $realuser->id, '', true) and !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
2857 if ($preventredirect) {
2858 throw new require_login_exception('Invalid course login-as access');
2860 echo $OUTPUT->header();
2861 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2865 $access = false;
2867 if (is_role_switched($course->id)) {
2868 // ok, user had to be inside this course before the switch
2869 $access = true;
2871 } else if (is_viewing($coursecontext, $USER)) {
2872 // ok, no need to mess with enrol
2873 $access = true;
2875 } else {
2876 if (isset($USER->enrol['enrolled'][$course->id])) {
2877 if ($USER->enrol['enrolled'][$course->id] > time()) {
2878 $access = true;
2879 if (isset($USER->enrol['tempguest'][$course->id])) {
2880 unset($USER->enrol['tempguest'][$course->id]);
2881 remove_temp_course_roles($coursecontext);
2883 } else {
2884 //expired
2885 unset($USER->enrol['enrolled'][$course->id]);
2888 if (isset($USER->enrol['tempguest'][$course->id])) {
2889 if ($USER->enrol['tempguest'][$course->id] == 0) {
2890 $access = true;
2891 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
2892 $access = true;
2893 } else {
2894 //expired
2895 unset($USER->enrol['tempguest'][$course->id]);
2896 remove_temp_course_roles($coursecontext);
2900 if ($access) {
2901 // cache ok
2902 } else {
2903 $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
2904 if ($until !== false) {
2905 // active participants may always access, a timestamp in the future, 0 (always) or false.
2906 if ($until == 0) {
2907 $until = ENROL_MAX_TIMESTAMP;
2909 $USER->enrol['enrolled'][$course->id] = $until;
2910 $access = true;
2912 } else {
2913 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2914 $enrols = enrol_get_plugins(true);
2915 // first ask all enabled enrol instances in course if they want to auto enrol user
2916 foreach($instances as $instance) {
2917 if (!isset($enrols[$instance->enrol])) {
2918 continue;
2920 // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
2921 $until = $enrols[$instance->enrol]->try_autoenrol($instance);
2922 if ($until !== false) {
2923 if ($until == 0) {
2924 $until = ENROL_MAX_TIMESTAMP;
2926 $USER->enrol['enrolled'][$course->id] = $until;
2927 $access = true;
2928 break;
2931 // if not enrolled yet try to gain temporary guest access
2932 if (!$access) {
2933 foreach($instances as $instance) {
2934 if (!isset($enrols[$instance->enrol])) {
2935 continue;
2937 // Get a duration for the guest access, a timestamp in the future or false.
2938 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
2939 if ($until !== false and $until > time()) {
2940 $USER->enrol['tempguest'][$course->id] = $until;
2941 $access = true;
2942 break;
2950 if (!$access) {
2951 if ($preventredirect) {
2952 throw new require_login_exception('Not enrolled');
2954 if ($setwantsurltome) {
2955 $SESSION->wantsurl = qualified_me();
2957 redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
2961 // Check visibility of activity to current user; includes visible flag, groupmembersonly,
2962 // conditional availability, etc
2963 if ($cm && !$cm->uservisible) {
2964 if ($preventredirect) {
2965 throw new require_login_exception('Activity is hidden');
2967 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
2970 // Finally access granted, update lastaccess times
2971 user_accesstime_log($course->id);
2976 * This function just makes sure a user is logged out.
2978 * @package core_access
2980 function require_logout() {
2981 global $USER;
2983 $params = $USER;
2985 if (isloggedin()) {
2986 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2988 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2989 foreach($authsequence as $authname) {
2990 $authplugin = get_auth_plugin($authname);
2991 $authplugin->prelogout_hook();
2995 events_trigger('user_logout', $params);
2996 session_get_instance()->terminate_current();
2997 unset($params);
3001 * Weaker version of require_login()
3003 * This is a weaker version of {@link require_login()} which only requires login
3004 * when called from within a course rather than the site page, unless
3005 * the forcelogin option is turned on.
3006 * @see require_login()
3008 * @package core_access
3009 * @category access
3011 * @param mixed $courseorid The course object or id in question
3012 * @param bool $autologinguest Allow autologin guests if that is wanted
3013 * @param object $cm Course activity module if known
3014 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
3015 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
3016 * in order to keep redirects working properly. MDL-14495
3017 * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
3018 * @return void
3020 function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
3021 global $CFG, $PAGE, $SITE;
3022 $issite = (is_object($courseorid) and $courseorid->id == SITEID)
3023 or (!is_object($courseorid) and $courseorid == SITEID);
3024 if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
3025 // note: nearly all pages call get_fast_modinfo anyway and it does not make any
3026 // db queries so this is not really a performance concern, however it is obviously
3027 // better if you use get_fast_modinfo to get the cm before calling this.
3028 if (is_object($courseorid)) {
3029 $course = $courseorid;
3030 } else {
3031 $course = clone($SITE);
3033 $modinfo = get_fast_modinfo($course);
3034 $cm = $modinfo->get_cm($cm->id);
3036 if (!empty($CFG->forcelogin)) {
3037 // login required for both SITE and courses
3038 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3040 } else if ($issite && !empty($cm) and !$cm->uservisible) {
3041 // always login for hidden activities
3042 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3044 } else if ($issite) {
3045 //login for SITE not required
3046 if ($cm and empty($cm->visible)) {
3047 // hidden activities are not accessible without login
3048 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3049 } else if ($cm and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
3050 // not-logged-in users do not have any group membership
3051 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3052 } else {
3053 // We still need to instatiate PAGE vars properly so that things
3054 // that rely on it like navigation function correctly.
3055 if (!empty($courseorid)) {
3056 if (is_object($courseorid)) {
3057 $course = $courseorid;
3058 } else {
3059 $course = clone($SITE);
3061 if ($cm) {
3062 if ($cm->course != $course->id) {
3063 throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
3065 $PAGE->set_cm($cm, $course);
3066 $PAGE->set_pagelayout('incourse');
3067 } else {
3068 $PAGE->set_course($course);
3070 } else {
3071 // If $PAGE->course, and hence $PAGE->context, have not already been set
3072 // up properly, set them up now.
3073 $PAGE->set_course($PAGE->course);
3075 //TODO: verify conditional activities here
3076 user_accesstime_log(SITEID);
3077 return;
3080 } else {
3081 // course login always required
3082 require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3087 * Require key login. Function terminates with error if key not found or incorrect.
3089 * @global object
3090 * @global object
3091 * @global object
3092 * @global object
3093 * @uses NO_MOODLE_COOKIES
3094 * @uses PARAM_ALPHANUM
3095 * @param string $script unique script identifier
3096 * @param int $instance optional instance id
3097 * @return int Instance ID
3099 function require_user_key_login($script, $instance=null) {
3100 global $USER, $SESSION, $CFG, $DB;
3102 if (!NO_MOODLE_COOKIES) {
3103 print_error('sessioncookiesdisable');
3106 /// extra safety
3107 @session_write_close();
3109 $keyvalue = required_param('key', PARAM_ALPHANUM);
3111 if (!$key = $DB->get_record('user_private_key', array('script'=>$script, 'value'=>$keyvalue, 'instance'=>$instance))) {
3112 print_error('invalidkey');
3115 if (!empty($key->validuntil) and $key->validuntil < time()) {
3116 print_error('expiredkey');
3119 if ($key->iprestriction) {
3120 $remoteaddr = getremoteaddr(null);
3121 if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
3122 print_error('ipmismatch');
3126 if (!$user = $DB->get_record('user', array('id'=>$key->userid))) {
3127 print_error('invaliduserid');
3130 /// emulate normal session
3131 enrol_check_plugins($user);
3132 session_set_user($user);
3134 /// note we are not using normal login
3135 if (!defined('USER_KEY_LOGIN')) {
3136 define('USER_KEY_LOGIN', true);
3139 /// return instance id - it might be empty
3140 return $key->instance;
3144 * Creates a new private user access key.
3146 * @global object
3147 * @param string $script unique target identifier
3148 * @param int $userid
3149 * @param int $instance optional instance id
3150 * @param string $iprestriction optional ip restricted access
3151 * @param timestamp $validuntil key valid only until given data
3152 * @return string access key value
3154 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3155 global $DB;
3157 $key = new stdClass();
3158 $key->script = $script;
3159 $key->userid = $userid;
3160 $key->instance = $instance;
3161 $key->iprestriction = $iprestriction;
3162 $key->validuntil = $validuntil;
3163 $key->timecreated = time();
3165 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
3166 while ($DB->record_exists('user_private_key', array('value'=>$key->value))) {
3167 // must be unique
3168 $key->value = md5($userid.'_'.time().random_string(40));
3170 $DB->insert_record('user_private_key', $key);
3171 return $key->value;
3175 * Delete the user's new private user access keys for a particular script.
3177 * @global object
3178 * @param string $script unique target identifier
3179 * @param int $userid
3180 * @return void
3182 function delete_user_key($script,$userid) {
3183 global $DB;
3184 $DB->delete_records('user_private_key', array('script'=>$script, 'userid'=>$userid));
3188 * Gets a private user access key (and creates one if one doesn't exist).
3190 * @global object
3191 * @param string $script unique target identifier
3192 * @param int $userid
3193 * @param int $instance optional instance id
3194 * @param string $iprestriction optional ip restricted access
3195 * @param timestamp $validuntil key valid only until given data
3196 * @return string access key value
3198 function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3199 global $DB;
3201 if ($key = $DB->get_record('user_private_key', array('script'=>$script, 'userid'=>$userid,
3202 'instance'=>$instance, 'iprestriction'=>$iprestriction,
3203 'validuntil'=>$validuntil))) {
3204 return $key->value;
3205 } else {
3206 return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
3212 * Modify the user table by setting the currently logged in user's
3213 * last login to now.
3215 * @global object
3216 * @global object
3217 * @return bool Always returns true
3219 function update_user_login_times() {
3220 global $USER, $DB;
3222 $user = new stdClass();
3223 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
3224 $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
3226 $user->id = $USER->id;
3228 $DB->update_record('user', $user);
3229 return true;
3233 * Determines if a user has completed setting up their account.
3235 * @param user $user A {@link $USER} object to test for the existence of a valid name and email
3236 * @return bool
3238 function user_not_fully_set_up($user) {
3239 if (isguestuser($user)) {
3240 return false;
3242 return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
3246 * Check whether the user has exceeded the bounce threshold
3248 * @global object
3249 * @global object
3250 * @param user $user A {@link $USER} object
3251 * @return bool true=>User has exceeded bounce threshold
3253 function over_bounce_threshold($user) {
3254 global $CFG, $DB;
3256 if (empty($CFG->handlebounces)) {
3257 return false;
3260 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3261 return false;
3264 // set sensible defaults
3265 if (empty($CFG->minbounces)) {
3266 $CFG->minbounces = 10;
3268 if (empty($CFG->bounceratio)) {
3269 $CFG->bounceratio = .20;
3271 $bouncecount = 0;
3272 $sendcount = 0;
3273 if ($bounce = $DB->get_record('user_preferences', array ('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3274 $bouncecount = $bounce->value;
3276 if ($send = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3277 $sendcount = $send->value;
3279 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
3283 * Used to increment or reset email sent count
3285 * @global object
3286 * @param user $user object containing an id
3287 * @param bool $reset will reset the count to 0
3288 * @return void
3290 function set_send_count($user,$reset=false) {
3291 global $DB;
3293 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
3294 return;
3297 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_send_count'))) {
3298 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3299 $DB->update_record('user_preferences', $pref);
3301 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3302 // make a new one
3303 $pref = new stdClass();
3304 $pref->name = 'email_send_count';
3305 $pref->value = 1;
3306 $pref->userid = $user->id;
3307 $DB->insert_record('user_preferences', $pref, false);
3312 * Increment or reset user's email bounce count
3314 * @global object
3315 * @param user $user object containing an id
3316 * @param bool $reset will reset the count to 0
3318 function set_bounce_count($user,$reset=false) {
3319 global $DB;
3321 if ($pref = $DB->get_record('user_preferences', array('userid'=>$user->id, 'name'=>'email_bounce_count'))) {
3322 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3323 $DB->update_record('user_preferences', $pref);
3325 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
3326 // make a new one
3327 $pref = new stdClass();
3328 $pref->name = 'email_bounce_count';
3329 $pref->value = 1;
3330 $pref->userid = $user->id;
3331 $DB->insert_record('user_preferences', $pref, false);
3336 * Keeps track of login attempts
3338 * @global object
3340 function update_login_count() {
3341 global $SESSION;
3343 $max_logins = 10;
3345 if (empty($SESSION->logincount)) {
3346 $SESSION->logincount = 1;
3347 } else {
3348 $SESSION->logincount++;
3351 if ($SESSION->logincount > $max_logins) {
3352 unset($SESSION->wantsurl);
3353 print_error('errortoomanylogins');
3358 * Resets login attempts
3360 * @global object
3362 function reset_login_count() {
3363 global $SESSION;
3365 $SESSION->logincount = 0;
3369 * Determines if the currently logged in user is in editing mode.
3370 * Note: originally this function had $userid parameter - it was not usable anyway
3372 * @deprecated since Moodle 2.0 - use $PAGE->user_is_editing() instead.
3373 * @todo Deprecated function remove when ready
3375 * @global object
3376 * @uses DEBUG_DEVELOPER
3377 * @return bool
3379 function isediting() {
3380 global $PAGE;
3381 debugging('call to deprecated function isediting(). Please use $PAGE->user_is_editing() instead', DEBUG_DEVELOPER);
3382 return $PAGE->user_is_editing();
3386 * Determines if the logged in user is currently moving an activity
3388 * @global object
3389 * @param int $courseid The id of the course being tested
3390 * @return bool
3392 function ismoving($courseid) {
3393 global $USER;
3395 if (!empty($USER->activitycopy)) {
3396 return ($USER->activitycopycourse == $courseid);
3398 return false;
3402 * Returns a persons full name
3404 * Given an object containing firstname and lastname
3405 * values, this function returns a string with the
3406 * full name of the person.
3407 * The result may depend on system settings
3408 * or language. 'override' will force both names
3409 * to be used even if system settings specify one.
3411 * @global object
3412 * @global object
3413 * @param object $user A {@link $USER} object to get full name of
3414 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
3415 * @return string
3417 function fullname($user, $override=false) {
3418 global $CFG, $SESSION;
3420 if (!isset($user->firstname) and !isset($user->lastname)) {
3421 return '';
3424 if (!$override) {
3425 if (!empty($CFG->forcefirstname)) {
3426 $user->firstname = $CFG->forcefirstname;
3428 if (!empty($CFG->forcelastname)) {
3429 $user->lastname = $CFG->forcelastname;
3433 if (!empty($SESSION->fullnamedisplay)) {
3434 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3437 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
3438 return $user->firstname .' '. $user->lastname;
3440 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
3441 return $user->lastname .' '. $user->firstname;
3443 } else if ($CFG->fullnamedisplay == 'firstname') {
3444 if ($override) {
3445 return get_string('fullnamedisplay', '', $user);
3446 } else {
3447 return $user->firstname;
3451 return get_string('fullnamedisplay', '', $user);
3455 * Checks if current user is shown any extra fields when listing users.
3456 * @param object $context Context
3457 * @param array $already Array of fields that we're going to show anyway
3458 * so don't bother listing them
3459 * @return array Array of field names from user table, not including anything
3460 * listed in $already
3462 function get_extra_user_fields($context, $already = array()) {
3463 global $CFG;
3465 // Only users with permission get the extra fields
3466 if (!has_capability('moodle/site:viewuseridentity', $context)) {
3467 return array();
3470 // Split showuseridentity on comma
3471 if (empty($CFG->showuseridentity)) {
3472 // Explode gives wrong result with empty string
3473 $extra = array();
3474 } else {
3475 $extra = explode(',', $CFG->showuseridentity);
3477 $renumber = false;
3478 foreach ($extra as $key => $field) {
3479 if (in_array($field, $already)) {
3480 unset($extra[$key]);
3481 $renumber = true;
3484 if ($renumber) {
3485 // For consistency, if entries are removed from array, renumber it
3486 // so they are numbered as you would expect
3487 $extra = array_merge($extra);
3489 return $extra;
3493 * If the current user is to be shown extra user fields when listing or
3494 * selecting users, returns a string suitable for including in an SQL select
3495 * clause to retrieve those fields.
3496 * @param object $context Context
3497 * @param string $alias Alias of user table, e.g. 'u' (default none)
3498 * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none)
3499 * @param array $already Array of fields that we're going to include anyway
3500 * so don't list them (default none)
3501 * @return string Partial SQL select clause, beginning with comma, for example
3502 * ',u.idnumber,u.department' unless it is blank
3504 function get_extra_user_fields_sql($context, $alias='', $prefix='',
3505 $already = array()) {
3506 $fields = get_extra_user_fields($context, $already);
3507 $result = '';
3508 // Add punctuation for alias
3509 if ($alias !== '') {
3510 $alias .= '.';
3512 foreach ($fields as $field) {
3513 $result .= ', ' . $alias . $field;
3514 if ($prefix) {
3515 $result .= ' AS ' . $prefix . $field;
3518 return $result;
3522 * Returns the display name of a field in the user table. Works for most fields
3523 * that are commonly displayed to users.
3524 * @param string $field Field name, e.g. 'phone1'
3525 * @return string Text description taken from language file, e.g. 'Phone number'
3527 function get_user_field_name($field) {
3528 // Some fields have language strings which are not the same as field name
3529 switch ($field) {
3530 case 'phone1' : return get_string('phone');
3532 // Otherwise just use the same lang string
3533 return get_string($field);
3537 * Returns whether a given authentication plugin exists.
3539 * @global object
3540 * @param string $auth Form of authentication to check for. Defaults to the
3541 * global setting in {@link $CFG}.
3542 * @return boolean Whether the plugin is available.
3544 function exists_auth_plugin($auth) {
3545 global $CFG;
3547 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3548 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3550 return false;
3554 * Checks if a given plugin is in the list of enabled authentication plugins.
3556 * @param string $auth Authentication plugin.
3557 * @return boolean Whether the plugin is enabled.
3559 function is_enabled_auth($auth) {
3560 if (empty($auth)) {
3561 return false;
3564 $enabled = get_enabled_auth_plugins();
3566 return in_array($auth, $enabled);
3570 * Returns an authentication plugin instance.
3572 * @global object
3573 * @param string $auth name of authentication plugin
3574 * @return auth_plugin_base An instance of the required authentication plugin.
3576 function get_auth_plugin($auth) {
3577 global $CFG;
3579 // check the plugin exists first
3580 if (! exists_auth_plugin($auth)) {
3581 print_error('authpluginnotfound', 'debug', '', $auth);
3584 // return auth plugin instance
3585 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
3586 $class = "auth_plugin_$auth";
3587 return new $class;
3591 * Returns array of active auth plugins.
3593 * @param bool $fix fix $CFG->auth if needed
3594 * @return array
3596 function get_enabled_auth_plugins($fix=false) {
3597 global $CFG;
3599 $default = array('manual', 'nologin');
3601 if (empty($CFG->auth)) {
3602 $auths = array();
3603 } else {
3604 $auths = explode(',', $CFG->auth);
3607 if ($fix) {
3608 $auths = array_unique($auths);
3609 foreach($auths as $k=>$authname) {
3610 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3611 unset($auths[$k]);
3614 $newconfig = implode(',', $auths);
3615 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
3616 set_config('auth', $newconfig);
3620 return (array_merge($default, $auths));
3624 * Returns true if an internal authentication method is being used.
3625 * if method not specified then, global default is assumed
3627 * @param string $auth Form of authentication required
3628 * @return bool
3630 function is_internal_auth($auth) {
3631 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
3632 return $authplugin->is_internal();
3636 * Returns true if the user is a 'restored' one
3638 * Used in the login process to inform the user
3639 * and allow him/her to reset the password
3641 * @uses $CFG
3642 * @uses $DB
3643 * @param string $username username to be checked
3644 * @return bool
3646 function is_restored_user($username) {
3647 global $CFG, $DB;
3649 return $DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'password'=>'restored'));
3653 * Returns an array of user fields
3655 * @return array User field/column names
3657 function get_user_fieldnames() {
3658 global $DB;
3660 $fieldarray = $DB->get_columns('user');
3661 unset($fieldarray['id']);
3662 $fieldarray = array_keys($fieldarray);
3664 return $fieldarray;
3668 * Creates a bare-bones user record
3670 * @todo Outline auth types and provide code example
3672 * @param string $username New user's username to add to record
3673 * @param string $password New user's password to add to record
3674 * @param string $auth Form of authentication required
3675 * @return stdClass A complete user object
3677 function create_user_record($username, $password, $auth = 'manual') {
3678 global $CFG, $DB;
3680 //just in case check text case
3681 $username = trim(textlib::strtolower($username));
3683 $authplugin = get_auth_plugin($auth);
3685 $newuser = new stdClass();
3687 if ($newinfo = $authplugin->get_userinfo($username)) {
3688 $newinfo = truncate_userinfo($newinfo);
3689 foreach ($newinfo as $key => $value){
3690 $newuser->$key = $value;
3694 if (!empty($newuser->email)) {
3695 if (email_is_not_allowed($newuser->email)) {
3696 unset($newuser->email);
3700 if (!isset($newuser->city)) {
3701 $newuser->city = '';
3704 $newuser->auth = $auth;
3705 $newuser->username = $username;
3707 // fix for MDL-8480
3708 // user CFG lang for user if $newuser->lang is empty
3709 // or $user->lang is not an installed language
3710 if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
3711 $newuser->lang = $CFG->lang;
3713 $newuser->confirmed = 1;
3714 $newuser->lastip = getremoteaddr();
3715 $newuser->timecreated = time();
3716 $newuser->timemodified = $newuser->timecreated;
3717 $newuser->mnethostid = $CFG->mnet_localhost_id;
3719 $newuser->id = $DB->insert_record('user', $newuser);
3720 $user = get_complete_user_data('id', $newuser->id);
3721 if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
3722 set_user_preference('auth_forcepasswordchange', 1, $user);
3724 update_internal_user_password($user, $password);
3726 // fetch full user record for the event, the complete user data contains too much info
3727 // and we want to be consistent with other places that trigger this event
3728 events_trigger('user_created', $DB->get_record('user', array('id'=>$user->id)));
3730 return $user;
3734 * Will update a local user record from an external source.
3735 * (MNET users can not be updated using this method!)
3737 * @param string $username user's username to update the record
3738 * @return stdClass A complete user object
3740 function update_user_record($username) {
3741 global $DB, $CFG;
3743 $username = trim(textlib::strtolower($username)); /// just in case check text case
3745 $oldinfo = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
3746 $newuser = array();
3747 $userauth = get_auth_plugin($oldinfo->auth);
3749 if ($newinfo = $userauth->get_userinfo($username)) {
3750 $newinfo = truncate_userinfo($newinfo);
3751 foreach ($newinfo as $key => $value){
3752 $key = strtolower($key);
3753 if (!property_exists($oldinfo, $key) or $key === 'username' or $key === 'id'
3754 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
3755 // unknown or must not be changed
3756 continue;
3758 $confval = $userauth->config->{'field_updatelocal_' . $key};
3759 $lockval = $userauth->config->{'field_lock_' . $key};
3760 if (empty($confval) || empty($lockval)) {
3761 continue;
3763 if ($confval === 'onlogin') {
3764 // MDL-4207 Don't overwrite modified user profile values with
3765 // empty LDAP values when 'unlocked if empty' is set. The purpose
3766 // of the setting 'unlocked if empty' is to allow the user to fill
3767 // in a value for the selected field _if LDAP is giving
3768 // nothing_ for this field. Thus it makes sense to let this value
3769 // stand in until LDAP is giving a value for this field.
3770 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3771 if ((string)$oldinfo->$key !== (string)$value) {
3772 $newuser[$key] = (string)$value;
3777 if ($newuser) {
3778 $newuser['id'] = $oldinfo->id;
3779 $newuser['timemodified'] = time();
3780 $DB->update_record('user', $newuser);
3781 // fetch full user record for the event, the complete user data contains too much info
3782 // and we want to be consistent with other places that trigger this event
3783 events_trigger('user_updated', $DB->get_record('user', array('id'=>$oldinfo->id)));
3787 return get_complete_user_data('id', $oldinfo->id);
3791 * Will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3792 * which may have large fields
3794 * @todo Add vartype handling to ensure $info is an array
3796 * @param array $info Array of user properties to truncate if needed
3797 * @return array The now truncated information that was passed in
3799 function truncate_userinfo($info) {
3800 // define the limits
3801 $limit = array(
3802 'username' => 100,
3803 'idnumber' => 255,
3804 'firstname' => 100,
3805 'lastname' => 100,
3806 'email' => 100,
3807 'icq' => 15,
3808 'phone1' => 20,
3809 'phone2' => 20,
3810 'institution' => 40,
3811 'department' => 30,
3812 'address' => 70,
3813 'city' => 120,
3814 'country' => 2,
3815 'url' => 255,
3818 // apply where needed
3819 foreach (array_keys($info) as $key) {
3820 if (!empty($limit[$key])) {
3821 $info[$key] = trim(textlib::substr($info[$key],0, $limit[$key]));
3825 return $info;
3829 * Marks user deleted in internal user database and notifies the auth plugin.
3830 * Also unenrols user from all roles and does other cleanup.
3832 * Any plugin that needs to purge user data should register the 'user_deleted' event.
3834 * @param stdClass $user full user object before delete
3835 * @return boolean always true
3837 function delete_user($user) {
3838 global $CFG, $DB;
3839 require_once($CFG->libdir.'/grouplib.php');
3840 require_once($CFG->libdir.'/gradelib.php');
3841 require_once($CFG->dirroot.'/message/lib.php');
3842 require_once($CFG->dirroot.'/tag/lib.php');
3844 // delete all grades - backup is kept in grade_grades_history table
3845 grade_user_delete($user->id);
3847 //move unread messages from this user to read
3848 message_move_userfrom_unread2read($user->id);
3850 // TODO: remove from cohorts using standard API here
3852 // remove user tags
3853 tag_set('user', $user->id, array());
3855 // unconditionally unenrol from all courses
3856 enrol_user_delete($user);
3858 // unenrol from all roles in all contexts
3859 role_unassign_all(array('userid'=>$user->id)); // this might be slow but it is really needed - modules might do some extra cleanup!
3861 //now do a brute force cleanup
3863 // remove from all cohorts
3864 $DB->delete_records('cohort_members', array('userid'=>$user->id));
3866 // remove from all groups
3867 $DB->delete_records('groups_members', array('userid'=>$user->id));
3869 // brute force unenrol from all courses
3870 $DB->delete_records('user_enrolments', array('userid'=>$user->id));
3872 // purge user preferences
3873 $DB->delete_records('user_preferences', array('userid'=>$user->id));
3875 // purge user extra profile info
3876 $DB->delete_records('user_info_data', array('userid'=>$user->id));
3878 // last course access not necessary either
3879 $DB->delete_records('user_lastaccess', array('userid'=>$user->id));
3881 // remove all user tokens
3882 $DB->delete_records('external_tokens', array('userid'=>$user->id));
3884 // unauthorise the user for all services
3885 $DB->delete_records('external_services_users', array('userid'=>$user->id));
3887 // force logout - may fail if file based sessions used, sorry
3888 session_kill_user($user->id);
3890 // now do a final accesslib cleanup - removes all role assignments in user context and context itself
3891 delete_context(CONTEXT_USER, $user->id);
3893 // workaround for bulk deletes of users with the same email address
3894 $delname = "$user->email.".time();
3895 while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
3896 $delname++;
3899 // mark internal user record as "deleted"
3900 $updateuser = new stdClass();
3901 $updateuser->id = $user->id;
3902 $updateuser->deleted = 1;
3903 $updateuser->username = $delname; // Remember it just in case
3904 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
3905 $updateuser->idnumber = ''; // Clear this field to free it up
3906 $updateuser->timemodified = time();
3908 $DB->update_record('user', $updateuser);
3909 // Add this action to log
3910 add_to_log(SITEID, 'user', 'delete', "view.php?id=$user->id", $user->firstname.' '.$user->lastname);
3913 // We will update the user's timemodified, as it will be passed to the user_deleted event, which
3914 // should know about this updated property persisted to the user's table.
3915 $user->timemodified = $updateuser->timemodified;
3917 // notify auth plugin - do not block the delete even when plugin fails
3918 $authplugin = get_auth_plugin($user->auth);
3919 $authplugin->user_delete($user);
3921 // any plugin that needs to cleanup should register this event
3922 events_trigger('user_deleted', $user);
3924 return true;
3928 * Retrieve the guest user object
3930 * @global object
3931 * @global object
3932 * @return user A {@link $USER} object
3934 function guest_user() {
3935 global $CFG, $DB;
3937 if ($newuser = $DB->get_record('user', array('id'=>$CFG->siteguest))) {
3938 $newuser->confirmed = 1;
3939 $newuser->lang = $CFG->lang;
3940 $newuser->lastip = getremoteaddr();
3943 return $newuser;
3947 * Authenticates a user against the chosen authentication mechanism
3949 * Given a username and password, this function looks them
3950 * up using the currently selected authentication mechanism,
3951 * and if the authentication is successful, it returns a
3952 * valid $user object from the 'user' table.
3954 * Uses auth_ functions from the currently active auth module
3956 * After authenticate_user_login() returns success, you will need to
3957 * log that the user has logged in, and call complete_user_login() to set
3958 * the session up.
3960 * Note: this function works only with non-mnet accounts!
3962 * @param string $username User's username
3963 * @param string $password User's password
3964 * @return user|flase A {@link $USER} object or false if error
3966 function authenticate_user_login($username, $password) {
3967 global $CFG, $DB;
3969 $authsenabled = get_enabled_auth_plugins();
3971 if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
3972 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
3973 if (!empty($user->suspended)) {
3974 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
3975 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3976 return false;
3978 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3979 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
3980 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3981 return false;
3983 $auths = array($auth);
3985 } else {
3986 // check if there's a deleted record (cheaply)
3987 if ($DB->get_field('user', 'id', array('username'=>$username, 'deleted'=>1))) {
3988 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3989 return false;
3992 // User does not exist
3993 $auths = $authsenabled;
3994 $user = new stdClass();
3995 $user->id = 0;
3998 foreach ($auths as $auth) {
3999 $authplugin = get_auth_plugin($auth);
4001 // on auth fail fall through to the next plugin
4002 if (!$authplugin->user_login($username, $password)) {
4003 continue;
4006 // successful authentication
4007 if ($user->id) { // User already exists in database
4008 if (empty($user->auth)) { // For some reason auth isn't set yet
4009 $DB->set_field('user', 'auth', $auth, array('username'=>$username));
4010 $user->auth = $auth;
4012 if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
4013 $DB->set_field('user','firstaccess', $user->timemodified, array('id' => $user->id));
4014 $user->firstaccess = $user->timemodified;
4017 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
4019 if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
4020 $user = update_user_record($username);
4022 } else {
4023 // if user not found and user creation is not disabled, create it
4024 if (empty($CFG->authpreventaccountcreation)) {
4025 $user = create_user_record($username, $password, $auth);
4026 } else {
4027 continue;
4031 $authplugin->sync_roles($user);
4033 foreach ($authsenabled as $hau) {
4034 $hauth = get_auth_plugin($hau);
4035 $hauth->user_authenticated_hook($user, $username, $password);
4038 if (empty($user->id)) {
4039 return false;
4042 if (!empty($user->suspended)) {
4043 // just in case some auth plugin suspended account
4044 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4045 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4046 return false;
4049 return $user;
4052 // failed if all the plugins have failed
4053 add_to_log(SITEID, 'login', 'error', 'index.php', $username);
4054 if (debugging('', DEBUG_ALL)) {
4055 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
4057 return false;
4061 * Call to complete the user login process after authenticate_user_login()
4062 * has succeeded. It will setup the $USER variable and other required bits
4063 * and pieces.
4065 * NOTE:
4066 * - It will NOT log anything -- up to the caller to decide what to log.
4067 * - this function does not set any cookies any more!
4069 * @param object $user
4070 * @return object A {@link $USER} object - BC only, do not use
4072 function complete_user_login($user) {
4073 global $CFG, $USER;
4075 // regenerate session id and delete old session,
4076 // this helps prevent session fixation attacks from the same domain
4077 session_regenerate_id(true);
4079 // let enrol plugins deal with new enrolments if necessary
4080 enrol_check_plugins($user);
4082 // check enrolments, load caps and setup $USER object
4083 session_set_user($user);
4085 // reload preferences from DB
4086 unset($USER->preference);
4087 check_user_preferences_loaded($USER);
4089 // update login times
4090 update_user_login_times();
4092 // extra session prefs init
4093 set_login_session_preferences();
4095 if (isguestuser()) {
4096 // no need to continue when user is THE guest
4097 return $USER;
4100 /// Select password change url
4101 $userauth = get_auth_plugin($USER->auth);
4103 /// check whether the user should be changing password
4104 if (get_user_preferences('auth_forcepasswordchange', false)){
4105 if ($userauth->can_change_password()) {
4106 if ($changeurl = $userauth->change_password_url()) {
4107 redirect($changeurl);
4108 } else {
4109 redirect($CFG->httpswwwroot.'/login/change_password.php');
4111 } else {
4112 print_error('nopasswordchangeforced', 'auth');
4115 return $USER;
4119 * Compare password against hash stored in internal user table.
4120 * If necessary it also updates the stored hash to new format.
4122 * @param stdClass $user (password property may be updated)
4123 * @param string $password plain text password
4124 * @return bool is password valid?
4126 function validate_internal_user_password($user, $password) {
4127 global $CFG;
4129 if (!isset($CFG->passwordsaltmain)) {
4130 $CFG->passwordsaltmain = '';
4133 $validated = false;
4135 if ($user->password === 'not cached') {
4136 // internal password is not used at all, it can not validate
4138 } else if ($user->password === md5($password.$CFG->passwordsaltmain)
4139 or $user->password === md5($password)
4140 or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
4141 or $user->password === md5(addslashes($password))) {
4142 // note: we are intentionally using the addslashes() here because we
4143 // need to accept old password hashes of passwords with magic quotes
4144 $validated = true;
4146 } else {
4147 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
4148 $alt = 'passwordsaltalt'.$i;
4149 if (!empty($CFG->$alt)) {
4150 if ($user->password === md5($password.$CFG->$alt) or $user->password === md5(addslashes($password).$CFG->$alt)) {
4151 $validated = true;
4152 break;
4158 if ($validated) {
4159 // force update of password hash using latest main password salt and encoding if needed
4160 update_internal_user_password($user, $password);
4163 return $validated;
4167 * Calculate hashed value from password using current hash mechanism.
4169 * @param string $password
4170 * @return string password hash
4172 function hash_internal_user_password($password) {
4173 global $CFG;
4175 if (isset($CFG->passwordsaltmain)) {
4176 return md5($password.$CFG->passwordsaltmain);
4177 } else {
4178 return md5($password);
4183 * Update password hash in user object.
4185 * @param stdClass $user (password property may be updated)
4186 * @param string $password plain text password
4187 * @return bool always returns true
4189 function update_internal_user_password($user, $password) {
4190 global $DB;
4192 $authplugin = get_auth_plugin($user->auth);
4193 if ($authplugin->prevent_local_passwords()) {
4194 $hashedpassword = 'not cached';
4195 } else {
4196 $hashedpassword = hash_internal_user_password($password);
4199 if ($user->password !== $hashedpassword) {
4200 $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
4201 $user->password = $hashedpassword;
4204 return true;
4208 * Get a complete user record, which includes all the info
4209 * in the user record.
4211 * Intended for setting as $USER session variable
4213 * @param string $field The user field to be checked for a given value.
4214 * @param string $value The value to match for $field.
4215 * @param int $mnethostid
4216 * @return mixed False, or A {@link $USER} object.
4218 function get_complete_user_data($field, $value, $mnethostid = null) {
4219 global $CFG, $DB;
4221 if (!$field || !$value) {
4222 return false;
4225 /// Build the WHERE clause for an SQL query
4226 $params = array('fieldval'=>$value);
4227 $constraints = "$field = :fieldval AND deleted <> 1";
4229 // If we are loading user data based on anything other than id,
4230 // we must also restrict our search based on mnet host.
4231 if ($field != 'id') {
4232 if (empty($mnethostid)) {
4233 // if empty, we restrict to local users
4234 $mnethostid = $CFG->mnet_localhost_id;
4237 if (!empty($mnethostid)) {
4238 $params['mnethostid'] = $mnethostid;
4239 $constraints .= " AND mnethostid = :mnethostid";
4242 /// Get all the basic user data
4244 if (! $user = $DB->get_record_select('user', $constraints, $params)) {
4245 return false;
4248 /// Get various settings and preferences
4250 // preload preference cache
4251 check_user_preferences_loaded($user);
4253 // load course enrolment related stuff
4254 $user->lastcourseaccess = array(); // during last session
4255 $user->currentcourseaccess = array(); // during current session
4256 if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid'=>$user->id))) {
4257 foreach ($lastaccesses as $lastaccess) {
4258 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
4262 $sql = "SELECT g.id, g.courseid
4263 FROM {groups} g, {groups_members} gm
4264 WHERE gm.groupid=g.id AND gm.userid=?";
4266 // this is a special hack to speedup calendar display
4267 $user->groupmember = array();
4268 if (!isguestuser($user)) {
4269 if ($groups = $DB->get_records_sql($sql, array($user->id))) {
4270 foreach ($groups as $group) {
4271 if (!array_key_exists($group->courseid, $user->groupmember)) {
4272 $user->groupmember[$group->courseid] = array();
4274 $user->groupmember[$group->courseid][$group->id] = $group->id;
4279 /// Add the custom profile fields to the user record
4280 $user->profile = array();
4281 if (!isguestuser($user)) {
4282 require_once($CFG->dirroot.'/user/profile/lib.php');
4283 profile_load_custom_fields($user);
4286 /// Rewrite some variables if necessary
4287 if (!empty($user->description)) {
4288 $user->description = true; // No need to cart all of it around
4290 if (isguestuser($user)) {
4291 $user->lang = $CFG->lang; // Guest language always same as site
4292 $user->firstname = get_string('guestuser'); // Name always in current language
4293 $user->lastname = ' ';
4296 return $user;
4300 * Validate a password against the configured password policy
4302 * @global object
4303 * @param string $password the password to be checked against the password policy
4304 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
4305 * @return bool true if the password is valid according to the policy. false otherwise.
4307 function check_password_policy($password, &$errmsg) {
4308 global $CFG;
4310 if (empty($CFG->passwordpolicy)) {
4311 return true;
4314 $errmsg = '';
4315 if (textlib::strlen($password) < $CFG->minpasswordlength) {
4316 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
4319 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
4320 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
4323 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
4324 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
4327 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
4328 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
4331 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
4332 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
4334 if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
4335 $errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
4338 if ($errmsg == '') {
4339 return true;
4340 } else {
4341 return false;
4347 * When logging in, this function is run to set certain preferences
4348 * for the current SESSION
4350 * @global object
4351 * @global object
4353 function set_login_session_preferences() {
4354 global $SESSION, $CFG;
4356 $SESSION->justloggedin = true;
4358 unset($SESSION->lang);
4363 * Delete a course, including all related data from the database,
4364 * and any associated files.
4366 * @global object
4367 * @global object
4368 * @param mixed $courseorid The id of the course or course object to delete.
4369 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4370 * @return bool true if all the removals succeeded. false if there were any failures. If this
4371 * method returns false, some of the removals will probably have succeeded, and others
4372 * failed, but you have no way of knowing which.
4374 function delete_course($courseorid, $showfeedback = true) {
4375 global $DB;
4377 if (is_object($courseorid)) {
4378 $courseid = $courseorid->id;
4379 $course = $courseorid;
4380 } else {
4381 $courseid = $courseorid;
4382 if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
4383 return false;
4386 $context = get_context_instance(CONTEXT_COURSE, $courseid);
4388 // frontpage course can not be deleted!!
4389 if ($courseid == SITEID) {
4390 return false;
4393 // make the course completely empty
4394 remove_course_contents($courseid, $showfeedback);
4396 // delete the course and related context instance
4397 delete_context(CONTEXT_COURSE, $courseid);
4399 // We will update the course's timemodified, as it will be passed to the course_deleted event,
4400 // which should know about this updated property, as this event is meant to pass the full course record
4401 $course->timemodified = time();
4403 $DB->delete_records("course", array("id"=>$courseid));
4405 //trigger events
4406 $course->context = $context; // you can not fetch context in the event because it was already deleted
4407 events_trigger('course_deleted', $course);
4409 return true;
4413 * Clear a course out completely, deleting all content
4414 * but don't delete the course itself.
4415 * This function does not verify any permissions.
4417 * Please note this function also deletes all user enrolments,
4418 * enrolment instances and role assignments by default.
4420 * $options:
4421 * - 'keep_roles_and_enrolments' - false by default
4422 * - 'keep_groups_and_groupings' - false by default
4424 * @param int $courseid The id of the course that is being deleted
4425 * @param bool $showfeedback Whether to display notifications of each action the function performs.
4426 * @param array $options extra options
4427 * @return bool true if all the removals succeeded. false if there were any failures. If this
4428 * method returns false, some of the removals will probably have succeeded, and others
4429 * failed, but you have no way of knowing which.
4431 function remove_course_contents($courseid, $showfeedback = true, array $options = null) {
4432 global $CFG, $DB, $OUTPUT;
4433 require_once($CFG->libdir.'/completionlib.php');
4434 require_once($CFG->libdir.'/questionlib.php');
4435 require_once($CFG->libdir.'/gradelib.php');
4436 require_once($CFG->dirroot.'/group/lib.php');
4437 require_once($CFG->dirroot.'/tag/coursetagslib.php');
4438 require_once($CFG->dirroot.'/comment/lib.php');
4439 require_once($CFG->dirroot.'/rating/lib.php');
4441 // NOTE: these concatenated strings are suboptimal, but it is just extra info...
4442 $strdeleted = get_string('deleted').' - ';
4444 // Some crazy wishlist of stuff we should skip during purging of course content
4445 $options = (array)$options;
4447 $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
4448 $coursecontext = context_course::instance($courseid);
4449 $fs = get_file_storage();
4451 // Delete course completion information, this has to be done before grades and enrols
4452 $cc = new completion_info($course);
4453 $cc->clear_criteria();
4454 if ($showfeedback) {
4455 echo $OUTPUT->notification($strdeleted.get_string('completion', 'completion'), 'notifysuccess');
4458 // Remove all data from gradebook - this needs to be done before course modules
4459 // because while deleting this information, the system may need to reference
4460 // the course modules that own the grades.
4461 remove_course_grades($courseid, $showfeedback);
4462 remove_grade_letters($coursecontext, $showfeedback);
4464 // Delete course blocks in any all child contexts,
4465 // they may depend on modules so delete them first
4466 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4467 foreach ($childcontexts as $childcontext) {
4468 blocks_delete_all_for_context($childcontext->id);
4470 unset($childcontexts);
4471 blocks_delete_all_for_context($coursecontext->id);
4472 if ($showfeedback) {
4473 echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
4476 // Delete every instance of every module,
4477 // this has to be done before deleting of course level stuff
4478 $locations = get_plugin_list('mod');
4479 foreach ($locations as $modname=>$moddir) {
4480 if ($modname === 'NEWMODULE') {
4481 continue;
4483 if ($module = $DB->get_record('modules', array('name'=>$modname))) {
4484 include_once("$moddir/lib.php"); // Shows php warning only if plugin defective
4485 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
4486 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
4488 if ($instances = $DB->get_records($modname, array('course'=>$course->id))) {
4489 foreach ($instances as $instance) {
4490 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
4491 /// Delete activity context questions and question categories
4492 question_delete_activity($cm, $showfeedback);
4494 if (function_exists($moddelete)) {
4495 // This purges all module data in related tables, extra user prefs, settings, etc.
4496 $moddelete($instance->id);
4497 } else {
4498 // NOTE: we should not allow installation of modules with missing delete support!
4499 debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
4500 $DB->delete_records($modname, array('id'=>$instance->id));
4503 if ($cm) {
4504 // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition
4505 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4506 $DB->delete_records('course_modules', array('id'=>$cm->id));
4510 if (function_exists($moddeletecourse)) {
4511 // Execute ptional course cleanup callback
4512 $moddeletecourse($course, $showfeedback);
4514 if ($instances and $showfeedback) {
4515 echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
4517 } else {
4518 // Ooops, this module is not properly installed, force-delete it in the next block
4521 // We have tried to delete everything the nice way - now let's force-delete any remaining module data
4522 $cms = $DB->get_records('course_modules', array('course'=>$course->id));
4523 foreach ($cms as $cm) {
4524 if ($module = $DB->get_record('modules', array('id'=>$cm->module))) {
4525 try {
4526 $DB->delete_records($module->name, array('id'=>$cm->instance));
4527 } catch (Exception $e) {
4528 // Ignore weird or missing table problems
4531 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
4532 $DB->delete_records('course_modules', array('id'=>$cm->id));
4534 // Remove all data from availability and completion tables that is associated
4535 // with course-modules belonging to this course. Note this is done even if the
4536 // features are not enabled now, in case they were enabled previously
4537 $DB->delete_records_select('course_modules_completion',
4538 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4539 array($courseid));
4540 $DB->delete_records_select('course_modules_availability',
4541 'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
4542 array($courseid));
4543 if ($showfeedback) {
4544 echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
4547 // Cleanup the rest of plugins
4548 $cleanuplugintypes = array('report', 'coursereport', 'format');
4549 foreach ($cleanuplugintypes as $type) {
4550 $plugins = get_plugin_list_with_function($type, 'delete_course', 'lib.php');
4551 foreach ($plugins as $plugin=>$pluginfunction) {
4552 $pluginfunction($course->id, $showfeedback);
4554 if ($showfeedback) {
4555 echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
4559 // Delete questions and question categories
4560 question_delete_course($course, $showfeedback);
4561 if ($showfeedback) {
4562 echo $OUTPUT->notification($strdeleted.get_string('questions', 'question'), 'notifysuccess');
4565 // Make sure there are no subcontexts left - all valid blocks and modules should be already gone
4566 $childcontexts = $coursecontext->get_child_contexts(); // returns all subcontexts since 2.2
4567 foreach ($childcontexts as $childcontext) {
4568 $childcontext->delete();
4570 unset($childcontexts);
4572 // Remove all roles and enrolments by default
4573 if (empty($options['keep_roles_and_enrolments'])) {
4574 // this hack is used in restore when deleting contents of existing course
4575 role_unassign_all(array('contextid'=>$coursecontext->id, 'component'=>''), true);
4576 enrol_course_delete($course);
4577 if ($showfeedback) {
4578 echo $OUTPUT->notification($strdeleted.get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
4582 // Delete any groups, removing members and grouping/course links first.
4583 if (empty($options['keep_groups_and_groupings'])) {
4584 groups_delete_groupings($course->id, $showfeedback);
4585 groups_delete_groups($course->id, $showfeedback);
4588 // filters be gone!
4589 filter_delete_all_for_context($coursecontext->id);
4591 // die comments!
4592 comment::delete_comments($coursecontext->id);
4594 // ratings are history too
4595 $delopt = new stdclass();
4596 $delopt->contextid = $coursecontext->id;
4597 $rm = new rating_manager();
4598 $rm->delete_ratings($delopt);
4600 // Delete course tags
4601 coursetag_delete_course_tags($course->id, $showfeedback);
4603 // Delete calendar events
4604 $DB->delete_records('event', array('courseid'=>$course->id));
4605 $fs->delete_area_files($coursecontext->id, 'calendar');
4607 // Delete all related records in other core tables that may have a courseid
4608 // This array stores the tables that need to be cleared, as
4609 // table_name => column_name that contains the course id.
4610 $tablestoclear = array(
4611 'log' => 'course', // Course logs (NOTE: this might be changed in the future)
4612 'backup_courses' => 'courseid', // Scheduled backup stuff
4613 'user_lastaccess' => 'courseid', // User access info
4615 foreach ($tablestoclear as $table => $col) {
4616 $DB->delete_records($table, array($col=>$course->id));
4619 // delete all course backup files
4620 $fs->delete_area_files($coursecontext->id, 'backup');
4622 // cleanup course record - remove links to deleted stuff
4623 $oldcourse = new stdClass();
4624 $oldcourse->id = $course->id;
4625 $oldcourse->summary = '';
4626 $oldcourse->modinfo = NULL;
4627 $oldcourse->legacyfiles = 0;
4628 $oldcourse->enablecompletion = 0;
4629 if (!empty($options['keep_groups_and_groupings'])) {
4630 $oldcourse->defaultgroupingid = 0;
4632 $DB->update_record('course', $oldcourse);
4634 // Delete course sections and user selections
4635 $DB->delete_records('course_sections', array('course'=>$course->id));
4636 $DB->delete_records('course_display', array('course'=>$course->id));
4638 // delete legacy, section and any other course files
4639 $fs->delete_area_files($coursecontext->id, 'course'); // files from summary and section
4641 // Delete all remaining stuff linked to context such as files, comments, ratings, etc.
4642 if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
4643 // Easy, do not delete the context itself...
4644 $coursecontext->delete_content();
4646 } else {
4647 // Hack alert!!!!
4648 // We can not drop all context stuff because it would bork enrolments and roles,
4649 // there might be also files used by enrol plugins...
4652 // Delete legacy files - just in case some files are still left there after conversion to new file api,
4653 // also some non-standard unsupported plugins may try to store something there
4654 fulldelete($CFG->dataroot.'/'.$course->id);
4656 // Finally trigger the event
4657 $course->context = $coursecontext; // you can not access context in cron event later after course is deleted
4658 $course->options = $options; // not empty if we used any crazy hack
4659 events_trigger('course_content_removed', $course);
4661 return true;
4665 * Change dates in module - used from course reset.
4667 * @global object
4668 * @global object
4669 * @param string $modname forum, assignment, etc
4670 * @param array $fields array of date fields from mod table
4671 * @param int $timeshift time difference
4672 * @param int $courseid
4673 * @return bool success
4675 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
4676 global $CFG, $DB;
4677 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
4679 $return = true;
4680 foreach ($fields as $field) {
4681 $updatesql = "UPDATE {".$modname."}
4682 SET $field = $field + ?
4683 WHERE course=? AND $field<>0 AND $field<>0";
4684 $return = $DB->execute($updatesql, array($timeshift, $courseid)) && $return;
4687 $refreshfunction = $modname.'_refresh_events';
4688 if (function_exists($refreshfunction)) {
4689 $refreshfunction($courseid);
4692 return $return;
4696 * This function will empty a course of user data.
4697 * It will retain the activities and the structure of the course.
4699 * @param object $data an object containing all the settings including courseid (without magic quotes)
4700 * @return array status array of array component, item, error
4702 function reset_course_userdata($data) {
4703 global $CFG, $USER, $DB;
4704 require_once($CFG->libdir.'/gradelib.php');
4705 require_once($CFG->libdir.'/completionlib.php');
4706 require_once($CFG->dirroot.'/group/lib.php');
4708 $data->courseid = $data->id;
4709 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
4711 // calculate the time shift of dates
4712 if (!empty($data->reset_start_date)) {
4713 // time part of course startdate should be zero
4714 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
4715 } else {
4716 $data->timeshift = 0;
4719 // result array: component, item, error
4720 $status = array();
4722 // start the resetting
4723 $componentstr = get_string('general');
4725 // move the course start time
4726 if (!empty($data->reset_start_date) and $data->timeshift) {
4727 // change course start data
4728 $DB->set_field('course', 'startdate', $data->reset_start_date, array('id'=>$data->courseid));
4729 // update all course and group events - do not move activity events
4730 $updatesql = "UPDATE {event}
4731 SET timestart = timestart + ?
4732 WHERE courseid=? AND instance=0";
4733 $DB->execute($updatesql, array($data->timeshift, $data->courseid));
4735 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
4738 if (!empty($data->reset_logs)) {
4739 $DB->delete_records('log', array('course'=>$data->courseid));
4740 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
4743 if (!empty($data->reset_events)) {
4744 $DB->delete_records('event', array('courseid'=>$data->courseid));
4745 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
4748 if (!empty($data->reset_notes)) {
4749 require_once($CFG->dirroot.'/notes/lib.php');
4750 note_delete_all($data->courseid);
4751 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
4754 if (!empty($data->delete_blog_associations)) {
4755 require_once($CFG->dirroot.'/blog/lib.php');
4756 blog_remove_associations_for_course($data->courseid);
4757 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteblogassociations', 'blog'), 'error'=>false);
4760 if (!empty($data->reset_course_completion)) {
4761 // Delete course completion information
4762 $course = $DB->get_record('course', array('id'=>$data->courseid));
4763 $cc = new completion_info($course);
4764 $cc->delete_course_completion_data();
4765 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecoursecompletiondata', 'completion'), 'error'=>false);
4768 $componentstr = get_string('roles');
4770 if (!empty($data->reset_roles_overrides)) {
4771 $children = get_child_contexts($context);
4772 foreach ($children as $child) {
4773 $DB->delete_records('role_capabilities', array('contextid'=>$child->id));
4775 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
4776 //force refresh for logged in users
4777 mark_context_dirty($context->path);
4778 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
4781 if (!empty($data->reset_roles_local)) {
4782 $children = get_child_contexts($context);
4783 foreach ($children as $child) {
4784 role_unassign_all(array('contextid'=>$child->id));
4786 //force refresh for logged in users
4787 mark_context_dirty($context->path);
4788 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
4791 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
4792 $data->unenrolled = array();
4793 if (!empty($data->unenrol_users)) {
4794 $plugins = enrol_get_plugins(true);
4795 $instances = enrol_get_instances($data->courseid, true);
4796 foreach ($instances as $key=>$instance) {
4797 if (!isset($plugins[$instance->enrol])) {
4798 unset($instances[$key]);
4799 continue;
4803 foreach($data->unenrol_users as $withroleid) {
4804 if ($withroleid) {
4805 $sql = "SELECT ue.*
4806 FROM {user_enrolments} ue
4807 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4808 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4809 JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
4810 $params = array('courseid'=>$data->courseid, 'roleid'=>$withroleid, 'courselevel'=>CONTEXT_COURSE);
4812 } else {
4813 // without any role assigned at course context
4814 $sql = "SELECT ue.*
4815 FROM {user_enrolments} ue
4816 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
4817 JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
4818 LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)
4819 WHERE ra.id IS NULL";
4820 $params = array('courseid'=>$data->courseid, 'courselevel'=>CONTEXT_COURSE);
4823 $rs = $DB->get_recordset_sql($sql, $params);
4824 foreach ($rs as $ue) {
4825 if (!isset($instances[$ue->enrolid])) {
4826 continue;
4828 $instance = $instances[$ue->enrolid];
4829 $plugin = $plugins[$instance->enrol];
4830 if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
4831 continue;
4834 $plugin->unenrol_user($instance, $ue->userid);
4835 $data->unenrolled[$ue->userid] = $ue->userid;
4837 $rs->close();
4840 if (!empty($data->unenrolled)) {
4841 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol', 'enrol').' ('.count($data->unenrolled).')', 'error'=>false);
4845 $componentstr = get_string('groups');
4847 // remove all group members
4848 if (!empty($data->reset_groups_members)) {
4849 groups_delete_group_members($data->courseid);
4850 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
4853 // remove all groups
4854 if (!empty($data->reset_groups_remove)) {
4855 groups_delete_groups($data->courseid, false);
4856 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
4859 // remove all grouping members
4860 if (!empty($data->reset_groupings_members)) {
4861 groups_delete_groupings_groups($data->courseid, false);
4862 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
4865 // remove all groupings
4866 if (!empty($data->reset_groupings_remove)) {
4867 groups_delete_groupings($data->courseid, false);
4868 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
4871 // Look in every instance of every module for data to delete
4872 $unsupported_mods = array();
4873 if ($allmods = $DB->get_records('modules') ) {
4874 foreach ($allmods as $mod) {
4875 $modname = $mod->name;
4876 if (!$DB->count_records($modname, array('course'=>$data->courseid))) {
4877 continue; // skip mods with no instances
4879 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
4880 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
4881 if (file_exists($modfile)) {
4882 include_once($modfile);
4883 if (function_exists($moddeleteuserdata)) {
4884 $modstatus = $moddeleteuserdata($data);
4885 if (is_array($modstatus)) {
4886 $status = array_merge($status, $modstatus);
4887 } else {
4888 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
4890 } else {
4891 $unsupported_mods[] = $mod;
4893 } else {
4894 debugging('Missing lib.php in '.$modname.' module!');
4899 // mention unsupported mods
4900 if (!empty($unsupported_mods)) {
4901 foreach($unsupported_mods as $mod) {
4902 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
4907 $componentstr = get_string('gradebook', 'grades');
4908 // reset gradebook
4909 if (!empty($data->reset_gradebook_items)) {
4910 remove_course_grades($data->courseid, false);
4911 grade_grab_course_grades($data->courseid);
4912 grade_regrade_final_grades($data->courseid);
4913 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4915 } else if (!empty($data->reset_gradebook_grades)) {
4916 grade_course_reset($data->courseid);
4917 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4919 // reset comments
4920 if (!empty($data->reset_comments)) {
4921 require_once($CFG->dirroot.'/comment/lib.php');
4922 comment::reset_course_page_comments($context);
4925 return $status;
4929 * Generate an email processing address
4931 * @param int $modid
4932 * @param string $modargs
4933 * @return string Returns email processing address
4935 function generate_email_processing_address($modid,$modargs) {
4936 global $CFG;
4938 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4939 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
4945 * @todo Finish documenting this function
4947 * @global object
4948 * @param string $modargs
4949 * @param string $body Currently unused
4951 function moodle_process_email($modargs,$body) {
4952 global $DB;
4954 // the first char should be an unencoded letter. We'll take this as an action
4955 switch ($modargs{0}) {
4956 case 'B': { // bounce
4957 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4958 if ($user = $DB->get_record("user", array('id'=>$userid), "id,email")) {
4959 // check the half md5 of their email
4960 $md5check = substr(md5($user->email),0,16);
4961 if ($md5check == substr($modargs, -16)) {
4962 set_bounce_count($user);
4964 // else maybe they've already changed it?
4967 break;
4968 // maybe more later?
4972 /// CORRESPONDENCE ////////////////////////////////////////////////
4975 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4977 * @global object
4978 * @param string $action 'get', 'buffer', 'close' or 'flush'
4979 * @return object|null mailer instance if 'get' used or nothing
4981 function get_mailer($action='get') {
4982 global $CFG;
4984 static $mailer = null;
4985 static $counter = 0;
4987 if (!isset($CFG->smtpmaxbulk)) {
4988 $CFG->smtpmaxbulk = 1;
4991 if ($action == 'get') {
4992 $prevkeepalive = false;
4994 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4995 if ($counter < $CFG->smtpmaxbulk and !$mailer->IsError()) {
4996 $counter++;
4997 // reset the mailer
4998 $mailer->Priority = 3;
4999 $mailer->CharSet = 'UTF-8'; // our default
5000 $mailer->ContentType = "text/plain";
5001 $mailer->Encoding = "8bit";
5002 $mailer->From = "root@localhost";
5003 $mailer->FromName = "Root User";
5004 $mailer->Sender = "";
5005 $mailer->Subject = "";
5006 $mailer->Body = "";
5007 $mailer->AltBody = "";
5008 $mailer->ConfirmReadingTo = "";
5010 $mailer->ClearAllRecipients();
5011 $mailer->ClearReplyTos();
5012 $mailer->ClearAttachments();
5013 $mailer->ClearCustomHeaders();
5014 return $mailer;
5017 $prevkeepalive = $mailer->SMTPKeepAlive;
5018 get_mailer('flush');
5021 include_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
5022 $mailer = new moodle_phpmailer();
5024 $counter = 1;
5026 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
5027 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
5028 $mailer->CharSet = 'UTF-8';
5030 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
5031 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
5032 $mailer->LE = "\r\n";
5033 } else {
5034 $mailer->LE = "\n";
5037 if ($CFG->smtphosts == 'qmail') {
5038 $mailer->IsQmail(); // use Qmail system
5040 } else if (empty($CFG->smtphosts)) {
5041 $mailer->IsMail(); // use PHP mail() = sendmail
5043 } else {
5044 $mailer->IsSMTP(); // use SMTP directly
5045 if (!empty($CFG->debugsmtp)) {
5046 $mailer->SMTPDebug = true;
5048 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
5049 $mailer->SMTPSecure = $CFG->smtpsecure; // specify secure connection protocol
5050 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
5052 if ($CFG->smtpuser) { // Use SMTP authentication
5053 $mailer->SMTPAuth = true;
5054 $mailer->Username = $CFG->smtpuser;
5055 $mailer->Password = $CFG->smtppass;
5059 return $mailer;
5062 $nothing = null;
5064 // keep smtp session open after sending
5065 if ($action == 'buffer') {
5066 if (!empty($CFG->smtpmaxbulk)) {
5067 get_mailer('flush');
5068 $m = get_mailer();
5069 if ($m->Mailer == 'smtp') {
5070 $m->SMTPKeepAlive = true;
5073 return $nothing;
5076 // close smtp session, but continue buffering
5077 if ($action == 'flush') {
5078 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5079 if (!empty($mailer->SMTPDebug)) {
5080 echo '<pre>'."\n";
5082 $mailer->SmtpClose();
5083 if (!empty($mailer->SMTPDebug)) {
5084 echo '</pre>';
5087 return $nothing;
5090 // close smtp session, do not buffer anymore
5091 if ($action == 'close') {
5092 if (isset($mailer) and $mailer->Mailer == 'smtp') {
5093 get_mailer('flush');
5094 $mailer->SMTPKeepAlive = false;
5096 $mailer = null; // better force new instance
5097 return $nothing;
5102 * Send an email to a specified user
5104 * @global object
5105 * @global string
5106 * @global string IdentityProvider(IDP) URL user hits to jump to mnet peer.
5107 * @uses SITEID
5108 * @param stdClass $user A {@link $USER} object
5109 * @param stdClass $from A {@link $USER} object
5110 * @param string $subject plain text subject line of the email
5111 * @param string $messagetext plain text version of the message
5112 * @param string $messagehtml complete html version of the message (optional)
5113 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
5114 * @param string $attachname the name of the file (extension indicates MIME)
5115 * @param bool $usetrueaddress determines whether $from email address should
5116 * be sent out. Will be overruled by user profile setting for maildisplay
5117 * @param string $replyto Email address to reply to
5118 * @param string $replytoname Name of reply to recipient
5119 * @param int $wordwrapwidth custom word wrap width, default 79
5120 * @return bool Returns true if mail was sent OK and false if there was an error.
5122 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
5124 global $CFG;
5126 if (empty($user) || empty($user->email)) {
5127 $nulluser = 'User is null or has no email';
5128 error_log($nulluser);
5129 if (CLI_SCRIPT) {
5130 mtrace('Error: lib/moodlelib.php email_to_user(): '.$nulluser);
5132 return false;
5135 if (!empty($user->deleted)) {
5136 // do not mail deleted users
5137 $userdeleted = 'User is deleted';
5138 error_log($userdeleted);
5139 if (CLI_SCRIPT) {
5140 mtrace('Error: lib/moodlelib.php email_to_user(): '.$userdeleted);
5142 return false;
5145 if (!empty($CFG->noemailever)) {
5146 // hidden setting for development sites, set in config.php if needed
5147 $noemail = 'Not sending email due to noemailever config setting';
5148 error_log($noemail);
5149 if (CLI_SCRIPT) {
5150 mtrace('Error: lib/moodlelib.php email_to_user(): '.$noemail);
5152 return true;
5155 if (!empty($CFG->divertallemailsto)) {
5156 $subject = "[DIVERTED {$user->email}] $subject";
5157 $user = clone($user);
5158 $user->email = $CFG->divertallemailsto;
5161 // skip mail to suspended users
5162 if ((isset($user->auth) && $user->auth=='nologin') or (isset($user->suspended) && $user->suspended)) {
5163 return true;
5166 if (!validate_email($user->email)) {
5167 // we can not send emails to invalid addresses - it might create security issue or confuse the mailer
5168 $invalidemail = "User $user->id (".fullname($user).") email ($user->email) is invalid! Not sending.";
5169 error_log($invalidemail);
5170 if (CLI_SCRIPT) {
5171 mtrace('Error: lib/moodlelib.php email_to_user(): '.$invalidemail);
5173 return false;
5176 if (over_bounce_threshold($user)) {
5177 $bouncemsg = "User $user->id (".fullname($user).") is over bounce threshold! Not sending.";
5178 error_log($bouncemsg);
5179 if (CLI_SCRIPT) {
5180 mtrace('Error: lib/moodlelib.php email_to_user(): '.$bouncemsg);
5182 return false;
5185 // If the user is a remote mnet user, parse the email text for URL to the
5186 // wwwroot and modify the url to direct the user's browser to login at their
5187 // home site (identity provider - idp) before hitting the link itself
5188 if (is_mnet_remote_user($user)) {
5189 require_once($CFG->dirroot.'/mnet/lib.php');
5191 $jumpurl = mnet_get_idp_jump_url($user);
5192 $callback = partial('mnet_sso_apply_indirection', $jumpurl);
5194 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
5195 $callback,
5196 $messagetext);
5197 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
5198 $callback,
5199 $messagehtml);
5201 $mail = get_mailer();
5203 if (!empty($mail->SMTPDebug)) {
5204 echo '<pre>' . "\n";
5207 $temprecipients = array();
5208 $tempreplyto = array();
5210 $supportuser = generate_email_supportuser();
5212 // make up an email address for handling bounces
5213 if (!empty($CFG->handlebounces)) {
5214 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
5215 $mail->Sender = generate_email_processing_address(0,$modargs);
5216 } else {
5217 $mail->Sender = $supportuser->email;
5220 if (is_string($from)) { // So we can pass whatever we want if there is need
5221 $mail->From = $CFG->noreplyaddress;
5222 $mail->FromName = $from;
5223 } else if ($usetrueaddress and $from->maildisplay) {
5224 $mail->From = $from->email;
5225 $mail->FromName = fullname($from);
5226 } else {
5227 $mail->From = $CFG->noreplyaddress;
5228 $mail->FromName = fullname($from);
5229 if (empty($replyto)) {
5230 $tempreplyto[] = array($CFG->noreplyaddress, get_string('noreplyname'));
5234 if (!empty($replyto)) {
5235 $tempreplyto[] = array($replyto, $replytoname);
5238 $mail->Subject = substr($subject, 0, 900);
5240 $temprecipients[] = array($user->email, fullname($user));
5242 $mail->WordWrap = $wordwrapwidth; // set word wrap
5244 if (!empty($from->customheaders)) { // Add custom headers
5245 if (is_array($from->customheaders)) {
5246 foreach ($from->customheaders as $customheader) {
5247 $mail->AddCustomHeader($customheader);
5249 } else {
5250 $mail->AddCustomHeader($from->customheaders);
5254 if (!empty($from->priority)) {
5255 $mail->Priority = $from->priority;
5258 if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
5259 $mail->IsHTML(true);
5260 $mail->Encoding = 'quoted-printable'; // Encoding to use
5261 $mail->Body = $messagehtml;
5262 $mail->AltBody = "\n$messagetext\n";
5263 } else {
5264 $mail->IsHTML(false);
5265 $mail->Body = "\n$messagetext\n";
5268 if ($attachment && $attachname) {
5269 if (preg_match( "~\\.\\.~" ,$attachment )) { // Security check for ".." in dir path
5270 $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
5271 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
5272 } else {
5273 require_once($CFG->libdir.'/filelib.php');
5274 $mimetype = mimeinfo('type', $attachname);
5275 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
5279 // Check if the email should be sent in an other charset then the default UTF-8
5280 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
5282 // use the defined site mail charset or eventually the one preferred by the recipient
5283 $charset = $CFG->sitemailcharset;
5284 if (!empty($CFG->allowusermailcharset)) {
5285 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
5286 $charset = $useremailcharset;
5290 // convert all the necessary strings if the charset is supported
5291 $charsets = get_list_of_charsets();
5292 unset($charsets['UTF-8']);
5293 if (in_array($charset, $charsets)) {
5294 $mail->CharSet = $charset;
5295 $mail->FromName = textlib::convert($mail->FromName, 'utf-8', strtolower($charset));
5296 $mail->Subject = textlib::convert($mail->Subject, 'utf-8', strtolower($charset));
5297 $mail->Body = textlib::convert($mail->Body, 'utf-8', strtolower($charset));
5298 $mail->AltBody = textlib::convert($mail->AltBody, 'utf-8', strtolower($charset));
5300 foreach ($temprecipients as $key => $values) {
5301 $temprecipients[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5303 foreach ($tempreplyto as $key => $values) {
5304 $tempreplyto[$key][1] = textlib::convert($values[1], 'utf-8', strtolower($charset));
5309 foreach ($temprecipients as $values) {
5310 $mail->AddAddress($values[0], $values[1]);
5312 foreach ($tempreplyto as $values) {
5313 $mail->AddReplyTo($values[0], $values[1]);
5316 if ($mail->Send()) {
5317 set_send_count($user);
5318 $mail->IsSMTP(); // use SMTP directly
5319 if (!empty($mail->SMTPDebug)) {
5320 echo '</pre>';
5322 return true;
5323 } else {
5324 add_to_log(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: '. $mail->ErrorInfo);
5325 if (CLI_SCRIPT) {
5326 mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
5328 if (!empty($mail->SMTPDebug)) {
5329 echo '</pre>';
5331 return false;
5336 * Generate a signoff for emails based on support settings
5338 * @global object
5339 * @return string
5341 function generate_email_signoff() {
5342 global $CFG;
5344 $signoff = "\n";
5345 if (!empty($CFG->supportname)) {
5346 $signoff .= $CFG->supportname."\n";
5348 if (!empty($CFG->supportemail)) {
5349 $signoff .= $CFG->supportemail."\n";
5351 if (!empty($CFG->supportpage)) {
5352 $signoff .= $CFG->supportpage."\n";
5354 return $signoff;
5358 * Generate a fake user for emails based on support settings
5359 * @global object
5360 * @return object user info
5362 function generate_email_supportuser() {
5363 global $CFG;
5365 static $supportuser;
5367 if (!empty($supportuser)) {
5368 return $supportuser;
5371 $supportuser = new stdClass();
5372 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
5373 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
5374 $supportuser->lastname = '';
5375 $supportuser->maildisplay = true;
5377 return $supportuser;
5382 * Sets specified user's password and send the new password to the user via email.
5384 * @global object
5385 * @global object
5386 * @param user $user A {@link $USER} object
5387 * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
5389 function setnew_password_and_mail($user) {
5390 global $CFG, $DB;
5392 // we try to send the mail in language the user understands,
5393 // unfortunately the filter_string() does not support alternative langs yet
5394 // so multilang will not work properly for site->fullname
5395 $lang = empty($user->lang) ? $CFG->lang : $user->lang;
5397 $site = get_site();
5399 $supportuser = generate_email_supportuser();
5401 $newpassword = generate_password();
5403 $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
5405 $a = new stdClass();
5406 $a->firstname = fullname($user, true);
5407 $a->sitename = format_string($site->fullname);
5408 $a->username = $user->username;
5409 $a->newpassword = $newpassword;
5410 $a->link = $CFG->wwwroot .'/login/';
5411 $a->signoff = generate_email_signoff();
5413 $message = (string)new lang_string('newusernewpasswordtext', '', $a, $lang);
5415 $subject = format_string($site->fullname) .': '. (string)new lang_string('newusernewpasswordsubj', '', $a, $lang);
5417 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5418 return email_to_user($user, $supportuser, $subject, $message);
5423 * Resets specified user's password and send the new password to the user via email.
5425 * @param stdClass $user A {@link $USER} object
5426 * @return bool Returns true if mail was sent OK and false if there was an error.
5428 function reset_password_and_mail($user) {
5429 global $CFG;
5431 $site = get_site();
5432 $supportuser = generate_email_supportuser();
5434 $userauth = get_auth_plugin($user->auth);
5435 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
5436 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
5437 return false;
5440 $newpassword = generate_password();
5442 if (!$userauth->user_update_password($user, $newpassword)) {
5443 print_error("cannotsetpassword");
5446 $a = new stdClass();
5447 $a->firstname = $user->firstname;
5448 $a->lastname = $user->lastname;
5449 $a->sitename = format_string($site->fullname);
5450 $a->username = $user->username;
5451 $a->newpassword = $newpassword;
5452 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
5453 $a->signoff = generate_email_signoff();
5455 $message = get_string('newpasswordtext', '', $a);
5457 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
5459 unset_user_preference('create_password', $user); // prevent cron from generating the password
5461 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5462 return email_to_user($user, $supportuser, $subject, $message);
5467 * Send email to specified user with confirmation text and activation link.
5469 * @global object
5470 * @param user $user A {@link $USER} object
5471 * @return bool Returns true if mail was sent OK and false if there was an error.
5473 function send_confirmation_email($user) {
5474 global $CFG;
5476 $site = get_site();
5477 $supportuser = generate_email_supportuser();
5479 $data = new stdClass();
5480 $data->firstname = fullname($user);
5481 $data->sitename = format_string($site->fullname);
5482 $data->admin = generate_email_signoff();
5484 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
5486 $username = urlencode($user->username);
5487 $username = str_replace('.', '%2E', $username); // prevent problems with trailing dots
5488 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. $username;
5489 $message = get_string('emailconfirmation', '', $data);
5490 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
5492 $user->mailformat = 1; // Always send HTML version as well
5494 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5495 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
5500 * send_password_change_confirmation_email.
5502 * @global object
5503 * @param user $user A {@link $USER} object
5504 * @return bool Returns true if mail was sent OK and false if there was an error.
5506 function send_password_change_confirmation_email($user) {
5507 global $CFG;
5509 $site = get_site();
5510 $supportuser = generate_email_supportuser();
5512 $data = new stdClass();
5513 $data->firstname = $user->firstname;
5514 $data->lastname = $user->lastname;
5515 $data->sitename = format_string($site->fullname);
5516 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
5517 $data->admin = generate_email_signoff();
5519 $message = get_string('emailpasswordconfirmation', '', $data);
5520 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
5522 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5523 return email_to_user($user, $supportuser, $subject, $message);
5528 * send_password_change_info.
5530 * @global object
5531 * @param user $user A {@link $USER} object
5532 * @return bool Returns true if mail was sent OK and false if there was an error.
5534 function send_password_change_info($user) {
5535 global $CFG;
5537 $site = get_site();
5538 $supportuser = generate_email_supportuser();
5539 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
5541 $data = new stdClass();
5542 $data->firstname = $user->firstname;
5543 $data->lastname = $user->lastname;
5544 $data->sitename = format_string($site->fullname);
5545 $data->admin = generate_email_signoff();
5547 $userauth = get_auth_plugin($user->auth);
5549 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
5550 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
5551 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5552 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5553 return email_to_user($user, $supportuser, $subject, $message);
5556 if ($userauth->can_change_password() and $userauth->change_password_url()) {
5557 // we have some external url for password changing
5558 $data->link .= $userauth->change_password_url();
5560 } else {
5561 //no way to change password, sorry
5562 $data->link = '';
5565 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
5566 $message = get_string('emailpasswordchangeinfo', '', $data);
5567 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5568 } else {
5569 $message = get_string('emailpasswordchangeinfofail', '', $data);
5570 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
5573 //directly email rather than using the messaging system to ensure its not routed to a popup or jabber
5574 return email_to_user($user, $supportuser, $subject, $message);
5579 * Check that an email is allowed. It returns an error message if there
5580 * was a problem.
5582 * @global object
5583 * @param string $email Content of email
5584 * @return string|false
5586 function email_is_not_allowed($email) {
5587 global $CFG;
5589 if (!empty($CFG->allowemailaddresses)) {
5590 $allowed = explode(' ', $CFG->allowemailaddresses);
5591 foreach ($allowed as $allowedpattern) {
5592 $allowedpattern = trim($allowedpattern);
5593 if (!$allowedpattern) {
5594 continue;
5596 if (strpos($allowedpattern, '.') === 0) {
5597 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
5598 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5599 return false;
5602 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
5603 return false;
5606 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
5608 } else if (!empty($CFG->denyemailaddresses)) {
5609 $denied = explode(' ', $CFG->denyemailaddresses);
5610 foreach ($denied as $deniedpattern) {
5611 $deniedpattern = trim($deniedpattern);
5612 if (!$deniedpattern) {
5613 continue;
5615 if (strpos($deniedpattern, '.') === 0) {
5616 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
5617 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
5618 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5621 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
5622 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
5627 return false;
5630 /// FILE HANDLING /////////////////////////////////////////////
5633 * Returns local file storage instance
5635 * @return file_storage
5637 function get_file_storage() {
5638 global $CFG;
5640 static $fs = null;
5642 if ($fs) {
5643 return $fs;
5646 require_once("$CFG->libdir/filelib.php");
5648 if (isset($CFG->filedir)) {
5649 $filedir = $CFG->filedir;
5650 } else {
5651 $filedir = $CFG->dataroot.'/filedir';
5654 if (isset($CFG->trashdir)) {
5655 $trashdirdir = $CFG->trashdir;
5656 } else {
5657 $trashdirdir = $CFG->dataroot.'/trashdir';
5660 $fs = new file_storage($filedir, $trashdirdir, "$CFG->tempdir/filestorage", $CFG->directorypermissions, $CFG->filepermissions);
5662 return $fs;
5666 * Returns local file storage instance
5668 * @return file_browser
5670 function get_file_browser() {
5671 global $CFG;
5673 static $fb = null;
5675 if ($fb) {
5676 return $fb;
5679 require_once("$CFG->libdir/filelib.php");
5681 $fb = new file_browser();
5683 return $fb;
5687 * Returns file packer
5689 * @param string $mimetype default application/zip
5690 * @return file_packer
5692 function get_file_packer($mimetype='application/zip') {
5693 global $CFG;
5695 static $fp = array();;
5697 if (isset($fp[$mimetype])) {
5698 return $fp[$mimetype];
5701 switch ($mimetype) {
5702 case 'application/zip':
5703 case 'application/vnd.moodle.backup':
5704 $classname = 'zip_packer';
5705 break;
5706 case 'application/x-tar':
5707 // $classname = 'tar_packer';
5708 // break;
5709 default:
5710 return false;
5713 require_once("$CFG->libdir/filestorage/$classname.php");
5714 $fp[$mimetype] = new $classname();
5716 return $fp[$mimetype];
5720 * Returns current name of file on disk if it exists.
5722 * @param string $newfile File to be verified
5723 * @return string Current name of file on disk if true
5725 function valid_uploaded_file($newfile) {
5726 if (empty($newfile)) {
5727 return '';
5729 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
5730 return $newfile['tmp_name'];
5731 } else {
5732 return '';
5737 * Returns the maximum size for uploading files.
5739 * There are seven possible upload limits:
5740 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
5741 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
5742 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
5743 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
5744 * 5. by the Moodle admin in $CFG->maxbytes
5745 * 6. by the teacher in the current course $course->maxbytes
5746 * 7. by the teacher for the current module, eg $assignment->maxbytes
5748 * These last two are passed to this function as arguments (in bytes).
5749 * Anything defined as 0 is ignored.
5750 * The smallest of all the non-zero numbers is returned.
5752 * @todo Finish documenting this function
5754 * @param int $sizebytes Set maximum size
5755 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5756 * @param int $modulebytes Current module ->maxbytes (in bytes)
5757 * @return int The maximum size for uploading files.
5759 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5761 if (! $filesize = ini_get('upload_max_filesize')) {
5762 $filesize = '5M';
5764 $minimumsize = get_real_size($filesize);
5766 if ($postsize = ini_get('post_max_size')) {
5767 $postsize = get_real_size($postsize);
5768 if ($postsize < $minimumsize) {
5769 $minimumsize = $postsize;
5773 if ($sitebytes and $sitebytes < $minimumsize) {
5774 $minimumsize = $sitebytes;
5777 if ($coursebytes and $coursebytes < $minimumsize) {
5778 $minimumsize = $coursebytes;
5781 if ($modulebytes and $modulebytes < $minimumsize) {
5782 $minimumsize = $modulebytes;
5785 return $minimumsize;
5789 * Returns an array of possible sizes in local language
5791 * Related to {@link get_max_upload_file_size()} - this function returns an
5792 * array of possible sizes in an array, translated to the
5793 * local language.
5795 * @todo Finish documenting this function
5797 * @global object
5798 * @uses SORT_NUMERIC
5799 * @param int $sizebytes Set maximum size
5800 * @param int $coursebytes Current course $course->maxbytes (in bytes)
5801 * @param int $modulebytes Current module ->maxbytes (in bytes)
5802 * @return array
5804 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
5805 global $CFG;
5807 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
5808 return array();
5811 $filesize[intval($maxsize)] = display_size($maxsize);
5813 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
5814 5242880, 10485760, 20971520, 52428800, 104857600);
5816 // Allow maxbytes to be selected if it falls outside the above boundaries
5817 if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
5818 // note: get_real_size() is used in order to prevent problems with invalid values
5819 $sizelist[] = get_real_size($CFG->maxbytes);
5822 foreach ($sizelist as $sizebytes) {
5823 if ($sizebytes < $maxsize) {
5824 $filesize[intval($sizebytes)] = display_size($sizebytes);
5828 krsort($filesize, SORT_NUMERIC);
5830 return $filesize;
5834 * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
5836 * If excludefiles is defined, then that file/directory is ignored
5837 * If getdirs is true, then (sub)directories are included in the output
5838 * If getfiles is true, then files are included in the output
5839 * (at least one of these must be true!)
5841 * @todo Finish documenting this function. Add examples of $excludefile usage.
5843 * @param string $rootdir A given root directory to start from
5844 * @param string|array $excludefile If defined then the specified file/directory is ignored
5845 * @param bool $descend If true then subdirectories are recursed as well
5846 * @param bool $getdirs If true then (sub)directories are included in the output
5847 * @param bool $getfiles If true then files are included in the output
5848 * @return array An array with all the filenames in
5849 * all subdirectories, relative to the given rootdir
5851 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5853 $dirs = array();
5855 if (!$getdirs and !$getfiles) { // Nothing to show
5856 return $dirs;
5859 if (!is_dir($rootdir)) { // Must be a directory
5860 return $dirs;
5863 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5864 return $dirs;
5867 if (!is_array($excludefiles)) {
5868 $excludefiles = array($excludefiles);
5871 while (false !== ($file = readdir($dir))) {
5872 $firstchar = substr($file, 0, 1);
5873 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5874 continue;
5876 $fullfile = $rootdir .'/'. $file;
5877 if (filetype($fullfile) == 'dir') {
5878 if ($getdirs) {
5879 $dirs[] = $file;
5881 if ($descend) {
5882 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5883 foreach ($subdirs as $subdir) {
5884 $dirs[] = $file .'/'. $subdir;
5887 } else if ($getfiles) {
5888 $dirs[] = $file;
5891 closedir($dir);
5893 asort($dirs);
5895 return $dirs;
5900 * Adds up all the files in a directory and works out the size.
5902 * @todo Finish documenting this function
5904 * @param string $rootdir The directory to start from
5905 * @param string $excludefile A file to exclude when summing directory size
5906 * @return int The summed size of all files and subfiles within the root directory
5908 function get_directory_size($rootdir, $excludefile='') {
5909 global $CFG;
5911 // do it this way if we can, it's much faster
5912 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5913 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5914 $output = null;
5915 $return = null;
5916 exec($command,$output,$return);
5917 if (is_array($output)) {
5918 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5922 if (!is_dir($rootdir)) { // Must be a directory
5923 return 0;
5926 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
5927 return 0;
5930 $size = 0;
5932 while (false !== ($file = readdir($dir))) {
5933 $firstchar = substr($file, 0, 1);
5934 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5935 continue;
5937 $fullfile = $rootdir .'/'. $file;
5938 if (filetype($fullfile) == 'dir') {
5939 $size += get_directory_size($fullfile, $excludefile);
5940 } else {
5941 $size += filesize($fullfile);
5944 closedir($dir);
5946 return $size;
5950 * Converts bytes into display form
5952 * @todo Finish documenting this function. Verify return type.
5954 * @staticvar string $gb Localized string for size in gigabytes
5955 * @staticvar string $mb Localized string for size in megabytes
5956 * @staticvar string $kb Localized string for size in kilobytes
5957 * @staticvar string $b Localized string for size in bytes
5958 * @param int $size The size to convert to human readable form
5959 * @return string
5961 function display_size($size) {
5963 static $gb, $mb, $kb, $b;
5965 if (empty($gb)) {
5966 $gb = get_string('sizegb');
5967 $mb = get_string('sizemb');
5968 $kb = get_string('sizekb');
5969 $b = get_string('sizeb');
5972 if ($size >= 1073741824) {
5973 $size = round($size / 1073741824 * 10) / 10 . $gb;
5974 } else if ($size >= 1048576) {
5975 $size = round($size / 1048576 * 10) / 10 . $mb;
5976 } else if ($size >= 1024) {
5977 $size = round($size / 1024 * 10) / 10 . $kb;
5978 } else {
5979 $size = intval($size) .' '. $b; // file sizes over 2GB can not work in 32bit PHP anyway
5981 return $size;
5985 * Cleans a given filename by removing suspicious or troublesome characters
5986 * @see clean_param()
5988 * @uses PARAM_FILE
5989 * @param string $string file name
5990 * @return string cleaned file name
5992 function clean_filename($string) {
5993 return clean_param($string, PARAM_FILE);
5997 /// STRING TRANSLATION ////////////////////////////////////////
6000 * Returns the code for the current language
6002 * @category string
6003 * @return string
6005 function current_language() {
6006 global $CFG, $USER, $SESSION, $COURSE;
6008 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
6009 $return = $COURSE->lang;
6011 } else if (!empty($SESSION->lang)) { // Session language can override other settings
6012 $return = $SESSION->lang;
6014 } else if (!empty($USER->lang)) {
6015 $return = $USER->lang;
6017 } else if (isset($CFG->lang)) {
6018 $return = $CFG->lang;
6020 } else {
6021 $return = 'en';
6024 $return = str_replace('_utf8', '', $return); // Just in case this slipped in from somewhere by accident
6026 return $return;
6030 * Returns parent language of current active language if defined
6032 * @category string
6033 * @uses COURSE
6034 * @uses SESSION
6035 * @param string $lang null means current language
6036 * @return string
6038 function get_parent_language($lang=null) {
6039 global $COURSE, $SESSION;
6041 //let's hack around the current language
6042 if (!empty($lang)) {
6043 $old_course_lang = empty($COURSE->lang) ? '' : $COURSE->lang;
6044 $old_session_lang = empty($SESSION->lang) ? '' : $SESSION->lang;
6045 $COURSE->lang = '';
6046 $SESSION->lang = $lang;
6049 $parentlang = get_string('parentlanguage', 'langconfig');
6050 if ($parentlang === 'en') {
6051 $parentlang = '';
6054 //let's hack around the current language
6055 if (!empty($lang)) {
6056 $COURSE->lang = $old_course_lang;
6057 $SESSION->lang = $old_session_lang;
6060 return $parentlang;
6064 * Returns current string_manager instance.
6066 * The param $forcereload is needed for CLI installer only where the string_manager instance
6067 * must be replaced during the install.php script life time.
6069 * @category string
6070 * @param bool $forcereload shall the singleton be released and new instance created instead?
6071 * @return string_manager
6073 function get_string_manager($forcereload=false) {
6074 global $CFG;
6076 static $singleton = null;
6078 if ($forcereload) {
6079 $singleton = null;
6081 if ($singleton === null) {
6082 if (empty($CFG->early_install_lang)) {
6084 if (empty($CFG->langcacheroot)) {
6085 $langcacheroot = $CFG->cachedir . '/lang';
6086 } else {
6087 $langcacheroot = $CFG->langcacheroot;
6090 if (empty($CFG->langlist)) {
6091 $translist = array();
6092 } else {
6093 $translist = explode(',', $CFG->langlist);
6096 if (empty($CFG->langmenucachefile)) {
6097 $langmenucache = $CFG->cachedir . '/languages';
6098 } else {
6099 $langmenucache = $CFG->langmenucachefile;
6102 $singleton = new core_string_manager($CFG->langotherroot, $CFG->langlocalroot, $langcacheroot,
6103 !empty($CFG->langstringcache), $translist, $langmenucache);
6105 } else {
6106 $singleton = new install_string_manager();
6110 return $singleton;
6115 * Interface for string manager
6117 * Interface describing class which is responsible for getting
6118 * of localised strings from language packs.
6120 * @package core
6121 * @copyright 2010 Petr Skoda (http://skodak.org)
6122 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6124 interface string_manager {
6126 * Get String returns a requested string
6128 * @param string $identifier The identifier of the string to search for
6129 * @param string $component The module the string is associated with
6130 * @param string|object|array $a An object, string or number that can be used
6131 * within translation strings
6132 * @param string $lang moodle translation language, NULL means use current
6133 * @return string The String !
6135 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL);
6138 * Does the string actually exist?
6140 * get_string() is throwing debug warnings, sometimes we do not want them
6141 * or we want to display better explanation of the problem.
6143 * Use with care!
6145 * @param string $identifier The identifier of the string to search for
6146 * @param string $component The module the string is associated with
6147 * @return boot true if exists
6149 public function string_exists($identifier, $component);
6152 * Returns a localised list of all country names, sorted by country keys.
6153 * @param bool $returnall return all or just enabled
6154 * @param string $lang moodle translation language, NULL means use current
6155 * @return array two-letter country code => translated name.
6157 public function get_list_of_countries($returnall = false, $lang = NULL);
6160 * Returns a localised list of languages, sorted by code keys.
6162 * @param string $lang moodle translation language, NULL means use current
6163 * @param string $standard language list standard
6164 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6165 * @return array language code => translated name
6167 public function get_list_of_languages($lang = NULL, $standard = 'iso6392');
6170 * Checks if the translation exists for the language
6172 * @param string $lang moodle translation language code
6173 * @param bool $includeall include also disabled translations
6174 * @return bool true if exists
6176 public function translation_exists($lang, $includeall = true);
6179 * Returns localised list of installed translations
6180 * @param bool $returnall return all or just enabled
6181 * @return array moodle translation code => localised translation name
6183 public function get_list_of_translations($returnall = false);
6186 * Returns localised list of currencies.
6188 * @param string $lang moodle translation language, NULL means use current
6189 * @return array currency code => localised currency name
6191 public function get_list_of_currencies($lang = NULL);
6194 * Load all strings for one component
6195 * @param string $component The module the string is associated with
6196 * @param string $lang
6197 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6198 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6199 * @return array of all string for given component and lang
6201 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false);
6204 * Invalidates all caches, should the implementation use any
6206 public function reset_caches();
6211 * Standard string_manager implementation
6213 * Implements string_manager with getting and printing localised strings
6215 * @package core
6216 * @category string
6217 * @copyright 2010 Petr Skoda (http://skodak.org)
6218 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6220 class core_string_manager implements string_manager {
6221 /** @var string location of all packs except 'en' */
6222 protected $otherroot;
6223 /** @var string location of all lang pack local modifications */
6224 protected $localroot;
6225 /** @var string location of on-disk cache of merged strings */
6226 protected $cacheroot;
6227 /** @var array lang string cache - it will be optimised more later */
6228 protected $cache = array();
6229 /** @var int get_string() counter */
6230 protected $countgetstring = 0;
6231 /** @var int in-memory cache hits counter */
6232 protected $countmemcache = 0;
6233 /** @var int on-disk cache hits counter */
6234 protected $countdiskcache = 0;
6235 /** @var bool use disk cache */
6236 protected $usediskcache;
6237 /** @var array limit list of translations */
6238 protected $translist;
6239 /** @var string location of a file that caches the list of available translations */
6240 protected $menucache;
6243 * Create new instance of string manager
6245 * @param string $otherroot location of downlaoded lang packs - usually $CFG->dataroot/lang
6246 * @param string $localroot usually the same as $otherroot
6247 * @param string $cacheroot usually lang dir in cache folder
6248 * @param bool $usediskcache use disk cache
6249 * @param array $translist limit list of visible translations
6250 * @param string $menucache the location of a file that caches the list of available translations
6252 public function __construct($otherroot, $localroot, $cacheroot, $usediskcache, $translist, $menucache) {
6253 $this->otherroot = $otherroot;
6254 $this->localroot = $localroot;
6255 $this->cacheroot = $cacheroot;
6256 $this->usediskcache = $usediskcache;
6257 $this->translist = $translist;
6258 $this->menucache = $menucache;
6262 * Returns dependencies of current language, en is not included.
6264 * @param string $lang
6265 * @return array all parents, the lang itself is last
6267 public function get_language_dependencies($lang) {
6268 if ($lang === 'en') {
6269 return array();
6271 if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
6272 return array();
6274 $string = array();
6275 include("$this->otherroot/$lang/langconfig.php");
6277 if (empty($string['parentlanguage'])) {
6278 return array($lang);
6279 } else {
6280 $parentlang = $string['parentlanguage'];
6281 unset($string);
6282 return array_merge($this->get_language_dependencies($parentlang), array($lang));
6287 * Load all strings for one component
6289 * @param string $component The module the string is associated with
6290 * @param string $lang
6291 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6292 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6293 * @return array of all string for given component and lang
6295 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6296 global $CFG;
6298 list($plugintype, $pluginname) = normalize_component($component);
6299 if ($plugintype == 'core' and is_null($pluginname)) {
6300 $component = 'core';
6301 } else {
6302 $component = $plugintype . '_' . $pluginname;
6305 if (!$disablecache and !$disablelocal) {
6306 // try in-memory cache first
6307 if (isset($this->cache[$lang][$component])) {
6308 $this->countmemcache++;
6309 return $this->cache[$lang][$component];
6312 // try on-disk cache then
6313 if ($this->usediskcache and file_exists($this->cacheroot . "/$lang/$component.php")) {
6314 $this->countdiskcache++;
6315 include($this->cacheroot . "/$lang/$component.php");
6316 return $this->cache[$lang][$component];
6320 // no cache found - let us merge all possible sources of the strings
6321 if ($plugintype === 'core') {
6322 $file = $pluginname;
6323 if ($file === null) {
6324 $file = 'moodle';
6326 $string = array();
6327 // first load english pack
6328 if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
6329 return array();
6331 include("$CFG->dirroot/lang/en/$file.php");
6332 $originalkeys = array_keys($string);
6333 $originalkeys = array_flip($originalkeys);
6335 // and then corresponding local if present and allowed
6336 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6337 include("$this->localroot/en_local/$file.php");
6339 // now loop through all langs in correct order
6340 $deps = $this->get_language_dependencies($lang);
6341 foreach ($deps as $dep) {
6342 // the main lang string location
6343 if (file_exists("$this->otherroot/$dep/$file.php")) {
6344 include("$this->otherroot/$dep/$file.php");
6346 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6347 include("$this->localroot/{$dep}_local/$file.php");
6351 } else {
6352 if (!$location = get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
6353 return array();
6355 if ($plugintype === 'mod') {
6356 // bloody mod hack
6357 $file = $pluginname;
6358 } else {
6359 $file = $plugintype . '_' . $pluginname;
6361 $string = array();
6362 // first load English pack
6363 if (!file_exists("$location/lang/en/$file.php")) {
6364 //English pack does not exist, so do not try to load anything else
6365 return array();
6367 include("$location/lang/en/$file.php");
6368 $originalkeys = array_keys($string);
6369 $originalkeys = array_flip($originalkeys);
6370 // and then corresponding local english if present
6371 if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
6372 include("$this->localroot/en_local/$file.php");
6375 // now loop through all langs in correct order
6376 $deps = $this->get_language_dependencies($lang);
6377 foreach ($deps as $dep) {
6378 // legacy location - used by contrib only
6379 if (file_exists("$location/lang/$dep/$file.php")) {
6380 include("$location/lang/$dep/$file.php");
6382 // the main lang string location
6383 if (file_exists("$this->otherroot/$dep/$file.php")) {
6384 include("$this->otherroot/$dep/$file.php");
6386 // local customisations
6387 if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
6388 include("$this->localroot/{$dep}_local/$file.php");
6393 // we do not want any extra strings from other languages - everything must be in en lang pack
6394 $string = array_intersect_key($string, $originalkeys);
6396 if (!$disablelocal) {
6397 // now we have a list of strings from all possible sources. put it into both in-memory and on-disk
6398 // caches so we do not need to do all this merging and dependencies resolving again
6399 $this->cache[$lang][$component] = $string;
6400 if ($this->usediskcache) {
6401 check_dir_exists("$this->cacheroot/$lang");
6402 file_put_contents("$this->cacheroot/$lang/$component.php", "<?php \$this->cache['$lang']['$component'] = ".var_export($string, true).";");
6405 return $string;
6409 * Does the string actually exist?
6411 * get_string() is throwing debug warnings, sometimes we do not want them
6412 * or we want to display better explanation of the problem.
6413 * Note: Use with care!
6415 * @param string $identifier The identifier of the string to search for
6416 * @param string $component The module the string is associated with
6417 * @return boot true if exists
6419 public function string_exists($identifier, $component) {
6420 $identifier = clean_param($identifier, PARAM_STRINGID);
6421 if (empty($identifier)) {
6422 return false;
6424 $lang = current_language();
6425 $string = $this->load_component_strings($component, $lang);
6426 return isset($string[$identifier]);
6430 * Get String returns a requested string
6432 * @param string $identifier The identifier of the string to search for
6433 * @param string $component The module the string is associated with
6434 * @param string|object|array $a An object, string or number that can be used
6435 * within translation strings
6436 * @param string $lang moodle translation language, NULL means use current
6437 * @return string The String !
6439 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6440 $this->countgetstring++;
6441 // there are very many uses of these time formating strings without the 'langconfig' component,
6442 // it would not be reasonable to expect that all of them would be converted during 2.0 migration
6443 static $langconfigstrs = array(
6444 'strftimedate' => 1,
6445 'strftimedatefullshort' => 1,
6446 'strftimedateshort' => 1,
6447 'strftimedatetime' => 1,
6448 'strftimedatetimeshort' => 1,
6449 'strftimedaydate' => 1,
6450 'strftimedaydatetime' => 1,
6451 'strftimedayshort' => 1,
6452 'strftimedaytime' => 1,
6453 'strftimemonthyear' => 1,
6454 'strftimerecent' => 1,
6455 'strftimerecentfull' => 1,
6456 'strftimetime' => 1);
6458 if (empty($component)) {
6459 if (isset($langconfigstrs[$identifier])) {
6460 $component = 'langconfig';
6461 } else {
6462 $component = 'moodle';
6466 if ($lang === NULL) {
6467 $lang = current_language();
6470 $string = $this->load_component_strings($component, $lang);
6472 if (!isset($string[$identifier])) {
6473 if ($component === 'pix' or $component === 'core_pix') {
6474 // this component contains only alt tags for emoticons,
6475 // not all of them are supposed to be defined
6476 return '';
6478 if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
6479 // parentlanguage is a special string, undefined means use English if not defined
6480 return 'en';
6482 if ($this->usediskcache) {
6483 // maybe the on-disk cache is dirty - let the last attempt be to find the string in original sources,
6484 // do NOT write the results to disk cache because it may end up in race conditions see MDL-31904
6485 $this->usediskcache = false;
6486 $string = $this->load_component_strings($component, $lang, true);
6487 $this->usediskcache = true;
6489 if (!isset($string[$identifier])) {
6490 // the string is still missing - should be fixed by developer
6491 list($plugintype, $pluginname) = normalize_component($component);
6492 if ($plugintype == 'core') {
6493 $file = "lang/en/{$component}.php";
6494 } else if ($plugintype == 'mod') {
6495 $file = "mod/{$pluginname}/lang/en/{$pluginname}.php";
6496 } else {
6497 $path = get_plugin_directory($plugintype, $pluginname);
6498 $file = "{$path}/lang/en/{$plugintype}_{$pluginname}.php";
6500 debugging("Invalid get_string() identifier: '{$identifier}' or component '{$component}'. " .
6501 "Perhaps you are missing \$string['{$identifier}'] = ''; in {$file}?", DEBUG_DEVELOPER);
6502 return "[[$identifier]]";
6506 $string = $string[$identifier];
6508 if ($a !== NULL) {
6509 // Process array's and objects (except lang_strings)
6510 if (is_array($a) or (is_object($a) && !($a instanceof lang_string))) {
6511 $a = (array)$a;
6512 $search = array();
6513 $replace = array();
6514 foreach ($a as $key=>$value) {
6515 if (is_int($key)) {
6516 // we do not support numeric keys - sorry!
6517 continue;
6519 if (is_array($value) or (is_object($value) && !($value instanceof lang_string))) {
6520 // we support just string or lang_string as value
6521 continue;
6523 $search[] = '{$a->'.$key.'}';
6524 $replace[] = (string)$value;
6526 if ($search) {
6527 $string = str_replace($search, $replace, $string);
6529 } else {
6530 $string = str_replace('{$a}', (string)$a, $string);
6534 return $string;
6538 * Returns information about the string_manager performance
6540 * @return array
6542 public function get_performance_summary() {
6543 return array(array(
6544 'langcountgetstring' => $this->countgetstring,
6545 'langcountmemcache' => $this->countmemcache,
6546 'langcountdiskcache' => $this->countdiskcache,
6547 ), array(
6548 'langcountgetstring' => 'get_string calls',
6549 'langcountmemcache' => 'strings mem cache hits',
6550 'langcountdiskcache' => 'strings disk cache hits',
6555 * Returns a localised list of all country names, sorted by localised name.
6557 * @param bool $returnall return all or just enabled
6558 * @param string $lang moodle translation language, NULL means use current
6559 * @return array two-letter country code => translated name.
6561 public function get_list_of_countries($returnall = false, $lang = NULL) {
6562 global $CFG;
6564 if ($lang === NULL) {
6565 $lang = current_language();
6568 $countries = $this->load_component_strings('core_countries', $lang);
6569 collatorlib::asort($countries);
6570 if (!$returnall and !empty($CFG->allcountrycodes)) {
6571 $enabled = explode(',', $CFG->allcountrycodes);
6572 $return = array();
6573 foreach ($enabled as $c) {
6574 if (isset($countries[$c])) {
6575 $return[$c] = $countries[$c];
6578 return $return;
6581 return $countries;
6585 * Returns a localised list of languages, sorted by code keys.
6587 * @param string $lang moodle translation language, NULL means use current
6588 * @param string $standard language list standard
6589 * - iso6392: three-letter language code (ISO 639-2/T) => translated name
6590 * - iso6391: two-letter langauge code (ISO 639-1) => translated name
6591 * @return array language code => translated name
6593 public function get_list_of_languages($lang = NULL, $standard = 'iso6391') {
6594 if ($lang === NULL) {
6595 $lang = current_language();
6598 if ($standard === 'iso6392') {
6599 $langs = $this->load_component_strings('core_iso6392', $lang);
6600 ksort($langs);
6601 return $langs;
6603 } else if ($standard === 'iso6391') {
6604 $langs2 = $this->load_component_strings('core_iso6392', $lang);
6605 static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
6606 'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
6607 'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
6608 'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
6609 'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
6610 'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
6611 'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
6612 'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
6613 'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
6614 'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
6615 'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
6616 'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
6617 'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
6618 'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
6619 'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
6620 'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
6621 'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
6622 $langs1 = array();
6623 foreach ($mapping as $c2=>$c1) {
6624 $langs1[$c1] = $langs2[$c2];
6626 ksort($langs1);
6627 return $langs1;
6629 } else {
6630 debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
6633 return array();
6637 * Checks if the translation exists for the language
6639 * @param string $lang moodle translation language code
6640 * @param bool $includeall include also disabled translations
6641 * @return bool true if exists
6643 public function translation_exists($lang, $includeall = true) {
6645 if (strpos($lang, '_local') !== false) {
6646 // _local packs are not real translations
6647 return false;
6649 if (!$includeall and !empty($this->translist)) {
6650 if (!in_array($lang, $this->translist)) {
6651 return false;
6654 if ($lang === 'en') {
6655 // part of distribution
6656 return true;
6658 return file_exists("$this->otherroot/$lang/langconfig.php");
6662 * Returns localised list of installed translations
6664 * @param bool $returnall return all or just enabled
6665 * @return array moodle translation code => localised translation name
6667 public function get_list_of_translations($returnall = false) {
6668 global $CFG;
6670 $languages = array();
6672 if (!empty($CFG->langcache) and is_readable($this->menucache)) {
6673 // try to re-use the cached list of all available languages
6674 $cachedlist = json_decode(file_get_contents($this->menucache), true);
6676 if (is_array($cachedlist) and !empty($cachedlist)) {
6677 // the cache file is restored correctly
6679 if (!$returnall and !empty($this->translist)) {
6680 // return just enabled translations
6681 foreach ($cachedlist as $langcode => $langname) {
6682 if (in_array($langcode, $this->translist)) {
6683 $languages[$langcode] = $langname;
6686 return $languages;
6688 } else {
6689 // return all translations
6690 return $cachedlist;
6695 // the cached list of languages is not available, let us populate the list
6697 if (!$returnall and !empty($this->translist)) {
6698 // return only some translations
6699 foreach ($this->translist as $lang) {
6700 $lang = trim($lang); //Just trim spaces to be a bit more permissive
6701 if (strstr($lang, '_local') !== false) {
6702 continue;
6704 if (strstr($lang, '_utf8') !== false) {
6705 continue;
6707 if ($lang !== 'en' and !file_exists("$this->otherroot/$lang/langconfig.php")) {
6708 // some broken or missing lang - can not switch to it anyway
6709 continue;
6711 $string = $this->load_component_strings('langconfig', $lang);
6712 if (!empty($string['thislanguage'])) {
6713 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6715 unset($string);
6718 } else {
6719 // return all languages available in system
6720 $langdirs = get_list_of_plugins('', '', $this->otherroot);
6722 $langdirs = array_merge($langdirs, array("$CFG->dirroot/lang/en"=>'en'));
6723 // Sort all
6725 // Loop through all langs and get info
6726 foreach ($langdirs as $lang) {
6727 if (strstr($lang, '_local') !== false) {
6728 continue;
6730 if (strstr($lang, '_utf8') !== false) {
6731 continue;
6733 $string = $this->load_component_strings('langconfig', $lang);
6734 if (!empty($string['thislanguage'])) {
6735 $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
6737 unset($string);
6740 if (!empty($CFG->langcache) and !empty($this->menucache)) {
6741 // cache the list so that it can be used next time
6742 collatorlib::asort($languages);
6743 check_dir_exists(dirname($this->menucache), true, true);
6744 file_put_contents($this->menucache, json_encode($languages));
6748 collatorlib::asort($languages);
6750 return $languages;
6754 * Returns localised list of currencies.
6756 * @param string $lang moodle translation language, NULL means use current
6757 * @return array currency code => localised currency name
6759 public function get_list_of_currencies($lang = NULL) {
6760 if ($lang === NULL) {
6761 $lang = current_language();
6764 $currencies = $this->load_component_strings('core_currencies', $lang);
6765 asort($currencies);
6767 return $currencies;
6771 * Clears both in-memory and on-disk caches
6773 public function reset_caches() {
6774 global $CFG;
6775 require_once("$CFG->libdir/filelib.php");
6777 // clear the on-disk disk with aggregated string files
6778 fulldelete($this->cacheroot);
6780 // clear the in-memory cache of loaded strings
6781 $this->cache = array();
6783 // clear the cache containing the list of available translations
6784 // and re-populate it again
6785 fulldelete($this->menucache);
6786 $this->get_list_of_translations(true);
6792 * Fetches minimum strings for installation
6794 * Minimalistic string fetching implementation
6795 * that is used in installer before we fetch the wanted
6796 * language pack from moodle.org lang download site.
6798 * @package core
6799 * @copyright 2010 Petr Skoda (http://skodak.org)
6800 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6802 class install_string_manager implements string_manager {
6803 /** @var string location of pre-install packs for all langs */
6804 protected $installroot;
6807 * Crate new instance of install string manager
6809 public function __construct() {
6810 global $CFG;
6811 $this->installroot = "$CFG->dirroot/install/lang";
6815 * Load all strings for one component
6816 * @param string $component The module the string is associated with
6817 * @param string $lang
6818 * @param bool $disablecache Do not use caches, force fetching the strings from sources
6819 * @param bool $disablelocal Do not use customized strings in xx_local language packs
6820 * @return array of all string for given component and lang
6822 public function load_component_strings($component, $lang, $disablecache=false, $disablelocal=false) {
6823 // not needed in installer
6824 return array();
6828 * Does the string actually exist?
6830 * get_string() is throwing debug warnings, sometimes we do not want them
6831 * or we want to display better explanation of the problem.
6833 * Use with care!
6835 * @param string $identifier The identifier of the string to search for
6836 * @param string $component The module the string is associated with
6837 * @return boot true if exists
6839 public function string_exists($identifier, $component) {
6840 $identifier = clean_param($identifier, PARAM_STRINGID);
6841 if (empty($identifier)) {
6842 return false;
6844 // simple old style hack ;)
6845 $str = get_string($identifier, $component);
6846 return (strpos($str, '[[') === false);
6850 * Get String returns a requested string
6852 * @param string $identifier The identifier of the string to search for
6853 * @param string $component The module the string is associated with
6854 * @param string|object|array $a An object, string or number that can be used
6855 * within translation strings
6856 * @param string $lang moodle translation language, NULL means use current
6857 * @return string The String !
6859 public function get_string($identifier, $component = '', $a = NULL, $lang = NULL) {
6860 if (!$component) {
6861 $component = 'moodle';
6864 if ($lang === NULL) {
6865 $lang = current_language();
6868 //get parent lang
6869 $parent = '';
6870 if ($lang !== 'en' and $identifier !== 'parentlanguage' and $component !== 'langconfig') {
6871 if (file_exists("$this->installroot/$lang/langconfig.php")) {
6872 $string = array();
6873 include("$this->installroot/$lang/langconfig.php");
6874 if (isset($string['parentlanguage'])) {
6875 $parent = $string['parentlanguage'];
6877 unset($string);
6881 // include en string first
6882 if (!file_exists("$this->installroot/en/$component.php")) {
6883 return "[[$identifier]]";
6885 $string = array();
6886 include("$this->installroot/en/$component.php");
6888 // now override en with parent if defined
6889 if ($parent and $parent !== 'en' and file_exists("$this->installroot/$parent/$component.php")) {
6890 include("$this->installroot/$parent/$component.php");
6893 // finally override with requested language
6894 if ($lang !== 'en' and file_exists("$this->installroot/$lang/$component.php")) {
6895 include("$this->installroot/$lang/$component.php");
6898 if (!isset($string[$identifier])) {
6899 return "[[$identifier]]";
6902 $string = $string[$identifier];
6904 if ($a !== NULL) {
6905 if (is_object($a) or is_array($a)) {
6906 $a = (array)$a;
6907 $search = array();
6908 $replace = array();
6909 foreach ($a as $key=>$value) {
6910 if (is_int($key)) {
6911 // we do not support numeric keys - sorry!
6912 continue;
6914 $search[] = '{$a->'.$key.'}';
6915 $replace[] = (string)$value;
6917 if ($search) {
6918 $string = str_replace($search, $replace, $string);
6920 } else {
6921 $string = str_replace('{$a}', (string)$a, $string);
6925 return $string;
6929 * Returns a localised list of all country names, sorted by country keys.
6931 * @param bool $returnall return all or just enabled
6932 * @param string $lang moodle translation language, NULL means use current
6933 * @return array two-letter country code => translated name.
6935 public function get_list_of_countries($returnall = false, $lang = NULL) {
6936 //not used in installer
6937 return array();
6941 * Returns a localised list of languages, sorted by code keys.
6943 * @param string $lang moodle translation language, NULL means use current
6944 * @param string $standard language list standard
6945 * iso6392: three-letter language code (ISO 639-2/T) => translated name.
6946 * @return array language code => translated name
6948 public function get_list_of_languages($lang = NULL, $standard = 'iso6392') {
6949 //not used in installer
6950 return array();
6954 * Checks if the translation exists for the language
6956 * @param string $lang moodle translation language code
6957 * @param bool $includeall include also disabled translations
6958 * @return bool true if exists
6960 public function translation_exists($lang, $includeall = true) {
6961 return file_exists($this->installroot.'/'.$lang.'/langconfig.php');
6965 * Returns localised list of installed translations
6966 * @param bool $returnall return all or just enabled
6967 * @return array moodle translation code => localised translation name
6969 public function get_list_of_translations($returnall = false) {
6970 // return all is ignored here - we need to know all langs in installer
6971 $languages = array();
6972 // Get raw list of lang directories
6973 $langdirs = get_list_of_plugins('install/lang');
6974 asort($langdirs);
6975 // Get some info from each lang
6976 foreach ($langdirs as $lang) {
6977 if (file_exists($this->installroot.'/'.$lang.'/langconfig.php')) {
6978 $string = array();
6979 include($this->installroot.'/'.$lang.'/langconfig.php');
6980 if (!empty($string['thislanguage'])) {
6981 $languages[$lang] = $string['thislanguage'].' ('.$lang.')';
6985 // Return array
6986 return $languages;
6990 * Returns localised list of currencies.
6992 * @param string $lang moodle translation language, NULL means use current
6993 * @return array currency code => localised currency name
6995 public function get_list_of_currencies($lang = NULL) {
6996 // not used in installer
6997 return array();
7001 * This implementation does not use any caches
7003 public function reset_caches() {}
7008 * Returns a localized string.
7010 * Returns the translated string specified by $identifier as
7011 * for $module. Uses the same format files as STphp.
7012 * $a is an object, string or number that can be used
7013 * within translation strings
7015 * eg 'hello {$a->firstname} {$a->lastname}'
7016 * or 'hello {$a}'
7018 * If you would like to directly echo the localized string use
7019 * the function {@link print_string()}
7021 * Example usage of this function involves finding the string you would
7022 * like a local equivalent of and using its identifier and module information
7023 * to retrieve it.<br/>
7024 * If you open moodle/lang/en/moodle.php and look near line 278
7025 * you will find a string to prompt a user for their word for 'course'
7026 * <code>
7027 * $string['course'] = 'Course';
7028 * </code>
7029 * So if you want to display the string 'Course'
7030 * in any language that supports it on your site
7031 * you just need to use the identifier 'course'
7032 * <code>
7033 * $mystring = '<strong>'. get_string('course') .'</strong>';
7034 * or
7035 * </code>
7036 * If the string you want is in another file you'd take a slightly
7037 * different approach. Looking in moodle/lang/en/calendar.php you find
7038 * around line 75:
7039 * <code>
7040 * $string['typecourse'] = 'Course event';
7041 * </code>
7042 * If you want to display the string "Course event" in any language
7043 * supported you would use the identifier 'typecourse' and the module 'calendar'
7044 * (because it is in the file calendar.php):
7045 * <code>
7046 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
7047 * </code>
7049 * As a last resort, should the identifier fail to map to a string
7050 * the returned string will be [[ $identifier ]]
7052 * In Moodle 2.3 there is a new argument to this function $lazyload.
7053 * Setting $lazyload to true causes get_string to return a lang_string object
7054 * rather than the string itself. The fetching of the string is then put off until
7055 * the string object is first used. The object can be used by calling it's out
7056 * method or by casting the object to a string, either directly e.g.
7057 * (string)$stringobject
7058 * or indirectly by using the string within another string or echoing it out e.g.
7059 * echo $stringobject
7060 * return "<p>{$stringobject}</p>";
7061 * It is worth noting that using $lazyload and attempting to use the string as an
7062 * array key will cause a fatal error as objects cannot be used as array keys.
7063 * But you should never do that anyway!
7064 * For more information {@see lang_string}
7066 * @category string
7067 * @param string $identifier The key identifier for the localized string
7068 * @param string $component The module where the key identifier is stored,
7069 * usually expressed as the filename in the language pack without the
7070 * .php on the end but can also be written as mod/forum or grade/export/xls.
7071 * If none is specified then moodle.php is used.
7072 * @param string|object|array $a An object, string or number that can be used
7073 * within translation strings
7074 * @param bool $lazyload If set to true a string object is returned instead of
7075 * the string itself. The string then isn't calculated until it is first used.
7076 * @return string The localized string.
7078 function get_string($identifier, $component = '', $a = NULL, $lazyload = false) {
7079 global $CFG;
7081 // If the lazy load argument has been supplied return a lang_string object
7082 // instead.
7083 // We need to make sure it is true (and a bool) as you will see below there
7084 // used to be a forth argument at one point.
7085 if ($lazyload === true) {
7086 return new lang_string($identifier, $component, $a);
7089 $identifier = clean_param($identifier, PARAM_STRINGID);
7090 if (empty($identifier)) {
7091 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please fix your get_string() call and string definition');
7094 // There is now a forth argument again, this time it is a boolean however so
7095 // we can still check for the old extralocations parameter.
7096 if (!is_bool($lazyload) && !empty($lazyload)) {
7097 debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
7100 if (strpos($component, '/') !== false) {
7101 debugging('The module name you passed to get_string is the deprecated format ' .
7102 'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.' , DEBUG_DEVELOPER);
7103 $componentpath = explode('/', $component);
7105 switch ($componentpath[0]) {
7106 case 'mod':
7107 $component = $componentpath[1];
7108 break;
7109 case 'blocks':
7110 case 'block':
7111 $component = 'block_'.$componentpath[1];
7112 break;
7113 case 'enrol':
7114 $component = 'enrol_'.$componentpath[1];
7115 break;
7116 case 'format':
7117 $component = 'format_'.$componentpath[1];
7118 break;
7119 case 'grade':
7120 $component = 'grade'.$componentpath[1].'_'.$componentpath[2];
7121 break;
7125 $result = get_string_manager()->get_string($identifier, $component, $a);
7127 // Debugging feature lets you display string identifier and component
7128 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
7129 $result .= ' {' . $identifier . '/' . $component . '}';
7131 return $result;
7135 * Converts an array of strings to their localized value.
7137 * @param array $array An array of strings
7138 * @param string $component The language module that these strings can be found in.
7139 * @return stdClass translated strings.
7141 function get_strings($array, $component = '') {
7142 $string = new stdClass;
7143 foreach ($array as $item) {
7144 $string->$item = get_string($item, $component);
7146 return $string;
7150 * Prints out a translated string.
7152 * Prints out a translated string using the return value from the {@link get_string()} function.
7154 * Example usage of this function when the string is in the moodle.php file:<br/>
7155 * <code>
7156 * echo '<strong>';
7157 * print_string('course');
7158 * echo '</strong>';
7159 * </code>
7161 * Example usage of this function when the string is not in the moodle.php file:<br/>
7162 * <code>
7163 * echo '<h1>';
7164 * print_string('typecourse', 'calendar');
7165 * echo '</h1>';
7166 * </code>
7168 * @category string
7169 * @param string $identifier The key identifier for the localized string
7170 * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
7171 * @param string|object|array $a An object, string or number that can be used within translation strings
7173 function print_string($identifier, $component = '', $a = NULL) {
7174 echo get_string($identifier, $component, $a);
7178 * Returns a list of charset codes
7180 * Returns a list of charset codes. It's hardcoded, so they should be added manually
7181 * (checking that such charset is supported by the texlib library!)
7183 * @return array And associative array with contents in the form of charset => charset
7185 function get_list_of_charsets() {
7187 $charsets = array(
7188 'EUC-JP' => 'EUC-JP',
7189 'ISO-2022-JP'=> 'ISO-2022-JP',
7190 'ISO-8859-1' => 'ISO-8859-1',
7191 'SHIFT-JIS' => 'SHIFT-JIS',
7192 'GB2312' => 'GB2312',
7193 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
7194 'UTF-8' => 'UTF-8');
7196 asort($charsets);
7198 return $charsets;
7202 * Returns a list of valid and compatible themes
7204 * @return array
7206 function get_list_of_themes() {
7207 global $CFG;
7209 $themes = array();
7211 if (!empty($CFG->themelist)) { // use admin's list of themes
7212 $themelist = explode(',', $CFG->themelist);
7213 } else {
7214 $themelist = array_keys(get_plugin_list("theme"));
7217 foreach ($themelist as $key => $themename) {
7218 $theme = theme_config::load($themename);
7219 $themes[$themename] = $theme;
7222 collatorlib::asort_objects_by_method($themes, 'get_theme_name');
7224 return $themes;
7228 * Returns a list of timezones in the current language
7230 * @global object
7231 * @global object
7232 * @return array
7234 function get_list_of_timezones() {
7235 global $CFG, $DB;
7237 static $timezones;
7239 if (!empty($timezones)) { // This function has been called recently
7240 return $timezones;
7243 $timezones = array();
7245 if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
7246 foreach($rawtimezones as $timezone) {
7247 if (!empty($timezone->name)) {
7248 if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
7249 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
7250 } else {
7251 $timezones[$timezone->name] = $timezone->name;
7253 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
7254 $timezones[$timezone->name] = $timezone->name;
7260 asort($timezones);
7262 for ($i = -13; $i <= 13; $i += .5) {
7263 $tzstring = 'UTC';
7264 if ($i < 0) {
7265 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
7266 } else if ($i > 0) {
7267 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
7268 } else {
7269 $timezones[sprintf("%.1f", $i)] = $tzstring;
7273 return $timezones;
7277 * Factory function for emoticon_manager
7279 * @return emoticon_manager singleton
7281 function get_emoticon_manager() {
7282 static $singleton = null;
7284 if (is_null($singleton)) {
7285 $singleton = new emoticon_manager();
7288 return $singleton;
7292 * Provides core support for plugins that have to deal with
7293 * emoticons (like HTML editor or emoticon filter).
7295 * Whenever this manager mentiones 'emoticon object', the following data
7296 * structure is expected: stdClass with properties text, imagename, imagecomponent,
7297 * altidentifier and altcomponent
7299 * @see admin_setting_emoticons
7301 class emoticon_manager {
7304 * Returns the currently enabled emoticons
7306 * @return array of emoticon objects
7308 public function get_emoticons() {
7309 global $CFG;
7311 if (empty($CFG->emoticons)) {
7312 return array();
7315 $emoticons = $this->decode_stored_config($CFG->emoticons);
7317 if (!is_array($emoticons)) {
7318 // something is wrong with the format of stored setting
7319 debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
7320 return array();
7323 return $emoticons;
7327 * Converts emoticon object into renderable pix_emoticon object
7329 * @param stdClass $emoticon emoticon object
7330 * @param array $attributes explicit HTML attributes to set
7331 * @return pix_emoticon
7333 public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array()) {
7334 $stringmanager = get_string_manager();
7335 if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
7336 $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
7337 } else {
7338 $alt = s($emoticon->text);
7340 return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
7344 * Encodes the array of emoticon objects into a string storable in config table
7346 * @see self::decode_stored_config()
7347 * @param array $emoticons array of emtocion objects
7348 * @return string
7350 public function encode_stored_config(array $emoticons) {
7351 return json_encode($emoticons);
7355 * Decodes the string into an array of emoticon objects
7357 * @see self::encode_stored_config()
7358 * @param string $encoded
7359 * @return string|null
7361 public function decode_stored_config($encoded) {
7362 $decoded = json_decode($encoded);
7363 if (!is_array($decoded)) {
7364 return null;
7366 return $decoded;
7370 * Returns default set of emoticons supported by Moodle
7372 * @return array of sdtClasses
7374 public function default_emoticons() {
7375 return array(
7376 $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
7377 $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
7378 $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
7379 $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
7380 $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
7381 $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
7382 $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
7383 $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
7384 $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
7385 $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
7386 $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
7387 $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
7388 $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
7389 $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
7390 $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
7391 $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
7392 $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
7393 $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
7394 $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
7395 $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
7396 $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
7397 $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
7398 $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
7399 $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
7400 $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
7401 $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
7402 $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
7403 $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
7404 $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
7405 $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
7410 * Helper method preparing the stdClass with the emoticon properties
7412 * @param string|array $text or array of strings
7413 * @param string $imagename to be used by {@see pix_emoticon}
7414 * @param string $altidentifier alternative string identifier, null for no alt
7415 * @param array $altcomponent where the alternative string is defined
7416 * @param string $imagecomponent to be used by {@see pix_emoticon}
7417 * @return stdClass
7419 protected function prepare_emoticon_object($text, $imagename, $altidentifier = null, $altcomponent = 'core_pix', $imagecomponent = 'core') {
7420 return (object)array(
7421 'text' => $text,
7422 'imagename' => $imagename,
7423 'imagecomponent' => $imagecomponent,
7424 'altidentifier' => $altidentifier,
7425 'altcomponent' => $altcomponent,
7430 /// ENCRYPTION ////////////////////////////////////////////////
7433 * rc4encrypt
7435 * Please note that in this version of moodle that the default for rc4encryption is
7436 * using the slightly more secure password key. There may be an issue when upgrading
7437 * from an older version of moodle.
7439 * @todo MDL-31836 Remove the old password key in version 2.4
7440 * Code also needs to be changed in sessionlib.php
7441 * @see get_moodle_cookie()
7442 * @see set_moodle_cookie()
7444 * @param string $data Data to encrypt.
7445 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7446 * @return string The now encrypted data.
7448 function rc4encrypt($data, $usesecurekey = true) {
7449 if (!$usesecurekey) {
7450 $passwordkey = 'nfgjeingjk';
7451 } else {
7452 $passwordkey = get_site_identifier();
7454 return endecrypt($passwordkey, $data, '');
7458 * rc4decrypt
7460 * Please note that in this version of moodle that the default for rc4encryption is
7461 * using the slightly more secure password key. There may be an issue when upgrading
7462 * from an older version of moodle.
7464 * @todo MDL-31836 Remove the old password key in version 2.4
7465 * Code also needs to be changed in sessionlib.php
7466 * @see get_moodle_cookie()
7467 * @see set_moodle_cookie()
7469 * @param string $data Data to decrypt.
7470 * @param bool $usesecurekey Lets us know if we are using the old or new secure password key.
7471 * @return string The now decrypted data.
7473 function rc4decrypt($data, $usesecurekey = true) {
7474 if (!$usesecurekey) {
7475 $passwordkey = 'nfgjeingjk';
7476 } else {
7477 $passwordkey = get_site_identifier();
7479 return endecrypt($passwordkey, $data, 'de');
7483 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
7485 * @todo Finish documenting this function
7487 * @param string $pwd The password to use when encrypting or decrypting
7488 * @param string $data The data to be decrypted/encrypted
7489 * @param string $case Either 'de' for decrypt or '' for encrypt
7490 * @return string
7492 function endecrypt ($pwd, $data, $case) {
7494 if ($case == 'de') {
7495 $data = urldecode($data);
7498 $key[] = '';
7499 $box[] = '';
7500 $temp_swap = '';
7501 $pwd_length = 0;
7503 $pwd_length = strlen($pwd);
7505 for ($i = 0; $i <= 255; $i++) {
7506 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
7507 $box[$i] = $i;
7510 $x = 0;
7512 for ($i = 0; $i <= 255; $i++) {
7513 $x = ($x + $box[$i] + $key[$i]) % 256;
7514 $temp_swap = $box[$i];
7515 $box[$i] = $box[$x];
7516 $box[$x] = $temp_swap;
7519 $temp = '';
7520 $k = '';
7522 $cipherby = '';
7523 $cipher = '';
7525 $a = 0;
7526 $j = 0;
7528 for ($i = 0; $i < strlen($data); $i++) {
7529 $a = ($a + 1) % 256;
7530 $j = ($j + $box[$a]) % 256;
7531 $temp = $box[$a];
7532 $box[$a] = $box[$j];
7533 $box[$j] = $temp;
7534 $k = $box[(($box[$a] + $box[$j]) % 256)];
7535 $cipherby = ord(substr($data, $i, 1)) ^ $k;
7536 $cipher .= chr($cipherby);
7539 if ($case == 'de') {
7540 $cipher = urldecode(urlencode($cipher));
7541 } else {
7542 $cipher = urlencode($cipher);
7545 return $cipher;
7548 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
7551 * Returns the exact absolute path to plugin directory.
7553 * @param string $plugintype type of plugin
7554 * @param string $name name of the plugin
7555 * @return string full path to plugin directory; NULL if not found
7557 function get_plugin_directory($plugintype, $name) {
7558 global $CFG;
7560 if ($plugintype === '') {
7561 $plugintype = 'mod';
7564 $types = get_plugin_types(true);
7565 if (!array_key_exists($plugintype, $types)) {
7566 return NULL;
7568 $name = clean_param($name, PARAM_SAFEDIR); // just in case ;-)
7570 if (!empty($CFG->themedir) and $plugintype === 'theme') {
7571 if (!is_dir($types['theme'] . '/' . $name)) {
7572 // ok, so the theme is supposed to be in the $CFG->themedir
7573 return $CFG->themedir . '/' . $name;
7577 return $types[$plugintype].'/'.$name;
7581 * Return exact absolute path to a plugin directory.
7583 * @param string $component name such as 'moodle', 'mod_forum'
7584 * @return string full path to component directory; NULL if not found
7586 function get_component_directory($component) {
7587 global $CFG;
7589 list($type, $plugin) = normalize_component($component);
7591 if ($type === 'core') {
7592 if ($plugin === NULL ) {
7593 $path = $CFG->libdir;
7594 } else {
7595 $subsystems = get_core_subsystems();
7596 if (isset($subsystems[$plugin])) {
7597 $path = $CFG->dirroot.'/'.$subsystems[$plugin];
7598 } else {
7599 $path = NULL;
7603 } else {
7604 $path = get_plugin_directory($type, $plugin);
7607 return $path;
7611 * Normalize the component name using the "frankenstyle" names.
7612 * @param string $component
7613 * @return array $type+$plugin elements
7615 function normalize_component($component) {
7616 if ($component === 'moodle' or $component === 'core') {
7617 $type = 'core';
7618 $plugin = NULL;
7620 } else if (strpos($component, '_') === false) {
7621 $subsystems = get_core_subsystems();
7622 if (array_key_exists($component, $subsystems)) {
7623 $type = 'core';
7624 $plugin = $component;
7625 } else {
7626 // everything else is a module
7627 $type = 'mod';
7628 $plugin = $component;
7631 } else {
7632 list($type, $plugin) = explode('_', $component, 2);
7633 $plugintypes = get_plugin_types(false);
7634 if ($type !== 'core' and !array_key_exists($type, $plugintypes)) {
7635 $type = 'mod';
7636 $plugin = $component;
7640 return array($type, $plugin);
7644 * List all core subsystems and their location
7646 * This is a whitelist of components that are part of the core and their
7647 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
7648 * plugin is not listed here and it does not have proper plugintype prefix,
7649 * then it is considered as course activity module.
7651 * The location is dirroot relative path. NULL means there is no special
7652 * directory for this subsystem. If the location is set, the subsystem's
7653 * renderer.php is expected to be there.
7655 * @return array of (string)name => (string|null)location
7657 function get_core_subsystems() {
7658 global $CFG;
7660 static $info = null;
7662 if (!$info) {
7663 $info = array(
7664 'access' => NULL,
7665 'admin' => $CFG->admin,
7666 'auth' => 'auth',
7667 'backup' => 'backup/util/ui',
7668 'block' => 'blocks',
7669 'blog' => 'blog',
7670 'bulkusers' => NULL,
7671 'calendar' => 'calendar',
7672 'cohort' => 'cohort',
7673 'condition' => NULL,
7674 'completion' => NULL,
7675 'countries' => NULL,
7676 'course' => 'course',
7677 'currencies' => NULL,
7678 'dbtransfer' => NULL,
7679 'debug' => NULL,
7680 'dock' => NULL,
7681 'editor' => 'lib/editor',
7682 'edufields' => NULL,
7683 'enrol' => 'enrol',
7684 'error' => NULL,
7685 'filepicker' => NULL,
7686 'files' => 'files',
7687 'filters' => NULL,
7688 'fonts' => NULL,
7689 'form' => 'lib/form',
7690 'grades' => 'grade',
7691 'grading' => 'grade/grading',
7692 'group' => 'group',
7693 'help' => NULL,
7694 'hub' => NULL,
7695 'imscc' => NULL,
7696 'install' => NULL,
7697 'iso6392' => NULL,
7698 'langconfig' => NULL,
7699 'license' => NULL,
7700 'mathslib' => NULL,
7701 'message' => 'message',
7702 'mimetypes' => NULL,
7703 'mnet' => 'mnet',
7704 'moodle.org' => NULL, // the dot is nasty, watch out! should be renamed to moodleorg
7705 'my' => 'my',
7706 'notes' => 'notes',
7707 'pagetype' => NULL,
7708 'pix' => NULL,
7709 'plagiarism' => 'plagiarism',
7710 'plugin' => NULL,
7711 'portfolio' => 'portfolio',
7712 'publish' => 'course/publish',
7713 'question' => 'question',
7714 'rating' => 'rating',
7715 'register' => 'admin/registration', //TODO: this is wrong, unfortunately we would need to modify hub code to pass around the correct url
7716 'repository' => 'repository',
7717 'rss' => 'rss',
7718 'role' => $CFG->admin.'/role',
7719 'search' => 'search',
7720 'table' => NULL,
7721 'tag' => 'tag',
7722 'timezones' => NULL,
7723 'user' => 'user',
7724 'userkey' => NULL,
7725 'webservice' => 'webservice',
7729 return $info;
7733 * Lists all plugin types
7734 * @param bool $fullpaths false means relative paths from dirroot
7735 * @return array Array of strings - name=>location
7737 function get_plugin_types($fullpaths=true) {
7738 global $CFG;
7740 static $info = null;
7741 static $fullinfo = null;
7743 if (!$info) {
7744 $info = array('qtype' => 'question/type',
7745 'mod' => 'mod',
7746 'auth' => 'auth',
7747 'enrol' => 'enrol',
7748 'message' => 'message/output',
7749 'block' => 'blocks',
7750 'filter' => 'filter',
7751 'editor' => 'lib/editor',
7752 'format' => 'course/format',
7753 'profilefield' => 'user/profile/field',
7754 'report' => 'report',
7755 'coursereport' => 'course/report', // must be after system reports
7756 'gradeexport' => 'grade/export',
7757 'gradeimport' => 'grade/import',
7758 'gradereport' => 'grade/report',
7759 'gradingform' => 'grade/grading/form',
7760 'mnetservice' => 'mnet/service',
7761 'webservice' => 'webservice',
7762 'repository' => 'repository',
7763 'portfolio' => 'portfolio',
7764 'qbehaviour' => 'question/behaviour',
7765 'qformat' => 'question/format',
7766 'plagiarism' => 'plagiarism',
7767 'tool' => $CFG->admin.'/tool',
7768 'theme' => 'theme', // this is a bit hacky, themes may be in $CFG->themedir too
7771 $mods = get_plugin_list('mod');
7772 foreach ($mods as $mod => $moddir) {
7773 if (file_exists("$moddir/db/subplugins.php")) {
7774 $subplugins = array();
7775 include("$moddir/db/subplugins.php");
7776 foreach ($subplugins as $subtype=>$dir) {
7777 $info[$subtype] = $dir;
7782 // local is always last!
7783 $info['local'] = 'local';
7785 $fullinfo = array();
7786 foreach ($info as $type => $dir) {
7787 $fullinfo[$type] = $CFG->dirroot.'/'.$dir;
7791 return ($fullpaths ? $fullinfo : $info);
7795 * Simplified version of get_list_of_plugins()
7796 * @param string $plugintype type of plugin
7797 * @return array name=>fulllocation pairs of plugins of given type
7799 function get_plugin_list($plugintype) {
7800 global $CFG;
7802 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
7803 if ($plugintype == 'auth') {
7804 // Historically we have had an auth plugin called 'db', so allow a special case.
7805 $key = array_search('db', $ignored);
7806 if ($key !== false) {
7807 unset($ignored[$key]);
7811 if ($plugintype === '') {
7812 $plugintype = 'mod';
7815 $fulldirs = array();
7817 if ($plugintype === 'mod') {
7818 // mod is an exception because we have to call this function from get_plugin_types()
7819 $fulldirs[] = $CFG->dirroot.'/mod';
7821 } else if ($plugintype === 'theme') {
7822 $fulldirs[] = $CFG->dirroot.'/theme';
7823 // themes are special because they may be stored also in separate directory
7824 if (!empty($CFG->themedir) and file_exists($CFG->themedir) and is_dir($CFG->themedir) ) {
7825 $fulldirs[] = $CFG->themedir;
7828 } else {
7829 $types = get_plugin_types(true);
7830 if (!array_key_exists($plugintype, $types)) {
7831 return array();
7833 $fulldir = $types[$plugintype];
7834 if (!file_exists($fulldir)) {
7835 return array();
7837 $fulldirs[] = $fulldir;
7840 $result = array();
7842 foreach ($fulldirs as $fulldir) {
7843 if (!is_dir($fulldir)) {
7844 continue;
7846 $items = new DirectoryIterator($fulldir);
7847 foreach ($items as $item) {
7848 if ($item->isDot() or !$item->isDir()) {
7849 continue;
7851 $pluginname = $item->getFilename();
7852 if (in_array($pluginname, $ignored)) {
7853 continue;
7855 $pluginname = clean_param($pluginname, PARAM_PLUGIN);
7856 if (empty($pluginname)) {
7857 // better ignore plugins with problematic names here
7858 continue;
7860 $result[$pluginname] = $fulldir.'/'.$pluginname;
7861 unset($item);
7863 unset($items);
7866 //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!
7867 ksort($result);
7868 return $result;
7872 * Get a list of all the plugins of a given type that contain a particular file.
7873 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
7874 * @param string $file the name of file that must be present in the plugin.
7875 * (e.g. 'view.php', 'db/install.xml').
7876 * @param bool $include if true (default false), the file will be include_once-ed if found.
7877 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
7878 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
7880 function get_plugin_list_with_file($plugintype, $file, $include = false) {
7881 global $CFG; // Necessary in case it is referenced by include()d PHP scripts.
7883 $plugins = array();
7885 foreach(get_plugin_list($plugintype) as $plugin => $dir) {
7886 $path = $dir . '/' . $file;
7887 if (file_exists($path)) {
7888 if ($include) {
7889 include_once($path);
7891 $plugins[$plugin] = $path;
7895 return $plugins;
7899 * Get a list of all the plugins of a given type that define a certain API function
7900 * in a certain file. The plugin component names and function names are returned.
7902 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
7903 * @param string $function the part of the name of the function after the
7904 * frankenstyle prefix. e.g 'hook' if you are looking for functions with
7905 * names like report_courselist_hook.
7906 * @param string $file the name of file within the plugin that defines the
7907 * function. Defaults to lib.php.
7908 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
7909 * and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
7911 function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php') {
7912 $pluginfunctions = array();
7913 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
7914 $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
7916 if (function_exists($fullfunction)) {
7917 // Function exists with standard name. Store, indexed by
7918 // frankenstyle name of plugin
7919 $pluginfunctions[$plugintype . '_' . $plugin] = $fullfunction;
7921 } else if ($plugintype === 'mod') {
7922 // For modules, we also allow plugin without full frankenstyle
7923 // but just starting with the module name
7924 $shortfunction = $plugin . '_' . $function;
7925 if (function_exists($shortfunction)) {
7926 $pluginfunctions[$plugintype . '_' . $plugin] = $shortfunction;
7930 return $pluginfunctions;
7934 * Get a list of all the plugins of a given type that define a certain class
7935 * in a certain file. The plugin component names and class names are returned.
7937 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
7938 * @param string $class the part of the name of the class after the
7939 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
7940 * names like report_courselist_thing. If you are looking for classes with
7941 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
7942 * @param string $file the name of file within the plugin that defines the class.
7943 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
7944 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
7946 function get_plugin_list_with_class($plugintype, $class, $file) {
7947 if ($class) {
7948 $suffix = '_' . $class;
7949 } else {
7950 $suffix = '';
7953 $pluginclasses = array();
7954 foreach (get_plugin_list_with_file($plugintype, $file, true) as $plugin => $notused) {
7955 $classname = $plugintype . '_' . $plugin . $suffix;
7956 if (class_exists($classname)) {
7957 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
7961 return $pluginclasses;
7965 * Lists plugin-like directories within specified directory
7967 * This function was originally used for standard Moodle plugins, please use
7968 * new get_plugin_list() now.
7970 * This function is used for general directory listing and backwards compatility.
7972 * @param string $directory relative directory from root
7973 * @param string $exclude dir name to exclude from the list (defaults to none)
7974 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
7975 * @return array Sorted array of directory names found under the requested parameters
7977 function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
7978 global $CFG;
7980 $plugins = array();
7982 if (empty($basedir)) {
7983 $basedir = $CFG->dirroot .'/'. $directory;
7985 } else {
7986 $basedir = $basedir .'/'. $directory;
7989 if (file_exists($basedir) && filetype($basedir) == 'dir') {
7990 $dirhandle = opendir($basedir);
7991 while (false !== ($dir = readdir($dirhandle))) {
7992 $firstchar = substr($dir, 0, 1);
7993 if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
7994 continue;
7996 if (filetype($basedir .'/'. $dir) != 'dir') {
7997 continue;
7999 $plugins[] = $dir;
8001 closedir($dirhandle);
8003 if ($plugins) {
8004 asort($plugins);
8006 return $plugins;
8010 * Invoke plugin's callback functions
8012 * @param string $type plugin type e.g. 'mod'
8013 * @param string $name plugin name
8014 * @param string $feature feature name
8015 * @param string $action feature's action
8016 * @param array $params parameters of callback function, should be an array
8017 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8018 * @return mixed
8020 * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
8022 function plugin_callback($type, $name, $feature, $action, $params = null, $default = null) {
8023 return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default);
8027 * Invoke component's callback functions
8029 * @param string $component frankenstyle component name, e.g. 'mod_quiz'
8030 * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
8031 * @param array $params parameters of callback function
8032 * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
8033 * @return mixed
8035 function component_callback($component, $function, array $params = array(), $default = null) {
8036 global $CFG; // this is needed for require_once() below
8038 $cleancomponent = clean_param($component, PARAM_COMPONENT);
8039 if (empty($cleancomponent)) {
8040 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8042 $component = $cleancomponent;
8044 list($type, $name) = normalize_component($component);
8045 $component = $type . '_' . $name;
8047 $oldfunction = $name.'_'.$function;
8048 $function = $component.'_'.$function;
8050 $dir = get_component_directory($component);
8051 if (empty($dir)) {
8052 throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
8055 // Load library and look for function
8056 if (file_exists($dir.'/lib.php')) {
8057 require_once($dir.'/lib.php');
8060 if (!function_exists($function) and function_exists($oldfunction)) {
8061 if ($type !== 'mod' and $type !== 'core') {
8062 debugging("Please use new function name $function instead of legacy $oldfunction");
8064 $function = $oldfunction;
8067 if (function_exists($function)) {
8068 // Function exists, so just return function result
8069 $ret = call_user_func_array($function, $params);
8070 if (is_null($ret)) {
8071 return $default;
8072 } else {
8073 return $ret;
8076 return $default;
8080 * Checks whether a plugin supports a specified feature.
8082 * @param string $type Plugin type e.g. 'mod'
8083 * @param string $name Plugin name e.g. 'forum'
8084 * @param string $feature Feature code (FEATURE_xx constant)
8085 * @param mixed $default default value if feature support unknown
8086 * @return mixed Feature result (false if not supported, null if feature is unknown,
8087 * otherwise usually true but may have other feature-specific value such as array)
8089 function plugin_supports($type, $name, $feature, $default = NULL) {
8090 global $CFG;
8092 if ($type === 'mod' and $name === 'NEWMODULE') {
8093 //somebody forgot to rename the module template
8094 return false;
8097 $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
8098 if (empty($component)) {
8099 throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
8102 $function = null;
8104 if ($type === 'mod') {
8105 // we need this special case because we support subplugins in modules,
8106 // otherwise it would end up in infinite loop
8107 if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
8108 include_once("$CFG->dirroot/mod/$name/lib.php");
8109 $function = $component.'_supports';
8110 if (!function_exists($function)) {
8111 // legacy non-frankenstyle function name
8112 $function = $name.'_supports';
8114 } else {
8115 // invalid module
8118 } else {
8119 if (!$path = get_plugin_directory($type, $name)) {
8120 // non existent plugin type
8121 return false;
8123 if (file_exists("$path/lib.php")) {
8124 include_once("$path/lib.php");
8125 $function = $component.'_supports';
8129 if ($function and function_exists($function)) {
8130 $supports = $function($feature);
8131 if (is_null($supports)) {
8132 // plugin does not know - use default
8133 return $default;
8134 } else {
8135 return $supports;
8139 //plugin does not care, so use default
8140 return $default;
8144 * Returns true if the current version of PHP is greater that the specified one.
8146 * @todo Check PHP version being required here is it too low?
8148 * @param string $version The version of php being tested.
8149 * @return bool
8151 function check_php_version($version='5.2.4') {
8152 return (version_compare(phpversion(), $version) >= 0);
8156 * Checks to see if is the browser operating system matches the specified
8157 * brand.
8159 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
8161 * @uses $_SERVER
8162 * @param string $brand The operating system identifier being tested
8163 * @return bool true if the given brand below to the detected operating system
8165 function check_browser_operating_system($brand) {
8166 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8167 return false;
8170 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
8171 return true;
8174 return false;
8178 * Checks to see if is a browser matches the specified
8179 * brand and is equal or better version.
8181 * @uses $_SERVER
8182 * @param string $brand The browser identifier being tested
8183 * @param int $version The version of the browser, if not specified any version (except 5.5 for IE for BC reasons)
8184 * @return bool true if the given version is below that of the detected browser
8186 function check_browser_version($brand, $version = null) {
8187 if (empty($_SERVER['HTTP_USER_AGENT'])) {
8188 return false;
8191 $agent = $_SERVER['HTTP_USER_AGENT'];
8193 switch ($brand) {
8195 case 'Camino': /// OSX browser using Gecke engine
8196 if (strpos($agent, 'Camino') === false) {
8197 return false;
8199 if (empty($version)) {
8200 return true; // no version specified
8202 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
8203 if (version_compare($match[1], $version) >= 0) {
8204 return true;
8207 break;
8210 case 'Firefox': /// Mozilla Firefox browsers
8211 if (strpos($agent, 'Iceweasel') === false and strpos($agent, 'Firefox') === false) {
8212 return false;
8214 if (empty($version)) {
8215 return true; // no version specified
8217 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $agent, $match)) {
8218 if (version_compare($match[2], $version) >= 0) {
8219 return true;
8222 break;
8225 case 'Gecko': /// Gecko based browsers
8226 if (empty($version) and substr_count($agent, 'Camino')) {
8227 // MacOS X Camino support
8228 $version = 20041110;
8231 // the proper string - Gecko/CCYYMMDD Vendor/Version
8232 // Faster version and work-a-round No IDN problem.
8233 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
8234 if ($match[1] > $version) {
8235 return true;
8238 break;
8241 case 'MSIE': /// Internet Explorer
8242 if (strpos($agent, 'Opera') !== false) { // Reject Opera
8243 return false;
8245 // in case of IE we have to deal with BC of the version parameter
8246 if (is_null($version)) {
8247 $version = 5.5; // anything older is not considered a browser at all!
8250 //see: http://www.useragentstring.com/pages/Internet%20Explorer/
8251 if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
8252 if (version_compare($match[1], $version) >= 0) {
8253 return true;
8256 break;
8259 case 'Opera': /// Opera
8260 if (strpos($agent, 'Opera') === false) {
8261 return false;
8263 if (empty($version)) {
8264 return true; // no version specified
8266 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
8267 if (version_compare($match[1], $version) >= 0) {
8268 return true;
8271 break;
8274 case 'WebKit': /// WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles)
8275 if (strpos($agent, 'AppleWebKit') === false) {
8276 return false;
8278 if (empty($version)) {
8279 return true; // no version specified
8281 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8282 if (version_compare($match[1], $version) >= 0) {
8283 return true;
8286 break;
8289 case 'Safari': /// Desktop version of Apple Safari browser - no mobile or touch devices
8290 if (strpos($agent, 'AppleWebKit') === false) {
8291 return false;
8293 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices
8294 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
8295 return false;
8297 if (strpos($agent, 'Shiira')) { // Reject Shiira
8298 return false;
8300 if (strpos($agent, 'SymbianOS')) { // Reject SymbianOS
8301 return false;
8303 if (strpos($agent, 'Android')) { // Reject Androids too
8304 return false;
8306 if (strpos($agent, 'iPhone') or strpos($agent, 'iPad') or strpos($agent, 'iPod')) {
8307 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
8308 return false;
8310 if (strpos($agent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly
8311 return false;
8314 if (empty($version)) {
8315 return true; // no version specified
8317 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8318 if (version_compare($match[1], $version) >= 0) {
8319 return true;
8322 break;
8325 case 'Chrome':
8326 if (strpos($agent, 'Chrome') === false) {
8327 return false;
8329 if (empty($version)) {
8330 return true; // no version specified
8332 if (preg_match("/Chrome\/(.*)[ ]+/i", $agent, $match)) {
8333 if (version_compare($match[1], $version) >= 0) {
8334 return true;
8337 break;
8340 case 'Safari iOS': /// Safari on iPhone, iPad and iPod touch
8341 if (strpos($agent, 'AppleWebKit') === false or strpos($agent, 'Safari') === false) {
8342 return false;
8344 if (!strpos($agent, 'iPhone') and !strpos($agent, 'iPad') and !strpos($agent, 'iPod')) {
8345 return false;
8347 if (empty($version)) {
8348 return true; // no version specified
8350 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8351 if (version_compare($match[1], $version) >= 0) {
8352 return true;
8355 break;
8358 case 'WebKit Android': /// WebKit browser on Android
8359 if (strpos($agent, 'Linux; U; Android') === false) {
8360 return false;
8362 if (empty($version)) {
8363 return true; // no version specified
8365 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
8366 if (version_compare($match[1], $version) >= 0) {
8367 return true;
8370 break;
8374 return false;
8378 * Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
8379 * an optional admin specified regular expression. If enabledevicedetection is set to no or not set
8380 * it returns default
8382 * @return string device type
8384 function get_device_type() {
8385 global $CFG;
8387 if (empty($CFG->enabledevicedetection) || empty($_SERVER['HTTP_USER_AGENT'])) {
8388 return 'default';
8391 $useragent = $_SERVER['HTTP_USER_AGENT'];
8393 if (!empty($CFG->devicedetectregex)) {
8394 $regexes = json_decode($CFG->devicedetectregex);
8396 foreach ($regexes as $value=>$regex) {
8397 if (preg_match($regex, $useragent)) {
8398 return $value;
8403 //mobile detection PHP direct copy from open source detectmobilebrowser.com
8404 $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';
8405 $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';
8406 if (preg_match($phonesregex,$useragent) || preg_match($modelsregex,substr($useragent, 0, 4))){
8407 return 'mobile';
8410 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
8411 if (preg_match($tabletregex, $useragent)) {
8412 return 'tablet';
8415 // Safe way to check for IE6 and not get false positives for some IE 7/8 users
8416 if (substr($_SERVER['HTTP_USER_AGENT'], 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
8417 return 'legacy';
8420 return 'default';
8424 * Returns a list of the device types supporting by Moodle
8426 * @param boolean $incusertypes includes types specified using the devicedetectregex admin setting
8427 * @return array $types
8429 function get_device_type_list($incusertypes = true) {
8430 global $CFG;
8432 $types = array('default', 'legacy', 'mobile', 'tablet');
8434 if ($incusertypes && !empty($CFG->devicedetectregex)) {
8435 $regexes = json_decode($CFG->devicedetectregex);
8437 foreach ($regexes as $value => $regex) {
8438 $types[] = $value;
8442 return $types;
8446 * Returns the theme selected for a particular device or false if none selected.
8448 * @param string $devicetype
8449 * @return string|false The name of the theme to use for the device or the false if not set
8451 function get_selected_theme_for_device_type($devicetype = null) {
8452 global $CFG;
8454 if (empty($devicetype)) {
8455 $devicetype = get_user_device_type();
8458 $themevarname = get_device_cfg_var_name($devicetype);
8459 if (empty($CFG->$themevarname)) {
8460 return false;
8463 return $CFG->$themevarname;
8467 * Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
8469 * @param string $devicetype
8470 * @return string The config variable to use to determine the theme
8472 function get_device_cfg_var_name($devicetype = null) {
8473 if ($devicetype == 'default' || empty($devicetype)) {
8474 return 'theme';
8477 return 'theme' . $devicetype;
8481 * Allows the user to switch the device they are seeing the theme for.
8482 * This allows mobile users to switch back to the default theme, or theme for any other device.
8484 * @param string $newdevice The device the user is currently using.
8485 * @return string The device the user has switched to
8487 function set_user_device_type($newdevice) {
8488 global $USER;
8490 $devicetype = get_device_type();
8491 $devicetypes = get_device_type_list();
8493 if ($newdevice == $devicetype) {
8494 unset_user_preference('switchdevice'.$devicetype);
8495 } else if (in_array($newdevice, $devicetypes)) {
8496 set_user_preference('switchdevice'.$devicetype, $newdevice);
8501 * Returns the device the user is currently using, or if the user has chosen to switch devices
8502 * for the current device type the type they have switched to.
8504 * @return string The device the user is currently using or wishes to use
8506 function get_user_device_type() {
8507 $device = get_device_type();
8508 $switched = get_user_preferences('switchdevice'.$device, false);
8509 if ($switched != false) {
8510 return $switched;
8512 return $device;
8516 * Returns one or several CSS class names that match the user's browser. These can be put
8517 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
8519 * @return array An array of browser version classes
8521 function get_browser_version_classes() {
8522 $classes = array();
8524 if (check_browser_version("MSIE", "0")) {
8525 $classes[] = 'ie';
8526 if (check_browser_version("MSIE", 9)) {
8527 $classes[] = 'ie9';
8528 } else if (check_browser_version("MSIE", 8)) {
8529 $classes[] = 'ie8';
8530 } elseif (check_browser_version("MSIE", 7)) {
8531 $classes[] = 'ie7';
8532 } elseif (check_browser_version("MSIE", 6)) {
8533 $classes[] = 'ie6';
8536 } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
8537 $classes[] = 'gecko';
8538 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
8539 $classes[] = "gecko{$matches[1]}{$matches[2]}";
8542 } else if (check_browser_version("WebKit")) {
8543 $classes[] = 'safari';
8544 if (check_browser_version("Safari iOS")) {
8545 $classes[] = 'ios';
8547 } else if (check_browser_version("WebKit Android")) {
8548 $classes[] = 'android';
8551 } else if (check_browser_version("Opera")) {
8552 $classes[] = 'opera';
8556 return $classes;
8560 * Can handle rotated text. Whether it is safe to use the trickery in textrotate.js.
8562 * @return bool True for yes, false for no
8564 function can_use_rotated_text() {
8565 global $USER;
8566 return ajaxenabled(array('Firefox' => 2.0)) && !$USER->screenreader;;
8570 * Hack to find out the GD version by parsing phpinfo output
8572 * @return int GD version (1, 2, or 0)
8574 function check_gd_version() {
8575 $gdversion = 0;
8577 if (function_exists('gd_info')){
8578 $gd_info = gd_info();
8579 if (substr_count($gd_info['GD Version'], '2.')) {
8580 $gdversion = 2;
8581 } else if (substr_count($gd_info['GD Version'], '1.')) {
8582 $gdversion = 1;
8585 } else {
8586 ob_start();
8587 phpinfo(INFO_MODULES);
8588 $phpinfo = ob_get_contents();
8589 ob_end_clean();
8591 $phpinfo = explode("\n", $phpinfo);
8594 foreach ($phpinfo as $text) {
8595 $parts = explode('</td>', $text);
8596 foreach ($parts as $key => $val) {
8597 $parts[$key] = trim(strip_tags($val));
8599 if ($parts[0] == 'GD Version') {
8600 if (substr_count($parts[1], '2.0')) {
8601 $parts[1] = '2.0';
8603 $gdversion = intval($parts[1]);
8608 return $gdversion; // 1, 2 or 0
8612 * Determine if moodle installation requires update
8614 * Checks version numbers of main code and all modules to see
8615 * if there are any mismatches
8617 * @global moodle_database $DB
8618 * @return bool
8620 function moodle_needs_upgrading() {
8621 global $CFG, $DB, $OUTPUT;
8623 if (empty($CFG->version)) {
8624 return true;
8627 // main versio nfirst
8628 $version = null;
8629 include($CFG->dirroot.'/version.php'); // defines $version and upgrades
8630 if ($version > $CFG->version) {
8631 return true;
8634 // modules
8635 $mods = get_plugin_list('mod');
8636 $installed = $DB->get_records('modules', array(), '', 'name, version');
8637 foreach ($mods as $mod => $fullmod) {
8638 if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
8639 continue;
8641 $module = new stdClass();
8642 if (!is_readable($fullmod.'/version.php')) {
8643 continue;
8645 include($fullmod.'/version.php'); // defines $module with version etc
8646 if (empty($installed[$mod])) {
8647 return true;
8648 } else if ($module->version > $installed[$mod]->version) {
8649 return true;
8652 unset($installed);
8654 // blocks
8655 $blocks = get_plugin_list('block');
8656 $installed = $DB->get_records('block', array(), '', 'name, version');
8657 require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
8658 foreach ($blocks as $blockname=>$fullblock) {
8659 if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
8660 continue;
8662 if (!is_readable($fullblock.'/version.php')) {
8663 continue;
8665 $plugin = new stdClass();
8666 $plugin->version = NULL;
8667 include($fullblock.'/version.php');
8668 if (empty($installed[$blockname])) {
8669 return true;
8670 } else if ($plugin->version > $installed[$blockname]->version) {
8671 return true;
8674 unset($installed);
8676 // now the rest of plugins
8677 $plugintypes = get_plugin_types();
8678 unset($plugintypes['mod']);
8679 unset($plugintypes['block']);
8681 $versions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin, value');
8682 foreach ($plugintypes as $type=>$unused) {
8683 $plugs = get_plugin_list($type);
8684 foreach ($plugs as $plug=>$fullplug) {
8685 $component = $type.'_'.$plug;
8686 if (!is_readable($fullplug.'/version.php')) {
8687 continue;
8689 $plugin = new stdClass();
8690 include($fullplug.'/version.php'); // defines $plugin with version etc
8691 if (array_key_exists($component, $versions)) {
8692 $installedversion = $versions[$component];
8693 } else {
8694 $installedversion = get_config($component, 'version');
8696 if (empty($installedversion)) { // new installation
8697 return true;
8698 } else if ($installedversion < $plugin->version) { // upgrade
8699 return true;
8704 return false;
8708 * Returns the major version of this site
8710 * Moodle version numbers consist of three numbers separated by a dot, for
8711 * example 1.9.11 or 2.0.2. The first two numbers, like 1.9 or 2.0, represent so
8712 * called major version. This function extracts the major version from either
8713 * $CFG->release (default) or eventually from the $release variable defined in
8714 * the main version.php.
8716 * @param bool $fromdisk should the version if source code files be used
8717 * @return string|false the major version like '2.3', false if could not be determined
8719 function moodle_major_version($fromdisk = false) {
8720 global $CFG;
8722 if ($fromdisk) {
8723 $release = null;
8724 require($CFG->dirroot.'/version.php');
8725 if (empty($release)) {
8726 return false;
8729 } else {
8730 if (empty($CFG->release)) {
8731 return false;
8733 $release = $CFG->release;
8736 if (preg_match('/^[0-9]+\.[0-9]+/', $release, $matches)) {
8737 return $matches[0];
8738 } else {
8739 return false;
8743 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
8746 * Sets the system locale
8748 * @category string
8749 * @param string $locale Can be used to force a locale
8751 function moodle_setlocale($locale='') {
8752 global $CFG;
8754 static $currentlocale = ''; // last locale caching
8756 $oldlocale = $currentlocale;
8758 /// Fetch the correct locale based on ostype
8759 if ($CFG->ostype == 'WINDOWS') {
8760 $stringtofetch = 'localewin';
8761 } else {
8762 $stringtofetch = 'locale';
8765 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
8766 if (!empty($locale)) {
8767 $currentlocale = $locale;
8768 } else if (!empty($CFG->locale)) { // override locale for all language packs
8769 $currentlocale = $CFG->locale;
8770 } else {
8771 $currentlocale = get_string($stringtofetch, 'langconfig');
8774 /// do nothing if locale already set up
8775 if ($oldlocale == $currentlocale) {
8776 return;
8779 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
8780 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
8781 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
8783 /// Get current values
8784 $monetary= setlocale (LC_MONETARY, 0);
8785 $numeric = setlocale (LC_NUMERIC, 0);
8786 $ctype = setlocale (LC_CTYPE, 0);
8787 if ($CFG->ostype != 'WINDOWS') {
8788 $messages= setlocale (LC_MESSAGES, 0);
8790 /// Set locale to all
8791 setlocale (LC_ALL, $currentlocale);
8792 /// Set old values
8793 setlocale (LC_MONETARY, $monetary);
8794 setlocale (LC_NUMERIC, $numeric);
8795 if ($CFG->ostype != 'WINDOWS') {
8796 setlocale (LC_MESSAGES, $messages);
8798 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
8799 setlocale (LC_CTYPE, $ctype);
8804 * Count words in a string.
8806 * Words are defined as things between whitespace.
8808 * @category string
8809 * @param string $string The text to be searched for words.
8810 * @return int The count of words in the specified string
8812 function count_words($string) {
8813 $string = strip_tags($string);
8814 return count(preg_split("/\w\b/", $string)) - 1;
8817 /** Count letters in a string.
8819 * Letters are defined as chars not in tags and different from whitespace.
8821 * @category string
8822 * @param string $string The text to be searched for letters.
8823 * @return int The count of letters in the specified text.
8825 function count_letters($string) {
8826 /// Loading the textlib singleton instance. We are going to need it.
8827 $string = strip_tags($string); // Tags are out now
8828 $string = preg_replace('/[[:space:]]*/','',$string); //Whitespace are out now
8830 return textlib::strlen($string);
8834 * Generate and return a random string of the specified length.
8836 * @param int $length The length of the string to be created.
8837 * @return string
8839 function random_string ($length=15) {
8840 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8841 $pool .= 'abcdefghijklmnopqrstuvwxyz';
8842 $pool .= '0123456789';
8843 $poollen = strlen($pool);
8844 mt_srand ((double) microtime() * 1000000);
8845 $string = '';
8846 for ($i = 0; $i < $length; $i++) {
8847 $string .= substr($pool, (mt_rand()%($poollen)), 1);
8849 return $string;
8853 * Generate a complex random string (useful for md5 salts)
8855 * This function is based on the above {@link random_string()} however it uses a
8856 * larger pool of characters and generates a string between 24 and 32 characters
8858 * @param int $length Optional if set generates a string to exactly this length
8859 * @return string
8861 function complex_random_string($length=null) {
8862 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
8863 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
8864 $poollen = strlen($pool);
8865 mt_srand ((double) microtime() * 1000000);
8866 if ($length===null) {
8867 $length = floor(rand(24,32));
8869 $string = '';
8870 for ($i = 0; $i < $length; $i++) {
8871 $string .= $pool[(mt_rand()%$poollen)];
8873 return $string;
8877 * Given some text (which may contain HTML) and an ideal length,
8878 * this function truncates the text neatly on a word boundary if possible
8880 * @category string
8881 * @global stdClass $CFG
8882 * @param string $text text to be shortened
8883 * @param int $ideal ideal string length
8884 * @param boolean $exact if false, $text will not be cut mid-word
8885 * @param string $ending The string to append if the passed string is truncated
8886 * @return string $truncate shortened string
8888 function shorten_text($text, $ideal=30, $exact = false, $ending='...') {
8890 global $CFG;
8892 // if the plain text is shorter than the maximum length, return the whole text
8893 if (textlib::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
8894 return $text;
8897 // Splits on HTML tags. Each open/close/empty tag will be the first thing
8898 // and only tag in its 'line'
8899 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
8901 $total_length = textlib::strlen($ending);
8902 $truncate = '';
8904 // This array stores information about open and close tags and their position
8905 // in the truncated string. Each item in the array is an object with fields
8906 // ->open (true if open), ->tag (tag name in lower case), and ->pos
8907 // (byte position in truncated text)
8908 $tagdetails = array();
8910 foreach ($lines as $line_matchings) {
8911 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
8912 if (!empty($line_matchings[1])) {
8913 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
8914 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
8915 // do nothing
8916 // if tag is a closing tag (f.e. </b>)
8917 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
8918 // record closing tag
8919 $tagdetails[] = (object)array('open'=>false,
8920 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
8921 // if tag is an opening tag (f.e. <b>)
8922 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
8923 // record opening tag
8924 $tagdetails[] = (object)array('open'=>true,
8925 'tag'=>textlib::strtolower($tag_matchings[1]), 'pos'=>textlib::strlen($truncate));
8927 // add html-tag to $truncate'd text
8928 $truncate .= $line_matchings[1];
8931 // calculate the length of the plain text part of the line; handle entities as one character
8932 $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]));
8933 if ($total_length+$content_length > $ideal) {
8934 // the number of characters which are left
8935 $left = $ideal - $total_length;
8936 $entities_length = 0;
8937 // search for html entities
8938 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)) {
8939 // calculate the real length of all entities in the legal range
8940 foreach ($entities[0] as $entity) {
8941 if ($entity[1]+1-$entities_length <= $left) {
8942 $left--;
8943 $entities_length += textlib::strlen($entity[0]);
8944 } else {
8945 // no more characters left
8946 break;
8950 $truncate .= textlib::substr($line_matchings[2], 0, $left+$entities_length);
8951 // maximum length is reached, so get off the loop
8952 break;
8953 } else {
8954 $truncate .= $line_matchings[2];
8955 $total_length += $content_length;
8958 // if the maximum length is reached, get off the loop
8959 if($total_length >= $ideal) {
8960 break;
8964 // if the words shouldn't be cut in the middle...
8965 if (!$exact) {
8966 // ...search the last occurence of a space...
8967 for ($k=textlib::strlen($truncate);$k>0;$k--) {
8968 if ($char = textlib::substr($truncate, $k, 1)) {
8969 if ($char === '.' or $char === ' ') {
8970 $breakpos = $k+1;
8971 break;
8972 } else if (strlen($char) > 2) { // Chinese/Japanese/Korean text
8973 $breakpos = $k+1; // can be truncated at any UTF-8
8974 break; // character boundary.
8979 if (isset($breakpos)) {
8980 // ...and cut the text in this position
8981 $truncate = textlib::substr($truncate, 0, $breakpos);
8985 // add the defined ending to the text
8986 $truncate .= $ending;
8988 // Now calculate the list of open html tags based on the truncate position
8989 $open_tags = array();
8990 foreach ($tagdetails as $taginfo) {
8991 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
8992 // Don't include tags after we made the break!
8993 break;
8995 if($taginfo->open) {
8996 // add tag to the beginning of $open_tags list
8997 array_unshift($open_tags, $taginfo->tag);
8998 } else {
8999 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
9000 if ($pos !== false) {
9001 unset($open_tags[$pos]);
9006 // close all unclosed html-tags
9007 foreach ($open_tags as $tag) {
9008 $truncate .= '</' . $tag . '>';
9011 return $truncate;
9016 * Given dates in seconds, how many weeks is the date from startdate
9017 * The first week is 1, the second 2 etc ...
9019 * @todo Finish documenting this function
9021 * @uses WEEKSECS
9022 * @param int $startdate Timestamp for the start date
9023 * @param int $thedate Timestamp for the end date
9024 * @return string
9026 function getweek ($startdate, $thedate) {
9027 if ($thedate < $startdate) { // error
9028 return 0;
9031 return floor(($thedate - $startdate) / WEEKSECS) + 1;
9035 * returns a randomly generated password of length $maxlen. inspired by
9037 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
9038 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
9040 * @global stdClass $CFG
9041 * @param int $maxlen The maximum size of the password being generated.
9042 * @return string
9044 function generate_password($maxlen=10) {
9045 global $CFG;
9047 if (empty($CFG->passwordpolicy)) {
9048 $fillers = PASSWORD_DIGITS;
9049 $wordlist = file($CFG->wordlist);
9050 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9051 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
9052 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
9053 $password = $word1 . $filler1 . $word2;
9054 } else {
9055 $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
9056 $digits = $CFG->minpassworddigits;
9057 $lower = $CFG->minpasswordlower;
9058 $upper = $CFG->minpasswordupper;
9059 $nonalphanum = $CFG->minpasswordnonalphanum;
9060 $total = $lower + $upper + $digits + $nonalphanum;
9061 // minlength should be the greater one of the two ( $minlen and $total )
9062 $minlen = $minlen < $total ? $total : $minlen;
9063 // maxlen can never be smaller than minlen
9064 $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
9065 $additional = $maxlen - $total;
9067 // Make sure we have enough characters to fulfill
9068 // complexity requirements
9069 $passworddigits = PASSWORD_DIGITS;
9070 while ($digits > strlen($passworddigits)) {
9071 $passworddigits .= PASSWORD_DIGITS;
9073 $passwordlower = PASSWORD_LOWER;
9074 while ($lower > strlen($passwordlower)) {
9075 $passwordlower .= PASSWORD_LOWER;
9077 $passwordupper = PASSWORD_UPPER;
9078 while ($upper > strlen($passwordupper)) {
9079 $passwordupper .= PASSWORD_UPPER;
9081 $passwordnonalphanum = PASSWORD_NONALPHANUM;
9082 while ($nonalphanum > strlen($passwordnonalphanum)) {
9083 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
9086 // Now mix and shuffle it all
9087 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
9088 substr(str_shuffle ($passwordupper), 0, $upper) .
9089 substr(str_shuffle ($passworddigits), 0, $digits) .
9090 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
9091 substr(str_shuffle ($passwordlower .
9092 $passwordupper .
9093 $passworddigits .
9094 $passwordnonalphanum), 0 , $additional));
9097 return substr ($password, 0, $maxlen);
9101 * Given a float, prints it nicely.
9102 * Localized floats must not be used in calculations!
9104 * @param float $float The float to print
9105 * @param int $decimalpoints The number of decimal places to print.
9106 * @param bool $localized use localized decimal separator
9107 * @return string locale float
9109 function format_float($float, $decimalpoints=1, $localized=true) {
9110 if (is_null($float)) {
9111 return '';
9113 if ($localized) {
9114 return number_format($float, $decimalpoints, get_string('decsep', 'langconfig'), '');
9115 } else {
9116 return number_format($float, $decimalpoints, '.', '');
9121 * Converts locale specific floating point/comma number back to standard PHP float value
9122 * Do NOT try to do any math operations before this conversion on any user submitted floats!
9124 * @param string $locale_float locale aware float representation
9125 * @return float
9127 function unformat_float($locale_float) {
9128 $locale_float = trim($locale_float);
9130 if ($locale_float == '') {
9131 return null;
9134 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
9136 return (float)str_replace(get_string('decsep', 'langconfig'), '.', $locale_float);
9140 * Given a simple array, this shuffles it up just like shuffle()
9141 * Unlike PHP's shuffle() this function works on any machine.
9143 * @param array $array The array to be rearranged
9144 * @return array
9146 function swapshuffle($array) {
9148 srand ((double) microtime() * 10000000);
9149 $last = count($array) - 1;
9150 for ($i=0;$i<=$last;$i++) {
9151 $from = rand(0,$last);
9152 $curr = $array[$i];
9153 $array[$i] = $array[$from];
9154 $array[$from] = $curr;
9156 return $array;
9160 * Like {@link swapshuffle()}, but works on associative arrays
9162 * @param array $array The associative array to be rearranged
9163 * @return array
9165 function swapshuffle_assoc($array) {
9167 $newarray = array();
9168 $newkeys = swapshuffle(array_keys($array));
9170 foreach ($newkeys as $newkey) {
9171 $newarray[$newkey] = $array[$newkey];
9173 return $newarray;
9177 * Given an arbitrary array, and a number of draws,
9178 * this function returns an array with that amount
9179 * of items. The indexes are retained.
9181 * @todo Finish documenting this function
9183 * @param array $array
9184 * @param int $draws
9185 * @return array
9187 function draw_rand_array($array, $draws) {
9188 srand ((double) microtime() * 10000000);
9190 $return = array();
9192 $last = count($array);
9194 if ($draws > $last) {
9195 $draws = $last;
9198 while ($draws > 0) {
9199 $last--;
9201 $keys = array_keys($array);
9202 $rand = rand(0, $last);
9204 $return[$keys[$rand]] = $array[$keys[$rand]];
9205 unset($array[$keys[$rand]]);
9207 $draws--;
9210 return $return;
9214 * Calculate the difference between two microtimes
9216 * @param string $a The first Microtime
9217 * @param string $b The second Microtime
9218 * @return string
9220 function microtime_diff($a, $b) {
9221 list($a_dec, $a_sec) = explode(' ', $a);
9222 list($b_dec, $b_sec) = explode(' ', $b);
9223 return $b_sec - $a_sec + $b_dec - $a_dec;
9227 * Given a list (eg a,b,c,d,e) this function returns
9228 * an array of 1->a, 2->b, 3->c etc
9230 * @param string $list The string to explode into array bits
9231 * @param string $separator The separator used within the list string
9232 * @return array The now assembled array
9234 function make_menu_from_list($list, $separator=',') {
9236 $array = array_reverse(explode($separator, $list), true);
9237 foreach ($array as $key => $item) {
9238 $outarray[$key+1] = trim($item);
9240 return $outarray;
9244 * Creates an array that represents all the current grades that
9245 * can be chosen using the given grading type.
9247 * Negative numbers
9248 * are scales, zero is no grade, and positive numbers are maximum
9249 * grades.
9251 * @todo Finish documenting this function or better deprecated this completely!
9253 * @param int $gradingtype
9254 * @return array
9256 function make_grades_menu($gradingtype) {
9257 global $DB;
9259 $grades = array();
9260 if ($gradingtype < 0) {
9261 if ($scale = $DB->get_record('scale', array('id'=> (-$gradingtype)))) {
9262 return make_menu_from_list($scale->scale);
9264 } else if ($gradingtype > 0) {
9265 for ($i=$gradingtype; $i>=0; $i--) {
9266 $grades[$i] = $i .' / '. $gradingtype;
9268 return $grades;
9270 return $grades;
9274 * This function returns the number of activities
9275 * using scaleid in a courseid
9277 * @todo Finish documenting this function
9279 * @global object
9280 * @global object
9281 * @param int $courseid ?
9282 * @param int $scaleid ?
9283 * @return int
9285 function course_scale_used($courseid, $scaleid) {
9286 global $CFG, $DB;
9288 $return = 0;
9290 if (!empty($scaleid)) {
9291 if ($cms = get_course_mods($courseid)) {
9292 foreach ($cms as $cm) {
9293 //Check cm->name/lib.php exists
9294 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
9295 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
9296 $function_name = $cm->modname.'_scale_used';
9297 if (function_exists($function_name)) {
9298 if ($function_name($cm->instance,$scaleid)) {
9299 $return++;
9306 // check if any course grade item makes use of the scale
9307 $return += $DB->count_records('grade_items', array('courseid'=>$courseid, 'scaleid'=>$scaleid));
9309 // check if any outcome in the course makes use of the scale
9310 $return += $DB->count_records_sql("SELECT COUNT('x')
9311 FROM {grade_outcomes_courses} goc,
9312 {grade_outcomes} go
9313 WHERE go.id = goc.outcomeid
9314 AND go.scaleid = ? AND goc.courseid = ?",
9315 array($scaleid, $courseid));
9317 return $return;
9321 * This function returns the number of activities
9322 * using scaleid in the entire site
9324 * @param int $scaleid
9325 * @param array $courses
9326 * @return int
9328 function site_scale_used($scaleid, &$courses) {
9329 $return = 0;
9331 if (!is_array($courses) || count($courses) == 0) {
9332 $courses = get_courses("all",false,"c.id,c.shortname");
9335 if (!empty($scaleid)) {
9336 if (is_array($courses) && count($courses) > 0) {
9337 foreach ($courses as $course) {
9338 $return += course_scale_used($course->id,$scaleid);
9342 return $return;
9346 * make_unique_id_code
9348 * @todo Finish documenting this function
9350 * @uses $_SERVER
9351 * @param string $extra Extra string to append to the end of the code
9352 * @return string
9354 function make_unique_id_code($extra='') {
9356 $hostname = 'unknownhost';
9357 if (!empty($_SERVER['HTTP_HOST'])) {
9358 $hostname = $_SERVER['HTTP_HOST'];
9359 } else if (!empty($_ENV['HTTP_HOST'])) {
9360 $hostname = $_ENV['HTTP_HOST'];
9361 } else if (!empty($_SERVER['SERVER_NAME'])) {
9362 $hostname = $_SERVER['SERVER_NAME'];
9363 } else if (!empty($_ENV['SERVER_NAME'])) {
9364 $hostname = $_ENV['SERVER_NAME'];
9367 $date = gmdate("ymdHis");
9369 $random = random_string(6);
9371 if ($extra) {
9372 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
9373 } else {
9374 return $hostname .'+'. $date .'+'. $random;
9380 * Function to check the passed address is within the passed subnet
9382 * The parameter is a comma separated string of subnet definitions.
9383 * Subnet strings can be in one of three formats:
9384 * 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask)
9385 * 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)
9386 * 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-)
9387 * Code for type 1 modified from user posted comments by mediator at
9388 * {@link http://au.php.net/manual/en/function.ip2long.php}
9390 * @param string $addr The address you are checking
9391 * @param string $subnetstr The string of subnet addresses
9392 * @return bool
9394 function address_in_subnet($addr, $subnetstr) {
9396 if ($addr == '0.0.0.0') {
9397 return false;
9399 $subnets = explode(',', $subnetstr);
9400 $found = false;
9401 $addr = trim($addr);
9402 $addr = cleanremoteaddr($addr, false); // normalise
9403 if ($addr === null) {
9404 return false;
9406 $addrparts = explode(':', $addr);
9408 $ipv6 = strpos($addr, ':');
9410 foreach ($subnets as $subnet) {
9411 $subnet = trim($subnet);
9412 if ($subnet === '') {
9413 continue;
9416 if (strpos($subnet, '/') !== false) {
9417 ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
9418 list($ip, $mask) = explode('/', $subnet);
9419 $mask = trim($mask);
9420 if (!is_number($mask)) {
9421 continue; // incorect mask number, eh?
9423 $ip = cleanremoteaddr($ip, false); // normalise
9424 if ($ip === null) {
9425 continue;
9427 if (strpos($ip, ':') !== false) {
9428 // IPv6
9429 if (!$ipv6) {
9430 continue;
9432 if ($mask > 128 or $mask < 0) {
9433 continue; // nonsense
9435 if ($mask == 0) {
9436 return true; // any address
9438 if ($mask == 128) {
9439 if ($ip === $addr) {
9440 return true;
9442 continue;
9444 $ipparts = explode(':', $ip);
9445 $modulo = $mask % 16;
9446 $ipnet = array_slice($ipparts, 0, ($mask-$modulo)/16);
9447 $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
9448 if (implode(':', $ipnet) === implode(':', $addrnet)) {
9449 if ($modulo == 0) {
9450 return true;
9452 $pos = ($mask-$modulo)/16;
9453 $ipnet = hexdec($ipparts[$pos]);
9454 $addrnet = hexdec($addrparts[$pos]);
9455 $mask = 0xffff << (16 - $modulo);
9456 if (($addrnet & $mask) == ($ipnet & $mask)) {
9457 return true;
9461 } else {
9462 // IPv4
9463 if ($ipv6) {
9464 continue;
9466 if ($mask > 32 or $mask < 0) {
9467 continue; // nonsense
9469 if ($mask == 0) {
9470 return true;
9472 if ($mask == 32) {
9473 if ($ip === $addr) {
9474 return true;
9476 continue;
9478 $mask = 0xffffffff << (32 - $mask);
9479 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
9480 return true;
9484 } else if (strpos($subnet, '-') !== false) {
9485 /// 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.
9486 $parts = explode('-', $subnet);
9487 if (count($parts) != 2) {
9488 continue;
9491 if (strpos($subnet, ':') !== false) {
9492 // IPv6
9493 if (!$ipv6) {
9494 continue;
9496 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9497 if ($ipstart === null) {
9498 continue;
9500 $ipparts = explode(':', $ipstart);
9501 $start = hexdec(array_pop($ipparts));
9502 $ipparts[] = trim($parts[1]);
9503 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
9504 if ($ipend === null) {
9505 continue;
9507 $ipparts[7] = '';
9508 $ipnet = implode(':', $ipparts);
9509 if (strpos($addr, $ipnet) !== 0) {
9510 continue;
9512 $ipparts = explode(':', $ipend);
9513 $end = hexdec($ipparts[7]);
9515 $addrend = hexdec($addrparts[7]);
9517 if (($addrend >= $start) and ($addrend <= $end)) {
9518 return true;
9521 } else {
9522 // IPv4
9523 if ($ipv6) {
9524 continue;
9526 $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
9527 if ($ipstart === null) {
9528 continue;
9530 $ipparts = explode('.', $ipstart);
9531 $ipparts[3] = trim($parts[1]);
9532 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
9533 if ($ipend === null) {
9534 continue;
9537 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
9538 return true;
9542 } else {
9543 /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
9544 if (strpos($subnet, ':') !== false) {
9545 // IPv6
9546 if (!$ipv6) {
9547 continue;
9549 $parts = explode(':', $subnet);
9550 $count = count($parts);
9551 if ($parts[$count-1] === '') {
9552 unset($parts[$count-1]); // trim trailing :
9553 $count--;
9554 $subnet = implode('.', $parts);
9556 $isip = cleanremoteaddr($subnet, false); // normalise
9557 if ($isip !== null) {
9558 if ($isip === $addr) {
9559 return true;
9561 continue;
9562 } else if ($count > 8) {
9563 continue;
9565 $zeros = array_fill(0, 8-$count, '0');
9566 $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
9567 if (address_in_subnet($addr, $subnet)) {
9568 return true;
9571 } else {
9572 // IPv4
9573 if ($ipv6) {
9574 continue;
9576 $parts = explode('.', $subnet);
9577 $count = count($parts);
9578 if ($parts[$count-1] === '') {
9579 unset($parts[$count-1]); // trim trailing .
9580 $count--;
9581 $subnet = implode('.', $parts);
9583 if ($count == 4) {
9584 $subnet = cleanremoteaddr($subnet, false); // normalise
9585 if ($subnet === $addr) {
9586 return true;
9588 continue;
9589 } else if ($count > 4) {
9590 continue;
9592 $zeros = array_fill(0, 4-$count, '0');
9593 $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
9594 if (address_in_subnet($addr, $subnet)) {
9595 return true;
9601 return false;
9605 * For outputting debugging info
9607 * @uses STDOUT
9608 * @param string $string The string to write
9609 * @param string $eol The end of line char(s) to use
9610 * @param string $sleep Period to make the application sleep
9611 * This ensures any messages have time to display before redirect
9613 function mtrace($string, $eol="\n", $sleep=0) {
9615 if (defined('STDOUT')) {
9616 fwrite(STDOUT, $string.$eol);
9617 } else {
9618 echo $string . $eol;
9621 flush();
9623 //delay to keep message on user's screen in case of subsequent redirect
9624 if ($sleep) {
9625 sleep($sleep);
9630 * Replace 1 or more slashes or backslashes to 1 slash
9632 * @param string $path The path to strip
9633 * @return string the path with double slashes removed
9635 function cleardoubleslashes ($path) {
9636 return preg_replace('/(\/|\\\){1,}/','/',$path);
9640 * Is current ip in give list?
9642 * @param string $list
9643 * @return bool
9645 function remoteip_in_list($list){
9646 $inlist = false;
9647 $client_ip = getremoteaddr(null);
9649 if(!$client_ip){
9650 // ensure access on cli
9651 return true;
9654 $list = explode("\n", $list);
9655 foreach($list as $subnet) {
9656 $subnet = trim($subnet);
9657 if (address_in_subnet($client_ip, $subnet)) {
9658 $inlist = true;
9659 break;
9662 return $inlist;
9666 * Returns most reliable client address
9668 * @global object
9669 * @param string $default If an address can't be determined, then return this
9670 * @return string The remote IP address
9672 function getremoteaddr($default='0.0.0.0') {
9673 global $CFG;
9675 if (empty($CFG->getremoteaddrconf)) {
9676 // This will happen, for example, before just after the upgrade, as the
9677 // user is redirected to the admin screen.
9678 $variablestoskip = 0;
9679 } else {
9680 $variablestoskip = $CFG->getremoteaddrconf;
9682 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
9683 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
9684 $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
9685 return $address ? $address : $default;
9688 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
9689 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
9690 $address = cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
9691 return $address ? $address : $default;
9694 if (!empty($_SERVER['REMOTE_ADDR'])) {
9695 $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
9696 return $address ? $address : $default;
9697 } else {
9698 return $default;
9703 * Cleans an ip address. Internal addresses are now allowed.
9704 * (Originally local addresses were not allowed.)
9706 * @param string $addr IPv4 or IPv6 address
9707 * @param bool $compress use IPv6 address compression
9708 * @return string normalised ip address string, null if error
9710 function cleanremoteaddr($addr, $compress=false) {
9711 $addr = trim($addr);
9713 //TODO: maybe add a separate function is_addr_public() or something like this
9715 if (strpos($addr, ':') !== false) {
9716 // can be only IPv6
9717 $parts = explode(':', $addr);
9718 $count = count($parts);
9720 if (strpos($parts[$count-1], '.') !== false) {
9721 //legacy ipv4 notation
9722 $last = array_pop($parts);
9723 $ipv4 = cleanremoteaddr($last, true);
9724 if ($ipv4 === null) {
9725 return null;
9727 $bits = explode('.', $ipv4);
9728 $parts[] = dechex($bits[0]).dechex($bits[1]);
9729 $parts[] = dechex($bits[2]).dechex($bits[3]);
9730 $count = count($parts);
9731 $addr = implode(':', $parts);
9734 if ($count < 3 or $count > 8) {
9735 return null; // severly malformed
9738 if ($count != 8) {
9739 if (strpos($addr, '::') === false) {
9740 return null; // malformed
9742 // uncompress ::
9743 $insertat = array_search('', $parts, true);
9744 $missing = array_fill(0, 1 + 8 - $count, '0');
9745 array_splice($parts, $insertat, 1, $missing);
9746 foreach ($parts as $key=>$part) {
9747 if ($part === '') {
9748 $parts[$key] = '0';
9753 $adr = implode(':', $parts);
9754 if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
9755 return null; // incorrect format - sorry
9758 // normalise 0s and case
9759 $parts = array_map('hexdec', $parts);
9760 $parts = array_map('dechex', $parts);
9762 $result = implode(':', $parts);
9764 if (!$compress) {
9765 return $result;
9768 if ($result === '0:0:0:0:0:0:0:0') {
9769 return '::'; // all addresses
9772 $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
9773 if ($compressed !== $result) {
9774 return $compressed;
9777 $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
9778 if ($compressed !== $result) {
9779 return $compressed;
9782 $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
9783 if ($compressed !== $result) {
9784 return $compressed;
9787 return $result;
9790 // first get all things that look like IPv4 addresses
9791 $parts = array();
9792 if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
9793 return null;
9795 unset($parts[0]);
9797 foreach ($parts as $key=>$match) {
9798 if ($match > 255) {
9799 return null;
9801 $parts[$key] = (int)$match; // normalise 0s
9804 return implode('.', $parts);
9808 * This function will make a complete copy of anything it's given,
9809 * regardless of whether it's an object or not.
9811 * @param mixed $thing Something you want cloned
9812 * @return mixed What ever it is you passed it
9814 function fullclone($thing) {
9815 return unserialize(serialize($thing));
9820 * This function expects to called during shutdown
9821 * should be set via register_shutdown_function()
9822 * in lib/setup.php .
9824 * @return void
9826 function moodle_request_shutdown() {
9827 global $CFG;
9829 // help apache server if possible
9830 $apachereleasemem = false;
9831 if (function_exists('apache_child_terminate') && function_exists('memory_get_usage')
9832 && ini_get_bool('child_terminate')) {
9834 $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); //64MB default
9835 if (memory_get_usage() > get_real_size($limit)) {
9836 $apachereleasemem = $limit;
9837 @apache_child_terminate();
9841 // deal with perf logging
9842 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
9843 if ($apachereleasemem) {
9844 error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
9846 if (defined('MDL_PERFTOLOG')) {
9847 $perf = get_performance_info();
9848 error_log("PERF: " . $perf['txt']);
9850 if (defined('MDL_PERFINC')) {
9851 $inc = get_included_files();
9852 $ts = 0;
9853 foreach($inc as $f) {
9854 if (preg_match(':^/:', $f)) {
9855 $fs = filesize($f);
9856 $ts += $fs;
9857 $hfs = display_size($fs);
9858 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
9859 , NULL, NULL, 0);
9860 } else {
9861 error_log($f , NULL, NULL, 0);
9864 if ($ts > 0 ) {
9865 $hts = display_size($ts);
9866 error_log("Total size of files included: $ts ($hts)");
9873 * If new messages are waiting for the current user, then insert
9874 * JavaScript to pop up the messaging window into the page
9876 * @global moodle_page $PAGE
9877 * @return void
9879 function message_popup_window() {
9880 global $USER, $DB, $PAGE, $CFG, $SITE;
9882 if (!$PAGE->get_popup_notification_allowed() || empty($CFG->messaging)) {
9883 return;
9886 if (!isloggedin() || isguestuser()) {
9887 return;
9890 if (!isset($USER->message_lastpopup)) {
9891 $USER->message_lastpopup = 0;
9892 } else if ($USER->message_lastpopup > (time()-120)) {
9893 //dont run the query to check whether to display a popup if its been run in the last 2 minutes
9894 return;
9897 //a quick query to check whether the user has new messages
9898 $messagecount = $DB->count_records('message', array('useridto' => $USER->id));
9899 if ($messagecount<1) {
9900 return;
9903 //got unread messages so now do another query that joins with the user table
9904 $messagesql = "SELECT m.id, m.smallmessage, m.fullmessageformat, m.notification, u.firstname, u.lastname
9905 FROM {message} m
9906 JOIN {message_working} mw ON m.id=mw.unreadmessageid
9907 JOIN {message_processors} p ON mw.processorid=p.id
9908 JOIN {user} u ON m.useridfrom=u.id
9909 WHERE m.useridto = :userid
9910 AND p.name='popup'";
9912 //if the user was last notified over an hour ago we can renotify them of old messages
9913 //so don't worry about when the new message was sent
9914 $lastnotifiedlongago = $USER->message_lastpopup < (time()-3600);
9915 if (!$lastnotifiedlongago) {
9916 $messagesql .= 'AND m.timecreated > :lastpopuptime';
9919 $message_users = $DB->get_records_sql($messagesql, array('userid'=>$USER->id, 'lastpopuptime'=>$USER->message_lastpopup));
9921 //if we have new messages to notify the user about
9922 if (!empty($message_users)) {
9924 $strmessages = '';
9925 if (count($message_users)>1) {
9926 $strmessages = get_string('unreadnewmessages', 'message', count($message_users));
9927 } else {
9928 $message_users = reset($message_users);
9930 //show who the message is from if its not a notification
9931 if (!$message_users->notification) {
9932 $strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
9935 //try to display the small version of the message
9936 $smallmessage = null;
9937 if (!empty($message_users->smallmessage)) {
9938 //display the first 200 chars of the message in the popup
9939 $smallmessage = null;
9940 if (textlib::strlen($message_users->smallmessage) > 200) {
9941 $smallmessage = textlib::substr($message_users->smallmessage,0,200).'...';
9942 } else {
9943 $smallmessage = $message_users->smallmessage;
9946 //prevent html symbols being displayed
9947 if ($message_users->fullmessageformat == FORMAT_HTML) {
9948 $smallmessage = html_to_text($smallmessage);
9949 } else {
9950 $smallmessage = s($smallmessage);
9952 } else if ($message_users->notification) {
9953 //its a notification with no smallmessage so just say they have a notification
9954 $smallmessage = get_string('unreadnewnotification', 'message');
9956 if (!empty($smallmessage)) {
9957 $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
9961 $strgomessage = get_string('gotomessages', 'message');
9962 $strstaymessage = get_string('ignore','admin');
9964 $url = $CFG->wwwroot.'/message/index.php';
9965 $content = html_writer::start_tag('div', array('id'=>'newmessageoverlay','class'=>'mdl-align')).
9966 html_writer::start_tag('div', array('id'=>'newmessagetext')).
9967 $strmessages.
9968 html_writer::end_tag('div').
9970 html_writer::start_tag('div', array('id'=>'newmessagelinks')).
9971 html_writer::link($url, $strgomessage, array('id'=>'notificationyes')).'&nbsp;&nbsp;&nbsp;'.
9972 html_writer::link('', $strstaymessage, array('id'=>'notificationno')).
9973 html_writer::end_tag('div');
9974 html_writer::end_tag('div');
9976 $PAGE->requires->js_init_call('M.core_message.init_notification', array('', $content, $url));
9978 $USER->message_lastpopup = time();
9983 * Used to make sure that $min <= $value <= $max
9985 * Make sure that value is between min, and max
9987 * @param int $min The minimum value
9988 * @param int $value The value to check
9989 * @param int $max The maximum value
9991 function bounded_number($min, $value, $max) {
9992 if($value < $min) {
9993 return $min;
9995 if($value > $max) {
9996 return $max;
9998 return $value;
10002 * Check if there is a nested array within the passed array
10004 * @param array $array
10005 * @return bool true if there is a nested array false otherwise
10007 function array_is_nested($array) {
10008 foreach ($array as $value) {
10009 if (is_array($value)) {
10010 return true;
10013 return false;
10017 * get_performance_info() pairs up with init_performance_info()
10018 * loaded in setup.php. Returns an array with 'html' and 'txt'
10019 * values ready for use, and each of the individual stats provided
10020 * separately as well.
10022 * @global object
10023 * @global object
10024 * @global object
10025 * @return array
10027 function get_performance_info() {
10028 global $CFG, $PERF, $DB, $PAGE;
10030 $info = array();
10031 $info['html'] = ''; // holds userfriendly HTML representation
10032 $info['txt'] = me() . ' '; // holds log-friendly representation
10034 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
10036 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
10037 $info['txt'] .= 'time: '.$info['realtime'].'s ';
10039 if (function_exists('memory_get_usage')) {
10040 $info['memory_total'] = memory_get_usage();
10041 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
10042 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
10043 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
10046 if (function_exists('memory_get_peak_usage')) {
10047 $info['memory_peak'] = memory_get_peak_usage();
10048 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
10049 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
10052 $inc = get_included_files();
10053 //error_log(print_r($inc,1));
10054 $info['includecount'] = count($inc);
10055 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
10056 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
10058 $filtermanager = filter_manager::instance();
10059 if (method_exists($filtermanager, 'get_performance_summary')) {
10060 list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
10061 $info = array_merge($filterinfo, $info);
10062 foreach ($filterinfo as $key => $value) {
10063 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10064 $info['txt'] .= "$key: $value ";
10068 $stringmanager = get_string_manager();
10069 if (method_exists($stringmanager, 'get_performance_summary')) {
10070 list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
10071 $info = array_merge($filterinfo, $info);
10072 foreach ($filterinfo as $key => $value) {
10073 $info['html'] .= "<span class='$key'>$nicenames[$key]: $value </span> ";
10074 $info['txt'] .= "$key: $value ";
10078 $jsmodules = $PAGE->requires->get_loaded_modules();
10079 if ($jsmodules) {
10080 $yuicount = 0;
10081 $othercount = 0;
10082 $details = '';
10083 foreach ($jsmodules as $module => $backtraces) {
10084 if (strpos($module, 'yui') === 0) {
10085 $yuicount += 1;
10086 } else {
10087 $othercount += 1;
10089 $details .= "<div class='yui-module'><p>$module</p>";
10090 foreach ($backtraces as $backtrace) {
10091 $details .= "<div class='backtrace'>$backtrace</div>";
10093 $details .= '</div>';
10095 $info['html'] .= "<span class='includedyuimodules'>Included YUI modules: $yuicount</span> ";
10096 $info['txt'] .= "includedyuimodules: $yuicount ";
10097 $info['html'] .= "<span class='includedjsmodules'>Other JavaScript modules: $othercount</span> ";
10098 $info['txt'] .= "includedjsmodules: $othercount ";
10099 // Slightly odd to output the details in a display: none div. The point
10100 // Is that it takes a lot of space, and if you care you can reveal it
10101 // using firebug.
10102 $info['html'] .= '<div id="yui-module-debug" class="notifytiny">'.$details.'</div>';
10105 if (!empty($PERF->logwrites)) {
10106 $info['logwrites'] = $PERF->logwrites;
10107 $info['html'] .= '<span class="logwrites">Log DB writes '.$info['logwrites'].'</span> ';
10108 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
10111 $info['dbqueries'] = $DB->perf_get_reads().'/'.($DB->perf_get_writes() - $PERF->logwrites);
10112 $info['html'] .= '<span class="dbqueries">DB reads/writes: '.$info['dbqueries'].'</span> ';
10113 $info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
10115 if (function_exists('posix_times')) {
10116 $ptimes = posix_times();
10117 if (is_array($ptimes)) {
10118 foreach ($ptimes as $key => $val) {
10119 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
10121 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
10122 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
10126 // Grab the load average for the last minute
10127 // /proc will only work under some linux configurations
10128 // while uptime is there under MacOSX/Darwin and other unices
10129 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
10130 list($server_load) = explode(' ', $loadavg[0]);
10131 unset($loadavg);
10132 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
10133 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
10134 $server_load = $matches[1];
10135 } else {
10136 trigger_error('Could not parse uptime output!');
10139 if (!empty($server_load)) {
10140 $info['serverload'] = $server_load;
10141 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
10142 $info['txt'] .= "serverload: {$info['serverload']} ";
10145 // Display size of session if session started
10146 if (session_id()) {
10147 $info['sessionsize'] = display_size(strlen(session_encode()));
10148 $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
10149 $info['txt'] .= "Session: {$info['sessionsize']} ";
10152 /* if (isset($rcache->hits) && isset($rcache->misses)) {
10153 $info['rcachehits'] = $rcache->hits;
10154 $info['rcachemisses'] = $rcache->misses;
10155 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
10156 "{$rcache->hits}/{$rcache->misses}</span> ";
10157 $info['txt'] .= 'rcache: '.
10158 "{$rcache->hits}/{$rcache->misses} ";
10160 $info['html'] = '<div class="performanceinfo siteinfo">'.$info['html'].'</div>';
10161 return $info;
10165 * @todo Document this function linux people
10167 function apd_get_profiling() {
10168 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
10172 * Delete directory or only it's content
10174 * @param string $dir directory path
10175 * @param bool $content_only
10176 * @return bool success, true also if dir does not exist
10178 function remove_dir($dir, $content_only=false) {
10179 if (!file_exists($dir)) {
10180 // nothing to do
10181 return true;
10183 $handle = opendir($dir);
10184 $result = true;
10185 while (false!==($item = readdir($handle))) {
10186 if($item != '.' && $item != '..') {
10187 if(is_dir($dir.'/'.$item)) {
10188 $result = remove_dir($dir.'/'.$item) && $result;
10189 }else{
10190 $result = unlink($dir.'/'.$item) && $result;
10194 closedir($handle);
10195 if ($content_only) {
10196 clearstatcache(); // make sure file stat cache is properly invalidated
10197 return $result;
10199 $result = rmdir($dir); // if anything left the result will be false, no need for && $result
10200 clearstatcache(); // make sure file stat cache is properly invalidated
10201 return $result;
10205 * Detect if an object or a class contains a given property
10206 * will take an actual object or the name of a class
10208 * @param mix $obj Name of class or real object to test
10209 * @param string $property name of property to find
10210 * @return bool true if property exists
10212 function object_property_exists( $obj, $property ) {
10213 if (is_string( $obj )) {
10214 $properties = get_class_vars( $obj );
10216 else {
10217 $properties = get_object_vars( $obj );
10219 return array_key_exists( $property, $properties );
10223 * Converts an object into an associative array
10225 * This function converts an object into an associative array by iterating
10226 * over its public properties. Because this function uses the foreach
10227 * construct, Iterators are respected. It works recursively on arrays of objects.
10228 * Arrays and simple values are returned as is.
10230 * If class has magic properties, it can implement IteratorAggregate
10231 * and return all available properties in getIterator()
10233 * @param mixed $var
10234 * @return array
10236 function convert_to_array($var) {
10237 $result = array();
10238 $references = array();
10240 // loop over elements/properties
10241 foreach ($var as $key => $value) {
10242 // recursively convert objects
10243 if (is_object($value) || is_array($value)) {
10244 // but prevent cycles
10245 if (!in_array($value, $references)) {
10246 $result[$key] = convert_to_array($value);
10247 $references[] = $value;
10249 } else {
10250 // simple values are untouched
10251 $result[$key] = $value;
10254 return $result;
10258 * Detect a custom script replacement in the data directory that will
10259 * replace an existing moodle script
10261 * @return string|bool full path name if a custom script exists, false if no custom script exists
10263 function custom_script_path() {
10264 global $CFG, $SCRIPT;
10266 if ($SCRIPT === null) {
10267 // Probably some weird external script
10268 return false;
10271 $scriptpath = $CFG->customscripts . $SCRIPT;
10273 // check the custom script exists
10274 if (file_exists($scriptpath) and is_file($scriptpath)) {
10275 return $scriptpath;
10276 } else {
10277 return false;
10282 * Returns whether or not the user object is a remote MNET user. This function
10283 * is in moodlelib because it does not rely on loading any of the MNET code.
10285 * @global object
10286 * @param object $user A valid user object
10287 * @return bool True if the user is from a remote Moodle.
10289 function is_mnet_remote_user($user) {
10290 global $CFG;
10292 if (!isset($CFG->mnet_localhost_id)) {
10293 include_once $CFG->dirroot . '/mnet/lib.php';
10294 $env = new mnet_environment();
10295 $env->init();
10296 unset($env);
10299 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
10303 * This function will search for browser prefereed languages, setting Moodle
10304 * to use the best one available if $SESSION->lang is undefined
10306 * @global object
10307 * @global object
10308 * @global object
10310 function setup_lang_from_browser() {
10312 global $CFG, $SESSION, $USER;
10314 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
10315 // Lang is defined in session or user profile, nothing to do
10316 return;
10319 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
10320 return;
10323 /// Extract and clean langs from headers
10324 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
10325 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
10326 $rawlangs = explode(',', $rawlangs); // Convert to array
10327 $langs = array();
10329 $order = 1.0;
10330 foreach ($rawlangs as $lang) {
10331 if (strpos($lang, ';') === false) {
10332 $langs[(string)$order] = $lang;
10333 $order = $order-0.01;
10334 } else {
10335 $parts = explode(';', $lang);
10336 $pos = strpos($parts[1], '=');
10337 $langs[substr($parts[1], $pos+1)] = $parts[0];
10340 krsort($langs, SORT_NUMERIC);
10342 /// Look for such langs under standard locations
10343 foreach ($langs as $lang) {
10344 $lang = strtolower(clean_param($lang, PARAM_SAFEDIR)); // clean it properly for include
10345 if (get_string_manager()->translation_exists($lang, false)) {
10346 $SESSION->lang = $lang; /// Lang exists, set it in session
10347 break; /// We have finished. Go out
10350 return;
10354 * check if $url matches anything in proxybypass list
10356 * any errors just result in the proxy being used (least bad)
10358 * @global object
10359 * @param string $url url to check
10360 * @return boolean true if we should bypass the proxy
10362 function is_proxybypass( $url ) {
10363 global $CFG;
10365 // sanity check
10366 if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
10367 return false;
10370 // get the host part out of the url
10371 if (!$host = parse_url( $url, PHP_URL_HOST )) {
10372 return false;
10375 // get the possible bypass hosts into an array
10376 $matches = explode( ',', $CFG->proxybypass );
10378 // check for a match
10379 // (IPs need to match the left hand side and hosts the right of the url,
10380 // but we can recklessly check both as there can't be a false +ve)
10381 $bypass = false;
10382 foreach ($matches as $match) {
10383 $match = trim($match);
10385 // try for IP match (Left side)
10386 $lhs = substr($host,0,strlen($match));
10387 if (strcasecmp($match,$lhs)==0) {
10388 return true;
10391 // try for host match (Right side)
10392 $rhs = substr($host,-strlen($match));
10393 if (strcasecmp($match,$rhs)==0) {
10394 return true;
10398 // nothing matched.
10399 return false;
10403 ////////////////////////////////////////////////////////////////////////////////
10406 * Check if the passed navigation is of the new style
10408 * @param mixed $navigation
10409 * @return bool true for yes false for no
10411 function is_newnav($navigation) {
10412 if (is_array($navigation) && !empty($navigation['newnav'])) {
10413 return true;
10414 } else {
10415 return false;
10420 * Checks whether the given variable name is defined as a variable within the given object.
10422 * This will NOT work with stdClass objects, which have no class variables.
10424 * @param string $var The variable name
10425 * @param object $object The object to check
10426 * @return boolean
10428 function in_object_vars($var, $object) {
10429 $class_vars = get_class_vars(get_class($object));
10430 $class_vars = array_keys($class_vars);
10431 return in_array($var, $class_vars);
10435 * Returns an array without repeated objects.
10436 * This function is similar to array_unique, but for arrays that have objects as values
10438 * @param array $array
10439 * @param bool $keep_key_assoc
10440 * @return array
10442 function object_array_unique($array, $keep_key_assoc = true) {
10443 $duplicate_keys = array();
10444 $tmp = array();
10446 foreach ($array as $key=>$val) {
10447 // convert objects to arrays, in_array() does not support objects
10448 if (is_object($val)) {
10449 $val = (array)$val;
10452 if (!in_array($val, $tmp)) {
10453 $tmp[] = $val;
10454 } else {
10455 $duplicate_keys[] = $key;
10459 foreach ($duplicate_keys as $key) {
10460 unset($array[$key]);
10463 return $keep_key_assoc ? $array : array_values($array);
10467 * Is a userid the primary administrator?
10469 * @param int $userid int id of user to check
10470 * @return boolean
10472 function is_primary_admin($userid){
10473 $primaryadmin = get_admin();
10475 if($userid == $primaryadmin->id){
10476 return true;
10477 }else{
10478 return false;
10483 * Returns the site identifier
10485 * @global object
10486 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
10488 function get_site_identifier() {
10489 global $CFG;
10490 // Check to see if it is missing. If so, initialise it.
10491 if (empty($CFG->siteidentifier)) {
10492 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
10494 // Return it.
10495 return $CFG->siteidentifier;
10499 * Check whether the given password has no more than the specified
10500 * number of consecutive identical characters.
10502 * @param string $password password to be checked against the password policy
10503 * @param integer $maxchars maximum number of consecutive identical characters
10505 function check_consecutive_identical_characters($password, $maxchars) {
10507 if ($maxchars < 1) {
10508 return true; // 0 is to disable this check
10510 if (strlen($password) <= $maxchars) {
10511 return true; // too short to fail this test
10514 $previouschar = '';
10515 $consecutivecount = 1;
10516 foreach (str_split($password) as $char) {
10517 if ($char != $previouschar) {
10518 $consecutivecount = 1;
10520 else {
10521 $consecutivecount++;
10522 if ($consecutivecount > $maxchars) {
10523 return false; // check failed already
10527 $previouschar = $char;
10530 return true;
10534 * helper function to do partial function binding
10535 * so we can use it for preg_replace_callback, for example
10536 * this works with php functions, user functions, static methods and class methods
10537 * it returns you a callback that you can pass on like so:
10539 * $callback = partial('somefunction', $arg1, $arg2);
10540 * or
10541 * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
10542 * or even
10543 * $obj = new someclass();
10544 * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
10546 * and then the arguments that are passed through at calltime are appended to the argument list.
10548 * @param mixed $function a php callback
10549 * $param mixed $arg1.. $argv arguments to partially bind with
10551 * @return callback
10553 function partial() {
10554 if (!class_exists('partial')) {
10555 class partial{
10556 var $values = array();
10557 var $func;
10559 function __construct($func, $args) {
10560 $this->values = $args;
10561 $this->func = $func;
10564 function method() {
10565 $args = func_get_args();
10566 return call_user_func_array($this->func, array_merge($this->values, $args));
10570 $args = func_get_args();
10571 $func = array_shift($args);
10572 $p = new partial($func, $args);
10573 return array($p, 'method');
10577 * helper function to load up and initialise the mnet environment
10578 * this must be called before you use mnet functions.
10580 * @return mnet_environment the equivalent of old $MNET global
10582 function get_mnet_environment() {
10583 global $CFG;
10584 require_once($CFG->dirroot . '/mnet/lib.php');
10585 static $instance = null;
10586 if (empty($instance)) {
10587 $instance = new mnet_environment();
10588 $instance->init();
10590 return $instance;
10594 * during xmlrpc server code execution, any code wishing to access
10595 * information about the remote peer must use this to get it.
10597 * @return mnet_remote_client the equivalent of old $MNET_REMOTE_CLIENT global
10599 function get_mnet_remote_client() {
10600 if (!defined('MNET_SERVER')) {
10601 debugging(get_string('notinxmlrpcserver', 'mnet'));
10602 return false;
10604 global $MNET_REMOTE_CLIENT;
10605 if (isset($MNET_REMOTE_CLIENT)) {
10606 return $MNET_REMOTE_CLIENT;
10608 return false;
10612 * during the xmlrpc server code execution, this will be called
10613 * to setup the object returned by {@see get_mnet_remote_client}
10615 * @param mnet_remote_client $client the client to set up
10617 function set_mnet_remote_client($client) {
10618 if (!defined('MNET_SERVER')) {
10619 throw new moodle_exception('notinxmlrpcserver', 'mnet');
10621 global $MNET_REMOTE_CLIENT;
10622 $MNET_REMOTE_CLIENT = $client;
10626 * return the jump url for a given remote user
10627 * this is used for rewriting forum post links in emails, etc
10629 * @param stdclass $user the user to get the idp url for
10631 function mnet_get_idp_jump_url($user) {
10632 global $CFG;
10634 static $mnetjumps = array();
10635 if (!array_key_exists($user->mnethostid, $mnetjumps)) {
10636 $idp = mnet_get_peer_host($user->mnethostid);
10637 $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
10638 $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
10640 return $mnetjumps[$user->mnethostid];
10644 * Gets the homepage to use for the current user
10646 * @return int One of HOMEPAGE_*
10648 function get_home_page() {
10649 global $CFG;
10651 if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
10652 if ($CFG->defaulthomepage == HOMEPAGE_MY) {
10653 return HOMEPAGE_MY;
10654 } else {
10655 return (int)get_user_preferences('user_home_page_preference', HOMEPAGE_MY);
10658 return HOMEPAGE_SITE;
10662 * The lang_string class
10664 * This special class is used to create an object representation of a string request.
10665 * It is special because processing doesn't occur until the object is first used.
10666 * The class was created especially to aid performance in areas where strings were
10667 * required to be generated but were not necessarily used.
10668 * As an example the admin tree when generated uses over 1500 strings, of which
10669 * normally only 1/3 are ever actually printed at any time.
10670 * The performance advantage is achieved by not actually processing strings that
10671 * arn't being used, as such reducing the processing required for the page.
10673 * How to use the lang_string class?
10674 * There are two methods of using the lang_string class, first through the
10675 * forth argument of the get_string function, and secondly directly.
10676 * The following are examples of both.
10677 * 1. Through get_string calls e.g.
10678 * $string = get_string($identifier, $component, $a, true);
10679 * $string = get_string('yes', 'moodle', null, true);
10680 * 2. Direct instantiation
10681 * $string = new lang_string($identifier, $component, $a, $lang);
10682 * $string = new lang_string('yes');
10684 * How do I use a lang_string object?
10685 * The lang_string object makes use of a magic __toString method so that you
10686 * are able to use the object exactly as you would use a string in most cases.
10687 * This means you are able to collect it into a variable and then directly
10688 * echo it, or concatenate it into another string, or similar.
10689 * The other thing you can do is manually get the string by calling the
10690 * lang_strings out method e.g.
10691 * $string = new lang_string('yes');
10692 * $string->out();
10693 * Also worth noting is that the out method can take one argument, $lang which
10694 * allows the developer to change the language on the fly.
10696 * When should I use a lang_string object?
10697 * The lang_string object is designed to be used in any situation where a
10698 * string may not be needed, but needs to be generated.
10699 * The admin tree is a good example of where lang_string objects should be
10700 * used.
10701 * A more practical example would be any class that requries strings that may
10702 * not be printed (after all classes get renderer by renderers and who knows
10703 * what they will do ;))
10705 * When should I not use a lang_string object?
10706 * Don't use lang_strings when you are going to use a string immediately.
10707 * There is no need as it will be processed immediately and there will be no
10708 * advantage, and in fact perhaps a negative hit as a class has to be
10709 * instantiated for a lang_string object, however get_string won't require
10710 * that.
10712 * Limitations:
10713 * 1. You cannot use a lang_string object as an array offset. Doing so will
10714 * result in PHP throwing an error. (You can use it as an object property!)
10716 * @package core
10717 * @category string
10718 * @copyright 2011 Sam Hemelryk
10719 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
10721 class lang_string {
10723 /** @var string The strings identifier */
10724 protected $identifier;
10725 /** @var string The strings component. Default '' */
10726 protected $component = '';
10727 /** @var array|stdClass Any arguments required for the string. Default null */
10728 protected $a = null;
10729 /** @var string The language to use when processing the string. Default null */
10730 protected $lang = null;
10732 /** @var string The processed string (once processed) */
10733 protected $string = null;
10736 * A special boolean. If set to true then the object has been woken up and
10737 * cannot be regenerated. If this is set then $this->string MUST be used.
10738 * @var bool
10740 protected $forcedstring = false;
10743 * Constructs a lang_string object
10745 * This function should do as little processing as possible to ensure the best
10746 * performance for strings that won't be used.
10748 * @param string $identifier The strings identifier
10749 * @param string $component The strings component
10750 * @param stdClass|array $a Any arguments the string requires
10751 * @param string $lang The language to use when processing the string.
10753 public function __construct($identifier, $component = '', $a = null, $lang = null) {
10754 if (empty($component)) {
10755 $component = 'moodle';
10758 $this->identifier = $identifier;
10759 $this->component = $component;
10760 $this->lang = $lang;
10762 // We MUST duplicate $a to ensure that it if it changes by reference those
10763 // changes are not carried across.
10764 // To do this we always ensure $a or its properties/values are strings
10765 // and that any properties/values that arn't convertable are forgotten.
10766 if (!empty($a)) {
10767 if (is_scalar($a)) {
10768 $this->a = $a;
10769 } else if ($a instanceof lang_string) {
10770 $this->a = $a->out();
10771 } else if (is_object($a) or is_array($a)) {
10772 $a = (array)$a;
10773 $this->a = array();
10774 foreach ($a as $key => $value) {
10775 // Make sure conversion errors don't get displayed (results in '')
10776 if (is_array($value)) {
10777 $this->a[$key] = '';
10778 } else if (is_object($value)) {
10779 if (method_exists($value, '__toString')) {
10780 $this->a[$key] = $value->__toString();
10781 } else {
10782 $this->a[$key] = '';
10784 } else {
10785 $this->a[$key] = (string)$value;
10791 if (debugging(false, DEBUG_DEVELOPER)) {
10792 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
10793 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
10795 if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') {
10796 throw new coding_exception('Invalid string compontent. Please check your string definition');
10798 if (!get_string_manager()->string_exists($this->identifier, $this->component)) {
10799 debugging('String does not exist. Please check your string definition for '.$this->identifier.'/'.$this->component, DEBUG_DEVELOPER);
10805 * Processes the string.
10807 * This function actually processes the string, stores it in the string property
10808 * and then returns it.
10809 * You will notice that this function is VERY similar to the get_string method.
10810 * That is because it is pretty much doing the same thing.
10811 * However as this function is an upgrade it isn't as tolerant to backwards
10812 * compatability.
10814 * @return string
10816 protected function get_string() {
10817 global $CFG;
10819 // Check if we need to process the string
10820 if ($this->string === null) {
10821 // Check the quality of the identifier.
10822 if (clean_param($this->identifier, PARAM_STRINGID) == '') {
10823 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
10826 // Process the string
10827 $this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
10828 // Debugging feature lets you display string identifier and component
10829 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
10830 $this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
10833 // Return the string
10834 return $this->string;
10838 * Returns the string
10840 * @param string $lang The langauge to use when processing the string
10841 * @return string
10843 public function out($lang = null) {
10844 if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
10845 if ($this->forcedstring) {
10846 debugging('lang_string objects that have been serialised and unserialised cannot be printed in another language. ('.$this->lang.' used)', DEBUG_DEVELOPER);
10847 return $this->get_string();
10849 $translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
10850 return $translatedstring->out();
10852 return $this->get_string();
10856 * Magic __toString method for printing a string
10858 * @return string
10860 public function __toString() {
10861 return $this->get_string();
10865 * Magic __set_state method used for var_export
10867 * @return string
10869 public function __set_state() {
10870 return $this->get_string();
10874 * Prepares the lang_string for sleep and stores only the forcedstring and
10875 * string properties... the string cannot be regenerated so we need to ensure
10876 * it is generated for this.
10878 * @return string
10880 public function __sleep() {
10881 $this->get_string();
10882 $this->forcedstring = true;
10883 return array('forcedstring', 'string', 'lang');