timeline: if a section is set to hidden and the user is not capable of editing a...
[moodle-blog-course-format.git] / lib / moodlelib.php
blob18038da47e9451ca98f2461c211964b9db2c0ab8
1 <?php // $Id$
3 ///////////////////////////////////////////////////////////////////////////
4 // //
5 // NOTICE OF COPYRIGHT //
6 // //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment //
8 // http://moodle.org //
9 // //
10 // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
11 // //
12 // This program is free software; you can redistribute it and/or modify //
13 // it under the terms of the GNU General Public License as published by //
14 // the Free Software Foundation; either version 2 of the License, or //
15 // (at your option) any later version. //
16 // //
17 // This program is distributed in the hope that it will be useful, //
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
20 // GNU General Public License for more details: //
21 // //
22 // http://www.gnu.org/copyleft/gpl.html //
23 // //
24 ///////////////////////////////////////////////////////////////////////////
26 /**
27 * moodlelib.php - Moodle main library
29 * Main library file of miscellaneous general-purpose Moodle functions.
30 * Other main libraries:
31 * - weblib.php - functions that produce web output
32 * - datalib.php - functions that access the database
33 * @author Martin Dougiamas
34 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
35 * @package moodlecore
38 /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
40 /**
41 * Used by some scripts to check they are being called by Moodle
43 define('MOODLE_INTERNAL', true);
45 /// Date and time constants ///
46 /**
47 * Time constant - the number of seconds in a year
50 define('YEARSECS', 31536000);
52 /**
53 * Time constant - the number of seconds in a week
55 define('WEEKSECS', 604800);
57 /**
58 * Time constant - the number of seconds in a day
60 define('DAYSECS', 86400);
62 /**
63 * Time constant - the number of seconds in an hour
65 define('HOURSECS', 3600);
67 /**
68 * Time constant - the number of seconds in a minute
70 define('MINSECS', 60);
72 /**
73 * Time constant - the number of minutes in a day
75 define('DAYMINS', 1440);
77 /**
78 * Time constant - the number of minutes in an hour
80 define('HOURMINS', 60);
82 /// Parameter constants - every call to optional_param(), required_param() ///
83 /// or clean_param() should have a specified type of parameter. //////////////
85 /**
86 * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
87 * originally was 0, but changed because we need to detect unknown
88 * parameter types and swiched order in clean_param().
90 define('PARAM_RAW', 666);
92 /**
93 * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
94 * It was one of the first types, that is why it is abused so much ;-)
96 define('PARAM_CLEAN', 0x0001);
98 /**
99 * PARAM_INT - integers only, use when expecting only numbers.
101 define('PARAM_INT', 0x0002);
104 * PARAM_INTEGER - an alias for PARAM_INT
106 define('PARAM_INTEGER', 0x0002);
109 * PARAM_NUMBER - a real/floating point number.
111 define('PARAM_NUMBER', 0x000a);
114 * PARAM_ALPHA - contains only english letters.
116 define('PARAM_ALPHA', 0x0004);
119 * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
120 * @TODO: should we alias it to PARAM_ALPHANUM ?
122 define('PARAM_ACTION', 0x0004);
125 * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
126 * @TODO: should we alias it to PARAM_ALPHANUM ?
128 define('PARAM_FORMAT', 0x0004);
131 * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
133 define('PARAM_NOTAGS', 0x0008);
136 * PARAM_MULTILANG - alias of PARAM_TEXT.
138 define('PARAM_MULTILANG', 0x0009);
141 * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
143 define('PARAM_TEXT', 0x0009);
146 * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
148 define('PARAM_FILE', 0x0010);
151 * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
153 define('PARAM_TAG', 0x0011);
156 * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
158 define('PARAM_TAGLIST', 0x0012);
161 * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
162 * note: the leading slash is not removed, window drive letter is not allowed
164 define('PARAM_PATH', 0x0020);
167 * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
169 define('PARAM_HOST', 0x0040);
172 * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
174 define('PARAM_URL', 0x0080);
177 * 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!)
179 define('PARAM_LOCALURL', 0x0180);
182 * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
183 * use when you want to store a new file submitted by students
185 define('PARAM_CLEANFILE',0x0200);
188 * PARAM_ALPHANUM - expected numbers and letters only.
190 define('PARAM_ALPHANUM', 0x0400);
193 * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
195 define('PARAM_BOOL', 0x0800);
198 * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
199 * note: do not forget to addslashes() before storing into database!
201 define('PARAM_CLEANHTML',0x1000);
204 * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
205 * suitable for include() and require()
206 * @TODO: should we rename this function to PARAM_SAFEDIRS??
208 define('PARAM_ALPHAEXT', 0x2000);
211 * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
213 define('PARAM_SAFEDIR', 0x4000);
216 * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
218 define('PARAM_SEQUENCE', 0x8000);
221 * PARAM_PEM - Privacy Enhanced Mail format
223 define('PARAM_PEM', 0x10000);
226 * PARAM_BASE64 - Base 64 encoded format
228 define('PARAM_BASE64', 0x20000);
231 /// Page types ///
233 * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
235 define('PAGE_COURSE_VIEW', 'course-view');
237 /// Debug levels ///
238 /** no warnings at all */
239 define ('DEBUG_NONE', 0);
240 /** E_ERROR | E_PARSE */
241 define ('DEBUG_MINIMAL', 5);
242 /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
243 define ('DEBUG_NORMAL', 15);
244 /** E_ALL without E_STRICT for now, do show recoverable fatal errors */
245 define ('DEBUG_ALL', 6143);
246 /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
247 define ('DEBUG_DEVELOPER', 38911);
250 * Blog access level constant declaration
252 define ('BLOG_USER_LEVEL', 1);
253 define ('BLOG_GROUP_LEVEL', 2);
254 define ('BLOG_COURSE_LEVEL', 3);
255 define ('BLOG_SITE_LEVEL', 4);
256 define ('BLOG_GLOBAL_LEVEL', 5);
259 * Tag constanst
261 //To prevent problems with multibytes strings, this should not exceed the
262 //length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
263 define('TAG_MAX_LENGTH', 50);
266 * Password policy constants
268 define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
269 define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
270 define ('PASSWORD_DIGITS', '0123456789');
271 define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
273 if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
274 define('SORT_LOCALE_STRING', SORT_STRING);
278 /// PARAMETER HANDLING ////////////////////////////////////////////////////
281 * Returns a particular value for the named variable, taken from
282 * POST or GET. If the parameter doesn't exist then an error is
283 * thrown because we require this variable.
285 * This function should be used to initialise all required values
286 * in a script that are based on parameters. Usually it will be
287 * used like this:
288 * $id = required_param('id');
290 * @param string $parname the name of the page parameter we want
291 * @param int $type expected type of parameter
292 * @return mixed
294 function required_param($parname, $type=PARAM_CLEAN) {
296 // detect_unchecked_vars addition
297 global $CFG;
298 if (!empty($CFG->detect_unchecked_vars)) {
299 global $UNCHECKED_VARS;
300 unset ($UNCHECKED_VARS->vars[$parname]);
303 if (isset($_POST[$parname])) { // POST has precedence
304 $param = $_POST[$parname];
305 } else if (isset($_GET[$parname])) {
306 $param = $_GET[$parname];
307 } else {
308 error('A required parameter ('.$parname.') was missing');
311 return clean_param($param, $type);
315 * Returns a particular value for the named variable, taken from
316 * POST or GET, otherwise returning a given default.
318 * This function should be used to initialise all optional values
319 * in a script that are based on parameters. Usually it will be
320 * used like this:
321 * $name = optional_param('name', 'Fred');
323 * @param string $parname the name of the page parameter we want
324 * @param mixed $default the default value to return if nothing is found
325 * @param int $type expected type of parameter
326 * @return mixed
328 function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
330 // detect_unchecked_vars addition
331 global $CFG;
332 if (!empty($CFG->detect_unchecked_vars)) {
333 global $UNCHECKED_VARS;
334 unset ($UNCHECKED_VARS->vars[$parname]);
337 if (isset($_POST[$parname])) { // POST has precedence
338 $param = $_POST[$parname];
339 } else if (isset($_GET[$parname])) {
340 $param = $_GET[$parname];
341 } else {
342 return $default;
345 return clean_param($param, $type);
349 * Used by {@link optional_param()} and {@link required_param()} to
350 * clean the variables and/or cast to specific types, based on
351 * an options field.
352 * <code>
353 * $course->format = clean_param($course->format, PARAM_ALPHA);
354 * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
355 * </code>
357 * @uses $CFG
358 * @uses PARAM_RAW
359 * @uses PARAM_CLEAN
360 * @uses PARAM_CLEANHTML
361 * @uses PARAM_INT
362 * @uses PARAM_NUMBER
363 * @uses PARAM_ALPHA
364 * @uses PARAM_ALPHANUM
365 * @uses PARAM_ALPHAEXT
366 * @uses PARAM_SEQUENCE
367 * @uses PARAM_BOOL
368 * @uses PARAM_NOTAGS
369 * @uses PARAM_TEXT
370 * @uses PARAM_SAFEDIR
371 * @uses PARAM_CLEANFILE
372 * @uses PARAM_FILE
373 * @uses PARAM_PATH
374 * @uses PARAM_HOST
375 * @uses PARAM_URL
376 * @uses PARAM_LOCALURL
377 * @uses PARAM_PEM
378 * @uses PARAM_BASE64
379 * @uses PARAM_TAG
380 * @uses PARAM_SEQUENCE
381 * @param mixed $param the variable we are cleaning
382 * @param int $type expected format of param after cleaning.
383 * @return mixed
385 function clean_param($param, $type) {
387 global $CFG;
389 if (is_array($param)) { // Let's loop
390 $newparam = array();
391 foreach ($param as $key => $value) {
392 $newparam[$key] = clean_param($value, $type);
394 return $newparam;
397 switch ($type) {
398 case PARAM_RAW: // no cleaning at all
399 return $param;
401 case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
402 if (is_numeric($param)) {
403 return $param;
405 $param = stripslashes($param); // Needed for kses to work fine
406 $param = clean_text($param); // Sweep for scripts, etc
407 return addslashes($param); // Restore original request parameter slashes
409 case PARAM_CLEANHTML: // prepare html fragment for display, do not store it into db!!
410 $param = stripslashes($param); // Remove any slashes
411 $param = clean_text($param); // Sweep for scripts, etc
412 return trim($param);
414 case PARAM_INT:
415 return (int)$param; // Convert to integer
417 case PARAM_NUMBER:
418 return (float)$param; // Convert to integer
420 case PARAM_ALPHA: // Remove everything not a-z
421 return eregi_replace('[^a-zA-Z]', '', $param);
423 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
424 return eregi_replace('[^A-Za-z0-9]', '', $param);
426 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z/_-
427 return eregi_replace('[^a-zA-Z/_-]', '', $param);
429 case PARAM_SEQUENCE: // Remove everything not 0-9,
430 return eregi_replace('[^0-9,]', '', $param);
432 case PARAM_BOOL: // Convert to 1 or 0
433 $tempstr = strtolower($param);
434 if ($tempstr == 'on' or $tempstr == 'yes' ) {
435 $param = 1;
436 } else if ($tempstr == 'off' or $tempstr == 'no') {
437 $param = 0;
438 } else {
439 $param = empty($param) ? 0 : 1;
441 return $param;
443 case PARAM_NOTAGS: // Strip all tags
444 return strip_tags($param);
446 case PARAM_TEXT: // leave only tags needed for multilang
447 return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
449 case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
450 return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
452 case PARAM_CLEANFILE: // allow only safe characters
453 return clean_filename($param);
455 case PARAM_FILE: // Strip all suspicious characters from filename
456 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
457 $param = ereg_replace('\.\.+', '', $param);
458 if($param == '.') {
459 $param = '';
461 return $param;
463 case PARAM_PATH: // Strip all suspicious characters from file path
464 $param = str_replace('\\\'', '\'', $param);
465 $param = str_replace('\\"', '"', $param);
466 $param = str_replace('\\', '/', $param);
467 $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
468 $param = ereg_replace('\.\.+', '', $param);
469 $param = ereg_replace('//+', '/', $param);
470 return ereg_replace('/(\./)+', '/', $param);
472 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
473 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
474 // match ipv4 dotted quad
475 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
476 // confirm values are ok
477 if ( $match[0] > 255
478 || $match[1] > 255
479 || $match[3] > 255
480 || $match[4] > 255 ) {
481 // hmmm, what kind of dotted quad is this?
482 $param = '';
484 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
485 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
486 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
488 // all is ok - $param is respected
489 } else {
490 // all is not ok...
491 $param='';
493 return $param;
495 case PARAM_URL: // allow safe ftp, http, mailto urls
496 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
497 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
498 // all is ok, param is respected
499 } else {
500 $param =''; // not really ok
502 return $param;
504 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
505 $param = clean_param($param, PARAM_URL);
506 if (!empty($param)) {
507 if (preg_match(':^/:', $param)) {
508 // root-relative, ok!
509 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
510 // absolute, and matches our wwwroot
511 } else {
512 // relative - let's make sure there are no tricks
513 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
514 // looks ok.
515 } else {
516 $param = '';
520 return $param;
522 case PARAM_PEM:
523 $param = trim($param);
524 // PEM formatted strings may contain letters/numbers and the symbols
525 // forward slash: /
526 // plus sign: +
527 // equal sign: =
528 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
529 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
530 list($wholething, $body) = $matches;
531 unset($wholething, $matches);
532 $b64 = clean_param($body, PARAM_BASE64);
533 if (!empty($b64)) {
534 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
535 } else {
536 return '';
539 return '';
541 case PARAM_BASE64:
542 if (!empty($param)) {
543 // PEM formatted strings may contain letters/numbers and the symbols
544 // forward slash: /
545 // plus sign: +
546 // equal sign: =
547 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
548 return '';
550 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
551 // Each line of base64 encoded data must be 64 characters in
552 // length, except for the last line which may be less than (or
553 // equal to) 64 characters long.
554 for ($i=0, $j=count($lines); $i < $j; $i++) {
555 if ($i + 1 == $j) {
556 if (64 < strlen($lines[$i])) {
557 return '';
559 continue;
562 if (64 != strlen($lines[$i])) {
563 return '';
566 return implode("\n",$lines);
567 } else {
568 return '';
571 case PARAM_TAG:
572 //as long as magic_quotes_gpc is used, a backslash will be a
573 //problem, so remove *all* backslash.
574 $param = str_replace('\\', '', $param);
575 //convert many whitespace chars into one
576 $param = preg_replace('/\s+/', ' ', $param);
577 $textlib = textlib_get_instance();
578 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
579 return $param;
582 case PARAM_TAGLIST:
583 $tags = explode(',', $param);
584 $result = array();
585 foreach ($tags as $tag) {
586 $res = clean_param($tag, PARAM_TAG);
587 if ($res != '') {
588 $result[] = $res;
591 if ($result) {
592 return implode(',', $result);
593 } else {
594 return '';
597 default: // throw error, switched parameters in optional_param or another serious problem
598 error("Unknown parameter type: $type");
603 * Return true if given value is integer or string with integer value
605 * @param mixed $value String or Int
606 * @return bool true if number, false if not
608 function is_number($value) {
609 if (is_int($value)) {
610 return true;
611 } else if (is_string($value)) {
612 return ((string)(int)$value) === $value;
613 } else {
614 return false;
619 * This function is useful for testing whether something you got back from
620 * the HTML editor actually contains anything. Sometimes the HTML editor
621 * appear to be empty, but actually you get back a <br> tag or something.
623 * @param string $string a string containing HTML.
624 * @return boolean does the string contain any actual content - that is text,
625 * images, objcts, etc.
627 function html_is_blank($string) {
628 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
632 * Set a key in global configuration
634 * Set a key/value pair in both this session's {@link $CFG} global variable
635 * and in the 'config' database table for future sessions.
637 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
638 * In that case it doesn't affect $CFG.
640 * A NULL value will delete the entry.
642 * @param string $name the key to set
643 * @param string $value the value to set (without magic quotes)
644 * @param string $plugin (optional) the plugin scope
645 * @uses $CFG
646 * @return bool
648 function set_config($name, $value, $plugin=NULL) {
649 /// No need for get_config because they are usually always available in $CFG
651 global $CFG;
653 if (empty($plugin)) {
654 if (!array_key_exists($name, $CFG->config_php_settings)) {
655 // So it's defined for this invocation at least
656 if (is_null($value)) {
657 unset($CFG->$name);
658 } else {
659 $CFG->$name = (string)$value; // settings from db are always strings
663 if (get_field('config', 'name', 'name', $name)) {
664 if ($value===null) {
665 return delete_records('config', 'name', $name);
666 } else {
667 return set_field('config', 'value', addslashes($value), 'name', $name);
669 } else {
670 if ($value===null) {
671 return true;
673 $config = new object();
674 $config->name = $name;
675 $config->value = addslashes($value);
676 return insert_record('config', $config);
678 } else { // plugin scope
679 if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
680 if ($value===null) {
681 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
682 } else {
683 return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
685 } else {
686 if ($value===null) {
687 return true;
689 $config = new object();
690 $config->plugin = addslashes($plugin);
691 $config->name = $name;
692 $config->value = addslashes($value);
693 return insert_record('config_plugins', $config);
699 * Get configuration values from the global config table
700 * or the config_plugins table.
702 * If called with no parameters it will do the right thing
703 * generating $CFG safely from the database without overwriting
704 * existing values.
706 * If called with 2 parameters it will return a $string single
707 * value or false of the value is not found.
709 * @param string $plugin
710 * @param string $name
711 * @uses $CFG
712 * @return hash-like object or single value
715 function get_config($plugin=NULL, $name=NULL) {
717 global $CFG;
719 if (!empty($name)) { // the user is asking for a specific value
720 if (!empty($plugin)) {
721 return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
722 } else {
723 return get_field('config', 'value', 'name', $name);
727 // the user is after a recordset
728 if (!empty($plugin)) {
729 if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
730 $configs = (array)$configs;
731 $localcfg = array();
732 foreach ($configs as $config) {
733 $localcfg[$config->name] = $config->value;
735 return (object)$localcfg;
736 } else {
737 return false;
739 } else {
740 // this was originally in setup.php
741 if ($configs = get_records('config')) {
742 $localcfg = (array)$CFG;
743 foreach ($configs as $config) {
744 if (!isset($localcfg[$config->name])) {
745 $localcfg[$config->name] = $config->value;
747 // do not complain anymore if config.php overrides settings from db
750 $localcfg = (object)$localcfg;
751 return $localcfg;
752 } else {
753 // preserve $CFG if DB returns nothing or error
754 return $CFG;
761 * Removes a key from global configuration
763 * @param string $name the key to set
764 * @param string $plugin (optional) the plugin scope
765 * @uses $CFG
766 * @return bool
768 function unset_config($name, $plugin=NULL) {
770 global $CFG;
772 unset($CFG->$name);
774 if (empty($plugin)) {
775 return delete_records('config', 'name', $name);
776 } else {
777 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
782 * Get volatile flags
784 * @param string $type
785 * @param int $changedsince
786 * @return records array
789 function get_cache_flags($type, $changedsince=NULL) {
791 $type = addslashes($type);
793 $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
794 if ($changedsince !== NULL) {
795 $changedsince = (int)$changedsince;
796 $sqlwhere .= ' AND timemodified > ' . $changedsince;
798 $cf = array();
799 if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
800 foreach ($flags as $flag) {
801 $cf[$flag->name] = $flag->value;
804 return $cf;
808 * Use this funciton to get a list of users from a config setting of type admin_setting_users_with_capability.
809 * @param string $value the value of the config setting.
810 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
811 * @return array of user objects.
813 function get_users_from_config($value, $capability) {
814 global $CFG;
815 if ($value == '$@ALL@$') {
816 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
817 } else if ($value) {
818 $usernames = explode(',', $value);
819 $users = get_records_select('user', "username IN ('" . implode("','", $usernames) . "') AND mnethostid = " . $CFG->mnet_localhost_id);
820 } else {
821 $users = array();
823 return $users;
827 * Get volatile flags
829 * @param string $type
830 * @param string $name
831 * @param int $changedsince
832 * @return records array
835 function get_cache_flag($type, $name, $changedsince=NULL) {
837 $type = addslashes($type);
838 $name = addslashes($name);
840 $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
841 if ($changedsince !== NULL) {
842 $changedsince = (int)$changedsince;
843 $sqlwhere .= ' AND timemodified > ' . $changedsince;
845 return get_field_select('cache_flags', 'value', $sqlwhere);
849 * Set a volatile flag
851 * @param string $type the "type" namespace for the key
852 * @param string $name the key to set
853 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
854 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
855 * @return bool
857 function set_cache_flag($type, $name, $value, $expiry=NULL) {
860 $timemodified = time();
861 if ($expiry===NULL || $expiry < $timemodified) {
862 $expiry = $timemodified + 24 * 60 * 60;
863 } else {
864 $expiry = (int)$expiry;
867 if ($value === NULL) {
868 return unset_cache_flag($type,$name);
871 $type = addslashes($type);
872 $name = addslashes($name);
873 if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
874 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
875 return true; //no need to update; helps rcache too
877 $f->value = addslashes($value);
878 $f->expiry = $expiry;
879 $f->timemodified = $timemodified;
880 return update_record('cache_flags', $f);
881 } else {
882 $f = new object();
883 $f->flagtype = $type;
884 $f->name = $name;
885 $f->value = addslashes($value);
886 $f->expiry = $expiry;
887 $f->timemodified = $timemodified;
888 return (bool)insert_record('cache_flags', $f);
893 * Removes a single volatile flag
895 * @param string $type the "type" namespace for the key
896 * @param string $name the key to set
897 * @uses $CFG
898 * @return bool
900 function unset_cache_flag($type, $name) {
902 return delete_records('cache_flags',
903 'name', addslashes($name),
904 'flagtype', addslashes($type));
908 * Garbage-collect volatile flags
911 function gc_cache_flags() {
912 return delete_records_select('cache_flags', 'expiry < ' . time());
916 * Refresh current $USER session global variable with all their current preferences.
917 * @uses $USER
919 function reload_user_preferences() {
921 global $USER;
923 //reset preference
924 $USER->preference = array();
926 if (!isloggedin() or isguestuser()) {
927 // no permanent storage for not-logged-in user and guest
929 } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
930 foreach ($preferences as $preference) {
931 $USER->preference[$preference->name] = $preference->value;
935 return true;
939 * Sets a preference for the current user
940 * Optionally, can set a preference for a different user object
941 * @uses $USER
942 * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
944 * @param string $name The key to set as preference for the specified user
945 * @param string $value The value to set forthe $name key in the specified user's record
946 * @param int $otheruserid A moodle user ID
947 * @return bool
949 function set_user_preference($name, $value, $otheruserid=NULL) {
951 global $USER;
953 if (!isset($USER->preference)) {
954 reload_user_preferences();
957 if (empty($name)) {
958 return false;
961 $nostore = false;
963 if (empty($otheruserid)){
964 if (!isloggedin() or isguestuser()) {
965 $nostore = true;
967 $userid = $USER->id;
968 } else {
969 if (isguestuser($otheruserid)) {
970 $nostore = true;
972 $userid = $otheruserid;
975 $return = true;
976 if ($nostore) {
977 // no permanent storage for not-logged-in user and guest
979 } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
980 if ($preference->value === $value) {
981 return true;
983 if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
984 $return = false;
987 } else {
988 $preference = new object();
989 $preference->userid = $userid;
990 $preference->name = addslashes($name);
991 $preference->value = addslashes((string)$value);
992 if (!insert_record('user_preferences', $preference)) {
993 $return = false;
997 // update value in USER session if needed
998 if ($userid == $USER->id) {
999 $USER->preference[$name] = (string)$value;
1002 return $return;
1006 * Unsets a preference completely by deleting it from the database
1007 * Optionally, can set a preference for a different user id
1008 * @uses $USER
1009 * @param string $name The key to unset as preference for the specified user
1010 * @param int $otheruserid A moodle user ID
1012 function unset_user_preference($name, $otheruserid=NULL) {
1014 global $USER;
1016 if (!isset($USER->preference)) {
1017 reload_user_preferences();
1020 if (empty($otheruserid)){
1021 $userid = $USER->id;
1022 } else {
1023 $userid = $otheruserid;
1026 //Delete the preference from $USER if needed
1027 if ($userid == $USER->id) {
1028 unset($USER->preference[$name]);
1031 //Then from DB
1032 return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
1037 * Sets a whole array of preferences for the current user
1038 * @param array $prefarray An array of key/value pairs to be set
1039 * @param int $otheruserid A moodle user ID
1040 * @return bool
1042 function set_user_preferences($prefarray, $otheruserid=NULL) {
1044 if (!is_array($prefarray) or empty($prefarray)) {
1045 return false;
1048 $return = true;
1049 foreach ($prefarray as $name => $value) {
1050 // The order is important; test for return is done first
1051 $return = (set_user_preference($name, $value, $otheruserid) && $return);
1053 return $return;
1057 * If no arguments are supplied this function will return
1058 * all of the current user preferences as an array.
1059 * If a name is specified then this function
1060 * attempts to return that particular preference value. If
1061 * none is found, then the optional value $default is returned,
1062 * otherwise NULL.
1063 * @param string $name Name of the key to use in finding a preference value
1064 * @param string $default Value to be returned if the $name key is not set in the user preferences
1065 * @param int $otheruserid A moodle user ID
1066 * @uses $USER
1067 * @return string
1069 function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
1070 global $USER;
1072 if (!isset($USER->preference)) {
1073 reload_user_preferences();
1076 if (empty($otheruserid)){
1077 $userid = $USER->id;
1078 } else {
1079 $userid = $otheruserid;
1082 if ($userid == $USER->id) {
1083 $preference = $USER->preference;
1085 } else {
1086 $preference = array();
1087 if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
1088 foreach ($prefdata as $pref) {
1089 $preference[$pref->name] = $pref->value;
1094 if (empty($name)) {
1095 return $preference; // All values
1097 } else if (array_key_exists($name, $preference)) {
1098 return $preference[$name]; // The single value
1100 } else {
1101 return $default; // Default value (or NULL)
1106 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1109 * Given date parts in user time produce a GMT timestamp.
1111 * @param int $year The year part to create timestamp of
1112 * @param int $month The month part to create timestamp of
1113 * @param int $day The day part to create timestamp of
1114 * @param int $hour The hour part to create timestamp of
1115 * @param int $minute The minute part to create timestamp of
1116 * @param int $second The second part to create timestamp of
1117 * @param float $timezone ?
1118 * @param bool $applydst ?
1119 * @return int timestamp
1120 * @todo Finish documenting this function
1122 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1124 $strtimezone = NULL;
1125 if (!is_numeric($timezone)) {
1126 $strtimezone = $timezone;
1129 $timezone = get_user_timezone_offset($timezone);
1131 if (abs($timezone) > 13) {
1132 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1133 } else {
1134 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1135 $time = usertime($time, $timezone);
1136 if($applydst) {
1137 $time -= dst_offset_on($time, $strtimezone);
1141 return $time;
1146 * Given an amount of time in seconds, returns string
1147 * formatted nicely as weeks, days, hours etc as needed
1149 * @uses MINSECS
1150 * @uses HOURSECS
1151 * @uses DAYSECS
1152 * @uses YEARSECS
1153 * @param int $totalsecs ?
1154 * @param array $str ?
1155 * @return string
1157 function format_time($totalsecs, $str=NULL) {
1159 $totalsecs = abs($totalsecs);
1161 if (!$str) { // Create the str structure the slow way
1162 $str->day = get_string('day');
1163 $str->days = get_string('days');
1164 $str->hour = get_string('hour');
1165 $str->hours = get_string('hours');
1166 $str->min = get_string('min');
1167 $str->mins = get_string('mins');
1168 $str->sec = get_string('sec');
1169 $str->secs = get_string('secs');
1170 $str->year = get_string('year');
1171 $str->years = get_string('years');
1175 $years = floor($totalsecs/YEARSECS);
1176 $remainder = $totalsecs - ($years*YEARSECS);
1177 $days = floor($remainder/DAYSECS);
1178 $remainder = $totalsecs - ($days*DAYSECS);
1179 $hours = floor($remainder/HOURSECS);
1180 $remainder = $remainder - ($hours*HOURSECS);
1181 $mins = floor($remainder/MINSECS);
1182 $secs = $remainder - ($mins*MINSECS);
1184 $ss = ($secs == 1) ? $str->sec : $str->secs;
1185 $sm = ($mins == 1) ? $str->min : $str->mins;
1186 $sh = ($hours == 1) ? $str->hour : $str->hours;
1187 $sd = ($days == 1) ? $str->day : $str->days;
1188 $sy = ($years == 1) ? $str->year : $str->years;
1190 $oyears = '';
1191 $odays = '';
1192 $ohours = '';
1193 $omins = '';
1194 $osecs = '';
1196 if ($years) $oyears = $years .' '. $sy;
1197 if ($days) $odays = $days .' '. $sd;
1198 if ($hours) $ohours = $hours .' '. $sh;
1199 if ($mins) $omins = $mins .' '. $sm;
1200 if ($secs) $osecs = $secs .' '. $ss;
1202 if ($years) return trim($oyears .' '. $odays);
1203 if ($days) return trim($odays .' '. $ohours);
1204 if ($hours) return trim($ohours .' '. $omins);
1205 if ($mins) return trim($omins .' '. $osecs);
1206 if ($secs) return $osecs;
1207 return get_string('now');
1211 * Returns a formatted string that represents a date in user time
1212 * <b>WARNING: note that the format is for strftime(), not date().</b>
1213 * Because of a bug in most Windows time libraries, we can't use
1214 * the nicer %e, so we have to use %d which has leading zeroes.
1215 * A lot of the fuss in the function is just getting rid of these leading
1216 * zeroes as efficiently as possible.
1218 * If parameter fixday = true (default), then take off leading
1219 * zero from %d, else mantain it.
1221 * @uses HOURSECS
1222 * @param int $date timestamp in GMT
1223 * @param string $format strftime format
1224 * @param float $timezone
1225 * @param bool $fixday If true (default) then the leading
1226 * zero from %d is removed. If false then the leading zero is mantained.
1227 * @return string
1229 function userdate($date, $format='', $timezone=99, $fixday = true) {
1231 global $CFG;
1233 $strtimezone = NULL;
1234 if (!is_numeric($timezone)) {
1235 $strtimezone = $timezone;
1238 if (empty($format)) {
1239 $format = get_string('strftimedaydatetime');
1242 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
1243 $fixday = false;
1244 } else if ($fixday) {
1245 $formatnoday = str_replace('%d', 'DD', $format);
1246 $fixday = ($formatnoday != $format);
1249 $date += dst_offset_on($date, $strtimezone);
1251 $timezone = get_user_timezone_offset($timezone);
1253 if (abs($timezone) > 13) { /// Server time
1254 if ($fixday) {
1255 $datestring = strftime($formatnoday, $date);
1256 $daystring = str_replace(' 0', '', strftime(' %d', $date));
1257 $datestring = str_replace('DD', $daystring, $datestring);
1258 } else {
1259 $datestring = strftime($format, $date);
1261 } else {
1262 $date += (int)($timezone * 3600);
1263 if ($fixday) {
1264 $datestring = gmstrftime($formatnoday, $date);
1265 $daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
1266 $datestring = str_replace('DD', $daystring, $datestring);
1267 } else {
1268 $datestring = gmstrftime($format, $date);
1272 /// If we are running under Windows convert from windows encoding to UTF-8
1273 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1275 if ($CFG->ostype == 'WINDOWS') {
1276 if ($localewincharset = get_string('localewincharset')) {
1277 $textlib = textlib_get_instance();
1278 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1282 return $datestring;
1286 * Given a $time timestamp in GMT (seconds since epoch),
1287 * returns an array that represents the date in user time
1289 * @uses HOURSECS
1290 * @param int $time Timestamp in GMT
1291 * @param float $timezone ?
1292 * @return array An array that represents the date in user time
1293 * @todo Finish documenting this function
1295 function usergetdate($time, $timezone=99) {
1297 $strtimezone = NULL;
1298 if (!is_numeric($timezone)) {
1299 $strtimezone = $timezone;
1302 $timezone = get_user_timezone_offset($timezone);
1304 if (abs($timezone) > 13) { // Server time
1305 return getdate($time);
1308 // There is no gmgetdate so we use gmdate instead
1309 $time += dst_offset_on($time, $strtimezone);
1310 $time += intval((float)$timezone * HOURSECS);
1312 $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
1314 list(
1315 $getdate['seconds'],
1316 $getdate['minutes'],
1317 $getdate['hours'],
1318 $getdate['mday'],
1319 $getdate['mon'],
1320 $getdate['year'],
1321 $getdate['wday'],
1322 $getdate['yday'],
1323 $getdate['weekday'],
1324 $getdate['month']
1325 ) = explode('_', $datestring);
1327 return $getdate;
1331 * Given a GMT timestamp (seconds since epoch), offsets it by
1332 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1334 * @uses HOURSECS
1335 * @param int $date Timestamp in GMT
1336 * @param float $timezone
1337 * @return int
1339 function usertime($date, $timezone=99) {
1341 $timezone = get_user_timezone_offset($timezone);
1343 if (abs($timezone) > 13) {
1344 return $date;
1346 return $date - (int)($timezone * HOURSECS);
1350 * Given a time, return the GMT timestamp of the most recent midnight
1351 * for the current user.
1353 * @param int $date Timestamp in GMT
1354 * @param float $timezone ?
1355 * @return ?
1357 function usergetmidnight($date, $timezone=99) {
1359 $userdate = usergetdate($date, $timezone);
1361 // Time of midnight of this user's day, in GMT
1362 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1367 * Returns a string that prints the user's timezone
1369 * @param float $timezone The user's timezone
1370 * @return string
1372 function usertimezone($timezone=99) {
1374 $tz = get_user_timezone($timezone);
1376 if (!is_float($tz)) {
1377 return $tz;
1380 if(abs($tz) > 13) { // Server time
1381 return get_string('serverlocaltime');
1384 if($tz == intval($tz)) {
1385 // Don't show .0 for whole hours
1386 $tz = intval($tz);
1389 if($tz == 0) {
1390 return 'UTC';
1392 else if($tz > 0) {
1393 return 'UTC+'.$tz;
1395 else {
1396 return 'UTC'.$tz;
1402 * Returns a float which represents the user's timezone difference from GMT in hours
1403 * Checks various settings and picks the most dominant of those which have a value
1405 * @uses $CFG
1406 * @uses $USER
1407 * @param float $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
1408 * @return int
1410 function get_user_timezone_offset($tz = 99) {
1412 global $USER, $CFG;
1414 $tz = get_user_timezone($tz);
1416 if (is_float($tz)) {
1417 return $tz;
1418 } else {
1419 $tzrecord = get_timezone_record($tz);
1420 if (empty($tzrecord)) {
1421 return 99.0;
1423 return (float)$tzrecord->gmtoff / HOURMINS;
1428 * Returns an int which represents the systems's timezone difference from GMT in seconds
1429 * @param mixed $tz timezone
1430 * @return int if found, false is timezone 99 or error
1432 function get_timezone_offset($tz) {
1433 global $CFG;
1435 if ($tz == 99) {
1436 return false;
1439 if (is_numeric($tz)) {
1440 return intval($tz * 60*60);
1443 if (!$tzrecord = get_timezone_record($tz)) {
1444 return false;
1446 return intval($tzrecord->gmtoff * 60);
1450 * Returns a float or a string which denotes the user's timezone
1451 * 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)
1452 * means that for this timezone there are also DST rules to be taken into account
1453 * Checks various settings and picks the most dominant of those which have a value
1455 * @uses $USER
1456 * @uses $CFG
1457 * @param float $tz If this value is provided and not equal to 99, it will be returned as is and no other settings will be checked
1458 * @return mixed
1460 function get_user_timezone($tz = 99) {
1461 global $USER, $CFG;
1463 $timezones = array(
1464 $tz,
1465 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1466 isset($USER->timezone) ? $USER->timezone : 99,
1467 isset($CFG->timezone) ? $CFG->timezone : 99,
1470 $tz = 99;
1472 while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
1473 $tz = $next['value'];
1476 return is_numeric($tz) ? (float) $tz : $tz;
1482 * @uses $CFG
1483 * @uses $db
1484 * @param string $timezonename ?
1485 * @return object
1487 function get_timezone_record($timezonename) {
1488 global $CFG, $db;
1489 static $cache = NULL;
1491 if ($cache === NULL) {
1492 $cache = array();
1495 if (isset($cache[$timezonename])) {
1496 return $cache[$timezonename];
1499 return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1500 WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1506 * @uses $CFG
1507 * @uses $USER
1508 * @param ? $fromyear ?
1509 * @param ? $to_year ?
1510 * @return bool
1512 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1513 global $CFG, $SESSION;
1515 $usertz = get_user_timezone($strtimezone);
1517 if (is_float($usertz)) {
1518 // Trivial timezone, no DST
1519 return false;
1522 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1523 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1524 unset($SESSION->dst_offsets);
1525 unset($SESSION->dst_range);
1528 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1529 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1530 // This will be the return path most of the time, pretty light computationally
1531 return true;
1534 // Reaching here means we either need to extend our table or create it from scratch
1536 // Remember which TZ we calculated these changes for
1537 $SESSION->dst_offsettz = $usertz;
1539 if(empty($SESSION->dst_offsets)) {
1540 // If we 're creating from scratch, put the two guard elements in there
1541 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1543 if(empty($SESSION->dst_range)) {
1544 // If creating from scratch
1545 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1546 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
1548 // Fill in the array with the extra years we need to process
1549 $yearstoprocess = array();
1550 for($i = $from; $i <= $to; ++$i) {
1551 $yearstoprocess[] = $i;
1554 // Take note of which years we have processed for future calls
1555 $SESSION->dst_range = array($from, $to);
1557 else {
1558 // If needing to extend the table, do the same
1559 $yearstoprocess = array();
1561 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1562 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
1564 if($from < $SESSION->dst_range[0]) {
1565 // Take note of which years we need to process and then note that we have processed them for future calls
1566 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1567 $yearstoprocess[] = $i;
1569 $SESSION->dst_range[0] = $from;
1571 if($to > $SESSION->dst_range[1]) {
1572 // Take note of which years we need to process and then note that we have processed them for future calls
1573 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1574 $yearstoprocess[] = $i;
1576 $SESSION->dst_range[1] = $to;
1580 if(empty($yearstoprocess)) {
1581 // This means that there was a call requesting a SMALLER range than we have already calculated
1582 return true;
1585 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1586 // Also, the array is sorted in descending timestamp order!
1588 // Get DB data
1590 static $presets_cache = array();
1591 if (!isset($presets_cache[$usertz])) {
1592 $presets_cache[$usertz] = get_records('timezone', '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');
1594 if(empty($presets_cache[$usertz])) {
1595 return false;
1598 // Remove ending guard (first element of the array)
1599 reset($SESSION->dst_offsets);
1600 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1602 // Add all required change timestamps
1603 foreach($yearstoprocess as $y) {
1604 // Find the record which is in effect for the year $y
1605 foreach($presets_cache[$usertz] as $year => $preset) {
1606 if($year <= $y) {
1607 break;
1611 $changes = dst_changes_for_year($y, $preset);
1613 if($changes === NULL) {
1614 continue;
1616 if($changes['dst'] != 0) {
1617 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1619 if($changes['std'] != 0) {
1620 $SESSION->dst_offsets[$changes['std']] = 0;
1624 // Put in a guard element at the top
1625 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1626 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1628 // Sort again
1629 krsort($SESSION->dst_offsets);
1631 return true;
1634 function dst_changes_for_year($year, $timezone) {
1636 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1637 return NULL;
1640 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1641 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1643 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1644 list($std_hour, $std_min) = explode(':', $timezone->std_time);
1646 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1647 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1649 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1650 // This has the advantage of being able to have negative values for hour, i.e. for timezones
1651 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1653 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1654 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1656 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1659 // $time must NOT be compensated at all, it has to be a pure timestamp
1660 function dst_offset_on($time, $strtimezone = NULL) {
1661 global $SESSION;
1663 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
1664 return 0;
1667 reset($SESSION->dst_offsets);
1668 while(list($from, $offset) = each($SESSION->dst_offsets)) {
1669 if($from <= $time) {
1670 break;
1674 // This is the normal return path
1675 if($offset !== NULL) {
1676 return $offset;
1679 // Reaching this point means we haven't calculated far enough, do it now:
1680 // Calculate extra DST changes if needed and recurse. The recursion always
1681 // moves toward the stopping condition, so will always end.
1683 if($from == 0) {
1684 // We need a year smaller than $SESSION->dst_range[0]
1685 if($SESSION->dst_range[0] == 1971) {
1686 return 0;
1688 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
1689 return dst_offset_on($time, $strtimezone);
1691 else {
1692 // We need a year larger than $SESSION->dst_range[1]
1693 if($SESSION->dst_range[1] == 2035) {
1694 return 0;
1696 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
1697 return dst_offset_on($time, $strtimezone);
1701 function find_day_in_month($startday, $weekday, $month, $year) {
1703 $daysinmonth = days_in_month($month, $year);
1705 if($weekday == -1) {
1706 // Don't care about weekday, so return:
1707 // abs($startday) if $startday != -1
1708 // $daysinmonth otherwise
1709 return ($startday == -1) ? $daysinmonth : abs($startday);
1712 // From now on we 're looking for a specific weekday
1714 // Give "end of month" its actual value, since we know it
1715 if($startday == -1) {
1716 $startday = -1 * $daysinmonth;
1719 // Starting from day $startday, the sign is the direction
1721 if($startday < 1) {
1723 $startday = abs($startday);
1724 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1726 // This is the last such weekday of the month
1727 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1728 if($lastinmonth > $daysinmonth) {
1729 $lastinmonth -= 7;
1732 // Find the first such weekday <= $startday
1733 while($lastinmonth > $startday) {
1734 $lastinmonth -= 7;
1737 return $lastinmonth;
1740 else {
1742 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1744 $diff = $weekday - $indexweekday;
1745 if($diff < 0) {
1746 $diff += 7;
1749 // This is the first such weekday of the month equal to or after $startday
1750 $firstfromindex = $startday + $diff;
1752 return $firstfromindex;
1758 * Calculate the number of days in a given month
1760 * @param int $month The month whose day count is sought
1761 * @param int $year The year of the month whose day count is sought
1762 * @return int
1764 function days_in_month($month, $year) {
1765 return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1769 * Calculate the position in the week of a specific calendar day
1771 * @param int $day The day of the date whose position in the week is sought
1772 * @param int $month The month of the date whose position in the week is sought
1773 * @param int $year The year of the date whose position in the week is sought
1774 * @return int
1776 function dayofweek($day, $month, $year) {
1777 // I wonder if this is any different from
1778 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1779 return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1782 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1785 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1786 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1787 * sesskey string if $USER exists, or boolean false if not.
1789 * @uses $USER
1790 * @return string
1792 function sesskey() {
1793 global $USER;
1795 if(!isset($USER)) {
1796 return false;
1799 if (empty($USER->sesskey)) {
1800 $USER->sesskey = random_string(10);
1803 return $USER->sesskey;
1808 * For security purposes, this function will check that the currently
1809 * given sesskey (passed as a parameter to the script or this function)
1810 * matches that of the current user.
1812 * @param string $sesskey optionally provided sesskey
1813 * @return bool
1815 function confirm_sesskey($sesskey=NULL) {
1816 global $USER;
1818 if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1819 return true;
1822 if (empty($sesskey)) {
1823 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
1826 if (!isset($USER->sesskey)) {
1827 return false;
1830 return ($USER->sesskey === $sesskey);
1834 * Check the session key using {@link confirm_sesskey()},
1835 * and cause a fatal error if it does not match.
1837 function require_sesskey() {
1838 if (!confirm_sesskey()) {
1839 print_error('invalidsesskey');
1844 * Setup all global $CFG course variables, set locale and also themes
1845 * This function can be used on pages that do not require login instead of require_login()
1847 * @param mixed $courseorid id of the course or course object
1849 function course_setup($courseorid=0) {
1850 global $COURSE, $CFG, $SITE;
1852 /// Redefine global $COURSE if needed
1853 if (empty($courseorid)) {
1854 // no change in global $COURSE - for backwards compatibiltiy
1855 // if require_rogin() used after require_login($courseid);
1856 } else if (is_object($courseorid)) {
1857 $COURSE = clone($courseorid);
1858 } else {
1859 global $course; // used here only to prevent repeated fetching from DB - may be removed later
1860 if ($courseorid == SITEID) {
1861 $COURSE = clone($SITE);
1862 } else if (!empty($course->id) and $course->id == $courseorid) {
1863 $COURSE = clone($course);
1864 } else {
1865 if (!$COURSE = get_record('course', 'id', $courseorid)) {
1866 error('Invalid course ID');
1871 /// set locale and themes
1872 moodle_setlocale();
1873 theme_setup();
1878 * This function checks that the current user is logged in and has the
1879 * required privileges
1881 * This function checks that the current user is logged in, and optionally
1882 * whether they are allowed to be in a particular course and view a particular
1883 * course module.
1884 * If they are not logged in, then it redirects them to the site login unless
1885 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1886 * case they are automatically logged in as guests.
1887 * If $courseid is given and the user is not enrolled in that course then the
1888 * user is redirected to the course enrolment page.
1889 * If $cm is given and the coursemodule is hidden and the user is not a teacher
1890 * in the course then the user is redirected to the course home page.
1892 * @uses $CFG
1893 * @uses $SESSION
1894 * @uses $USER
1895 * @uses $FULLME
1896 * @uses SITEID
1897 * @uses $COURSE
1898 * @param mixed $courseorid id of the course or course object
1899 * @param bool $autologinguest
1900 * @param object $cm course module object
1901 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
1902 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
1903 * in order to keep redirects working properly. MDL-14495
1905 function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
1907 global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1909 /// setup global $COURSE, themes, language and locale
1910 course_setup($courseorid);
1912 /// If the user is not even logged in yet then make sure they are
1913 if (!isloggedin()) {
1914 //NOTE: $USER->site check was obsoleted by session test cookie,
1915 // $USER->confirmed test is in login/index.php
1916 if ($setwantsurltome) {
1917 $SESSION->wantsurl = $FULLME;
1919 if (!empty($_SERVER['HTTP_REFERER'])) {
1920 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
1922 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1923 $loginguest = '?loginguest=true';
1924 } else {
1925 $loginguest = '';
1927 if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
1928 redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1929 } else {
1930 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1931 redirect($wwwroot .'/login/index.php');
1933 exit;
1936 /// loginas as redirection if needed
1937 if ($COURSE->id != SITEID and !empty($USER->realuser)) {
1938 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
1939 if ($USER->loginascontext->instanceid != $COURSE->id) {
1940 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
1945 /// check whether the user should be changing password (but only if it is REALLY them)
1946 if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
1947 $userauth = get_auth_plugin($USER->auth);
1948 if ($userauth->can_change_password()) {
1949 $SESSION->wantsurl = $FULLME;
1950 if ($changeurl = $userauth->change_password_url()) {
1951 //use plugin custom url
1952 redirect($changeurl);
1953 } else {
1954 //use moodle internal method
1955 if (empty($CFG->loginhttps)) {
1956 redirect($CFG->wwwroot .'/login/change_password.php');
1957 } else {
1958 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1959 redirect($wwwroot .'/login/change_password.php');
1962 } else {
1963 print_error('nopasswordchangeforced', 'auth');
1967 /// Check that the user account is properly set up
1968 if (user_not_fully_set_up($USER)) {
1969 $SESSION->wantsurl = $FULLME;
1970 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1973 /// Make sure current IP matches the one for this session (if required)
1974 if (!empty($CFG->tracksessionip)) {
1975 if ($USER->sessionIP != md5(getremoteaddr())) {
1976 print_error('sessionipnomatch', 'error');
1980 /// Make sure the USER has a sesskey set up. Used for checking script parameters.
1981 sesskey();
1983 // Check that the user has agreed to a site policy if there is one
1984 if (!empty($CFG->sitepolicy)) {
1985 if (!$USER->policyagreed) {
1986 $SESSION->wantsurl = $FULLME;
1987 redirect($CFG->wwwroot .'/user/policy.php');
1991 // Fetch the system context, we are going to use it a lot.
1992 $sysctx = get_context_instance(CONTEXT_SYSTEM);
1994 /// If the site is currently under maintenance, then print a message
1995 if (!has_capability('moodle/site:config', $sysctx)) {
1996 if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1997 print_maintenance_message();
1998 exit;
2002 /// groupmembersonly access control
2003 if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2004 if (isguestuser() or !groups_has_membership($cm)) {
2005 print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
2009 // Fetch the course context, and prefetch its child contexts
2010 if (!isset($COURSE->context)) {
2011 if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
2012 print_error('nocontext');
2015 if (!empty($cm) && !isset($cm->context)) {
2016 if ( ! $cm->context = get_context_instance(CONTEXT_MODULE, $cm->id) ) {
2017 print_error('nocontext');
2020 if ($COURSE->id == SITEID) {
2021 /// Eliminate hidden site activities straight away
2022 if (!empty($cm) && !$cm->visible
2023 && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2024 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
2026 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2027 return;
2029 } else {
2031 /// Check if the user can be in a particular course
2032 if (empty($USER->access['rsw'][$COURSE->context->path])) {
2034 // MDL-13900 - If the course or the parent category are hidden
2035 // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
2037 if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
2038 !has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
2039 print_header_simple();
2040 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2044 /// Non-guests who don't currently have access, check if they can be allowed in as a guest
2046 if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
2047 if ($COURSE->guest == 1) {
2048 // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
2049 $USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
2053 /// If the user is a guest then treat them according to the course policy about guests
2055 if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
2056 if (has_capability('moodle/site:doanything', $sysctx)) {
2057 // administrators must be able to access any course - even if somebody gives them guest access
2058 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2059 return;
2062 switch ($COURSE->guest) { /// Check course policy about guest access
2064 case 1: /// Guests always allowed
2065 if (!has_capability('moodle/course:view', $COURSE->context)) { // Prohibited by capability
2066 print_header_simple();
2067 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2069 if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
2070 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
2071 get_string('activityiscurrentlyhidden'));
2074 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2075 return; // User is allowed to see this course
2077 break;
2079 case 2: /// Guests allowed with key
2080 if (!empty($USER->enrolkey[$COURSE->id])) { // Set by enrol/manual/enrol.php
2081 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2082 return true;
2084 // otherwise drop through to logic below (--> enrol.php)
2085 break;
2087 default: /// Guests not allowed
2088 $strloggedinasguest = get_string('loggedinasguest');
2089 print_header_simple('', '',
2090 build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
2091 if (empty($USER->access['rsw'][$COURSE->context->path])) { // Normal guest
2092 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2093 } else {
2094 notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
2095 echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
2096 print_footer($COURSE);
2097 exit;
2099 break;
2102 /// For non-guests, check if they have course view access
2104 } else if (has_capability('moodle/course:view', $COURSE->context)) {
2105 if (!empty($USER->realuser)) { // Make sure the REAL person can also access this course
2106 if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
2107 print_header_simple();
2108 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2112 /// Make sure they can read this activity too, if specified
2114 if (!empty($cm) && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2115 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
2117 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2118 return; // User is allowed to see this course
2123 /// Currently not enrolled in the course, so see if they want to enrol
2124 $SESSION->wantsurl = $FULLME;
2125 redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
2126 die;
2133 * This function just makes sure a user is logged out.
2135 * @uses $CFG
2136 * @uses $USER
2138 function require_logout() {
2140 global $USER, $CFG, $SESSION;
2142 if (isloggedin()) {
2143 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2145 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2146 foreach($authsequence as $authname) {
2147 $authplugin = get_auth_plugin($authname);
2148 $authplugin->prelogout_hook();
2152 if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
2153 // This method is just to try to avoid silly warnings from PHP 4.3.0
2154 session_unregister("USER");
2155 session_unregister("SESSION");
2158 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
2159 $file = $line = null;
2160 if (headers_sent($file, $line)) {
2161 error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
2162 error_log('Headers were already sent in file: '.$file.' on line '.$line);
2163 } else {
2164 if (check_php_version('5.2.0')) {
2165 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
2166 } else {
2167 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2171 unset($_SESSION['USER']);
2172 unset($_SESSION['SESSION']);
2174 unset($SESSION);
2175 unset($USER);
2180 * This is a weaker version of {@link require_login()} which only requires login
2181 * when called from within a course rather than the site page, unless
2182 * the forcelogin option is turned on.
2184 * @uses $CFG
2185 * @param mixed $courseorid The course object or id in question
2186 * @param bool $autologinguest Allow autologin guests if that is wanted
2187 * @param object $cm Course activity module if known
2188 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2189 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2190 * in order to keep redirects working properly. MDL-14495
2192 function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
2193 global $CFG;
2194 if (!empty($CFG->forcelogin)) {
2195 // login required for both SITE and courses
2196 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2198 } else if (!empty($cm) and !$cm->visible) {
2199 // always login for hidden activities
2200 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2202 } else if ((is_object($courseorid) and $courseorid->id == SITEID)
2203 or (!is_object($courseorid) and $courseorid == SITEID)) {
2204 //login for SITE not required
2205 if ($cm and empty($cm->visible)) {
2206 // hidden activities are not accessible without login
2207 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2208 } else if ($cm and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
2209 // not-logged-in users do not have any group membership
2210 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2211 } else {
2212 user_accesstime_log(SITEID);
2213 return;
2216 } else {
2217 // course login always required
2218 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2223 * Require key login. Function terminates with error if key not found or incorrect.
2224 * @param string $script unique script identifier
2225 * @param int $instance optional instance id
2227 function require_user_key_login($script, $instance=null) {
2228 global $nomoodlecookie, $USER, $SESSION, $CFG;
2230 if (empty($nomoodlecookie)) {
2231 error('Incorrect use of require_key_login() - session cookies must be disabled!');
2234 /// extra safety
2235 @session_write_close();
2237 $keyvalue = required_param('key', PARAM_ALPHANUM);
2239 if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
2240 error('Incorrect key');
2243 if (!empty($key->validuntil) and $key->validuntil < time()) {
2244 error('Expired key');
2247 if ($key->iprestriction) {
2248 $remoteaddr = getremoteaddr();
2249 if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
2250 error('Client IP address mismatch');
2254 if (!$user = get_record('user', 'id', $key->userid)) {
2255 error('Incorrect user record');
2258 /// emulate normal session
2259 $SESSION = new object();
2260 $USER = $user;
2262 /// note we are not using normal login
2263 if (!defined('USER_KEY_LOGIN')) {
2264 define('USER_KEY_LOGIN', true);
2267 load_all_capabilities();
2269 /// return isntance id - it might be empty
2270 return $key->instance;
2274 * Creates a new private user access key.
2275 * @param string $script unique target identifier
2276 * @param int $userid
2277 * @param instance $int optional instance id
2278 * @param string $iprestriction optional ip restricted access
2279 * @param timestamp $validuntil key valid only until given data
2280 * @return string access key value
2282 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2283 $key = new object();
2284 $key->script = $script;
2285 $key->userid = $userid;
2286 $key->instance = $instance;
2287 $key->iprestriction = $iprestriction;
2288 $key->validuntil = $validuntil;
2289 $key->timecreated = time();
2291 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
2292 while (record_exists('user_private_key', 'value', $key->value)) {
2293 // must be unique
2294 $key->value = md5($userid.'_'.time().random_string(40));
2297 if (!insert_record('user_private_key', $key)) {
2298 error('Can not insert new key');
2301 return $key->value;
2305 * Modify the user table by setting the currently logged in user's
2306 * last login to now.
2308 * @uses $USER
2309 * @return bool
2311 function update_user_login_times() {
2312 global $USER;
2314 $user = new object();
2315 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
2316 $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
2318 $user->id = $USER->id;
2320 return update_record('user', $user);
2324 * Determines if a user has completed setting up their account.
2326 * @param user $user A {@link $USER} object to test for the existance of a valid name and email
2327 * @return bool
2329 function user_not_fully_set_up($user) {
2330 return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
2333 function over_bounce_threshold($user) {
2335 global $CFG;
2337 if (empty($CFG->handlebounces)) {
2338 return false;
2341 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2342 return false;
2345 // set sensible defaults
2346 if (empty($CFG->minbounces)) {
2347 $CFG->minbounces = 10;
2349 if (empty($CFG->bounceratio)) {
2350 $CFG->bounceratio = .20;
2352 $bouncecount = 0;
2353 $sendcount = 0;
2354 if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2355 $bouncecount = $bounce->value;
2357 if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2358 $sendcount = $send->value;
2360 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
2364 * @param $user - object containing an id
2365 * @param $reset - will reset the count to 0
2367 function set_send_count($user,$reset=false) {
2369 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2370 return;
2373 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2374 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2375 update_record('user_preferences',$pref);
2377 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2378 // make a new one
2379 $pref->name = 'email_send_count';
2380 $pref->value = 1;
2381 $pref->userid = $user->id;
2382 insert_record('user_preferences',$pref, false);
2387 * @param $user - object containing an id
2388 * @param $reset - will reset the count to 0
2390 function set_bounce_count($user,$reset=false) {
2391 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2392 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2393 update_record('user_preferences',$pref);
2395 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2396 // make a new one
2397 $pref->name = 'email_bounce_count';
2398 $pref->value = 1;
2399 $pref->userid = $user->id;
2400 insert_record('user_preferences',$pref, false);
2405 * Keeps track of login attempts
2407 * @uses $SESSION
2409 function update_login_count() {
2411 global $SESSION;
2413 $max_logins = 10;
2415 if (empty($SESSION->logincount)) {
2416 $SESSION->logincount = 1;
2417 } else {
2418 $SESSION->logincount++;
2421 if ($SESSION->logincount > $max_logins) {
2422 unset($SESSION->wantsurl);
2423 print_error('errortoomanylogins');
2428 * Resets login attempts
2430 * @uses $SESSION
2432 function reset_login_count() {
2433 global $SESSION;
2435 $SESSION->logincount = 0;
2438 function sync_metacourses() {
2440 global $CFG;
2442 if (!$courses = get_records('course', 'metacourse', 1)) {
2443 return;
2446 foreach ($courses as $course) {
2447 sync_metacourse($course);
2452 * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2454 * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2456 function sync_metacourse($course) {
2457 global $CFG;
2459 // Check the course is valid.
2460 if (!is_object($course)) {
2461 if (!$course = get_record('course', 'id', $course)) {
2462 return false; // invalid course id
2466 // Check that we actually have a metacourse.
2467 if (empty($course->metacourse)) {
2468 return false;
2471 // Get a list of roles that should not be synced.
2472 if (!empty($CFG->nonmetacoursesyncroleids)) {
2473 $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2474 } else {
2475 $roleexclusions = '';
2478 // Get the context of the metacourse.
2479 $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2481 // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2482 if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2483 $managers = array_keys($users);
2484 } else {
2485 $managers = array();
2488 // Get assignments of a user to a role that exist in a child course, but
2489 // not in the meta coure. That is, get a list of the assignments that need to be made.
2490 if (!$assignments = get_records_sql("
2491 SELECT
2492 ra.id, ra.roleid, ra.userid
2493 FROM
2494 {$CFG->prefix}role_assignments ra,
2495 {$CFG->prefix}context con,
2496 {$CFG->prefix}course_meta cm
2497 WHERE
2498 ra.contextid = con.id AND
2499 con.contextlevel = " . CONTEXT_COURSE . " AND
2500 con.instanceid = cm.child_course AND
2501 cm.parent_course = {$course->id} AND
2502 $roleexclusions
2503 NOT EXISTS (
2504 SELECT 1 FROM
2505 {$CFG->prefix}role_assignments ra2
2506 WHERE
2507 ra2.userid = ra.userid AND
2508 ra2.roleid = ra.roleid AND
2509 ra2.contextid = {$context->id}
2511 ")) {
2512 $assignments = array();
2515 // Get assignments of a user to a role that exist in the meta course, but
2516 // not in any child courses. That is, get a list of the unassignments that need to be made.
2517 if (!$unassignments = get_records_sql("
2518 SELECT
2519 ra.id, ra.roleid, ra.userid
2520 FROM
2521 {$CFG->prefix}role_assignments ra
2522 WHERE
2523 ra.contextid = {$context->id} AND
2524 $roleexclusions
2525 NOT EXISTS (
2526 SELECT 1 FROM
2527 {$CFG->prefix}role_assignments ra2,
2528 {$CFG->prefix}context con2,
2529 {$CFG->prefix}course_meta cm
2530 WHERE
2531 ra2.userid = ra.userid AND
2532 ra2.roleid = ra.roleid AND
2533 ra2.contextid = con2.id AND
2534 con2.contextlevel = " . CONTEXT_COURSE . " AND
2535 con2.instanceid = cm.child_course AND
2536 cm.parent_course = {$course->id}
2538 ")) {
2539 $unassignments = array();
2542 $success = true;
2544 // Make the unassignments, if they are not managers.
2545 foreach ($unassignments as $unassignment) {
2546 if (!in_array($unassignment->userid, $managers)) {
2547 $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2551 // Make the assignments.
2552 foreach ($assignments as $assignment) {
2553 $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id) && $success;
2556 return $success;
2558 // TODO: finish timeend and timestart
2559 // maybe we could rely on cron job to do the cleaning from time to time
2563 * Adds a record to the metacourse table and calls sync_metacoures
2565 function add_to_metacourse ($metacourseid, $courseid) {
2567 if (!$metacourse = get_record("course","id",$metacourseid)) {
2568 return false;
2571 if (!$course = get_record("course","id",$courseid)) {
2572 return false;
2575 if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2576 $rec = new object();
2577 $rec->parent_course = $metacourseid;
2578 $rec->child_course = $courseid;
2579 if (!insert_record('course_meta',$rec)) {
2580 return false;
2582 return sync_metacourse($metacourseid);
2584 return true;
2589 * Removes the record from the metacourse table and calls sync_metacourse
2591 function remove_from_metacourse($metacourseid, $courseid) {
2593 if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2594 return sync_metacourse($metacourseid);
2596 return false;
2601 * Determines if a user is currently logged in
2603 * @uses $USER
2604 * @return bool
2606 function isloggedin() {
2607 global $USER;
2609 return (!empty($USER->id));
2613 * Determines if a user is logged in as real guest user with username 'guest'.
2614 * This function is similar to original isguest() in 1.6 and earlier.
2615 * Current isguest() is deprecated - do not use it anymore.
2617 * @param $user mixed user object or id, $USER if not specified
2618 * @return bool true if user is the real guest user, false if not logged in or other user
2620 function isguestuser($user=NULL) {
2621 global $USER;
2622 if ($user === NULL) {
2623 $user = $USER;
2624 } else if (is_numeric($user)) {
2625 $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2628 if (empty($user->id)) {
2629 return false; // not logged in, can not be guest
2632 return ($user->username == 'guest');
2636 * Determines if the currently logged in user is in editing mode.
2637 * Note: originally this function had $userid parameter - it was not usable anyway
2639 * @uses $USER, $PAGE
2640 * @return bool
2642 function isediting() {
2643 global $USER, $PAGE;
2645 if (empty($USER->editing)) {
2646 return false;
2647 } elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
2648 return $PAGE->user_allowed_editing();
2650 return true;//false;
2654 * Determines if the logged in user is currently moving an activity
2656 * @uses $USER
2657 * @param int $courseid The id of the course being tested
2658 * @return bool
2660 function ismoving($courseid) {
2661 global $USER;
2663 if (!empty($USER->activitycopy)) {
2664 return ($USER->activitycopycourse == $courseid);
2666 return false;
2670 * Given an object containing firstname and lastname
2671 * values, this function returns a string with the
2672 * full name of the person.
2673 * The result may depend on system settings
2674 * or language. 'override' will force both names
2675 * to be used even if system settings specify one.
2677 * @uses $CFG
2678 * @uses $SESSION
2679 * @param object $user A {@link $USER} object to get full name of
2680 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2682 function fullname($user, $override=false) {
2684 global $CFG, $SESSION;
2686 if (!isset($user->firstname) and !isset($user->lastname)) {
2687 return '';
2690 if (!$override) {
2691 if (!empty($CFG->forcefirstname)) {
2692 $user->firstname = $CFG->forcefirstname;
2694 if (!empty($CFG->forcelastname)) {
2695 $user->lastname = $CFG->forcelastname;
2699 if (!empty($SESSION->fullnamedisplay)) {
2700 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2703 if ($CFG->fullnamedisplay == 'firstname lastname') {
2704 return $user->firstname .' '. $user->lastname;
2706 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2707 return $user->lastname .' '. $user->firstname;
2709 } else if ($CFG->fullnamedisplay == 'firstname') {
2710 if ($override) {
2711 return get_string('fullnamedisplay', '', $user);
2712 } else {
2713 return $user->firstname;
2717 return get_string('fullnamedisplay', '', $user);
2721 * Sets a moodle cookie with an encrypted string
2723 * @uses $CFG
2724 * @uses DAYSECS
2725 * @uses HOURSECS
2726 * @param string $thing The string to encrypt and place in a cookie
2728 function set_moodle_cookie($thing) {
2729 global $CFG;
2731 if ($thing == 'guest') { // Ignore guest account
2732 return;
2735 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2737 $days = 60;
2738 $seconds = DAYSECS*$days;
2740 setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2741 setCookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2745 * Gets a moodle cookie with an encrypted string
2747 * @uses $CFG
2748 * @return string
2750 function get_moodle_cookie() {
2751 global $CFG;
2753 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2755 if (empty($_COOKIE[$cookiename])) {
2756 return '';
2757 } else {
2758 $thing = rc4decrypt($_COOKIE[$cookiename]);
2759 return ($thing == 'guest') ? '': $thing; // Ignore guest account
2764 * Returns whether a given authentication plugin exists.
2766 * @uses $CFG
2767 * @param string $auth Form of authentication to check for. Defaults to the
2768 * global setting in {@link $CFG}.
2769 * @return boolean Whether the plugin is available.
2771 function exists_auth_plugin($auth) {
2772 global $CFG;
2774 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2775 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2777 return false;
2781 * Checks if a given plugin is in the list of enabled authentication plugins.
2783 * @param string $auth Authentication plugin.
2784 * @return boolean Whether the plugin is enabled.
2786 function is_enabled_auth($auth) {
2787 if (empty($auth)) {
2788 return false;
2791 $enabled = get_enabled_auth_plugins();
2793 return in_array($auth, $enabled);
2797 * Returns an authentication plugin instance.
2799 * @uses $CFG
2800 * @param string $auth name of authentication plugin
2801 * @return object An instance of the required authentication plugin.
2803 function get_auth_plugin($auth) {
2804 global $CFG;
2806 // check the plugin exists first
2807 if (! exists_auth_plugin($auth)) {
2808 error("Authentication plugin '$auth' not found.");
2811 // return auth plugin instance
2812 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2813 $class = "auth_plugin_$auth";
2814 return new $class;
2818 * Returns array of active auth plugins.
2820 * @param bool $fix fix $CFG->auth if needed
2821 * @return array
2823 function get_enabled_auth_plugins($fix=false) {
2824 global $CFG;
2826 $default = array('manual', 'nologin');
2828 if (empty($CFG->auth)) {
2829 $auths = array();
2830 } else {
2831 $auths = explode(',', $CFG->auth);
2834 if ($fix) {
2835 $auths = array_unique($auths);
2836 foreach($auths as $k=>$authname) {
2837 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
2838 unset($auths[$k]);
2841 $newconfig = implode(',', $auths);
2842 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
2843 set_config('auth', $newconfig);
2847 return (array_merge($default, $auths));
2851 * Returns true if an internal authentication method is being used.
2852 * if method not specified then, global default is assumed
2854 * @uses $CFG
2855 * @param string $auth Form of authentication required
2856 * @return bool
2858 function is_internal_auth($auth) {
2859 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2860 return $authplugin->is_internal();
2864 * Returns true if the user is a 'restored' one
2866 * Used in the login process to inform the user
2867 * and allow him/her to reset the password
2869 * @uses $CFG
2870 * @param string $username username to be checked
2871 * @return bool
2873 function is_restored_user($username) {
2874 global $CFG;
2876 return record_exists('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, 'password', 'restored');
2880 * Returns an array of user fields
2882 * @uses $CFG
2883 * @uses $db
2884 * @return array User field/column names
2886 function get_user_fieldnames() {
2888 global $CFG, $db;
2890 $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2891 unset($fieldarray['ID']);
2893 return $fieldarray;
2897 * Creates the default "guest" user. Used both from
2898 * admin/index.php and login/index.php
2899 * @return mixed user object created or boolean false if the creation has failed
2901 function create_guest_record() {
2903 global $CFG;
2905 $guest = new stdClass();
2906 $guest->auth = 'manual';
2907 $guest->username = 'guest';
2908 $guest->password = hash_internal_user_password('guest');
2909 $guest->firstname = addslashes(get_string('guestuser'));
2910 $guest->lastname = ' ';
2911 $guest->email = 'root@localhost';
2912 $guest->description = addslashes(get_string('guestuserinfo'));
2913 $guest->mnethostid = $CFG->mnet_localhost_id;
2914 $guest->confirmed = 1;
2915 $guest->lang = $CFG->lang;
2916 $guest->timemodified= time();
2918 if (! $guest->id = insert_record("user", $guest)) {
2919 return false;
2922 return $guest;
2926 * Creates a bare-bones user record
2928 * @uses $CFG
2929 * @param string $username New user's username to add to record
2930 * @param string $password New user's password to add to record
2931 * @param string $auth Form of authentication required
2932 * @return object A {@link $USER} object
2933 * @todo Outline auth types and provide code example
2935 function create_user_record($username, $password, $auth='manual') {
2936 global $CFG;
2938 //just in case check text case
2939 $username = trim(moodle_strtolower($username));
2941 $authplugin = get_auth_plugin($auth);
2943 if ($newinfo = $authplugin->get_userinfo($username)) {
2944 $newinfo = truncate_userinfo($newinfo);
2945 foreach ($newinfo as $key => $value){
2946 $newuser->$key = addslashes($value);
2950 if (!empty($newuser->email)) {
2951 if (email_is_not_allowed($newuser->email)) {
2952 unset($newuser->email);
2956 if (!isset($newuser->city)) {
2957 $newuser->city = '';
2960 $newuser->auth = $auth;
2961 $newuser->username = $username;
2963 // fix for MDL-8480
2964 // user CFG lang for user if $newuser->lang is empty
2965 // or $user->lang is not an installed language
2966 $sitelangs = array_keys(get_list_of_languages());
2967 if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
2968 $newuser -> lang = $CFG->lang;
2970 $newuser->confirmed = 1;
2971 $newuser->lastip = getremoteaddr();
2972 $newuser->timemodified = time();
2973 $newuser->mnethostid = $CFG->mnet_localhost_id;
2975 if (insert_record('user', $newuser)) {
2976 $user = get_complete_user_data('username', $newuser->username);
2977 if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
2978 set_user_preference('auth_forcepasswordchange', 1, $user->id);
2980 update_internal_user_password($user, $password);
2981 return $user;
2983 return false;
2987 * Will update a local user record from an external source
2989 * @uses $CFG
2990 * @param string $username New user's username to add to record
2991 * @return user A {@link $USER} object
2993 function update_user_record($username, $authplugin) {
2994 $username = trim(moodle_strtolower($username)); /// just in case check text case
2996 $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
2997 $userauth = get_auth_plugin($oldinfo->auth);
2999 if ($newinfo = $userauth->get_userinfo($username)) {
3000 $newinfo = truncate_userinfo($newinfo);
3001 foreach ($newinfo as $key => $value){
3002 if ($key === 'username') {
3003 // 'username' is not a mapped updateable/lockable field, so skip it.
3004 continue;
3006 $confval = $userauth->config->{'field_updatelocal_' . $key};
3007 $lockval = $userauth->config->{'field_lock_' . $key};
3008 if (empty($confval) || empty($lockval)) {
3009 continue;
3011 if ($confval === 'onlogin') {
3012 $value = addslashes($value);
3013 // MDL-4207 Don't overwrite modified user profile values with
3014 // empty LDAP values when 'unlocked if empty' is set. The purpose
3015 // of the setting 'unlocked if empty' is to allow the user to fill
3016 // in a value for the selected field _if LDAP is giving
3017 // nothing_ for this field. Thus it makes sense to let this value
3018 // stand in until LDAP is giving a value for this field.
3019 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3020 set_field('user', $key, $value, 'username', $username)
3021 || error_log("Error updating $key for $username");
3027 return get_complete_user_data('username', $username);
3030 function truncate_userinfo($info) {
3031 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3032 /// which may have large fields
3034 // define the limits
3035 $limit = array(
3036 'username' => 100,
3037 'idnumber' => 255,
3038 'firstname' => 100,
3039 'lastname' => 100,
3040 'email' => 100,
3041 'icq' => 15,
3042 'phone1' => 20,
3043 'phone2' => 20,
3044 'institution' => 40,
3045 'department' => 30,
3046 'address' => 70,
3047 'city' => 20,
3048 'country' => 2,
3049 'url' => 255,
3052 // apply where needed
3053 foreach (array_keys($info) as $key) {
3054 if (!empty($limit[$key])) {
3055 $info[$key] = trim(substr($info[$key],0, $limit[$key]));
3059 return $info;
3063 * Marks user deleted in internal user database and notifies the auth plugin.
3064 * Also unenrols user from all roles and does other cleanup.
3065 * @param object $user Userobject before delete (without system magic quotes)
3066 * @return boolean success
3068 function delete_user($user) {
3069 global $CFG;
3070 require_once($CFG->libdir.'/grouplib.php');
3071 require_once($CFG->libdir.'/gradelib.php');
3072 require_once($CFG->dirroot.'/message/lib.php');
3074 begin_sql();
3076 // delete all grades - backup is kept in grade_grades_history table
3077 if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
3078 foreach ($grades as $grade) {
3079 $grade->delete('userdelete');
3083 //move unread messages from this user to read
3084 message_move_userfrom_unread2read($user->id);
3086 // remove from all groups
3087 delete_records('groups_members', 'userid', $user->id);
3089 // unenrol from all roles in all contexts
3090 role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
3092 // now do a final accesslib cleanup - removes all role assingments in user context and context itself
3093 delete_context(CONTEXT_USER, $user->id);
3095 require_once($CFG->dirroot.'/tag/lib.php');
3096 tag_set('user', $user->id, array());
3098 // workaround for bulk deletes of users with the same email address
3099 $delname = addslashes("$user->email.".time());
3100 while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
3101 $delname++;
3104 // mark internal user record as "deleted"
3105 $updateuser = new object();
3106 $updateuser->id = $user->id;
3107 $updateuser->deleted = 1;
3108 $updateuser->username = $delname; // Remember it just in case
3109 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
3110 $updateuser->idnumber = ''; // Clear this field to free it up
3111 $updateuser->timemodified = time();
3113 if (update_record('user', $updateuser)) {
3114 commit_sql();
3115 // notify auth plugin - do not block the delete even when plugin fails
3116 $authplugin = get_auth_plugin($user->auth);
3117 $authplugin->user_delete($user);
3119 events_trigger('user_deleted', $user);
3120 return true;
3122 } else {
3123 rollback_sql();
3124 return false;
3129 * Retrieve the guest user object
3131 * @uses $CFG
3132 * @return user A {@link $USER} object
3134 function guest_user() {
3135 global $CFG;
3137 if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id)) {
3138 $newuser->confirmed = 1;
3139 $newuser->lang = $CFG->lang;
3140 $newuser->lastip = getremoteaddr();
3143 return $newuser;
3147 * Given a username and password, this function looks them
3148 * up using the currently selected authentication mechanism,
3149 * and if the authentication is successful, it returns a
3150 * valid $user object from the 'user' table.
3152 * Uses auth_ functions from the currently active auth module
3154 * After authenticate_user_login() returns success, you will need to
3155 * log that the user has logged in, and call complete_user_login() to set
3156 * the session up.
3158 * @uses $CFG
3159 * @param string $username User's username (with system magic quotes)
3160 * @param string $password User's password (with system magic quotes)
3161 * @return user|flase A {@link $USER} object or false if error
3163 function authenticate_user_login($username, $password) {
3165 global $CFG;
3167 $authsenabled = get_enabled_auth_plugins();
3169 if ($user = get_complete_user_data('username', $username)) {
3170 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
3171 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3172 add_to_log(0, 'login', 'error', 'index.php', $username);
3173 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3174 return false;
3176 $auths = array($auth);
3178 } else {
3179 // check if there's a deleted record (cheaply)
3180 if (get_field('user', 'id', 'username', $username, 'deleted', 1, '')) {
3181 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3182 return false;
3185 $auths = $authsenabled;
3186 $user = new object();
3187 $user->id = 0; // User does not exist
3190 foreach ($auths as $auth) {
3191 $authplugin = get_auth_plugin($auth);
3193 // on auth fail fall through to the next plugin
3194 if (!$authplugin->user_login($username, $password)) {
3195 continue;
3198 // successful authentication
3199 if ($user->id) { // User already exists in database
3200 if (empty($user->auth)) { // For some reason auth isn't set yet
3201 set_field('user', 'auth', $auth, 'username', $username);
3202 $user->auth = $auth;
3204 if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
3205 set_field('user','firstaccess', $user->timemodified, 'id', $user->id);
3206 $user->firstaccess = $user->timemodified;
3209 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3211 if (!$authplugin->is_internal()) { // update user record from external DB
3212 $user = update_user_record($username, get_auth_plugin($user->auth));
3214 } else {
3215 // if user not found, create him
3216 $user = create_user_record($username, $password, $auth);
3219 $authplugin->sync_roles($user);
3221 foreach ($authsenabled as $hau) {
3222 $hauth = get_auth_plugin($hau);
3223 $hauth->user_authenticated_hook($user, $username, $password);
3226 /// Log in to a second system if necessary
3227 /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
3228 if (!empty($CFG->sso)) {
3229 include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
3230 if (function_exists('sso_user_login')) {
3231 if (!sso_user_login($username, $password)) { // Perform the signon process
3232 notify('Second sign-on failed');
3237 if ($user->id===0) {
3238 return false;
3240 return $user;
3243 // failed if all the plugins have failed
3244 add_to_log(0, 'login', 'error', 'index.php', $username);
3245 if (debugging('', DEBUG_ALL)) {
3246 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3248 return false;
3252 * Call to complete the user login process after authenticate_user_login()
3253 * has succeeded. It will setup the $USER variable and other required bits
3254 * and pieces.
3256 * NOTE:
3257 * - It will NOT log anything -- up to the caller to decide what to log.
3261 * @uses $CFG, $USER
3262 * @param string $user obj
3263 * @return user|flase A {@link $USER} object or false if error
3265 function complete_user_login($user) {
3266 global $CFG, $USER;
3268 $USER = $user; // this is required because we need to access preferences here!
3270 if (!empty($CFG->regenloginsession)) {
3271 // please note this setting may break some auth plugins
3272 session_regenerate_id();
3275 reload_user_preferences();
3277 update_user_login_times();
3278 if (empty($CFG->nolastloggedin)) {
3279 set_moodle_cookie($USER->username);
3280 } else {
3281 // do not store last logged in user in cookie
3282 // auth plugins can temporarily override this from loginpage_hook()
3283 // do not save $CFG->nolastloggedin in database!
3284 set_moodle_cookie('nobody');
3286 set_login_session_preferences();
3288 // Call enrolment plugins
3289 check_enrolment_plugins($user);
3291 /// This is what lets the user do anything on the site :-)
3292 load_all_capabilities();
3294 /// Select password change url
3295 $userauth = get_auth_plugin($USER->auth);
3297 /// check whether the user should be changing password
3298 if (get_user_preferences('auth_forcepasswordchange', false)){
3299 if ($userauth->can_change_password()) {
3300 if ($changeurl = $userauth->change_password_url()) {
3301 redirect($changeurl);
3302 } else {
3303 redirect($CFG->httpswwwroot.'/login/change_password.php');
3305 } else {
3306 print_error('nopasswordchangeforced', 'auth');
3309 return $USER;
3313 * Compare password against hash stored in internal user table.
3314 * If necessary it also updates the stored hash to new format.
3316 * @param object user
3317 * @param string plain text password
3318 * @return bool is password valid?
3320 function validate_internal_user_password(&$user, $password) {
3321 global $CFG;
3323 if (!isset($CFG->passwordsaltmain)) {
3324 $CFG->passwordsaltmain = '';
3327 $validated = false;
3329 // get password original encoding in case it was not updated to unicode yet
3330 $textlib = textlib_get_instance();
3331 $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
3333 if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
3334 or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
3335 $validated = true;
3336 } else {
3337 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
3338 $alt = 'passwordsaltalt'.$i;
3339 if (!empty($CFG->$alt)) {
3340 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
3341 $validated = true;
3342 break;
3348 if ($validated) {
3349 // force update of password hash using latest main password salt and encoding if needed
3350 update_internal_user_password($user, $password);
3353 return $validated;
3357 * Calculate hashed value from password using current hash mechanism.
3359 * @param string password
3360 * @return string password hash
3362 function hash_internal_user_password($password) {
3363 global $CFG;
3365 if (isset($CFG->passwordsaltmain)) {
3366 return md5($password.$CFG->passwordsaltmain);
3367 } else {
3368 return md5($password);
3373 * Update pssword hash in user object.
3375 * @param object user
3376 * @param string plain text password
3377 * @param bool store changes also in db, default true
3378 * @return true if hash changed
3380 function update_internal_user_password(&$user, $password) {
3381 global $CFG;
3383 $authplugin = get_auth_plugin($user->auth);
3384 if ($authplugin->prevent_local_passwords()) {
3385 $hashedpassword = 'not cached';
3386 } else {
3387 $hashedpassword = hash_internal_user_password($password);
3390 return set_field('user', 'password', $hashedpassword, 'id', $user->id);
3394 * Get a complete user record, which includes all the info
3395 * in the user record
3396 * Intended for setting as $USER session variable
3398 * @uses $CFG
3399 * @uses SITEID
3400 * @param string $field The user field to be checked for a given value.
3401 * @param string $value The value to match for $field.
3402 * @return user A {@link $USER} object.
3404 function get_complete_user_data($field, $value, $mnethostid=null) {
3406 global $CFG;
3408 if (!$field || !$value) {
3409 return false;
3412 /// Build the WHERE clause for an SQL query
3414 $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
3416 // If we are loading user data based on anything other than id,
3417 // we must also restrict our search based on mnet host.
3418 if ($field != 'id') {
3419 if (empty($mnethostid)) {
3420 // if empty, we restrict to local users
3421 $mnethostid = $CFG->mnet_localhost_id;
3424 if (!empty($mnethostid)) {
3425 $mnethostid = (int)$mnethostid;
3426 $constraints .= ' AND mnethostid = ' . $mnethostid;
3429 /// Get all the basic user data
3431 if (! $user = get_record_select('user', $constraints)) {
3432 return false;
3435 /// Get various settings and preferences
3437 if ($displays = get_records('course_display', 'userid', $user->id)) {
3438 foreach ($displays as $display) {
3439 $user->display[$display->course] = $display->display;
3443 $user->preference = get_user_preferences(null, null, $user->id);
3445 $user->lastcourseaccess = array(); // during last session
3446 $user->currentcourseaccess = array(); // during current session
3447 if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
3448 foreach ($lastaccesses as $lastaccess) {
3449 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
3453 $sql = "SELECT g.id, g.courseid
3454 FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
3455 WHERE gm.groupid=g.id AND gm.userid={$user->id}";
3457 // this is a special hack to speedup calendar display
3458 $user->groupmember = array();
3459 if ($groups = get_records_sql($sql)) {
3460 foreach ($groups as $group) {
3461 if (!array_key_exists($group->courseid, $user->groupmember)) {
3462 $user->groupmember[$group->courseid] = array();
3464 $user->groupmember[$group->courseid][$group->id] = $group->id;
3468 /// Add the custom profile fields to the user record
3469 include_once($CFG->dirroot.'/user/profile/lib.php');
3470 $customfields = (array)profile_user_record($user->id);
3471 foreach ($customfields as $cname=>$cvalue) {
3472 if (!isset($user->$cname)) { // Don't overwrite any standard fields
3473 $user->$cname = $cvalue;
3477 /// Rewrite some variables if necessary
3478 if (!empty($user->description)) {
3479 $user->description = true; // No need to cart all of it around
3481 if ($user->username == 'guest') {
3482 $user->lang = $CFG->lang; // Guest language always same as site
3483 $user->firstname = get_string('guestuser'); // Name always in current language
3484 $user->lastname = ' ';
3487 if (isset($_SERVER['REMOTE_ADDR'])) {
3488 $user->sesskey = random_string(10);
3489 $user->sessionIP = md5(getremoteaddr()); // Store the current IP in the session
3492 return $user;
3496 * @uses $CFG
3497 * @param string $password the password to be checked agains the password policy
3498 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3499 * @return bool true if the password is valid according to the policy. false otherwise.
3501 function check_password_policy($password, &$errmsg) {
3502 global $CFG;
3504 if (empty($CFG->passwordpolicy)) {
3505 return true;
3508 $textlib = textlib_get_instance();
3509 $errmsg = '';
3510 if ($textlib->strlen($password) < $CFG->minpasswordlength) {
3511 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
3514 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
3515 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
3518 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
3519 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
3522 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
3523 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
3526 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
3527 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
3530 if ($errmsg == '') {
3531 return true;
3532 } else {
3533 return false;
3539 * When logging in, this function is run to set certain preferences
3540 * for the current SESSION
3542 function set_login_session_preferences() {
3543 global $SESSION, $CFG;
3545 $SESSION->justloggedin = true;
3547 unset($SESSION->lang);
3549 // Restore the calendar filters, if saved
3550 if (intval(get_user_preferences('calendar_persistflt', 0))) {
3551 include_once($CFG->dirroot.'/calendar/lib.php');
3552 calendar_set_filters_status(get_user_preferences('calendar_savedflt', 0xff));
3558 * Delete a course, including all related data from the database,
3559 * and any associated files from the moodledata folder.
3561 * @param mixed $courseorid The id of the course or course object to delete.
3562 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3563 * @return bool true if all the removals succeeded. false if there were any failures. If this
3564 * method returns false, some of the removals will probably have succeeded, and others
3565 * failed, but you have no way of knowing which.
3567 function delete_course($courseorid, $showfeedback = true) {
3568 global $CFG;
3569 $result = true;
3571 if (is_object($courseorid)) {
3572 $courseid = $courseorid->id;
3573 $course = $courseorid;
3574 } else {
3575 $courseid = $courseorid;
3576 if (!$course = get_record('course', 'id', $courseid)) {
3577 return false;
3581 // frontpage course can not be deleted!!
3582 if ($courseid == SITEID) {
3583 return false;
3586 if (!remove_course_contents($courseid, $showfeedback)) {
3587 if ($showfeedback) {
3588 notify("An error occurred while deleting some of the course contents.");
3590 $result = false;
3593 if (!delete_records("course", "id", $courseid)) {
3594 if ($showfeedback) {
3595 notify("An error occurred while deleting the main course record.");
3597 $result = false;
3600 /// Delete all roles and overiddes in the course context
3601 if (!delete_context(CONTEXT_COURSE, $courseid)) {
3602 if ($showfeedback) {
3603 notify("An error occurred while deleting the main course context.");
3605 $result = false;
3608 if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3609 if ($showfeedback) {
3610 notify("An error occurred while deleting the course files.");
3612 $result = false;
3615 if ($result) {
3616 //trigger events
3617 events_trigger('course_deleted', $course);
3620 return $result;
3624 * Clear a course out completely, deleting all content
3625 * but don't delete the course itself
3627 * @uses $CFG
3628 * @param int $courseid The id of the course that is being deleted
3629 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3630 * @return bool true if all the removals succeeded. false if there were any failures. If this
3631 * method returns false, some of the removals will probably have succeeded, and others
3632 * failed, but you have no way of knowing which.
3634 function remove_course_contents($courseid, $showfeedback=true) {
3636 global $CFG;
3637 require_once($CFG->libdir.'/questionlib.php');
3638 require_once($CFG->libdir.'/gradelib.php');
3640 $result = true;
3642 if (! $course = get_record('course', 'id', $courseid)) {
3643 error('Course ID was incorrect (can\'t find it)');
3646 $strdeleted = get_string('deleted');
3648 /// Clean up course formats (iterate through all formats in the even the course format was ever changed)
3649 $formats = get_list_of_plugins('course/format');
3650 foreach ($formats as $format) {
3651 $formatdelete = $format.'_course_format_delete_course';
3652 $formatlib = "$CFG->dirroot/course/format/$format/lib.php";
3653 if (file_exists($formatlib)) {
3654 include_once($formatlib);
3655 if (function_exists($formatdelete)) {
3656 if ($showfeedback) {
3657 notify($strdeleted.' '.$format);
3659 $formatdelete($course->id);
3664 /// Delete every instance of every module
3666 if ($allmods = get_records('modules') ) {
3667 foreach ($allmods as $mod) {
3668 $modname = $mod->name;
3669 $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3670 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
3671 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
3672 $count=0;
3673 if (file_exists($modfile)) {
3674 include_once($modfile);
3675 if (function_exists($moddelete)) {
3676 if ($instances = get_records($modname, 'course', $course->id)) {
3677 foreach ($instances as $instance) {
3678 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
3679 /// Delete activity context questions and question categories
3680 question_delete_activity($cm, $showfeedback);
3682 if ($moddelete($instance->id)) {
3683 $count++;
3685 } else {
3686 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
3687 $result = false;
3689 if ($cm) {
3690 // delete cm and its context in correct order
3691 delete_records('course_modules', 'id', $cm->id);
3692 delete_context(CONTEXT_MODULE, $cm->id);
3696 } else {
3697 notify('Function '.$moddelete.'() doesn\'t exist!');
3698 $result = false;
3701 if (function_exists($moddeletecourse)) {
3702 $moddeletecourse($course, $showfeedback);
3705 if ($showfeedback) {
3706 notify($strdeleted .' '. $count .' x '. $modname);
3709 } else {
3710 error('No modules are installed!');
3713 /// Give local code a chance to delete its references to this course.
3714 require_once($CFG->libdir.'/locallib.php');
3715 notify_local_delete_course($courseid, $showfeedback);
3717 /// Delete course blocks
3719 if ($blocks = get_records_sql("SELECT *
3720 FROM {$CFG->prefix}block_instance
3721 WHERE pagetype = '".PAGE_COURSE_VIEW."'
3722 AND pageid = $course->id")) {
3723 if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3724 if ($showfeedback) {
3725 notify($strdeleted .' block_instance');
3728 require_once($CFG->libdir.'/blocklib.php');
3729 foreach ($blocks as $block) { /// Delete any associated contexts for this block
3731 delete_context(CONTEXT_BLOCK, $block->id);
3733 // fix for MDL-7164
3734 // Get the block object and call instance_delete()
3735 if (!$record = blocks_get_record($block->blockid)) {
3736 $result = false;
3737 continue;
3739 if (!$obj = block_instance($record->name, $block)) {
3740 $result = false;
3741 continue;
3743 // Return value ignored, in core mods this does not do anything, but just in case
3744 // third party blocks might have stuff to clean up
3745 // we execute this anyway
3746 $obj->instance_delete();
3749 } else {
3750 $result = false;
3754 /// Delete any groups, removing members and grouping/course links first.
3755 require_once($CFG->dirroot.'/group/lib.php');
3756 groups_delete_groupings($courseid, $showfeedback);
3757 groups_delete_groups($courseid, $showfeedback);
3759 /// Delete all related records in other tables that may have a courseid
3760 /// This array stores the tables that need to be cleared, as
3761 /// table_name => column_name that contains the course id.
3763 $tablestoclear = array(
3764 'event' => 'courseid', // Delete events
3765 'log' => 'course', // Delete logs
3766 'course_sections' => 'course', // Delete any course stuff
3767 'course_modules' => 'course',
3768 'backup_courses' => 'courseid', // Delete scheduled backup stuff
3769 'user_lastaccess' => 'courseid',
3770 'backup_log' => 'courseid'
3772 foreach ($tablestoclear as $table => $col) {
3773 if (delete_records($table, $col, $course->id)) {
3774 if ($showfeedback) {
3775 notify($strdeleted . ' ' . $table);
3777 } else {
3778 $result = false;
3783 /// Clean up metacourse stuff
3785 if ($course->metacourse) {
3786 delete_records("course_meta","parent_course",$course->id);
3787 sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3788 if ($showfeedback) {
3789 notify("$strdeleted course_meta");
3791 } else {
3792 if ($parents = get_records("course_meta","child_course",$course->id)) {
3793 foreach ($parents as $parent) {
3794 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3796 if ($showfeedback) {
3797 notify("$strdeleted course_meta");
3802 /// Delete questions and question categories
3803 question_delete_course($course, $showfeedback);
3805 /// Remove all data from gradebook
3806 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3807 remove_course_grades($courseid, $showfeedback);
3808 remove_grade_letters($context, $showfeedback);
3810 return $result;
3814 * Change dates in module - used from course reset.
3815 * @param strin $modname forum, assignent, etc
3816 * @param array $fields array of date fields from mod table
3817 * @param int $timeshift time difference
3818 * @return success
3820 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
3821 global $CFG;
3822 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
3824 $return = true;
3825 foreach ($fields as $field) {
3826 $updatesql = "UPDATE {$CFG->prefix}$modname
3827 SET $field = $field + ($timeshift)
3828 WHERE course=$courseid AND $field<>0 AND $field<>0";
3829 $return = execute_sql($updatesql, false) && $return;
3832 $refreshfunction = $modname.'_refresh_events';
3833 if (function_exists($refreshfunction)) {
3834 $refreshfunction($courseid);
3837 return $return;
3841 * This function will empty a course of user data.
3842 * It will retain the activities and the structure of the course.
3843 * @param object $data an object containing all the settings including courseid (without magic quotes)
3844 * @return array status array of array component, item, error
3846 function reset_course_userdata($data) {
3847 global $CFG, $USER;
3848 require_once($CFG->libdir.'/gradelib.php');
3849 require_once($CFG->dirroot.'/group/lib.php');
3851 $data->courseid = $data->id;
3852 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3854 // calculate the time shift of dates
3855 if (!empty($data->reset_start_date)) {
3856 // time part of course startdate should be zero
3857 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
3858 } else {
3859 $data->timeshift = 0;
3862 // result array: component, item, error
3863 $status = array();
3865 // start the resetting
3866 $componentstr = get_string('general');
3868 // move the course start time
3869 if (!empty($data->reset_start_date) and $data->timeshift) {
3870 // change course start data
3871 set_field('course', 'startdate', $data->reset_start_date, 'id', $data->courseid);
3872 // update all course and group events - do not move activity events
3873 $updatesql = "UPDATE {$CFG->prefix}event
3874 SET timestart = timestart + ({$data->timeshift})
3875 WHERE courseid={$data->courseid} AND instance=0";
3876 execute_sql($updatesql, false);
3878 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3881 if (!empty($data->reset_logs)) {
3882 delete_records('log', 'course', $data->courseid);
3883 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
3886 if (!empty($data->reset_events)) {
3887 delete_records('event', 'courseid', $data->courseid);
3888 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
3891 if (!empty($data->reset_notes)) {
3892 require_once($CFG->dirroot.'/notes/lib.php');
3893 note_delete_all($data->courseid);
3894 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
3897 $componentstr = get_string('roles');
3899 if (!empty($data->reset_roles_overrides)) {
3900 $children = get_child_contexts($context);
3901 foreach ($children as $child) {
3902 delete_records('role_capabilities', 'contextid', $child->id);
3904 delete_records('role_capabilities', 'contextid', $context->id);
3905 //force refresh for logged in users
3906 mark_context_dirty($context->path);
3907 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
3910 if (!empty($data->reset_roles_local)) {
3911 $children = get_child_contexts($context);
3912 foreach ($children as $child) {
3913 role_unassign(0, 0, 0, $child->id);
3915 //force refresh for logged in users
3916 mark_context_dirty($context->path);
3917 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
3920 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
3921 $data->unenrolled = array();
3922 if (!empty($data->reset_roles)) {
3923 foreach($data->reset_roles as $roleid) {
3924 if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) {
3925 foreach ($users as $user) {
3926 role_unassign($roleid, $user->id, 0, $context->id);
3927 if (!has_capability('moodle/course:view', $context, $user->id)) {
3928 $data->unenrolled[$user->id] = $user->id;
3934 if (!empty($data->unenrolled)) {
3935 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol').' ('.count($data->unenrolled).')', 'error'=>false);
3939 $componentstr = get_string('groups');
3941 // remove all group members
3942 if (!empty($data->reset_groups_members)) {
3943 groups_delete_group_members($data->courseid);
3944 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
3947 // remove all groups
3948 if (!empty($data->reset_groups_remove)) {
3949 groups_delete_groups($data->courseid, false);
3950 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
3953 // remove all grouping members
3954 if (!empty($data->reset_groupings_members)) {
3955 groups_delete_groupings_groups($data->courseid, false);
3956 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
3959 // remove all groupings
3960 if (!empty($data->reset_groupings_remove)) {
3961 groups_delete_groupings($data->courseid, false);
3962 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
3965 // Look in every instance of every module for data to delete
3966 $unsupported_mods = array();
3967 if ($allmods = get_records('modules') ) {
3968 foreach ($allmods as $mod) {
3969 $modname = $mod->name;
3970 if (!count_records($modname, 'course', $data->courseid)) {
3971 continue; // skip mods with no instances
3973 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
3974 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
3975 if (file_exists($modfile)) {
3976 include_once($modfile);
3977 if (function_exists($moddeleteuserdata)) {
3978 $modstatus = $moddeleteuserdata($data);
3979 if (is_array($modstatus)) {
3980 $status = array_merge($status, $modstatus);
3981 } else {
3982 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
3984 } else {
3985 $unsupported_mods[] = $mod;
3987 } else {
3988 debugging('Missing lib.php in '.$modname.' module!');
3993 // mention unsupported mods
3994 if (!empty($unsupported_mods)) {
3995 foreach($unsupported_mods as $mod) {
3996 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
4001 $componentstr = get_string('gradebook', 'grades');
4002 // reset gradebook
4003 if (!empty($data->reset_gradebook_items)) {
4004 remove_course_grades($data->courseid, false);
4005 grade_grab_course_grades($data->courseid);
4006 grade_regrade_final_grades($data->courseid);
4007 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4009 } else if (!empty($data->reset_gradebook_grades)) {
4010 grade_course_reset($data->courseid);
4011 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4014 return $status;
4017 function generate_email_processing_address($modid,$modargs) {
4018 global $CFG;
4020 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4021 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
4024 function moodle_process_email($modargs,$body) {
4025 // the first char should be an unencoded letter. We'll take this as an action
4026 switch ($modargs{0}) {
4027 case 'B': { // bounce
4028 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4029 if ($user = get_record_select("user","id=$userid","id,email")) {
4030 // check the half md5 of their email
4031 $md5check = substr(md5($user->email),0,16);
4032 if ($md5check == substr($modargs, -16)) {
4033 set_bounce_count($user);
4035 // else maybe they've already changed it?
4038 break;
4039 // maybe more later?
4043 /// CORRESPONDENCE ////////////////////////////////////////////////
4046 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4047 * @param $action string 'get', 'buffer', 'close' or 'flush'
4048 * @return reference to mailer instance if 'get' used or nothing
4050 function &get_mailer($action='get') {
4051 global $CFG;
4053 static $mailer = null;
4054 static $counter = 0;
4056 if (!isset($CFG->smtpmaxbulk)) {
4057 $CFG->smtpmaxbulk = 1;
4060 if ($action == 'get') {
4061 $prevkeepalive = false;
4063 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4064 if ($counter < $CFG->smtpmaxbulk and empty($mailer->error_count)) {
4065 $counter++;
4066 // reset the mailer
4067 $mailer->Priority = 3;
4068 $mailer->CharSet = 'UTF-8'; // our default
4069 $mailer->ContentType = "text/plain";
4070 $mailer->Encoding = "8bit";
4071 $mailer->From = "root@localhost";
4072 $mailer->FromName = "Root User";
4073 $mailer->Sender = "";
4074 $mailer->Subject = "";
4075 $mailer->Body = "";
4076 $mailer->AltBody = "";
4077 $mailer->ConfirmReadingTo = "";
4079 $mailer->ClearAllRecipients();
4080 $mailer->ClearReplyTos();
4081 $mailer->ClearAttachments();
4082 $mailer->ClearCustomHeaders();
4083 return $mailer;
4086 $prevkeepalive = $mailer->SMTPKeepAlive;
4087 get_mailer('flush');
4090 include_once($CFG->libdir.'/phpmailer/class.phpmailer.php');
4091 $mailer = new phpmailer();
4093 $counter = 1;
4095 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
4096 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
4097 $mailer->CharSet = 'UTF-8';
4099 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
4100 // hmm, this is a bit hacky because LE should be private
4101 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
4102 $mailer->LE = "\r\n";
4103 } else {
4104 $mailer->LE = "\n";
4107 if ($CFG->smtphosts == 'qmail') {
4108 $mailer->IsQmail(); // use Qmail system
4110 } else if (empty($CFG->smtphosts)) {
4111 $mailer->IsMail(); // use PHP mail() = sendmail
4113 } else {
4114 $mailer->IsSMTP(); // use SMTP directly
4115 if (!empty($CFG->debugsmtp)) {
4116 $mailer->SMTPDebug = true;
4118 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
4119 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
4121 if ($CFG->smtpuser) { // Use SMTP authentication
4122 $mailer->SMTPAuth = true;
4123 $mailer->Username = $CFG->smtpuser;
4124 $mailer->Password = $CFG->smtppass;
4128 return $mailer;
4131 $nothing = null;
4133 // keep smtp session open after sending
4134 if ($action == 'buffer') {
4135 if (!empty($CFG->smtpmaxbulk)) {
4136 get_mailer('flush');
4137 $m =& get_mailer();
4138 if ($m->Mailer == 'smtp') {
4139 $m->SMTPKeepAlive = true;
4142 return $nothing;
4145 // close smtp session, but continue buffering
4146 if ($action == 'flush') {
4147 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4148 if (!empty($mailer->SMTPDebug)) {
4149 echo '<pre>'."\n";
4151 $mailer->SmtpClose();
4152 if (!empty($mailer->SMTPDebug)) {
4153 echo '</pre>';
4156 return $nothing;
4159 // close smtp session, do not buffer anymore
4160 if ($action == 'close') {
4161 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4162 get_mailer('flush');
4163 $mailer->SMTPKeepAlive = false;
4165 $mailer = null; // better force new instance
4166 return $nothing;
4171 * Send an email to a specified user
4173 * @uses $CFG
4174 * @uses $FULLME
4175 * @uses $MNETIDPJUMPURL IdentityProvider(IDP) URL user hits to jump to mnet peer.
4176 * @uses SITEID
4177 * @param user $user A {@link $USER} object
4178 * @param user $from A {@link $USER} object
4179 * @param string $subject plain text subject line of the email
4180 * @param string $messagetext plain text version of the message
4181 * @param string $messagehtml complete html version of the message (optional)
4182 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4183 * @param string $attachname the name of the file (extension indicates MIME)
4184 * @param bool $usetrueaddress determines whether $from email address should
4185 * be sent out. Will be overruled by user profile setting for maildisplay
4186 * @param int $wordwrapwidth custom word wrap width
4187 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4188 * was blocked by user and "false" if there was another sort of error.
4190 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4192 global $CFG, $FULLME, $MNETIDPJUMPURL;
4193 static $mnetjumps = array();
4195 if (empty($user) || empty($user->email)) {
4196 return false;
4199 if (!empty($user->deleted)) {
4200 // do not mail delted users
4201 return false;
4204 if (!empty($CFG->noemailever)) {
4205 // hidden setting for development sites, set in config.php if needed
4206 return true;
4209 // skip mail to suspended users
4210 if (isset($user->auth) && $user->auth=='nologin') {
4211 return true;
4214 if (!empty($user->emailstop)) {
4215 return 'emailstop';
4218 if (over_bounce_threshold($user)) {
4219 error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
4220 return false;
4223 // If the user is a remote mnet user, parse the email text for URL to the
4224 // wwwroot and modify the url to direct the user's browser to login at their
4225 // home site (identity provider - idp) before hitting the link itself
4226 if (is_mnet_remote_user($user)) {
4227 require_once($CFG->dirroot.'/mnet/lib.php');
4228 // Form the request url to hit the idp's jump.php
4229 if (isset($mnetjumps[$user->mnethostid])) {
4230 $MNETIDPJUMPURL = $mnetjumps[$user->mnethostid];
4231 } else {
4232 $idp = mnet_get_peer_host($user->mnethostid);
4233 $idpjumppath = '/auth/mnet/jump.php';
4234 $MNETIDPJUMPURL = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
4235 $mnetjumps[$user->mnethostid] = $MNETIDPJUMPURL;
4238 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4239 'mnet_sso_apply_indirection',
4240 $messagetext);
4241 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4242 'mnet_sso_apply_indirection',
4243 $messagehtml);
4245 $mail =& get_mailer();
4247 if (!empty($mail->SMTPDebug)) {
4248 echo '<pre>' . "\n";
4251 /// We are going to use textlib services here
4252 $textlib = textlib_get_instance();
4254 $supportuser = generate_email_supportuser();
4256 // make up an email address for handling bounces
4257 if (!empty($CFG->handlebounces)) {
4258 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
4259 $mail->Sender = generate_email_processing_address(0,$modargs);
4260 } else {
4261 $mail->Sender = $supportuser->email;
4264 if (is_string($from)) { // So we can pass whatever we want if there is need
4265 $mail->From = $CFG->noreplyaddress;
4266 $mail->FromName = $from;
4267 } else if ($usetrueaddress and $from->maildisplay) {
4268 $mail->From = stripslashes($from->email);
4269 $mail->FromName = fullname($from);
4270 } else {
4271 $mail->From = $CFG->noreplyaddress;
4272 $mail->FromName = fullname($from);
4273 if (empty($replyto)) {
4274 $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
4278 if (!empty($replyto)) {
4279 $mail->AddReplyTo($replyto,$replytoname);
4282 $mail->Subject = substr(stripslashes($subject), 0, 900);
4284 $mail->AddAddress(stripslashes($user->email), fullname($user) );
4286 $mail->WordWrap = $wordwrapwidth; // set word wrap
4288 if (!empty($from->customheaders)) { // Add custom headers
4289 if (is_array($from->customheaders)) {
4290 foreach ($from->customheaders as $customheader) {
4291 $mail->AddCustomHeader($customheader);
4293 } else {
4294 $mail->AddCustomHeader($from->customheaders);
4298 if (!empty($from->priority)) {
4299 $mail->Priority = $from->priority;
4302 if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
4303 $mail->IsHTML(true);
4304 $mail->Encoding = 'quoted-printable'; // Encoding to use
4305 $mail->Body = $messagehtml;
4306 $mail->AltBody = "\n$messagetext\n";
4307 } else {
4308 $mail->IsHTML(false);
4309 $mail->Body = "\n$messagetext\n";
4312 if ($attachment && $attachname) {
4313 if (ereg( "\\.\\." ,$attachment )) { // Security check for ".." in dir path
4314 $mail->AddAddress($supportuser->email, fullname($supportuser, true) );
4315 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4316 } else {
4317 require_once($CFG->libdir.'/filelib.php');
4318 $mimetype = mimeinfo('type', $attachname);
4319 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
4325 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
4326 /// encoding to the specified one
4327 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
4328 /// Set it to site mail charset
4329 $charset = $CFG->sitemailcharset;
4330 /// Overwrite it with the user mail charset
4331 if (!empty($CFG->allowusermailcharset)) {
4332 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
4333 $charset = $useremailcharset;
4336 /// If it has changed, convert all the necessary strings
4337 $charsets = get_list_of_charsets();
4338 unset($charsets['UTF-8']);
4339 if (in_array($charset, $charsets)) {
4340 /// Save the new mail charset
4341 $mail->CharSet = $charset;
4342 /// And convert some strings
4343 $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
4344 foreach ($mail->ReplyTo as $key => $rt) { //ReplyTo Names
4345 $mail->ReplyTo[$key][1] = $textlib->convert($rt[1], 'utf-8', $mail->CharSet);
4347 $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet); //Subject
4348 foreach ($mail->to as $key => $to) {
4349 $mail->to[$key][1] = $textlib->convert($to[1], 'utf-8', $mail->CharSet); //To Names
4351 $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet); //Body
4352 $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet); //Subject
4356 if ($mail->Send()) {
4357 set_send_count($user);
4358 $mail->IsSMTP(); // use SMTP directly
4359 if (!empty($mail->SMTPDebug)) {
4360 echo '</pre>';
4362 return true;
4363 } else {
4364 mtrace('ERROR: '. $mail->ErrorInfo);
4365 add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
4366 if (!empty($mail->SMTPDebug)) {
4367 echo '</pre>';
4369 return false;
4374 * Generate a signoff for emails based on support settings
4377 function generate_email_signoff() {
4378 global $CFG;
4380 $signoff = "\n";
4381 if (!empty($CFG->supportname)) {
4382 $signoff .= $CFG->supportname."\n";
4384 if (!empty($CFG->supportemail)) {
4385 $signoff .= $CFG->supportemail."\n";
4387 if (!empty($CFG->supportpage)) {
4388 $signoff .= $CFG->supportpage."\n";
4390 return $signoff;
4394 * Generate a fake user for emails based on support settings
4397 function generate_email_supportuser() {
4399 global $CFG;
4401 static $supportuser;
4403 if (!empty($supportuser)) {
4404 return $supportuser;
4407 $supportuser = new object;
4408 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
4409 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
4410 $supportuser->lastname = '';
4411 $supportuser->maildisplay = true;
4413 return $supportuser;
4418 * Sets specified user's password and send the new password to the user via email.
4420 * @uses $CFG
4421 * @param user $user A {@link $USER} object
4422 * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
4423 * was blocked by user and "false" if there was another sort of error.
4425 function setnew_password_and_mail($user) {
4427 global $CFG;
4429 $site = get_site();
4431 $supportuser = generate_email_supportuser();
4433 $newpassword = generate_password();
4435 if (! set_field('user', 'password', md5($newpassword), 'id', $user->id) ) {
4436 trigger_error('Could not set user password!');
4437 return false;
4440 $a = new object();
4441 $a->firstname = fullname($user, true);
4442 $a->sitename = format_string($site->fullname);
4443 $a->username = $user->username;
4444 $a->newpassword = $newpassword;
4445 $a->link = $CFG->wwwroot .'/login/';
4446 $a->signoff = generate_email_signoff();
4448 $message = get_string('newusernewpasswordtext', '', $a);
4450 $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
4452 return email_to_user($user, $supportuser, $subject, $message);
4457 * Resets specified user's password and send the new password to the user via email.
4459 * @uses $CFG
4460 * @param user $user A {@link $USER} object
4461 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4462 * was blocked by user and "false" if there was another sort of error.
4464 function reset_password_and_mail($user) {
4466 global $CFG;
4468 $site = get_site();
4469 $supportuser = generate_email_supportuser();
4471 $userauth = get_auth_plugin($user->auth);
4472 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
4473 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4474 return false;
4477 $newpassword = generate_password();
4479 if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
4480 error("Could not set user password!");
4483 $a = new object();
4484 $a->firstname = $user->firstname;
4485 $a->lastname = $user->lastname;
4486 $a->sitename = format_string($site->fullname);
4487 $a->username = $user->username;
4488 $a->newpassword = $newpassword;
4489 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
4490 $a->signoff = generate_email_signoff();
4492 $message = get_string('newpasswordtext', '', $a);
4494 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
4496 return email_to_user($user, $supportuser, $subject, $message);
4501 * Send email to specified user with confirmation text and activation link.
4503 * @uses $CFG
4504 * @param user $user A {@link $USER} object
4505 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4506 * was blocked by user and "false" if there was another sort of error.
4508 function send_confirmation_email($user) {
4510 global $CFG;
4512 $site = get_site();
4513 $supportuser = generate_email_supportuser();
4515 $data = new object();
4516 $data->firstname = fullname($user);
4517 $data->sitename = format_string($site->fullname);
4518 $data->admin = generate_email_signoff();
4520 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
4522 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
4523 $message = get_string('emailconfirmation', '', $data);
4524 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4526 $user->mailformat = 1; // Always send HTML version as well
4528 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4533 * send_password_change_confirmation_email.
4535 * @uses $CFG
4536 * @param user $user A {@link $USER} object
4537 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4538 * was blocked by user and "false" if there was another sort of error.
4540 function send_password_change_confirmation_email($user) {
4542 global $CFG;
4544 $site = get_site();
4545 $supportuser = generate_email_supportuser();
4547 $data = new object();
4548 $data->firstname = $user->firstname;
4549 $data->lastname = $user->lastname;
4550 $data->sitename = format_string($site->fullname);
4551 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
4552 $data->admin = generate_email_signoff();
4554 $message = get_string('emailpasswordconfirmation', '', $data);
4555 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
4557 return email_to_user($user, $supportuser, $subject, $message);
4562 * send_password_change_info.
4564 * @uses $CFG
4565 * @param user $user A {@link $USER} object
4566 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4567 * was blocked by user and "false" if there was another sort of error.
4569 function send_password_change_info($user) {
4571 global $CFG;
4573 $site = get_site();
4574 $supportuser = generate_email_supportuser();
4575 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4577 $data = new object();
4578 $data->firstname = $user->firstname;
4579 $data->lastname = $user->lastname;
4580 $data->sitename = format_string($site->fullname);
4581 $data->admin = generate_email_signoff();
4583 $userauth = get_auth_plugin($user->auth);
4585 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
4586 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4587 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4588 return email_to_user($user, $supportuser, $subject, $message);
4591 if ($userauth->can_change_password() and $userauth->change_password_url()) {
4592 // we have some external url for password changing
4593 $data->link .= $userauth->change_password_url();
4595 } else {
4596 //no way to change password, sorry
4597 $data->link = '';
4600 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
4601 $message = get_string('emailpasswordchangeinfo', '', $data);
4602 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4603 } else {
4604 $message = get_string('emailpasswordchangeinfofail', '', $data);
4605 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4608 return email_to_user($user, $supportuser, $subject, $message);
4613 * Check that an email is allowed. It returns an error message if there
4614 * was a problem.
4616 * @uses $CFG
4617 * @param string $email Content of email
4618 * @return string|false
4620 function email_is_not_allowed($email) {
4622 global $CFG;
4624 if (!empty($CFG->allowemailaddresses)) {
4625 $allowed = explode(' ', $CFG->allowemailaddresses);
4626 foreach ($allowed as $allowedpattern) {
4627 $allowedpattern = trim($allowedpattern);
4628 if (!$allowedpattern) {
4629 continue;
4631 if (strpos($allowedpattern, '.') === 0) {
4632 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
4633 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4634 return false;
4637 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
4638 return false;
4641 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
4643 } else if (!empty($CFG->denyemailaddresses)) {
4644 $denied = explode(' ', $CFG->denyemailaddresses);
4645 foreach ($denied as $deniedpattern) {
4646 $deniedpattern = trim($deniedpattern);
4647 if (!$deniedpattern) {
4648 continue;
4650 if (strpos($deniedpattern, '.') === 0) {
4651 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
4652 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4653 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4656 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
4657 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4662 return false;
4665 function email_welcome_message_to_user($course, $user=NULL) {
4666 global $CFG, $USER;
4668 if (isset($CFG->sendcoursewelcomemessage) and !$CFG->sendcoursewelcomemessage) {
4669 return;
4672 if (empty($user)) {
4673 if (!isloggedin()) {
4674 return false;
4676 $user = $USER;
4679 if (!empty($course->welcomemessage)) {
4680 $message = $course->welcomemessage;
4681 } else {
4682 $a = new Object();
4683 $a->coursename = $course->fullname;
4684 $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id";
4685 $message = get_string("welcometocoursetext", "", $a);
4688 /// If you don't want a welcome message sent, then make the message string blank.
4689 if (!empty($message)) {
4690 $subject = get_string('welcometocourse', '', format_string($course->fullname));
4692 if (! $teacher = get_teacher($course->id)) {
4693 $teacher = get_admin();
4695 email_to_user($user, $teacher, $subject, $message);
4699 /// FILE HANDLING /////////////////////////////////////////////
4703 * Makes an upload directory for a particular module.
4705 * @uses $CFG
4706 * @param int $courseid The id of the course in question - maps to id field of 'course' table.
4707 * @return string|false Returns full path to directory if successful, false if not
4709 function make_mod_upload_directory($courseid) {
4710 global $CFG;
4712 if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
4713 return false;
4716 $strreadme = get_string('readme');
4718 if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
4719 copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4720 } else {
4721 copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4723 return $moddata;
4727 * Makes a directory for a particular user.
4729 * @uses $CFG
4730 * @param int $userid The id of the user in question - maps to id field of 'user' table.
4731 * @param bool $test Whether we are only testing the return value (do not create the directory)
4732 * @return string|false Returns full path to directory if successful, false if not
4734 function make_user_directory($userid, $test=false) {
4735 global $CFG;
4737 if (is_bool($userid) || $userid < 0 || !ereg('^[0-9]{1,10}$', $userid) || $userid > 2147483647) {
4738 if (!$test) {
4739 notify("Given userid was not a valid integer! (" . gettype($userid) . " $userid)");
4741 return false;
4744 // Generate a two-level path for the userid. First level groups them by slices of 1000 users, second level is userid
4745 $level1 = floor($userid / 1000) * 1000;
4747 $userdir = "user/$level1/$userid";
4748 if ($test) {
4749 return $CFG->dataroot . '/' . $userdir;
4750 } else {
4751 return make_upload_directory($userdir);
4756 * Returns an array of full paths to user directories, indexed by their userids.
4758 * @param bool $only_non_empty Only return directories that contain files
4759 * @param bool $legacy Search for user directories in legacy location (dataroot/users/userid) instead of (dataroot/user/section/userid)
4760 * @return array An associative array: userid=>array(basedir => $basedir, userfolder => $userfolder)
4762 function get_user_directories($only_non_empty=true, $legacy=false) {
4763 global $CFG;
4765 $rootdir = $CFG->dataroot."/user";
4767 if ($legacy) {
4768 $rootdir = $CFG->dataroot."/users";
4770 $dirlist = array();
4772 //Check if directory exists
4773 if (check_dir_exists($rootdir, true)) {
4774 if ($legacy) {
4775 if ($userlist = get_directory_list($rootdir, '', true, true, false)) {
4776 foreach ($userlist as $userid) {
4777 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $userid);
4779 } else {
4780 notify("no directories found under $rootdir");
4782 } else {
4783 if ($grouplist =get_directory_list($rootdir, '', true, true, false)) { // directories will be in the form 0, 1000, 2000 etc...
4784 foreach ($grouplist as $group) {
4785 if ($userlist = get_directory_list("$rootdir/$group", '', true, true, false)) {
4786 foreach ($userlist as $userid) {
4787 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $group . '/' . $userid);
4793 } else {
4794 notify("$rootdir does not exist!");
4795 return false;
4797 return $dirlist;
4801 * Returns current name of file on disk if it exists.
4803 * @param string $newfile File to be verified
4804 * @return string Current name of file on disk if true
4806 function valid_uploaded_file($newfile) {
4807 if (empty($newfile)) {
4808 return '';
4810 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
4811 return $newfile['tmp_name'];
4812 } else {
4813 return '';
4818 * Returns the maximum size for uploading files.
4820 * There are seven possible upload limits:
4821 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
4822 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
4823 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
4824 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
4825 * 5. by the Moodle admin in $CFG->maxbytes
4826 * 6. by the teacher in the current course $course->maxbytes
4827 * 7. by the teacher for the current module, eg $assignment->maxbytes
4829 * These last two are passed to this function as arguments (in bytes).
4830 * Anything defined as 0 is ignored.
4831 * The smallest of all the non-zero numbers is returned.
4833 * @param int $sizebytes ?
4834 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4835 * @param int $modulebytes Current module ->maxbytes (in bytes)
4836 * @return int The maximum size for uploading files.
4837 * @todo Finish documenting this function
4839 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4841 if (! $filesize = ini_get('upload_max_filesize')) {
4842 $filesize = '5M';
4844 $minimumsize = get_real_size($filesize);
4846 if ($postsize = ini_get('post_max_size')) {
4847 $postsize = get_real_size($postsize);
4848 if ($postsize < $minimumsize) {
4849 $minimumsize = $postsize;
4853 if ($sitebytes and $sitebytes < $minimumsize) {
4854 $minimumsize = $sitebytes;
4857 if ($coursebytes and $coursebytes < $minimumsize) {
4858 $minimumsize = $coursebytes;
4861 if ($modulebytes and $modulebytes < $minimumsize) {
4862 $minimumsize = $modulebytes;
4865 return $minimumsize;
4869 * Related to {@link get_max_upload_file_size()} - this function returns an
4870 * array of possible sizes in an array, translated to the
4871 * local language.
4873 * @uses SORT_NUMERIC
4874 * @param int $sizebytes ?
4875 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4876 * @param int $modulebytes Current module ->maxbytes (in bytes)
4877 * @return int
4878 * @todo Finish documenting this function
4880 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4881 global $CFG;
4883 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
4884 return array();
4887 $filesize[$maxsize] = display_size($maxsize);
4889 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
4890 5242880, 10485760, 20971520, 52428800, 104857600);
4892 // Allow maxbytes to be selected if it falls outside the above boundaries
4893 if( isset($CFG->maxbytes) && !in_array($CFG->maxbytes, $sizelist) ){
4894 $sizelist[] = $CFG->maxbytes;
4897 foreach ($sizelist as $sizebytes) {
4898 if ($sizebytes < $maxsize) {
4899 $filesize[$sizebytes] = display_size($sizebytes);
4903 krsort($filesize, SORT_NUMERIC);
4905 return $filesize;
4909 * If there has been an error uploading a file, print the appropriate error message
4910 * Numerical constants used as constant definitions not added until PHP version 4.2.0
4912 * $filearray is a 1-dimensional sub-array of the $_FILES array
4913 * eg $filearray = $_FILES['userfile1']
4914 * If left empty then the first element of the $_FILES array will be used
4916 * @uses $_FILES
4917 * @param array $filearray A 1-dimensional sub-array of the $_FILES array
4918 * @param bool $returnerror If true then a string error message will be returned. Otherwise the user will be notified of the error in a notify() call.
4919 * @return bool|string
4921 function print_file_upload_error($filearray = '', $returnerror = false) {
4923 if ($filearray == '' or !isset($filearray['error'])) {
4925 if (empty($_FILES)) return false;
4927 $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
4928 $filearray = array_shift($files); /// use first element of array
4931 switch ($filearray['error']) {
4933 case 0: // UPLOAD_ERR_OK
4934 if ($filearray['size'] > 0) {
4935 $errmessage = get_string('uploadproblem', $filearray['name']);
4936 } else {
4937 $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
4939 break;
4941 case 1: // UPLOAD_ERR_INI_SIZE
4942 $errmessage = get_string('uploadserverlimit');
4943 break;
4945 case 2: // UPLOAD_ERR_FORM_SIZE
4946 $errmessage = get_string('uploadformlimit');
4947 break;
4949 case 3: // UPLOAD_ERR_PARTIAL
4950 $errmessage = get_string('uploadpartialfile');
4951 break;
4953 case 4: // UPLOAD_ERR_NO_FILE
4954 $errmessage = get_string('uploadnofilefound');
4955 break;
4957 default:
4958 $errmessage = get_string('uploadproblem', $filearray['name']);
4961 if ($returnerror) {
4962 return $errmessage;
4963 } else {
4964 notify($errmessage);
4965 return true;
4971 * handy function to loop through an array of files and resolve any filename conflicts
4972 * both in the array of filenames and for what is already on disk.
4973 * not really compatible with the similar function in uploadlib.php
4974 * but this could be used for files/index.php for moving files around.
4977 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
4978 foreach ($files as $k => $f) {
4979 if (check_potential_filename($destination,$f,$files)) {
4980 $bits = explode('.', $f);
4981 for ($i = 1; true; $i++) {
4982 $try = sprintf($format, $bits[0], $i, $bits[1]);
4983 if (!check_potential_filename($destination,$try,$files)) {
4984 $files[$k] = $try;
4985 break;
4990 return $files;
4994 * @used by resolve_filename_collisions
4996 function check_potential_filename($destination,$filename,$files) {
4997 if (file_exists($destination.'/'.$filename)) {
4998 return true;
5000 if (count(array_keys($files,$filename)) > 1) {
5001 return true;
5003 return false;
5008 * Returns an array with all the filenames in
5009 * all subdirectories, relative to the given rootdir.
5010 * If excludefile is defined, then that file/directory is ignored
5011 * If getdirs is true, then (sub)directories are included in the output
5012 * If getfiles is true, then files are included in the output
5013 * (at least one of these must be true!)
5015 * @param string $rootdir ?
5016 * @param string $excludefile If defined then the specified file/directory is ignored
5017 * @param bool $descend ?
5018 * @param bool $getdirs If true then (sub)directories are included in the output
5019 * @param bool $getfiles If true then files are included in the output
5020 * @return array An array with all the filenames in
5021 * all subdirectories, relative to the given rootdir
5022 * @todo Finish documenting this function. Add examples of $excludefile usage.
5024 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5026 $dirs = array();
5028 if (!$getdirs and !$getfiles) { // Nothing to show
5029 return $dirs;
5032 if (!is_dir($rootdir)) { // Must be a directory
5033 return $dirs;
5036 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5037 return $dirs;
5040 if (!is_array($excludefiles)) {
5041 $excludefiles = array($excludefiles);
5044 while (false !== ($file = readdir($dir))) {
5045 $firstchar = substr($file, 0, 1);
5046 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5047 continue;
5049 $fullfile = $rootdir .'/'. $file;
5050 if (filetype($fullfile) == 'dir') {
5051 if ($getdirs) {
5052 $dirs[] = $file;
5054 if ($descend) {
5055 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5056 foreach ($subdirs as $subdir) {
5057 $dirs[] = $file .'/'. $subdir;
5060 } else if ($getfiles) {
5061 $dirs[] = $file;
5064 closedir($dir);
5066 asort($dirs);
5068 return $dirs;
5073 * Adds up all the files in a directory and works out the size.
5075 * @param string $rootdir ?
5076 * @param string $excludefile ?
5077 * @return array
5078 * @todo Finish documenting this function
5080 function get_directory_size($rootdir, $excludefile='') {
5082 global $CFG;
5084 // do it this way if we can, it's much faster
5085 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5086 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5087 $output = null;
5088 $return = null;
5089 exec($command,$output,$return);
5090 if (is_array($output)) {
5091 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5095 if (!is_dir($rootdir)) { // Must be a directory
5096 return 0;
5099 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
5100 return 0;
5103 $size = 0;
5105 while (false !== ($file = readdir($dir))) {
5106 $firstchar = substr($file, 0, 1);
5107 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5108 continue;
5110 $fullfile = $rootdir .'/'. $file;
5111 if (filetype($fullfile) == 'dir') {
5112 $size += get_directory_size($fullfile, $excludefile);
5113 } else {
5114 $size += filesize($fullfile);
5117 closedir($dir);
5119 return $size;
5123 * Converts bytes into display form
5125 * @param string $size ?
5126 * @return string
5127 * @staticvar string $gb Localized string for size in gigabytes
5128 * @staticvar string $mb Localized string for size in megabytes
5129 * @staticvar string $kb Localized string for size in kilobytes
5130 * @staticvar string $b Localized string for size in bytes
5131 * @todo Finish documenting this function. Verify return type.
5133 function display_size($size) {
5135 static $gb, $mb, $kb, $b;
5137 if (empty($gb)) {
5138 $gb = get_string('sizegb');
5139 $mb = get_string('sizemb');
5140 $kb = get_string('sizekb');
5141 $b = get_string('sizeb');
5144 if ($size >= 1073741824) {
5145 $size = round($size / 1073741824 * 10) / 10 . $gb;
5146 } else if ($size >= 1048576) {
5147 $size = round($size / 1048576 * 10) / 10 . $mb;
5148 } else if ($size >= 1024) {
5149 $size = round($size / 1024 * 10) / 10 . $kb;
5150 } else {
5151 $size = $size .' '. $b;
5153 return $size;
5157 * Cleans a given filename by removing suspicious or troublesome characters
5158 * Only these are allowed: alphanumeric _ - .
5159 * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
5161 * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
5162 * because native zip binaries do weird character conversions. Use PHP zipping instead.
5164 * @param string $string file name
5165 * @return string cleaned file name
5167 function clean_filename($string) {
5168 global $CFG;
5169 if (empty($CFG->unicodecleanfilename)) {
5170 $textlib = textlib_get_instance();
5171 $string = $textlib->specialtoascii($string);
5172 $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
5173 } else {
5174 //clean only ascii range
5175 $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
5177 $string = preg_replace("/_+/", '_', $string);
5178 $string = preg_replace("/\.\.+/", '.', $string);
5179 return $string;
5183 /// STRING TRANSLATION ////////////////////////////////////////
5186 * Returns the code for the current language
5188 * @uses $CFG
5189 * @param $USER
5190 * @param $SESSION
5191 * @return string
5193 function current_language() {
5194 global $CFG, $USER, $SESSION, $COURSE;
5196 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
5197 $return = $COURSE->lang;
5199 } else if (!empty($SESSION->lang)) { // Session language can override other settings
5200 $return = $SESSION->lang;
5202 } else if (!empty($USER->lang)) {
5203 $return = $USER->lang;
5205 } else {
5206 $return = $CFG->lang;
5209 if ($return == 'en') {
5210 $return = 'en_utf8';
5213 return $return;
5217 * Prints out a translated string.
5219 * Prints out a translated string using the return value from the {@link get_string()} function.
5221 * Example usage of this function when the string is in the moodle.php file:<br/>
5222 * <code>
5223 * echo '<strong>';
5224 * print_string('wordforstudent');
5225 * echo '</strong>';
5226 * </code>
5228 * Example usage of this function when the string is not in the moodle.php file:<br/>
5229 * <code>
5230 * echo '<h1>';
5231 * print_string('typecourse', 'calendar');
5232 * echo '</h1>';
5233 * </code>
5235 * @param string $identifier The key identifier for the localized string
5236 * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
5237 * @param mixed $a An object, string or number that can be used
5238 * within translation strings
5240 function print_string($identifier, $module='', $a=NULL) {
5241 echo get_string($identifier, $module, $a);
5245 * fix up the optional data in get_string()/print_string() etc
5246 * ensure possible sprintf() format characters are escaped correctly
5247 * needs to handle arbitrary strings and objects
5248 * @param mixed $a An object, string or number that can be used
5249 * @return mixed the supplied parameter 'cleaned'
5251 function clean_getstring_data( $a ) {
5252 if (is_string($a)) {
5253 return str_replace( '%','%%',$a );
5255 elseif (is_object($a)) {
5256 $a_vars = get_object_vars( $a );
5257 $new_a_vars = array();
5258 foreach ($a_vars as $fname => $a_var) {
5259 $new_a_vars[$fname] = clean_getstring_data( $a_var );
5261 return (object)$new_a_vars;
5263 else {
5264 return $a;
5269 * @return array places to look for lang strings based on the prefix to the
5270 * module name. For example qtype_ in question/type. Used by get_string and
5271 * help.php.
5273 function places_to_search_for_lang_strings() {
5274 global $CFG;
5276 return array(
5277 '__exceptions' => array('moodle', 'langconfig'),
5278 'assignment_' => array('mod/assignment/type'),
5279 'auth_' => array('auth'),
5280 'block_' => array('blocks'),
5281 'datafield_' => array('mod/data/field'),
5282 'datapreset_' => array('mod/data/preset'),
5283 'enrol_' => array('enrol'),
5284 'filter_' => array('filter'),
5285 'format_' => array('course/format'),
5286 'qtype_' => array('question/type'),
5287 'report_' => array($CFG->admin.'/report', 'course/report', 'mod/quiz/report'),
5288 'resource_' => array('mod/resource/type'),
5289 'gradereport_' => array('grade/report'),
5290 'gradeimport_' => array('grade/import'),
5291 'gradeexport_' => array('grade/export'),
5292 'qformat_' => array('question/format'),
5293 'profilefield_' => array('user/profile/field'),
5294 '' => array('mod')
5299 * Returns a localized string.
5301 * Returns the translated string specified by $identifier as
5302 * for $module. Uses the same format files as STphp.
5303 * $a is an object, string or number that can be used
5304 * within translation strings
5306 * eg "hello \$a->firstname \$a->lastname"
5307 * or "hello \$a"
5309 * If you would like to directly echo the localized string use
5310 * the function {@link print_string()}
5312 * Example usage of this function involves finding the string you would
5313 * like a local equivalent of and using its identifier and module information
5314 * to retrive it.<br/>
5315 * If you open moodle/lang/en/moodle.php and look near line 1031
5316 * you will find a string to prompt a user for their word for student
5317 * <code>
5318 * $string['wordforstudent'] = 'Your word for Student';
5319 * </code>
5320 * So if you want to display the string 'Your word for student'
5321 * in any language that supports it on your site
5322 * you just need to use the identifier 'wordforstudent'
5323 * <code>
5324 * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
5326 * </code>
5327 * If the string you want is in another file you'd take a slightly
5328 * different approach. Looking in moodle/lang/en/calendar.php you find
5329 * around line 75:
5330 * <code>
5331 * $string['typecourse'] = 'Course event';
5332 * </code>
5333 * If you want to display the string "Course event" in any language
5334 * supported you would use the identifier 'typecourse' and the module 'calendar'
5335 * (because it is in the file calendar.php):
5336 * <code>
5337 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
5338 * </code>
5340 * As a last resort, should the identifier fail to map to a string
5341 * the returned string will be [[ $identifier ]]
5343 * @uses $CFG
5344 * @param string $identifier The key identifier for the localized string
5345 * @param string $module The module where the key identifier is stored, usually expressed as the filename in the language pack without the .php on the end but can also be written as mod/forum or grade/export/xls. If none is specified then moodle.php is used.
5346 * @param mixed $a An object, string or number that can be used
5347 * within translation strings
5348 * @param array $extralocations An array of strings with other locations to look for string files
5349 * @return string The localized string.
5351 function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
5353 global $CFG;
5355 /// originally these special strings were stored in moodle.php now we are only in langconfig.php
5356 $langconfigstrs = array('alphabet', 'backupnameformat', 'decsep', 'firstdayofweek', 'listsep', 'locale',
5357 'localewin', 'localewincharset', 'oldcharset',
5358 'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
5359 'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
5360 'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
5361 'thischarset', 'thisdirection', 'thislanguage', 'strftimedatetimeshort', 'thousandssep');
5363 $filetocheck = 'langconfig.php';
5364 $defaultlang = 'en_utf8';
5365 if (in_array($identifier, $langconfigstrs)) {
5366 $module = 'langconfig'; //This strings are under langconfig.php for 1.6 lang packs
5369 $lang = current_language();
5371 if ($module == '') {
5372 $module = 'moodle';
5375 /// If the "module" is actually a pathname, then automatically derive the proper module name
5376 if (strpos($module, '/') !== false) {
5377 $modulepath = split('/', $module);
5379 switch ($modulepath[0]) {
5381 case 'mod':
5382 $module = $modulepath[1];
5383 break;
5385 case 'blocks':
5386 case 'block':
5387 $module = 'block_'.$modulepath[1];
5388 break;
5390 case 'enrol':
5391 $module = 'enrol_'.$modulepath[1];
5392 break;
5394 case 'format':
5395 $module = 'format_'.$modulepath[1];
5396 break;
5398 case 'grade':
5399 $module = 'grade'.$modulepath[1].'_'.$modulepath[2];
5400 break;
5404 /// if $a happens to have % in it, double it so sprintf() doesn't break
5405 if ($a) {
5406 $a = clean_getstring_data( $a );
5409 /// Define the two or three major locations of language strings for this module
5410 $locations = array();
5412 if (!empty($extralocations)) { // Calling code has a good idea where to look
5413 if (is_array($extralocations)) {
5414 $locations += $extralocations;
5415 } else if (is_string($extralocations)) {
5416 $locations[] = $extralocations;
5417 } else {
5418 debugging('Bad lang path provided');
5422 if (isset($CFG->running_installer)) {
5423 $module = 'installer';
5424 $filetocheck = 'installer.php';
5425 $locations[] = $CFG->dirroot.'/install/lang/';
5426 $locations[] = $CFG->dataroot.'/lang/';
5427 $locations[] = $CFG->dirroot.'/lang/';
5428 $defaultlang = 'en_utf8';
5429 } else {
5430 $locations[] = $CFG->dataroot.'/lang/';
5431 $locations[] = $CFG->dirroot.'/lang/';
5434 /// Add extra places to look for strings for particular plugin types.
5435 $rules = places_to_search_for_lang_strings();
5436 $exceptions = $rules['__exceptions'];
5437 unset($rules['__exceptions']);
5439 if (!in_array($module, $exceptions)) {
5440 $dividerpos = strpos($module, '_');
5441 if ($dividerpos === false) {
5442 $type = '';
5443 $plugin = $module;
5444 } else {
5445 $type = substr($module, 0, $dividerpos + 1);
5446 $plugin = substr($module, $dividerpos + 1);
5448 if ($module == 'local') {
5449 $locations[] = $CFG->dirroot . '/local/lang/';
5450 } if (!empty($rules[$type])) {
5451 foreach ($rules[$type] as $location) {
5452 $locations[] = $CFG->dirroot . "/$location/$plugin/lang/";
5457 /// First check all the normal locations for the string in the current language
5458 $resultstring = '';
5459 foreach ($locations as $location) {
5460 $locallangfile = $location.$lang.'_local'.'/'.$module.'.php'; //first, see if there's a local file
5461 if (file_exists($locallangfile)) {
5462 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5463 if (eval($result) === FALSE) {
5464 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5466 return $resultstring;
5469 //if local directory not found, or particular string does not exist in local direcotry
5470 $langfile = $location.$lang.'/'.$module.'.php';
5471 if (file_exists($langfile)) {
5472 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5473 if (eval($result) === FALSE) {
5474 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5476 return $resultstring;
5481 /// If the preferred language was English (utf8) we can abort now
5482 /// saving some checks beacuse it's the only "root" lang
5483 if ($lang == 'en_utf8') {
5484 return '[['. $identifier .']]';
5487 /// Is a parent language defined? If so, try to find this string in a parent language file
5489 foreach ($locations as $location) {
5490 $langfile = $location.$lang.'/'.$filetocheck;
5491 if (file_exists($langfile)) {
5492 if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
5493 if (eval($result) === FALSE) {
5494 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5496 if (!empty($parentlang)) { // found it!
5498 //first, see if there's a local file for parent
5499 $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
5500 if (file_exists($locallangfile)) {
5501 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5502 if (eval($result) === FALSE) {
5503 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5505 return $resultstring;
5509 //if local directory not found, or particular string does not exist in local direcotry
5510 $langfile = $location.$parentlang.'/'.$module.'.php';
5511 if (file_exists($langfile)) {
5512 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5513 eval($result);
5514 return $resultstring;
5522 /// Our only remaining option is to try English
5524 foreach ($locations as $location) {
5525 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5526 if (file_exists($locallangfile)) {
5527 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5528 eval($result);
5529 return $resultstring;
5533 //if local_en not found, or string not found in local_en
5534 $langfile = $location.$defaultlang.'/'.$module.'.php';
5536 if (file_exists($langfile)) {
5537 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5538 eval($result);
5539 return $resultstring;
5544 /// And, because under 1.6 en is defined as en_utf8 child, me must try
5545 /// if it hasn't been queried before.
5546 if ($defaultlang == 'en') {
5547 $defaultlang = 'en_utf8';
5548 foreach ($locations as $location) {
5549 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5550 if (file_exists($locallangfile)) {
5551 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5552 eval($result);
5553 return $resultstring;
5557 //if local_en not found, or string not found in local_en
5558 $langfile = $location.$defaultlang.'/'.$module.'.php';
5560 if (file_exists($langfile)) {
5561 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5562 eval($result);
5563 return $resultstring;
5569 return '[['.$identifier.']]'; // Last resort
5573 * This function is only used from {@link get_string()}.
5575 * @internal Only used from get_string, not meant to be public API
5576 * @param string $identifier ?
5577 * @param string $langfile ?
5578 * @param string $destination ?
5579 * @return string|false ?
5580 * @staticvar array $strings Localized strings
5581 * @access private
5582 * @todo Finish documenting this function.
5584 function get_string_from_file($identifier, $langfile, $destination) {
5586 static $strings; // Keep the strings cached in memory.
5588 if (empty($strings[$langfile])) {
5589 $string = array();
5590 include ($langfile);
5591 $strings[$langfile] = $string;
5592 } else {
5593 $string = &$strings[$langfile];
5596 if (!isset ($string[$identifier])) {
5597 return false;
5600 return $destination .'= sprintf("'. $string[$identifier] .'");';
5604 * Converts an array of strings to their localized value.
5606 * @param array $array An array of strings
5607 * @param string $module The language module that these strings can be found in.
5608 * @return string
5610 function get_strings($array, $module='') {
5612 $string = NULL;
5613 foreach ($array as $item) {
5614 $string->$item = get_string($item, $module);
5616 return $string;
5620 * Returns a list of language codes and their full names
5621 * hides the _local files from everyone.
5622 * @param bool refreshcache force refreshing of lang cache
5623 * @param bool returnall ignore langlist, return all languages available
5624 * @return array An associative array with contents in the form of LanguageCode => LanguageName
5626 function get_list_of_languages($refreshcache=false, $returnall=false) {
5628 global $CFG;
5630 $languages = array();
5632 $filetocheck = 'langconfig.php';
5634 if (!$refreshcache && !$returnall && !empty($CFG->langcache) && file_exists($CFG->dataroot .'/cache/languages')) {
5635 /// read available langs from cache
5637 $lines = file($CFG->dataroot .'/cache/languages');
5638 foreach ($lines as $line) {
5639 $line = trim($line);
5640 if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
5641 $languages[$matches[1]] = $matches[2];
5644 unset($lines); unset($line); unset($matches);
5645 return $languages;
5648 if (!$returnall && !empty($CFG->langlist)) {
5649 /// return only languages allowed in langlist admin setting
5651 $langlist = explode(',', $CFG->langlist);
5652 // fix short lang names first - non existing langs are skipped anyway...
5653 foreach ($langlist as $lang) {
5654 if (strpos($lang, '_utf8') === false) {
5655 $langlist[] = $lang.'_utf8';
5658 // find existing langs from langlist
5659 foreach ($langlist as $lang) {
5660 $lang = trim($lang); //Just trim spaces to be a bit more permissive
5661 if (strstr($lang, '_local')!==false) {
5662 continue;
5664 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5665 $shortlang = substr($lang, 0, -5);
5666 } else {
5667 $shortlang = $lang;
5669 /// Search under dirroot/lang
5670 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5671 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5672 if (!empty($string['thislanguage'])) {
5673 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5675 unset($string);
5677 /// And moodledata/lang
5678 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5679 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5680 if (!empty($string['thislanguage'])) {
5681 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5683 unset($string);
5687 } else {
5688 /// return all languages available in system
5689 /// Fetch langs from moodle/lang directory
5690 $langdirs = get_list_of_plugins('lang');
5691 /// Fetch langs from moodledata/lang directory
5692 $langdirs2 = get_list_of_plugins('lang', '', $CFG->dataroot);
5693 /// Merge both lists of langs
5694 $langdirs = array_merge($langdirs, $langdirs2);
5695 /// Sort all
5696 asort($langdirs);
5697 /// Get some info from each lang (first from moodledata, then from moodle)
5698 foreach ($langdirs as $lang) {
5699 if (strstr($lang, '_local')!==false) {
5700 continue;
5702 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5703 $shortlang = substr($lang, 0, -5);
5704 } else {
5705 $shortlang = $lang;
5707 /// Search under moodledata/lang
5708 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5709 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5710 if (!empty($string['thislanguage'])) {
5711 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5713 unset($string);
5715 /// And dirroot/lang
5716 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5717 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5718 if (!empty($string['thislanguage'])) {
5719 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5721 unset($string);
5726 if ($refreshcache && !empty($CFG->langcache)) {
5727 if ($returnall) {
5728 // we have a list of all langs only, just delete old cache
5729 @unlink($CFG->dataroot.'/cache/languages');
5731 } else {
5732 // store the list of allowed languages
5733 if ($file = fopen($CFG->dataroot .'/cache/languages', 'w')) {
5734 foreach ($languages as $key => $value) {
5735 fwrite($file, "$key $value\n");
5737 fclose($file);
5742 return $languages;
5746 * Returns a list of charset codes. It's hardcoded, so they should be added manually
5747 * (cheking that such charset is supported by the texlib library!)
5749 * @return array And associative array with contents in the form of charset => charset
5751 function get_list_of_charsets() {
5753 $charsets = array(
5754 'EUC-JP' => 'EUC-JP',
5755 'ISO-2022-JP'=> 'ISO-2022-JP',
5756 'ISO-8859-1' => 'ISO-8859-1',
5757 'SHIFT-JIS' => 'SHIFT-JIS',
5758 'GB2312' => 'GB2312',
5759 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
5760 'UTF-8' => 'UTF-8');
5762 asort($charsets);
5764 return $charsets;
5768 * For internal use only.
5769 * @return array with two elements, the path to use and the name of the lang.
5771 function get_list_of_countries_language() {
5772 global $CFG;
5774 $lang = current_language();
5775 if (is_readable($CFG->dataroot.'/lang/'. $lang .'/countries.php')) {
5776 return array($CFG->dataroot, $lang);
5778 if (is_readable($CFG->dirroot .'/lang/'. $lang .'/countries.php')) {
5779 return array($CFG->dirroot , $lang);
5782 if ($lang == 'en_utf8') {
5783 return;
5786 $parentlang = get_string('parentlanguage');
5787 if (substr($parentlang, 0, 1) != '[') {
5788 if (is_readable($CFG->dataroot.'/lang/'. $parentlang .'/countries.php')) {
5789 return array($CFG->dataroot, $parentlang);
5791 if (is_readable($CFG->dirroot .'/lang/'. $parentlang .'/countries.php')) {
5792 return array($CFG->dirroot , $parentlang);
5795 if ($parentlang == 'en_utf8') {
5796 return;
5800 if (is_readable($CFG->dataroot.'/lang/en_utf8/countries.php')) {
5801 return array($CFG->dataroot, 'en_utf8');
5803 if (is_readable($CFG->dirroot .'/lang/en_utf8/countries.php')) {
5804 return array($CFG->dirroot , 'en_utf8');
5807 return array(null, null);
5811 * Returns a list of country names in the current language
5813 * @uses $CFG
5814 * @uses $USER
5815 * @return array
5817 function get_list_of_countries() {
5818 global $CFG;
5820 list($path, $lang) = get_list_of_countries_language();
5822 if (empty($path)) {
5823 print_error('countriesphpempty', '', '', $lang);
5826 // Load all the strings into $string.
5827 include($path . '/lang/' . $lang . '/countries.php');
5829 // See if there are local overrides to countries.php.
5830 // If so, override those elements of $string.
5831 if (is_readable($CFG->dirroot .'/lang/' . $lang . '_local/countries.php')) {
5832 include($CFG->dirroot .'/lang/' . $lang . '_local/countries.php');
5834 if (is_readable($CFG->dataroot.'/lang/' . $lang . '_local/countries.php')) {
5835 include($CFG->dataroot.'/lang/' . $lang . '_local/countries.php');
5838 if (empty($string)) {
5839 print_error('countriesphpempty', '', '', $lang);
5842 uasort($string, 'strcoll');
5843 return $string;
5847 * Returns a list of valid and compatible themes
5849 * @uses $CFG
5850 * @return array
5852 function get_list_of_themes() {
5854 global $CFG;
5856 $themes = array();
5858 if (!empty($CFG->themelist)) { // use admin's list of themes
5859 $themelist = explode(',', $CFG->themelist);
5860 } else {
5861 $themelist = get_list_of_plugins("theme");
5864 foreach ($themelist as $key => $theme) {
5865 if (!file_exists("$CFG->themedir/$theme/config.php")) { // bad folder
5866 continue;
5868 $THEME = new object(); // Note this is not the global one!! :-)
5869 include("$CFG->themedir/$theme/config.php");
5870 if (!isset($THEME->sheets)) { // Not a valid 1.5 theme
5871 continue;
5873 $themes[$theme] = $theme;
5875 asort($themes);
5877 return $themes;
5882 * Returns a list of picture names in the current or specified language
5884 * @uses $CFG
5885 * @return array
5887 function get_list_of_pixnames($lang = '') {
5888 global $CFG;
5890 if (empty($lang)) {
5891 $lang = current_language();
5894 $string = array();
5896 $path = $CFG->dirroot .'/lang/en_utf8/pix.php'; // always exists
5898 if (file_exists($CFG->dataroot .'/lang/'. $lang .'_local/pix.php')) {
5899 $path = $CFG->dataroot .'/lang/'. $lang .'_local/pix.php';
5901 } else if (file_exists($CFG->dirroot .'/lang/'. $lang .'/pix.php')) {
5902 $path = $CFG->dirroot .'/lang/'. $lang .'/pix.php';
5904 } else if (file_exists($CFG->dataroot .'/lang/'. $lang .'/pix.php')) {
5905 $path = $CFG->dataroot .'/lang/'. $lang .'/pix.php';
5907 } else if ($parentlang = get_string('parentlanguage') and $parentlang != '[[parentlanguage]]') {
5908 return get_list_of_pixnames($parentlang); //return pixnames from parent language instead
5911 include($path);
5913 return $string;
5917 * Returns a list of timezones in the current language
5919 * @uses $CFG
5920 * @return array
5922 function get_list_of_timezones() {
5923 global $CFG;
5925 static $timezones;
5927 if (!empty($timezones)) { // This function has been called recently
5928 return $timezones;
5931 $timezones = array();
5933 if ($rawtimezones = get_records_sql('SELECT MAX(id), name FROM '.$CFG->prefix.'timezone GROUP BY name')) {
5934 foreach($rawtimezones as $timezone) {
5935 if (!empty($timezone->name)) {
5936 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
5937 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
5938 $timezones[$timezone->name] = $timezone->name;
5944 asort($timezones);
5946 for ($i = -13; $i <= 13; $i += .5) {
5947 $tzstring = 'UTC';
5948 if ($i < 0) {
5949 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
5950 } else if ($i > 0) {
5951 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
5952 } else {
5953 $timezones[sprintf("%.1f", $i)] = $tzstring;
5957 return $timezones;
5961 * Returns a list of currencies in the current language
5963 * @uses $CFG
5964 * @uses $USER
5965 * @return array
5967 function get_list_of_currencies() {
5968 global $CFG, $USER;
5970 $lang = current_language();
5972 if (!file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5973 if ($parentlang = get_string('parentlanguage')) {
5974 if (file_exists($CFG->dataroot .'/lang/'. $parentlang .'/currencies.php')) {
5975 $lang = $parentlang;
5976 } else {
5977 $lang = 'en_utf8'; // currencies.php must exist in this pack
5979 } else {
5980 $lang = 'en_utf8'; // currencies.php must exist in this pack
5984 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5985 include_once($CFG->dataroot .'/lang/'. $lang .'/currencies.php');
5986 } else { //if en_utf8 is not installed in dataroot
5987 include_once($CFG->dirroot .'/lang/'. $lang .'/currencies.php');
5990 if (!empty($string)) {
5991 asort($string);
5994 return $string;
5998 /// ENCRYPTION ////////////////////////////////////////////////
6001 * rc4encrypt
6003 * @param string $data ?
6004 * @return string
6005 * @todo Finish documenting this function
6007 function rc4encrypt($data) {
6008 $password = 'nfgjeingjk';
6009 return endecrypt($password, $data, '');
6013 * rc4decrypt
6015 * @param string $data ?
6016 * @return string
6017 * @todo Finish documenting this function
6019 function rc4decrypt($data) {
6020 $password = 'nfgjeingjk';
6021 return endecrypt($password, $data, 'de');
6025 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6027 * @param string $pwd ?
6028 * @param string $data ?
6029 * @param string $case ?
6030 * @return string
6031 * @todo Finish documenting this function
6033 function endecrypt ($pwd, $data, $case) {
6035 if ($case == 'de') {
6036 $data = urldecode($data);
6039 $key[] = '';
6040 $box[] = '';
6041 $temp_swap = '';
6042 $pwd_length = 0;
6044 $pwd_length = strlen($pwd);
6046 for ($i = 0; $i <= 255; $i++) {
6047 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
6048 $box[$i] = $i;
6051 $x = 0;
6053 for ($i = 0; $i <= 255; $i++) {
6054 $x = ($x + $box[$i] + $key[$i]) % 256;
6055 $temp_swap = $box[$i];
6056 $box[$i] = $box[$x];
6057 $box[$x] = $temp_swap;
6060 $temp = '';
6061 $k = '';
6063 $cipherby = '';
6064 $cipher = '';
6066 $a = 0;
6067 $j = 0;
6069 for ($i = 0; $i < strlen($data); $i++) {
6070 $a = ($a + 1) % 256;
6071 $j = ($j + $box[$a]) % 256;
6072 $temp = $box[$a];
6073 $box[$a] = $box[$j];
6074 $box[$j] = $temp;
6075 $k = $box[(($box[$a] + $box[$j]) % 256)];
6076 $cipherby = ord(substr($data, $i, 1)) ^ $k;
6077 $cipher .= chr($cipherby);
6080 if ($case == 'de') {
6081 $cipher = urldecode(urlencode($cipher));
6082 } else {
6083 $cipher = urlencode($cipher);
6086 return $cipher;
6090 /// CALENDAR MANAGEMENT ////////////////////////////////////////////////////////////////
6094 * Call this function to add an event to the calendar table
6095 * and to call any calendar plugins
6097 * @uses $CFG
6098 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field. The object event should include the following:
6099 * <ul>
6100 * <li><b>$event->name</b> - Name for the event
6101 * <li><b>$event->description</b> - Description of the event (defaults to '')
6102 * <li><b>$event->format</b> - Format for the description (using formatting types defined at the top of weblib.php)
6103 * <li><b>$event->courseid</b> - The id of the course this event belongs to (0 = all courses)
6104 * <li><b>$event->groupid</b> - The id of the group this event belongs to (0 = no group)
6105 * <li><b>$event->userid</b> - The id of the user this event belongs to (0 = no user)
6106 * <li><b>$event->modulename</b> - Name of the module that creates this event
6107 * <li><b>$event->instance</b> - Instance of the module that owns this event
6108 * <li><b>$event->eventtype</b> - The type info together with the module info could
6109 * be used by calendar plugins to decide how to display event
6110 * <li><b>$event->timestart</b>- Timestamp for start of event
6111 * <li><b>$event->timeduration</b> - Duration (defaults to zero)
6112 * <li><b>$event->visible</b> - 0 if the event should be hidden (e.g. because the activity that created it is hidden)
6113 * </ul>
6114 * @return int The id number of the resulting record
6116 function add_event($event) {
6118 global $CFG;
6120 $event->timemodified = time();
6122 if (!$event->id = insert_record('event', $event)) {
6123 return false;
6126 if (!empty($CFG->calendar)) { // call the add_event function of the selected calendar
6127 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6128 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6129 $calendar_add_event = $CFG->calendar.'_add_event';
6130 if (function_exists($calendar_add_event)) {
6131 $calendar_add_event($event);
6136 return $event->id;
6140 * Call this function to update an event in the calendar table
6141 * the event will be identified by the id field of the $event object.
6143 * @uses $CFG
6144 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6145 * @return bool
6147 function update_event($event) {
6149 global $CFG;
6151 $event->timemodified = time();
6153 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6154 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6155 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6156 $calendar_update_event = $CFG->calendar.'_update_event';
6157 if (function_exists($calendar_update_event)) {
6158 $calendar_update_event($event);
6162 return update_record('event', $event);
6166 * Call this function to delete the event with id $id from calendar table.
6168 * @uses $CFG
6169 * @param int $id The id of an event from the 'calendar' table.
6170 * @return array An associative array with the results from the SQL call.
6171 * @todo Verify return type
6173 function delete_event($id) {
6175 global $CFG;
6177 if (!empty($CFG->calendar)) { // call the delete_event function of the selected calendar
6178 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6179 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6180 $calendar_delete_event = $CFG->calendar.'_delete_event';
6181 if (function_exists($calendar_delete_event)) {
6182 $calendar_delete_event($id);
6186 return delete_records('event', 'id', $id);
6190 * Call this function to hide an event in the calendar table
6191 * the event will be identified by the id field of the $event object.
6193 * @uses $CFG
6194 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6195 * @return array An associative array with the results from the SQL call.
6196 * @todo Verify return type
6198 function hide_event($event) {
6199 global $CFG;
6201 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6202 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6203 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6204 $calendar_hide_event = $CFG->calendar.'_hide_event';
6205 if (function_exists($calendar_hide_event)) {
6206 $calendar_hide_event($event);
6210 return set_field('event', 'visible', 0, 'id', $event->id);
6214 * Call this function to unhide an event in the calendar table
6215 * the event will be identified by the id field of the $event object.
6217 * @uses $CFG
6218 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6219 * @return array An associative array with the results from the SQL call.
6220 * @todo Verify return type
6222 function show_event($event) {
6223 global $CFG;
6225 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6226 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6227 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6228 $calendar_show_event = $CFG->calendar.'_show_event';
6229 if (function_exists($calendar_show_event)) {
6230 $calendar_show_event($event);
6234 return set_field('event', 'visible', 1, 'id', $event->id);
6238 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6241 * Lists plugin directories within some directory
6243 * @uses $CFG
6244 * @param string $plugin dir under we'll look for plugins (defaults to 'mod')
6245 * @param string $exclude dir name to exclude from the list (defaults to none)
6246 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
6247 * @return array of plugins found under the requested parameters
6249 function get_list_of_plugins($plugin='mod', $exclude='', $basedir='') {
6251 global $CFG;
6253 $plugins = array();
6255 if (empty($basedir)) {
6257 # This switch allows us to use the appropiate theme directory - and potentialy alternatives for other plugins
6258 switch ($plugin) {
6259 case "theme":
6260 $basedir = $CFG->themedir;
6261 break;
6263 default:
6264 $basedir = $CFG->dirroot .'/'. $plugin;
6267 } else {
6268 $basedir = $basedir .'/'. $plugin;
6271 if (file_exists($basedir) && filetype($basedir) == 'dir') {
6272 $dirhandle = opendir($basedir);
6273 while (false !== ($dir = readdir($dirhandle))) {
6274 $firstchar = substr($dir, 0, 1);
6275 if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == 'simpletest' or $dir == $exclude) {
6276 continue;
6278 if (filetype($basedir .'/'. $dir) != 'dir') {
6279 continue;
6281 $plugins[] = $dir;
6283 closedir($dirhandle);
6285 if ($plugins) {
6286 asort($plugins);
6288 return $plugins;
6292 * Returns true if the current version of PHP is greater that the specified one.
6294 * @param string $version The version of php being tested.
6295 * @return bool
6297 function check_php_version($version='4.1.0') {
6298 return (version_compare(phpversion(), $version) >= 0);
6302 * Checks to see if is the browser operating system matches the specified
6303 * brand.
6305 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
6307 * @uses $_SERVER
6308 * @param string $brand The operating system identifier being tested
6309 * @return bool true if the given brand below to the detected operating system
6311 function check_browser_operating_system($brand) {
6312 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6313 return false;
6316 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
6317 return true;
6320 return false;
6324 * Checks to see if is a browser matches the specified
6325 * brand and is equal or better version.
6327 * @uses $_SERVER
6328 * @param string $brand The browser identifier being tested
6329 * @param int $version The version of the browser
6330 * @return bool true if the given version is below that of the detected browser
6332 function check_browser_version($brand='MSIE', $version=5.5) {
6333 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6334 return false;
6337 $agent = $_SERVER['HTTP_USER_AGENT'];
6339 switch ($brand) {
6341 case 'Camino': /// Mozilla Firefox browsers
6343 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
6344 if (version_compare($match[1], $version) >= 0) {
6345 return true;
6348 break;
6351 case 'Firefox': /// Mozilla Firefox browsers
6353 if (preg_match("/Firefox\/([0-9\.]+)/i", $agent, $match)) {
6354 if (version_compare($match[1], $version) >= 0) {
6355 return true;
6358 break;
6361 case 'Gecko': /// Gecko based browsers
6363 if (substr_count($agent, 'Camino')) {
6364 // MacOS X Camino support
6365 $version = 20041110;
6368 // the proper string - Gecko/CCYYMMDD Vendor/Version
6369 // Faster version and work-a-round No IDN problem.
6370 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
6371 if ($match[1] > $version) {
6372 return true;
6375 break;
6378 case 'MSIE': /// Internet Explorer
6380 if (strpos($agent, 'Opera')) { // Reject Opera
6381 return false;
6383 $string = explode(';', $agent);
6384 if (!isset($string[1])) {
6385 return false;
6387 $string = explode(' ', trim($string[1]));
6388 if (!isset($string[0]) and !isset($string[1])) {
6389 return false;
6391 if ($string[0] == $brand and (float)$string[1] >= $version ) {
6392 return true;
6394 break;
6396 case 'Opera': /// Opera
6398 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
6399 if (version_compare($match[1], $version) >= 0) {
6400 return true;
6403 break;
6405 case 'Safari': /// Safari
6406 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SimbianOS
6407 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
6408 return false;
6409 } elseif (strpos($agent, 'Shiira')) { // Reject Shiira
6410 return false;
6411 } elseif (strpos($agent, 'SimbianOS')) { // Reject SimbianOS
6412 return false;
6415 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
6416 if (version_compare($match[1], $version) >= 0) {
6417 return true;
6421 break;
6425 return false;
6429 * Returns one or several CSS class names that match the user's browser. These can be put
6430 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
6432 function get_browser_version_classes() {
6433 $classes = '';
6434 if (check_browser_version("MSIE", "0")) {
6435 $classes .= 'ie ';
6436 if (check_browser_version("MSIE", 8)) {
6437 $classes .= 'ie8 ';
6438 } elseif (check_browser_version("MSIE", 7)) {
6439 $classes .= 'ie7 ';
6440 } elseif (check_browser_version("MSIE", 6)) {
6441 $classes .= 'ie6 ';
6443 } elseif (check_browser_version("Firefox", 0) || check_browser_version("Gecko", 0) || check_browser_version("Camino", 0)) {
6444 $classes .= 'gecko ';
6446 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
6447 $classes .= "gecko{$matches[1]}{$matches[2]} ";
6450 } elseif (check_browser_version("Safari", 0)) {
6451 $classes .= 'safari ';
6453 } elseif (check_browser_version("Opera", 0)) {
6454 $classes .= 'opera ';
6458 return $classes;
6462 * This function makes the return value of ini_get consistent if you are
6463 * setting server directives through the .htaccess file in apache.
6464 * Current behavior for value set from php.ini On = 1, Off = [blank]
6465 * Current behavior for value set from .htaccess On = On, Off = Off
6466 * Contributed by jdell @ unr.edu
6468 * @param string $ini_get_arg ?
6469 * @return bool
6470 * @todo Finish documenting this function
6472 function ini_get_bool($ini_get_arg) {
6473 $temp = ini_get($ini_get_arg);
6475 if ($temp == '1' or strtolower($temp) == 'on') {
6476 return true;
6478 return false;
6482 * Compatibility stub to provide backward compatibility
6484 * Determines if the HTML editor is enabled.
6485 * @deprecated Use {@link can_use_html_editor()} instead.
6487 function can_use_richtext_editor() {
6488 return can_use_html_editor();
6492 * Determines if the HTML editor is enabled.
6494 * This depends on site and user
6495 * settings, as well as the current browser being used.
6497 * @return string|false Returns false if editor is not being used, otherwise
6498 * returns 'MSIE' or 'Gecko'.
6500 function can_use_html_editor() {
6501 global $USER, $CFG;
6503 if (!empty($USER->htmleditor) and !empty($CFG->htmleditor)) {
6504 if (check_browser_version('MSIE', 5.5)) {
6505 return 'MSIE';
6506 } else if (check_browser_version('Gecko', 20030516)) {
6507 return 'Gecko';
6510 return false;
6514 * Hack to find out the GD version by parsing phpinfo output
6516 * @return int GD version (1, 2, or 0)
6518 function check_gd_version() {
6519 $gdversion = 0;
6521 if (function_exists('gd_info')){
6522 $gd_info = gd_info();
6523 if (substr_count($gd_info['GD Version'], '2.')) {
6524 $gdversion = 2;
6525 } else if (substr_count($gd_info['GD Version'], '1.')) {
6526 $gdversion = 1;
6529 } else {
6530 ob_start();
6531 phpinfo(INFO_MODULES);
6532 $phpinfo = ob_get_contents();
6533 ob_end_clean();
6535 $phpinfo = explode("\n", $phpinfo);
6538 foreach ($phpinfo as $text) {
6539 $parts = explode('</td>', $text);
6540 foreach ($parts as $key => $val) {
6541 $parts[$key] = trim(strip_tags($val));
6543 if ($parts[0] == 'GD Version') {
6544 if (substr_count($parts[1], '2.0')) {
6545 $parts[1] = '2.0';
6547 $gdversion = intval($parts[1]);
6552 return $gdversion; // 1, 2 or 0
6556 * Determine if moodle installation requires update
6558 * Checks version numbers of main code and all modules to see
6559 * if there are any mismatches
6561 * @uses $CFG
6562 * @return bool
6564 function moodle_needs_upgrading() {
6565 global $CFG;
6567 $version = null;
6568 include_once($CFG->dirroot .'/version.php'); # defines $version and upgrades
6569 if ($CFG->version) {
6570 if ($version > $CFG->version) {
6571 return true;
6573 if ($mods = get_list_of_plugins('mod')) {
6574 foreach ($mods as $mod) {
6575 $fullmod = $CFG->dirroot .'/mod/'. $mod;
6576 $module = new object();
6577 if (!is_readable($fullmod .'/version.php')) {
6578 notify('Module "'. $mod .'" is not readable - check permissions');
6579 continue;
6581 include_once($fullmod .'/version.php'); # defines $module with version etc
6582 if ($currmodule = get_record('modules', 'name', $mod)) {
6583 if ($module->version > $currmodule->version) {
6584 return true;
6589 } else {
6590 return true;
6592 return false;
6596 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
6599 * Notify admin users or admin user of any failed logins (since last notification).
6601 * Note that this function must be only executed from the cron script
6602 * It uses the cache_flags system to store temporary records, deleting them
6603 * by name before finishing
6605 * @uses $CFG
6606 * @uses $db
6607 * @uses HOURSECS
6609 function notify_login_failures() {
6610 global $CFG, $db;
6612 switch ($CFG->notifyloginfailures) {
6613 case 'mainadmin' :
6614 $recip = array(get_admin());
6615 break;
6616 case 'alladmins':
6617 $recip = get_admins();
6618 break;
6621 if (empty($CFG->lastnotifyfailure)) {
6622 $CFG->lastnotifyfailure=0;
6625 // we need to deal with the threshold stuff first.
6626 if (empty($CFG->notifyloginthreshold)) {
6627 $CFG->notifyloginthreshold = 10; // default to something sensible.
6630 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
6631 /// and insert them into the cache_flags temp table
6632 $iprs = get_recordset_sql("SELECT ip, count(*)
6633 FROM {$CFG->prefix}log
6634 WHERE module = 'login'
6635 AND action = 'error'
6636 AND time > $CFG->lastnotifyfailure
6637 GROUP BY ip
6638 HAVING count(*) >= $CFG->notifyloginthreshold");
6639 while ($iprec = rs_fetch_next_record($iprs)) {
6640 if (!empty($iprec->ip)) {
6641 set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
6644 rs_close($iprs);
6646 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
6647 /// and insert them into the cache_flags temp table
6648 $infors = get_recordset_sql("SELECT info, count(*)
6649 FROM {$CFG->prefix}log
6650 WHERE module = 'login'
6651 AND action = 'error'
6652 AND time > $CFG->lastnotifyfailure
6653 GROUP BY info
6654 HAVING count(*) >= $CFG->notifyloginthreshold");
6655 while ($inforec = rs_fetch_next_record($infors)) {
6656 if (!empty($inforec->info)) {
6657 set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
6660 rs_close($infors);
6662 /// Now, select all the login error logged records belonging to the ips and infos
6663 /// since lastnotifyfailure, that we have stored in the cache_flags table
6664 $logsrs = get_recordset_sql("SELECT l.*, u.firstname, u.lastname
6665 FROM {$CFG->prefix}log l
6666 JOIN {$CFG->prefix}cache_flags cf ON (l.ip = cf.name)
6667 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6668 WHERE l.module = 'login'
6669 AND l.action = 'error'
6670 AND l.time > $CFG->lastnotifyfailure
6671 AND cf.flagtype = 'login_failure_by_ip'
6672 UNION ALL
6673 SELECT l.*, u.firstname, u.lastname
6674 FROM {$CFG->prefix}log l
6675 JOIN {$CFG->prefix}cache_flags cf ON (l.info = cf.name)
6676 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6677 WHERE l.module = 'login'
6678 AND l.action = 'error'
6679 AND l.time > $CFG->lastnotifyfailure
6680 AND cf.flagtype = 'login_failure_by_info'
6681 ORDER BY time DESC");
6683 /// Init some variables
6684 $count = 0;
6685 $messages = '';
6686 /// Iterate over the logs recordset
6687 while ($log = rs_fetch_next_record($logsrs)) {
6688 $log->time = userdate($log->time);
6689 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
6690 $count++;
6692 rs_close($logsrs);
6694 /// If we haven't run in the last hour and
6695 /// we have something useful to report and we
6696 /// are actually supposed to be reporting to somebody
6697 if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
6698 $site = get_site();
6699 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
6700 /// Calculate the complete body of notification (start + messages + end)
6701 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
6702 (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
6703 $messages .
6704 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
6706 /// For each destination, send mail
6707 foreach ($recip as $admin) {
6708 mtrace('Emailing '. $admin->username .' about '. $count .' failed login attempts');
6709 email_to_user($admin,get_admin(), $subject, $body);
6712 /// Update lastnotifyfailure with current time
6713 set_config('lastnotifyfailure', time());
6716 /// Finally, delete all the temp records we have created in cache_flags
6717 delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
6721 * moodle_setlocale
6723 * @uses $CFG
6724 * @param string $locale ?
6725 * @todo Finish documenting this function
6727 function moodle_setlocale($locale='') {
6729 global $CFG;
6731 static $currentlocale = ''; // last locale caching
6733 $oldlocale = $currentlocale;
6735 /// Fetch the correct locale based on ostype
6736 if($CFG->ostype == 'WINDOWS') {
6737 $stringtofetch = 'localewin';
6738 } else {
6739 $stringtofetch = 'locale';
6742 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
6743 if (!empty($locale)) {
6744 $currentlocale = $locale;
6745 } else if (!empty($CFG->locale)) { // override locale for all language packs
6746 $currentlocale = $CFG->locale;
6747 } else {
6748 $currentlocale = get_string($stringtofetch);
6751 /// do nothing if locale already set up
6752 if ($oldlocale == $currentlocale) {
6753 return;
6756 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
6757 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
6758 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
6760 /// Get current values
6761 $monetary= setlocale (LC_MONETARY, 0);
6762 $numeric = setlocale (LC_NUMERIC, 0);
6763 $ctype = setlocale (LC_CTYPE, 0);
6764 if ($CFG->ostype != 'WINDOWS') {
6765 $messages= setlocale (LC_MESSAGES, 0);
6767 /// Set locale to all
6768 setlocale (LC_ALL, $currentlocale);
6769 /// Set old values
6770 setlocale (LC_MONETARY, $monetary);
6771 setlocale (LC_NUMERIC, $numeric);
6772 if ($CFG->ostype != 'WINDOWS') {
6773 setlocale (LC_MESSAGES, $messages);
6775 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
6776 setlocale (LC_CTYPE, $ctype);
6781 * Converts string to lowercase using most compatible function available.
6783 * @param string $string The string to convert to all lowercase characters.
6784 * @param string $encoding The encoding on the string.
6785 * @return string
6786 * @todo Add examples of calling this function with/without encoding types
6787 * @deprecated Use textlib->strtolower($text) instead.
6789 function moodle_strtolower ($string, $encoding='') {
6791 //If not specified use utf8
6792 if (empty($encoding)) {
6793 $encoding = 'UTF-8';
6795 //Use text services
6796 $textlib = textlib_get_instance();
6798 return $textlib->strtolower($string, $encoding);
6802 * Count words in a string.
6804 * Words are defined as things between whitespace.
6806 * @param string $string The text to be searched for words.
6807 * @return int The count of words in the specified string
6809 function count_words($string) {
6810 $string = strip_tags($string);
6811 return count(preg_split("/\w\b/", $string)) - 1;
6814 /** Count letters in a string.
6816 * Letters are defined as chars not in tags and different from whitespace.
6818 * @param string $string The text to be searched for letters.
6819 * @return int The count of letters in the specified text.
6821 function count_letters($string) {
6822 /// Loading the textlib singleton instance. We are going to need it.
6823 $textlib = textlib_get_instance();
6825 $string = strip_tags($string); // Tags are out now
6826 $string = ereg_replace('[[:space:]]*','',$string); //Whitespace are out now
6828 return $textlib->strlen($string);
6832 * Generate and return a random string of the specified length.
6834 * @param int $length The length of the string to be created.
6835 * @return string
6837 function random_string ($length=15) {
6838 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
6839 $pool .= 'abcdefghijklmnopqrstuvwxyz';
6840 $pool .= '0123456789';
6841 $poollen = strlen($pool);
6842 mt_srand ((double) microtime() * 1000000);
6843 $string = '';
6844 for ($i = 0; $i < $length; $i++) {
6845 $string .= substr($pool, (mt_rand()%($poollen)), 1);
6847 return $string;
6851 * Generate a complex random string (usefull for md5 salts)
6853 * This function is based on the above {@link random_string()} however it uses a
6854 * larger pool of characters and generates a string between 24 and 32 characters
6856 * @param int $length Optional if set generates a string to exactly this length
6857 * @return string
6859 function complex_random_string($length=null) {
6860 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
6861 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
6862 $poollen = strlen($pool);
6863 mt_srand ((double) microtime() * 1000000);
6864 if ($length===null) {
6865 $length = floor(rand(24,32));
6867 $string = '';
6868 for ($i = 0; $i < $length; $i++) {
6869 $string .= $pool[(mt_rand()%$poollen)];
6871 return $string;
6875 * Given some text (which may contain HTML) and an ideal length,
6876 * this function truncates the text neatly on a word boundary if possible
6877 * @param string $text - text to be shortened
6878 * @param int $ideal - ideal string length
6879 * @param boolean $exact if false, $text will not be cut mid-word
6880 * @return string $truncate - shortened string
6883 function shorten_text($text, $ideal=30, $exact = false) {
6885 global $CFG;
6886 $ending = '...';
6888 // if the plain text is shorter than the maximum length, return the whole text
6889 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
6890 return $text;
6893 // Splits on HTML tags. Each open/close/empty tag will be the first thing
6894 // and only tag in its 'line'
6895 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
6897 $total_length = strlen($ending);
6898 $truncate = '';
6900 // This array stores information about open and close tags and their position
6901 // in the truncated string. Each item in the array is an object with fields
6902 // ->open (true if open), ->tag (tag name in lower case), and ->pos
6903 // (byte position in truncated text)
6904 $tagdetails = array();
6906 foreach ($lines as $line_matchings) {
6907 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
6908 if (!empty($line_matchings[1])) {
6909 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
6910 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
6911 // do nothing
6912 // if tag is a closing tag (f.e. </b>)
6913 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
6914 // record closing tag
6915 $tagdetails[] = (object)array('open'=>false,
6916 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6917 // if tag is an opening tag (f.e. <b>)
6918 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
6919 // record opening tag
6920 $tagdetails[] = (object)array('open'=>true,
6921 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6923 // add html-tag to $truncate'd text
6924 $truncate .= $line_matchings[1];
6927 // calculate the length of the plain text part of the line; handle entities as one character
6928 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
6929 if ($total_length+$content_length > $ideal) {
6930 // the number of characters which are left
6931 $left = $ideal - $total_length;
6932 $entities_length = 0;
6933 // search for html entities
6934 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)) {
6935 // calculate the real length of all entities in the legal range
6936 foreach ($entities[0] as $entity) {
6937 if ($entity[1]+1-$entities_length <= $left) {
6938 $left--;
6939 $entities_length += strlen($entity[0]);
6940 } else {
6941 // no more characters left
6942 break;
6946 $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
6947 // maximum lenght is reached, so get off the loop
6948 break;
6949 } else {
6950 $truncate .= $line_matchings[2];
6951 $total_length += $content_length;
6954 // if the maximum length is reached, get off the loop
6955 if($total_length >= $ideal) {
6956 break;
6960 // if the words shouldn't be cut in the middle...
6961 if (!$exact) {
6962 // ...search the last occurance of a space...
6963 for ($k=strlen($truncate);$k>0;$k--) {
6964 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
6965 if ($char == '.' or $char == ' ') {
6966 $breakpos = $k+1;
6967 break;
6968 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
6969 $breakpos = $k; // can be truncated at any UTF-8
6970 break; // character boundary.
6975 if (isset($breakpos)) {
6976 // ...and cut the text in this position
6977 $truncate = substr($truncate, 0, $breakpos);
6981 // add the defined ending to the text
6982 $truncate .= $ending;
6984 // Now calculate the list of open html tags based on the truncate position
6985 $open_tags = array();
6986 foreach ($tagdetails as $taginfo) {
6987 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
6988 // Don't include tags after we made the break!
6989 break;
6991 if($taginfo->open) {
6992 // add tag to the beginning of $open_tags list
6993 array_unshift($open_tags, $taginfo->tag);
6994 } else {
6995 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
6996 if ($pos !== false) {
6997 unset($open_tags[$pos]);
7002 // close all unclosed html-tags
7003 foreach ($open_tags as $tag) {
7004 $truncate .= '</' . $tag . '>';
7007 return $truncate;
7012 * Given dates in seconds, how many weeks is the date from startdate
7013 * The first week is 1, the second 2 etc ...
7015 * @uses WEEKSECS
7016 * @param ? $startdate ?
7017 * @param ? $thedate ?
7018 * @return string
7019 * @todo Finish documenting this function
7021 function getweek ($startdate, $thedate) {
7022 if ($thedate < $startdate) { // error
7023 return 0;
7026 return floor(($thedate - $startdate) / WEEKSECS) + 1;
7030 * returns a randomly generated password of length $maxlen. inspired by
7031 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
7032 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
7034 * @param int $maxlen The maximum size of the password being generated.
7035 * @return string
7037 function generate_password($maxlen=10) {
7038 global $CFG;
7040 if (empty($CFG->passwordpolicy)) {
7041 $fillers = PASSWORD_DIGITS;
7042 $wordlist = file($CFG->wordlist);
7043 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7044 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7045 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
7046 $password = $word1 . $filler1 . $word2;
7047 } else {
7048 $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
7049 $digits = $CFG->minpassworddigits;
7050 $lower = $CFG->minpasswordlower;
7051 $upper = $CFG->minpasswordupper;
7052 $nonalphanum = $CFG->minpasswordnonalphanum;
7053 $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
7055 // Make sure we have enough characters to fulfill
7056 // complexity requirements
7057 $passworddigits = PASSWORD_DIGITS;
7058 while ($digits > strlen($passworddigits)) {
7059 $passworddigits .= PASSWORD_DIGITS;
7061 $passwordlower = PASSWORD_LOWER;
7062 while ($lower > strlen($passwordlower)) {
7063 $passwordlower .= PASSWORD_LOWER;
7065 $passwordupper = PASSWORD_UPPER;
7066 while ($upper > strlen($passwordupper)) {
7067 $passwordupper .= PASSWORD_UPPER;
7069 $passwordnonalphanum = PASSWORD_NONALPHANUM;
7070 while ($nonalphanum > strlen($passwordnonalphanum)) {
7071 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
7074 // Now mix and shuffle it all
7075 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
7076 substr(str_shuffle ($passwordupper), 0, $upper) .
7077 substr(str_shuffle ($passworddigits), 0, $digits) .
7078 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
7079 substr(str_shuffle ($passwordlower .
7080 $passwordupper .
7081 $passworddigits .
7082 $passwordnonalphanum), 0 , $additional));
7085 return substr ($password, 0, $maxlen);
7089 * Given a float, prints it nicely.
7090 * Localized floats must not be used in calculations!
7092 * @param float $flaot The float to print
7093 * @param int $places The number of decimal places to print.
7094 * @param bool $localized use localized decimal separator
7095 * @return string locale float
7097 function format_float($float, $decimalpoints=1, $localized=true) {
7098 if (is_null($float)) {
7099 return '';
7101 if ($localized) {
7102 return number_format($float, $decimalpoints, get_string('decsep'), '');
7103 } else {
7104 return number_format($float, $decimalpoints, '.', '');
7109 * Converts locale specific floating point/comma number back to standard PHP float value
7110 * Do NOT try to do any math operations before this conversion on any user submitted floats!
7112 * @param string $locale_float locale aware float representation
7114 function unformat_float($locale_float) {
7115 $locale_float = trim($locale_float);
7117 if ($locale_float == '') {
7118 return null;
7121 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
7123 return (float)str_replace(get_string('decsep'), '.', $locale_float);
7127 * Given a simple array, this shuffles it up just like shuffle()
7128 * Unlike PHP's shuffle() this function works on any machine.
7130 * @param array $array The array to be rearranged
7131 * @return array
7133 function swapshuffle($array) {
7135 srand ((double) microtime() * 10000000);
7136 $last = count($array) - 1;
7137 for ($i=0;$i<=$last;$i++) {
7138 $from = rand(0,$last);
7139 $curr = $array[$i];
7140 $array[$i] = $array[$from];
7141 $array[$from] = $curr;
7143 return $array;
7147 * Like {@link swapshuffle()}, but works on associative arrays
7149 * @param array $array The associative array to be rearranged
7150 * @return array
7152 function swapshuffle_assoc($array) {
7154 $newarray = array();
7155 $newkeys = swapshuffle(array_keys($array));
7157 foreach ($newkeys as $newkey) {
7158 $newarray[$newkey] = $array[$newkey];
7160 return $newarray;
7164 * Given an arbitrary array, and a number of draws,
7165 * this function returns an array with that amount
7166 * of items. The indexes are retained.
7168 * @param array $array ?
7169 * @param ? $draws ?
7170 * @return ?
7171 * @todo Finish documenting this function
7173 function draw_rand_array($array, $draws) {
7174 srand ((double) microtime() * 10000000);
7176 $return = array();
7178 $last = count($array);
7180 if ($draws > $last) {
7181 $draws = $last;
7184 while ($draws > 0) {
7185 $last--;
7187 $keys = array_keys($array);
7188 $rand = rand(0, $last);
7190 $return[$keys[$rand]] = $array[$keys[$rand]];
7191 unset($array[$keys[$rand]]);
7193 $draws--;
7196 return $return;
7200 * microtime_diff
7202 * @param string $a ?
7203 * @param string $b ?
7204 * @return string
7205 * @todo Finish documenting this function
7207 function microtime_diff($a, $b) {
7208 list($a_dec, $a_sec) = explode(' ', $a);
7209 list($b_dec, $b_sec) = explode(' ', $b);
7210 return $b_sec - $a_sec + $b_dec - $a_dec;
7214 * Given a list (eg a,b,c,d,e) this function returns
7215 * an array of 1->a, 2->b, 3->c etc
7217 * @param array $list ?
7218 * @param string $separator ?
7219 * @todo Finish documenting this function
7221 function make_menu_from_list($list, $separator=',') {
7223 $array = array_reverse(explode($separator, $list), true);
7224 foreach ($array as $key => $item) {
7225 $outarray[$key+1] = trim($item);
7227 return $outarray;
7231 * Creates an array that represents all the current grades that
7232 * can be chosen using the given grading type. Negative numbers
7233 * are scales, zero is no grade, and positive numbers are maximum
7234 * grades.
7236 * @param int $gradingtype ?
7237 * return int
7238 * @todo Finish documenting this function
7240 function make_grades_menu($gradingtype) {
7241 $grades = array();
7242 if ($gradingtype < 0) {
7243 if ($scale = get_record('scale', 'id', - $gradingtype)) {
7244 return make_menu_from_list($scale->scale);
7246 } else if ($gradingtype > 0) {
7247 for ($i=$gradingtype; $i>=0; $i--) {
7248 $grades[$i] = $i .' / '. $gradingtype;
7250 return $grades;
7252 return $grades;
7256 * This function returns the nummber of activities
7257 * using scaleid in a courseid
7259 * @param int $courseid ?
7260 * @param int $scaleid ?
7261 * @return int
7262 * @todo Finish documenting this function
7264 function course_scale_used($courseid, $scaleid) {
7266 global $CFG;
7268 $return = 0;
7270 if (!empty($scaleid)) {
7271 if ($cms = get_course_mods($courseid)) {
7272 foreach ($cms as $cm) {
7273 //Check cm->name/lib.php exists
7274 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
7275 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
7276 $function_name = $cm->modname.'_scale_used';
7277 if (function_exists($function_name)) {
7278 if ($function_name($cm->instance,$scaleid)) {
7279 $return++;
7286 // check if any course grade item makes use of the scale
7287 $return += count_records('grade_items', 'courseid', $courseid, 'scaleid', $scaleid);
7289 // check if any outcome in the course makes use of the scale
7290 $return += count_records_sql("SELECT COUNT(*)
7291 FROM {$CFG->prefix}grade_outcomes_courses goc,
7292 {$CFG->prefix}grade_outcomes go
7293 WHERE go.id = goc.outcomeid
7294 AND go.scaleid = $scaleid
7295 AND goc.courseid = $courseid");
7297 return $return;
7301 * This function returns the nummber of activities
7302 * using scaleid in the entire site
7304 * @param int $scaleid ?
7305 * @return int
7306 * @todo Finish documenting this function. Is return type correct?
7308 function site_scale_used($scaleid,&$courses) {
7310 global $CFG;
7312 $return = 0;
7314 if (!is_array($courses) || count($courses) == 0) {
7315 $courses = get_courses("all",false,"c.id,c.shortname");
7318 if (!empty($scaleid)) {
7319 if (is_array($courses) && count($courses) > 0) {
7320 foreach ($courses as $course) {
7321 $return += course_scale_used($course->id,$scaleid);
7325 return $return;
7329 * make_unique_id_code
7331 * @param string $extra ?
7332 * @return string
7333 * @todo Finish documenting this function
7335 function make_unique_id_code($extra='') {
7337 $hostname = 'unknownhost';
7338 if (!empty($_SERVER['HTTP_HOST'])) {
7339 $hostname = $_SERVER['HTTP_HOST'];
7340 } else if (!empty($_ENV['HTTP_HOST'])) {
7341 $hostname = $_ENV['HTTP_HOST'];
7342 } else if (!empty($_SERVER['SERVER_NAME'])) {
7343 $hostname = $_SERVER['SERVER_NAME'];
7344 } else if (!empty($_ENV['SERVER_NAME'])) {
7345 $hostname = $_ENV['SERVER_NAME'];
7348 $date = gmdate("ymdHis");
7350 $random = random_string(6);
7352 if ($extra) {
7353 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
7354 } else {
7355 return $hostname .'+'. $date .'+'. $random;
7361 * Function to check the passed address is within the passed subnet
7363 * The parameter is a comma separated string of subnet definitions.
7364 * Subnet strings can be in one of three formats:
7365 * 1: xxx.xxx.xxx.xxx/xx
7366 * 2: xxx.xxx
7367 * 3: xxx.xxx.xxx.xxx-xxx //a range of IP addresses in the last group.
7368 * Code for type 1 modified from user posted comments by mediator at
7369 * {@link http://au.php.net/manual/en/function.ip2long.php}
7371 * TODO one day we will have to make this work with IP6.
7373 * @param string $addr The address you are checking
7374 * @param string $subnetstr The string of subnet addresses
7375 * @return bool
7377 function address_in_subnet($addr, $subnetstr) {
7379 $subnets = explode(',', $subnetstr);
7380 $found = false;
7381 $addr = trim($addr);
7383 foreach ($subnets as $subnet) {
7384 $subnet = trim($subnet);
7385 if (strpos($subnet, '/') !== false) { /// type 1
7386 list($ip, $mask) = explode('/', $subnet);
7387 if (!is_number($mask) || $mask < 0 || $mask > 32) {
7388 continue;
7390 if ($mask == 0) {
7391 return true;
7393 if ($mask == 32) {
7394 if ($ip === $addr) {
7395 return true;
7397 continue;
7399 $mask = 0xffffffff << (32 - $mask);
7400 $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
7401 } else if (strpos($subnet, '-') !== false) {/// type 3
7402 $subnetparts = explode('.', $subnet);
7403 $addrparts = explode('.', $addr);
7404 $subnetrange = explode('-', array_pop($subnetparts));
7405 if (count($subnetrange) == 2) {
7406 $lastaddrpart = array_pop($addrparts);
7407 $found = ($subnetparts == $addrparts &&
7408 $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
7410 } else { /// type 2
7411 if ($subnet[strlen($subnet) - 1] != '.') {
7412 $subnet .= '.';
7414 $found = (strpos($addr . '.', $subnet) === 0);
7417 if ($found) {
7418 break;
7421 return $found;
7425 * This function sets the $HTTPSPAGEREQUIRED global
7426 * (used in some parts of moodle to change some links)
7427 * and calculate the proper wwwroot to be used
7429 * By using this function properly, we can ensure 100% https-ized pages
7430 * at our entire discretion (login, forgot_password, change_password)
7432 function httpsrequired() {
7434 global $CFG, $HTTPSPAGEREQUIRED;
7436 if (!empty($CFG->loginhttps)) {
7437 $HTTPSPAGEREQUIRED = true;
7438 $CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
7439 $CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
7441 // change theme URLs to https
7442 theme_setup();
7444 } else {
7445 $CFG->httpswwwroot = $CFG->wwwroot;
7446 $CFG->httpsthemewww = $CFG->themewww;
7451 * For outputting debugging info
7453 * @uses STDOUT
7454 * @param string $string ?
7455 * @param string $eol ?
7456 * @todo Finish documenting this function
7458 function mtrace($string, $eol="\n", $sleep=0) {
7460 if (defined('STDOUT')) {
7461 fwrite(STDOUT, $string.$eol);
7462 } else {
7463 echo $string . $eol;
7466 flush();
7468 //delay to keep message on user's screen in case of subsequent redirect
7469 if ($sleep) {
7470 sleep($sleep);
7474 //Replace 1 or more slashes or backslashes to 1 slash
7475 function cleardoubleslashes ($path) {
7476 return preg_replace('/(\/|\\\){1,}/','/',$path);
7479 function zip_files ($originalfiles, $destination) {
7480 //Zip an array of files/dirs to a destination zip file
7481 //Both parameters must be FULL paths to the files/dirs
7483 global $CFG;
7485 //Extract everything from destination
7486 $path_parts = pathinfo(cleardoubleslashes($destination));
7487 $destpath = $path_parts["dirname"]; //The path of the zip file
7488 $destfilename = $path_parts["basename"]; //The name of the zip file
7489 $extension = $path_parts["extension"]; //The extension of the file
7491 //If no file, error
7492 if (empty($destfilename)) {
7493 return false;
7496 //If no extension, add it
7497 if (empty($extension)) {
7498 $extension = 'zip';
7499 $destfilename = $destfilename.'.'.$extension;
7502 //Check destination path exists
7503 if (!is_dir($destpath)) {
7504 return false;
7507 //Check destination path is writable. TODO!!
7509 //Clean destination filename
7510 $destfilename = clean_filename($destfilename);
7512 //Now check and prepare every file
7513 $files = array();
7514 $origpath = NULL;
7516 foreach ($originalfiles as $file) { //Iterate over each file
7517 //Check for every file
7518 $tempfile = cleardoubleslashes($file); // no doubleslashes!
7519 //Calculate the base path for all files if it isn't set
7520 if ($origpath === NULL) {
7521 $origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
7523 //See if the file is readable
7524 if (!is_readable($tempfile)) { //Is readable
7525 continue;
7527 //See if the file/dir is in the same directory than the rest
7528 if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
7529 continue;
7531 //Add the file to the array
7532 $files[] = $tempfile;
7535 //Everything is ready:
7536 // -$origpath is the path where ALL the files to be compressed reside (dir).
7537 // -$destpath is the destination path where the zip file will go (dir).
7538 // -$files is an array of files/dirs to compress (fullpath)
7539 // -$destfilename is the name of the zip file (without path)
7541 //print_object($files); //Debug
7543 if (empty($CFG->zip)) { // Use built-in php-based zip function
7545 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7546 //rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
7547 $zipfiles = array();
7548 $start = strlen($origpath)+1;
7549 foreach($files as $file) {
7550 $tf = array();
7551 $tf[PCLZIP_ATT_FILE_NAME] = $file;
7552 $tf[PCLZIP_ATT_FILE_NEW_FULL_NAME] = substr($file, $start);
7553 $zipfiles[] = $tf;
7555 //create the archive
7556 $archive = new PclZip(cleardoubleslashes("$destpath/$destfilename"));
7557 if (($list = $archive->create($zipfiles) == 0)) {
7558 notice($archive->errorInfo(true));
7559 return false;
7562 } else { // Use external zip program
7564 $filestozip = "";
7565 foreach ($files as $filetozip) {
7566 $filestozip .= escapeshellarg(basename($filetozip));
7567 $filestozip .= " ";
7569 //Construct the command
7570 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7571 $command = 'cd '.escapeshellarg($origpath).$separator.
7572 escapeshellarg($CFG->zip).' -r '.
7573 escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
7574 //All converted to backslashes in WIN
7575 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7576 $command = str_replace('/','\\',$command);
7578 Exec($command);
7580 return true;
7583 function unzip_file ($zipfile, $destination = '', $showstatus = true) {
7584 //Unzip one zip file to a destination dir
7585 //Both parameters must be FULL paths
7586 //If destination isn't specified, it will be the
7587 //SAME directory where the zip file resides.
7589 global $CFG;
7591 //Extract everything from zipfile
7592 $path_parts = pathinfo(cleardoubleslashes($zipfile));
7593 $zippath = $path_parts["dirname"]; //The path of the zip file
7594 $zipfilename = $path_parts["basename"]; //The name of the zip file
7595 $extension = $path_parts["extension"]; //The extension of the file
7597 //If no file, error
7598 if (empty($zipfilename)) {
7599 return false;
7602 //If no extension, error
7603 if (empty($extension)) {
7604 return false;
7607 //Clear $zipfile
7608 $zipfile = cleardoubleslashes($zipfile);
7610 //Check zipfile exists
7611 if (!file_exists($zipfile)) {
7612 return false;
7615 //If no destination, passed let's go with the same directory
7616 if (empty($destination)) {
7617 $destination = $zippath;
7620 //Clear $destination
7621 $destpath = rtrim(cleardoubleslashes($destination), "/");
7623 //Check destination path exists
7624 if (!is_dir($destpath)) {
7625 return false;
7628 //Check destination path is writable. TODO!!
7630 //Everything is ready:
7631 // -$zippath is the path where the zip file resides (dir)
7632 // -$zipfilename is the name of the zip file (without path)
7633 // -$destpath is the destination path where the zip file will uncompressed (dir)
7635 $list = array();
7637 require_once("$CFG->libdir/filelib.php");
7639 do {
7640 $temppath = "$CFG->dataroot/temp/unzip/".random_string(10);
7641 } while (file_exists($temppath));
7642 if (!check_dir_exists($temppath, true, true)) {
7643 return false;
7646 if (empty($CFG->unzip)) { // Use built-in php-based unzip function
7648 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7649 $archive = new PclZip(cleardoubleslashes("$zippath/$zipfilename"));
7650 if (!$list = $archive->extract(PCLZIP_OPT_PATH, $temppath,
7651 PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename',
7652 PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $temppath)) {
7653 if (!empty($showstatus)) {
7654 notice($archive->errorInfo(true));
7656 return false;
7659 } else { // Use external unzip program
7661 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7662 $redirection = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? '' : ' 2>&1';
7664 $command = 'cd '.escapeshellarg($zippath).$separator.
7665 escapeshellarg($CFG->unzip).' -o '.
7666 escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
7667 escapeshellarg($temppath).$redirection;
7668 //All converted to backslashes in WIN
7669 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7670 $command = str_replace('/','\\',$command);
7672 Exec($command,$list);
7675 unzip_process_temp_dir($temppath, $destpath);
7676 fulldelete($temppath);
7678 //Display some info about the unzip execution
7679 if ($showstatus) {
7680 unzip_show_status($list, $temppath, $destpath);
7683 return true;
7687 * Sanitize temporary unzipped files and move to target dir.
7688 * @param string $temppath path to temporary dir with unzip output
7689 * @param string $destpath destination path
7690 * @return void
7692 function unzip_process_temp_dir($temppath, $destpath) {
7693 global $CFG;
7695 $filepermissions = ($CFG->directorypermissions & 0666); // strip execute flags
7697 if (check_dir_exists($destpath, true, true)) {
7698 $currdir = opendir($temppath);
7699 while (false !== ($file = readdir($currdir))) {
7700 if ($file <> ".." && $file <> ".") {
7701 $fullfile = "$temppath/$file";
7702 if (is_link($fullfile)) {
7703 //somebody tries to sneak in symbolik link - no way!
7704 continue;
7706 $cleanfile = clean_param($file, PARAM_FILE); // no dangerous chars
7707 if ($cleanfile === '') {
7708 // invalid file name
7709 continue;
7711 if ($cleanfile !== $file and file_exists("$temppath/$cleanfile")) {
7712 // eh, weird chars collision detected
7713 continue;
7715 $descfile = "$destpath/$cleanfile";
7716 if (is_dir($fullfile)) {
7717 // recurse into subdirs
7718 unzip_process_temp_dir($fullfile, $descfile);
7720 if (is_file($fullfile)) {
7721 // rename and move the file
7722 if (file_exists($descfile)) {
7723 //override existing files
7724 unlink($descfile);
7726 rename($fullfile, $descfile);
7727 chmod($descfile, $filepermissions);
7731 closedir($currdir);
7735 function unzip_cleanfilename ($p_event, &$p_header) {
7736 //This function is used as callback in unzip_file() function
7737 //to clean illegal characters for given platform and to prevent directory traversal.
7738 //Produces the same result as info-zip unzip.
7739 $p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
7740 $p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
7741 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7742 $p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
7743 $p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
7744 } else {
7745 //Add filtering for other systems here
7746 // BSD: none (tested)
7747 // Linux: ??
7748 // MacosX: ??
7750 $p_header['filename'] = cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
7751 return 1;
7754 function unzip_show_status($list, $removepath, $removepath2) {
7755 //This function shows the results of the unzip execution
7756 //depending of the value of the $CFG->zip, results will be
7757 //text or an array of files.
7759 global $CFG;
7761 if (empty($CFG->unzip)) { // Use built-in php-based zip function
7762 $strname = get_string("name");
7763 $strsize = get_string("size");
7764 $strmodified = get_string("modified");
7765 $strstatus = get_string("status");
7766 echo "<table width=\"640\">";
7767 echo "<tr><th class=\"header\" scope=\"col\">$strname</th>";
7768 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strsize</th>";
7769 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strmodified</th>";
7770 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strstatus</th></tr>";
7771 foreach ($list as $item) {
7772 echo "<tr>";
7773 $item['filename'] = str_replace(cleardoubleslashes($removepath).'/', "", $item['filename']);
7774 $item['filename'] = str_replace(cleardoubleslashes($removepath2).'/', "", $item['filename']);
7775 print_cell("left", s(clean_param($item['filename'], PARAM_PATH)));
7776 if (! $item['folder']) {
7777 print_cell("right", display_size($item['size']));
7778 } else {
7779 echo "<td>&nbsp;</td>";
7781 $filedate = userdate($item['mtime'], get_string("strftimedatetime"));
7782 print_cell("right", $filedate);
7783 print_cell("right", $item['status']);
7784 echo "</tr>";
7786 echo "</table>";
7788 } else { // Use external zip program
7789 print_simple_box_start("center");
7790 echo "<pre>";
7791 foreach ($list as $item) {
7792 $item = str_replace(cleardoubleslashes($removepath.'/'), '', $item);
7793 $item = str_replace(cleardoubleslashes($removepath2.'/'), '', $item);
7794 echo s($item).'<br />';
7796 echo "</pre>";
7797 print_simple_box_end();
7802 * Returns most reliable client address
7804 * @return string The remote IP address
7806 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
7807 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
7808 function getremoteaddr() {
7809 global $CFG;
7811 if (empty($CFG->getremoteaddrconf)) {
7812 // This will happen, for example, before just after the upgrade, as the
7813 // user is redirected to the admin screen.
7814 $variablestoskip = 0;
7815 } else {
7816 $variablestoskip = $CFG->getremoteaddrconf;
7818 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
7819 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
7820 return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
7823 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
7824 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
7825 return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
7828 if (!empty($_SERVER['REMOTE_ADDR'])) {
7829 return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
7830 } else {
7831 return null;
7836 * Cleans a remote address ready to put into the log table
7838 function cleanremoteaddr($addr) {
7839 $originaladdr = $addr;
7840 $matches = array();
7841 // first get all things that look like IP addresses.
7842 if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER)) {
7843 return '';
7845 $goodmatches = array();
7846 $lanmatches = array();
7847 foreach ($matches as $match) {
7848 // print_r($match);
7849 // check to make sure it's not an internal address.
7850 // the following are reserved for private lans...
7851 // 10.0.0.0 - 10.255.255.255
7852 // 172.16.0.0 - 172.31.255.255
7853 // 192.168.0.0 - 192.168.255.255
7854 // 169.254.0.0 -169.254.255.255
7855 $bits = explode('.',$match[0]);
7856 if (count($bits) != 4) {
7857 // weird, preg match shouldn't give us it.
7858 continue;
7860 if (($bits[0] == 10)
7861 || ($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
7862 || ($bits[0] == 192 && $bits[1] == 168)
7863 || ($bits[0] == 169 && $bits[1] == 254)) {
7864 $lanmatches[] = $match[0];
7865 continue;
7867 // finally, it's ok
7868 $goodmatches[] = $match[0];
7870 if (!count($goodmatches)) {
7871 // perhaps we have a lan match, it's probably better to return that.
7872 if (!count($lanmatches)) {
7873 return '';
7874 } else {
7875 return array_pop($lanmatches);
7878 if (count($goodmatches) == 1) {
7879 return $goodmatches[0];
7881 //Commented out following because there are so many, and it clogs the logs MDL-13544
7882 //error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
7884 // We need to return something, so return the first
7885 return array_pop($goodmatches);
7889 * file_put_contents is only supported by php 5.0 and higher
7890 * so if it is not predefined, define it here
7892 * @param $file full path of the file to write
7893 * @param $contents contents to be sent
7894 * @return number of bytes written (false on error)
7896 if(!function_exists('file_put_contents')) {
7897 function file_put_contents($file, $contents) {
7898 $result = false;
7899 if ($f = fopen($file, 'w')) {
7900 $result = fwrite($f, $contents);
7901 fclose($f);
7903 return $result;
7908 * The clone keyword is only supported from PHP 5 onwards.
7909 * The behaviour of $obj2 = $obj1 differs fundamentally
7910 * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
7911 * created, in PHP 5 $obj1 is referenced. To create a copy
7912 * in PHP 5 the clone keyword was introduced. This function
7913 * simulates this behaviour for PHP < 5.0.0.
7914 * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
7916 * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
7917 * Found a better implementation (more checks and possibilities) from PEAR:
7918 * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
7920 * @param object $obj
7921 * @return object
7923 if(!check_php_version('5.0.0')) {
7924 // the eval is needed to prevent PHP 5 from getting a parse error!
7925 eval('
7926 function clone($obj) {
7927 /// Sanity check
7928 if (!is_object($obj)) {
7929 user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
7930 return;
7933 /// Use serialize/unserialize trick to deep copy the object
7934 $obj = unserialize(serialize($obj));
7936 /// If there is a __clone method call it on the "new" class
7937 if (method_exists($obj, \'__clone\')) {
7938 $obj->__clone();
7941 return $obj;
7944 // Supply the PHP5 function scandir() to older versions.
7945 function scandir($directory) {
7946 $files = array();
7947 if ($dh = opendir($directory)) {
7948 while (($file = readdir($dh)) !== false) {
7949 $files[] = $file;
7951 closedir($dh);
7953 return $files;
7956 // Supply the PHP5 function array_combine() to older versions.
7957 function array_combine($keys, $values) {
7958 if (!is_array($keys) || !is_array($values) || count($keys) != count($values)) {
7959 return false;
7961 reset($values);
7962 $result = array();
7963 foreach ($keys as $key) {
7964 $result[$key] = current($values);
7965 next($values);
7967 return $result;
7973 * This function will make a complete copy of anything it's given,
7974 * regardless of whether it's an object or not.
7975 * @param mixed $thing
7976 * @return mixed
7978 function fullclone($thing) {
7979 return unserialize(serialize($thing));
7984 * This function expects to called during shutdown
7985 * should be set via register_shutdown_function()
7986 * in lib/setup.php .
7988 * Right now we do it only if we are under apache, to
7989 * make sure apache children that hog too much mem are
7990 * killed.
7993 function moodle_request_shutdown() {
7995 global $CFG;
7997 // initially, we are only ever called under apache
7998 // but check just in case
7999 if (function_exists('apache_child_terminate')
8000 && function_exists('memory_get_usage')
8001 && ini_get_bool('child_terminate')) {
8002 if (empty($CFG->apachemaxmem)) {
8003 $CFG->apachemaxmem = 25000000; // default 25MiB
8005 if (memory_get_usage() > (int)$CFG->apachemaxmem) {
8006 trigger_error('Mem usage over $CFG->apachemaxmem: marking child for reaping.');
8007 @apache_child_terminate();
8010 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
8011 if (defined('MDL_PERFTOLOG')) {
8012 $perf = get_performance_info();
8013 error_log("PERF: " . $perf['txt']);
8015 if (defined('MDL_PERFINC')) {
8016 $inc = get_included_files();
8017 $ts = 0;
8018 foreach($inc as $f) {
8019 if (preg_match(':^/:', $f)) {
8020 $fs = filesize($f);
8021 $ts += $fs;
8022 $hfs = display_size($fs);
8023 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
8024 , NULL, NULL, 0);
8025 } else {
8026 error_log($f , NULL, NULL, 0);
8029 if ($ts > 0 ) {
8030 $hts = display_size($ts);
8031 error_log("Total size of files included: $ts ($hts)");
8038 * If new messages are waiting for the current user, then return
8039 * Javascript code to create a popup window
8041 * @return string Javascript code
8043 function message_popup_window() {
8044 global $USER;
8046 $popuplimit = 30; // Minimum seconds between popups
8048 if (!defined('MESSAGE_WINDOW')) {
8049 if (!empty($USER->id) and !isguestuser()) {
8050 if (!isset($USER->message_lastpopup)) {
8051 $USER->message_lastpopup = 0;
8053 if ((time() - $USER->message_lastpopup) > $popuplimit) { /// It's been long enough
8054 if (get_user_preferences('message_showmessagewindow', 1) == 1) {
8055 if (count_records_select('message', 'useridto = \''.$USER->id.'\' AND timecreated > \''.$USER->message_lastpopup.'\'')) {
8056 $USER->message_lastpopup = time();
8057 return '<script type="text/javascript">'."\n//<![CDATA[\n openpopup('/message/index.php', 'message',
8058 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0);\n//]]>\n</script>";
8065 return '';
8068 // Used to make sure that $min <= $value <= $max
8069 function bounded_number($min, $value, $max) {
8070 if($value < $min) {
8071 return $min;
8073 if($value > $max) {
8074 return $max;
8076 return $value;
8079 function array_is_nested($array) {
8080 foreach ($array as $value) {
8081 if (is_array($value)) {
8082 return true;
8085 return false;
8089 *** get_performance_info() pairs up with init_performance_info()
8090 *** loaded in setup.php. Returns an array with 'html' and 'txt'
8091 *** values ready for use, and each of the individual stats provided
8092 *** separately as well.
8095 function get_performance_info() {
8096 global $CFG, $PERF, $rcache;
8098 $info = array();
8099 $info['html'] = ''; // holds userfriendly HTML representation
8100 $info['txt'] = me() . ' '; // holds log-friendly representation
8102 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
8104 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
8105 $info['txt'] .= 'time: '.$info['realtime'].'s ';
8107 if (function_exists('memory_get_usage')) {
8108 $info['memory_total'] = memory_get_usage();
8109 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
8110 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
8111 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
8114 if (function_exists('memory_get_peak_usage')) {
8115 $info['memory_peak'] = memory_get_peak_usage();
8116 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
8117 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
8120 $inc = get_included_files();
8121 //error_log(print_r($inc,1));
8122 $info['includecount'] = count($inc);
8123 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
8124 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
8126 if (!empty($PERF->dbqueries)) {
8127 $info['dbqueries'] = $PERF->dbqueries;
8128 $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
8129 $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
8132 if (!empty($PERF->logwrites)) {
8133 $info['logwrites'] = $PERF->logwrites;
8134 $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
8135 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
8138 if (!empty($PERF->profiling) && $PERF->profiling) {
8139 require_once($CFG->dirroot .'/lib/profilerlib.php');
8140 $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
8143 if (function_exists('posix_times')) {
8144 $ptimes = posix_times();
8145 if (is_array($ptimes)) {
8146 foreach ($ptimes as $key => $val) {
8147 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
8149 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
8150 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
8154 // Grab the load average for the last minute
8155 // /proc will only work under some linux configurations
8156 // while uptime is there under MacOSX/Darwin and other unices
8157 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
8158 list($server_load) = explode(' ', $loadavg[0]);
8159 unset($loadavg);
8160 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
8161 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
8162 $server_load = $matches[1];
8163 } else {
8164 trigger_error('Could not parse uptime output!');
8167 if (!empty($server_load)) {
8168 $info['serverload'] = $server_load;
8169 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
8170 $info['txt'] .= "serverload: {$info['serverload']} ";
8173 if (isset($rcache->hits) && isset($rcache->misses)) {
8174 $info['rcachehits'] = $rcache->hits;
8175 $info['rcachemisses'] = $rcache->misses;
8176 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
8177 "{$rcache->hits}/{$rcache->misses}</span> ";
8178 $info['txt'] .= 'rcache: '.
8179 "{$rcache->hits}/{$rcache->misses} ";
8181 $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
8182 return $info;
8185 function apd_get_profiling() {
8186 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
8190 * Delete directory or only it's content
8191 * @param string $dir directory path
8192 * @param bool $content_only
8193 * @return bool success, true also if dir does not exist
8195 function remove_dir($dir, $content_only=false) {
8196 if (!file_exists($dir)) {
8197 // nothing to do
8198 return true;
8200 $handle = opendir($dir);
8201 $result = true;
8202 while (false!==($item = readdir($handle))) {
8203 if($item != '.' && $item != '..') {
8204 if(is_dir($dir.'/'.$item)) {
8205 $result = remove_dir($dir.'/'.$item) && $result;
8206 }else{
8207 $result = unlink($dir.'/'.$item) && $result;
8211 closedir($handle);
8212 if ($content_only) {
8213 return $result;
8215 return rmdir($dir); // if anything left the result will be false, noo need for && $result
8219 * Function to check if a directory exists and optionally create it.
8221 * @param string absolute directory path (must be under $CFG->dataroot)
8222 * @param boolean create directory if does not exist
8223 * @param boolean create directory recursively
8225 * @return boolean true if directory exists or created
8227 function check_dir_exists($dir, $create=false, $recursive=false) {
8229 global $CFG;
8231 if (strstr(cleardoubleslashes($dir), cleardoubleslashes($CFG->dataroot.'/')) === false) {
8232 debugging('Warning. Wrong call to check_dir_exists(). $dir must be an absolute path under $CFG->dataroot ("' . $dir . '" is incorrect)', DEBUG_DEVELOPER);
8235 $status = true;
8237 if(!is_dir($dir)) {
8238 if (!$create) {
8239 $status = false;
8240 } else {
8241 umask(0000);
8242 if ($recursive) {
8243 /// We are going to make it recursive under $CFG->dataroot only
8244 /// (will help sites running open_basedir security and others)
8245 $dir = str_replace(cleardoubleslashes($CFG->dataroot . '/'), '', cleardoubleslashes($dir));
8246 /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-(
8247 $dirs = explode('/', $dir); /// Extract path parts
8248 /// Iterate over each part with start point $CFG->dataroot
8249 $dir = $CFG->dataroot . '/';
8250 foreach ($dirs as $part) {
8251 if ($part == '') {
8252 continue;
8254 $dir .= $part.'/';
8255 if (!is_dir($dir)) {
8256 if (!mkdir($dir, $CFG->directorypermissions)) {
8257 $status = false;
8258 break;
8262 } else {
8263 $status = mkdir($dir, $CFG->directorypermissions);
8267 return $status;
8270 function report_session_error() {
8271 global $CFG, $FULLME;
8273 if (empty($CFG->lang)) {
8274 $CFG->lang = "en";
8276 // Set up default theme and locale
8277 theme_setup();
8278 moodle_setlocale();
8280 //clear session cookies
8281 if (check_php_version('5.2.0')) {
8282 //PHP 5.2.0
8283 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8284 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8285 } else {
8286 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8287 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8289 //increment database error counters
8290 if (isset($CFG->session_error_counter)) {
8291 set_config('session_error_counter', 1 + $CFG->session_error_counter);
8292 } else {
8293 set_config('session_error_counter', 1);
8295 redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
8300 * Detect if an object or a class contains a given property
8301 * will take an actual object or the name of a class
8302 * @param mix $obj Name of class or real object to test
8303 * @param string $property name of property to find
8304 * @return bool true if property exists
8306 function object_property_exists( $obj, $property ) {
8307 if (is_string( $obj )) {
8308 $properties = get_class_vars( $obj );
8310 else {
8311 $properties = get_object_vars( $obj );
8313 return array_key_exists( $property, $properties );
8318 * Detect a custom script replacement in the data directory that will
8319 * replace an existing moodle script
8320 * @param string $urlpath path to the original script
8321 * @return string full path name if a custom script exists
8322 * @return bool false if no custom script exists
8324 function custom_script_path($urlpath='') {
8325 global $CFG;
8327 // set default $urlpath, if necessary
8328 if (empty($urlpath)) {
8329 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
8332 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
8333 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
8334 return false;
8337 // replace wwwroot with the path to the customscripts folder and clean path
8338 $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
8340 // remove the query string, if any
8341 if (($strpos = strpos($scriptpath, '?')) !== false) {
8342 $scriptpath = substr($scriptpath, 0, $strpos);
8345 // remove trailing slashes, if any
8346 $scriptpath = rtrim($scriptpath, '/\\');
8348 // append index.php, if necessary
8349 if (is_dir($scriptpath)) {
8350 $scriptpath .= '/index.php';
8353 // check the custom script exists
8354 if (file_exists($scriptpath)) {
8355 return $scriptpath;
8356 } else {
8357 return false;
8362 * Wrapper function to load necessary editor scripts
8363 * to $CFG->editorsrc array. Params can be coursei id
8364 * or associative array('courseid' => value, 'name' => 'editorname').
8365 * @uses $CFG
8366 * @param mixed $args Courseid or associative array.
8368 function loadeditor($args) {
8369 global $CFG;
8370 include($CFG->libdir .'/editorlib.php');
8371 return editorObject::loadeditor($args);
8375 * Returns whether or not the user object is a remote MNET user. This function
8376 * is in moodlelib because it does not rely on loading any of the MNET code.
8378 * @param object $user A valid user object
8379 * @return bool True if the user is from a remote Moodle.
8381 function is_mnet_remote_user($user) {
8382 global $CFG;
8384 if (!isset($CFG->mnet_localhost_id)) {
8385 include_once $CFG->dirroot . '/mnet/lib.php';
8386 $env = new mnet_environment();
8387 $env->init();
8388 unset($env);
8391 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
8395 * Checks if a given plugin is in the list of enabled enrolment plugins.
8397 * @param string $auth Enrolment plugin.
8398 * @return boolean Whether the plugin is enabled.
8400 function is_enabled_enrol($enrol='') {
8401 global $CFG;
8403 // use the global default if not specified
8404 if ($enrol == '') {
8405 $enrol = $CFG->enrol;
8407 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
8411 * This function will search for browser prefereed languages, setting Moodle
8412 * to use the best one available if $SESSION->lang is undefined
8414 function setup_lang_from_browser() {
8416 global $CFG, $SESSION, $USER;
8418 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
8419 // Lang is defined in session or user profile, nothing to do
8420 return;
8423 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
8424 return;
8427 /// Extract and clean langs from headers
8428 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
8429 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
8430 $rawlangs = explode(',', $rawlangs); // Convert to array
8431 $langs = array();
8433 $order = 1.0;
8434 foreach ($rawlangs as $lang) {
8435 if (strpos($lang, ';') === false) {
8436 $langs[(string)$order] = $lang;
8437 $order = $order-0.01;
8438 } else {
8439 $parts = explode(';', $lang);
8440 $pos = strpos($parts[1], '=');
8441 $langs[substr($parts[1], $pos+1)] = $parts[0];
8444 krsort($langs, SORT_NUMERIC);
8446 $langlist = get_list_of_languages();
8448 /// Look for such langs under standard locations
8449 foreach ($langs as $lang) {
8450 $lang = strtolower(clean_param($lang.'_utf8', PARAM_SAFEDIR)); // clean it properly for include
8451 if (!array_key_exists($lang, $langlist)) {
8452 continue; // language not allowed, try next one
8454 if (file_exists($CFG->dataroot .'/lang/'. $lang) or file_exists($CFG->dirroot .'/lang/'. $lang)) {
8455 $SESSION->lang = $lang; /// Lang exists, set it in session
8456 break; /// We have finished. Go out
8459 return;
8463 ////////////////////////////////////////////////////////////////////////////////
8465 function is_newnav($navigation) {
8466 if (is_array($navigation) && !empty($navigation['newnav'])) {
8467 return true;
8468 } else {
8469 return false;
8474 * Checks whether the given variable name is defined as a variable within the given object.
8475 * @note This will NOT work with stdClass objects, which have no class variables.
8476 * @param string $var The variable name
8477 * @param object $object The object to check
8478 * @return boolean
8480 function in_object_vars($var, $object) {
8481 $class_vars = get_class_vars(get_class($object));
8482 $class_vars = array_keys($class_vars);
8483 return in_array($var, $class_vars);
8487 * Returns an array without repeated objects.
8488 * This function is similar to array_unique, but for arrays that have objects as values
8490 * @param unknown_type $array
8491 * @param unknown_type $keep_key_assoc
8492 * @return unknown
8494 function object_array_unique($array, $keep_key_assoc = true) {
8495 $duplicate_keys = array();
8496 $tmp = array();
8498 foreach ($array as $key=>$val) {
8499 // convert objects to arrays, in_array() does not support objects
8500 if (is_object($val)) {
8501 $val = (array)$val;
8504 if (!in_array($val, $tmp)) {
8505 $tmp[] = $val;
8506 } else {
8507 $duplicate_keys[] = $key;
8511 foreach ($duplicate_keys as $key) {
8512 unset($array[$key]);
8515 return $keep_key_assoc ? $array : array_values($array);
8519 * Returns the language string for the given plugin.
8521 * @param string $plugin the plugin code name
8522 * @param string $type the type of plugin (mod, block, filter)
8523 * @return string The plugin language string
8525 function get_plugin_name($plugin, $type='mod') {
8526 $plugin_name = '';
8528 switch ($type) {
8529 case 'mod':
8530 $plugin_name = get_string('modulename', $plugin);
8531 break;
8532 case 'blocks':
8533 $plugin_name = get_string('blockname', "block_$plugin");
8534 if (empty($plugin_name) || $plugin_name == '[[blockname]]') {
8535 if (($block = block_instance($plugin)) !== false) {
8536 $plugin_name = $block->get_title();
8537 } else {
8538 $plugin_name = "[[$plugin]]";
8541 break;
8542 case 'filter':
8543 $plugin_name = trim(get_string('filtername', $plugin));
8544 if (empty($plugin_name) or ($plugin_name == '[[filtername]]')) {
8545 $textlib = textlib_get_instance();
8546 $plugin_name = $textlib->strtotitle($plugin);
8548 break;
8549 default:
8550 $plugin_name = $plugin;
8551 break;
8554 return $plugin_name;
8558 * Is a userid the primary administrator?
8560 * @param $userid int id of user to check
8561 * @return boolean
8563 function is_primary_admin($userid){
8564 $primaryadmin = get_admin();
8566 if($userid == $primaryadmin->id){
8567 return true;
8568 }else{
8569 return false;
8574 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
8576 function get_site_identifier() {
8577 global $CFG;
8578 // Check to see if it is missing. If so, initialise it.
8579 if (empty($CFG->siteidentifier)) {
8580 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
8582 // Return it.
8583 return $CFG->siteidentifier;
8586 // vim:autoindent:expandtab:shiftwidth=4:tabstop=4:tw=140: