Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

8599 lines (7335 sloc) 285.888 kb
<?php // $Id$
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.org //
// //
// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation; either version 2 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
/**
* moodlelib.php - Moodle main library
*
* Main library file of miscellaneous general-purpose Moodle functions.
* Other main libraries:
* - weblib.php - functions that produce web output
* - datalib.php - functions that access the database
* @author Martin Dougiamas
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package moodlecore
*/
/// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
/**
* Used by some scripts to check they are being called by Moodle
*/
define('MOODLE_INTERNAL', true);
/// Date and time constants ///
/**
* Time constant - the number of seconds in a year
*/
define('YEARSECS', 31536000);
/**
* Time constant - the number of seconds in a week
*/
define('WEEKSECS', 604800);
/**
* Time constant - the number of seconds in a day
*/
define('DAYSECS', 86400);
/**
* Time constant - the number of seconds in an hour
*/
define('HOURSECS', 3600);
/**
* Time constant - the number of seconds in a minute
*/
define('MINSECS', 60);
/**
* Time constant - the number of minutes in a day
*/
define('DAYMINS', 1440);
/**
* Time constant - the number of minutes in an hour
*/
define('HOURMINS', 60);
/// Parameter constants - every call to optional_param(), required_param() ///
/// or clean_param() should have a specified type of parameter. //////////////
/**
* PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
* originally was 0, but changed because we need to detect unknown
* parameter types and swiched order in clean_param().
*/
define('PARAM_RAW', 666);
/**
* PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
* It was one of the first types, that is why it is abused so much ;-)
*/
define('PARAM_CLEAN', 0x0001);
/**
* PARAM_INT - integers only, use when expecting only numbers.
*/
define('PARAM_INT', 0x0002);
/**
* PARAM_INTEGER - an alias for PARAM_INT
*/
define('PARAM_INTEGER', 0x0002);
/**
* PARAM_NUMBER - a real/floating point number.
*/
define('PARAM_NUMBER', 0x000a);
/**
* PARAM_ALPHA - contains only english letters.
*/
define('PARAM_ALPHA', 0x0004);
/**
* PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
* @TODO: should we alias it to PARAM_ALPHANUM ?
*/
define('PARAM_ACTION', 0x0004);
/**
* PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
* @TODO: should we alias it to PARAM_ALPHANUM ?
*/
define('PARAM_FORMAT', 0x0004);
/**
* PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
*/
define('PARAM_NOTAGS', 0x0008);
/**
* PARAM_MULTILANG - alias of PARAM_TEXT.
*/
define('PARAM_MULTILANG', 0x0009);
/**
* PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
*/
define('PARAM_TEXT', 0x0009);
/**
* PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
*/
define('PARAM_FILE', 0x0010);
/**
* PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
*/
define('PARAM_TAG', 0x0011);
/**
* PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
*/
define('PARAM_TAGLIST', 0x0012);
/**
* PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
* note: the leading slash is not removed, window drive letter is not allowed
*/
define('PARAM_PATH', 0x0020);
/**
* PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
*/
define('PARAM_HOST', 0x0040);
/**
* PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
*/
define('PARAM_URL', 0x0080);
/**
* 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!)
*/
define('PARAM_LOCALURL', 0x0180);
/**
* PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
* use when you want to store a new file submitted by students
*/
define('PARAM_CLEANFILE',0x0200);
/**
* PARAM_ALPHANUM - expected numbers and letters only.
*/
define('PARAM_ALPHANUM', 0x0400);
/**
* PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
*/
define('PARAM_BOOL', 0x0800);
/**
* PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
* note: do not forget to addslashes() before storing into database!
*/
define('PARAM_CLEANHTML',0x1000);
/**
* PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
* suitable for include() and require()
* @TODO: should we rename this function to PARAM_SAFEDIRS??
*/
define('PARAM_ALPHAEXT', 0x2000);
/**
* PARAM_SAFEDIR - safe directory name, suitable for include() and require()
*/
define('PARAM_SAFEDIR', 0x4000);
/**
* PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
*/
define('PARAM_SEQUENCE', 0x8000);
/**
* PARAM_PEM - Privacy Enhanced Mail format
*/
define('PARAM_PEM', 0x10000);
/**
* PARAM_BASE64 - Base 64 encoded format
*/
define('PARAM_BASE64', 0x20000);
/// Page types ///
/**
* PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
*/
define('PAGE_COURSE_VIEW', 'course-view');
/// Debug levels ///
/** no warnings at all */
define ('DEBUG_NONE', 0);
/** E_ERROR | E_PARSE */
define ('DEBUG_MINIMAL', 5);
/** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
define ('DEBUG_NORMAL', 15);
/** E_ALL without E_STRICT for now, do show recoverable fatal errors */
define ('DEBUG_ALL', 6143);
/** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
define ('DEBUG_DEVELOPER', 38911);
/**
* Blog access level constant declaration
*/
define ('BLOG_USER_LEVEL', 1);
define ('BLOG_GROUP_LEVEL', 2);
define ('BLOG_COURSE_LEVEL', 3);
define ('BLOG_SITE_LEVEL', 4);
define ('BLOG_GLOBAL_LEVEL', 5);
/**
* Tag constanst
*/
//To prevent problems with multibytes strings, this should not exceed the
//length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
define('TAG_MAX_LENGTH', 50);
/**
* Password policy constants
*/
define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
define ('PASSWORD_DIGITS', '0123456789');
define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
define('SORT_LOCALE_STRING', SORT_STRING);
}
/// PARAMETER HANDLING ////////////////////////////////////////////////////
/**
* Returns a particular value for the named variable, taken from
* POST or GET. If the parameter doesn't exist then an error is
* thrown because we require this variable.
*
* This function should be used to initialise all required values
* in a script that are based on parameters. Usually it will be
* used like this:
* $id = required_param('id');
*
* @param string $parname the name of the page parameter we want
* @param int $type expected type of parameter
* @return mixed
*/
function required_param($parname, $type=PARAM_CLEAN) {
// detect_unchecked_vars addition
global $CFG;
if (!empty($CFG->detect_unchecked_vars)) {
global $UNCHECKED_VARS;
unset ($UNCHECKED_VARS->vars[$parname]);
}
if (isset($_POST[$parname])) { // POST has precedence
$param = $_POST[$parname];
} else if (isset($_GET[$parname])) {
$param = $_GET[$parname];
} else {
error('A required parameter ('.$parname.') was missing');
}
return clean_param($param, $type);
}
/**
* Returns a particular value for the named variable, taken from
* POST or GET, otherwise returning a given default.
*
* This function should be used to initialise all optional values
* in a script that are based on parameters. Usually it will be
* used like this:
* $name = optional_param('name', 'Fred');
*
* @param string $parname the name of the page parameter we want
* @param mixed $default the default value to return if nothing is found
* @param int $type expected type of parameter
* @return mixed
*/
function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
// detect_unchecked_vars addition
global $CFG;
if (!empty($CFG->detect_unchecked_vars)) {
global $UNCHECKED_VARS;
unset ($UNCHECKED_VARS->vars[$parname]);
}
if (isset($_POST[$parname])) { // POST has precedence
$param = $_POST[$parname];
} else if (isset($_GET[$parname])) {
$param = $_GET[$parname];
} else {
return $default;
}
return clean_param($param, $type);
}
/**
* Used by {@link optional_param()} and {@link required_param()} to
* clean the variables and/or cast to specific types, based on
* an options field.
* <code>
* $course->format = clean_param($course->format, PARAM_ALPHA);
* $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
* </code>
*
* @uses $CFG
* @uses PARAM_RAW
* @uses PARAM_CLEAN
* @uses PARAM_CLEANHTML
* @uses PARAM_INT
* @uses PARAM_NUMBER
* @uses PARAM_ALPHA
* @uses PARAM_ALPHANUM
* @uses PARAM_ALPHAEXT
* @uses PARAM_SEQUENCE
* @uses PARAM_BOOL
* @uses PARAM_NOTAGS
* @uses PARAM_TEXT
* @uses PARAM_SAFEDIR
* @uses PARAM_CLEANFILE
* @uses PARAM_FILE
* @uses PARAM_PATH
* @uses PARAM_HOST
* @uses PARAM_URL
* @uses PARAM_LOCALURL
* @uses PARAM_PEM
* @uses PARAM_BASE64
* @uses PARAM_TAG
* @uses PARAM_SEQUENCE
* @param mixed $param the variable we are cleaning
* @param int $type expected format of param after cleaning.
* @return mixed
*/
function clean_param($param, $type) {
global $CFG;
if (is_array($param)) { // Let's loop
$newparam = array();
foreach ($param as $key => $value) {
$newparam[$key] = clean_param($value, $type);
}
return $newparam;
}
switch ($type) {
case PARAM_RAW: // no cleaning at all
return $param;
case PARAM_CLEAN: // General HTML cleaning, try to use more specific type if possible
if (is_numeric($param)) {
return $param;
}
$param = stripslashes($param); // Needed for kses to work fine
$param = clean_text($param); // Sweep for scripts, etc
return addslashes($param); // Restore original request parameter slashes
case PARAM_CLEANHTML: // prepare html fragment for display, do not store it into db!!
$param = stripslashes($param); // Remove any slashes
$param = clean_text($param); // Sweep for scripts, etc
return trim($param);
case PARAM_INT:
return (int)$param; // Convert to integer
case PARAM_NUMBER:
return (float)$param; // Convert to integer
case PARAM_ALPHA: // Remove everything not a-z
return preg_replace('/[^a-zA-Z]/i', '', $param);
case PARAM_ALPHANUM: // Remove everything not a-zA-Z0-9
return preg_replace('/[^A-Za-z0-9]/i', '', $param);
case PARAM_ALPHAEXT: // Remove everything not a-zA-Z/_-
return preg_replace('/[^a-zA-Z\/_-]/i', '', $param);
case PARAM_SEQUENCE: // Remove everything not 0-9,
return preg_replace('/[^0-9,]/i', '', $param);
case PARAM_BOOL: // Convert to 1 or 0
$tempstr = strtolower($param);
if ($tempstr == 'on' or $tempstr == 'yes' ) {
$param = 1;
} else if ($tempstr == 'off' or $tempstr == 'no') {
$param = 0;
} else {
$param = empty($param) ? 0 : 1;
}
return $param;
case PARAM_NOTAGS: // Strip all tags
return strip_tags($param);
case PARAM_TEXT: // leave only tags needed for multilang
return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
case PARAM_SAFEDIR: // Remove everything not a-zA-Z0-9_-
return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
case PARAM_CLEANFILE: // allow only safe characters
return clean_filename($param);
case PARAM_FILE: // Strip all suspicious characters from filename
$param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
$param = preg_replace('~\.\.+~', '', $param);
if ($param === '.') {
$param = '';
}
return $param;
case PARAM_PATH: // Strip all suspicious characters from file path
$param = str_replace('\\', '/', $param);
$param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $param);
$param = preg_replace('~\.\.+~', '', $param);
$param = preg_replace('~//+~', '/', $param);
return preg_replace('~/(\./)+~', '/', $param);
case PARAM_HOST: // allow FQDN or IPv4 dotted quad
$param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
// match ipv4 dotted quad
if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
// confirm values are ok
if ( $match[0] > 255
|| $match[1] > 255
|| $match[3] > 255
|| $match[4] > 255 ) {
// hmmm, what kind of dotted quad is this?
$param = '';
}
} elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
&& !preg_match('/^[\.-]/', $param) // no leading dots/hyphens
&& !preg_match('/[\.-]$/', $param) // no trailing dots/hyphens
) {
// all is ok - $param is respected
} else {
// all is not ok...
$param='';
}
return $param;
case PARAM_URL: // allow safe ftp, http, mailto urls
include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
// all is ok, param is respected
} else {
$param =''; // not really ok
}
return $param;
case PARAM_LOCALURL: // allow http absolute, root relative and relative URLs within wwwroot
$param = clean_param($param, PARAM_URL);
if (!empty($param)) {
if (preg_match(':^/:', $param)) {
// root-relative, ok!
} elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
// absolute, and matches our wwwroot
} else {
// relative - let's make sure there are no tricks
if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
// looks ok.
} else {
$param = '';
}
}
}
return $param;
case PARAM_PEM:
$param = trim($param);
// PEM formatted strings may contain letters/numbers and the symbols
// forward slash: /
// plus sign: +
// equal sign: =
// , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
list($wholething, $body) = $matches;
unset($wholething, $matches);
$b64 = clean_param($body, PARAM_BASE64);
if (!empty($b64)) {
return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
} else {
return '';
}
}
return '';
case PARAM_BASE64:
if (!empty($param)) {
// PEM formatted strings may contain letters/numbers and the symbols
// forward slash: /
// plus sign: +
// equal sign: =
if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
return '';
}
$lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
// Each line of base64 encoded data must be 64 characters in
// length, except for the last line which may be less than (or
// equal to) 64 characters long.
for ($i=0, $j=count($lines); $i < $j; $i++) {
if ($i + 1 == $j) {
if (64 < strlen($lines[$i])) {
return '';
}
continue;
}
if (64 != strlen($lines[$i])) {
return '';
}
}
return implode("\n",$lines);
} else {
return '';
}
case PARAM_TAG:
//as long as magic_quotes_gpc is used, a backslash will be a
//problem, so remove *all* backslash.
$param = str_replace('\\', '', $param);
//convert many whitespace chars into one
$param = preg_replace('/\s+/', ' ', $param);
$textlib = textlib_get_instance();
$param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
return $param;
case PARAM_TAGLIST:
$tags = explode(',', $param);
$result = array();
foreach ($tags as $tag) {
$res = clean_param($tag, PARAM_TAG);
if ($res != '') {
$result[] = $res;
}
}
if ($result) {
return implode(',', $result);
} else {
return '';
}
default: // throw error, switched parameters in optional_param or another serious problem
error("Unknown parameter type: $type");
}
}
/**
* Return true if given value is integer or string with integer value
*
* @param mixed $value String or Int
* @return bool true if number, false if not
*/
function is_number($value) {
if (is_int($value)) {
return true;
} else if (is_string($value)) {
return ((string)(int)$value) === $value;
} else {
return false;
}
}
/**
* This function is useful for testing whether something you got back from
* the HTML editor actually contains anything. Sometimes the HTML editor
* appear to be empty, but actually you get back a <br> tag or something.
*
* @param string $string a string containing HTML.
* @return boolean does the string contain any actual content - that is text,
* images, objcts, etc.
*/
function html_is_blank($string) {
return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
}
/**
* Set a key in global configuration
*
* Set a key/value pair in both this session's {@link $CFG} global variable
* and in the 'config' database table for future sessions.
*
* Can also be used to update keys for plugin-scoped configs in config_plugin table.
* In that case it doesn't affect $CFG.
*
* A NULL value will delete the entry.
*
* @param string $name the key to set
* @param string $value the value to set (without magic quotes)
* @param string $plugin (optional) the plugin scope
* @uses $CFG
* @return bool
*/
function set_config($name, $value, $plugin=NULL) {
/// No need for get_config because they are usually always available in $CFG
global $CFG;
if (empty($plugin)) {
if (!array_key_exists($name, $CFG->config_php_settings)) {
// So it's defined for this invocation at least
if (is_null($value)) {
unset($CFG->$name);
} else {
$CFG->$name = (string)$value; // settings from db are always strings
}
}
if (get_field('config', 'name', 'name', $name)) {
if ($value===null) {
return delete_records('config', 'name', $name);
} else {
return set_field('config', 'value', addslashes($value), 'name', $name);
}
} else {
if ($value===null) {
return true;
}
$config = new object();
$config->name = $name;
$config->value = addslashes($value);
return insert_record('config', $config);
}
} else { // plugin scope
if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
if ($value===null) {
return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
} else {
return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
}
} else {
if ($value===null) {
return true;
}
$config = new object();
$config->plugin = addslashes($plugin);
$config->name = $name;
$config->value = addslashes($value);
return insert_record('config_plugins', $config);
}
}
}
/**
* Get configuration values from the global config table
* or the config_plugins table.
*
* If called with no parameters it will do the right thing
* generating $CFG safely from the database without overwriting
* existing values.
*
* If called with 2 parameters it will return a $string single
* value or false of the value is not found.
*
* @param string $plugin
* @param string $name
* @uses $CFG
* @return hash-like object or single value
*
*/
function get_config($plugin=NULL, $name=NULL) {
global $CFG;
if (!empty($name)) { // the user is asking for a specific value
if (!empty($plugin)) {
return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
} else {
return get_field('config', 'value', 'name', $name);
}
}
// the user is after a recordset
if (!empty($plugin)) {
if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
$configs = (array)$configs;
$localcfg = array();
foreach ($configs as $config) {
$localcfg[$config->name] = $config->value;
}
return (object)$localcfg;
} else {
return false;
}
} else {
// this was originally in setup.php
if ($configs = get_records('config')) {
$localcfg = (array)$CFG;
foreach ($configs as $config) {
if (!isset($localcfg[$config->name])) {
$localcfg[$config->name] = $config->value;
}
// do not complain anymore if config.php overrides settings from db
}
$localcfg = (object)$localcfg;
return $localcfg;
} else {
// preserve $CFG if DB returns nothing or error
return $CFG;
}
}
}
/**
* Removes a key from global configuration
*
* @param string $name the key to set
* @param string $plugin (optional) the plugin scope
* @uses $CFG
* @return bool
*/
function unset_config($name, $plugin=NULL) {
global $CFG;
unset($CFG->$name);
if (empty($plugin)) {
return delete_records('config', 'name', $name);
} else {
return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
}
}
/**
* Get volatile flags
*
* @param string $type
* @param int $changedsince
* @return records array
*
*/
function get_cache_flags($type, $changedsince=NULL) {
$type = addslashes($type);
$sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
if ($changedsince !== NULL) {
$changedsince = (int)$changedsince;
$sqlwhere .= ' AND timemodified > ' . $changedsince;
}
$cf = array();
if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
foreach ($flags as $flag) {
$cf[$flag->name] = $flag->value;
}
}
return $cf;
}
/**
* Use this funciton to get a list of users from a config setting of type admin_setting_users_with_capability.
* @param string $value the value of the config setting.
* @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
* @return array of user objects.
*/
function get_users_from_config($value, $capability) {
global $CFG;
if ($value == '$@ALL@$') {
$users = get_users_by_capability(get_context_instance(CONTEXT_SYSTEM), $capability);
} else if ($value) {
$usernames = explode(',', $value);
$users = get_records_select('user', "username IN ('" . implode("','", $usernames) . "') AND mnethostid = " . $CFG->mnet_localhost_id);
} else {
$users = array();
}
return $users;
}
/**
* Get volatile flags
*
* @param string $type
* @param string $name
* @param int $changedsince
* @return records array
*
*/
function get_cache_flag($type, $name, $changedsince=NULL) {
$type = addslashes($type);
$name = addslashes($name);
$sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
if ($changedsince !== NULL) {
$changedsince = (int)$changedsince;
$sqlwhere .= ' AND timemodified > ' . $changedsince;
}
return get_field_select('cache_flags', 'value', $sqlwhere);
}
/**
* Set a volatile flag
*
* @param string $type the "type" namespace for the key
* @param string $name the key to set
* @param string $value the value to set (without magic quotes) - NULL will remove the flag
* @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
* @return bool
*/
function set_cache_flag($type, $name, $value, $expiry=NULL) {
$timemodified = time();
if ($expiry===NULL || $expiry < $timemodified) {
$expiry = $timemodified + 24 * 60 * 60;
} else {
$expiry = (int)$expiry;
}
if ($value === NULL) {
return unset_cache_flag($type,$name);
}
$type = addslashes($type);
$name = addslashes($name);
if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
return true; //no need to update; helps rcache too
}
$f->value = addslashes($value);
$f->expiry = $expiry;
$f->timemodified = $timemodified;
return update_record('cache_flags', $f);
} else {
$f = new object();
$f->flagtype = $type;
$f->name = $name;
$f->value = addslashes($value);
$f->expiry = $expiry;
$f->timemodified = $timemodified;
return (bool)insert_record('cache_flags', $f);
}
}
/**
* Removes a single volatile flag
*
* @param string $type the "type" namespace for the key
* @param string $name the key to set
* @uses $CFG
* @return bool
*/
function unset_cache_flag($type, $name) {
return delete_records('cache_flags',
'name', addslashes($name),
'flagtype', addslashes($type));
}
/**
* Garbage-collect volatile flags
*
*/
function gc_cache_flags() {
return delete_records_select('cache_flags', 'expiry < ' . time());
}
/**
* Refresh current $USER session global variable with all their current preferences.
* @uses $USER
*/
function reload_user_preferences() {
global $USER;
//reset preference
$USER->preference = array();
if (!isloggedin() or isguestuser()) {
// no permanent storage for not-logged-in user and guest
} else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
foreach ($preferences as $preference) {
$USER->preference[$preference->name] = $preference->value;
}
}
return true;
}
/**
* Sets a preference for the current user
* Optionally, can set a preference for a different user object
* @uses $USER
* @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
* @param string $name The key to set as preference for the specified user
* @param string $value The value to set forthe $name key in the specified user's record
* @param int $otheruserid A moodle user ID
* @return bool
*/
function set_user_preference($name, $value, $otheruserid=NULL) {
global $USER;
if (!isset($USER->preference)) {
reload_user_preferences();
}
if (empty($name)) {
return false;
}
$nostore = false;
if (empty($otheruserid)){
if (!isloggedin() or isguestuser()) {
$nostore = true;
}
$userid = $USER->id;
} else {
if (isguestuser($otheruserid)) {
$nostore = true;
}
$userid = $otheruserid;
}
$return = true;
if ($nostore) {
// no permanent storage for not-logged-in user and guest
} else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
if ($preference->value === $value) {
return true;
}
if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
$return = false;
}
} else {
$preference = new object();
$preference->userid = $userid;
$preference->name = addslashes($name);
$preference->value = addslashes((string)$value);
if (!insert_record('user_preferences', $preference)) {
$return = false;
}
}
// update value in USER session if needed
if ($userid == $USER->id) {
$USER->preference[$name] = (string)$value;
}
return $return;
}
/**
* Unsets a preference completely by deleting it from the database
* Optionally, can set a preference for a different user id
* @uses $USER
* @param string $name The key to unset as preference for the specified user
* @param int $otheruserid A moodle user ID
*/
function unset_user_preference($name, $otheruserid=NULL) {
global $USER;
if (!isset($USER->preference)) {
reload_user_preferences();
}
if (empty($otheruserid)){
$userid = $USER->id;
} else {
$userid = $otheruserid;
}
//Delete the preference from $USER if needed
if ($userid == $USER->id) {
unset($USER->preference[$name]);
}
//Then from DB
return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
}
/**
* Sets a whole array of preferences for the current user
* @param array $prefarray An array of key/value pairs to be set
* @param int $otheruserid A moodle user ID
* @return bool
*/
function set_user_preferences($prefarray, $otheruserid=NULL) {
if (!is_array($prefarray) or empty($prefarray)) {
return false;
}
$return = true;
foreach ($prefarray as $name => $value) {
// The order is important; test for return is done first
$return = (set_user_preference($name, $value, $otheruserid) && $return);
}
return $return;
}
/**
* If no arguments are supplied this function will return
* all of the current user preferences as an array.
* If a name is specified then this function
* attempts to return that particular preference value. If
* none is found, then the optional value $default is returned,
* otherwise NULL.
* @param string $name Name of the key to use in finding a preference value
* @param string $default Value to be returned if the $name key is not set in the user preferences
* @param int $otheruserid A moodle user ID
* @uses $USER
* @return string
*/
function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
global $USER;
if (!isset($USER->preference)) {
reload_user_preferences();
}
if (empty($otheruserid)){
$userid = $USER->id;
} else {
$userid = $otheruserid;
}
if ($userid == $USER->id) {
$preference = $USER->preference;
} else {
$preference = array();
if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
foreach ($prefdata as $pref) {
$preference[$pref->name] = $pref->value;
}
}
}
if (empty($name)) {
return $preference; // All values
} else if (array_key_exists($name, $preference)) {
return $preference[$name]; // The single value
} else {
return $default; // Default value (or NULL)
}
}
/// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
/**
* Given date parts in user time produce a GMT timestamp.
*
* @param int $year The year part to create timestamp of
* @param int $month The month part to create timestamp of
* @param int $day The day part to create timestamp of
* @param int $hour The hour part to create timestamp of
* @param int $minute The minute part to create timestamp of
* @param int $second The second part to create timestamp of
* @param float $timezone ?
* @param bool $applydst ?
* @return int timestamp
* @todo Finish documenting this function
*/
function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
$strtimezone = NULL;
if (!is_numeric($timezone)) {
$strtimezone = $timezone;
}
$timezone = get_user_timezone_offset($timezone);
if (abs($timezone) > 13) {
$time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
} else {
$time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
$time = usertime($time, $timezone);
if($applydst) {
$time -= dst_offset_on($time, $strtimezone);
}
}
return $time;
}
/**
* Given an amount of time in seconds, returns string
* formatted nicely as weeks, days, hours etc as needed
*
* @uses MINSECS
* @uses HOURSECS
* @uses DAYSECS
* @uses YEARSECS
* @param int $totalsecs ?
* @param array $str ?
* @return string
*/
function format_time($totalsecs, $str=NULL) {
$totalsecs = abs($totalsecs);
if (!$str) { // Create the str structure the slow way
$str->day = get_string('day');
$str->days = get_string('days');
$str->hour = get_string('hour');
$str->hours = get_string('hours');
$str->min = get_string('min');
$str->mins = get_string('mins');
$str->sec = get_string('sec');
$str->secs = get_string('secs');
$str->year = get_string('year');
$str->years = get_string('years');
}
$years = floor($totalsecs/YEARSECS);
$remainder = $totalsecs - ($years*YEARSECS);
$days = floor($remainder/DAYSECS);
$remainder = $totalsecs - ($days*DAYSECS);
$hours = floor($remainder/HOURSECS);
$remainder = $remainder - ($hours*HOURSECS);
$mins = floor($remainder/MINSECS);
$secs = $remainder - ($mins*MINSECS);
$ss = ($secs == 1) ? $str->sec : $str->secs;
$sm = ($mins == 1) ? $str->min : $str->mins;
$sh = ($hours == 1) ? $str->hour : $str->hours;
$sd = ($days == 1) ? $str->day : $str->days;
$sy = ($years == 1) ? $str->year : $str->years;
$oyears = '';
$odays = '';
$ohours = '';
$omins = '';
$osecs = '';
if ($years) $oyears = $years .' '. $sy;
if ($days) $odays = $days .' '. $sd;
if ($hours) $ohours = $hours .' '. $sh;
if ($mins) $omins = $mins .' '. $sm;
if ($secs) $osecs = $secs .' '. $ss;
if ($years) return trim($oyears .' '. $odays);
if ($days) return trim($odays .' '. $ohours);
if ($hours) return trim($ohours .' '. $omins);
if ($mins) return trim($omins .' '. $osecs);
if ($secs) return $osecs;
return get_string('now');
}
/**
* Returns a formatted string that represents a date in user time
* <b>WARNING: note that the format is for strftime(), not date().</b>
* Because of a bug in most Windows time libraries, we can't use
* the nicer %e, so we have to use %d which has leading zeroes.
* A lot of the fuss in the function is just getting rid of these leading
* zeroes as efficiently as possible.
*
* If parameter fixday = true (default), then take off leading
* zero from %d, else mantain it.
*
* @uses HOURSECS
* @param int $date timestamp in GMT
* @param string $format strftime format
* @param float $timezone
* @param bool $fixday If true (default) then the leading
* zero from %d is removed. If false then the leading zero is mantained.
* @return string
*/
function userdate($date, $format='', $timezone=99, $fixday = true) {
global $CFG;
$strtimezone = NULL;
if (!is_numeric($timezone)) {
$strtimezone = $timezone;
}
if (empty($format)) {
$format = get_string('strftimedaydatetime');
}
if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
$fixday = false;
} else if ($fixday) {
$formatnoday = str_replace('%d', 'DD', $format);
$fixday = ($formatnoday != $format);
}
$date += dst_offset_on($date, $strtimezone);
$timezone = get_user_timezone_offset($timezone);
if (abs($timezone) > 13) { /// Server time
if ($fixday) {
$datestring = strftime($formatnoday, $date);
$daystring = str_replace(' 0', '', strftime(' %d', $date));
$datestring = str_replace('DD', $daystring, $datestring);
} else {
$datestring = strftime($format, $date);
}
} else {
$date += (int)($timezone * 3600);
if ($fixday) {
$datestring = gmstrftime($formatnoday, $date);
$daystring = str_replace(' 0', '', gmstrftime(' %d', $date));
$datestring = str_replace('DD', $daystring, $datestring);
} else {
$datestring = gmstrftime($format, $date);
}
}
/// If we are running under Windows convert from windows encoding to UTF-8
/// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
if ($CFG->ostype == 'WINDOWS') {
if ($localewincharset = get_string('localewincharset')) {
$textlib = textlib_get_instance();
$datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
}
}
return $datestring;
}
/**
* Given a $time timestamp in GMT (seconds since epoch),
* returns an array that represents the date in user time
*
* @uses HOURSECS
* @param int $time Timestamp in GMT
* @param float $timezone ?
* @return array An array that represents the date in user time
* @todo Finish documenting this function
*/
function usergetdate($time, $timezone=99) {
$strtimezone = NULL;
if (!is_numeric($timezone)) {
$strtimezone = $timezone;
}
$timezone = get_user_timezone_offset($timezone);
if (abs($timezone) > 13) { // Server time
return getdate($time);
}
// There is no gmgetdate so we use gmdate instead
$time += dst_offset_on($time, $strtimezone);
$time += intval((float)$timezone * HOURSECS);
$datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
//be careful to ensure the returned array matches that produced by getdate() above
list(
$getdate['month'],
$getdate['weekday'],
$getdate['yday'],
$getdate['year'],
$getdate['mon'],
$getdate['wday'],
$getdate['mday'],
$getdate['hours'],
$getdate['minutes'],
$getdate['seconds']
) = explode('_', $datestring);
return $getdate;
}
/**
* Given a GMT timestamp (seconds since epoch), offsets it by
* the timezone. eg 3pm in India is 3pm GMT - 7 * 3600 seconds
*
* @uses HOURSECS
* @param int $date Timestamp in GMT
* @param float $timezone
* @return int
*/
function usertime($date, $timezone=99) {
$timezone = get_user_timezone_offset($timezone);
if (abs($timezone) > 13) {
return $date;
}
return $date - (int)($timezone * HOURSECS);
}
/**
* Given a time, return the GMT timestamp of the most recent midnight
* for the current user.
*
* @param int $date Timestamp in GMT
* @param float $timezone ?
* @return ?
*/
function usergetmidnight($date, $timezone=99) {
$userdate = usergetdate($date, $timezone);
// Time of midnight of this user's day, in GMT
return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
}
/**
* Returns a string that prints the user's timezone
*
* @param float $timezone The user's timezone
* @return string
*/
function usertimezone($timezone=99) {
$tz = get_user_timezone($timezone);
if (!is_float($tz)) {
return $tz;
}
if(abs($tz) > 13) { // Server time
return get_string('serverlocaltime');
}
if($tz == intval($tz)) {
// Don't show .0 for whole hours
$tz = intval($tz);
}
if($tz == 0) {
return 'UTC';
}
else if($tz > 0) {
return 'UTC+'.$tz;
}
else {
return 'UTC'.$tz;
}
}
/**
* Returns a float which represents the user's timezone difference from GMT in hours
* Checks various settings and picks the most dominant of those which have a value
*
* @uses $CFG
* @uses $USER
* @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
* @return int
*/
function get_user_timezone_offset($tz = 99) {
global $USER, $CFG;
$tz = get_user_timezone($tz);
if (is_float($tz)) {
return $tz;
} else {
$tzrecord = get_timezone_record($tz);
if (empty($tzrecord)) {
return 99.0;
}
return (float)$tzrecord->gmtoff / HOURMINS;
}
}
/**
* Returns an int which represents the systems's timezone difference from GMT in seconds
* @param mixed $tz timezone
* @return int if found, false is timezone 99 or error
*/
function get_timezone_offset($tz) {
global $CFG;
if ($tz == 99) {
return false;
}
if (is_numeric($tz)) {
return intval($tz * 60*60);
}
if (!$tzrecord = get_timezone_record($tz)) {
return false;
}
return intval($tzrecord->gmtoff * 60);
}
/**
* Returns a float or a string which denotes the user's timezone
* 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)
* means that for this timezone there are also DST rules to be taken into account
* Checks various settings and picks the most dominant of those which have a value
*
* @uses $USER
* @uses $CFG
* @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
* @return mixed
*/
function get_user_timezone($tz = 99) {
global $USER, $CFG;
$timezones = array(
$tz,
isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
isset($USER->timezone) ? $USER->timezone : 99,
isset($CFG->timezone) ? $CFG->timezone : 99,
);
$tz = 99;
while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
$tz = $next['value'];
}
return is_numeric($tz) ? (float) $tz : $tz;
}
/**
* ?
*
* @uses $CFG
* @uses $db
* @param string $timezonename ?
* @return object
*/
function get_timezone_record($timezonename) {
global $CFG, $db;
static $cache = NULL;
if ($cache === NULL) {
$cache = array();
}
if (isset($cache[$timezonename])) {
return $cache[$timezonename];
}
return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
}
/**
* ?
*
* @uses $CFG
* @uses $USER
* @param ? $fromyear ?
* @param ? $to_year ?
* @return bool
*/
function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
global $CFG, $SESSION;
$usertz = get_user_timezone($strtimezone);
if (is_float($usertz)) {
// Trivial timezone, no DST
return false;
}
if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
// We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
unset($SESSION->dst_offsets);
unset($SESSION->dst_range);
}
if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
// Repeat calls which do not request specific year ranges stop here, we have already calculated the table
// This will be the return path most of the time, pretty light computationally
return true;
}
// Reaching here means we either need to extend our table or create it from scratch
// Remember which TZ we calculated these changes for
$SESSION->dst_offsettz = $usertz;
if(empty($SESSION->dst_offsets)) {
// If we 're creating from scratch, put the two guard elements in there
$SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
}
if(empty($SESSION->dst_range)) {
// If creating from scratch
$from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
$to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
// Fill in the array with the extra years we need to process
$yearstoprocess = array();
for($i = $from; $i <= $to; ++$i) {
$yearstoprocess[] = $i;
}
// Take note of which years we have processed for future calls
$SESSION->dst_range = array($from, $to);
}
else {
// If needing to extend the table, do the same
$yearstoprocess = array();
$from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
$to = min((empty($to_year) ? $SESSION->dst_range[1] : $to_year), 2035);
if($from < $SESSION->dst_range[0]) {
// Take note of which years we need to process and then note that we have processed them for future calls
for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
$yearstoprocess[] = $i;
}
$SESSION->dst_range[0] = $from;
}
if($to > $SESSION->dst_range[1]) {
// Take note of which years we need to process and then note that we have processed them for future calls
for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
$yearstoprocess[] = $i;
}
$SESSION->dst_range[1] = $to;
}
}
if(empty($yearstoprocess)) {
// This means that there was a call requesting a SMALLER range than we have already calculated
return true;
}
// From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
// Also, the array is sorted in descending timestamp order!
// Get DB data
static $presets_cache = array();
if (!isset($presets_cache[$usertz])) {
$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');
}
if(empty($presets_cache[$usertz])) {
return false;
}
// Remove ending guard (first element of the array)
reset($SESSION->dst_offsets);
unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
// Add all required change timestamps
foreach($yearstoprocess as $y) {
// Find the record which is in effect for the year $y
foreach($presets_cache[$usertz] as $year => $preset) {
if($year <= $y) {
break;
}
}
$changes = dst_changes_for_year($y, $preset);
if($changes === NULL) {
continue;
}
if($changes['dst'] != 0) {
$SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
}
if($changes['std'] != 0) {
$SESSION->dst_offsets[$changes['std']] = 0;
}
}
// Put in a guard element at the top
$maxtimestamp = max(array_keys($SESSION->dst_offsets));
$SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
// Sort again
krsort($SESSION->dst_offsets);
return true;
}
function dst_changes_for_year($year, $timezone) {
if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
return NULL;
}
$monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
$monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
list($std_hour, $std_min) = explode(':', $timezone->std_time);
$timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
$timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
// Instead of putting hour and minute in make_timestamp(), we add them afterwards.
// This has the advantage of being able to have negative values for hour, i.e. for timezones
// where GMT time would be in the PREVIOUS day than the local one on which DST changes.
$timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
$timestd += $std_hour * HOURSECS + $std_min * MINSECS;
return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
}
// $time must NOT be compensated at all, it has to be a pure timestamp
function dst_offset_on($time, $strtimezone = NULL) {
global $SESSION;
if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
return 0;
}
reset($SESSION->dst_offsets);
while(list($from, $offset) = each($SESSION->dst_offsets)) {
if($from <= $time) {
break;
}
}
// This is the normal return path
if($offset !== NULL) {
return $offset;
}
// Reaching this point means we haven't calculated far enough, do it now:
// Calculate extra DST changes if needed and recurse. The recursion always
// moves toward the stopping condition, so will always end.
if($from == 0) {
// We need a year smaller than $SESSION->dst_range[0]
if($SESSION->dst_range[0] == 1971) {
return 0;
}
calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
return dst_offset_on($time, $strtimezone);
}
else {
// We need a year larger than $SESSION->dst_range[1]
if($SESSION->dst_range[1] == 2035) {
return 0;
}
calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
return dst_offset_on($time, $strtimezone);
}
}
function find_day_in_month($startday, $weekday, $month, $year) {
$daysinmonth = days_in_month($month, $year);
if($weekday == -1) {
// Don't care about weekday, so return:
// abs($startday) if $startday != -1
// $daysinmonth otherwise
return ($startday == -1) ? $daysinmonth : abs($startday);
}
// From now on we 're looking for a specific weekday
// Give "end of month" its actual value, since we know it
if($startday == -1) {
$startday = -1 * $daysinmonth;
}
// Starting from day $startday, the sign is the direction
if($startday < 1) {
$startday = abs($startday);
$lastmonthweekday = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
// This is the last such weekday of the month
$lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
if($lastinmonth > $daysinmonth) {
$lastinmonth -= 7;
}
// Find the first such weekday <= $startday
while($lastinmonth > $startday) {
$lastinmonth -= 7;
}
return $lastinmonth;
}
else {
$indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
$diff = $weekday - $indexweekday;
if($diff < 0) {
$diff += 7;
}
// This is the first such weekday of the month equal to or after $startday
$firstfromindex = $startday + $diff;
return $firstfromindex;
}
}
/**
* Calculate the number of days in a given month
*
* @param int $month The month whose day count is sought
* @param int $year The year of the month whose day count is sought
* @return int
*/
function days_in_month($month, $year) {
return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
}
/**
* Calculate the position in the week of a specific calendar day
*
* @param int $day The day of the date whose position in the week is sought
* @param int $month The month of the date whose position in the week is sought
* @param int $year The year of the date whose position in the week is sought
* @return int
*/
function dayofweek($day, $month, $year) {
// I wonder if this is any different from
// strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
}
/// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
/**
* Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
* if one does not already exist, but does not overwrite existing sesskeys. Returns the
* sesskey string if $USER exists, or boolean false if not.
*
* @uses $USER
* @return string
*/
function sesskey() {
global $USER;
if(!isset($USER)) {
return false;
}
if (empty($USER->sesskey)) {
$USER->sesskey = random_string(10);
}
return $USER->sesskey;
}
/**
* For security purposes, this function will check that the currently
* given sesskey (passed as a parameter to the script or this function)
* matches that of the current user.
*
* @param string $sesskey optionally provided sesskey
* @return bool
*/
function confirm_sesskey($sesskey=NULL) {
global $USER;
if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
return true;
}
if (empty($sesskey)) {
$sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
}
if (!isset($USER->sesskey)) {
return false;
}
return ($USER->sesskey === $sesskey);
}
/**
* Check the session key using {@link confirm_sesskey()},
* and cause a fatal error if it does not match.
*/
function require_sesskey() {
if (!confirm_sesskey()) {
print_error('invalidsesskey');
}
}
/**
* Setup all global $CFG course variables, set locale and also themes
* This function can be used on pages that do not require login instead of require_login()
*
* @param mixed $courseorid id of the course or course object
*/
function course_setup($courseorid=0) {
global $COURSE, $CFG, $SITE;
/// Redefine global $COURSE if needed
if (empty($courseorid)) {
// no change in global $COURSE - for backwards compatibiltiy
// if require_rogin() used after require_login($courseid);
} else if (is_object($courseorid)) {
$COURSE = clone($courseorid);
} else {
global $course; // used here only to prevent repeated fetching from DB - may be removed later
if ($courseorid == SITEID) {
$COURSE = clone($SITE);
} else if (!empty($course->id) and $course->id == $courseorid) {
$COURSE = clone($course);
} else {
if (!$COURSE = get_record('course', 'id', $courseorid)) {
error('Invalid course ID');
}
}
}
/// set locale and themes
moodle_setlocale();
theme_setup();
}
/**
* This function checks that the current user is logged in and has the
* required privileges
*
* This function checks that the current user is logged in, and optionally
* whether they are allowed to be in a particular course and view a particular
* course module.
* If they are not logged in, then it redirects them to the site login unless
* $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
* case they are automatically logged in as guests.
* If $courseid is given and the user is not enrolled in that course then the
* user is redirected to the course enrolment page.
* If $cm is given and the coursemodule is hidden and the user is not a teacher
* in the course then the user is redirected to the course home page.
*
* @uses $CFG
* @uses $SESSION
* @uses $USER
* @uses $FULLME
* @uses SITEID
* @uses $COURSE
* @param mixed $courseorid id of the course or course object
* @param bool $autologinguest
* @param object $cm course module object
* @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
* true. Used to avoid (=false) some scripts (file.php...) to set that variable,
* in order to keep redirects working properly. MDL-14495
*/
function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
global $CFG, $SESSION, $USER, $COURSE, $FULLME;
/// setup global $COURSE, themes, language and locale
course_setup($courseorid);
/// If the user is not even logged in yet then make sure they are
if (!isloggedin()) {
//NOTE: $USER->site check was obsoleted by session test cookie,
// $USER->confirmed test is in login/index.php
if ($setwantsurltome) {
$SESSION->wantsurl = $FULLME;
}
if (!empty($_SERVER['HTTP_REFERER'])) {
$SESSION->fromurl = $_SERVER['HTTP_REFERER'];
}
if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
$loginguest = '?loginguest=true';
} else {
$loginguest = '';
}
if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
} else {
$wwwroot = str_replace('http:','https:', $CFG->wwwroot);
redirect($wwwroot .'/login/index.php');
}
exit;
}
/// loginas as redirection if needed
if ($COURSE->id != SITEID and !empty($USER->realuser)) {
if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
if ($USER->loginascontext->instanceid != $COURSE->id) {
print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
}
}
}
/// check whether the user should be changing password (but only if it is REALLY them)
if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
$userauth = get_auth_plugin($USER->auth);
if ($userauth->can_change_password()) {
$SESSION->wantsurl = $FULLME;
if ($changeurl = $userauth->change_password_url()) {
//use plugin custom url
redirect($changeurl);
} else {
//use moodle internal method
if (empty($CFG->loginhttps)) {
redirect($CFG->wwwroot .'/login/change_password.php');
} else {
$wwwroot = str_replace('http:','https:', $CFG->wwwroot);
redirect($wwwroot .'/login/change_password.php');
}
}
} else {
print_error('nopasswordchangeforced', 'auth');
}
}
/// Check that the user account is properly set up
if (user_not_fully_set_up($USER)) {
$SESSION->wantsurl = $FULLME;
redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
}
/// Make sure current IP matches the one for this session (if required)
if (!empty($CFG->tracksessionip)) {
if ($USER->sessionIP != md5(getremoteaddr())) {
print_error('sessionipnomatch', 'error');
}
}
/// Make sure the USER has a sesskey set up. Used for checking script parameters.
sesskey();
// Check that the user has agreed to a site policy if there is one
if (!empty($CFG->sitepolicy)) {
if (!$USER->policyagreed) {
$SESSION->wantsurl = $FULLME;
redirect($CFG->wwwroot .'/user/policy.php');
}
}
// Fetch the system context, we are going to use it a lot.
$sysctx = get_context_instance(CONTEXT_SYSTEM);
/// If the site is currently under maintenance, then print a message
if (!has_capability('moodle/site:config', $sysctx)) {
if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
print_maintenance_message();
exit;
}
}
/// groupmembersonly access control
if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
if (isguestuser() or !groups_has_membership($cm)) {
print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
}
}
// Fetch the course context, and prefetch its child contexts
if (!isset($COURSE->context)) {
if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
print_error('nocontext');
}
}
if (!empty($cm) && !isset($cm->context)) {
if ( ! $cm->context = get_context_instance(CONTEXT_MODULE, $cm->id) ) {
print_error('nocontext');
}
}
if ($COURSE->id == SITEID) {
/// Eliminate hidden site activities straight away
if (!empty($cm) && !$cm->visible
&& !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
}
user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
return;
} else {
/// Check if the user can be in a particular course
if (empty($USER->access['rsw'][$COURSE->context->path])) {
//
// MDL-13900 - If the course or the parent category are hidden
// and the user hasn't the 'course:viewhiddencourses' capability, prevent access
//
if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
!has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
print_header_simple();
notice(get_string('coursehidden'), $CFG->wwwroot .'/');
}
}
/// Non-guests who don't currently have access, check if they can be allowed in as a guest
if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
if ($COURSE->guest == 1) {
// Temporarily assign them guest role for this context, if it fails later user is asked to enrol
$USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
}
}
/// If the user is a guest then treat them according to the course policy about guests
if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
if (has_capability('moodle/site:doanything', $sysctx)) {
// administrators must be able to access any course - even if somebody gives them guest access
user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
return;
}
switch ($COURSE->guest) { /// Check course policy about guest access
case 1: /// Guests always allowed
if (!has_capability('moodle/course:view', $COURSE->context)) { // Prohibited by capability
print_header_simple();
notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
}
if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
get_string('activityiscurrentlyhidden'));
}
user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
return; // User is allowed to see this course
break;
case 2: /// Guests allowed with key
if (!empty($USER->enrolkey[$COURSE->id])) { // Set by enrol/manual/enrol.php
user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
return true;
}
// otherwise drop through to logic below (--> enrol.php)
break;
default: /// Guests not allowed
$strloggedinasguest = get_string('loggedinasguest');
print_header_simple('', '',
build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
if (empty($USER->access['rsw'][$COURSE->context->path])) { // Normal guest
notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
} else {
notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
print_footer($COURSE);
exit;
}
break;
}
/// For non-guests, check if they have course view access
} else if (has_capability('moodle/course:view', $COURSE->context)) {
if (!empty($USER->realuser)) { // Make sure the REAL person can also access this course
if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
print_header_simple();
notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
}
}
/// Make sure they can read this activity too, if specified
if (!empty($cm) && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
}
user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
return; // User is allowed to see this course
}
/// Currently not enrolled in the course, so see if they want to enrol
$SESSION->wantsurl = $FULLME;
redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
die;
}
}
/**
* This function just makes sure a user is logged out.
*
* @uses $CFG
* @uses $USER
*/
function require_logout() {
global $USER, $CFG, $SESSION;
if (isloggedin()) {
add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
$authsequence = get_enabled_auth_plugins(); // auths, in sequence
foreach($authsequence as $authname) {
$authplugin = get_auth_plugin($authname);
$authplugin->prelogout_hook();
}
}
if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
// This method is just to try to avoid silly warnings from PHP 4.3.0
session_unregister("USER");
session_unregister("SESSION");
}
// Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
$file = $line = null;
if (headers_sent($file, $line)) {
error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
error_log('Headers were already sent in file: '.$file.' on line '.$line);
} else {
if (check_php_version('5.2.0')) {
setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
} else {
setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
}
}
unset($_SESSION['USER']);
unset($_SESSION['SESSION']);
unset($SESSION);
unset($USER);
}
/**
* This is a weaker version of {@link require_login()} which only requires login
* when called from within a course rather than the site page, unless
* the forcelogin option is turned on.
*
* @uses $CFG
* @param mixed $courseorid The course object or id in question
* @param bool $autologinguest Allow autologin guests if that is wanted
* @param object $cm Course activity module if known
* @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
* true. Used to avoid (=false) some scripts (file.php...) to set that variable,
* in order to keep redirects working properly. MDL-14495
*/
function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
global $CFG;
if (!empty($CFG->forcelogin)) {
// login required for both SITE and courses
require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
} else if (!empty($cm) and !$cm->visible) {
// always login for hidden activities
require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
} else if ((is_object($courseorid) and $courseorid->id == SITEID)
or (!is_object($courseorid) and $courseorid == SITEID)) {
//login for SITE not required
if ($cm and empty($cm->visible)) {
// hidden activities are not accessible without login
require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
} else if ($cm and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
// not-logged-in users do not have any group membership
require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
} else {
user_accesstime_log(SITEID);
return;
}
} else {
// course login always required
require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
}
}
/**
* Require key login. Function terminates with error if key not found or incorrect.
* @param string $script unique script identifier
* @param int $instance optional instance id
*/
function require_user_key_login($script, $instance=null) {
global $nomoodlecookie, $USER, $SESSION, $CFG;
if (empty($nomoodlecookie)) {
error('Incorrect use of require_key_login() - session cookies must be disabled!');
}
/// extra safety
@session_write_close();
$keyvalue = required_param('key', PARAM_ALPHANUM);
if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
error('Incorrect key');
}
if (!empty($key->validuntil) and $key->validuntil < time()) {
error('Expired key');
}
if ($key->iprestriction) {
$remoteaddr = getremoteaddr();
if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
error('Client IP address mismatch');
}
}
if (!$user = get_record('user', 'id', $key->userid)) {
error('Incorrect user record');
}
/// emulate normal session
$SESSION = new object();
$USER = $user;
/// note we are not using normal login
if (!defined('USER_KEY_LOGIN')) {
define('USER_KEY_LOGIN', true);
}
load_all_capabilities();
/// return isntance id - it might be empty
return $key->instance;
}
/**
* Creates a new private user access key.
* @param string $script unique target identifier
* @param int $userid
* @param instance $int optional instance id
* @param string $iprestriction optional ip restricted access
* @param timestamp $validuntil key valid only until given data
* @return string access key value
*/
function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
$key = new object();
$key->script = $script;
$key->userid = $userid;
$key->instance = $instance;
$key->iprestriction = $iprestriction;
$key->validuntil = $validuntil;
$key->timecreated = time();
$key->value = md5($userid.'_'.time().random_string(40)); // something long and unique
while (record_exists('user_private_key', 'value', $key->value)) {
// must be unique
$key->value = md5($userid.'_'.time().random_string(40));
}
if (!insert_record('user_private_key', $key)) {
error('Can not insert new key');
}
return $key->value;
}
/**
* Modify the user table by setting the currently logged in user's
* last login to now.
*
* @uses $USER
* @return bool
*/
function update_user_login_times() {
global $USER;
$user = new object();
$USER->lastlogin = $user->lastlogin = $USER->currentlogin;
$USER->currentlogin = $user->lastaccess = $user->currentlogin = time();
$user->id = $USER->id;
return update_record('user', $user);
}
/**
* Determines if a user has completed setting up their account.
*
* @param user $user A {@link $USER} object to test for the existance of a valid name and email
* @return bool
*/
function user_not_fully_set_up($user) {
return ($user->username != 'guest' and (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)));
}
function over_bounce_threshold($user) {
global $CFG;
if (empty($CFG->handlebounces)) {
return false;
}
if (empty($user->id)) { /// No real (DB) user, nothing to do here.
return false;
}
// set sensible defaults
if (empty($CFG->minbounces)) {
$CFG->minbounces = 10;
}
if (empty($CFG->bounceratio)) {
$CFG->bounceratio = .20;
}
$bouncecount = 0;
$sendcount = 0;
if ($bounce = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
$bouncecount = $bounce->value;
}
if ($send = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
$sendcount = $send->value;
}
return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
}
/**
* @param $user - object containing an id
* @param $reset - will reset the count to 0
*/
function set_send_count($user,$reset=false) {
if (empty($user->id)) { /// No real (DB) user, nothing to do here.
return;
}
if ($pref = get_record('user_preferences','userid',$user->id,'name','email_send_count')) {
$pref->value = (!empty($reset)) ? 0 : $pref->value+1;
update_record('user_preferences',$pref);
}
else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
// make a new one
$pref->name = 'email_send_count';
$pref->value = 1;
$pref->userid = $user->id;
insert_record('user_preferences',$pref, false);
}
}
/**
* @param $user - object containing an id
* @param $reset - will reset the count to 0
*/
function set_bounce_count($user,$reset=false) {
if ($pref = get_record('user_preferences','userid',$user->id,'name','email_bounce_count')) {
$pref->value = (!empty($reset)) ? 0 : $pref->value+1;
update_record('user_preferences',$pref);
}
else if (!empty($reset)) { // if it's not there and we're resetting, don't bother.
// make a new one
$pref->name = 'email_bounce_count';
$pref->value = 1;
$pref->userid = $user->id;
insert_record('user_preferences',$pref, false);
}
}
/**
* Keeps track of login attempts
*
* @uses $SESSION
*/
function update_login_count() {
global $SESSION;
$max_logins = 10;
if (empty($SESSION->logincount)) {
$SESSION->logincount = 1;
} else {
$SESSION->logincount++;
}
if ($SESSION->logincount > $max_logins) {
unset($SESSION->wantsurl);
print_error('errortoomanylogins');
}
}
/**
* Resets login attempts
*
* @uses $SESSION
*/
function reset_login_count() {
global $SESSION;
$SESSION->logincount = 0;
}
function sync_metacourses() {
global $CFG;
if (!$courses = get_records('course', 'metacourse', 1)) {
return;
}
foreach ($courses as $course) {
sync_metacourse($course);
}
}
/**
* Goes through all enrolment records for the courses inside the metacourse and sync with them.
*
* @param mixed $course the metacourse to synch. Either the course object itself, or the courseid.
*/
function sync_metacourse($course) {
global $CFG;
// Check the course is valid.
if (!is_object($course)) {
if (!$course = get_record('course', 'id', $course)) {
return false; // invalid course id
}
}
// Check that we actually have a metacourse.
if (empty($course->metacourse)) {
return false;
}
// Get a list of roles that should not be synced.
if (!empty($CFG->nonmetacoursesyncroleids)) {
$roleexclusions = 'ra.roleid NOT IN (' . $CFG->nonmetacoursesyncroleids . ') AND';
} else {
$roleexclusions = '';
}
// Get the context of the metacourse.
$context = get_context_instance(CONTEXT_COURSE, $course->id); // SITEID can not be a metacourse
// We do not ever want to unassign the list of metacourse manager, so get a list of them.
if ($users = get_users_by_capability($context, 'moodle/course:managemetacourse')) {
$managers = array_keys($users);
} else {
$managers = array();
}
// Get assignments of a user to a role that exist in a child course, but
// not in the meta coure. That is, get a list of the assignments that need to be made.
if (!$assignments = get_records_sql("
SELECT
ra.id, ra.roleid, ra.userid, ra.hidden
FROM
{$CFG->prefix}role_assignments ra,
{$CFG->prefix}context con,
{$CFG->prefix}course_meta cm
WHERE
ra.contextid = con.id AND
con.contextlevel = " . CONTEXT_COURSE . " AND
con.instanceid = cm.child_course AND
cm.parent_course = {$course->id} AND
$roleexclusions
NOT EXISTS (
SELECT 1 FROM
{$CFG->prefix}role_assignments ra2
WHERE
ra2.userid = ra.userid AND
ra2.roleid = ra.roleid AND
ra2.contextid = {$context->id}
)
")) {
$assignments = array();
}
// Get assignments of a user to a role that exist in the meta course, but
// not in any child courses. That is, get a list of the unassignments that need to be made.
if (!$unassignments = get_records_sql("
SELECT
ra.id, ra.roleid, ra.userid
FROM
{$CFG->prefix}role_assignments ra
WHERE
ra.contextid = {$context->id} AND
$roleexclusions
NOT EXISTS (
SELECT 1 FROM
{$CFG->prefix}role_assignments ra2,
{$CFG->prefix}context con2,
{$CFG->prefix}course_meta cm
WHERE
ra2.userid = ra.userid AND
ra2.roleid = ra.roleid AND
ra2.contextid = con2.id AND
con2.contextlevel = " . CONTEXT_COURSE . " AND
con2.instanceid = cm.child_course AND
cm.parent_course = {$course->id}
)
")) {
$unassignments = array();
}
$success = true;
// Make the unassignments, if they are not managers.
foreach ($unassignments as $unassignment) {
if (!in_array($unassignment->userid, $managers)) {
$success = role_unassign($unassignment->roleid, $unassignment->userid, 0, $context->id) && $success;
}
}
// Make the assignments.
foreach ($assignments as $assignment) {
$success = role_assign($assignment->roleid, $assignment->userid, 0, $context->id, 0, 0, $assignment->hidden) && $success;
}
return $success;
// TODO: finish timeend and timestart
// maybe we could rely on cron job to do the cleaning from time to time
}
/**
* Adds a record to the metacourse table and calls sync_metacoures
*/
function add_to_metacourse ($metacourseid, $courseid) {
if (!$metacourse = get_record("course","id",$metacourseid)) {
return false;
}
if (!$course = get_record("course","id",$courseid)) {
return false;
}
if (!$record = get_record("course_meta","parent_course",$metacourseid,"child_course",$courseid)) {
$rec = new object();
$rec->parent_course = $metacourseid;
$rec->child_course = $courseid;
if (!insert_record('course_meta',$rec)) {
return false;
}
return sync_metacourse($metacourseid);
}
return true;
}
/**
* Removes the record from the metacourse table and calls sync_metacourse
*/
function remove_from_metacourse($metacourseid, $courseid) {
if (delete_records('course_meta','parent_course',$metacourseid,'child_course',$courseid)) {
return sync_metacourse($metacourseid);
}
return false;
}
/**
* Determines if a user is currently logged in
*
* @uses $USER
* @return bool
*/
function isloggedin() {
global $USER;
return (!empty($USER->id));
}
/**
* Determines if a user is logged in as real guest user with username 'guest'.
* This function is similar to original isguest() in 1.6 and earlier.
* Current isguest() is deprecated - do not use it anymore.
*
* @param $user mixed user object or id, $USER if not specified
* @return bool true if user is the real guest user, false if not logged in or other user
*/
function isguestuser($user=NULL) {
global $USER, $CFG;
if ($user === NULL) {
$user = $USER;
} else if (is_numeric($user)) {
$user = get_record('user', 'id', $user, '', '', '', '', 'id, username');
}
if (empty($user->id)) {
return false; // not logged in, can not be guest
}
return ($user->username == 'guest' and $user->mnethostid == $CFG->mnet_localhost_id);
}
/**
* Determines if the currently logged in user is in editing mode.
* Note: originally this function had $userid parameter - it was not usable anyway
*
* @uses $USER, $PAGE
* @return bool
*/
function isediting() {
global $USER, $PAGE;
if (empty($USER->editing)) {
return false;
} elseif (is_object($PAGE) && method_exists($PAGE,'user_allowed_editing')) {
return $PAGE->user_allowed_editing();
}
return true;//false;
}
/**
* Determines if the logged in user is currently moving an activity
*
* @uses $USER
* @param int $courseid The id of the course being tested
* @return bool
*/
function ismoving($courseid) {
global $USER;
if (!empty($USER->activitycopy)) {
return ($USER->activitycopycourse == $courseid);
}
return false;
}
/**
* Given an object containing firstname and lastname
* values, this function returns a string with the
* full name of the person.
* The result may depend on system settings
* or language. 'override' will force both names
* to be used even if system settings specify one.
*
* @uses $CFG
* @uses $SESSION
* @param object $user A {@link $USER} object to get full name of
* @param bool $override If true then the name will be first name followed by last name rather than adhering to fullnamedisplay setting.
*/
function fullname($user, $override=false) {
global $CFG, $SESSION;
if (!isset($user->firstname) and !isset($user->lastname)) {
return '';
}
if (!$override) {
if (!empty($CFG->forcefirstname)) {
$user->firstname = $CFG->forcefirstname;
}
if (!empty($CFG->forcelastname)) {
$user->lastname = $CFG->forcelastname;
}
}
if (!empty($SESSION->fullnamedisplay)) {
$CFG->fullnamedisplay = $SESSION->fullnamedisplay;
}
if ($CFG->fullnamedisplay == 'firstname lastname') {
return $user->firstname .' '. $user->lastname;
} else if ($CFG->fullnamedisplay == 'lastname firstname') {
return $user->lastname .' '. $user->firstname;
} else if ($CFG->fullnamedisplay == 'firstname') {
if ($override) {
return get_string('fullnamedisplay', '', $user);
} else {
return $user->firstname;
}
}
return get_string('fullnamedisplay', '', $user);
}
/**
* Sets a moodle cookie with an encrypted string
*
* @uses $CFG
* @uses DAYSECS
* @uses HOURSECS
* @param string $thing The string to encrypt and place in a cookie
*/
function set_moodle_cookie($thing) {
global $CFG;
if ($thing == 'guest') { // Ignore guest account
return;
}
$cookiename = 'MOODLEID_'.$CFG->sessioncookie;
$days = 60;
$seconds = DAYSECS*$days;
setCookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
setCookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure);
}
/**
* Gets a moodle cookie with an encrypted string
*
* @uses $CFG
* @return string
*/
function get_moodle_cookie() {
global $CFG;
$cookiename = 'MOODLEID_'.$CFG->sessioncookie;
if (empty($_COOKIE[$cookiename])) {
return '';
} else {
$thing = rc4decrypt($_COOKIE[$cookiename]);
return ($thing == 'guest') ? '': $thing; // Ignore guest account
}
}
/**
* Returns whether a given authentication plugin exists.
*
* @uses $CFG
* @param string $auth Form of authentication to check for. Defaults to the
* global setting in {@link $CFG}.
* @return boolean Whether the plugin is available.
*/
function exists_auth_plugin($auth) {
global $CFG;
if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
}
return false;
}
/**
* Checks if a given plugin is in the list of enabled authentication plugins.
*
* @param string $auth Authentication plugin.
* @return boolean Whether the plugin is enabled.
*/
function is_enabled_auth($auth) {
if (empty($auth)) {
return false;
}
$enabled = get_enabled_auth_plugins();
return in_array($auth, $enabled);
}
/**
* Returns an authentication plugin instance.
*
* @uses $CFG
* @param string $auth name of authentication plugin
* @return object An instance of the required authentication plugin.
*/
function get_auth_plugin($auth) {
global $CFG;
// check the plugin exists first
if (! exists_auth_plugin($auth)) {
error("Authentication plugin '$auth' not found.");
}
// return auth plugin instance
require_once "{$CFG->dirroot}/auth/$auth/auth.php";
$class = "auth_plugin_$auth";
return new $class;
}
/**
* Returns array of active auth plugins.
*
* @param bool $fix fix $CFG->auth if needed
* @return array
*/
function get_enabled_auth_plugins($fix=false) {
global $CFG;
$default = array('manual', 'nologin');
if (empty($CFG->auth)) {
$auths = array();
} else {
$auths = explode(',', $CFG->auth);
}
if ($fix) {
$auths = array_unique($auths);
foreach($auths as $k=>$authname) {
if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
unset($auths[$k]);
}
}
$newconfig = implode(',', $auths);
if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
set_config('auth', $newconfig);
}
}
return (array_merge($default, $auths));
}
/**
* Returns true if an internal authentication method is being used.
* if method not specified then, global default is assumed
*
* @uses $CFG
* @param string $auth Form of authentication required
* @return bool
*/
function is_internal_auth($auth) {
$authplugin = get_auth_plugin($auth); // throws error if bad $auth
return $authplugin->is_internal();
}
/**
* Returns true if the user is a 'restored' one
*
* Used in the login process to inform the user
* and allow him/her to reset the password
*
* @uses $CFG
* @param string $username username to be checked
* @return bool
*/
function is_restored_user($username) {
global $CFG;
return record_exists('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id, 'password', 'restored');
}
/**
* Returns an array of user fields
*
* @uses $CFG
* @uses $db
* @return array User field/column names
*/
function get_user_fieldnames() {
global $CFG, $db;
$fieldarray = $db->MetaColumnNames($CFG->prefix.'user');
unset($fieldarray['ID']);
return $fieldarray;
}
/**
* Creates the default "guest" user. Used both from
* admin/index.php and login/index.php
* @return mixed user object created or boolean false if the creation has failed
*/
function create_guest_record() {
global $CFG;
$guest = new stdClass();
$guest->auth = 'manual';
$guest->username = 'guest';
$guest->password = hash_internal_user_password('guest');
$guest->firstname = addslashes(get_string('guestuser'));
$guest->lastname = ' ';
$guest->email = 'root@localhost';
$guest->description = addslashes(get_string('guestuserinfo'));
$guest->mnethostid = $CFG->mnet_localhost_id;
$guest->confirmed = 1;
$guest->lang = $CFG->lang;
$guest->timemodified= time();
if (! $guest->id = insert_record("user", $guest)) {
return false;
}
return $guest;
}
/**
* Creates a bare-bones user record
*
* @uses $CFG
* @param string $username New user's username to add to record
* @param string $password New user's password to add to record
* @param string $auth Form of authentication required
* @return object A {@link $USER} object
* @todo Outline auth types and provide code example
*/
function create_user_record($username, $password, $auth='manual') {
global $CFG;
//just in case check text case
$username = trim(moodle_strtolower($username));
$authplugin = get_auth_plugin($auth);
if ($newinfo = $authplugin->get_userinfo($username)) {
$newinfo = truncate_userinfo($newinfo);
foreach ($newinfo as $key => $value){
$newuser->$key = addslashes($value);
}
}
if (!empty($newuser->email)) {
if (email_is_not_allowed($newuser->email)) {
unset($newuser->email);
}
}
if (!isset($newuser->city)) {
$newuser->city = '';
}
$newuser->auth = $auth;
$newuser->username = $username;
// fix for MDL-8480
// user CFG lang for user if $newuser->lang is empty
// or $user->lang is not an installed language
$sitelangs = array_keys(get_list_of_languages());
if (empty($newuser->lang) || !in_array($newuser->lang, $sitelangs)) {
$newuser -> lang = $CFG->lang;
}
$newuser->confirmed = 1;
$newuser->lastip = getremoteaddr();
if (empty($newuser->lastip)) {
$newuser->lastip = '0.0.0.0';
}
$newuser->timemodified = time();
$newuser->mnethostid = $CFG->mnet_localhost_id;
if (insert_record('user', $newuser)) {
$user = get_complete_user_data('username', $newuser->username);
if(!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
set_user_preference('auth_forcepasswordchange', 1, $user->id);
}
update_internal_user_password($user, $password);
return $user;
}
return false;
}
/**
* Will update a local user record from an external source
*
* @uses $CFG
* @param string $username New user's username to add to record
* @return user A {@link $USER} object
*/
function update_user_record($username, $authplugin) {
$username = trim(moodle_strtolower($username)); /// just in case check text case
$oldinfo = get_record('user', 'username', $username, '','','','', 'username, auth');
$userauth = get_auth_plugin($oldinfo->auth);
if ($newinfo = $userauth->get_userinfo($username)) {
$newinfo = truncate_userinfo($newinfo);
foreach ($newinfo as $key => $value){
if ($key === 'username') {
// 'username' is not a mapped updateable/lockable field, so skip it.
continue;
}
$confval = $userauth->config->{'field_updatelocal_' . $key};
$lockval = $userauth->config->{'field_lock_' . $key};
if (empty($confval) || empty($lockval)) {
continue;
}
if ($confval === 'onlogin') {
$value = addslashes($value);
// MDL-4207 Don't overwrite modified user profile values with
// empty LDAP values when 'unlocked if empty' is set. The purpose
// of the setting 'unlocked if empty' is to allow the user to fill
// in a value for the selected field _if LDAP is giving
// nothing_ for this field. Thus it makes sense to let this value
// stand in until LDAP is giving a value for this field.
if (!(empty($value) && $lockval === 'unlockedifempty')) {
set_field('user', $key, $value, 'username', $username)
|| error_log("Error updating $key for $username");
}
}
}
}
return get_complete_user_data('username', $username);
}
function truncate_userinfo($info) {
/// will truncate userinfo as it comes from auth_get_userinfo (from external auth)
/// which may have large fields
// define the limits
$limit = array(
'username' => 100,
'idnumber' => 255,
'firstname' => 100,
'lastname' => 100,
'email' => 100,
'icq' => 15,
'phone1' => 20,
'phone2' => 20,
'institution' => 40,
'department' => 30,
'address' => 70,
'city' => 20,
'country' => 2,
'url' => 255,
);
// apply where needed
foreach (array_keys($info) as $key) {
if (!empty($limit[$key])) {
$info[$key] = trim(substr($info[$key],0, $limit[$key]));
}
}
return $info;
}
/**
* Marks user deleted in internal user database and notifies the auth plugin.
* Also unenrols user from all roles and does other cleanup.
* @param object $user Userobject before delete (without system magic quotes)
* @return boolean success
*/
function delete_user($user) {
global $CFG;
require_once($CFG->libdir.'/grouplib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/message/lib.php');
begin_sql();
// delete all grades - backup is kept in grade_grades_history table
if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
foreach ($grades as $grade) {
$grade->delete('userdelete');
}
}
//move unread messages from this user to read
message_move_userfrom_unread2read($user->id);
// remove from all groups
delete_records('groups_members', 'userid', $user->id);
// unenrol from all roles in all contexts
role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
// now do a final accesslib cleanup - removes all role assingments in user context and context itself
delete_context(CONTEXT_USER, $user->id);
require_once($CFG->dirroot.'/tag/lib.php');
tag_set('user', $user->id, array());
// workaround for bulk deletes of users with the same email address
$delname = addslashes("$user->email.".time());
while (record_exists('user', 'username', $delname)) { // no need to use mnethostid here
$delname++;
}
// mark internal user record as "deleted"
$updateuser = new object();
$updateuser->id = $user->id;
$updateuser->deleted = 1;
$updateuser->username = $delname; // Remember it just in case
$updateuser->email = md5($user->username);// Store hash of username, useful importing/restoring users
$updateuser->idnumber = ''; // Clear this field to free it up
$updateuser->timemodified = time();
if (update_record('user', $updateuser)) {
commit_sql();
// notify auth plugin - do not block the delete even when plugin fails
$authplugin = get_auth_plugin($user->auth);
$authplugin->user_delete($user);
events_trigger('user_deleted', $user);
return true;
} else {
rollback_sql();
return false;
}
}
/**
* Retrieve the guest user object
*
* @uses $CFG
* @return user A {@link $USER} object
*/
function guest_user() {
global $CFG;
if ($newuser = get_record('user', 'username', 'guest', 'mnethostid', $CFG->mnet_localhost_id)) {
$newuser->confirmed = 1;
$newuser->lang = $CFG->lang;
$newuser->lastip = getremoteaddr();
if (empty($newuser->lastip)) {
$newuser->lastip = '0.0.0.0';
}
}
return $newuser;
}
/**
* Given a username and password, this function looks them
* up using the currently selected authentication mechanism,
* and if the authentication is successful, it returns a
* valid $user object from the 'user' table.
*
* Uses auth_ functions from the currently active auth module
*
* After authenticate_user_login() returns success, you will need to
* log that the user has logged in, and call complete_user_login() to set
* the session up.
*
* @uses $CFG
* @param string $username User's username (with system magic quotes)
* @param string $password User's password (with system magic quotes)
* @return user|flase A {@link $USER} object or false if error
*/
function authenticate_user_login($username, $password) {
global $CFG;
$authsenabled = get_enabled_auth_plugins();
if ($user = get_complete_user_data('username', $username)) {
$auth = empty($user->auth) ? 'manual' : $user->auth; // use manual if auth not set
if ($auth=='nologin' or !is_enabled_auth($auth)) {
add_to_log(0, 'login', 'error', 'index.php', $username);
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
return false;
}
$auths = array($auth);
} else {
// check if there's a deleted record (cheaply)
if (get_field('user', 'id', 'username', $username, 'deleted', 1, '')) {
error_log('[client '.$_SERVER['REMOTE_ADDR']."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
return false;
}
$auths = $authsenabled;
$user = new object();
$user->id = 0; // User does not exist
}
foreach ($auths as $auth) {
$authplugin = get_auth_plugin($auth);
// on auth fail fall through to the next plugin
if (!$authplugin->user_login($username, $password)) {
continue;
}
// successful authentication
if ($user->id) { // User already exists in database
if (empty($user->auth)) { // For some reason auth isn't set yet
set_field('user', 'auth', $auth, 'username', $username);
$user->auth = $auth;
}
if (empty($user->firstaccess)) { //prevent firstaccess from remaining 0 for manual account that never required confirmation
set_field('user','firstaccess', $user->timemodified, 'id', $user->id);
$user->firstaccess = $user->timemodified;
}
update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
if (!$authplugin->is_internal()) { // update user record from external DB