MDL-24803 fixed tagid typo in tag correlation table update
[moodle.git] / lib / moodlelib.php
blob19bfa16f1c1f132b21058a306ad97bb80dd473dd
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 preg_replace('/[^a-zA-Z]/i', '', $param);
423 case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
424 return preg_replace('/[^A-Za-z0-9]/i', '', $param);
426 case PARAM_ALPHAEXT: // Remove everything not a-zA-Z/_-
427 return preg_replace('/[^a-zA-Z\/_-]/i', '', $param);
429 case PARAM_SEQUENCE: // Remove everything not 0-9,
430 return preg_replace('/[^0-9,]/i', '', $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 preg_replace('/[^a-zA-Z0-9_-]/i', '', $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 = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
457 $param = preg_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 = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
466 $param = preg_replace('~\.\.+~', '', $param);
467 $param = preg_replace('~//+~', '/', $param);
468 return preg_replace('~/(\./)+~', '/', $param);
470 case PARAM_HOST: // allow FQDN or IPv4 dotted quad
471 $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
472 // match ipv4 dotted quad
473 if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
474 // confirm values are ok
475 if ( $match[0] > 255
476 || $match[1] > 255
477 || $match[3] > 255
478 || $match[4] > 255 ) {
479 // hmmm, what kind of dotted quad is this?
480 $param = '';
482 } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
483 && !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
484 && !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
486 // all is ok - $param is respected
487 } else {
488 // all is not ok...
489 $param='';
491 return $param;
493 case PARAM_URL: // allow safe ftp, http, mailto urls
494 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
495 if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
496 // all is ok, param is respected
497 } else {
498 $param =''; // not really ok
500 return $param;
502 case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
503 $param = clean_param($param, PARAM_URL);
504 if (!empty($param)) {
505 if (preg_match(':^/:', $param)) {
506 // root-relative, ok!
507 } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
508 // absolute, and matches our wwwroot
509 } else {
510 // relative - let's make sure there are no tricks
511 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
512 // looks ok.
513 } else {
514 $param = '';
518 return $param;
520 case PARAM_PEM:
521 $param = trim($param);
522 // PEM formatted strings may contain letters/numbers and the symbols
523 // forward slash: /
524 // plus sign: +
525 // equal sign: =
526 // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
527 if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
528 list($wholething, $body) = $matches;
529 unset($wholething, $matches);
530 $b64 = clean_param($body, PARAM_BASE64);
531 if (!empty($b64)) {
532 return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
533 } else {
534 return '';
537 return '';
539 case PARAM_BASE64:
540 if (!empty($param)) {
541 // PEM formatted strings may contain letters/numbers and the symbols
542 // forward slash: /
543 // plus sign: +
544 // equal sign: =
545 if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
546 return '';
548 $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
549 // Each line of base64 encoded data must be 64 characters in
550 // length, except for the last line which may be less than (or
551 // equal to) 64 characters long.
552 for ($i=0, $j=count($lines); $i < $j; $i++) {
553 if ($i + 1 == $j) {
554 if (64 < strlen($lines[$i])) {
555 return '';
557 continue;
560 if (64 != strlen($lines[$i])) {
561 return '';
564 return implode("\n",$lines);
565 } else {
566 return '';
569 case PARAM_TAG:
570 //as long as magic_quotes_gpc is used, a backslash will be a
571 //problem, so remove *all* backslash.
572 $param = str_replace('\\', '', $param);
573 //convert many whitespace chars into one
574 $param = preg_replace('/\s+/', ' ', $param);
575 $textlib = textlib_get_instance();
576 $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
577 return $param;
580 case PARAM_TAGLIST:
581 $tags = explode(',', $param);
582 $result = array();
583 foreach ($tags as $tag) {
584 $res = clean_param($tag, PARAM_TAG);
585 if ($res != '') {
586 $result[] = $res;
589 if ($result) {
590 return implode(',', $result);
591 } else {
592 return '';
595 default: // throw error, switched parameters in optional_param or another serious problem
596 error("Unknown parameter type: $type");
601 * Return true if given value is integer or string with integer value
603 * @param mixed $value String or Int
604 * @return bool true if number, false if not
606 function is_number($value) {
607 if (is_int($value)) {
608 return true;
609 } else if (is_string($value)) {
610 return ((string)(int)$value) === $value;
611 } else {
612 return false;
617 * This function is useful for testing whether something you got back from
618 * the HTML editor actually contains anything. Sometimes the HTML editor
619 * appear to be empty, but actually you get back a <br> tag or something.
621 * @param string $string a string containing HTML.
622 * @return boolean does the string contain any actual content - that is text,
623 * images, objcts, etc.
625 function html_is_blank($string) {
626 return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
630 * Set a key in global configuration
632 * Set a key/value pair in both this session's {@link $CFG} global variable
633 * and in the 'config' database table for future sessions.
635 * Can also be used to update keys for plugin-scoped configs in config_plugin table.
636 * In that case it doesn't affect $CFG.
638 * A NULL value will delete the entry.
640 * @param string $name the key to set
641 * @param string $value the value to set (without magic quotes)
642 * @param string $plugin (optional) the plugin scope
643 * @uses $CFG
644 * @return bool
646 function set_config($name, $value, $plugin=NULL) {
647 /// No need for get_config because they are usually always available in $CFG
649 global $CFG;
651 if (empty($plugin)) {
652 if (!array_key_exists($name, $CFG->config_php_settings)) {
653 // So it's defined for this invocation at least
654 if (is_null($value)) {
655 unset($CFG->$name);
656 } else {
657 $CFG->$name = (string)$value; // settings from db are always strings
661 if (get_field('config', 'name', 'name', $name)) {
662 if ($value===null) {
663 return delete_records('config', 'name', $name);
664 } else {
665 return set_field('config', 'value', addslashes($value), 'name', $name);
667 } else {
668 if ($value===null) {
669 return true;
671 $config = new object();
672 $config->name = $name;
673 $config->value = addslashes($value);
674 return insert_record('config', $config);
676 } else { // plugin scope
677 if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
678 if ($value===null) {
679 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
680 } else {
681 return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
683 } else {
684 if ($value===null) {
685 return true;
687 $config = new object();
688 $config->plugin = addslashes($plugin);
689 $config->name = $name;
690 $config->value = addslashes($value);
691 return insert_record('config_plugins', $config);
697 * Get configuration values from the global config table
698 * or the config_plugins table.
700 * If called with no parameters it will do the right thing
701 * generating $CFG safely from the database without overwriting
702 * existing values.
704 * If called with 2 parameters it will return a $string single
705 * value or false of the value is not found.
707 * @param string $plugin
708 * @param string $name
709 * @uses $CFG
710 * @return hash-like object or single value
713 function get_config($plugin=NULL, $name=NULL) {
715 global $CFG;
717 if (!empty($name)) { // the user is asking for a specific value
718 if (!empty($plugin)) {
719 return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
720 } else {
721 return get_field('config', 'value', 'name', $name);
725 // the user is after a recordset
726 if (!empty($plugin)) {
727 if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
728 $configs = (array)$configs;
729 $localcfg = array();
730 foreach ($configs as $config) {
731 $localcfg[$config->name] = $config->value;
733 return (object)$localcfg;
734 } else {
735 return false;
737 } else {
738 // this was originally in setup.php
739 if ($configs = get_records('config')) {
740 $localcfg = (array)$CFG;
741 foreach ($configs as $config) {
742 if (!isset($localcfg[$config->name])) {
743 $localcfg[$config->name] = $config->value;
745 // do not complain anymore if config.php overrides settings from db
748 $localcfg = (object)$localcfg;
749 return $localcfg;
750 } else {
751 // preserve $CFG if DB returns nothing or error
752 return $CFG;
759 * Removes a key from global configuration
761 * @param string $name the key to set
762 * @param string $plugin (optional) the plugin scope
763 * @uses $CFG
764 * @return bool
766 function unset_config($name, $plugin=NULL) {
768 global $CFG;
770 unset($CFG->$name);
772 if (empty($plugin)) {
773 return delete_records('config', 'name', $name);
774 } else {
775 return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
780 * Get volatile flags
782 * @param string $type
783 * @param int $changedsince
784 * @return records array
787 function get_cache_flags($type, $changedsince=NULL) {
789 $type = addslashes($type);
791 $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
792 if ($changedsince !== NULL) {
793 $changedsince = (int)$changedsince;
794 $sqlwhere .= ' AND timemodified > ' . $changedsince;
796 $cf = array();
797 if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
798 foreach ($flags as $flag) {
799 $cf[$flag->name] = $flag->value;
802 return $cf;
806 * Use this funciton to get a list of users from a config setting of type admin_setting_users_with_capability.
807 * @param string $value the value of the config setting.
808 * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
809 * @return array of user objects.
811 function get_users_from_config($value, $capability) {
812 global $CFG;
813 if ($value == '$@ALL@$') {
814 $users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
815 } else if ($value) {
816 $usernames = explode(',', $value);
817 $users = get_records_select('user', "username IN ('" . implode("','", $usernames) . "') AND mnethostid = " . $CFG->mnet_localhost_id);
818 } else {
819 $users = array();
821 return $users;
825 * Get volatile flags
827 * @param string $type
828 * @param string $name
829 * @param int $changedsince
830 * @return records array
833 function get_cache_flag($type, $name, $changedsince=NULL) {
835 $type = addslashes($type);
836 $name = addslashes($name);
838 $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
839 if ($changedsince !== NULL) {
840 $changedsince = (int)$changedsince;
841 $sqlwhere .= ' AND timemodified > ' . $changedsince;
843 return get_field_select('cache_flags', 'value', $sqlwhere);
847 * Set a volatile flag
849 * @param string $type the "type" namespace for the key
850 * @param string $name the key to set
851 * @param string $value the value to set (without magic quotes) - NULL will remove the flag
852 * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
853 * @return bool
855 function set_cache_flag($type, $name, $value, $expiry=NULL) {
858 $timemodified = time();
859 if ($expiry===NULL || $expiry < $timemodified) {
860 $expiry = $timemodified + 24 * 60 * 60;
861 } else {
862 $expiry = (int)$expiry;
865 if ($value === NULL) {
866 return unset_cache_flag($type,$name);
869 $type = addslashes($type);
870 $name = addslashes($name);
871 if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
872 if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
873 return true; //no need to update; helps rcache too
875 $f->value = addslashes($value);
876 $f->expiry = $expiry;
877 $f->timemodified = $timemodified;
878 return update_record('cache_flags', $f);
879 } else {
880 $f = new object();
881 $f->flagtype = $type;
882 $f->name = $name;
883 $f->value = addslashes($value);
884 $f->expiry = $expiry;
885 $f->timemodified = $timemodified;
886 return (bool)insert_record('cache_flags', $f);
891 * Removes a single volatile flag
893 * @param string $type the "type" namespace for the key
894 * @param string $name the key to set
895 * @uses $CFG
896 * @return bool
898 function unset_cache_flag($type, $name) {
900 return delete_records('cache_flags',
901 'name', addslashes($name),
902 'flagtype', addslashes($type));
906 * Garbage-collect volatile flags
909 function gc_cache_flags() {
910 return delete_records_select('cache_flags', 'expiry < ' . time());
914 * Refresh current $USER session global variable with all their current preferences.
915 * @uses $USER
917 function reload_user_preferences() {
919 global $USER;
921 //reset preference
922 $USER->preference = array();
924 if (!isloggedin() or isguestuser()) {
925 // no permanent storage for not-logged-in user and guest
927 } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
928 foreach ($preferences as $preference) {
929 $USER->preference[$preference->name] = $preference->value;
933 return true;
937 * Sets a preference for the current user
938 * Optionally, can set a preference for a different user object
939 * @uses $USER
940 * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
942 * @param string $name The key to set as preference for the specified user
943 * @param string $value The value to set forthe $name key in the specified user's record
944 * @param int $otheruserid A moodle user ID
945 * @return bool
947 function set_user_preference($name, $value, $otheruserid=NULL) {
949 global $USER;
951 if (!isset($USER->preference)) {
952 reload_user_preferences();
955 if (empty($name)) {
956 return false;
959 $nostore = false;
961 if (empty($otheruserid)){
962 if (!isloggedin() or isguestuser()) {
963 $nostore = true;
965 $userid = $USER->id;
966 } else {
967 if (isguestuser($otheruserid)) {
968 $nostore = true;
970 $userid = $otheruserid;
973 $return = true;
974 if ($nostore) {
975 // no permanent storage for not-logged-in user and guest
977 } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
978 if ($preference->value === $value) {
979 return true;
981 if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
982 $return = false;
985 } else {
986 $preference = new object();
987 $preference->userid = $userid;
988 $preference->name = addslashes($name);
989 $preference->value = addslashes((string)$value);
990 if (!insert_record('user_preferences', $preference)) {
991 $return = false;
995 // update value in USER session if needed
996 if ($userid == $USER->id) {
997 $USER->preference[$name] = (string)$value;
1000 return $return;
1004 * Unsets a preference completely by deleting it from the database
1005 * Optionally, can set a preference for a different user id
1006 * @uses $USER
1007 * @param string $name The key to unset as preference for the specified user
1008 * @param int $otheruserid A moodle user ID
1010 function unset_user_preference($name, $otheruserid=NULL) {
1012 global $USER;
1014 if (!isset($USER->preference)) {
1015 reload_user_preferences();
1018 if (empty($otheruserid)){
1019 $userid = $USER->id;
1020 } else {
1021 $userid = $otheruserid;
1024 //Delete the preference from $USER if needed
1025 if ($userid == $USER->id) {
1026 unset($USER->preference[$name]);
1029 //Then from DB
1030 return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
1035 * Sets a whole array of preferences for the current user
1036 * @param array $prefarray An array of key/value pairs to be set
1037 * @param int $otheruserid A moodle user ID
1038 * @return bool
1040 function set_user_preferences($prefarray, $otheruserid=NULL) {
1042 if (!is_array($prefarray) or empty($prefarray)) {
1043 return false;
1046 $return = true;
1047 foreach ($prefarray as $name => $value) {
1048 // The order is important; test for return is done first
1049 $return = (set_user_preference($name, $value, $otheruserid) && $return);
1051 return $return;
1055 * If no arguments are supplied this function will return
1056 * all of the current user preferences as an array.
1057 * If a name is specified then this function
1058 * attempts to return that particular preference value. If
1059 * none is found, then the optional value $default is returned,
1060 * otherwise NULL.
1061 * @param string $name Name of the key to use in finding a preference value
1062 * @param string $default Value to be returned if the $name key is not set in the user preferences
1063 * @param int $otheruserid A moodle user ID
1064 * @uses $USER
1065 * @return string
1067 function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
1068 global $USER;
1070 if (!isset($USER->preference)) {
1071 reload_user_preferences();
1074 if (empty($otheruserid)){
1075 $userid = $USER->id;
1076 } else {
1077 $userid = $otheruserid;
1080 if ($userid == $USER->id) {
1081 $preference = $USER->preference;
1083 } else {
1084 $preference = array();
1085 if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
1086 foreach ($prefdata as $pref) {
1087 $preference[$pref->name] = $pref->value;
1092 if (empty($name)) {
1093 return $preference; // All values
1095 } else if (array_key_exists($name, $preference)) {
1096 return $preference[$name]; // The single value
1098 } else {
1099 return $default; // Default value (or NULL)
1104 /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1107 * Given date parts in user time produce a GMT timestamp.
1109 * @param int $year The year part to create timestamp of
1110 * @param int $month The month part to create timestamp of
1111 * @param int $day The day part to create timestamp of
1112 * @param int $hour The hour part to create timestamp of
1113 * @param int $minute The minute part to create timestamp of
1114 * @param int $second The second part to create timestamp of
1115 * @param float $timezone ?
1116 * @param bool $applydst ?
1117 * @return int timestamp
1118 * @todo Finish documenting this function
1120 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1122 $strtimezone = NULL;
1123 if (!is_numeric($timezone)) {
1124 $strtimezone = $timezone;
1127 $timezone = get_user_timezone_offset($timezone);
1129 if (abs($timezone) > 13) {
1130 $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1131 } else {
1132 $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1133 $time = usertime($time, $timezone);
1134 if($applydst) {
1135 $time -= dst_offset_on($time, $strtimezone);
1139 return $time;
1144 * Given an amount of time in seconds, returns string
1145 * formatted nicely as weeks, days, hours etc as needed
1147 * @uses MINSECS
1148 * @uses HOURSECS
1149 * @uses DAYSECS
1150 * @uses YEARSECS
1151 * @param int $totalsecs ?
1152 * @param array $str ?
1153 * @return string
1155 function format_time($totalsecs, $str=NULL) {
1157 $totalsecs = abs($totalsecs);
1159 if (!$str) { // Create the str structure the slow way
1160 $str->day = get_string('day');
1161 $str->days = get_string('days');
1162 $str->hour = get_string('hour');
1163 $str->hours = get_string('hours');
1164 $str->min = get_string('min');
1165 $str->mins = get_string('mins');
1166 $str->sec = get_string('sec');
1167 $str->secs = get_string('secs');
1168 $str->year = get_string('year');
1169 $str->years = get_string('years');
1173 $years = floor($totalsecs/YEARSECS);
1174 $remainder = $totalsecs - ($years*YEARSECS);
1175 $days = floor($remainder/DAYSECS);
1176 $remainder = $totalsecs - ($days*DAYSECS);
1177 $hours = floor($remainder/HOURSECS);
1178 $remainder = $remainder - ($hours*HOURSECS);
1179 $mins = floor($remainder/MINSECS);
1180 $secs = $remainder - ($mins*MINSECS);
1182 $ss = ($secs == 1) ? $str->sec : $str->secs;
1183 $sm = ($mins == 1) ? $str->min : $str->mins;
1184 $sh = ($hours == 1) ? $str->hour : $str->hours;
1185 $sd = ($days == 1) ? $str->day : $str->days;
1186 $sy = ($years == 1) ? $str->year : $str->years;
1188 $oyears = '';
1189 $odays = '';
1190 $ohours = '';
1191 $omins = '';
1192 $osecs = '';
1194 if ($years) $oyears = $years .' '. $sy;
1195 if ($days) $odays = $days .' '. $sd;
1196 if ($hours) $ohours = $hours .' '. $sh;
1197 if ($mins) $omins = $mins .' '. $sm;
1198 if ($secs) $osecs = $secs .' '. $ss;
1200 if ($years) return trim($oyears .' '. $odays);
1201 if ($days) return trim($odays .' '. $ohours);
1202 if ($hours) return trim($ohours .' '. $omins);
1203 if ($mins) return trim($omins .' '. $osecs);
1204 if ($secs) return $osecs;
1205 return get_string('now');
1209 * Returns a formatted string that represents a date in user time
1210 * <b>WARNING: note that the format is for strftime(), not date().</b>
1211 * Because of a bug in most Windows time libraries, we can't use
1212 * the nicer %e, so we have to use %d which has leading zeroes.
1213 * A lot of the fuss in the function is just getting rid of these leading
1214 * zeroes as efficiently as possible.
1216 * If parameter fixday = true (default), then take off leading
1217 * zero from %d, else mantain it.
1219 * @uses HOURSECS
1220 * @param int $date timestamp in GMT
1221 * @param string $format strftime format
1222 * @param float $timezone
1223 * @param bool $fixday If true (default) then the leading
1224 * zero from %d is removed. If false then the leading zero is mantained.
1225 * @return string
1227 function userdate($date, $format='', $timezone=99, $fixday = true) {
1229 global $CFG;
1231 $strtimezone = NULL;
1232 if (!is_numeric($timezone)) {
1233 $strtimezone = $timezone;
1236 if (empty($format)) {
1237 $format = get_string('strftimedaydatetime');
1240 if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
1241 $fixday = false;
1242 } else if ($fixday) {
1243 $formatnoday = str_replace('%d', 'DD', $format);
1244 $fixday = ($formatnoday != $format);
1247 $date += dst_offset_on($date, $strtimezone);
1249 $timezone = get_user_timezone_offset($timezone);
1251 if (abs($timezone) > 13) { /// Server time
1252 if ($fixday) {
1253 $datestring = strftime($formatnoday, $date);
1254 $daystring = str_replace(' 0', '', strftime(' %d', $date));
1255 $datestring = str_replace('DD', $daystring, $datestring);
1256 } else {
1257 $datestring = strftime($format, $date);
1259 } else {
1260 $date += (int)($timezone * 3600);
1261 if ($fixday) {
1262 $datestring = gmstrftime($formatnoday, $date);
1263 $daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
1264 $datestring = str_replace('DD', $daystring, $datestring);
1265 } else {
1266 $datestring = gmstrftime($format, $date);
1270 /// If we are running under Windows convert from windows encoding to UTF-8
1271 /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1273 if ($CFG->ostype == 'WINDOWS') {
1274 if ($localewincharset = get_string('localewincharset')) {
1275 $textlib = textlib_get_instance();
1276 $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1280 return $datestring;
1284 * Given a $time timestamp in GMT (seconds since epoch),
1285 * returns an array that represents the date in user time
1287 * @uses HOURSECS
1288 * @param int $time Timestamp in GMT
1289 * @param float $timezone ?
1290 * @return array An array that represents the date in user time
1291 * @todo Finish documenting this function
1293 function usergetdate($time, $timezone=99) {
1295 $strtimezone = NULL;
1296 if (!is_numeric($timezone)) {
1297 $strtimezone = $timezone;
1300 $timezone = get_user_timezone_offset($timezone);
1302 if (abs($timezone) > 13) { // Server time
1303 return getdate($time);
1306 // There is no gmgetdate so we use gmdate instead
1307 $time += dst_offset_on($time, $strtimezone);
1308 $time += intval((float)$timezone * HOURSECS);
1310 $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
1312 //be careful to ensure the returned array matches that produced by getdate() above
1313 list(
1314 $getdate['month'],
1315 $getdate['weekday'],
1316 $getdate['yday'],
1317 $getdate['year'],
1318 $getdate['mon'],
1319 $getdate['wday'],
1320 $getdate['mday'],
1321 $getdate['hours'],
1322 $getdate['minutes'],
1323 $getdate['seconds']
1324 ) = explode('_', $datestring);
1326 return $getdate;
1330 * Given a GMT timestamp (seconds since epoch), offsets it by
1331 * the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1333 * @uses HOURSECS
1334 * @param int $date Timestamp in GMT
1335 * @param float $timezone
1336 * @return int
1338 function usertime($date, $timezone=99) {
1340 $timezone = get_user_timezone_offset($timezone);
1342 if (abs($timezone) > 13) {
1343 return $date;
1345 return $date - (int)($timezone * HOURSECS);
1349 * Given a time, return the GMT timestamp of the most recent midnight
1350 * for the current user.
1352 * @param int $date Timestamp in GMT
1353 * @param float $timezone ?
1354 * @return ?
1356 function usergetmidnight($date, $timezone=99) {
1358 $userdate = usergetdate($date, $timezone);
1360 // Time of midnight of this user's day, in GMT
1361 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1366 * Returns a string that prints the user's timezone
1368 * @param float $timezone The user's timezone
1369 * @return string
1371 function usertimezone($timezone=99) {
1373 $tz = get_user_timezone($timezone);
1375 if (!is_float($tz)) {
1376 return $tz;
1379 if(abs($tz) > 13) { // Server time
1380 return get_string('serverlocaltime');
1383 if($tz == intval($tz)) {
1384 // Don't show .0 for whole hours
1385 $tz = intval($tz);
1388 if($tz == 0) {
1389 return 'UTC';
1391 else if($tz > 0) {
1392 return 'UTC+'.$tz;
1394 else {
1395 return 'UTC'.$tz;
1401 * Returns a float which represents the user's timezone difference from GMT in hours
1402 * Checks various settings and picks the most dominant of those which have a value
1404 * @uses $CFG
1405 * @uses $USER
1406 * @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
1407 * @return int
1409 function get_user_timezone_offset($tz = 99) {
1411 global $USER, $CFG;
1413 $tz = get_user_timezone($tz);
1415 if (is_float($tz)) {
1416 return $tz;
1417 } else {
1418 $tzrecord = get_timezone_record($tz);
1419 if (empty($tzrecord)) {
1420 return 99.0;
1422 return (float)$tzrecord->gmtoff / HOURMINS;
1427 * Returns an int which represents the systems's timezone difference from GMT in seconds
1428 * @param mixed $tz timezone
1429 * @return int if found, false is timezone 99 or error
1431 function get_timezone_offset($tz) {
1432 global $CFG;
1434 if ($tz == 99) {
1435 return false;
1438 if (is_numeric($tz)) {
1439 return intval($tz * 60*60);
1442 if (!$tzrecord = get_timezone_record($tz)) {
1443 return false;
1445 return intval($tzrecord->gmtoff * 60);
1449 * Returns a float or a string which denotes the user's timezone
1450 * 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)
1451 * means that for this timezone there are also DST rules to be taken into account
1452 * Checks various settings and picks the most dominant of those which have a value
1454 * @uses $USER
1455 * @uses $CFG
1456 * @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
1457 * @return mixed
1459 function get_user_timezone($tz = 99) {
1460 global $USER, $CFG;
1462 $timezones = array(
1463 $tz,
1464 isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1465 isset($USER->timezone) ? $USER->timezone : 99,
1466 isset($CFG->timezone) ? $CFG->timezone : 99,
1469 $tz = 99;
1471 while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
1472 $tz = $next['value'];
1475 return is_numeric($tz) ? (float) $tz : $tz;
1481 * @uses $CFG
1482 * @uses $db
1483 * @param string $timezonename ?
1484 * @return object
1486 function get_timezone_record($timezonename) {
1487 global $CFG, $db;
1488 static $cache = NULL;
1490 if ($cache === NULL) {
1491 $cache = array();
1494 if (isset($cache[$timezonename])) {
1495 return $cache[$timezonename];
1498 return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1499 WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1505 * @uses $CFG
1506 * @uses $USER
1507 * @param ? $fromyear ?
1508 * @param ? $to_year ?
1509 * @return bool
1511 function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1512 global $CFG, $SESSION;
1514 $usertz = get_user_timezone($strtimezone);
1516 if (is_float($usertz)) {
1517 // Trivial timezone, no DST
1518 return false;
1521 if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1522 // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1523 unset($SESSION->dst_offsets);
1524 unset($SESSION->dst_range);
1527 if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1528 // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1529 // This will be the return path most of the time, pretty light computationally
1530 return true;
1533 // Reaching here means we either need to extend our table or create it from scratch
1535 // Remember which TZ we calculated these changes for
1536 $SESSION->dst_offsettz = $usertz;
1538 if(empty($SESSION->dst_offsets)) {
1539 // If we 're creating from scratch, put the two guard elements in there
1540 $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1542 if(empty($SESSION->dst_range)) {
1543 // If creating from scratch
1544 $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1545 $to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
1547 // Fill in the array with the extra years we need to process
1548 $yearstoprocess = array();
1549 for($i = $from; $i <= $to; ++$i) {
1550 $yearstoprocess[] = $i;
1553 // Take note of which years we have processed for future calls
1554 $SESSION->dst_range = array($from, $to);
1556 else {
1557 // If needing to extend the table, do the same
1558 $yearstoprocess = array();
1560 $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1561 $to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
1563 if($from < $SESSION->dst_range[0]) {
1564 // Take note of which years we need to process and then note that we have processed them for future calls
1565 for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1566 $yearstoprocess[] = $i;
1568 $SESSION->dst_range[0] = $from;
1570 if($to > $SESSION->dst_range[1]) {
1571 // Take note of which years we need to process and then note that we have processed them for future calls
1572 for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1573 $yearstoprocess[] = $i;
1575 $SESSION->dst_range[1] = $to;
1579 if(empty($yearstoprocess)) {
1580 // This means that there was a call requesting a SMALLER range than we have already calculated
1581 return true;
1584 // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1585 // Also, the array is sorted in descending timestamp order!
1587 // Get DB data
1589 static $presets_cache = array();
1590 if (!isset($presets_cache[$usertz])) {
1591 $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');
1593 if(empty($presets_cache[$usertz])) {
1594 return false;
1597 // Remove ending guard (first element of the array)
1598 reset($SESSION->dst_offsets);
1599 unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1601 // Add all required change timestamps
1602 foreach($yearstoprocess as $y) {
1603 // Find the record which is in effect for the year $y
1604 foreach($presets_cache[$usertz] as $year => $preset) {
1605 if($year <= $y) {
1606 break;
1610 $changes = dst_changes_for_year($y, $preset);
1612 if($changes === NULL) {
1613 continue;
1615 if($changes['dst'] != 0) {
1616 $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1618 if($changes['std'] != 0) {
1619 $SESSION->dst_offsets[$changes['std']] = 0;
1623 // Put in a guard element at the top
1624 $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1625 $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1627 // Sort again
1628 krsort($SESSION->dst_offsets);
1630 return true;
1633 function dst_changes_for_year($year, $timezone) {
1635 if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1636 return NULL;
1639 $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1640 $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1642 list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1643 list($std_hour, $std_min) = explode(':', $timezone->std_time);
1645 $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1646 $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1648 // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1649 // This has the advantage of being able to have negative values for hour, i.e. for timezones
1650 // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1652 $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1653 $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1655 return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1658 // $time must NOT be compensated at all, it has to be a pure timestamp
1659 function dst_offset_on($time, $strtimezone = NULL) {
1660 global $SESSION;
1662 if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
1663 return 0;
1666 reset($SESSION->dst_offsets);
1667 while(list($from, $offset) = each($SESSION->dst_offsets)) {
1668 if($from <= $time) {
1669 break;
1673 // This is the normal return path
1674 if($offset !== NULL) {
1675 return $offset;
1678 // Reaching this point means we haven't calculated far enough, do it now:
1679 // Calculate extra DST changes if needed and recurse. The recursion always
1680 // moves toward the stopping condition, so will always end.
1682 if($from == 0) {
1683 // We need a year smaller than $SESSION->dst_range[0]
1684 if($SESSION->dst_range[0] == 1971) {
1685 return 0;
1687 calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
1688 return dst_offset_on($time, $strtimezone);
1690 else {
1691 // We need a year larger than $SESSION->dst_range[1]
1692 if($SESSION->dst_range[1] == 2035) {
1693 return 0;
1695 calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
1696 return dst_offset_on($time, $strtimezone);
1700 function find_day_in_month($startday, $weekday, $month, $year) {
1702 $daysinmonth = days_in_month($month, $year);
1704 if($weekday == -1) {
1705 // Don't care about weekday, so return:
1706 // abs($startday) if $startday != -1
1707 // $daysinmonth otherwise
1708 return ($startday == -1) ? $daysinmonth : abs($startday);
1711 // From now on we 're looking for a specific weekday
1713 // Give "end of month" its actual value, since we know it
1714 if($startday == -1) {
1715 $startday = -1 * $daysinmonth;
1718 // Starting from day $startday, the sign is the direction
1720 if($startday < 1) {
1722 $startday = abs($startday);
1723 $lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1725 // This is the last such weekday of the month
1726 $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1727 if($lastinmonth > $daysinmonth) {
1728 $lastinmonth -= 7;
1731 // Find the first such weekday <= $startday
1732 while($lastinmonth > $startday) {
1733 $lastinmonth -= 7;
1736 return $lastinmonth;
1739 else {
1741 $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1743 $diff = $weekday - $indexweekday;
1744 if($diff < 0) {
1745 $diff += 7;
1748 // This is the first such weekday of the month equal to or after $startday
1749 $firstfromindex = $startday + $diff;
1751 return $firstfromindex;
1757 * Calculate the number of days in a given month
1759 * @param int $month The month whose day count is sought
1760 * @param int $year The year of the month whose day count is sought
1761 * @return int
1763 function days_in_month($month, $year) {
1764 return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1768 * Calculate the position in the week of a specific calendar day
1770 * @param int $day The day of the date whose position in the week is sought
1771 * @param int $month The month of the date whose position in the week is sought
1772 * @param int $year The year of the date whose position in the week is sought
1773 * @return int
1775 function dayofweek($day, $month, $year) {
1776 // I wonder if this is any different from
1777 // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1778 return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1781 /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1784 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1785 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1786 * sesskey string if $USER exists, or boolean false if not.
1788 * @uses $USER
1789 * @return string
1791 function sesskey() {
1792 global $USER;
1794 if(!isset($USER)) {
1795 return false;
1798 if (empty($USER->sesskey)) {
1799 $USER->sesskey = random_string(10);
1802 return $USER->sesskey;
1807 * For security purposes, this function will check that the currently
1808 * given sesskey (passed as a parameter to the script or this function)
1809 * matches that of the current user.
1811 * @param string $sesskey optionally provided sesskey
1812 * @return bool
1814 function confirm_sesskey($sesskey=NULL) {
1815 global $USER;
1817 if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1818 return true;
1821 if (empty($sesskey)) {
1822 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
1825 if (!isset($USER->sesskey)) {
1826 return false;
1829 return ($USER->sesskey === $sesskey);
1833 * Check the session key using {@link confirm_sesskey()},
1834 * and cause a fatal error if it does not match.
1836 function require_sesskey() {
1837 if (!confirm_sesskey()) {
1838 print_error('invalidsesskey');
1843 * Setup all global $CFG course variables, set locale and also themes
1844 * This function can be used on pages that do not require login instead of require_login()
1846 * @param mixed $courseorid id of the course or course object
1848 function course_setup($courseorid=0) {
1849 global $COURSE, $CFG, $SITE;
1851 /// Redefine global $COURSE if needed
1852 if (empty($courseorid)) {
1853 // no change in global $COURSE - for backwards compatibiltiy
1854 // if require_rogin() used after require_login($courseid);
1855 } else if (is_object($courseorid)) {
1856 $COURSE = clone($courseorid);
1857 } else {
1858 global $course; // used here only to prevent repeated fetching from DB - may be removed later
1859 if ($courseorid == SITEID) {
1860 $COURSE = clone($SITE);
1861 } else if (!empty($course->id) and $course->id == $courseorid) {
1862 $COURSE = clone($course);
1863 } else {
1864 if (!$COURSE = get_record('course', 'id', $courseorid)) {
1865 error('Invalid course ID');
1870 /// set locale and themes
1871 moodle_setlocale();
1872 theme_setup();
1877 * This function checks that the current user is logged in and has the
1878 * required privileges
1880 * This function checks that the current user is logged in, and optionally
1881 * whether they are allowed to be in a particular course and view a particular
1882 * course module.
1883 * If they are not logged in, then it redirects them to the site login unless
1884 * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1885 * case they are automatically logged in as guests.
1886 * If $courseid is given and the user is not enrolled in that course then the
1887 * user is redirected to the course enrolment page.
1888 * If $cm is given and the coursemodule is hidden and the user is not a teacher
1889 * in the course then the user is redirected to the course home page.
1891 * @uses $CFG
1892 * @uses $SESSION
1893 * @uses $USER
1894 * @uses $FULLME
1895 * @uses SITEID
1896 * @uses $COURSE
1897 * @param mixed $courseorid id of the course or course object
1898 * @param bool $autologinguest
1899 * @param object $cm course module object
1900 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
1901 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
1902 * in order to keep redirects working properly. MDL-14495
1904 function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
1906 global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1908 /// setup global $COURSE, themes, language and locale
1909 course_setup($courseorid);
1911 /// If the user is not even logged in yet then make sure they are
1912 if (!isloggedin()) {
1913 //NOTE: $USER->site check was obsoleted by session test cookie,
1914 // $USER->confirmed test is in login/index.php
1915 if ($setwantsurltome) {
1916 $SESSION->wantsurl = $FULLME;
1918 if (!empty($_SERVER['HTTP_REFERER'])) {
1919 $SESSION->fromurl = $_SERVER['HTTP_REFERER'];
1921 if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1922 $loginguest = '?loginguest=true';
1923 } else {
1924 $loginguest = '';
1926 if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
1927 redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1928 } else {
1929 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1930 redirect($wwwroot .'/login/index.php');
1932 exit;
1935 /// loginas as redirection if needed
1936 if ($COURSE->id != SITEID and !empty($USER->realuser)) {
1937 if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
1938 if ($USER->loginascontext->instanceid != $COURSE->id) {
1939 print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
1944 /// check whether the user should be changing password (but only if it is REALLY them)
1945 if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
1946 $userauth = get_auth_plugin($USER->auth);
1947 if ($userauth->can_change_password()) {
1948 $SESSION->wantsurl = $FULLME;
1949 if ($changeurl = $userauth->change_password_url()) {
1950 //use plugin custom url
1951 redirect($changeurl);
1952 } else {
1953 //use moodle internal method
1954 if (empty($CFG->loginhttps)) {
1955 redirect($CFG->wwwroot .'/login/change_password.php');
1956 } else {
1957 $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1958 redirect($wwwroot .'/login/change_password.php');
1961 } else {
1962 print_error('nopasswordchangeforced', 'auth');
1966 /// Check that the user account is properly set up
1967 if (user_not_fully_set_up($USER)) {
1968 $SESSION->wantsurl = $FULLME;
1969 redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1972 /// Make sure current IP matches the one for this session (if required)
1973 if (!empty($CFG->tracksessionip)) {
1974 if ($USER->sessionIP != md5(getremoteaddr())) {
1975 print_error('sessionipnomatch', 'error');
1979 /// Make sure the USER has a sesskey set up. Used for checking script parameters.
1980 sesskey();
1982 // Check that the user has agreed to a site policy if there is one
1983 if (!empty($CFG->sitepolicy)) {
1984 if (!$USER->policyagreed) {
1985 $SESSION->wantsurl = $FULLME;
1986 redirect($CFG->wwwroot .'/user/policy.php');
1990 // Fetch the system context, we are going to use it a lot.
1991 $sysctx = get_context_instance(CONTEXT_SYSTEM);
1993 /// If the site is currently under maintenance, then print a message
1994 if (!has_capability('moodle/site:config', $sysctx)) {
1995 if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1996 print_maintenance_message();
1997 exit;
2001 /// groupmembersonly access control
2002 if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2003 if (isguestuser() or !groups_has_membership($cm)) {
2004 print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
2008 // Fetch the course context, and prefetch its child contexts
2009 if (!isset($COURSE->context)) {
2010 if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
2011 print_error('nocontext');
2014 if (!empty($cm) && !isset($cm->context)) {
2015 if ( ! $cm->context = get_context_instance(CONTEXT_MODULE, $cm->id) ) {
2016 print_error('nocontext');
2019 if ($COURSE->id == SITEID) {
2020 /// Eliminate hidden site activities straight away
2021 if (!empty($cm) && !$cm->visible
2022 && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2023 redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
2025 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2026 return;
2028 } else {
2030 /// Check if the user can be in a particular course
2031 if (empty($USER->access['rsw'][$COURSE->context->path])) {
2033 // MDL-13900 - If the course or the parent category are hidden
2034 // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
2036 if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
2037 !has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
2038 print_header_simple();
2039 notice(get_string('coursehidden'), $CFG->wwwroot .'/');
2043 /// Non-guests who don't currently have access, check if they can be allowed in as a guest
2045 if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
2046 if ($COURSE->guest == 1) {
2047 // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
2048 $USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
2052 /// If the user is a guest then treat them according to the course policy about guests
2054 if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
2055 if (has_capability('moodle/site:doanything', $sysctx)) {
2056 // administrators must be able to access any course - even if somebody gives them guest access
2057 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2058 return;
2061 switch ($COURSE->guest) { /// Check course policy about guest access
2063 case 1: /// Guests always allowed
2064 if (!has_capability('moodle/course:view', $COURSE->context)) { // Prohibited by capability
2065 print_header_simple();
2066 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2068 if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
2069 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
2070 get_string('activityiscurrentlyhidden'));
2073 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2074 return; // User is allowed to see this course
2076 break;
2078 case 2: /// Guests allowed with key
2079 if (!empty($USER->enrolkey[$COURSE->id])) { // Set by enrol/manual/enrol.php
2080 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2081 return true;
2083 // otherwise drop through to logic below (--> enrol.php)
2084 break;
2086 default: /// Guests not allowed
2087 $strloggedinasguest = get_string('loggedinasguest');
2088 print_header_simple('', '',
2089 build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
2090 if (empty($USER->access['rsw'][$COURSE->context->path])) { // Normal guest
2091 $loginurl = "$CFG->wwwroot/login/index.php";
2092 if (!empty($CFG->loginhttps)) {
2093 $loginurl = str_replace('http:','https:', $loginurl);
2095 notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), $loginurl);
2096 } else {
2097 notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
2098 echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
2099 print_footer($COURSE);
2100 exit;
2102 break;
2105 /// For non-guests, check if they have course view access
2107 } else if (has_capability('moodle/course:view', $COURSE->context)) {
2108 if (!empty($USER->realuser)) { // Make sure the REAL person can also access this course
2109 if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
2110 print_header_simple();
2111 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2115 /// Make sure they can read this activity too, if specified
2117 if (!empty($cm) && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
2118 redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
2120 user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2121 return; // User is allowed to see this course
2126 /// Currently not enrolled in the course, so see if they want to enrol
2127 $SESSION->wantsurl = $FULLME;
2128 redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
2129 die;
2136 * This function just makes sure a user is logged out.
2138 * @uses $CFG
2139 * @uses $USER
2141 function require_logout() {
2143 global $USER, $CFG, $SESSION;
2145 if (isloggedin()) {
2146 add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2148 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2149 foreach($authsequence as $authname) {
2150 $authplugin = get_auth_plugin($authname);
2151 $authplugin->prelogout_hook();
2155 if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
2156 // This method is just to try to avoid silly warnings from PHP 4.3.0
2157 session_unregister("USER");
2158 session_unregister("SESSION");
2161 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
2162 $file = $line = null;
2163 if (headers_sent($file, $line)) {
2164 error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
2165 error_log('Headers were already sent in file: '.$file.' on line '.$line);
2166 } else {
2167 if (check_php_version('5.2.0')) {
2168 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
2169 } else {
2170 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2174 unset($_SESSION['USER']);
2175 unset($_SESSION['SESSION']);
2177 unset($SESSION);
2178 unset($USER);
2183 * This is a weaker version of {@link require_login()} which only requires login
2184 * when called from within a course rather than the site page, unless
2185 * the forcelogin option is turned on.
2187 * @uses $CFG
2188 * @param mixed $courseorid The course object or id in question
2189 * @param bool $autologinguest Allow autologin guests if that is wanted
2190 * @param object $cm Course activity module if known
2191 * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2192 * true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2193 * in order to keep redirects working properly. MDL-14495
2195 function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
2196 global $CFG;
2197 if (!empty($CFG->forcelogin)) {
2198 // login required for both SITE and courses
2199 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2201 } else if (!empty($cm) and !$cm->visible) {
2202 // always login for hidden activities
2203 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2205 } else if ((is_object($courseorid) and $courseorid->id == SITEID)
2206 or (!is_object($courseorid) and $courseorid == SITEID)) {
2207 //login for SITE not required
2208 if ($cm and empty($cm->visible)) {
2209 // hidden activities are not accessible without login
2210 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2211 } else if ($cm and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
2212 // not-logged-in users do not have any group membership
2213 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2214 } else {
2215 user_accesstime_log(SITEID);
2216 return;
2219 } else {
2220 // course login always required
2221 require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2226 * Require key login. Function terminates with error if key not found or incorrect.
2227 * @param string $script unique script identifier
2228 * @param int $instance optional instance id
2230 function require_user_key_login($script, $instance=null) {
2231 global $nomoodlecookie, $USER, $SESSION, $CFG;
2233 if (empty($nomoodlecookie)) {
2234 error('Incorrect use of require_key_login() - session cookies must be disabled!');
2237 /// extra safety
2238 @session_write_close();
2240 $keyvalue = required_param('key', PARAM_ALPHANUM);
2242 if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
2243 error('Incorrect key');
2246 if (!empty($key->validuntil) and $key->validuntil < time()) {
2247 error('Expired key');
2250 if ($key->iprestriction) {
2251 $remoteaddr = getremoteaddr();
2252 if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
2253 error('Client IP address mismatch');
2257 if (!$user = get_record('user', 'id', $key->userid)) {
2258 error('Incorrect user record');
2261 /// emulate normal session
2262 $SESSION = new object();
2263 $USER = $user;
2265 /// note we are not using normal login
2266 if (!defined('USER_KEY_LOGIN')) {
2267 define('USER_KEY_LOGIN', true);
2270 load_all_capabilities();
2272 /// return isntance id - it might be empty
2273 return $key->instance;
2277 * Creates a new private user access key.
2278 * @param string $script unique target identifier
2279 * @param int $userid
2280 * @param instance $int optional instance id
2281 * @param string $iprestriction optional ip restricted access
2282 * @param timestamp $validuntil key valid only until given data
2283 * @return string access key value
2285 function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2286 $key = new object();
2287 $key->script = $script;
2288 $key->userid = $userid;
2289 $key->instance = $instance;
2290 $key->iprestriction = $iprestriction;
2291 $key->validuntil = $validuntil;
2292 $key->timecreated = time();
2294 $key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
2295 while (record_exists('user_private_key', 'value', $key->value)) {
2296 // must be unique
2297 $key->value = md5($userid.'_'.time().random_string(40));
2300 if (!insert_record('user_private_key', $key)) {
2301 error('Can not insert new key');
2304 return $key->value;
2308 * Modify the user table by setting the currently logged in user's
2309 * last login to now.
2311 * @uses $USER
2312 * @return bool
2314 function update_user_login_times() {
2315 global $USER;
2317 $user = new object();
2318 $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
2319 $USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
2321 $user->id = $USER->id;
2323 return update_record('user', $user);
2327 * Determines if a user has completed setting up their account.
2329 * @param user $user A {@link $USER} object to test for the existance of a valid name and email
2330 * @return bool
2332 function user_not_fully_set_up($user) {
2333 return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
2336 function over_bounce_threshold($user) {
2338 global $CFG;
2340 if (empty($CFG->handlebounces)) {
2341 return false;
2344 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2345 return false;
2348 // set sensible defaults
2349 if (empty($CFG->minbounces)) {
2350 $CFG->minbounces = 10;
2352 if (empty($CFG->bounceratio)) {
2353 $CFG->bounceratio = .20;
2355 $bouncecount = 0;
2356 $sendcount = 0;
2357 if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2358 $bouncecount = $bounce->value;
2360 if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2361 $sendcount = $send->value;
2363 return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
2367 * @param $user - object containing an id
2368 * @param $reset - will reset the count to 0
2370 function set_send_count($user,$reset=false) {
2372 if (empty($user->id)) { /// No real (DB) user, nothing to do here.
2373 return;
2376 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
2377 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2378 update_record('user_preferences',$pref);
2380 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2381 // make a new one
2382 $pref->name = 'email_send_count';
2383 $pref->value = 1;
2384 $pref->userid = $user->id;
2385 insert_record('user_preferences',$pref, false);
2390 * @param $user - object containing an id
2391 * @param $reset - will reset the count to 0
2393 function set_bounce_count($user,$reset=false) {
2394 if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
2395 $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
2396 update_record('user_preferences',$pref);
2398 else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
2399 // make a new one
2400 $pref->name = 'email_bounce_count';
2401 $pref->value = 1;
2402 $pref->userid = $user->id;
2403 insert_record('user_preferences',$pref, false);
2408 * Keeps track of login attempts
2410 * @uses $SESSION
2412 function update_login_count() {
2414 global $SESSION;
2416 $max_logins = 10;
2418 if (empty($SESSION->logincount)) {
2419 $SESSION->logincount = 1;
2420 } else {
2421 $SESSION->logincount++;
2424 if ($SESSION->logincount > $max_logins) {
2425 unset($SESSION->wantsurl);
2426 print_error('errortoomanylogins');
2431 * Resets login attempts
2433 * @uses $SESSION
2435 function reset_login_count() {
2436 global $SESSION;
2438 $SESSION->logincount = 0;
2441 function sync_metacourses() {
2443 global $CFG;
2445 if (!$courses = get_records('course', 'metacourse', 1)) {
2446 return;
2449 foreach ($courses as $course) {
2450 sync_metacourse($course);
2455 * Goes through all enrolment records for the courses inside the metacourse and sync with them.
2457 * @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
2459 function sync_metacourse($course) {
2460 global $CFG;
2462 // Check the course is valid.
2463 if (!is_object($course)) {
2464 if (!$course = get_record('course', 'id', $course)) {
2465 return false; // invalid course id
2469 // Check that we actually have a metacourse.
2470 if (empty($course->metacourse)) {
2471 return false;
2474 // Get a list of roles that should not be synced.
2475 if (!empty($CFG->nonmetacoursesyncroleids)) {
2476 $roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
2477 } else {
2478 $roleexclusions = '';
2481 // Get the context of the metacourse.
2482 $context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
2484 // We do not ever want to unassign the list of metacourse manager, so get a list of them.
2485 if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
2486 $managers = array_keys($users);
2487 } else {
2488 $managers = array();
2491 // Get assignments of a user to a role that exist in a child course, but
2492 // not in the meta coure. That is, get a list of the assignments that need to be made.
2493 if (!$assignments = get_records_sql("
2494 SELECT
2495 ra.id, ra.roleid, ra.userid, ra.hidden
2496 FROM
2497 {$CFG->prefix}role_assignments ra,
2498 {$CFG->prefix}context con,
2499 {$CFG->prefix}course_meta cm
2500 WHERE
2501 ra.contextid = con.id AND
2502 con.contextlevel = " . CONTEXT_COURSE . " AND
2503 con.instanceid = cm.child_course AND
2504 cm.parent_course = {$course->id} AND
2505 $roleexclusions
2506 NOT EXISTS (
2507 SELECT 1 FROM
2508 {$CFG->prefix}role_assignments ra2
2509 WHERE
2510 ra2.userid = ra.userid AND
2511 ra2.roleid = ra.roleid AND
2512 ra2.contextid = {$context->id}
2514 ")) {
2515 $assignments = array();
2518 // Get assignments of a user to a role that exist in the meta course, but
2519 // not in any child courses. That is, get a list of the unassignments that need to be made.
2520 if (!$unassignments = get_records_sql("
2521 SELECT
2522 ra.id, ra.roleid, ra.userid
2523 FROM
2524 {$CFG->prefix}role_assignments ra
2525 WHERE
2526 ra.contextid = {$context->id} AND
2527 $roleexclusions
2528 NOT EXISTS (
2529 SELECT 1 FROM
2530 {$CFG->prefix}role_assignments ra2,
2531 {$CFG->prefix}context con2,
2532 {$CFG->prefix}course_meta cm
2533 WHERE
2534 ra2.userid = ra.userid AND
2535 ra2.roleid = ra.roleid AND
2536 ra2.contextid = con2.id AND
2537 con2.contextlevel = " . CONTEXT_COURSE . " AND
2538 con2.instanceid = cm.child_course AND
2539 cm.parent_course = {$course->id}
2541 ")) {
2542 $unassignments = array();
2545 $success = true;
2547 // Make the unassignments, if they are not managers.
2548 foreach ($unassignments as $unassignment) {
2549 if (!in_array($unassignment->userid, $managers)) {
2550 $success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
2554 // Make the assignments.
2555 foreach ($assignments as $assignment) {
2556 $success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id, 0, 0, $assignment->hidden) && $success;
2559 return $success;
2561 // TODO: finish timeend and timestart
2562 // maybe we could rely on cron job to do the cleaning from time to time
2566 * Adds a record to the metacourse table and calls sync_metacoures
2568 function add_to_metacourse ($metacourseid, $courseid) {
2570 if (!$metacourse = get_record("course","id",$metacourseid)) {
2571 return false;
2574 if (!$course = get_record("course","id",$courseid)) {
2575 return false;
2578 if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
2579 $rec = new object();
2580 $rec->parent_course = $metacourseid;
2581 $rec->child_course = $courseid;
2582 if (!insert_record('course_meta',$rec)) {
2583 return false;
2585 return sync_metacourse($metacourseid);
2587 return true;
2592 * Removes the record from the metacourse table and calls sync_metacourse
2594 function remove_from_metacourse($metacourseid, $courseid) {
2596 if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
2597 return sync_metacourse($metacourseid);
2599 return false;
2604 * Determines if a user is currently logged in
2606 * @uses $USER
2607 * @return bool
2609 function isloggedin() {
2610 global $USER;
2612 return (!empty($USER->id));
2616 * Determines if a user is logged in as real guest user with username 'guest'.
2617 * This function is similar to original isguest() in 1.6 and earlier.
2618 * Current isguest() is deprecated - do not use it anymore.
2620 * @param $user mixed user object or id, $USER if not specified
2621 * @return bool true if user is the real guest user, false if not logged in or other user
2623 function isguestuser($user=NULL) {
2624 global $USER, $CFG;
2625 if ($user === NULL) {
2626 $user = $USER;
2627 } else if (is_numeric($user)) {
2628 $user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
2631 if (empty($user->id)) {
2632 return false; // not logged in, can not be guest
2635 return ($user->username == 'guest' and $user->mnethostid == $CFG->mnet_localhost_id);
2639 * Determines if the currently logged in user is in editing mode.
2640 * Note: originally this function had $userid parameter - it was not usable anyway
2642 * @uses $USER, $PAGE
2643 * @return bool
2645 function isediting() {
2646 global $USER, $PAGE;
2648 if (empty($USER->editing)) {
2649 return false;
2650 } elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
2651 return $PAGE->user_allowed_editing();
2653 return true;//false;
2657 * Determines if the logged in user is currently moving an activity
2659 * @uses $USER
2660 * @param int $courseid The id of the course being tested
2661 * @return bool
2663 function ismoving($courseid) {
2664 global $USER;
2666 if (!empty($USER->activitycopy)) {
2667 return ($USER->activitycopycourse == $courseid);
2669 return false;
2673 * Given an object containing firstname and lastname
2674 * values, this function returns a string with the
2675 * full name of the person.
2676 * The result may depend on system settings
2677 * or language. 'override' will force both names
2678 * to be used even if system settings specify one.
2680 * @uses $CFG
2681 * @uses $SESSION
2682 * @param object $user A {@link $USER} object to get full name of
2683 * @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
2685 function fullname($user, $override=false) {
2686 global $CFG, $SESSION;
2688 if (!isset($user->firstname) and !isset($user->lastname)) {
2689 return '';
2692 if (!$override) {
2693 if (!empty($CFG->forcefirstname)) {
2694 $user->firstname = $CFG->forcefirstname;
2696 if (!empty($CFG->forcelastname)) {
2697 $user->lastname = $CFG->forcelastname;
2701 if (!empty($SESSION->fullnamedisplay)) {
2702 $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
2705 if (!isset($CFG->fullnamedisplay) or $CFG->fullnamedisplay === 'firstname lastname') {
2706 return $user->firstname .' '. $user->lastname;
2708 } else if ($CFG->fullnamedisplay == 'lastname firstname') {
2709 return $user->lastname .' '. $user->firstname;
2711 } else if ($CFG->fullnamedisplay == 'firstname') {
2712 if ($override) {
2713 return get_string('fullnamedisplay', '', $user);
2714 } else {
2715 return $user->firstname;
2719 return get_string('fullnamedisplay', '', $user);
2723 * Sets a moodle cookie with an encrypted string
2725 * @uses $CFG
2726 * @uses DAYSECS
2727 * @uses HOURSECS
2728 * @param string $thing The string to encrypt and place in a cookie
2730 function set_moodle_cookie($thing) {
2731 global $CFG;
2733 if ($thing == 'guest') { // Ignore guest account
2734 return;
2737 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2739 $days = 60;
2740 $seconds = DAYSECS*$days;
2742 setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2743 setCookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
2747 * Gets a moodle cookie with an encrypted string
2749 * @uses $CFG
2750 * @return string
2752 function get_moodle_cookie() {
2753 global $CFG;
2755 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
2757 if (empty($_COOKIE[$cookiename])) {
2758 return '';
2759 } else {
2760 $thing = rc4decrypt($_COOKIE[$cookiename]);
2761 return ($thing == 'guest') ? '': $thing; // Ignore guest account
2766 * Returns whether a given authentication plugin exists.
2768 * @uses $CFG
2769 * @param string $auth Form of authentication to check for. Defaults to the
2770 * global setting in {@link $CFG}.
2771 * @return boolean Whether the plugin is available.
2773 function exists_auth_plugin($auth) {
2774 global $CFG;
2776 if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
2777 return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
2779 return false;
2783 * Checks if a given plugin is in the list of enabled authentication plugins.
2785 * @param string $auth Authentication plugin.
2786 * @return boolean Whether the plugin is enabled.
2788 function is_enabled_auth($auth) {
2789 if (empty($auth)) {
2790 return false;
2793 $enabled = get_enabled_auth_plugins();
2795 return in_array($auth, $enabled);
2799 * Returns an authentication plugin instance.
2801 * @uses $CFG
2802 * @param string $auth name of authentication plugin
2803 * @return object An instance of the required authentication plugin.
2805 function get_auth_plugin($auth) {
2806 global $CFG;
2808 // check the plugin exists first
2809 if (! exists_auth_plugin($auth)) {
2810 error("Authentication plugin '$auth' not found.");
2813 // return auth plugin instance
2814 require_once "{$CFG->dirroot}/auth/$auth/auth.php";
2815 $class = "auth_plugin_$auth";
2816 return new $class;
2820 * Returns array of active auth plugins.
2822 * @param bool $fix fix $CFG->auth if needed
2823 * @return array
2825 function get_enabled_auth_plugins($fix=false) {
2826 global $CFG;
2828 $default = array('manual', 'nologin');
2830 if (empty($CFG->auth)) {
2831 $auths = array();
2832 } else {
2833 $auths = explode(',', $CFG->auth);
2836 if ($fix) {
2837 $auths = array_unique($auths);
2838 foreach($auths as $k=>$authname) {
2839 if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
2840 unset($auths[$k]);
2843 $newconfig = implode(',', $auths);
2844 if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
2845 set_config('auth', $newconfig);
2849 return (array_merge($default, $auths));
2853 * Returns true if an internal authentication method is being used.
2854 * if method not specified then, global default is assumed
2856 * @uses $CFG
2857 * @param string $auth Form of authentication required
2858 * @return bool
2860 function is_internal_auth($auth) {
2861 $authplugin = get_auth_plugin($auth); // throws error if bad $auth
2862 return $authplugin->is_internal();
2866 * Returns true if the user is a 'restored' one
2868 * Used in the login process to inform the user
2869 * and allow him/her to reset the password
2871 * @uses $CFG
2872 * @param string $username username to be checked
2873 * @return bool
2875 function is_restored_user($username) {
2876 global $CFG;
2878 return record_exists('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, 'password', 'restored');
2882 * Returns an array of user fields
2884 * @uses $CFG
2885 * @uses $db
2886 * @return array User field/column names
2888 function get_user_fieldnames() {
2890 global $CFG, $db;
2892 $fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
2893 unset($fieldarray['ID']);
2895 return $fieldarray;
2899 * Creates the default "guest" user. Used both from
2900 * admin/index.php and login/index.php
2901 * @return mixed user object created or boolean false if the creation has failed
2903 function create_guest_record() {
2905 global $CFG;
2907 $guest = new stdClass();
2908 $guest->auth = 'manual';
2909 $guest->username = 'guest';
2910 $guest->password = hash_internal_user_password('guest');
2911 $guest->firstname = addslashes(get_string('guestuser'));
2912 $guest->lastname = ' ';
2913 $guest->email = 'root@localhost';
2914 $guest->description = addslashes(get_string('guestuserinfo'));
2915 $guest->mnethostid = $CFG->mnet_localhost_id;
2916 $guest->confirmed = 1;
2917 $guest->lang = $CFG->lang;
2918 $guest->timemodified= time();
2920 if (! $guest->id = insert_record("user", $guest)) {
2921 return false;
2924 return $guest;
2928 * Creates a bare-bones user record
2930 * @uses $CFG
2931 * @param string $username New user's username to add to record
2932 * @param string $password New user's password to add to record
2933 * @param string $auth Form of authentication required
2934 * @return object A {@link $USER} object
2935 * @todo Outline auth types and provide code example
2937 function create_user_record($username, $password, $auth='manual') {
2938 global $CFG;
2940 //just in case check text case
2941 $username = trim(moodle_strtolower($username));
2943 $authplugin = get_auth_plugin($auth);
2945 if ($newinfo = $authplugin->get_userinfo($username)) {
2946 $newinfo = truncate_userinfo($newinfo);
2947 foreach ($newinfo as $key => $value){
2948 $newuser->$key = addslashes($value);
2952 if (!empty($newuser->email)) {
2953 if (email_is_not_allowed($newuser->email)) {
2954 unset($newuser->email);
2958 if (!isset($newuser->city)) {
2959 $newuser->city = '';
2962 $newuser->auth = $auth;
2963 $newuser->username = $username;
2965 // fix for MDL-8480
2966 // user CFG lang for user if $newuser->lang is empty
2967 // or $user->lang is not an installed language
2968 $sitelangs = array_keys(get_list_of_languages());
2969 if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
2970 $newuser -> lang = $CFG->lang;
2972 $newuser->confirmed = 1;
2973 $newuser->lastip = getremoteaddr();
2974 if (empty($newuser->lastip)) {
2975 $newuser->lastip = '0.0.0.0';
2977 $newuser->timemodified = time();
2978 $newuser->mnethostid = $CFG->mnet_localhost_id;
2980 if (insert_record('user', $newuser)) {
2981 $user = get_complete_user_data('username', $newuser->username);
2982 if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
2983 set_user_preference('auth_forcepasswordchange', 1, $user->id);
2985 update_internal_user_password($user, $password);
2986 return $user;
2988 return false;
2992 * Will update a local user record from an external source
2994 * @uses $CFG
2995 * @param string $username New user's username to add to record
2996 * @return user A {@link $USER} object
2998 function update_user_record($username, $authplugin) {
2999 $username = trim(moodle_strtolower($username)); /// just in case check text case
3001 $oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
3002 $userauth = get_auth_plugin($oldinfo->auth);
3004 if ($newinfo = $userauth->get_userinfo($username)) {
3005 $newinfo = truncate_userinfo($newinfo);
3006 foreach ($newinfo as $key => $value){
3007 if ($key === 'username') {
3008 // 'username' is not a mapped updateable/lockable field, so skip it.
3009 continue;
3011 $confval = $userauth->config->{'field_updatelocal_' . $key};
3012 $lockval = $userauth->config->{'field_lock_' . $key};
3013 if (empty($confval) || empty($lockval)) {
3014 continue;
3016 if ($confval === 'onlogin') {
3017 $value = addslashes($value);
3018 // MDL-4207 Don't overwrite modified user profile values with
3019 // empty LDAP values when 'unlocked if empty' is set. The purpose
3020 // of the setting 'unlocked if empty' is to allow the user to fill
3021 // in a value for the selected field _if LDAP is giving
3022 // nothing_ for this field. Thus it makes sense to let this value
3023 // stand in until LDAP is giving a value for this field.
3024 if (!(empty($value) && $lockval === 'unlockedifempty')) {
3025 set_field('user', $key, $value, 'username', $username)
3026 || error_log("Error updating $key for $username");
3032 return get_complete_user_data('username', $username);
3035 function truncate_userinfo($info) {
3036 /// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
3037 /// which may have large fields
3039 // define the limits
3040 $limit = array(
3041 'username' => 100,
3042 'idnumber' => 255,
3043 'firstname' => 100,
3044 'lastname' => 100,
3045 'email' => 100,
3046 'icq' => 15,
3047 'phone1' => 20,
3048 'phone2' => 20,
3049 'institution' => 40,
3050 'department' => 30,
3051 'address' => 70,
3052 'city' => 20,
3053 'country' => 2,
3054 'url' => 255,
3057 // apply where needed
3058 $textlib = textlib_get_instance();
3059 foreach (array_keys($info) as $key) {
3060 if (!empty($limit[$key])) {
3061 $info[$key] = trim($textlib->substr($info[$key],0, $limit[$key]));
3065 return $info;
3069 * Marks user deleted in internal user database and notifies the auth plugin.
3070 * Also unenrols user from all roles and does other cleanup.
3071 * @param object $user Userobject before delete (without system magic quotes)
3072 * @return boolean success
3074 function delete_user($user) {
3075 global $CFG;
3076 require_once($CFG->libdir.'/grouplib.php');
3077 require_once($CFG->libdir.'/gradelib.php');
3078 require_once($CFG->dirroot.'/message/lib.php');
3080 begin_sql();
3082 // delete all grades - backup is kept in grade_grades_history table
3083 if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
3084 foreach ($grades as $grade) {
3085 $grade->delete('userdelete');
3089 //move unread messages from this user to read
3090 message_move_userfrom_unread2read($user->id);
3092 // remove from all groups
3093 delete_records('groups_members', 'userid', $user->id);
3095 // unenrol from all roles in all contexts
3096 role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
3098 // now do a final accesslib cleanup - removes all role assingments in user context and context itself
3099 delete_context(CONTEXT_USER, $user->id);
3101 require_once($CFG->dirroot.'/tag/lib.php');
3102 tag_set('user', $user->id, array());
3104 // workaround for bulk deletes of users with the same email address
3105 $delname = addslashes("$user->email.".time());
3106 while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
3107 $delname++;
3110 // mark internal user record as "deleted"
3111 $updateuser = new object();
3112 $updateuser->id = $user->id;
3113 $updateuser->deleted = 1;
3114 $updateuser->username = $delname; // Remember it just in case
3115 $updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
3116 $updateuser->idnumber = ''; // Clear this field to free it up
3117 $updateuser->timemodified = time();
3119 if (update_record('user', $updateuser)) {
3120 commit_sql();
3121 // notify auth plugin - do not block the delete even when plugin fails
3122 $authplugin = get_auth_plugin($user->auth);
3123 $authplugin->user_delete($user);
3125 events_trigger('user_deleted', $user);
3126 return true;
3128 } else {
3129 rollback_sql();
3130 return false;
3135 * Retrieve the guest user object
3137 * @uses $CFG
3138 * @return user A {@link $USER} object
3140 function guest_user() {
3141 global $CFG;
3143 if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id)) {
3144 $newuser->confirmed = 1;
3145 $newuser->lang = $CFG->lang;
3146 $newuser->lastip = getremoteaddr();
3147 if (empty($newuser->lastip)) {
3148 $newuser->lastip = '0.0.0.0';
3152 return $newuser;
3156 * Given a username and password, this function looks them
3157 * up using the currently selected authentication mechanism,
3158 * and if the authentication is successful, it returns a
3159 * valid $user object from the 'user' table.
3161 * Uses auth_ functions from the currently active auth module
3163 * After authenticate_user_login() returns success, you will need to
3164 * log that the user has logged in, and call complete_user_login() to set
3165 * the session up.
3167 * @uses $CFG
3168 * @param string $username User's username (with system magic quotes)
3169 * @param string $password User's password (with system magic quotes)
3170 * @return user|flase A {@link $USER} object or false if error
3172 function authenticate_user_login($username, $password) {
3174 global $CFG;
3176 $authsenabled = get_enabled_auth_plugins();
3178 if ($user = get_complete_user_data('username', $username)) {
3179 $auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
3180 if ($auth=='nologin' or !is_enabled_auth($auth)) {
3181 add_to_log(0, 'login', 'error', 'index.php', $username);
3182 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3183 return false;
3185 $auths = array($auth);
3187 } else {
3188 // check if there's a deleted record (cheaply)
3189 if (get_field('user', 'id', 'username', $username, 'deleted', 1, '')) {
3190 error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3191 return false;
3194 $auths = $authsenabled;
3195 $user = new object();
3196 $user->id = 0; // User does not exist
3199 foreach ($auths as $auth) {
3200 $authplugin = get_auth_plugin($auth);
3202 // on auth fail fall through to the next plugin
3203 if (!$authplugin->user_login($username, $password)) {
3204 continue;
3207 // successful authentication
3208 if ($user->id) { // User already exists in database
3209 if (empty($user->auth)) { // For some reason auth isn't set yet
3210 set_field('user', 'auth', $auth, 'username', $username);
3211 $user->auth = $auth;
3213 if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
3214 set_field('user','firstaccess', $user->timemodified, 'id', $user->id);
3215 $user->firstaccess = $user->timemodified;
3218 update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
3220 if (!$authplugin->is_internal()) { // update user record from external DB
3221 $user = update_user_record($username, get_auth_plugin($user->auth));
3223 } else {
3224 // if user not found, create him
3225 $user = create_user_record($username, $password, $auth);
3228 $authplugin->sync_roles($user);
3230 foreach ($authsenabled as $hau) {
3231 $hauth = get_auth_plugin($hau);
3232 $hauth->user_authenticated_hook($user, $username, $password);
3235 /// Log in to a second system if necessary
3236 /// NOTICE: /sso/ will be moved to auth and deprecated soon; use user_authenticated_hook() instead
3237 if (!empty($CFG->sso)) {
3238 include_once($CFG->dirroot .'/sso/'. $CFG->sso .'/lib.php');
3239 if (function_exists('sso_user_login')) {
3240 if (!sso_user_login($username, $password)) { // Perform the signon process
3241 notify('Second sign-on failed');
3246 if ($user->id===0) {
3247 return false;
3249 return $user;
3252 // failed if all the plugins have failed
3253 add_to_log(0, 'login', 'error', 'index.php', $username);
3254 if (debugging('', DEBUG_ALL)) {
3255 error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
3257 return false;
3261 * Call to complete the user login process after authenticate_user_login()
3262 * has succeeded. It will setup the $USER variable and other required bits
3263 * and pieces.
3265 * NOTE:
3266 * - It will NOT log anything -- up to the caller to decide what to log.
3270 * @uses $CFG, $USER
3271 * @param string $user obj
3272 * @return user|flase A {@link $USER} object or false if error
3274 function complete_user_login($user) {
3275 global $CFG, $USER;
3277 $USER = $user; // this is required because we need to access preferences here!
3279 if (!empty($CFG->regenloginsession)) {
3280 // please note this setting may break some auth plugins
3281 session_regenerate_id();
3284 reload_user_preferences();
3286 update_user_login_times();
3287 if (empty($CFG->nolastloggedin)) {
3288 set_moodle_cookie($USER->username);
3289 } else {
3290 // do not store last logged in user in cookie
3291 // auth plugins can temporarily override this from loginpage_hook()
3292 // do not save $CFG->nolastloggedin in database!
3293 set_moodle_cookie('nobody');
3295 set_login_session_preferences();
3297 // Call enrolment plugins
3298 check_enrolment_plugins($user);
3300 /// This is what lets the user do anything on the site :-)
3301 load_all_capabilities();
3303 /// Select password change url
3304 $userauth = get_auth_plugin($USER->auth);
3306 /// check whether the user should be changing password
3307 if (get_user_preferences('auth_forcepasswordchange', false)){
3308 if ($userauth->can_change_password()) {
3309 if ($changeurl = $userauth->change_password_url()) {
3310 redirect($changeurl);
3311 } else {
3312 redirect($CFG->httpswwwroot.'/login/change_password.php');
3314 } else {
3315 print_error('nopasswordchangeforced', 'auth');
3318 return $USER;
3322 * Compare password against hash stored in internal user table.
3323 * If necessary it also updates the stored hash to new format.
3325 * @param object user
3326 * @param string plain text password
3327 * @return bool is password valid?
3329 function validate_internal_user_password(&$user, $password) {
3330 global $CFG;
3332 if (!isset($CFG->passwordsaltmain)) {
3333 $CFG->passwordsaltmain = '';
3336 $validated = false;
3338 // get password original encoding in case it was not updated to unicode yet
3339 $textlib = textlib_get_instance();
3340 $convpassword = $textlib->convert($password, 'utf-8', get_string('oldcharset'));
3342 if ($user->password == md5($password.$CFG->passwordsaltmain) or $user->password == md5($password)
3343 or $user->password == md5($convpassword.$CFG->passwordsaltmain) or $user->password == md5($convpassword)) {
3344 $validated = true;
3345 } else {
3346 for ($i=1; $i<=20; $i++) { //20 alternative salts should be enough, right?
3347 $alt = 'passwordsaltalt'.$i;
3348 if (!empty($CFG->$alt)) {
3349 if ($user->password == md5($password.$CFG->$alt) or $user->password == md5($convpassword.$CFG->$alt)) {
3350 $validated = true;
3351 break;
3357 if ($validated) {
3358 // force update of password hash using latest main password salt and encoding if needed
3359 update_internal_user_password($user, $password);
3362 return $validated;
3366 * Calculate hashed value from password using current hash mechanism.
3368 * @param string password
3369 * @return string password hash
3371 function hash_internal_user_password($password) {
3372 global $CFG;
3374 if (isset($CFG->passwordsaltmain)) {
3375 return md5($password.$CFG->passwordsaltmain);
3376 } else {
3377 return md5($password);
3382 * Update pssword hash in user object.
3384 * @param object user
3385 * @param string plain text password
3386 * @param bool store changes also in db, default true
3387 * @return true if hash changed
3389 function update_internal_user_password(&$user, $password) {
3390 global $CFG;
3392 $authplugin = get_auth_plugin($user->auth);
3393 if ($authplugin->prevent_local_passwords()) {
3394 $hashedpassword = 'not cached';
3395 } else {
3396 $hashedpassword = hash_internal_user_password($password);
3399 return set_field('user', 'password', $hashedpassword, 'id', $user->id);
3403 * Get a complete user record, which includes all the info
3404 * in the user record
3405 * Intended for setting as $USER session variable
3407 * @uses $CFG
3408 * @uses SITEID
3409 * @param string $field The user field to be checked for a given value.
3410 * @param string $value The value to match for $field.
3411 * @return user A {@link $USER} object.
3413 function get_complete_user_data($field, $value, $mnethostid=null) {
3415 global $CFG;
3417 if (!$field || !$value) {
3418 return false;
3421 /// Build the WHERE clause for an SQL query
3423 $constraints = $field .' = \''. $value .'\' AND deleted <> \'1\'';
3425 // If we are loading user data based on anything other than id,
3426 // we must also restrict our search based on mnet host.
3427 if ($field != 'id') {
3428 if (empty($mnethostid)) {
3429 // if empty, we restrict to local users
3430 $mnethostid = $CFG->mnet_localhost_id;
3433 if (!empty($mnethostid)) {
3434 $mnethostid = (int)$mnethostid;
3435 $constraints .= ' AND mnethostid = ' . $mnethostid;
3438 /// Get all the basic user data
3440 if (! $user = get_record_select('user', $constraints)) {
3441 return false;
3444 /// Get various settings and preferences
3446 if ($displays = get_records('course_display', 'userid', $user->id)) {
3447 foreach ($displays as $display) {
3448 $user->display[$display->course] = $display->display;
3452 $user->preference = get_user_preferences(null, null, $user->id);
3454 $user->lastcourseaccess = array(); // during last session
3455 $user->currentcourseaccess = array(); // during current session
3456 if ($lastaccesses = get_records('user_lastaccess', 'userid', $user->id)) {
3457 foreach ($lastaccesses as $lastaccess) {
3458 $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
3462 $sql = "SELECT g.id, g.courseid
3463 FROM {$CFG->prefix}groups g, {$CFG->prefix}groups_members gm
3464 WHERE gm.groupid=g.id AND gm.userid={$user->id}";
3466 // this is a special hack to speedup calendar display
3467 $user->groupmember = array();
3468 if ($groups = get_records_sql($sql)) {
3469 foreach ($groups as $group) {
3470 if (!array_key_exists($group->courseid, $user->groupmember)) {
3471 $user->groupmember[$group->courseid] = array();
3473 $user->groupmember[$group->courseid][$group->id] = $group->id;
3477 /// Add the custom profile fields to the user record
3478 include_once($CFG->dirroot.'/user/profile/lib.php');
3479 $customfields = (array)profile_user_record($user->id);
3480 foreach ($customfields as $cname=>$cvalue) {
3481 if (!isset($user->$cname)) { // Don't overwrite any standard fields
3482 $user->$cname = $cvalue;
3486 /// Rewrite some variables if necessary
3487 if (!empty($user->description)) {
3488 $user->description = true; // No need to cart all of it around
3490 if ($user->username == 'guest') {
3491 $user->lang = $CFG->lang; // Guest language always same as site
3492 $user->firstname = get_string('guestuser'); // Name always in current language
3493 $user->lastname = ' ';
3496 if (isset($_SERVER['REMOTE_ADDR'])) {
3497 $user->sesskey = random_string(10);
3498 $user->sessionIP = md5(getremoteaddr()); // Store the current IP in the session
3501 return $user;
3505 * @uses $CFG
3506 * @param string $password the password to be checked agains the password policy
3507 * @param string $errmsg the error message to display when the password doesn't comply with the policy.
3508 * @return bool true if the password is valid according to the policy. false otherwise.
3510 function check_password_policy($password, &$errmsg) {
3511 global $CFG;
3513 if (empty($CFG->passwordpolicy)) {
3514 return true;
3517 $textlib = textlib_get_instance();
3518 $errmsg = '';
3519 if ($textlib->strlen($password) < $CFG->minpasswordlength) {
3520 $errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
3523 if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
3524 $errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
3527 if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
3528 $errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
3531 if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
3532 $errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
3535 if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
3536 $errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
3539 if ($errmsg == '') {
3540 return true;
3541 } else {
3542 return false;
3548 * When logging in, this function is run to set certain preferences
3549 * for the current SESSION
3551 function set_login_session_preferences() {
3552 global $SESSION, $CFG;
3554 $SESSION->justloggedin = true;
3556 unset($SESSION->lang);
3558 // Restore the calendar filters, if saved
3559 if (intval(get_user_preferences('calendar_persistflt', 0))) {
3560 include_once($CFG->dirroot.'/calendar/lib.php');
3561 calendar_set_filters_status(get_user_preferences('calendar_savedflt', 0xff));
3567 * Delete a course, including all related data from the database,
3568 * and any associated files from the moodledata folder.
3570 * @param mixed $courseorid The id of the course or course object to delete.
3571 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3572 * @return bool true if all the removals succeeded. false if there were any failures. If this
3573 * method returns false, some of the removals will probably have succeeded, and others
3574 * failed, but you have no way of knowing which.
3576 function delete_course($courseorid, $showfeedback = true) {
3577 global $CFG;
3578 $result = true;
3580 if (is_object($courseorid)) {
3581 $courseid = $courseorid->id;
3582 $course = $courseorid;
3583 } else {
3584 $courseid = $courseorid;
3585 if (!$course = get_record('course', 'id', $courseid)) {
3586 return false;
3590 // frontpage course can not be deleted!!
3591 if ($courseid == SITEID) {
3592 return false;
3595 if (!remove_course_contents($courseid, $showfeedback)) {
3596 if ($showfeedback) {
3597 notify("An error occurred while deleting some of the course contents.");
3599 $result = false;
3602 if (!delete_records("course", "id", $courseid)) {
3603 if ($showfeedback) {
3604 notify("An error occurred while deleting the main course record.");
3606 $result = false;
3609 /// Delete all roles and overiddes in the course context
3610 if (!delete_context(CONTEXT_COURSE, $courseid)) {
3611 if ($showfeedback) {
3612 notify("An error occurred while deleting the main course context.");
3614 $result = false;
3617 if (!fulldelete($CFG->dataroot.'/'.$courseid)) {
3618 if ($showfeedback) {
3619 notify("An error occurred while deleting the course files.");
3621 $result = false;
3624 if ($result) {
3625 //trigger events
3626 events_trigger('course_deleted', $course);
3629 return $result;
3633 * Clear a course out completely, deleting all content
3634 * but don't delete the course itself
3636 * @uses $CFG
3637 * @param int $courseid The id of the course that is being deleted
3638 * @param bool $showfeedback Whether to display notifications of each action the function performs.
3639 * @return bool true if all the removals succeeded. false if there were any failures. If this
3640 * method returns false, some of the removals will probably have succeeded, and others
3641 * failed, but you have no way of knowing which.
3643 function remove_course_contents($courseid, $showfeedback=true) {
3645 global $CFG;
3646 require_once($CFG->libdir.'/questionlib.php');
3647 require_once($CFG->libdir.'/gradelib.php');
3649 $result = true;
3651 if (! $course = get_record('course', 'id', $courseid)) {
3652 error('Course ID was incorrect (can\'t find it)');
3655 $strdeleted = get_string('deleted');
3657 /// Clean up course formats (iterate through all formats in the even the course format was ever changed)
3658 $formats = get_list_of_plugins('course/format');
3659 foreach ($formats as $format) {
3660 $formatdelete = $format.'_course_format_delete_course';
3661 $formatlib = "$CFG->dirroot/course/format/$format/lib.php";
3662 if (file_exists($formatlib)) {
3663 include_once($formatlib);
3664 if (function_exists($formatdelete)) {
3665 if ($showfeedback) {
3666 notify($strdeleted.' '.$format);
3668 $formatdelete($course->id);
3673 /// Delete every instance of every module
3675 if ($allmods = get_records('modules') ) {
3676 foreach ($allmods as $mod) {
3677 $modname = $mod->name;
3678 $modfile = $CFG->dirroot .'/mod/'. $modname .'/lib.php';
3679 $moddelete = $modname .'_delete_instance'; // Delete everything connected to an instance
3680 $moddeletecourse = $modname .'_delete_course'; // Delete other stray stuff (uncommon)
3681 $count=0;
3682 if (file_exists($modfile)) {
3683 include_once($modfile);
3684 if (function_exists($moddelete)) {
3685 if ($instances = get_records($modname, 'course', $course->id)) {
3686 foreach ($instances as $instance) {
3687 if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
3688 /// Delete activity context questions and question categories
3689 question_delete_activity($cm, $showfeedback);
3691 if ($moddelete($instance->id)) {
3692 $count++;
3694 } else {
3695 notify('Could not delete '. $modname .' instance '. $instance->id .' ('. format_string($instance->name) .')');
3696 $result = false;
3698 if ($cm) {
3699 // delete cm and its context in correct order
3700 delete_records('course_modules', 'id', $cm->id);
3701 delete_context(CONTEXT_MODULE, $cm->id);
3705 } else {
3706 notify('Function '.$moddelete.'() doesn\'t exist!');
3707 $result = false;
3710 if (function_exists($moddeletecourse)) {
3711 $moddeletecourse($course, $showfeedback);
3714 if ($showfeedback) {
3715 notify($strdeleted .' '. $count .' x '. $modname);
3718 } else {
3719 error('No modules are installed!');
3722 /// Give local code a chance to delete its references to this course.
3723 require_once($CFG->libdir.'/locallib.php');
3724 notify_local_delete_course($courseid, $showfeedback);
3726 /// Delete course blocks
3728 if ($blocks = get_records_sql("SELECT *
3729 FROM {$CFG->prefix}block_instance
3730 WHERE pagetype = '".PAGE_COURSE_VIEW."'
3731 AND pageid = $course->id")) {
3732 if (delete_records('block_instance', 'pagetype', PAGE_COURSE_VIEW, 'pageid', $course->id)) {
3733 if ($showfeedback) {
3734 notify($strdeleted .' block_instance');
3737 require_once($CFG->libdir.'/blocklib.php');
3738 foreach ($blocks as $block) { /// Delete any associated contexts for this block
3740 delete_context(CONTEXT_BLOCK, $block->id);
3742 // fix for MDL-7164
3743 // Get the block object and call instance_delete()
3744 if (!$record = blocks_get_record($block->blockid)) {
3745 $result = false;
3746 continue;
3748 if (!$obj = block_instance($record->name, $block)) {
3749 $result = false;
3750 continue;
3752 // Return value ignored, in core mods this does not do anything, but just in case
3753 // third party blocks might have stuff to clean up
3754 // we execute this anyway
3755 $obj->instance_delete();
3758 } else {
3759 $result = false;
3763 /// Delete any groups, removing members and grouping/course links first.
3764 require_once($CFG->dirroot.'/group/lib.php');
3765 groups_delete_groupings($courseid, $showfeedback);
3766 groups_delete_groups($courseid, $showfeedback);
3768 /// Delete all related records in other tables that may have a courseid
3769 /// This array stores the tables that need to be cleared, as
3770 /// table_name => column_name that contains the course id.
3772 $tablestoclear = array(
3773 'event' => 'courseid', // Delete events
3774 'log' => 'course', // Delete logs
3775 'course_sections' => 'course', // Delete any course stuff
3776 'course_modules' => 'course',
3777 'backup_courses' => 'courseid', // Delete scheduled backup stuff
3778 'user_lastaccess' => 'courseid',
3779 'backup_log' => 'courseid'
3781 foreach ($tablestoclear as $table => $col) {
3782 if (delete_records($table, $col, $course->id)) {
3783 if ($showfeedback) {
3784 notify($strdeleted . ' ' . $table);
3786 } else {
3787 $result = false;
3792 /// Clean up metacourse stuff
3794 if ($course->metacourse) {
3795 delete_records("course_meta","parent_course",$course->id);
3796 sync_metacourse($course->id); // have to do it here so the enrolments get nuked. sync_metacourses won't find it without the id.
3797 if ($showfeedback) {
3798 notify("$strdeleted course_meta");
3800 } else {
3801 if ($parents = get_records("course_meta","child_course",$course->id)) {
3802 foreach ($parents as $parent) {
3803 remove_from_metacourse($parent->parent_course,$parent->child_course); // this will do the unenrolments as well.
3805 if ($showfeedback) {
3806 notify("$strdeleted course_meta");
3811 /// Delete questions and question categories
3812 question_delete_course($course, $showfeedback);
3814 /// Remove all data from gradebook
3815 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3816 remove_course_grades($courseid, $showfeedback);
3817 remove_grade_letters($context, $showfeedback);
3819 return $result;
3823 * Change dates in module - used from course reset.
3824 * @param strin $modname forum, assignent, etc
3825 * @param array $fields array of date fields from mod table
3826 * @param int $timeshift time difference
3827 * @return success
3829 function shift_course_mod_dates($modname, $fields, $timeshift, $courseid) {
3830 global $CFG;
3831 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
3833 $return = true;
3834 foreach ($fields as $field) {
3835 $updatesql = "UPDATE {$CFG->prefix}$modname
3836 SET $field = $field + ($timeshift)
3837 WHERE course=$courseid AND $field<>0 AND $field<>0";
3838 $return = execute_sql($updatesql, false) && $return;
3841 $refreshfunction = $modname.'_refresh_events';
3842 if (function_exists($refreshfunction)) {
3843 $refreshfunction($courseid);
3846 return $return;
3850 * This function will empty a course of user data.
3851 * It will retain the activities and the structure of the course.
3852 * @param object $data an object containing all the settings including courseid (without magic quotes)
3853 * @return array status array of array component, item, error
3855 function reset_course_userdata($data) {
3856 global $CFG, $USER;
3857 require_once($CFG->libdir.'/gradelib.php');
3858 require_once($CFG->dirroot.'/group/lib.php');
3860 $data->courseid = $data->id;
3861 $context = get_context_instance(CONTEXT_COURSE, $data->courseid);
3863 // calculate the time shift of dates
3864 if (!empty($data->reset_start_date)) {
3865 // time part of course startdate should be zero
3866 $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
3867 } else {
3868 $data->timeshift = 0;
3871 // result array: component, item, error
3872 $status = array();
3874 // start the resetting
3875 $componentstr = get_string('general');
3877 // move the course start time
3878 if (!empty($data->reset_start_date) and $data->timeshift) {
3879 // change course start data
3880 set_field('course', 'startdate', $data->reset_start_date, 'id', $data->courseid);
3881 // update all course and group events - do not move activity events
3882 $updatesql = "UPDATE {$CFG->prefix}event
3883 SET timestart = timestart + ({$data->timeshift})
3884 WHERE courseid={$data->courseid} AND instance=0";
3885 execute_sql($updatesql, false);
3887 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3890 if (!empty($data->reset_logs)) {
3891 delete_records('log', 'course', $data->courseid);
3892 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelogs'), 'error'=>false);
3895 if (!empty($data->reset_events)) {
3896 delete_records('event', 'courseid', $data->courseid);
3897 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteevents', 'calendar'), 'error'=>false);
3900 if (!empty($data->reset_notes)) {
3901 require_once($CFG->dirroot.'/notes/lib.php');
3902 note_delete_all($data->courseid);
3903 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotes', 'notes'), 'error'=>false);
3906 $componentstr = get_string('roles');
3908 if (!empty($data->reset_roles_overrides)) {
3909 $children = get_child_contexts($context);
3910 foreach ($children as $child) {
3911 delete_records('role_capabilities', 'contextid', $child->id);
3913 delete_records('role_capabilities', 'contextid', $context->id);
3914 //force refresh for logged in users
3915 mark_context_dirty($context->path);
3916 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletecourseoverrides', 'role'), 'error'=>false);
3919 if (!empty($data->reset_roles_local)) {
3920 $children = get_child_contexts($context);
3921 foreach ($children as $child) {
3922 role_unassign(0, 0, 0, $child->id);
3924 //force refresh for logged in users
3925 mark_context_dirty($context->path);
3926 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletelocalroles', 'role'), 'error'=>false);
3929 // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
3930 $data->unenrolled = array();
3931 if (!empty($data->reset_roles)) {
3932 foreach($data->reset_roles as $roleid) {
3933 if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) {
3934 foreach ($users as $user) {
3935 role_unassign($roleid, $user->id, 0, $context->id);
3936 if (!has_capability('moodle/course:view', $context, $user->id)) {
3937 $data->unenrolled[$user->id] = $user->id;
3943 if (!empty($data->unenrolled)) {
3944 $status[] = array('component'=>$componentstr, 'item'=>get_string('unenrol').' ('.count($data->unenrolled).')', 'error'=>false);
3948 $componentstr = get_string('groups');
3950 // remove all group members
3951 if (!empty($data->reset_groups_members)) {
3952 groups_delete_group_members($data->courseid);
3953 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupsmembers', 'group'), 'error'=>false);
3956 // remove all groups
3957 if (!empty($data->reset_groups_remove)) {
3958 groups_delete_groups($data->courseid, false);
3959 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroups', 'group'), 'error'=>false);
3962 // remove all grouping members
3963 if (!empty($data->reset_groupings_members)) {
3964 groups_delete_groupings_groups($data->courseid, false);
3965 $status[] = array('component'=>$componentstr, 'item'=>get_string('removegroupingsmembers', 'group'), 'error'=>false);
3968 // remove all groupings
3969 if (!empty($data->reset_groupings_remove)) {
3970 groups_delete_groupings($data->courseid, false);
3971 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallgroupings', 'group'), 'error'=>false);
3974 // Look in every instance of every module for data to delete
3975 $unsupported_mods = array();
3976 if ($allmods = get_records('modules') ) {
3977 foreach ($allmods as $mod) {
3978 $modname = $mod->name;
3979 if (!count_records($modname, 'course', $data->courseid)) {
3980 continue; // skip mods with no instances
3982 $modfile = $CFG->dirroot.'/mod/'. $modname.'/lib.php';
3983 $moddeleteuserdata = $modname.'_reset_userdata'; // Function to delete user data
3984 if (file_exists($modfile)) {
3985 include_once($modfile);
3986 if (function_exists($moddeleteuserdata)) {
3987 $modstatus = $moddeleteuserdata($data);
3988 if (is_array($modstatus)) {
3989 $status = array_merge($status, $modstatus);
3990 } else {
3991 debugging('Module '.$modname.' returned incorrect staus - must be an array!');
3993 } else {
3994 $unsupported_mods[] = $mod;
3996 } else {
3997 debugging('Missing lib.php in '.$modname.' module!');
4002 // mention unsupported mods
4003 if (!empty($unsupported_mods)) {
4004 foreach($unsupported_mods as $mod) {
4005 $status[] = array('component'=>get_string('modulenameplural', $mod->name), 'item'=>'', 'error'=>get_string('resetnotimplemented'));
4010 $componentstr = get_string('gradebook', 'grades');
4011 // reset gradebook
4012 if (!empty($data->reset_gradebook_items)) {
4013 remove_course_grades($data->courseid, false);
4014 grade_grab_course_grades($data->courseid);
4015 grade_regrade_final_grades($data->courseid);
4016 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcourseitems', 'grades'), 'error'=>false);
4018 } else if (!empty($data->reset_gradebook_grades)) {
4019 grade_course_reset($data->courseid);
4020 $status[] = array('component'=>$componentstr, 'item'=>get_string('removeallcoursegrades', 'grades'), 'error'=>false);
4023 return $status;
4026 function generate_email_processing_address($modid,$modargs) {
4027 global $CFG;
4029 $header = $CFG->mailprefix . substr(base64_encode(pack('C',$modid)),0,2).$modargs;
4030 return $header . substr(md5($header.get_site_identifier()),0,16).'@'.$CFG->maildomain;
4033 function moodle_process_email($modargs,$body) {
4034 // the first char should be an unencoded letter. We'll take this as an action
4035 switch ($modargs{0}) {
4036 case 'B': { // bounce
4037 list(,$userid) = unpack('V',base64_decode(substr($modargs,1,8)));
4038 if ($user = get_record_select("user","id=$userid","id,email")) {
4039 // check the half md5 of their email
4040 $md5check = substr(md5($user->email),0,16);
4041 if ($md5check == substr($modargs, -16)) {
4042 set_bounce_count($user);
4044 // else maybe they've already changed it?
4047 break;
4048 // maybe more later?
4052 /// CORRESPONDENCE ////////////////////////////////////////////////
4055 * Get mailer instance, enable buffering, flush buffer or disable buffering.
4056 * @param $action string 'get', 'buffer', 'close' or 'flush'
4057 * @return reference to mailer instance if 'get' used or nothing
4059 function &get_mailer($action='get') {
4060 global $CFG;
4062 static $mailer = null;
4063 static $counter = 0;
4065 if (!isset($CFG->smtpmaxbulk)) {
4066 $CFG->smtpmaxbulk = 1;
4069 if ($action == 'get') {
4070 $prevkeepalive = false;
4072 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4073 if ($counter < $CFG->smtpmaxbulk and empty($mailer->error_count)) {
4074 $counter++;
4075 // reset the mailer
4076 $mailer->Priority = 3;
4077 $mailer->CharSet = 'UTF-8'; // our default
4078 $mailer->ContentType = "text/plain";
4079 $mailer->Encoding = "8bit";
4080 $mailer->From = "root@localhost";
4081 $mailer->FromName = "Root User";
4082 $mailer->Sender = "";
4083 $mailer->Subject = "";
4084 $mailer->Body = "";
4085 $mailer->AltBody = "";
4086 $mailer->ConfirmReadingTo = "";
4088 $mailer->ClearAllRecipients();
4089 $mailer->ClearReplyTos();
4090 $mailer->ClearAttachments();
4091 $mailer->ClearCustomHeaders();
4092 return $mailer;
4095 $prevkeepalive = $mailer->SMTPKeepAlive;
4096 get_mailer('flush');
4099 include_once($CFG->libdir.'/phpmailer/class.phpmailer.php');
4100 $mailer = new phpmailer();
4102 $counter = 1;
4104 $mailer->Version = 'Moodle '.$CFG->version; // mailer version
4105 $mailer->PluginDir = $CFG->libdir.'/phpmailer/'; // plugin directory (eg smtp plugin)
4106 $mailer->CharSet = 'UTF-8';
4108 // some MTAs may do double conversion of LF if CRLF used, CRLF is required line ending in RFC 822bis
4109 // hmm, this is a bit hacky because LE should be private
4110 if (isset($CFG->mailnewline) and $CFG->mailnewline == 'CRLF') {
4111 $mailer->LE = "\r\n";
4112 } else {
4113 $mailer->LE = "\n";
4116 if ($CFG->smtphosts == 'qmail') {
4117 $mailer->IsQmail(); // use Qmail system
4119 } else if (empty($CFG->smtphosts)) {
4120 $mailer->IsMail(); // use PHP mail() = sendmail
4122 } else {
4123 $mailer->IsSMTP(); // use SMTP directly
4124 if (!empty($CFG->debugsmtp)) {
4125 $mailer->SMTPDebug = true;
4127 $mailer->Host = $CFG->smtphosts; // specify main and backup servers
4128 $mailer->SMTPKeepAlive = $prevkeepalive; // use previous keepalive
4130 if ($CFG->smtpuser) { // Use SMTP authentication
4131 $mailer->SMTPAuth = true;
4132 $mailer->Username = $CFG->smtpuser;
4133 $mailer->Password = $CFG->smtppass;
4137 return $mailer;
4140 $nothing = null;
4142 // keep smtp session open after sending
4143 if ($action == 'buffer') {
4144 if (!empty($CFG->smtpmaxbulk)) {
4145 get_mailer('flush');
4146 $m =& get_mailer();
4147 if ($m->Mailer == 'smtp') {
4148 $m->SMTPKeepAlive = true;
4151 return $nothing;
4154 // close smtp session, but continue buffering
4155 if ($action == 'flush') {
4156 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4157 if (!empty($mailer->SMTPDebug)) {
4158 echo '<pre>'."\n";
4160 $mailer->SmtpClose();
4161 if (!empty($mailer->SMTPDebug)) {
4162 echo '</pre>';
4165 return $nothing;
4168 // close smtp session, do not buffer anymore
4169 if ($action == 'close') {
4170 if (isset($mailer) and $mailer->Mailer == 'smtp') {
4171 get_mailer('flush');
4172 $mailer->SMTPKeepAlive = false;
4174 $mailer = null; // better force new instance
4175 return $nothing;
4180 * Send an email to a specified user
4182 * @uses $CFG
4183 * @uses $FULLME
4184 * @uses $MNETIDPJUMPURL IdentityProvider(IDP) URL user hits to jump to mnet peer.
4185 * @uses SITEID
4186 * @param user $user A {@link $USER} object
4187 * @param user $from A {@link $USER} object
4188 * @param string $subject plain text subject line of the email
4189 * @param string $messagetext plain text version of the message
4190 * @param string $messagehtml complete html version of the message (optional)
4191 * @param string $attachment a file on the filesystem, relative to $CFG->dataroot
4192 * @param string $attachname the name of the file (extension indicates MIME)
4193 * @param bool $usetrueaddress determines whether $from email address should
4194 * be sent out. Will be overruled by user profile setting for maildisplay
4195 * @param int $wordwrapwidth custom word wrap width
4196 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4197 * was blocked by user and "false" if there was another sort of error.
4199 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='', $wordwrapwidth=79) {
4201 global $CFG, $FULLME, $MNETIDPJUMPURL;
4202 static $mnetjumps = array();
4204 if (empty($user) || empty($user->email)) {
4205 return false;
4208 if (!empty($user->deleted)) {
4209 // do not mail delted users
4210 return false;
4213 if (!empty($CFG->noemailever)) {
4214 // hidden setting for development sites, set in config.php if needed
4215 return true;
4218 if (!empty($CFG->divertallemailsto)) {
4219 $subject = "[DIVERTED {$user->email}] $subject";
4220 $user = clone($user);
4221 $user->email = $CFG->divertallemailsto;
4224 // skip mail to suspended users
4225 if (isset($user->auth) && $user->auth=='nologin') {
4226 return true;
4229 if (!empty($user->emailstop)) {
4230 return 'emailstop';
4233 if (over_bounce_threshold($user)) {
4234 error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
4235 return false;
4238 // If the user is a remote mnet user, parse the email text for URL to the
4239 // wwwroot and modify the url to direct the user's browser to login at their
4240 // home site (identity provider - idp) before hitting the link itself
4241 if (is_mnet_remote_user($user)) {
4242 require_once($CFG->dirroot.'/mnet/lib.php');
4243 // Form the request url to hit the idp's jump.php
4244 if (isset($mnetjumps[$user->mnethostid])) {
4245 $MNETIDPJUMPURL = $mnetjumps[$user->mnethostid];
4246 } else {
4247 $idp = mnet_get_peer_host($user->mnethostid);
4248 $idpjumppath = '/auth/mnet/jump.php';
4249 $MNETIDPJUMPURL = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
4250 $mnetjumps[$user->mnethostid] = $MNETIDPJUMPURL;
4253 $messagetext = preg_replace_callback("%($CFG->wwwroot[^[:space:]]*)%",
4254 'mnet_sso_apply_indirection',
4255 $messagetext);
4256 $messagehtml = preg_replace_callback("%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
4257 'mnet_sso_apply_indirection',
4258 $messagehtml);
4260 $mail =& get_mailer();
4262 if (!empty($mail->SMTPDebug)) {
4263 echo '<pre>' . "\n";
4266 /// We are going to use textlib services here
4267 $textlib = textlib_get_instance();
4269 $supportuser = generate_email_supportuser();
4271 // make up an email address for handling bounces
4272 if (!empty($CFG->handlebounces)) {
4273 $modargs = 'B'.base64_encode(pack('V',$user->id)).substr(md5($user->email),0,16);
4274 $mail->Sender = generate_email_processing_address(0,$modargs);
4275 } else {
4276 $mail->Sender = $supportuser->email;
4279 if (is_string($from)) { // So we can pass whatever we want if there is need
4280 $mail->From = $CFG->noreplyaddress;
4281 $mail->FromName = $from;
4282 } else if ($usetrueaddress and $from->maildisplay) {
4283 $mail->From = stripslashes($from->email);
4284 $mail->FromName = fullname($from);
4285 } else {
4286 $mail->From = $CFG->noreplyaddress;
4287 $mail->FromName = fullname($from);
4288 if (empty($replyto)) {
4289 $mail->AddReplyTo($CFG->noreplyaddress,get_string('noreplyname'));
4293 if (!empty($replyto)) {
4294 $mail->AddReplyTo($replyto,$replytoname);
4297 $mail->Subject = substr(stripslashes($subject), 0, 900);
4299 $mail->AddAddress(stripslashes($user->email), fullname($user) );
4301 $mail->WordWrap = $wordwrapwidth; // set word wrap
4303 if (!empty($from->customheaders)) { // Add custom headers
4304 if (is_array($from->customheaders)) {
4305 foreach ($from->customheaders as $customheader) {
4306 $mail->AddCustomHeader($customheader);
4308 } else {
4309 $mail->AddCustomHeader($from->customheaders);
4313 if (!empty($from->priority)) {
4314 $mail->Priority = $from->priority;
4317 if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
4318 $mail->IsHTML(true);
4319 $mail->Encoding = 'quoted-printable'; // Encoding to use
4320 $mail->Body = $messagehtml;
4321 $mail->AltBody = "\n$messagetext\n";
4322 } else {
4323 $mail->IsHTML(false);
4324 $mail->Body = "\n$messagetext\n";
4327 if ($attachment && $attachname) {
4328 if (ereg( "\\.\\." ,$attachment )) { // Security check for ".." in dir path
4329 $mail->AddAddress($supportuser->email, fullname($supportuser, true) );
4330 $mail->AddStringAttachment('Error in attachment. User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
4331 } else {
4332 require_once($CFG->libdir.'/filelib.php');
4333 $mimetype = mimeinfo('type', $attachname);
4334 $mail->AddAttachment($CFG->dataroot .'/'. $attachment, $attachname, 'base64', $mimetype);
4340 /// If we are running under Unicode and sitemailcharset or allowusermailcharset are set, convert the email
4341 /// encoding to the specified one
4342 if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
4343 /// Set it to site mail charset
4344 $charset = $CFG->sitemailcharset;
4345 /// Overwrite it with the user mail charset
4346 if (!empty($CFG->allowusermailcharset)) {
4347 if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
4348 $charset = $useremailcharset;
4351 /// If it has changed, convert all the necessary strings
4352 $charsets = get_list_of_charsets();
4353 unset($charsets['UTF-8']);
4354 if (in_array($charset, $charsets)) {
4355 /// Save the new mail charset
4356 $mail->CharSet = $charset;
4357 /// And convert some strings
4358 $mail->FromName = $textlib->convert($mail->FromName, 'utf-8', $mail->CharSet); //From Name
4359 foreach ($mail->ReplyTo as $key => $rt) { //ReplyTo Names
4360 $mail->ReplyTo[$key][1] = $textlib->convert($rt[1], 'utf-8', $mail->CharSet);
4362 $mail->Subject = $textlib->convert($mail->Subject, 'utf-8', $mail->CharSet); //Subject
4363 foreach ($mail->to as $key => $to) {
4364 $mail->to[$key][1] = $textlib->convert($to[1], 'utf-8', $mail->CharSet); //To Names
4366 $mail->Body = $textlib->convert($mail->Body, 'utf-8', $mail->CharSet); //Body
4367 $mail->AltBody = $textlib->convert($mail->AltBody, 'utf-8', $mail->CharSet); //Subject
4371 if ($mail->Send()) {
4372 set_send_count($user);
4373 $mail->IsSMTP(); // use SMTP directly
4374 if (!empty($mail->SMTPDebug)) {
4375 echo '</pre>';
4377 return true;
4378 } else {
4379 mtrace('ERROR: '. $mail->ErrorInfo);
4380 add_to_log(SITEID, 'library', 'mailer', $FULLME, 'ERROR: '. $mail->ErrorInfo);
4381 if (!empty($mail->SMTPDebug)) {
4382 echo '</pre>';
4384 return false;
4389 * Generate a signoff for emails based on support settings
4392 function generate_email_signoff() {
4393 global $CFG;
4395 $signoff = "\n";
4396 if (!empty($CFG->supportname)) {
4397 $signoff .= $CFG->supportname."\n";
4399 if (!empty($CFG->supportemail)) {
4400 $signoff .= $CFG->supportemail."\n";
4402 if (!empty($CFG->supportpage)) {
4403 $signoff .= $CFG->supportpage."\n";
4405 return $signoff;
4409 * Generate a fake user for emails based on support settings
4412 function generate_email_supportuser() {
4414 global $CFG;
4416 static $supportuser;
4418 if (!empty($supportuser)) {
4419 return $supportuser;
4422 $supportuser = new object;
4423 $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $CFG->noreplyaddress;
4424 $supportuser->firstname = $CFG->supportname ? $CFG->supportname : get_string('noreplyname');
4425 $supportuser->lastname = '';
4426 $supportuser->maildisplay = true;
4428 return $supportuser;
4433 * Sets specified user's password and send the new password to the user via email.
4435 * @uses $CFG
4436 * @param user $user A {@link $USER} object
4437 * @return boolean|string Returns "true" if mail was sent OK, "emailstop" if email
4438 * was blocked by user and "false" if there was another sort of error.
4440 function setnew_password_and_mail($user) {
4442 global $CFG;
4444 $site = get_site();
4446 $supportuser = generate_email_supportuser();
4448 $newpassword = generate_password();
4450 if (! set_field('user', 'password', hash_internal_user_password($newpassword), 'id', $user->id) ) {
4451 trigger_error('Could not set user password!');
4452 return false;
4455 $a = new object();
4456 $a->firstname = fullname($user, true);
4457 $a->sitename = format_string($site->fullname);
4458 $a->username = $user->username;
4459 $a->newpassword = $newpassword;
4460 $a->link = $CFG->wwwroot .'/login/';
4461 $a->signoff = generate_email_signoff();
4463 $message = get_string('newusernewpasswordtext', '', $a);
4465 $subject = format_string($site->fullname) .': '. get_string('newusernewpasswordsubj');
4467 return email_to_user($user, $supportuser, $subject, $message);
4472 * Resets specified user's password and send the new password to the user via email.
4474 * @uses $CFG
4475 * @param user $user A {@link $USER} object
4476 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4477 * was blocked by user and "false" if there was another sort of error.
4479 function reset_password_and_mail($user) {
4481 global $CFG;
4483 $site = get_site();
4484 $supportuser = generate_email_supportuser();
4486 $userauth = get_auth_plugin($user->auth);
4487 if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
4488 trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
4489 return false;
4492 $newpassword = generate_password();
4494 if (!$userauth->user_update_password(addslashes_recursive($user), addslashes($newpassword))) {
4495 error("Could not set user password!");
4498 $a = new object();
4499 $a->firstname = $user->firstname;
4500 $a->lastname = $user->lastname;
4501 $a->sitename = format_string($site->fullname);
4502 $a->username = $user->username;
4503 $a->newpassword = $newpassword;
4504 $a->link = $CFG->httpswwwroot .'/login/change_password.php';
4505 $a->signoff = generate_email_signoff();
4507 $message = get_string('newpasswordtext', '', $a);
4509 $subject = format_string($site->fullname) .': '. get_string('changedpassword');
4511 return email_to_user($user, $supportuser, $subject, $message);
4516 * Send email to specified user with confirmation text and activation link.
4518 * @uses $CFG
4519 * @param user $user A {@link $USER} object
4520 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4521 * was blocked by user and "false" if there was another sort of error.
4523 function send_confirmation_email($user) {
4525 global $CFG;
4527 $site = get_site();
4528 $supportuser = generate_email_supportuser();
4530 $data = new object();
4531 $data->firstname = fullname($user);
4532 $data->sitename = format_string($site->fullname);
4533 $data->admin = generate_email_signoff();
4535 $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
4537 $data->link = $CFG->wwwroot .'/login/confirm.php?data='. $user->secret .'/'. urlencode($user->username);
4538 $message = get_string('emailconfirmation', '', $data);
4539 $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
4541 $user->mailformat = 1; // Always send HTML version as well
4543 return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
4548 * send_password_change_confirmation_email.
4550 * @uses $CFG
4551 * @param user $user A {@link $USER} object
4552 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4553 * was blocked by user and "false" if there was another sort of error.
4555 function send_password_change_confirmation_email($user) {
4557 global $CFG;
4559 $site = get_site();
4560 $supportuser = generate_email_supportuser();
4562 $data = new object();
4563 $data->firstname = $user->firstname;
4564 $data->lastname = $user->lastname;
4565 $data->sitename = format_string($site->fullname);
4566 $data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
4567 $data->admin = generate_email_signoff();
4569 $message = get_string('emailpasswordconfirmation', '', $data);
4570 $subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
4572 return email_to_user($user, $supportuser, $subject, $message);
4577 * send_password_change_info.
4579 * @uses $CFG
4580 * @param user $user A {@link $USER} object
4581 * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
4582 * was blocked by user and "false" if there was another sort of error.
4584 function send_password_change_info($user) {
4586 global $CFG;
4588 $site = get_site();
4589 $supportuser = generate_email_supportuser();
4590 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4592 $data = new object();
4593 $data->firstname = $user->firstname;
4594 $data->lastname = $user->lastname;
4595 $data->sitename = format_string($site->fullname);
4596 $data->admin = generate_email_signoff();
4598 $userauth = get_auth_plugin($user->auth);
4600 if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
4601 $message = get_string('emailpasswordchangeinfodisabled', '', $data);
4602 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4603 return email_to_user($user, $supportuser, $subject, $message);
4606 if ($userauth->can_change_password() and $userauth->change_password_url()) {
4607 // we have some external url for password changing
4608 $data->link .= $userauth->change_password_url();
4610 } else {
4611 //no way to change password, sorry
4612 $data->link = '';
4615 if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
4616 $message = get_string('emailpasswordchangeinfo', '', $data);
4617 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4618 } else {
4619 $message = get_string('emailpasswordchangeinfofail', '', $data);
4620 $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
4623 return email_to_user($user, $supportuser, $subject, $message);
4628 * Check that an email is allowed. It returns an error message if there
4629 * was a problem.
4631 * @uses $CFG
4632 * @param string $email Content of email
4633 * @return string|false
4635 function email_is_not_allowed($email) {
4637 global $CFG;
4639 if (!empty($CFG->allowemailaddresses)) {
4640 $allowed = explode(' ', $CFG->allowemailaddresses);
4641 foreach ($allowed as $allowedpattern) {
4642 $allowedpattern = trim($allowedpattern);
4643 if (!$allowedpattern) {
4644 continue;
4646 if (strpos($allowedpattern, '.') === 0) {
4647 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
4648 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4649 return false;
4652 } else if (strpos(strrev($email), strrev('@'.$allowedpattern)) === 0) { // Match! (bug 5250)
4653 return false;
4656 return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
4658 } else if (!empty($CFG->denyemailaddresses)) {
4659 $denied = explode(' ', $CFG->denyemailaddresses);
4660 foreach ($denied as $deniedpattern) {
4661 $deniedpattern = trim($deniedpattern);
4662 if (!$deniedpattern) {
4663 continue;
4665 if (strpos($deniedpattern, '.') === 0) {
4666 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
4667 // subdomains are in a form ".example.com" - matches "xxx@anything.example.com"
4668 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4671 } else if (strpos(strrev($email), strrev('@'.$deniedpattern)) === 0) { // Match! (bug 5250)
4672 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
4677 return false;
4680 function email_welcome_message_to_user($course, $user=NULL) {
4681 global $CFG, $USER;
4683 if (isset($CFG->sendcoursewelcomemessage) and !$CFG->sendcoursewelcomemessage) {
4684 return;
4687 if (empty($user)) {
4688 if (!isloggedin()) {
4689 return false;
4691 $user = $USER;
4694 if (!empty($course->welcomemessage)) {
4695 $message = $course->welcomemessage;
4696 } else {
4697 $a = new Object();
4698 $a->coursename = $course->fullname;
4699 $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id";
4700 $message = get_string("welcometocoursetext", "", $a);
4703 /// If you don't want a welcome message sent, then make the message string blank.
4704 if (!empty($message)) {
4705 $subject = get_string('welcometocourse', '', format_string($course->fullname));
4707 if (! $teacher = get_teacher($course->id)) {
4708 $teacher = get_admin();
4710 email_to_user($user, $teacher, $subject, $message);
4714 /// FILE HANDLING /////////////////////////////////////////////
4718 * Makes an upload directory for a particular module.
4720 * @uses $CFG
4721 * @param int $courseid The id of the course in question - maps to id field of 'course' table.
4722 * @return string|false Returns full path to directory if successful, false if not
4724 function make_mod_upload_directory($courseid) {
4725 global $CFG;
4727 if (! $moddata = make_upload_directory($courseid .'/'. $CFG->moddata)) {
4728 return false;
4731 $strreadme = get_string('readme');
4733 if (file_exists($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt')) {
4734 copy($CFG->dirroot .'/lang/'. $CFG->lang .'/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4735 } else {
4736 copy($CFG->dirroot .'/lang/en_utf8/docs/module_files.txt', $moddata .'/'. $strreadme .'.txt');
4738 return $moddata;
4742 * Makes a directory for a particular user.
4744 * @uses $CFG
4745 * @param int $userid The id of the user in question - maps to id field of 'user' table.
4746 * @param bool $test Whether we are only testing the return value (do not create the directory)
4747 * @return string|false Returns full path to directory if successful, false if not
4749 function make_user_directory($userid, $test=false) {
4750 global $CFG;
4752 if (is_bool($userid) || $userid < 0 || !ereg('^[0-9]{1,10}$', $userid) || $userid > 2147483647) {
4753 if (!$test) {
4754 notify("Given userid was not a valid integer! (" . gettype($userid) . " $userid)");
4756 return false;
4759 // Generate a two-level path for the userid. First level groups them by slices of 1000 users, second level is userid
4760 $level1 = floor($userid / 1000) * 1000;
4762 $userdir = "user/$level1/$userid";
4763 if ($test) {
4764 return $CFG->dataroot . '/' . $userdir;
4765 } else {
4766 return make_upload_directory($userdir);
4771 * Returns an array of full paths to user directories, indexed by their userids.
4773 * @param bool $only_non_empty Only return directories that contain files
4774 * @param bool $legacy Search for user directories in legacy location (dataroot/users/userid) instead of (dataroot/user/section/userid)
4775 * @return array An associative array: userid=>array(basedir => $basedir, userfolder => $userfolder)
4777 function get_user_directories($only_non_empty=true, $legacy=false) {
4778 global $CFG;
4780 $rootdir = $CFG->dataroot."/user";
4782 if ($legacy) {
4783 $rootdir = $CFG->dataroot."/users";
4785 $dirlist = array();
4787 //Check if directory exists
4788 if (check_dir_exists($rootdir, true)) {
4789 if ($legacy) {
4790 if ($userlist = get_directory_list($rootdir, '', true, true, false)) {
4791 foreach ($userlist as $userid) {
4792 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $userid);
4794 } else {
4795 notify("no directories found under $rootdir");
4797 } else {
4798 if ($grouplist =get_directory_list($rootdir, '', true, true, false)) { // directories will be in the form 0, 1000, 2000 etc...
4799 foreach ($grouplist as $group) {
4800 if ($userlist = get_directory_list("$rootdir/$group", '', true, true, false)) {
4801 foreach ($userlist as $userid) {
4802 $dirlist[$userid] = array('basedir' => $rootdir, 'userfolder' => $group . '/' . $userid);
4808 } else {
4809 notify("$rootdir does not exist!");
4810 return false;
4812 return $dirlist;
4816 * Returns current name of file on disk if it exists.
4818 * @param string $newfile File to be verified
4819 * @return string Current name of file on disk if true
4821 function valid_uploaded_file($newfile) {
4822 if (empty($newfile)) {
4823 return '';
4825 if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
4826 return $newfile['tmp_name'];
4827 } else {
4828 return '';
4833 * Returns the maximum size for uploading files.
4835 * There are seven possible upload limits:
4836 * 1. in Apache using LimitRequestBody (no way of checking or changing this)
4837 * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
4838 * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
4839 * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
4840 * 5. by the Moodle admin in $CFG->maxbytes
4841 * 6. by the teacher in the current course $course->maxbytes
4842 * 7. by the teacher for the current module, eg $assignment->maxbytes
4844 * These last two are passed to this function as arguments (in bytes).
4845 * Anything defined as 0 is ignored.
4846 * The smallest of all the non-zero numbers is returned.
4848 * @param int $sizebytes ?
4849 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4850 * @param int $modulebytes Current module ->maxbytes (in bytes)
4851 * @return int The maximum size for uploading files.
4852 * @todo Finish documenting this function
4854 function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4856 if (! $filesize = ini_get('upload_max_filesize')) {
4857 $filesize = '5M';
4859 $minimumsize = get_real_size($filesize);
4861 if ($postsize = ini_get('post_max_size')) {
4862 $postsize = get_real_size($postsize);
4863 if ($postsize < $minimumsize) {
4864 $minimumsize = $postsize;
4868 if ($sitebytes and $sitebytes < $minimumsize) {
4869 $minimumsize = $sitebytes;
4872 if ($coursebytes and $coursebytes < $minimumsize) {
4873 $minimumsize = $coursebytes;
4876 if ($modulebytes and $modulebytes < $minimumsize) {
4877 $minimumsize = $modulebytes;
4880 return $minimumsize;
4884 * Related to {@link get_max_upload_file_size()} - this function returns an
4885 * array of possible sizes in an array, translated to the
4886 * local language.
4888 * @uses SORT_NUMERIC
4889 * @param int $sizebytes ?
4890 * @param int $coursebytes Current course $course->maxbytes (in bytes)
4891 * @param int $modulebytes Current module ->maxbytes (in bytes)
4892 * @return int
4893 * @todo Finish documenting this function
4895 function get_max_upload_sizes($sitebytes=0, $coursebytes=0, $modulebytes=0) {
4896 global $CFG;
4898 if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
4899 return array();
4902 $filesize[$maxsize] = display_size($maxsize);
4904 $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152,
4905 5242880, 10485760, 20971520, 52428800, 104857600);
4907 // Allow maxbytes to be selected if it falls outside the above boundaries
4908 if( isset($CFG->maxbytes) && !in_array($CFG->maxbytes, $sizelist) ){
4909 $sizelist[] = $CFG->maxbytes;
4912 foreach ($sizelist as $sizebytes) {
4913 if ($sizebytes < $maxsize) {
4914 $filesize[$sizebytes] = display_size($sizebytes);
4918 krsort($filesize, SORT_NUMERIC);
4920 return $filesize;
4924 * If there has been an error uploading a file, print the appropriate error message
4925 * Numerical constants used as constant definitions not added until PHP version 4.2.0
4927 * $filearray is a 1-dimensional sub-array of the $_FILES array
4928 * eg $filearray = $_FILES['userfile1']
4929 * If left empty then the first element of the $_FILES array will be used
4931 * @uses $_FILES
4932 * @param array $filearray A 1-dimensional sub-array of the $_FILES array
4933 * @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.
4934 * @return bool|string
4936 function print_file_upload_error($filearray = '', $returnerror = false) {
4938 if ($filearray == '' or !isset($filearray['error'])) {
4940 if (empty($_FILES)) return false;
4942 $files = $_FILES; /// so we don't mess up the _FILES array for subsequent code
4943 $filearray = array_shift($files); /// use first element of array
4946 switch ($filearray['error']) {
4948 case 0: // UPLOAD_ERR_OK
4949 if ($filearray['size'] > 0) {
4950 $errmessage = get_string('uploadproblem', $filearray['name']);
4951 } else {
4952 $errmessage = get_string('uploadnofilefound'); /// probably a dud file name
4954 break;
4956 case 1: // UPLOAD_ERR_INI_SIZE
4957 $errmessage = get_string('uploadserverlimit');
4958 break;
4960 case 2: // UPLOAD_ERR_FORM_SIZE
4961 $errmessage = get_string('uploadformlimit');
4962 break;
4964 case 3: // UPLOAD_ERR_PARTIAL
4965 $errmessage = get_string('uploadpartialfile');
4966 break;
4968 case 4: // UPLOAD_ERR_NO_FILE
4969 $errmessage = get_string('uploadnofilefound');
4970 break;
4972 default:
4973 $errmessage = get_string('uploadproblem', $filearray['name']);
4976 if ($returnerror) {
4977 return $errmessage;
4978 } else {
4979 notify($errmessage);
4980 return true;
4986 * handy function to loop through an array of files and resolve any filename conflicts
4987 * both in the array of filenames and for what is already on disk.
4988 * not really compatible with the similar function in uploadlib.php
4989 * but this could be used for files/index.php for moving files around.
4992 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
4993 foreach ($files as $k => $f) {
4994 if (check_potential_filename($destination,$f,$files)) {
4995 $bits = explode('.', $f);
4996 for ($i = 1; true; $i++) {
4997 $try = sprintf($format, $bits[0], $i, $bits[1]);
4998 if (!check_potential_filename($destination,$try,$files)) {
4999 $files[$k] = $try;
5000 break;
5005 return $files;
5009 * @used by resolve_filename_collisions
5011 function check_potential_filename($destination,$filename,$files) {
5012 if (file_exists($destination.'/'.$filename)) {
5013 return true;
5015 if (count(array_keys($files,$filename)) > 1) {
5016 return true;
5018 return false;
5023 * Returns an array with all the filenames in
5024 * all subdirectories, relative to the given rootdir.
5025 * If excludefile is defined, then that file/directory is ignored
5026 * If getdirs is true, then (sub)directories are included in the output
5027 * If getfiles is true, then files are included in the output
5028 * (at least one of these must be true!)
5030 * @param string $rootdir ?
5031 * @param string $excludefile If defined then the specified file/directory is ignored
5032 * @param bool $descend ?
5033 * @param bool $getdirs If true then (sub)directories are included in the output
5034 * @param bool $getfiles If true then files are included in the output
5035 * @return array An array with all the filenames in
5036 * all subdirectories, relative to the given rootdir
5037 * @todo Finish documenting this function. Add examples of $excludefile usage.
5039 function get_directory_list($rootdir, $excludefiles='', $descend=true, $getdirs=false, $getfiles=true) {
5041 $dirs = array();
5043 if (!$getdirs and !$getfiles) { // Nothing to show
5044 return $dirs;
5047 if (!is_dir($rootdir)) { // Must be a directory
5048 return $dirs;
5051 if (!$dir = opendir($rootdir)) { // Can't open it for some reason
5052 return $dirs;
5055 if (!is_array($excludefiles)) {
5056 $excludefiles = array($excludefiles);
5059 while (false !== ($file = readdir($dir))) {
5060 $firstchar = substr($file, 0, 1);
5061 if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
5062 continue;
5064 $fullfile = $rootdir .'/'. $file;
5065 if (filetype($fullfile) == 'dir') {
5066 if ($getdirs) {
5067 $dirs[] = $file;
5069 if ($descend) {
5070 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
5071 foreach ($subdirs as $subdir) {
5072 $dirs[] = $file .'/'. $subdir;
5075 } else if ($getfiles) {
5076 $dirs[] = $file;
5079 closedir($dir);
5081 asort($dirs);
5083 return $dirs;
5088 * Adds up all the files in a directory and works out the size.
5090 * @param string $rootdir ?
5091 * @param string $excludefile ?
5092 * @return array
5093 * @todo Finish documenting this function
5095 function get_directory_size($rootdir, $excludefile='') {
5097 global $CFG;
5099 // do it this way if we can, it's much faster
5100 if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
5101 $command = trim($CFG->pathtodu).' -sk '.escapeshellarg($rootdir);
5102 $output = null;
5103 $return = null;
5104 exec($command,$output,$return);
5105 if (is_array($output)) {
5106 return get_real_size(intval($output[0]).'k'); // we told it to return k.
5110 if (!is_dir($rootdir)) { // Must be a directory
5111 return 0;
5114 if (!$dir = @opendir($rootdir)) { // Can't open it for some reason
5115 return 0;
5118 $size = 0;
5120 while (false !== ($file = readdir($dir))) {
5121 $firstchar = substr($file, 0, 1);
5122 if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
5123 continue;
5125 $fullfile = $rootdir .'/'. $file;
5126 if (filetype($fullfile) == 'dir') {
5127 $size += get_directory_size($fullfile, $excludefile);
5128 } else {
5129 $size += filesize($fullfile);
5132 closedir($dir);
5134 return $size;
5138 * Converts bytes into display form
5140 * @param string $size ?
5141 * @return string
5142 * @staticvar string $gb Localized string for size in gigabytes
5143 * @staticvar string $mb Localized string for size in megabytes
5144 * @staticvar string $kb Localized string for size in kilobytes
5145 * @staticvar string $b Localized string for size in bytes
5146 * @todo Finish documenting this function. Verify return type.
5148 function display_size($size) {
5150 static $gb, $mb, $kb, $b;
5152 if (empty($gb)) {
5153 $gb = get_string('sizegb');
5154 $mb = get_string('sizemb');
5155 $kb = get_string('sizekb');
5156 $b = get_string('sizeb');
5159 if ($size >= 1073741824) {
5160 $size = round($size / 1073741824 * 10) / 10 . $gb;
5161 } else if ($size >= 1048576) {
5162 $size = round($size / 1048576 * 10) / 10 . $mb;
5163 } else if ($size >= 1024) {
5164 $size = round($size / 1024 * 10) / 10 . $kb;
5165 } else {
5166 $size = $size .' '. $b;
5168 return $size;
5172 * Cleans a given filename by removing suspicious or troublesome characters
5173 * Only these are allowed: alphanumeric _ - .
5174 * Unicode characters can be enabled by setting $CFG->unicodecleanfilename = true in config.php
5176 * WARNING: unicode characters may not be compatible with zip compression in backup/restore,
5177 * because native zip binaries do weird character conversions. Use PHP zipping instead.
5179 * @param string $string file name
5180 * @return string cleaned file name
5182 function clean_filename($string) {
5183 global $CFG;
5184 if (empty($CFG->unicodecleanfilename)) {
5185 $textlib = textlib_get_instance();
5186 $string = $textlib->specialtoascii($string);
5187 $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
5188 } else {
5189 //clean only ascii range
5190 $string = preg_replace("/[\\000-\\x2c\\x2f\\x3a-\\x40\\x5b-\\x5e\\x60\\x7b-\\177]/s", '_', $string);
5192 $string = preg_replace("/_+/", '_', $string);
5193 $string = preg_replace("/\.\.+/", '.', $string);
5194 return $string;
5198 /// STRING TRANSLATION ////////////////////////////////////////
5201 * Returns the code for the current language
5203 * @uses $CFG
5204 * @param $USER
5205 * @param $SESSION
5206 * @return string
5208 function current_language() {
5209 global $CFG, $USER, $SESSION, $COURSE;
5211 if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) { // Course language can override all other settings for this page
5212 $return = $COURSE->lang;
5214 } else if (!empty($SESSION->lang)) { // Session language can override other settings
5215 $return = $SESSION->lang;
5217 } else if (!empty($USER->lang)) {
5218 $return = $USER->lang;
5220 } else {
5221 $return = $CFG->lang;
5224 if ($return == 'en') {
5225 $return = 'en_utf8';
5228 return $return;
5232 * Prints out a translated string.
5234 * Prints out a translated string using the return value from the {@link get_string()} function.
5236 * Example usage of this function when the string is in the moodle.php file:<br/>
5237 * <code>
5238 * echo '<strong>';
5239 * print_string('wordforstudent');
5240 * echo '</strong>';
5241 * </code>
5243 * Example usage of this function when the string is not in the moodle.php file:<br/>
5244 * <code>
5245 * echo '<h1>';
5246 * print_string('typecourse', 'calendar');
5247 * echo '</h1>';
5248 * </code>
5250 * @param string $identifier The key identifier for the localized string
5251 * @param string $module The module where the key identifier is stored. If none is specified then moodle.php is used.
5252 * @param mixed $a An object, string or number that can be used
5253 * within translation strings
5255 function print_string($identifier, $module='', $a=NULL) {
5256 echo get_string($identifier, $module, $a);
5260 * fix up the optional data in get_string()/print_string() etc
5261 * ensure possible sprintf() format characters are escaped correctly
5262 * needs to handle arbitrary strings and objects
5263 * @param mixed $a An object, string or number that can be used
5264 * @return mixed the supplied parameter 'cleaned'
5266 function clean_getstring_data( $a ) {
5267 if (is_string($a)) {
5268 return str_replace( '%','%%',$a );
5270 elseif (is_object($a)) {
5271 $a_vars = get_object_vars( $a );
5272 $new_a_vars = array();
5273 foreach ($a_vars as $fname => $a_var) {
5274 $new_a_vars[$fname] = clean_getstring_data( $a_var );
5276 return (object)$new_a_vars;
5278 else {
5279 return $a;
5284 * @return array places to look for lang strings based on the prefix to the
5285 * module name. For example qtype_ in question/type. Used by get_string and
5286 * help.php.
5288 function places_to_search_for_lang_strings() {
5289 global $CFG;
5291 return array(
5292 '__exceptions' => array('moodle', 'langconfig'),
5293 'assignment_' => array('mod/assignment/type'),
5294 'auth_' => array('auth'),
5295 'block_' => array('blocks'),
5296 'datafield_' => array('mod/data/field'),
5297 'datapreset_' => array('mod/data/preset'),
5298 'enrol_' => array('enrol'),
5299 'filter_' => array('filter'),
5300 'format_' => array('course/format'),
5301 'qtype_' => array('question/type'),
5302 'report_' => array($CFG->admin.'/report', 'course/report', 'mod/quiz/report'),
5303 'resource_' => array('mod/resource/type'),
5304 'gradereport_' => array('grade/report'),
5305 'gradeimport_' => array('grade/import'),
5306 'gradeexport_' => array('grade/export'),
5307 'qformat_' => array('question/format'),
5308 'profilefield_' => array('user/profile/field'),
5309 '' => array('mod')
5314 * Returns a localized string.
5316 * Returns the translated string specified by $identifier as
5317 * for $module. Uses the same format files as STphp.
5318 * $a is an object, string or number that can be used
5319 * within translation strings
5321 * eg "hello \$a->firstname \$a->lastname"
5322 * or "hello \$a"
5324 * If you would like to directly echo the localized string use
5325 * the function {@link print_string()}
5327 * Example usage of this function involves finding the string you would
5328 * like a local equivalent of and using its identifier and module information
5329 * to retrive it.<br/>
5330 * If you open moodle/lang/en/moodle.php and look near line 1031
5331 * you will find a string to prompt a user for their word for student
5332 * <code>
5333 * $string['wordforstudent'] = 'Your word for Student';
5334 * </code>
5335 * So if you want to display the string 'Your word for student'
5336 * in any language that supports it on your site
5337 * you just need to use the identifier 'wordforstudent'
5338 * <code>
5339 * $mystring = '<strong>'. get_string('wordforstudent') .'</strong>';
5341 * </code>
5342 * If the string you want is in another file you'd take a slightly
5343 * different approach. Looking in moodle/lang/en/calendar.php you find
5344 * around line 75:
5345 * <code>
5346 * $string['typecourse'] = 'Course event';
5347 * </code>
5348 * If you want to display the string "Course event" in any language
5349 * supported you would use the identifier 'typecourse' and the module 'calendar'
5350 * (because it is in the file calendar.php):
5351 * <code>
5352 * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
5353 * </code>
5355 * As a last resort, should the identifier fail to map to a string
5356 * the returned string will be [[ $identifier ]]
5358 * @uses $CFG
5359 * @param string $identifier The key identifier for the localized string
5360 * @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.
5361 * @param mixed $a An object, string or number that can be used
5362 * within translation strings
5363 * @param array $extralocations An array of strings with other locations to look for string files
5364 * @return string The localized string.
5366 function get_string($identifier, $module='', $a=NULL, $extralocations=NULL) {
5368 global $CFG;
5370 /// originally these special strings were stored in moodle.php now we are only in langconfig.php
5371 $langconfigstrs = array('alphabet', 'backupnameformat', 'decsep', 'firstdayofweek', 'listsep', 'locale',
5372 'localewin', 'localewincharset', 'oldcharset',
5373 'parentlanguage', 'strftimedate', 'strftimedateshort', 'strftimedatetime',
5374 'strftimedaydate', 'strftimedaydatetime', 'strftimedayshort', 'strftimedaytime',
5375 'strftimemonthyear', 'strftimerecent', 'strftimerecentfull', 'strftimetime',
5376 'thischarset', 'thisdirection', 'thislanguage', 'strftimedatetimeshort', 'thousandssep');
5378 $filetocheck = 'langconfig.php';
5379 $defaultlang = 'en_utf8';
5380 if (in_array($identifier, $langconfigstrs)) {
5381 $module = 'langconfig'; //This strings are under langconfig.php for 1.6 lang packs
5384 $lang = current_language();
5386 if ($module == '') {
5387 $module = 'moodle';
5390 /// If the "module" is actually a pathname, then automatically derive the proper module name
5391 if (strpos($module, '/') !== false) {
5392 $modulepath = split('/', $module);
5394 switch ($modulepath[0]) {
5396 case 'mod':
5397 $module = $modulepath[1];
5398 break;
5400 case 'blocks':
5401 case 'block':
5402 $module = 'block_'.$modulepath[1];
5403 break;
5405 case 'enrol':
5406 $module = 'enrol_'.$modulepath[1];
5407 break;
5409 case 'format':
5410 $module = 'format_'.$modulepath[1];
5411 break;
5413 case 'grade':
5414 $module = 'grade'.$modulepath[1].'_'.$modulepath[2];
5415 break;
5419 /// if $a happens to have % in it, double it so sprintf() doesn't break
5420 if ($a) {
5421 $a = clean_getstring_data( $a );
5424 /// Define the two or three major locations of language strings for this module
5425 $locations = array();
5427 if (!empty($extralocations)) { // Calling code has a good idea where to look
5428 if (is_array($extralocations)) {
5429 $locations += $extralocations;
5430 } else if (is_string($extralocations)) {
5431 $locations[] = $extralocations;
5432 } else {
5433 debugging('Bad lang path provided');
5437 if (isset($CFG->running_installer)) {
5438 $module = 'installer';
5439 $filetocheck = 'installer.php';
5440 $locations[] = $CFG->dirroot.'/install/lang/';
5441 $locations[] = $CFG->dataroot.'/lang/';
5442 $locations[] = $CFG->dirroot.'/lang/';
5443 $defaultlang = 'en_utf8';
5444 } else {
5445 $locations[] = $CFG->dataroot.'/lang/';
5446 $locations[] = $CFG->dirroot.'/lang/';
5449 /// Add extra places to look for strings for particular plugin types.
5450 $rules = places_to_search_for_lang_strings();
5451 $exceptions = $rules['__exceptions'];
5452 unset($rules['__exceptions']);
5454 if (!in_array($module, $exceptions)) {
5455 $dividerpos = strpos($module, '_');
5456 if ($dividerpos === false) {
5457 $type = '';
5458 $plugin = $module;
5459 } else {
5460 $type = substr($module, 0, $dividerpos + 1);
5461 $plugin = substr($module, $dividerpos + 1);
5463 if ($module == 'local') {
5464 $locations[] = $CFG->dirroot . '/local/lang/';
5465 } if (!empty($rules[$type])) {
5466 foreach ($rules[$type] as $location) {
5467 $locations[] = $CFG->dirroot . "/$location/$plugin/lang/";
5472 /// First check all the normal locations for the string in the current language
5473 $resultstring = '';
5474 foreach ($locations as $location) {
5475 $locallangfile = $location.$lang.'_local'.'/'.$module.'.php'; //first, see if there's a local file
5476 if (file_exists($locallangfile)) {
5477 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5478 if (eval($result) === FALSE) {
5479 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5481 return $resultstring;
5484 //if local directory not found, or particular string does not exist in local direcotry
5485 $langfile = $location.$lang.'/'.$module.'.php';
5486 if (file_exists($langfile)) {
5487 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5488 if (eval($result) === FALSE) {
5489 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5491 return $resultstring;
5496 /// If the preferred language was English (utf8) we can abort now
5497 /// saving some checks beacuse it's the only "root" lang
5498 if ($lang == 'en_utf8') {
5499 return '[['. $identifier .']]';
5502 /// Is a parent language defined? If so, try to find this string in a parent language file
5504 foreach ($locations as $location) {
5505 $langfile = $location.$lang.'/'.$filetocheck;
5506 if (file_exists($langfile)) {
5507 if ($result = get_string_from_file('parentlanguage', $langfile, "\$parentlang")) {
5508 if (eval($result) === FALSE) {
5509 trigger_error('Lang error: '.$identifier.':'.$langfile, E_USER_NOTICE);
5511 if (!empty($parentlang)) { // found it!
5513 //first, see if there's a local file for parent
5514 $locallangfile = $location.$parentlang.'_local'.'/'.$module.'.php';
5515 if (file_exists($locallangfile)) {
5516 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5517 if (eval($result) === FALSE) {
5518 trigger_error('Lang error: '.$identifier.':'.$locallangfile, E_USER_NOTICE);
5520 return $resultstring;
5524 //if local directory not found, or particular string does not exist in local direcotry
5525 $langfile = $location.$parentlang.'/'.$module.'.php';
5526 if (file_exists($langfile)) {
5527 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5528 eval($result);
5529 return $resultstring;
5537 /// Our only remaining option is to try English
5539 foreach ($locations as $location) {
5540 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5541 if (file_exists($locallangfile)) {
5542 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5543 eval($result);
5544 return $resultstring;
5548 //if local_en not found, or string not found in local_en
5549 $langfile = $location.$defaultlang.'/'.$module.'.php';
5551 if (file_exists($langfile)) {
5552 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5553 eval($result);
5554 return $resultstring;
5559 /// And, because under 1.6 en is defined as en_utf8 child, me must try
5560 /// if it hasn't been queried before.
5561 if ($defaultlang == 'en') {
5562 $defaultlang = 'en_utf8';
5563 foreach ($locations as $location) {
5564 $locallangfile = $location.$defaultlang.'_local/'.$module.'.php'; //first, see if there's a local file
5565 if (file_exists($locallangfile)) {
5566 if ($result = get_string_from_file($identifier, $locallangfile, "\$resultstring")) {
5567 eval($result);
5568 return $resultstring;
5572 //if local_en not found, or string not found in local_en
5573 $langfile = $location.$defaultlang.'/'.$module.'.php';
5575 if (file_exists($langfile)) {
5576 if ($result = get_string_from_file($identifier, $langfile, "\$resultstring")) {
5577 eval($result);
5578 return $resultstring;
5584 return '[['.$identifier.']]'; // Last resort
5588 * This function is only used from {@link get_string()}.
5590 * @internal Only used from get_string, not meant to be public API
5591 * @param string $identifier ?
5592 * @param string $langfile ?
5593 * @param string $destination ?
5594 * @return string|false ?
5595 * @staticvar array $strings Localized strings
5596 * @access private
5597 * @todo Finish documenting this function.
5599 function get_string_from_file($identifier, $langfile, $destination) {
5601 static $strings; // Keep the strings cached in memory.
5603 if (empty($strings[$langfile])) {
5604 $string = array();
5605 include ($langfile);
5606 $strings[$langfile] = $string;
5607 } else {
5608 $string = &$strings[$langfile];
5611 if (!isset ($string[$identifier])) {
5612 return false;
5615 return $destination .'= sprintf("'. $string[$identifier] .'");';
5619 * Converts an array of strings to their localized value.
5621 * @param array $array An array of strings
5622 * @param string $module The language module that these strings can be found in.
5623 * @return string
5625 function get_strings($array, $module='') {
5627 $string = NULL;
5628 foreach ($array as $item) {
5629 $string->$item = get_string($item, $module);
5631 return $string;
5635 * Returns a list of language codes and their full names
5636 * hides the _local files from everyone.
5637 * @param bool refreshcache force refreshing of lang cache
5638 * @param bool returnall ignore langlist, return all languages available
5639 * @return array An associative array with contents in the form of LanguageCode => LanguageName
5641 function get_list_of_languages($refreshcache=false, $returnall=false) {
5643 global $CFG;
5645 $languages = array();
5647 $filetocheck = 'langconfig.php';
5649 if (!$refreshcache && !$returnall && !empty($CFG->langcache) && file_exists($CFG->dataroot .'/cache/languages')) {
5650 /// read available langs from cache
5652 $lines = file($CFG->dataroot .'/cache/languages');
5653 foreach ($lines as $line) {
5654 $line = trim($line);
5655 if (preg_match('/^(\w+)\s+(.+)/', $line, $matches)) {
5656 $languages[$matches[1]] = $matches[2];
5659 unset($lines); unset($line); unset($matches);
5660 return $languages;
5663 if (!$returnall && !empty($CFG->langlist)) {
5664 /// return only languages allowed in langlist admin setting
5666 $langlist = explode(',', $CFG->langlist);
5667 // fix short lang names first - non existing langs are skipped anyway...
5668 foreach ($langlist as $lang) {
5669 if (strpos($lang, '_utf8') === false) {
5670 $langlist[] = $lang.'_utf8';
5673 // find existing langs from langlist
5674 foreach ($langlist as $lang) {
5675 $lang = trim($lang); //Just trim spaces to be a bit more permissive
5676 if (strstr($lang, '_local')!==false) {
5677 continue;
5679 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5680 $shortlang = substr($lang, 0, -5);
5681 } else {
5682 $shortlang = $lang;
5684 /// Search under dirroot/lang
5685 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5686 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5687 if (!empty($string['thislanguage'])) {
5688 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5690 unset($string);
5692 /// And moodledata/lang
5693 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5694 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5695 if (!empty($string['thislanguage'])) {
5696 $languages[$lang] = $string['thislanguage'].' ('. $shortlang .')';
5698 unset($string);
5702 } else {
5703 /// return all languages available in system
5704 /// Fetch langs from moodle/lang directory
5705 $langdirs = get_list_of_plugins('lang');
5706 /// Fetch langs from moodledata/lang directory
5707 $langdirs2 = get_list_of_plugins('lang', '', $CFG->dataroot);
5708 /// Merge both lists of langs
5709 $langdirs = array_merge($langdirs, $langdirs2);
5710 /// Sort all
5711 asort($langdirs);
5712 /// Get some info from each lang (first from moodledata, then from moodle)
5713 foreach ($langdirs as $lang) {
5714 if (strstr($lang, '_local')!==false) {
5715 continue;
5717 if (substr($lang, -5) == '_utf8') { //Remove the _utf8 suffix from the lang to show
5718 $shortlang = substr($lang, 0, -5);
5719 } else {
5720 $shortlang = $lang;
5722 /// Search under moodledata/lang
5723 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck)) {
5724 include($CFG->dataroot .'/lang/'. $lang .'/'. $filetocheck);
5725 if (!empty($string['thislanguage'])) {
5726 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5728 unset($string);
5730 /// And dirroot/lang
5731 if (file_exists($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck)) {
5732 include($CFG->dirroot .'/lang/'. $lang .'/'. $filetocheck);
5733 if (!empty($string['thislanguage'])) {
5734 $languages[$lang] = $string['thislanguage'] .' ('. $shortlang .')';
5736 unset($string);
5741 if ($refreshcache && !empty($CFG->langcache)) {
5742 if ($returnall) {
5743 // we have a list of all langs only, just delete old cache
5744 @unlink($CFG->dataroot.'/cache/languages');
5746 } else {
5747 // store the list of allowed languages
5748 if ($file = fopen($CFG->dataroot .'/cache/languages', 'w')) {
5749 foreach ($languages as $key => $value) {
5750 fwrite($file, "$key $value\n");
5752 fclose($file);
5757 return $languages;
5761 * Returns a list of charset codes. It's hardcoded, so they should be added manually
5762 * (cheking that such charset is supported by the texlib library!)
5764 * @return array And associative array with contents in the form of charset => charset
5766 function get_list_of_charsets() {
5768 $charsets = array(
5769 'EUC-JP' => 'EUC-JP',
5770 'ISO-2022-JP'=> 'ISO-2022-JP',
5771 'ISO-8859-1' => 'ISO-8859-1',
5772 'SHIFT-JIS' => 'SHIFT-JIS',
5773 'GB2312' => 'GB2312',
5774 'GB18030' => 'GB18030', // gb18030 not supported by typo and mbstring
5775 'UTF-8' => 'UTF-8');
5777 asort($charsets);
5779 return $charsets;
5783 * For internal use only.
5784 * @return array with two elements, the path to use and the name of the lang.
5786 function get_list_of_countries_language() {
5787 global $CFG;
5789 $lang = current_language();
5790 if (is_readable($CFG->dataroot.'/lang/'. $lang .'/countries.php')) {
5791 return array($CFG->dataroot, $lang);
5793 if (is_readable($CFG->dirroot .'/lang/'. $lang .'/countries.php')) {
5794 return array($CFG->dirroot , $lang);
5797 if ($lang == 'en_utf8') {
5798 return;
5801 $parentlang = get_string('parentlanguage');
5802 if (substr($parentlang, 0, 1) != '[') {
5803 if (is_readable($CFG->dataroot.'/lang/'. $parentlang .'/countries.php')) {
5804 return array($CFG->dataroot, $parentlang);
5806 if (is_readable($CFG->dirroot .'/lang/'. $parentlang .'/countries.php')) {
5807 return array($CFG->dirroot , $parentlang);
5810 if ($parentlang == 'en_utf8') {
5811 return;
5815 if (is_readable($CFG->dataroot.'/lang/en_utf8/countries.php')) {
5816 return array($CFG->dataroot, 'en_utf8');
5818 if (is_readable($CFG->dirroot .'/lang/en_utf8/countries.php')) {
5819 return array($CFG->dirroot , 'en_utf8');
5822 return array(null, null);
5826 * Returns a list of country names in the current language
5828 * @uses $CFG
5829 * @uses $USER
5830 * @return array
5832 function get_list_of_countries() {
5833 global $CFG;
5835 list($path, $lang) = get_list_of_countries_language();
5837 if (empty($path)) {
5838 print_error('countriesphpempty', '', '', $lang);
5841 // Load all the strings into $string.
5842 include($path . '/lang/' . $lang . '/countries.php');
5844 // See if there are local overrides to countries.php.
5845 // If so, override those elements of $string.
5846 if (is_readable($CFG->dirroot .'/lang/' . $lang . '_local/countries.php')) {
5847 include($CFG->dirroot .'/lang/' . $lang . '_local/countries.php');
5849 if (is_readable($CFG->dataroot.'/lang/' . $lang . '_local/countries.php')) {
5850 include($CFG->dataroot.'/lang/' . $lang . '_local/countries.php');
5853 if (empty($string)) {
5854 print_error('countriesphpempty', '', '', $lang);
5857 uasort($string, 'strcoll');
5858 return $string;
5862 * Returns a list of valid and compatible themes
5864 * @uses $CFG
5865 * @return array
5867 function get_list_of_themes() {
5869 global $CFG;
5871 $themes = array();
5873 if (!empty($CFG->themelist)) { // use admin's list of themes
5874 $themelist = explode(',', $CFG->themelist);
5875 } else {
5876 $themelist = get_list_of_plugins("theme");
5879 foreach ($themelist as $key => $theme) {
5880 if (!file_exists("$CFG->themedir/$theme/config.php")) { // bad folder
5881 continue;
5883 $THEME = new object(); // Note this is not the global one!! :-)
5884 include("$CFG->themedir/$theme/config.php");
5885 if (!isset($THEME->sheets)) { // Not a valid 1.5 theme
5886 continue;
5888 $themes[$theme] = $theme;
5890 asort($themes);
5892 return $themes;
5897 * Returns a list of picture names in the current or specified language
5899 * @uses $CFG
5900 * @return array
5902 function get_list_of_pixnames($lang = '') {
5903 global $CFG;
5905 if (empty($lang)) {
5906 $lang = current_language();
5909 $string = array();
5911 $path = $CFG->dirroot .'/lang/en_utf8/pix.php'; // always exists
5913 if (file_exists($CFG->dataroot .'/lang/'. $lang .'_local/pix.php')) {
5914 $path = $CFG->dataroot .'/lang/'. $lang .'_local/pix.php';
5916 } else if (file_exists($CFG->dirroot .'/lang/'. $lang .'/pix.php')) {
5917 $path = $CFG->dirroot .'/lang/'. $lang .'/pix.php';
5919 } else if (file_exists($CFG->dataroot .'/lang/'. $lang .'/pix.php')) {
5920 $path = $CFG->dataroot .'/lang/'. $lang .'/pix.php';
5922 } else if ($parentlang = get_string('parentlanguage') and $parentlang != '[[parentlanguage]]') {
5923 return get_list_of_pixnames($parentlang); //return pixnames from parent language instead
5926 include($path);
5928 return $string;
5932 * Returns a list of timezones in the current language
5934 * @uses $CFG
5935 * @return array
5937 function get_list_of_timezones() {
5938 global $CFG;
5940 static $timezones;
5942 if (!empty($timezones)) { // This function has been called recently
5943 return $timezones;
5946 $timezones = array();
5948 if ($rawtimezones = get_records_sql('SELECT MAX(id), name FROM '.$CFG->prefix.'timezone GROUP BY name')) {
5949 foreach($rawtimezones as $timezone) {
5950 if (!empty($timezone->name)) {
5951 $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
5952 if (substr($timezones[$timezone->name], 0, 1) == '[') { // No translation found
5953 $timezones[$timezone->name] = $timezone->name;
5959 asort($timezones);
5961 for ($i = -13; $i <= 13; $i += .5) {
5962 $tzstring = 'UTC';
5963 if ($i < 0) {
5964 $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
5965 } else if ($i > 0) {
5966 $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
5967 } else {
5968 $timezones[sprintf("%.1f", $i)] = $tzstring;
5972 return $timezones;
5976 * Returns a list of currencies in the current language
5978 * @uses $CFG
5979 * @uses $USER
5980 * @return array
5982 function get_list_of_currencies() {
5983 global $CFG, $USER;
5985 $lang = current_language();
5987 if (!file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
5988 if ($parentlang = get_string('parentlanguage')) {
5989 if (file_exists($CFG->dataroot .'/lang/'. $parentlang .'/currencies.php')) {
5990 $lang = $parentlang;
5991 } else {
5992 $lang = 'en_utf8'; // currencies.php must exist in this pack
5994 } else {
5995 $lang = 'en_utf8'; // currencies.php must exist in this pack
5999 if (file_exists($CFG->dataroot .'/lang/'. $lang .'/currencies.php')) {
6000 include_once($CFG->dataroot .'/lang/'. $lang .'/currencies.php');
6001 } else { //if en_utf8 is not installed in dataroot
6002 include_once($CFG->dirroot .'/lang/'. $lang .'/currencies.php');
6005 if (!empty($string)) {
6006 asort($string);
6009 return $string;
6013 /// ENCRYPTION ////////////////////////////////////////////////
6016 * rc4encrypt
6018 * @param string $data ?
6019 * @return string
6020 * @todo Finish documenting this function
6022 function rc4encrypt($data) {
6023 $password = 'nfgjeingjk';
6024 return endecrypt($password, $data, '');
6028 * rc4decrypt
6030 * @param string $data ?
6031 * @return string
6032 * @todo Finish documenting this function
6034 function rc4decrypt($data) {
6035 $password = 'nfgjeingjk';
6036 return endecrypt($password, $data, 'de');
6040 * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
6042 * @param string $pwd ?
6043 * @param string $data ?
6044 * @param string $case ?
6045 * @return string
6046 * @todo Finish documenting this function
6048 function endecrypt ($pwd, $data, $case) {
6050 if ($case == 'de') {
6051 $data = urldecode($data);
6054 $key[] = '';
6055 $box[] = '';
6056 $temp_swap = '';
6057 $pwd_length = 0;
6059 $pwd_length = strlen($pwd);
6061 for ($i = 0; $i <= 255; $i++) {
6062 $key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
6063 $box[$i] = $i;
6066 $x = 0;
6068 for ($i = 0; $i <= 255; $i++) {
6069 $x = ($x + $box[$i] + $key[$i]) % 256;
6070 $temp_swap = $box[$i];
6071 $box[$i] = $box[$x];
6072 $box[$x] = $temp_swap;
6075 $temp = '';
6076 $k = '';
6078 $cipherby = '';
6079 $cipher = '';
6081 $a = 0;
6082 $j = 0;
6084 for ($i = 0; $i < strlen($data); $i++) {
6085 $a = ($a + 1) % 256;
6086 $j = ($j + $box[$a]) % 256;
6087 $temp = $box[$a];
6088 $box[$a] = $box[$j];
6089 $box[$j] = $temp;
6090 $k = $box[(($box[$a] + $box[$j]) % 256)];
6091 $cipherby = ord(substr($data, $i, 1)) ^ $k;
6092 $cipher .= chr($cipherby);
6095 if ($case == 'de') {
6096 $cipher = urldecode(urlencode($cipher));
6097 } else {
6098 $cipher = urlencode($cipher);
6101 return $cipher;
6105 /// CALENDAR MANAGEMENT ////////////////////////////////////////////////////////////////
6109 * Call this function to add an event to the calendar table
6110 * and to call any calendar plugins
6112 * @uses $CFG
6113 * @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:
6114 * <ul>
6115 * <li><b>$event->name</b> - Name for the event
6116 * <li><b>$event->description</b> - Description of the event (defaults to '')
6117 * <li><b>$event->format</b> - Format for the description (using formatting types defined at the top of weblib.php)
6118 * <li><b>$event->courseid</b> - The id of the course this event belongs to (0 = all courses)
6119 * <li><b>$event->groupid</b> - The id of the group this event belongs to (0 = no group)
6120 * <li><b>$event->userid</b> - The id of the user this event belongs to (0 = no user)
6121 * <li><b>$event->modulename</b> - Name of the module that creates this event
6122 * <li><b>$event->instance</b> - Instance of the module that owns this event
6123 * <li><b>$event->eventtype</b> - The type info together with the module info could
6124 * be used by calendar plugins to decide how to display event
6125 * <li><b>$event->timestart</b>- Timestamp for start of event
6126 * <li><b>$event->timeduration</b> - Duration (defaults to zero)
6127 * <li><b>$event->visible</b> - 0 if the event should be hidden (e.g. because the activity that created it is hidden)
6128 * </ul>
6129 * @return int The id number of the resulting record
6131 function add_event($event) {
6133 global $CFG;
6135 $event->timemodified = time();
6137 if (!$event->id = insert_record('event', $event)) {
6138 return false;
6141 if (!empty($CFG->calendar)) { // call the add_event function of the selected calendar
6142 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6143 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6144 $calendar_add_event = $CFG->calendar.'_add_event';
6145 if (function_exists($calendar_add_event)) {
6146 $calendar_add_event($event);
6151 return $event->id;
6155 * Call this function to update an event in the calendar table
6156 * the event will be identified by the id field of the $event object.
6158 * @uses $CFG
6159 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6160 * @return bool
6162 function update_event($event) {
6164 global $CFG;
6166 $event->timemodified = time();
6168 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6169 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6170 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6171 $calendar_update_event = $CFG->calendar.'_update_event';
6172 if (function_exists($calendar_update_event)) {
6173 $calendar_update_event($event);
6177 return update_record('event', $event);
6181 * Call this function to delete the event with id $id from calendar table.
6183 * @uses $CFG
6184 * @param int $id The id of an event from the 'calendar' table.
6185 * @return array An associative array with the results from the SQL call.
6186 * @todo Verify return type
6188 function delete_event($id) {
6190 global $CFG;
6192 if (!empty($CFG->calendar)) { // call the delete_event function of the selected calendar
6193 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6194 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6195 $calendar_delete_event = $CFG->calendar.'_delete_event';
6196 if (function_exists($calendar_delete_event)) {
6197 $calendar_delete_event($id);
6201 return delete_records('event', 'id', $id);
6205 * Call this function to hide an event in the calendar table
6206 * the event will be identified by the id field of the $event object.
6208 * @uses $CFG
6209 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6210 * @return array An associative array with the results from the SQL call.
6211 * @todo Verify return type
6213 function hide_event($event) {
6214 global $CFG;
6216 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6217 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6218 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6219 $calendar_hide_event = $CFG->calendar.'_hide_event';
6220 if (function_exists($calendar_hide_event)) {
6221 $calendar_hide_event($event);
6225 return set_field('event', 'visible', 0, 'id', $event->id);
6229 * Call this function to unhide an event in the calendar table
6230 * the event will be identified by the id field of the $event object.
6232 * @uses $CFG
6233 * @param array $event An associative array representing an event from the calendar table. The event will be identified by the id field.
6234 * @return array An associative array with the results from the SQL call.
6235 * @todo Verify return type
6237 function show_event($event) {
6238 global $CFG;
6240 if (!empty($CFG->calendar)) { // call the update_event function of the selected calendar
6241 if (file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
6242 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
6243 $calendar_show_event = $CFG->calendar.'_show_event';
6244 if (function_exists($calendar_show_event)) {
6245 $calendar_show_event($event);
6249 return set_field('event', 'visible', 1, 'id', $event->id);
6253 /// ENVIRONMENT CHECKING ////////////////////////////////////////////////////////////
6256 * Lists plugin directories within some directory
6258 * @uses $CFG
6259 * @param string $plugin dir under we'll look for plugins (defaults to 'mod')
6260 * @param string $exclude dir name to exclude from the list (defaults to none)
6261 * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
6262 * @return array of plugins found under the requested parameters
6264 function get_list_of_plugins($plugin='mod', $exclude='', $basedir='') {
6266 global $CFG;
6268 $plugins = array();
6270 if (empty($basedir)) {
6272 # This switch allows us to use the appropiate theme directory - and potentialy alternatives for other plugins
6273 switch ($plugin) {
6274 case "theme":
6275 $basedir = $CFG->themedir;
6276 break;
6278 default:
6279 $basedir = $CFG->dirroot .'/'. $plugin;
6282 } else {
6283 $basedir = $basedir .'/'. $plugin;
6286 if (file_exists($basedir) && filetype($basedir) == 'dir') {
6287 $dirhandle = opendir($basedir);
6288 while (false !== ($dir = readdir($dirhandle))) {
6289 $firstchar = substr($dir, 0, 1);
6290 if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == 'simpletest' or $dir == $exclude) {
6291 continue;
6293 if (filetype($basedir .'/'. $dir) != 'dir') {
6294 continue;
6296 $plugins[] = $dir;
6298 closedir($dirhandle);
6300 if ($plugins) {
6301 asort($plugins);
6303 return $plugins;
6307 * Returns true if the current version of PHP is greater that the specified one.
6309 * @param string $version The version of php being tested.
6310 * @return bool
6312 function check_php_version($version='4.1.0') {
6313 return (version_compare(phpversion(), $version) >= 0);
6317 * Checks to see if is the browser operating system matches the specified
6318 * brand.
6320 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
6322 * @uses $_SERVER
6323 * @param string $brand The operating system identifier being tested
6324 * @return bool true if the given brand below to the detected operating system
6326 function check_browser_operating_system($brand) {
6327 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6328 return false;
6331 if (preg_match("/$brand/i", $_SERVER['HTTP_USER_AGENT'])) {
6332 return true;
6335 return false;
6339 * Checks to see if is a browser matches the specified
6340 * brand and is equal or better version.
6342 * @uses $_SERVER
6343 * @param string $brand The browser identifier being tested
6344 * @param int $version The version of the browser
6345 * @return bool true if the given version is below that of the detected browser
6347 function check_browser_version($brand='MSIE', $version=5.5) {
6348 if (empty($_SERVER['HTTP_USER_AGENT'])) {
6349 return false;
6352 $agent = $_SERVER['HTTP_USER_AGENT'];
6354 switch ($brand) {
6356 case 'Camino': /// Mozilla Firefox browsers
6358 if (preg_match("/Camino\/([0-9\.]+)/i", $agent, $match)) {
6359 if (version_compare($match[1], $version) >= 0) {
6360 return true;
6363 break;
6366 case 'Firefox': /// Mozilla Firefox browsers
6368 if (preg_match("/Firefox\/([0-9\.]+)/i", $agent, $match)) {
6369 if (version_compare($match[1], $version) >= 0) {
6370 return true;
6373 break;
6376 case 'Gecko': /// Gecko based browsers
6378 if (substr_count($agent, 'Camino')) {
6379 // MacOS X Camino support
6380 $version = 20041110;
6383 // the proper string - Gecko/CCYYMMDD Vendor/Version
6384 // Faster version and work-a-round No IDN problem.
6385 if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
6386 if ($match[1] > $version) {
6387 return true;
6390 break;
6393 case 'MSIE': /// Internet Explorer
6395 if (strpos($agent, 'Opera')) { // Reject Opera
6396 return false;
6398 $string = explode(';', $agent);
6399 if (!isset($string[1])) {
6400 return false;
6402 $string = explode(' ', trim($string[1]));
6403 if (!isset($string[0]) and !isset($string[1])) {
6404 return false;
6406 if ($string[0] == $brand and (float)$string[1] >= $version ) {
6407 return true;
6409 break;
6411 case 'Opera': /// Opera
6413 if (preg_match("/Opera\/([0-9\.]+)/i", $agent, $match)) {
6414 if (version_compare($match[1], $version) >= 0) {
6415 return true;
6418 break;
6420 case 'Safari': /// Safari
6421 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SimbianOS
6422 if (strpos($agent, 'OmniWeb')) { // Reject OmniWeb
6423 return false;
6424 } elseif (strpos($agent, 'Shiira')) { // Reject Shiira
6425 return false;
6426 } elseif (strpos($agent, 'SimbianOS')) { // Reject SimbianOS
6427 return false;
6430 if (preg_match("/AppleWebKit\/([0-9]+)/i", $agent, $match)) {
6431 if (version_compare($match[1], $version) >= 0) {
6432 return true;
6436 break;
6440 return false;
6444 * Returns one or several CSS class names that match the user's browser. These can be put
6445 * in the body tag of the page to apply browser-specific rules without relying on CSS hacks
6447 function get_browser_version_classes() {
6448 $classes = '';
6449 if (check_browser_version("MSIE", "0")) {
6450 $classes .= 'ie ';
6451 if (check_browser_version("MSIE", 8)) {
6452 $classes .= 'ie8 ';
6453 } elseif (check_browser_version("MSIE", 7)) {
6454 $classes .= 'ie7 ';
6455 } elseif (check_browser_version("MSIE", 6)) {
6456 $classes .= 'ie6 ';
6458 } elseif (check_browser_version("Firefox", 0) || check_browser_version("Gecko", 0) || check_browser_version("Camino", 0)) {
6459 $classes .= 'gecko ';
6461 if (preg_match('/rv\:([1-2])\.([0-9])/', $_SERVER['HTTP_USER_AGENT'], $matches)) {
6462 $classes .= "gecko{$matches[1]}{$matches[2]} ";
6465 } elseif (check_browser_version("Safari", 0)) {
6466 $classes .= 'safari ';
6468 } elseif (check_browser_version("Opera", 0)) {
6469 $classes .= 'opera ';
6473 return $classes;
6477 * This function makes the return value of ini_get consistent if you are
6478 * setting server directives through the .htaccess file in apache.
6479 * Current behavior for value set from php.ini On = 1, Off = [blank]
6480 * Current behavior for value set from .htaccess On = On, Off = Off
6481 * Contributed by jdell @ unr.edu
6483 * @param string $ini_get_arg ?
6484 * @return bool
6485 * @todo Finish documenting this function
6487 function ini_get_bool($ini_get_arg) {
6488 $temp = ini_get($ini_get_arg);
6490 if ($temp == '1' or strtolower($temp) == 'on') {
6491 return true;
6493 return false;
6497 * Compatibility stub to provide backward compatibility
6499 * Determines if the HTML editor is enabled.
6500 * @deprecated Use {@link can_use_html_editor()} instead.
6502 function can_use_richtext_editor() {
6503 return can_use_html_editor();
6507 * Determines if the HTML editor is enabled.
6509 * This depends on site and user
6510 * settings, as well as the current browser being used.
6512 * @return string|false Returns false if editor is not being used, otherwise
6513 * returns 'MSIE' or 'Gecko'.
6515 function can_use_html_editor() {
6516 global $USER, $CFG;
6518 if (!empty($USER->htmleditor) and !empty($CFG->htmleditor)) {
6519 if (check_browser_version('MSIE', 5.5)) {
6520 return 'MSIE';
6521 } else if (check_browser_version('Gecko', 20030516)) {
6522 return 'Gecko';
6525 return false;
6529 * Hack to find out the GD version by parsing phpinfo output
6531 * @return int GD version (1, 2, or 0)
6533 function check_gd_version() {
6534 $gdversion = 0;
6536 if (function_exists('gd_info')){
6537 $gd_info = gd_info();
6538 if (substr_count($gd_info['GD Version'], '2.')) {
6539 $gdversion = 2;
6540 } else if (substr_count($gd_info['GD Version'], '1.')) {
6541 $gdversion = 1;
6544 } else {
6545 ob_start();
6546 phpinfo(INFO_MODULES);
6547 $phpinfo = ob_get_contents();
6548 ob_end_clean();
6550 $phpinfo = explode("\n", $phpinfo);
6553 foreach ($phpinfo as $text) {
6554 $parts = explode('</td>', $text);
6555 foreach ($parts as $key => $val) {
6556 $parts[$key] = trim(strip_tags($val));
6558 if ($parts[0] == 'GD Version') {
6559 if (substr_count($parts[1], '2.0')) {
6560 $parts[1] = '2.0';
6562 $gdversion = intval($parts[1]);
6567 return $gdversion; // 1, 2 or 0
6571 * Determine if moodle installation requires update
6573 * Checks version numbers of main code and all modules to see
6574 * if there are any mismatches
6576 * @uses $CFG
6577 * @return bool
6579 function moodle_needs_upgrading() {
6580 global $CFG;
6582 $version = null;
6583 include_once($CFG->dirroot .'/version.php'); # defines $version and upgrades
6584 if ($CFG->version) {
6585 if ($version > $CFG->version) {
6586 return true;
6588 if ($mods = get_list_of_plugins('mod')) {
6589 foreach ($mods as $mod) {
6590 $fullmod = $CFG->dirroot .'/mod/'. $mod;
6591 $module = new object();
6592 if (!is_readable($fullmod .'/version.php')) {
6593 notify('Module "'. $mod .'" is not readable - check permissions');
6594 continue;
6596 include_once($fullmod .'/version.php'); # defines $module with version etc
6597 if ($currmodule = get_record('modules', 'name', $mod)) {
6598 if ($module->version > $currmodule->version) {
6599 return true;
6604 } else {
6605 return true;
6607 return false;
6611 /// MISCELLANEOUS ////////////////////////////////////////////////////////////////////
6614 * Notify admin users or admin user of any failed logins (since last notification).
6616 * Note that this function must be only executed from the cron script
6617 * It uses the cache_flags system to store temporary records, deleting them
6618 * by name before finishing
6620 * @uses $CFG
6621 * @uses $db
6622 * @uses HOURSECS
6624 function notify_login_failures() {
6625 global $CFG, $db;
6627 switch ($CFG->notifyloginfailures) {
6628 case 'mainadmin' :
6629 $recip = array(get_admin());
6630 break;
6631 case 'alladmins':
6632 $recip = get_admins();
6633 break;
6636 if (empty($CFG->lastnotifyfailure)) {
6637 $CFG->lastnotifyfailure=0;
6640 // we need to deal with the threshold stuff first.
6641 if (empty($CFG->notifyloginthreshold)) {
6642 $CFG->notifyloginthreshold = 10; // default to something sensible.
6645 /// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
6646 /// and insert them into the cache_flags temp table
6647 $iprs = get_recordset_sql("SELECT ip, count(*)
6648 FROM {$CFG->prefix}log
6649 WHERE module = 'login'
6650 AND action = 'error'
6651 AND time > $CFG->lastnotifyfailure
6652 GROUP BY ip
6653 HAVING count(*) >= $CFG->notifyloginthreshold");
6654 while ($iprec = rs_fetch_next_record($iprs)) {
6655 if (!empty($iprec->ip)) {
6656 set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
6659 rs_close($iprs);
6661 /// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
6662 /// and insert them into the cache_flags temp table
6663 $infors = get_recordset_sql("SELECT info, count(*)
6664 FROM {$CFG->prefix}log
6665 WHERE module = 'login'
6666 AND action = 'error'
6667 AND time > $CFG->lastnotifyfailure
6668 GROUP BY info
6669 HAVING count(*) >= $CFG->notifyloginthreshold");
6670 while ($inforec = rs_fetch_next_record($infors)) {
6671 if (!empty($inforec->info)) {
6672 set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
6675 rs_close($infors);
6677 /// Now, select all the login error logged records belonging to the ips and infos
6678 /// since lastnotifyfailure, that we have stored in the cache_flags table
6679 $logsrs = get_recordset_sql("SELECT l.*, u.firstname, u.lastname
6680 FROM {$CFG->prefix}log l
6681 JOIN {$CFG->prefix}cache_flags cf ON (l.ip = cf.name)
6682 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6683 WHERE l.module = 'login'
6684 AND l.action = 'error'
6685 AND l.time > $CFG->lastnotifyfailure
6686 AND cf.flagtype = 'login_failure_by_ip'
6687 UNION ALL
6688 SELECT l.*, u.firstname, u.lastname
6689 FROM {$CFG->prefix}log l
6690 JOIN {$CFG->prefix}cache_flags cf ON (l.info = cf.name)
6691 LEFT JOIN {$CFG->prefix}user u ON (l.userid = u.id)
6692 WHERE l.module = 'login'
6693 AND l.action = 'error'
6694 AND l.time > $CFG->lastnotifyfailure
6695 AND cf.flagtype = 'login_failure_by_info'
6696 ORDER BY time DESC");
6698 /// Init some variables
6699 $count = 0;
6700 $messages = '';
6701 /// Iterate over the logs recordset
6702 while ($log = rs_fetch_next_record($logsrs)) {
6703 $log->time = userdate($log->time);
6704 $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
6705 $count++;
6707 rs_close($logsrs);
6709 /// If we haven't run in the last hour and
6710 /// we have something useful to report and we
6711 /// are actually supposed to be reporting to somebody
6712 if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
6713 $site = get_site();
6714 $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
6715 /// Calculate the complete body of notification (start + messages + end)
6716 $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
6717 (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
6718 $messages .
6719 "\n\n".get_string('notifyloginfailuresmessageend','',$CFG->wwwroot)."\n\n";
6721 /// For each destination, send mail
6722 mtrace('Emailing admins about '. $count .' failed login attempts');
6723 foreach ($recip as $admin) {
6724 email_to_user($admin,get_admin(), $subject, $body);
6727 /// Update lastnotifyfailure with current time
6728 set_config('lastnotifyfailure', time());
6731 /// Finally, delete all the temp records we have created in cache_flags
6732 delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
6736 * moodle_setlocale
6738 * @uses $CFG
6739 * @param string $locale ?
6740 * @todo Finish documenting this function
6742 function moodle_setlocale($locale='') {
6744 global $CFG;
6746 static $currentlocale = ''; // last locale caching
6748 $oldlocale = $currentlocale;
6750 /// Fetch the correct locale based on ostype
6751 if($CFG->ostype == 'WINDOWS') {
6752 $stringtofetch = 'localewin';
6753 } else {
6754 $stringtofetch = 'locale';
6757 /// the priority is the same as in get_string() - parameter, config, course, session, user, global language
6758 if (!empty($locale)) {
6759 $currentlocale = $locale;
6760 } else if (!empty($CFG->locale)) { // override locale for all language packs
6761 $currentlocale = $CFG->locale;
6762 } else {
6763 $currentlocale = get_string($stringtofetch);
6766 /// do nothing if locale already set up
6767 if ($oldlocale == $currentlocale) {
6768 return;
6771 /// Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
6772 /// set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
6773 /// Some day, numeric, monetary and other categories should be set too, I think. :-/
6775 /// Get current values
6776 $monetary= setlocale (LC_MONETARY, 0);
6777 $numeric = setlocale (LC_NUMERIC, 0);
6778 $ctype = setlocale (LC_CTYPE, 0);
6779 if ($CFG->ostype != 'WINDOWS') {
6780 $messages= setlocale (LC_MESSAGES, 0);
6782 /// Set locale to all
6783 setlocale (LC_ALL, $currentlocale);
6784 /// Set old values
6785 setlocale (LC_MONETARY, $monetary);
6786 setlocale (LC_NUMERIC, $numeric);
6787 if ($CFG->ostype != 'WINDOWS') {
6788 setlocale (LC_MESSAGES, $messages);
6790 if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') { // To workaround a well-known PHP problem with Turkish letter Ii
6791 setlocale (LC_CTYPE, $ctype);
6796 * Converts string to lowercase using most compatible function available.
6798 * @param string $string The string to convert to all lowercase characters.
6799 * @param string $encoding The encoding on the string.
6800 * @return string
6801 * @todo Add examples of calling this function with/without encoding types
6802 * @deprecated Use textlib->strtolower($text) instead.
6804 function moodle_strtolower ($string, $encoding='') {
6806 //If not specified use utf8
6807 if (empty($encoding)) {
6808 $encoding = 'UTF-8';
6810 //Use text services
6811 $textlib = textlib_get_instance();
6813 return $textlib->strtolower($string, $encoding);
6817 * Count words in a string.
6819 * Words are defined as things between whitespace.
6821 * @param string $string The text to be searched for words.
6822 * @return int The count of words in the specified string
6824 function count_words($string) {
6825 $string = strip_tags($string);
6826 return count(preg_split("/\w\b/", $string)) - 1;
6829 /** Count letters in a string.
6831 * Letters are defined as chars not in tags and different from whitespace.
6833 * @param string $string The text to be searched for letters.
6834 * @return int The count of letters in the specified text.
6836 function count_letters($string) {
6837 /// Loading the textlib singleton instance. We are going to need it.
6838 $textlib = textlib_get_instance();
6840 $string = strip_tags($string); // Tags are out now
6841 $string = ereg_replace('[[:space:]]*','',$string); //Whitespace are out now
6843 return $textlib->strlen($string);
6847 * Generate and return a random string of the specified length.
6849 * @param int $length The length of the string to be created.
6850 * @return string
6852 function random_string ($length=15) {
6853 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
6854 $pool .= 'abcdefghijklmnopqrstuvwxyz';
6855 $pool .= '0123456789';
6856 $poollen = strlen($pool);
6857 mt_srand ((double) microtime() * 1000000);
6858 $string = '';
6859 for ($i = 0; $i < $length; $i++) {
6860 $string .= substr($pool, (mt_rand()%($poollen)), 1);
6862 return $string;
6866 * Generate a complex random string (usefull for md5 salts)
6868 * This function is based on the above {@link random_string()} however it uses a
6869 * larger pool of characters and generates a string between 24 and 32 characters
6871 * @param int $length Optional if set generates a string to exactly this length
6872 * @return string
6874 function complex_random_string($length=null) {
6875 $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
6876 $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
6877 $poollen = strlen($pool);
6878 mt_srand ((double) microtime() * 1000000);
6879 if ($length===null) {
6880 $length = floor(rand(24,32));
6882 $string = '';
6883 for ($i = 0; $i < $length; $i++) {
6884 $string .= $pool[(mt_rand()%$poollen)];
6886 return $string;
6890 * Given some text (which may contain HTML) and an ideal length,
6891 * this function truncates the text neatly on a word boundary if possible
6892 * @param string $text - text to be shortened
6893 * @param int $ideal - ideal string length
6894 * @param boolean $exact if false, $text will not be cut mid-word
6895 * @return string $truncate - shortened string
6898 function shorten_text($text, $ideal=30, $exact = false) {
6900 global $CFG;
6901 $ending = '...';
6903 // if the plain text is shorter than the maximum length, return the whole text
6904 if (strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
6905 return $text;
6908 // Splits on HTML tags. Each open/close/empty tag will be the first thing
6909 // and only tag in its 'line'
6910 preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
6912 $total_length = strlen($ending);
6913 $truncate = '';
6915 // This array stores information about open and close tags and their position
6916 // in the truncated string. Each item in the array is an object with fields
6917 // ->open (true if open), ->tag (tag name in lower case), and ->pos
6918 // (byte position in truncated text)
6919 $tagdetails = array();
6921 foreach ($lines as $line_matchings) {
6922 // if there is any html-tag in this line, handle it and add it (uncounted) to the output
6923 if (!empty($line_matchings[1])) {
6924 // if it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>)
6925 if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
6926 // do nothing
6927 // if tag is a closing tag (f.e. </b>)
6928 } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
6929 // record closing tag
6930 $tagdetails[] = (object)array('open'=>false,
6931 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6932 // if tag is an opening tag (f.e. <b>)
6933 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
6934 // record opening tag
6935 $tagdetails[] = (object)array('open'=>true,
6936 'tag'=>strtolower($tag_matchings[1]), 'pos'=>strlen($truncate));
6938 // add html-tag to $truncate'd text
6939 $truncate .= $line_matchings[1];
6942 // calculate the length of the plain text part of the line; handle entities as one character
6943 $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
6944 if ($total_length+$content_length > $ideal) {
6945 // the number of characters which are left
6946 $left = $ideal - $total_length;
6947 $entities_length = 0;
6948 // search for html entities
6949 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)) {
6950 // calculate the real length of all entities in the legal range
6951 foreach ($entities[0] as $entity) {
6952 if ($entity[1]+1-$entities_length <= $left) {
6953 $left--;
6954 $entities_length += strlen($entity[0]);
6955 } else {
6956 // no more characters left
6957 break;
6961 $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
6962 // maximum lenght is reached, so get off the loop
6963 break;
6964 } else {
6965 $truncate .= $line_matchings[2];
6966 $total_length += $content_length;
6969 // if the maximum length is reached, get off the loop
6970 if($total_length >= $ideal) {
6971 break;
6975 // if the words shouldn't be cut in the middle...
6976 if (!$exact) {
6977 // ...search the last occurance of a space...
6978 for ($k=strlen($truncate);$k>0;$k--) {
6979 if (!empty($truncate[$k]) && ($char = $truncate[$k])) {
6980 if ($char == '.' or $char == ' ') {
6981 $breakpos = $k+1;
6982 break;
6983 } else if (ord($char) >= 0xE0) { // Chinese/Japanese/Korean text
6984 $breakpos = $k; // can be truncated at any UTF-8
6985 break; // character boundary.
6990 if (isset($breakpos)) {
6991 // ...and cut the text in this position
6992 $truncate = substr($truncate, 0, $breakpos);
6996 // add the defined ending to the text
6997 $truncate .= $ending;
6999 // Now calculate the list of open html tags based on the truncate position
7000 $open_tags = array();
7001 foreach ($tagdetails as $taginfo) {
7002 if(isset($breakpos) && $taginfo->pos >= $breakpos) {
7003 // Don't include tags after we made the break!
7004 break;
7006 if($taginfo->open) {
7007 // add tag to the beginning of $open_tags list
7008 array_unshift($open_tags, $taginfo->tag);
7009 } else {
7010 $pos = array_search($taginfo->tag, array_reverse($open_tags, true)); // can have multiple exact same open tags, close the last one
7011 if ($pos !== false) {
7012 unset($open_tags[$pos]);
7017 // close all unclosed html-tags
7018 foreach ($open_tags as $tag) {
7019 $truncate .= '</' . $tag . '>';
7022 return $truncate;
7027 * Given dates in seconds, how many weeks is the date from startdate
7028 * The first week is 1, the second 2 etc ...
7030 * @uses WEEKSECS
7031 * @param ? $startdate ?
7032 * @param ? $thedate ?
7033 * @return string
7034 * @todo Finish documenting this function
7036 function getweek ($startdate, $thedate) {
7037 if ($thedate < $startdate) { // error
7038 return 0;
7041 return floor(($thedate - $startdate) / WEEKSECS) + 1;
7045 * returns a randomly generated password of length $maxlen. inspired by
7046 * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
7047 * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
7049 * @param int $maxlen The maximum size of the password being generated.
7050 * @return string
7052 function generate_password($maxlen=10) {
7053 global $CFG;
7055 if (empty($CFG->passwordpolicy)) {
7056 $fillers = PASSWORD_DIGITS;
7057 $wordlist = file($CFG->wordlist);
7058 $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7059 $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
7060 $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
7061 $password = $word1 . $filler1 . $word2;
7062 } else {
7063 $maxlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
7064 $digits = $CFG->minpassworddigits;
7065 $lower = $CFG->minpasswordlower;
7066 $upper = $CFG->minpasswordupper;
7067 $nonalphanum = $CFG->minpasswordnonalphanum;
7068 $additional = $maxlen - ($lower + $upper + $digits + $nonalphanum);
7070 // Make sure we have enough characters to fulfill
7071 // complexity requirements
7072 $passworddigits = PASSWORD_DIGITS;
7073 while ($digits > strlen($passworddigits)) {
7074 $passworddigits .= PASSWORD_DIGITS;
7076 $passwordlower = PASSWORD_LOWER;
7077 while ($lower > strlen($passwordlower)) {
7078 $passwordlower .= PASSWORD_LOWER;
7080 $passwordupper = PASSWORD_UPPER;
7081 while ($upper > strlen($passwordupper)) {
7082 $passwordupper .= PASSWORD_UPPER;
7084 $passwordnonalphanum = PASSWORD_NONALPHANUM;
7085 while ($nonalphanum > strlen($passwordnonalphanum)) {
7086 $passwordnonalphanum .= PASSWORD_NONALPHANUM;
7089 // Now mix and shuffle it all
7090 $password = str_shuffle (substr(str_shuffle ($passwordlower), 0, $lower) .
7091 substr(str_shuffle ($passwordupper), 0, $upper) .
7092 substr(str_shuffle ($passworddigits), 0, $digits) .
7093 substr(str_shuffle ($passwordnonalphanum), 0 , $nonalphanum) .
7094 substr(str_shuffle ($passwordlower .
7095 $passwordupper .
7096 $passworddigits .
7097 $passwordnonalphanum), 0 , $additional));
7100 return substr ($password, 0, $maxlen);
7104 * Given a float, prints it nicely.
7105 * Localized floats must not be used in calculations!
7107 * @param float $flaot The float to print
7108 * @param int $places The number of decimal places to print.
7109 * @param bool $localized use localized decimal separator
7110 * @return string locale float
7112 function format_float($float, $decimalpoints=1, $localized=true) {
7113 if (is_null($float)) {
7114 return '';
7116 if ($localized) {
7117 return number_format($float, $decimalpoints, get_string('decsep'), '');
7118 } else {
7119 return number_format($float, $decimalpoints, '.', '');
7124 * Converts locale specific floating point/comma number back to standard PHP float value
7125 * Do NOT try to do any math operations before this conversion on any user submitted floats!
7127 * @param string $locale_float locale aware float representation
7129 function unformat_float($locale_float) {
7130 $locale_float = trim($locale_float);
7132 if ($locale_float == '') {
7133 return null;
7136 $locale_float = str_replace(' ', '', $locale_float); // no spaces - those might be used as thousand separators
7138 return (float)str_replace(get_string('decsep'), '.', $locale_float);
7142 * Given a simple array, this shuffles it up just like shuffle()
7143 * Unlike PHP's shuffle() this function works on any machine.
7145 * @param array $array The array to be rearranged
7146 * @return array
7148 function swapshuffle($array) {
7150 srand ((double) microtime() * 10000000);
7151 $last = count($array) - 1;
7152 for ($i=0;$i<=$last;$i++) {
7153 $from = rand(0,$last);
7154 $curr = $array[$i];
7155 $array[$i] = $array[$from];
7156 $array[$from] = $curr;
7158 return $array;
7162 * Like {@link swapshuffle()}, but works on associative arrays
7164 * @param array $array The associative array to be rearranged
7165 * @return array
7167 function swapshuffle_assoc($array) {
7169 $newarray = array();
7170 $newkeys = swapshuffle(array_keys($array));
7172 foreach ($newkeys as $newkey) {
7173 $newarray[$newkey] = $array[$newkey];
7175 return $newarray;
7179 * Given an arbitrary array, and a number of draws,
7180 * this function returns an array with that amount
7181 * of items. The indexes are retained.
7183 * @param array $array ?
7184 * @param ? $draws ?
7185 * @return ?
7186 * @todo Finish documenting this function
7188 function draw_rand_array($array, $draws) {
7189 srand ((double) microtime() * 10000000);
7191 $return = array();
7193 $last = count($array);
7195 if ($draws > $last) {
7196 $draws = $last;
7199 while ($draws > 0) {
7200 $last--;
7202 $keys = array_keys($array);
7203 $rand = rand(0, $last);
7205 $return[$keys[$rand]] = $array[$keys[$rand]];
7206 unset($array[$keys[$rand]]);
7208 $draws--;
7211 return $return;
7215 * microtime_diff
7217 * @param string $a ?
7218 * @param string $b ?
7219 * @return string
7220 * @todo Finish documenting this function
7222 function microtime_diff($a, $b) {
7223 list($a_dec, $a_sec) = explode(' ', $a);
7224 list($b_dec, $b_sec) = explode(' ', $b);
7225 return $b_sec - $a_sec + $b_dec - $a_dec;
7229 * Given a list (eg a,b,c,d,e) this function returns
7230 * an array of 1->a, 2->b, 3->c etc
7232 * @param array $list ?
7233 * @param string $separator ?
7234 * @todo Finish documenting this function
7236 function make_menu_from_list($list, $separator=',') {
7238 $array = array_reverse(explode($separator, $list), true);
7239 foreach ($array as $key => $item) {
7240 $outarray[$key+1] = trim($item);
7242 return $outarray;
7246 * Creates an array that represents all the current grades that
7247 * can be chosen using the given grading type. Negative numbers
7248 * are scales, zero is no grade, and positive numbers are maximum
7249 * grades.
7251 * @param int $gradingtype ?
7252 * return int
7253 * @todo Finish documenting this function
7255 function make_grades_menu($gradingtype) {
7256 $grades = array();
7257 if ($gradingtype < 0) {
7258 if ($scale = get_record('scale', 'id', - $gradingtype)) {
7259 return make_menu_from_list($scale->scale);
7261 } else if ($gradingtype > 0) {
7262 for ($i=$gradingtype; $i>=0; $i--) {
7263 $grades[$i] = $i .' / '. $gradingtype;
7265 return $grades;
7267 return $grades;
7271 * This function returns the nummber of activities
7272 * using scaleid in a courseid
7274 * @param int $courseid ?
7275 * @param int $scaleid ?
7276 * @return int
7277 * @todo Finish documenting this function
7279 function course_scale_used($courseid, $scaleid) {
7281 global $CFG;
7283 $return = 0;
7285 if (!empty($scaleid)) {
7286 if ($cms = get_course_mods($courseid)) {
7287 foreach ($cms as $cm) {
7288 //Check cm->name/lib.php exists
7289 if (file_exists($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php')) {
7290 include_once($CFG->dirroot.'/mod/'.$cm->modname.'/lib.php');
7291 $function_name = $cm->modname.'_scale_used';
7292 if (function_exists($function_name)) {
7293 if ($function_name($cm->instance,$scaleid)) {
7294 $return++;
7301 // check if any course grade item makes use of the scale
7302 $return += count_records('grade_items', 'courseid', $courseid, 'scaleid', $scaleid);
7304 // check if any outcome in the course makes use of the scale
7305 $return += count_records_sql("SELECT COUNT(*)
7306 FROM {$CFG->prefix}grade_outcomes_courses goc,
7307 {$CFG->prefix}grade_outcomes go
7308 WHERE go.id = goc.outcomeid
7309 AND go.scaleid = $scaleid
7310 AND goc.courseid = $courseid");
7312 return $return;
7316 * This function returns the nummber of activities
7317 * using scaleid in the entire site
7319 * @param int $scaleid ?
7320 * @return int
7321 * @todo Finish documenting this function. Is return type correct?
7323 function site_scale_used($scaleid,&$courses) {
7325 global $CFG;
7327 $return = 0;
7329 if (!is_array($courses) || count($courses) == 0) {
7330 $courses = get_courses("all",false,"c.id,c.shortname");
7333 if (!empty($scaleid)) {
7334 if (is_array($courses) && count($courses) > 0) {
7335 foreach ($courses as $course) {
7336 $return += course_scale_used($course->id,$scaleid);
7340 return $return;
7344 * make_unique_id_code
7346 * @param string $extra ?
7347 * @return string
7348 * @todo Finish documenting this function
7350 function make_unique_id_code($extra='') {
7352 $hostname = 'unknownhost';
7353 if (!empty($_SERVER['HTTP_HOST'])) {
7354 $hostname = $_SERVER['HTTP_HOST'];
7355 } else if (!empty($_ENV['HTTP_HOST'])) {
7356 $hostname = $_ENV['HTTP_HOST'];
7357 } else if (!empty($_SERVER['SERVER_NAME'])) {
7358 $hostname = $_SERVER['SERVER_NAME'];
7359 } else if (!empty($_ENV['SERVER_NAME'])) {
7360 $hostname = $_ENV['SERVER_NAME'];
7363 $date = gmdate("ymdHis");
7365 $random = random_string(6);
7367 if ($extra) {
7368 return $hostname .'+'. $date .'+'. $random .'+'. $extra;
7369 } else {
7370 return $hostname .'+'. $date .'+'. $random;
7376 * Function to check the passed address is within the passed subnet
7378 * The parameter is a comma separated string of subnet definitions.
7379 * Subnet strings can be in one of three formats:
7380 * 1: xxx.xxx.xxx.xxx/xx
7381 * 2: xxx.xxx
7382 * 3: xxx.xxx.xxx.xxx-xxx //a range of IP addresses in the last group.
7383 * Code for type 1 modified from user posted comments by mediator at
7384 * {@link http://au.php.net/manual/en/function.ip2long.php}
7386 * TODO one day we will have to make this work with IP6.
7388 * @param string $addr The address you are checking
7389 * @param string $subnetstr The string of subnet addresses
7390 * @return bool
7392 function address_in_subnet($addr, $subnetstr) {
7394 $subnets = explode(',', $subnetstr);
7395 $found = false;
7396 $addr = trim($addr);
7398 foreach ($subnets as $subnet) {
7399 $subnet = trim($subnet);
7400 if (strpos($subnet, '/') !== false) { /// type 1
7401 list($ip, $mask) = explode('/', $subnet);
7402 if (!is_number($mask) || $mask < 0 || $mask > 32) {
7403 continue;
7405 if ($mask == 0) {
7406 return true;
7408 if ($mask == 32) {
7409 if ($ip === $addr) {
7410 return true;
7412 continue;
7414 $mask = 0xffffffff << (32 - $mask);
7415 $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
7416 } else if (strpos($subnet, '-') !== false) {/// type 3
7417 $subnetparts = explode('.', $subnet);
7418 $addrparts = explode('.', $addr);
7419 $subnetrange = explode('-', array_pop($subnetparts));
7420 if (count($subnetrange) == 2) {
7421 $lastaddrpart = array_pop($addrparts);
7422 $found = ($subnetparts == $addrparts &&
7423 $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
7425 } else { /// type 2
7426 if ($subnet[strlen($subnet) - 1] != '.') {
7427 $subnet .= '.';
7429 $found = (strpos($addr . '.', $subnet) === 0);
7432 if ($found) {
7433 break;
7436 return $found;
7440 * This function sets the $HTTPSPAGEREQUIRED global
7441 * (used in some parts of moodle to change some links)
7442 * and calculate the proper wwwroot to be used
7444 * By using this function properly, we can ensure 100% https-ized pages
7445 * at our entire discretion (login, forgot_password, change_password)
7447 function httpsrequired() {
7449 global $CFG, $HTTPSPAGEREQUIRED;
7451 if (!empty($CFG->loginhttps)) {
7452 $HTTPSPAGEREQUIRED = true;
7453 $CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
7454 $CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
7456 // change theme URLs to https
7457 theme_setup();
7459 } else {
7460 $CFG->httpswwwroot = $CFG->wwwroot;
7461 $CFG->httpsthemewww = $CFG->themewww;
7466 * For outputting debugging info
7468 * @uses STDOUT
7469 * @param string $string ?
7470 * @param string $eol ?
7471 * @todo Finish documenting this function
7473 function mtrace($string, $eol="\n", $sleep=0) {
7475 if (defined('STDOUT')) {
7476 fwrite(STDOUT, $string.$eol);
7477 } else {
7478 echo $string . $eol;
7481 flush();
7483 //delay to keep message on user's screen in case of subsequent redirect
7484 if ($sleep) {
7485 sleep($sleep);
7489 //Replace 1 or more slashes or backslashes to 1 slash
7490 function cleardoubleslashes ($path) {
7491 return preg_replace('/(\/|\\\){1,}/','/',$path);
7494 function zip_files ($originalfiles, $destination) {
7495 //Zip an array of files/dirs to a destination zip file
7496 //Both parameters must be FULL paths to the files/dirs
7498 global $CFG;
7500 //Extract everything from destination
7501 $path_parts = pathinfo(cleardoubleslashes($destination));
7502 $destpath = $path_parts["dirname"]; //The path of the zip file
7503 $destfilename = $path_parts["basename"]; //The name of the zip file
7504 $extension = $path_parts["extension"]; //The extension of the file
7506 //If no file, error
7507 if (empty($destfilename)) {
7508 return false;
7511 //If no extension, add it
7512 if (empty($extension)) {
7513 $extension = 'zip';
7514 $destfilename = $destfilename.'.'.$extension;
7517 //Check destination path exists
7518 if (!is_dir($destpath)) {
7519 return false;
7522 //Check destination path is writable. TODO!!
7524 //Clean destination filename
7525 $destfilename = clean_filename($destfilename);
7527 //Now check and prepare every file
7528 $files = array();
7529 $origpath = NULL;
7531 foreach ($originalfiles as $file) { //Iterate over each file
7532 //Check for every file
7533 $tempfile = cleardoubleslashes($file); // no doubleslashes!
7534 //Calculate the base path for all files if it isn't set
7535 if ($origpath === NULL) {
7536 $origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
7538 //See if the file is readable
7539 if (!is_readable($tempfile)) { //Is readable
7540 continue;
7542 //See if the file/dir is in the same directory than the rest
7543 if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
7544 continue;
7546 //Add the file to the array
7547 $files[] = $tempfile;
7550 //Everything is ready:
7551 // -$origpath is the path where ALL the files to be compressed reside (dir).
7552 // -$destpath is the destination path where the zip file will go (dir).
7553 // -$files is an array of files/dirs to compress (fullpath)
7554 // -$destfilename is the name of the zip file (without path)
7556 //print_object($files); //Debug
7558 if (empty($CFG->zip)) { // Use built-in php-based zip function
7560 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7561 //rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
7562 $zipfiles = array();
7563 $start = strlen($origpath)+1;
7564 foreach($files as $file) {
7565 $tf = array();
7566 $tf[PCLZIP_ATT_FILE_NAME] = $file;
7567 $tf[PCLZIP_ATT_FILE_NEW_FULL_NAME] = substr($file, $start);
7568 $zipfiles[] = $tf;
7570 //create the archive
7571 $archive = new PclZip(cleardoubleslashes("$destpath/$destfilename"));
7572 if (($list = $archive->create($zipfiles) == 0)) {
7573 notice($archive->errorInfo(true));
7574 return false;
7577 } else { // Use external zip program
7579 $filestozip = "";
7580 foreach ($files as $filetozip) {
7581 $filestozip .= escapeshellarg(basename($filetozip));
7582 $filestozip .= " ";
7584 //Construct the command
7585 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7586 $command = 'cd '.escapeshellarg($origpath).$separator.
7587 escapeshellarg($CFG->zip).' -r '.
7588 escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
7589 //All converted to backslashes in WIN
7590 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7591 $command = str_replace('/','\\',$command);
7593 Exec($command);
7595 return true;
7598 function unzip_file ($zipfile, $destination = '', $showstatus = true) {
7599 //Unzip one zip file to a destination dir
7600 //Both parameters must be FULL paths
7601 //If destination isn't specified, it will be the
7602 //SAME directory where the zip file resides.
7604 global $CFG;
7606 //Extract everything from zipfile
7607 $path_parts = pathinfo(cleardoubleslashes($zipfile));
7608 $zippath = $path_parts["dirname"]; //The path of the zip file
7609 $zipfilename = $path_parts["basename"]; //The name of the zip file
7610 $extension = $path_parts["extension"]; //The extension of the file
7612 //If no file, error
7613 if (empty($zipfilename)) {
7614 return false;
7617 //If no extension, error
7618 if (empty($extension)) {
7619 return false;
7622 //Clear $zipfile
7623 $zipfile = cleardoubleslashes($zipfile);
7625 //Check zipfile exists
7626 if (!file_exists($zipfile)) {
7627 return false;
7630 //If no destination, passed let's go with the same directory
7631 if (empty($destination)) {
7632 $destination = $zippath;
7635 //Clear $destination
7636 $destpath = rtrim(cleardoubleslashes($destination), "/");
7638 //Check destination path exists
7639 if (!is_dir($destpath)) {
7640 return false;
7643 //Check destination path is writable. TODO!!
7645 //Everything is ready:
7646 // -$zippath is the path where the zip file resides (dir)
7647 // -$zipfilename is the name of the zip file (without path)
7648 // -$destpath is the destination path where the zip file will uncompressed (dir)
7650 $list = array();
7652 require_once("$CFG->libdir/filelib.php");
7654 do {
7655 $temppath = "$CFG->dataroot/temp/unzip/".random_string(10);
7656 } while (file_exists($temppath));
7657 if (!check_dir_exists($temppath, true, true)) {
7658 return false;
7661 if (empty($CFG->unzip)) { // Use built-in php-based unzip function
7663 include_once("$CFG->libdir/pclzip/pclzip.lib.php");
7664 $archive = new PclZip(cleardoubleslashes("$zippath/$zipfilename"));
7665 if (!$list = $archive->extract(PCLZIP_OPT_PATH, $temppath,
7666 PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename',
7667 PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $temppath)) {
7668 if (!empty($showstatus)) {
7669 notice($archive->errorInfo(true));
7671 return false;
7674 } else { // Use external unzip program
7676 $separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
7677 $redirection = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? '' : ' 2>&1';
7679 $command = 'cd '.escapeshellarg($zippath).$separator.
7680 escapeshellarg($CFG->unzip).' -o '.
7681 escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
7682 escapeshellarg($temppath).$redirection;
7683 //All converted to backslashes in WIN
7684 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7685 $command = str_replace('/','\\',$command);
7687 Exec($command,$list);
7690 unzip_process_temp_dir($temppath, $destpath);
7691 fulldelete($temppath);
7693 //Display some info about the unzip execution
7694 if ($showstatus) {
7695 unzip_show_status($list, $temppath, $destpath);
7698 return true;
7702 * Sanitize temporary unzipped files and move to target dir.
7703 * @param string $temppath path to temporary dir with unzip output
7704 * @param string $destpath destination path
7705 * @return void
7707 function unzip_process_temp_dir($temppath, $destpath) {
7708 global $CFG;
7710 $filepermissions = ($CFG->directorypermissions & 0666); // strip execute flags
7712 if (check_dir_exists($destpath, true, true)) {
7713 $currdir = opendir($temppath);
7714 while (false !== ($file = readdir($currdir))) {
7715 if ($file <> ".." && $file <> ".") {
7716 $fullfile = "$temppath/$file";
7717 if (is_link($fullfile)) {
7718 //somebody tries to sneak in symbolik link - no way!
7719 continue;
7721 $cleanfile = clean_param($file, PARAM_FILE); // no dangerous chars
7722 if ($cleanfile === '') {
7723 // invalid file name
7724 continue;
7726 if ($cleanfile !== $file and file_exists("$temppath/$cleanfile")) {
7727 // eh, weird chars collision detected
7728 continue;
7730 $descfile = "$destpath/$cleanfile";
7731 if (is_dir($fullfile)) {
7732 // recurse into subdirs
7733 unzip_process_temp_dir($fullfile, $descfile);
7735 if (is_file($fullfile)) {
7736 // rename and move the file
7737 if (file_exists($descfile)) {
7738 //override existing files
7739 unlink($descfile);
7741 rename($fullfile, $descfile);
7742 chmod($descfile, $filepermissions);
7746 closedir($currdir);
7750 function unzip_cleanfilename ($p_event, &$p_header) {
7751 //This function is used as callback in unzip_file() function
7752 //to clean illegal characters for given platform and to prevent directory traversal.
7753 //Produces the same result as info-zip unzip.
7754 $p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
7755 $p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
7756 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
7757 $p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
7758 $p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
7759 } else {
7760 //Add filtering for other systems here
7761 // BSD: none (tested)
7762 // Linux: ??
7763 // MacosX: ??
7765 $p_header['filename'] = cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
7766 return 1;
7769 function unzip_show_status($list, $removepath, $removepath2) {
7770 //This function shows the results of the unzip execution
7771 //depending of the value of the $CFG->zip, results will be
7772 //text or an array of files.
7774 global $CFG;
7776 if (empty($CFG->unzip)) { // Use built-in php-based zip function
7777 $strname = get_string("name");
7778 $strsize = get_string("size");
7779 $strmodified = get_string("modified");
7780 $strstatus = get_string("status");
7781 echo "<table width=\"640\">";
7782 echo "<tr><th class=\"header\" scope=\"col\">$strname</th>";
7783 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strsize</th>";
7784 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strmodified</th>";
7785 echo "<th class=\"header\" align=\"right\" scope=\"col\">$strstatus</th></tr>";
7786 foreach ($list as $item) {
7787 echo "<tr>";
7788 $item['filename'] = str_replace(cleardoubleslashes($removepath).'/', "", $item['filename']);
7789 $item['filename'] = str_replace(cleardoubleslashes($removepath2).'/', "", $item['filename']);
7790 echo '<td align="left" style="white-space:nowrap ">'.s(clean_param($item['filename'], PARAM_PATH)).'</td>';
7791 if (! $item['folder']) {
7792 echo '<td align="right" style="white-space:nowrap ">'.display_size($item['size']).'</td>';
7793 } else {
7794 echo "<td>&nbsp;</td>";
7796 $filedate = userdate($item['mtime'], get_string("strftimedatetime"));
7797 echo '<td align="right" style="white-space:nowrap ">'.$filedate.'</td>';
7798 echo '<td align="right" style="white-space:nowrap ">'.$item['status'].'</td>';
7799 echo "</tr>";
7801 echo "</table>";
7803 } else { // Use external zip program
7804 print_simple_box_start("center");
7805 echo "<pre>";
7806 foreach ($list as $item) {
7807 $item = str_replace(cleardoubleslashes($removepath.'/'), '', $item);
7808 $item = str_replace(cleardoubleslashes($removepath2.'/'), '', $item);
7809 echo s($item).'<br />';
7811 echo "</pre>";
7812 print_simple_box_end();
7817 * Returns most reliable client address
7819 * @return string The remote IP address
7821 define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
7822 define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
7823 function getremoteaddr() {
7824 global $CFG;
7826 if (empty($CFG->getremoteaddrconf)) {
7827 // This will happen, for example, before just after the upgrade, as the
7828 // user is redirected to the admin screen.
7829 $variablestoskip = 0;
7830 } else {
7831 $variablestoskip = $CFG->getremoteaddrconf;
7833 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
7834 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
7835 return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
7838 if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
7839 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
7840 return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
7843 if (!empty($_SERVER['REMOTE_ADDR'])) {
7844 return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
7845 } else {
7846 return null;
7851 * Cleans a remote address ready to put into the log table
7853 function cleanremoteaddr($addr) {
7854 $originaladdr = $addr;
7855 $matches = array();
7856 // first get all things that look like IP addresses.
7857 if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER)) {
7858 return '';
7860 $goodmatches = array();
7861 $lanmatches = array();
7862 foreach ($matches as $match) {
7863 // print_r($match);
7864 // check to make sure it's not an internal address.
7865 // the following are reserved for private lans...
7866 // 10.0.0.0 - 10.255.255.255
7867 // 172.16.0.0 - 172.31.255.255
7868 // 192.168.0.0 - 192.168.255.255
7869 // 169.254.0.0 -169.254.255.255
7870 $bits = explode('.',$match[0]);
7871 if (count($bits) != 4) {
7872 // weird, preg match shouldn't give us it.
7873 continue;
7875 if (($bits[0] == 10)
7876 || ($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
7877 || ($bits[0] == 192 && $bits[1] == 168)
7878 || ($bits[0] == 169 && $bits[1] == 254)) {
7879 $lanmatches[] = $match[0];
7880 continue;
7882 // finally, it's ok
7883 $goodmatches[] = $match[0];
7885 if (!count($goodmatches)) {
7886 // perhaps we have a lan match, it's probably better to return that.
7887 if (!count($lanmatches)) {
7888 return '';
7889 } else {
7890 return array_pop($lanmatches);
7893 if (count($goodmatches) == 1) {
7894 return $goodmatches[0];
7896 //Commented out following because there are so many, and it clogs the logs MDL-13544
7897 //error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
7899 // We need to return something, so return the first
7900 return array_pop($goodmatches);
7904 * file_put_contents is only supported by php 5.0 and higher
7905 * so if it is not predefined, define it here
7907 * @param $file full path of the file to write
7908 * @param $contents contents to be sent
7909 * @return number of bytes written (false on error)
7911 if(!function_exists('file_put_contents')) {
7912 function file_put_contents($file, $contents) {
7913 $result = false;
7914 if ($f = fopen($file, 'w')) {
7915 $result = fwrite($f, $contents);
7916 fclose($f);
7918 return $result;
7923 * The clone keyword is only supported from PHP 5 onwards.
7924 * The behaviour of $obj2 = $obj1 differs fundamentally
7925 * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
7926 * created, in PHP 5 $obj1 is referenced. To create a copy
7927 * in PHP 5 the clone keyword was introduced. This function
7928 * simulates this behaviour for PHP < 5.0.0.
7929 * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
7931 * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
7932 * Found a better implementation (more checks and possibilities) from PEAR:
7933 * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
7935 * @param object $obj
7936 * @return object
7938 if(!check_php_version('5.0.0')) {
7939 // the eval is needed to prevent PHP 5 from getting a parse error!
7940 eval('
7941 function clone($obj) {
7942 /// Sanity check
7943 if (!is_object($obj)) {
7944 user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
7945 return;
7948 /// Use serialize/unserialize trick to deep copy the object
7949 $obj = unserialize(serialize($obj));
7951 /// If there is a __clone method call it on the "new" class
7952 if (method_exists($obj, \'__clone\')) {
7953 $obj->__clone();
7956 return $obj;
7959 // Supply the PHP5 function scandir() to older versions.
7960 function scandir($directory) {
7961 $files = array();
7962 if ($dh = opendir($directory)) {
7963 while (($file = readdir($dh)) !== false) {
7964 $files[] = $file;
7966 closedir($dh);
7968 return $files;
7971 // Supply the PHP5 function array_combine() to older versions.
7972 function array_combine($keys, $values) {
7973 if (!is_array($keys) || !is_array($values) || count($keys) != count($values)) {
7974 return false;
7976 reset($values);
7977 $result = array();
7978 foreach ($keys as $key) {
7979 $result[$key] = current($values);
7980 next($values);
7982 return $result;
7988 * This function will make a complete copy of anything it's given,
7989 * regardless of whether it's an object or not.
7990 * @param mixed $thing
7991 * @return mixed
7993 function fullclone($thing) {
7994 return unserialize(serialize($thing));
7999 * This function expects to called during shutdown
8000 * should be set via register_shutdown_function()
8001 * in lib/setup.php .
8003 * Right now we do it only if we are under apache, to
8004 * make sure apache children that hog too much mem are
8005 * killed.
8008 function moodle_request_shutdown() {
8010 global $CFG;
8012 // initially, we are only ever called under apache
8013 // but check just in case
8014 if (function_exists('apache_child_terminate')
8015 && function_exists('memory_get_usage')
8016 && ini_get_bool('child_terminate')) {
8017 if (empty($CFG->apachemaxmem)) {
8018 $CFG->apachemaxmem = 25000000; // default 25MiB
8020 if (memory_get_usage() > (int)$CFG->apachemaxmem) {
8021 trigger_error('Mem usage over $CFG->apachemaxmem: marking child for reaping.');
8022 @apache_child_terminate();
8025 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
8026 if (defined('MDL_PERFTOLOG')) {
8027 $perf = get_performance_info();
8028 error_log("PERF: " . $perf['txt']);
8030 if (defined('MDL_PERFINC')) {
8031 $inc = get_included_files();
8032 $ts = 0;
8033 foreach($inc as $f) {
8034 if (preg_match(':^/:', $f)) {
8035 $fs = filesize($f);
8036 $ts += $fs;
8037 $hfs = display_size($fs);
8038 error_log(substr($f,strlen($CFG->dirroot)) . " size: $fs ($hfs)"
8039 , NULL, NULL, 0);
8040 } else {
8041 error_log($f , NULL, NULL, 0);
8044 if ($ts > 0 ) {
8045 $hts = display_size($ts);
8046 error_log("Total size of files included: $ts ($hts)");
8053 * If new messages are waiting for the current user, then return
8054 * Javascript code to create a popup window
8056 * @return string Javascript code
8058 function message_popup_window() {
8059 global $USER;
8061 $popuplimit = 30; // Minimum seconds between popups
8063 if (!defined('MESSAGE_WINDOW')) {
8064 if (!empty($USER->id) and !isguestuser()) {
8065 if (!isset($USER->message_lastpopup)) {
8066 $USER->message_lastpopup = 0;
8068 if ((time() - $USER->message_lastpopup) > $popuplimit) { /// It's been long enough
8069 if (get_user_preferences('message_showmessagewindow', 1) == 1) {
8070 if (count_records_select('message', 'useridto = \''.$USER->id.'\' AND timecreated > \''.$USER->message_lastpopup.'\'')) {
8071 $USER->message_lastpopup = time();
8072 return '<script type="text/javascript">'."\n//<![CDATA[\n openpopup('/message/index.php', 'message',
8073 'menubar=0,location=0,scrollbars,status,resizable,width=400,height=500', 0);\n//]]>\n</script>";
8080 return '';
8083 // Used to make sure that $min <= $value <= $max
8084 function bounded_number($min, $value, $max) {
8085 if($value < $min) {
8086 return $min;
8088 if($value > $max) {
8089 return $max;
8091 return $value;
8094 function array_is_nested($array) {
8095 foreach ($array as $value) {
8096 if (is_array($value)) {
8097 return true;
8100 return false;
8104 *** get_performance_info() pairs up with init_performance_info()
8105 *** loaded in setup.php. Returns an array with 'html' and 'txt'
8106 *** values ready for use, and each of the individual stats provided
8107 *** separately as well.
8110 function get_performance_info() {
8111 global $CFG, $PERF, $rcache;
8113 $info = array();
8114 $info['html'] = ''; // holds userfriendly HTML representation
8115 $info['txt'] = me() . ' '; // holds log-friendly representation
8117 $info['realtime'] = microtime_diff($PERF->starttime, microtime());
8119 $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
8120 $info['txt'] .= 'time: '.$info['realtime'].'s ';
8122 if (function_exists('memory_get_usage')) {
8123 $info['memory_total'] = memory_get_usage();
8124 $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
8125 $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
8126 $info['txt'] .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
8129 if (function_exists('memory_get_peak_usage')) {
8130 $info['memory_peak'] = memory_get_peak_usage();
8131 $info['html'] .= '<span class="memoryused">RAM peak: '.display_size($info['memory_peak']).'</span> ';
8132 $info['txt'] .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
8135 $inc = get_included_files();
8136 //error_log(print_r($inc,1));
8137 $info['includecount'] = count($inc);
8138 $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
8139 $info['txt'] .= 'includecount: '.$info['includecount'].' ';
8141 if (!empty($PERF->dbqueries)) {
8142 $info['dbqueries'] = $PERF->dbqueries;
8143 $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
8144 $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
8147 if (!empty($PERF->logwrites)) {
8148 $info['logwrites'] = $PERF->logwrites;
8149 $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
8150 $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
8153 if (!empty($PERF->profiling) && $PERF->profiling) {
8154 require_once($CFG->dirroot .'/lib/profilerlib.php');
8155 $info['html'] .= '<span class="profilinginfo">'.Profiler::get_profiling(array('-R')).'</span>';
8158 if (function_exists('posix_times')) {
8159 $ptimes = posix_times();
8160 if (is_array($ptimes)) {
8161 foreach ($ptimes as $key => $val) {
8162 $info[$key] = $ptimes[$key] - $PERF->startposixtimes[$key];
8164 $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
8165 $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
8169 // Grab the load average for the last minute
8170 // /proc will only work under some linux configurations
8171 // while uptime is there under MacOSX/Darwin and other unices
8172 if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
8173 list($server_load) = explode(' ', $loadavg[0]);
8174 unset($loadavg);
8175 } else if ( function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
8176 if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
8177 $server_load = $matches[1];
8178 } else {
8179 trigger_error('Could not parse uptime output!');
8182 if (!empty($server_load)) {
8183 $info['serverload'] = $server_load;
8184 $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
8185 $info['txt'] .= "serverload: {$info['serverload']} ";
8188 if (isset($rcache->hits) && isset($rcache->misses)) {
8189 $info['rcachehits'] = $rcache->hits;
8190 $info['rcachemisses'] = $rcache->misses;
8191 $info['html'] .= '<span class="rcache">Record cache hit/miss ratio : '.
8192 "{$rcache->hits}/{$rcache->misses}</span> ";
8193 $info['txt'] .= 'rcache: '.
8194 "{$rcache->hits}/{$rcache->misses} ";
8196 $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
8197 return $info;
8200 function apd_get_profiling() {
8201 return shell_exec('pprofp -u ' . ini_get('apd.dumpdir') . '/pprof.' . getmypid() . '.*');
8205 * Delete directory or only it's content
8206 * @param string $dir directory path
8207 * @param bool $content_only
8208 * @return bool success, true also if dir does not exist
8210 function remove_dir($dir, $content_only=false) {
8211 if (!file_exists($dir)) {
8212 // nothing to do
8213 return true;
8215 $handle = opendir($dir);
8216 $result = true;
8217 while (false!==($item = readdir($handle))) {
8218 if($item != '.' && $item != '..') {
8219 if(is_dir($dir.'/'.$item)) {
8220 $result = remove_dir($dir.'/'.$item) && $result;
8221 }else{
8222 $result = unlink($dir.'/'.$item) && $result;
8226 closedir($handle);
8227 if ($content_only) {
8228 return $result;
8230 return rmdir($dir); // if anything left the result will be false, noo need for && $result
8234 * Function to check if a directory exists and optionally create it.
8236 * @param string absolute directory path (must be under $CFG->dataroot)
8237 * @param boolean create directory if does not exist
8238 * @param boolean create directory recursively
8240 * @return boolean true if directory exists or created
8242 function check_dir_exists($dir, $create=false, $recursive=false) {
8244 global $CFG;
8246 if (strstr(cleardoubleslashes($dir), cleardoubleslashes($CFG->dataroot.'/')) === false) {
8247 debugging('Warning. Wrong call to check_dir_exists(). $dir must be an absolute path under $CFG->dataroot ("' . $dir . '" is incorrect)', DEBUG_DEVELOPER);
8250 $status = true;
8252 if(!is_dir($dir)) {
8253 if (!$create) {
8254 $status = false;
8255 } else {
8256 umask(0000);
8257 if ($recursive) {
8258 /// We are going to make it recursive under $CFG->dataroot only
8259 /// (will help sites running open_basedir security and others)
8260 $dir = str_replace(cleardoubleslashes($CFG->dataroot . '/'), '', cleardoubleslashes($dir));
8261 /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-(
8262 $dirs = explode('/', $dir); /// Extract path parts
8263 /// Iterate over each part with start point $CFG->dataroot
8264 $dir = $CFG->dataroot . '/';
8265 foreach ($dirs as $part) {
8266 if ($part == '') {
8267 continue;
8269 $dir .= $part.'/';
8270 if (!is_dir($dir)) {
8271 if (!mkdir($dir, $CFG->directorypermissions)) {
8272 $status = false;
8273 break;
8277 } else {
8278 $status = mkdir($dir, $CFG->directorypermissions);
8282 return $status;
8285 function report_session_error() {
8286 global $CFG, $FULLME;
8288 if (empty($CFG->lang)) {
8289 $CFG->lang = "en";
8291 // Set up default theme and locale
8292 theme_setup();
8293 moodle_setlocale();
8295 //clear session cookies
8296 if (check_php_version('5.2.0')) {
8297 //PHP 5.2.0
8298 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8299 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
8300 } else {
8301 setcookie('MoodleSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8302 setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
8304 //increment database error counters
8305 if (isset($CFG->session_error_counter)) {
8306 set_config('session_error_counter', 1 + $CFG->session_error_counter);
8307 } else {
8308 set_config('session_error_counter', 1);
8310 redirect($FULLME, get_string('sessionerroruser2', 'error'), 5);
8315 * Detect if an object or a class contains a given property
8316 * will take an actual object or the name of a class
8317 * @param mix $obj Name of class or real object to test
8318 * @param string $property name of property to find
8319 * @return bool true if property exists
8321 function object_property_exists( $obj, $property ) {
8322 if (is_string( $obj )) {
8323 $properties = get_class_vars( $obj );
8325 else {
8326 $properties = get_object_vars( $obj );
8328 return array_key_exists( $property, $properties );
8333 * Detect a custom script replacement in the data directory that will
8334 * replace an existing moodle script
8335 * @param string $urlpath path to the original script
8336 * @return string full path name if a custom script exists
8337 * @return bool false if no custom script exists
8339 function custom_script_path($urlpath='') {
8340 global $CFG;
8342 // set default $urlpath, if necessary
8343 if (empty($urlpath)) {
8344 $urlpath = qualified_me(); // e.g. http://www.this-server.com/moodle/this-script.php
8347 // $urlpath is invalid if it is empty or does not start with the Moodle wwwroot
8348 if (empty($urlpath) or (strpos($urlpath, $CFG->wwwroot) === false )) {
8349 return false;
8352 // replace wwwroot with the path to the customscripts folder and clean path
8353 $scriptpath = $CFG->customscripts . clean_param(substr($urlpath, strlen($CFG->wwwroot)), PARAM_PATH);
8355 // remove the query string, if any
8356 if (($strpos = strpos($scriptpath, '?')) !== false) {
8357 $scriptpath = substr($scriptpath, 0, $strpos);
8360 // remove trailing slashes, if any
8361 $scriptpath = rtrim($scriptpath, '/\\');
8363 // append index.php, if necessary
8364 if (is_dir($scriptpath)) {
8365 $scriptpath .= '/index.php';
8368 // check the custom script exists
8369 if (file_exists($scriptpath)) {
8370 return $scriptpath;
8371 } else {
8372 return false;
8377 * Wrapper function to load necessary editor scripts
8378 * to $CFG->editorsrc array. Params can be coursei id
8379 * or associative array('courseid' => value, 'name' => 'editorname').
8380 * @uses $CFG
8381 * @param mixed $args Courseid or associative array.
8383 function loadeditor($args) {
8384 global $CFG;
8385 include($CFG->libdir .'/editorlib.php');
8386 return editorObject::loadeditor($args);
8390 * Returns whether or not the user object is a remote MNET user. This function
8391 * is in moodlelib because it does not rely on loading any of the MNET code.
8393 * @param object $user A valid user object
8394 * @return bool True if the user is from a remote Moodle.
8396 function is_mnet_remote_user($user) {
8397 global $CFG;
8399 if (!isset($CFG->mnet_localhost_id)) {
8400 include_once $CFG->dirroot . '/mnet/lib.php';
8401 $env = new mnet_environment();
8402 $env->init();
8403 unset($env);
8406 return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
8410 * Checks if a given plugin is in the list of enabled enrolment plugins.
8412 * @param string $auth Enrolment plugin.
8413 * @return boolean Whether the plugin is enabled.
8415 function is_enabled_enrol($enrol='') {
8416 global $CFG;
8418 // use the global default if not specified
8419 if ($enrol == '') {
8420 $enrol = $CFG->enrol;
8422 return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
8426 * This function will search for browser prefereed languages, setting Moodle
8427 * to use the best one available if $SESSION->lang is undefined
8429 function setup_lang_from_browser() {
8431 global $CFG, $SESSION, $USER;
8433 if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
8434 // Lang is defined in session or user profile, nothing to do
8435 return;
8438 if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do
8439 return;
8442 /// Extract and clean langs from headers
8443 $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
8444 $rawlangs = str_replace('-', '_', $rawlangs); // we are using underscores
8445 $rawlangs = explode(',', $rawlangs); // Convert to array
8446 $langs = array();
8448 $order = 1.0;
8449 foreach ($rawlangs as $lang) {
8450 if (strpos($lang, ';') === false) {
8451 $langs[(string)$order] = $lang;
8452 $order = $order-0.01;
8453 } else {
8454 $parts = explode(';', $lang);
8455 $pos = strpos($parts[1], '=');
8456 $langs[substr($parts[1], $pos+1)] = $parts[0];
8459 krsort($langs, SORT_NUMERIC);
8461 $langlist = get_list_of_languages();
8463 /// Look for such langs under standard locations
8464 foreach ($langs as $lang) {
8465 $lang = strtolower(clean_param($lang.'_utf8', PARAM_SAFEDIR)); // clean it properly for include
8466 if (!array_key_exists($lang, $langlist)) {
8467 continue; // language not allowed, try next one
8469 if (file_exists($CFG->dataroot .'/lang/'. $lang) or file_exists($CFG->dirroot .'/lang/'. $lang)) {
8470 $SESSION->lang = $lang; /// Lang exists, set it in session
8471 break; /// We have finished. Go out
8474 return;
8478 ////////////////////////////////////////////////////////////////////////////////
8480 function is_newnav($navigation) {
8481 if (is_array($navigation) && !empty($navigation['newnav'])) {
8482 return true;
8483 } else {
8484 return false;
8489 * Checks whether the given variable name is defined as a variable within the given object.
8490 * @note This will NOT work with stdClass objects, which have no class variables.
8491 * @param string $var The variable name
8492 * @param object $object The object to check
8493 * @return boolean
8495 function in_object_vars($var, $object) {
8496 $class_vars = get_class_vars(get_class($object));
8497 $class_vars = array_keys($class_vars);
8498 return in_array($var, $class_vars);
8502 * Returns an array without repeated objects.
8503 * This function is similar to array_unique, but for arrays that have objects as values
8505 * @param unknown_type $array
8506 * @param unknown_type $keep_key_assoc
8507 * @return unknown
8509 function object_array_unique($array, $keep_key_assoc = true) {
8510 $duplicate_keys = array();
8511 $tmp = array();
8513 foreach ($array as $key=>$val) {
8514 // convert objects to arrays, in_array() does not support objects
8515 if (is_object($val)) {
8516 $val = (array)$val;
8519 if (!in_array($val, $tmp)) {
8520 $tmp[] = $val;
8521 } else {
8522 $duplicate_keys[] = $key;
8526 foreach ($duplicate_keys as $key) {
8527 unset($array[$key]);
8530 return $keep_key_assoc ? $array : array_values($array);
8534 * Returns the language string for the given plugin.
8536 * @param string $plugin the plugin code name
8537 * @param string $type the type of plugin (mod, block, filter)
8538 * @return string The plugin language string
8540 function get_plugin_name($plugin, $type='mod') {
8541 $plugin_name = '';
8543 switch ($type) {
8544 case 'mod':
8545 $plugin_name = get_string('modulename', $plugin);
8546 break;
8547 case 'blocks':
8548 $plugin_name = get_string('blockname', "block_$plugin");
8549 if (empty($plugin_name) || $plugin_name == '[[blockname]]') {
8550 if (($block = block_instance($plugin)) !== false) {
8551 $plugin_name = $block->get_title();
8552 } else {
8553 $plugin_name = "[[$plugin]]";
8556 break;
8557 case 'filter':
8558 $plugin_name = trim(get_string('filtername', $plugin));
8559 if (empty($plugin_name) or ($plugin_name == '[[filtername]]')) {
8560 $textlib = textlib_get_instance();
8561 $plugin_name = $textlib->strtotitle($plugin);
8563 break;
8564 default:
8565 $plugin_name = $plugin;
8566 break;
8569 return $plugin_name;
8573 * Is a userid the primary administrator?
8575 * @param $userid int id of user to check
8576 * @return boolean
8578 function is_primary_admin($userid){
8579 $primaryadmin = get_admin();
8581 if($userid == $primaryadmin->id){
8582 return true;
8583 }else{
8584 return false;
8589 * @return string $CFG->siteidentifier, first making sure it is properly initialised.
8591 function get_site_identifier() {
8592 global $CFG;
8593 // Check to see if it is missing. If so, initialise it.
8594 if (empty($CFG->siteidentifier)) {
8595 set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
8597 // Return it.
8598 return $CFG->siteidentifier;
8601 // vim:autoindent:expandtab:shiftwidth=4:tabstop=4:tw=140: