-
New Feature
-
Resolution: Won't Do
-
Trivial
-
None
-
1.5.2
-
None
-
All
-
MOODLE_15_STABLE
For Slav(ic) and I think Finnish and there are certainly more languages, language-specific grammar is not handled in Moodle code, no matter the version is. For instance, when Admin goes to the Edit User Accounts he can see e.g. 6 Korisnici in Serbian instead of 6 korisnika for 6 Users, because plural form when in no count-related context (Korisnici) differs from the count-related versions, namely: 1 poruka (being 1 message in English), 2, 3, 4 poruke (messages), but then if we count further, we say 5, 6, 7,..., 20 poruka (in English it remains messages), then 21, 31, 41,... are the same as 1, 22-24,32-34,42-44 the same as 2-4, and 25-30, 35-40,... are the same as 5-10 and 11-19, 111-119, 211-219 (poruka). I can suggest a function for handling this:
/**
- more specific function to accommodate for plural forms of known words
- @param int $num numerical value that has to have appropriate plural form
- @param string $singlr singular form
- @param string $frm2 word form for $num values ending with a number between 2 and 4
- @param string $frm5 word form for $num values ending with a number between 5 and 9, 0 and between 11 and 14
- @return string (hopefully) correct plural form
*/
function sr_la_utf8_plu_ext($num = 1, $singlrfrm = '', $frm2 = '', $frm5 = '') {
//determine what plural form to apply according to $num
if (($num % 10 > 4 and $num % 10 < 10) or ($num % 100 < 15 and $num % 100 > 10))
{ $result = $frm5; } elseif ($num % 10 == 1) { $result = $singlrfrm; } else { $result = $frm5; }return $result;
}
Declinations are another problem - there are so many languages with them, but no support at all!
e.g. resource = izvor in Serbian, but
Adding a resource = dodavanje izvora
activity=aktivnost, but adding a new activity is dodavanje nove aktivnosti.
I have also got functions for this, they don't seem to be general enough, but I 'm still working on them. More important is, I think, how to organize the support for this. I myself have put all language-grammar-related functions into langfun.php, residing in language pack folder and then updated functions in lib/moodlelib.php to allow one more code evaluation (eval function in PHP) in get_string for translated expressions between e.g. <eval> and </eval> tags. An example for this comes from moodle.php in language pack:
$string['addinganewto'] = 'Dodavanje nov<eval>sr_la_utf8_gen_ext(sr_la_utf8_ma_fem(\$a->what),\'og\',\'e\',\'og\')</eval> <eval>sr_la_utf8_gen(\'$a->what\')</eval> <eval>sr_la_utf8_dat(\'$a->to\')</eval>';
(sr_la_utf8_ma_fem is the gender detection function, the others are for handling declinations, namely the Genitive and Dative Case)
another example:
$string['displayingrecords'] = 'Za prikaz: $a zapis<eval>sr_la_utf8_plu_ext($a,\'\',\'a\',\'a\')</eval>';
Function for accommodating this is as follows (it is eval_get_string, located in moodlelib.php; here is the whole file, the code sequences added are commented with //Bojan Milosavljevic:...); look in lines: 4514, and for the rest of code in 836, 861, 4411, 4420, 4442, 4458:
<?php // $Id: moodlelib.php,v 1.565.2.21 2005/07/15 11:11:08 stronk7 Exp $
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.org //
// //
// Copyright (C) 1999-2004 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
- @version $Id: moodlelib.php,v 1.565.2.21 2005/07/15 11:11:08 stronk7 Exp $
- @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- @package moodlecore
*/
/// CONSTANTS /////////////////////////////////////////////////////////////
/**
- Used by some scripts to check they are being called by Moodle
*/
define('MOODLE_INTERNAL', true);
/**
- No groups used?
*/
define('NOGROUPS', 0);
/**
- Groups used?
*/
define('SEPARATEGROUPS', 1);
/**
- Groups visible?
*/
define('VISIBLEGROUPS', 2);
/**
- 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 - if set then the parameter is cleaned of scripts etc
*/
define('PARAM_RAW', 0x0000);
define('PARAM_CLEAN', 0x0001);
define('PARAM_INT', 0x0002);
define('PARAM_INTEGER', 0x0002); // Alias for PARAM_INT
define('PARAM_ALPHA', 0x0004);
define('PARAM_ACTION', 0x0004); // Alias for PARAM_ALPHA
define('PARAM_FORMAT', 0x0004); // Alias for PARAM_ALPHA
define('PARAM_NOTAGS', 0x0008);
define('PARAM_FILE', 0x0010);
define('PARAM_PATH', 0x0020);
define('PARAM_HOST', 0x0040); // FQDN or IPv4 dotted quad
define('PARAM_URL', 0x0080);
define('PARAM_LOCALURL', 0x0180); // NOT orthogonal to the others! Implies PARAM_URL!
define('PARAM_CLEANFILE',0x0200);
define('PARAM_ALPHANUM', 0x0400); //numbers or letters only
define('PARAM_BOOL', 0x0800); //convert to value 1 or 0 using empty()
define('PARAM_CLEANHTML',0x1000); //actual HTML code that you want cleaned and slashes removed
define('PARAM_ALPHAEXT', 0x2000); // PARAM_ALPHA plus the chars in quotes: /-_ allowed
define('PARAM_SAFEDIR', 0x4000); // safe directory name, suitable for include() and require()
/**
- Definition of page types
*/
define('PAGE_COURSE_VIEW', 'course-view');
/// 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 $varname the name of the parameter variable we want
- @param integer $options a bit field that specifies any cleaning needed
- @return mixed
*/
function required_param($varname, $options=PARAM_CLEAN) {
if (isset($_POST[$varname]))
{ // POST has precedence $param = $_POST[$varname]; } else if (isset($_GET[$varname])) { $param = $_GET[$varname]; } else { error('A required parameter ('.$varname.') was missing'); }return clean_param($param, $options);
}
/**
* 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 $varname the name of the parameter variable we want
* @param mixed $default the default value to return if nothing is found
* @param integer $options a bit field that specifies any cleaning needed
* @return mixed
*/
function optional_param($varname, $default=NULL, $options=PARAM_CLEAN) {
if (isset($_POST[$varname])) { // POST has precedence $param = $_POST[$varname]; }
else if (isset($_GET[$varname]))
{ $param = $_GET[$varname]; }else
{ return $default; }return clean_param($param, $options);
}
/**
- Used by
{@link optional_param()}
and
{@link required_param()} to
* clean the variables and/or cast to specific types, based on
* an options field.
*
* @param mixed $param the variable we are cleaning
* @param integer $options a bit field that specifies the cleaning needed
* @return mixed
*/
function clean_param($param, $options) {
global $CFG;
if (!$options) { return $param; // Return raw value }
if ((string)$param == (string)(int)$param) { // It's just an integer return (int)$param; }
if ($options & PARAM_CLEAN) { $param = stripslashes($param); // Needed by kses to work fine $param = clean_text($param); // Sweep for scripts, etc $param = addslashes($param); // Restore original request parameter slashes }
if ($options & PARAM_INT) { $param = (int)$param; // Convert to integer }
if ($options & PARAM_ALPHA) { // Remove everything not a-z $param = eregi_replace('[^a-zA-Z]', '', $param); }
if ($options & PARAM_ALPHANUM) { // Remove everything not a-zA-Z0-9 $param = eregi_replace('[^A-Za-z0-9]', '', $param); }
if ($options & PARAM_ALPHAEXT) { // Remove everything not a-zA-Z/_- $param = eregi_replace('[^a-zA-Z/_-]', '', $param); }
if ($options & PARAM_BOOL) { // Convert to 1 or 0 $param = empty($param) ? 0 : 1; }
if ($options & PARAM_NOTAGS) { // Strip all tags completely $param = strip_tags($param); }
if ($options & PARAM_SAFEDIR) { // Remove everything not a-zA-Z0-9_- $param = eregi_replace('[^a-zA-Z0-9_-]', '', $param); }
if ($options & PARAM_CLEANFILE) { // allow only safe characters $param = clean_filename($param); }
if ($options & PARAM_FILE) { // Strip all suspicious characters from filename
$param = ereg_replace('[[:cntrl:]]/[<>`\/\':\\/]', '', $param);
$param = ereg_replace('\.\.+', '', $param);
if($param == '.') { $param = ''; }
}
if ($options & PARAM_PATH) { // Strip all suspicious characters from file path $param = str_replace('\\\'', '\'', $param); $param = str_replace('\\', '', $param); $param = str_replace('\\', '/', $param); $param = ereg_replace('[[:cntrl:]]/[<>`\/\':]', '', $param); $param = ereg_replace('\.\.+', '', $param); $param = ereg_replace('//+', '/', $param); $param = ereg_replace('/(\./)+', '/', $param); }
if ($options & PARAM_HOST) { // allow FQDN or IPv4 dotted quad
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=''; }
}
if ($options & PARAM_URL) { // allow safe ftp, http, mailto urls
include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
//
// Parameters to validateurlsyntax()
//
// s? scheme is optional
// H? http optional
// S? https optional
// F? ftp optional
// E? mailto optional
// u- user section not allowed
// P- password not allowed
// a? address optional
// I? Numeric IP address optional (can use IP or domain)
// p- port not allowed – restrict to default port
// f? file path section optional
// q? query section optional
// r? fragment (anchor) optional
//
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 }
$options ^= PARAM_URL; // Turn off the URL bit so that simple PARAM_URLs don't test true for PARAM_LOCALURL
}
if ($options & PARAM_LOCALURL) {
// assume we passed the PARAM_URL test...
// allow http absolute, root relative and relative URLs within wwwroot
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 = ''; }
}
}
}
if ($options & PARAM_CLEANHTML) { $param = stripslashes($param); // Remove any slashes $param = clean_text($param); // Sweep for scripts, etc $param = trim($param); // Sweep for scripts, etc }
return $param;
}
/**
* 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 boolean
*/
function confirm_sesskey($sesskey=NULL) {
global $USER;
if (!empty($USER->ignoresesskey) // !empty($CFG->ignoresesskey)) { return true; }
if (empty($sesskey)) { $sesskey = required_param('sesskey'); // Check script parameters }
if (!isset($USER->sesskey)) { return false; }
return ($USER->sesskey === $sesskey);
}
/**
* Improved ensure a variable is set
* Issue a (custom) error message if not
* @param mixed $var the variable
* @param string $error optional additional error message
*/
function assert_var_set( $var, $error='' ) {
if (! isset($var)) { error( a required variable is not set - $error ); }
}
/**
* Ensure that a variable is set
*
* If $var is undefined throw an error, otherwise return $var.
* This function will soon be made obsolete by {@link required_param()}
*
- @param mixed $var the variable which may be unset
- @param mixed $default the value to return if $var is unset
*/
function require_variable($var) {
if (! isset($var))
{ error('A required parameter was missing'); }}
/**
- Ensure that a variable is set
*
- If $var is undefined set it (by reference), otherwise return $var.
*
- @param mixed $var the variable which may be unset
- @param mixed $default the value to return if $var is unset
*/
function optional_variable(&$var, $default=0) {
if (! isset($var))
{ $var = $default; }}
/**
- 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.
*
* @param string $name the key to set
* @param string $value the value to set
* @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)) {
$CFG->$name = $value; // So it's defined for this invocation at least
if (get_field('config', 'name', 'name', $name)) { return set_field('config', 'value', $value, 'name', $name); } else { $config->name = $name; $config->value = $value; return insert_record('config', $config); }
} else { // plugin scope
if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) { return set_field('config_plugins', 'value', $value, 'id', $id); } else { $config->plugin = $plugin; $config->name = $name; $config->value = $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.
*
* @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_record('config_plugins', 'plugin' , $plugin, 'name', $name); } else { return get_record('config', '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; } else {
if ($localcfg[$config->name] != $config->value ) {
// complain if the DB has a different
// value than config.php does
error_log(\$CFG->{$config->name} in config.php ({$localcfg[$config->name]}) overrides database setting ({$config->value}));
}
}
}
$localcfg = (object)$localcfg;
return $localcfg;
} else { // preserve $CFG if DB returns nothing or error return $CFG; }
}
}
/**
* Refresh current $USER session global variable with all their current preferences.
* @uses $USER
*/
function reload_user_preferences() {
global $USER;
if(empty($USER) // empty($USER->id)) { return false; }
unset($USER->preference);
if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
foreach ($preferences as $preference) { $USER->preference[$preference->name] = $preference->value; }
} else { //return empty preference array to hold new values $USER->preference = array(); }
}
/**
* 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.
* @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 $userid A moodle user ID
* @todo Add inline links to $USER and user functions in above line.
* @return boolean
*/
function set_user_preference($name, $value, $otheruser=NULL) {
global $USER;
if (empty($otheruser)){
if (!empty($USER) && !empty($USER->id)) { $userid = $USER->id; } else { return false; }
} else { $userid = $otheruser; }
if (empty($name)) { return false; }
if ($preference = get_record('user_preferences', 'userid', $userid, 'name', $name)) {
if (set_field('user_preferences', 'value', $value, 'id', $preference->id)) {
if (empty($otheruser) and !empty($USER)) { $USER->preference[$name] = $value; }
return true;
} else { return false; }
} else {
$preference->userid = $userid;
$preference->name = $name;
$preference->value = (string)$value;
if (insert_record('user_preferences', $preference)) {
if (empty($otheruser) and !empty($USER)) { $USER->preference[$name] = $value; }
return true;
} else { return false; }
}
}
/**
* 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 $userid A moodle user ID
* @return boolean
*/
function unset_user_preference($name, $userid=NULL) {
global $USER;
if (empty($userid)){
if(!empty($USER) && !empty($USER->id)) { $userid = $USER->id; }
else { return false; }
}
return delete_records('user_preferences', 'userid', $userid, 'name', $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 $userid A moodle user ID
* @return boolean
*/
function set_user_preferences($prefarray, $userid=NULL) {
global $USER;
if (!is_array($prefarray) or empty($prefarray)) { return false; }
if (empty($userid)){
if (!empty($USER) && !empty($USER->id)) { $userid = NULL; // Continue with the current user below } else { return false; // No-one to set! }
}
$return = true;
foreach ($prefarray as $name => $value) { // The order is important; if the test for return is done first, then // if one function call fails all the remaining ones will be optimized away $return = set_user_preference($name, $value, $userid) and $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 $userid A moodle user ID
* @uses $USER
* @return string
*/
function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
global $USER;
if (empty($userid)) { // assume current user
if (empty($USER->preference)) { return $default; // Default value (or NULL) }
if (empty($name)) { return $USER->preference; // Whole array }
if (!isset($USER->preference[$name])) { return $default; // Default value (or NULL) }
return $USER->preference[$name]; // The single value
} else {
$preference = get_records_menu('user_preferences', 'userid', $userid, 'name', 'name,value');
if (empty($name)) { return $preference; }
if (!isset($preference[$name])) { return $default; // Default value (or NULL) }
return $preference[$name]; // The single value
}
}
/// 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
* @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) {
$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); }
}
return $time;
}
/**
* Given an amount of time in seconds, returns string
* formatted nicely as months, days, hours etc as needed
*
* @uses MINSECS
* @uses HOURSECS
* @uses DAYSECS
* @param int $totalsecs ?
* @param array $str ?
* @return string
* @todo Finish documenting this function
*/
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'); }
$days = floor($totalsecs/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;
$odays = '';
$ohours = '';
$omins = '';
$osecs = '';
if ($days) $odays = $days .' '. $sd;
if ($hours) $ohours = $hours .' '. $sh;
if ($mins) $omins = $mins .' '. $sm;
if ($secs) $osecs = $secs .' '. $ss;
if ($days) return $odays .' '. $ohours;
if ($hours) return $ohours .' '. $omins;
if ($mins) return $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 boolean $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;
static $strftimedaydatetime;
if ($format == '') {
if (empty($strftimedaydatetime)) { $strftimedaydatetime = get_string('strftimedaydatetime'); }
$format = $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);
$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); }
}
//Bojan Milosavljevic: Language specific unicode from (primarily Windows) server system date to current language charset
sys_to_currlang_charset ($datestring);
return $datestring;
}
/**
* Converts (Windows) Unicode characters to current language character set
*
* @param string $datestr A date string to convert
* @return string the converted date
* @todo SHOULD BE MORE GENERAL!... find server-system- and current-language charset and then map corresponding chars,
* but this is just a patch
*/
function sys_to_currlang_charset(&$datestr) {
//COPIED FROM get_string FUNCTION!!
global $CFG;
$lang = current_language();
/// Define the two or three major locations of language strings for this module
$location = $CFG->dirroot.'/lang/';
/// Bojan Milosavljevic: functions for language specific grammar
$langfunfile = $location.$lang.'/langfun.php';
if (file_exists($langfunfile)) {
include_once($langfunfile);
$maps = charset_maps();
// END OF COPIED CODE FROM get_string FUNCTION
foreach ($maps as $i => $map) { $datestr = str_replace ($i, $map,$datestr); }
}
return 0;
}
/**
* 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) {
$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);
$time += intval((float)$timezone * HOURSECS);
$datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
list(
$getdate['seconds'],
$getdate['minutes'],
$getdate['hours'],
$getdate['mday'],
$getdate['mon'],
$getdate['year'],
$getdate['wday'],
$getdate['yday'],
$getdate['weekday'],
$getdate['month']
) = 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) { $timezone = get_user_timezone_offset($timezone); $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 'GMT'; }
else if($tz > 0) { return 'GMT+'.$tz; }
else { return 'GMT'.$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 The user's timezone
* @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;
}
}
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) && $next = each($timezones)) { $tz = $next['value']; }
return is_numeric($tz) ? (float) $tz : $tz;
}
function get_timezone_record($timezonename) {
global $CFG, $db;
static $cache = NULL;
if ($cache === NULL) { $cache = array(); }
if (isset($cache[$timezonename])) { return $cache[$timezonename]; }
return get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
}
function calculate_user_dst_table($from_year = NULL, $to_year = NULL) {
global $CFG, $USER;
if (empty($USER)) { return false; }
$usertz = get_user_timezone();
if (is_float($usertz)) { // Trivial timezone, no DST return false; }
if (!empty($USER->dstoffsettz) && $USER->dstoffsettz != $usertz) { // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset unset($USER->dstoffsets); unset($USER->dstrange); }
if (!empty($USER->dstoffsets) && 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
$USER->dstoffsettz = $usertz;
if(empty($USER->dstoffsets)) { // If we 're creating from scratch, put the two guard elements in there $USER->dstoffsets = array(1 => NULL, 0 => NULL); }
if(empty($USER->dstrange)) {
// 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
$USER->dstrange = array($from, $to);
}
else {
// If needing to extend the table, do the same
$yearstoprocess = array();
$from = max((empty($from_year) ? $USER->dstrange[0] : $from_year), 1971);
$to = min((empty($to_year) ? $USER->dstrange[1] : $to_year), 2035);
if($from < $USER->dstrange[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 < $USER->dstrange[0]; ++$i) { $yearstoprocess[] = $i; }
$USER->dstrange[0] = $from;
}
if($to > $USER->dstrange[1]) {
// Take note of which years we need to process and then note that we have processed them for future calls
for($i = $USER->dstrange[1] + 1; $i <= $to; ++$i) { $yearstoprocess[] = $i; }
$USER->dstrange[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
$presetrecords = 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($presetrecords)) { return false; }
// Remove ending guard (first element of the array)
reset($USER->dstoffsets);
unset($USER->dstoffsets[key($USER->dstoffsets)]);
// Add all required change timestamps
foreach($yearstoprocess as $y) {
// Find the record which is in effect for the year $y
foreach($presetrecords as $year => $preset) {
if($year <= $y) { break; }
}
$changes = dst_changes_for_year($y, $preset);
if($changes === NULL) { continue; }
if($changes['dst'] != 0) { $USER->dstoffsets[$changes['dst']] = $preset->dstoff * MINSECS; }
if($changes['std'] != 0) { $USER->dstoffsets[$changes['std']] = 0; }
}
// Put in a guard element at the top
$maxtimestamp = max(array_keys($USER->dstoffsets));
$USER->dstoffsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any small number will do
// Sort again
krsort($USER->dstoffsets);
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) {
global $USER;
if(!calculate_user_dst_table()) { return 0; }
if(empty($USER) // empty($USER->dstoffsets)) { return 0; }
reset($USER->dstoffsets);
while(list($from, $offset) = each($USER->dstoffsets)) {
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 $USER->dstrange[0]
if($USER->dstrange[0] == 1971) { return 0; }
calculate_user_dst_table($USER->dstrange[0] - 5, NULL);
return dst_offset_on($time);
}
else {
// We need a year larger than $USER->dstrange[1]
if($USER->dstrange[1] == 2035) { return 0; }
calculate_user_dst_table(NULL, $USER->dstrange[1] + 5);
return dst_offset_on($time);
}
}
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;
}
}
function days_in_month($month, $year) { return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0))); }
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.
function sesskey() {
global $USER;
if(!isset($USER)) { return false; }
if (empty($USER->sesskey)) { $USER->sesskey = random_string(10); }
return $USER->sesskey;
}
/**
* 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 $MoodleSession
- @param int $courseid id of the course
- @param boolean $autologinguest
- @param $cm course module object
*/
function require_login($courseid=0, $autologinguest=true, $cm=null) {
global $CFG, $SESSION, $USER, $FULLME, $MoodleSession;
// First check that the user is logged in to the site.
if (! (isset($USER->loggedin) and $USER->confirmed and ($USER->site == $CFG->wwwroot)) ) { // They're not
$SESSION->wantsurl = $FULLME;
if (!empty($_SERVER['HTTP_REFERER']))
{ $SESSION->fromurl = $_SERVER['HTTP_REFERER']; }$USER = NULL;
if ($autologinguest and $CFG->autologinguests and $courseid and get_field('course','guest','id',$courseid))
{ $loginguest = '?loginguest=true'; }else
{ $loginguest = ''; }if (empty($CFG->loginhttps))
{ redirect($CFG->wwwroot .'/login/index.php'. $loginguest); }else
{ $wwwroot = str_replace('http','https', $CFG->wwwroot); redirect($wwwroot .'/login/index.php'. $loginguest); }exit;
}
// check whether the user should be changing password
// reload_user_preferences(); // Why is this necessary? Seems wasteful. - MD
if (!empty($USER->preference['auth_forcepasswordchange'])){
if (is_internal_auth() // $CFG->
{'auth_'.$USER->auth.'_stdchangepassword'})
{ $SESSION->wantsurl = $FULLME; redirect($CFG->wwwroot .'/login/change_password.php'); }elseif($CFG->changepassword)
{ redirect($CFG->changepassword); }else
{ error('You cannot proceed without changing your password. However there is no available page for changing it. Please contact your Moodle Administrator.'); }}
// 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 .'&course='. SITEID); }// Make sure current IP matches the one for this session (if required)
if (!empty($CFG->tracksessionip)) {
if ($USER->sessionIP != md5(getremoteaddr()))
{ error(get_string('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'); }}
// If the site is currently under maintenance, then print a message
if (!isadmin()) {
if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html'))
{ print_maintenance_message(); exit; }}
// Next, check if the user can be in a particular course
if ($courseid) {
if ($courseid == SITEID) { // Anyone can be in the site course
if (isset($cm) and !$cm->visible and !isteacher(SITEID))
{ // Not allowed to see module, send to course page redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden')); }return;
}
if (!empty($USER->student[$courseid]) or !empty($USER->teacher[$courseid]) or !empty($USER->admin)) {
if (isset($USER->realuser)) { // Make sure the REAL person can also access this course
if (!isteacher($courseid, $USER->realuser)) { print_header(); notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/'); }
}
if (isset($cm) and !$cm->visible and !isteacher($courseid)) { // Not allowed to see module, send to course page redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden')); }
return; // user is a member of this course.
}
if (! $course = get_record('course', 'id', $courseid))
{ error('That course doesn\'t exist'); }if (!$course->visible)
{ print_header(); notice(get_string('coursehidden'), $CFG->wwwroot .'/'); }if ($USER->username == 'guest') {
switch ($course->guest) {
case 0: // Guests not allowed
print_header();
notice(get_string('guestsnotallowed', '', $course->fullname), $CFG->wwwroot/login/index.php);
break;
case 1: // Guests allowed
if (isset($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')); }return;
case 2: // Guests allowed with key (drop through)
break;
}
}
//User is not enrolled in the course, wants to access course content
//as a guest, and course setting allow unlimited guest access
//Code cribbed from course/loginas.php
if (strstr($FULLME,username=guest) && ($course->guest==1)) {
$realuser = $USER->id;
$realname = fullname($USER, true);
$USER = guest_user();
$USER->loggedin = true;
$USER->site = $CFG->wwwroot;
$USER->realuser = $realuser;
$USER->sessionIP = md5(getremoteaddr()); // Store the current IP in the session
if (isset($SESSION->currentgroup))
{ // Remember current cache setting for later $SESSION->oldcurrentgroup = $SESSION->currentgroup; unset($SESSION->currentgroup); }$guest_name = fullname($USER, true);
add_to_log($course->id, course, loginas, ../user/view.php?id=$course->id&$USER->id$, $realname -> $guest_name);
if (isset($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')); }return;
}
// Currently not enrolled in the course, so see if they want to enrol
$SESSION->wantsurl = $FULLME;
redirect($CFG->wwwroot .'/course/enrol.php?id='. $courseid);
die;
}
}
/**
- 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 object $course The course object in question
- @param boolean $autologinguest Allow autologin guests if that is wanted
- @param object $cm Course activity module if known
*/
function require_course_login($course, $autologinguest=true, $cm=null) {
global $CFG;
if (!empty($CFG->forcelogin))
{ require_login(); }if ($course->id != SITEID)
{ require_login($course->id, $autologinguest, $cm); }}
/**
- Modify the user table by setting the currently logged in user's
- last login to now.
*
- @uses $USER
- @return boolean
*/
function update_user_login_times()
{ global $USER; $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 boolean
*/
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; }// 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 ($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); }
}
/**
* @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); }
}
/**
* 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); error(get_string('errortoomanylogins')); }
}
/**
* Resets login attempts
*
* @uses $SESSION
*/
function reset_login_count() { global $SESSION; $SESSION->logincount = 0; }
/**
* check_for_restricted_user
*
* @uses $CFG
* @uses $USER
* @param string $username ?
* @param string $redirect ?
* @todo Finish documenting this function
*/
function check_for_restricted_user($username=NULL, $redirect='') {
global $CFG, $USER;
if (!$username) {
if (!empty($USER->username)) { $username = $USER->username; } else { return false; }
}
if (!empty($CFG->restrictusers)) {
$names = explode(',', $CFG->restrictusers);
if (in_array($username, $names)) { error(get_string('restricteduser', 'error', fullname($USER)), $redirect); }
}
}
function sync_metacourses() {
global $CFG;
if (!$courses = get_records_sql(SELECT DISTINCT parent_course,1 FROM {$CFG->prefix}course_meta)) { return; }
foreach ($courses as $course) { sync_metacourse($course->parent_course); }
}
/**
* Goes through all enrolment records for the courses inside the metacourse and sync with them.
*/
function sync_metacourse($metacourseid) {
global $CFG,$db;
if (!$metacourse = get_record(course,id,$metacourseid)) { return false; }
if (count_records('course_meta','parent_course',$metacourseid) == 0) { // if there are no child courses for this meta course, nuke the enrolments
if ($enrolments = get_records('user_students','course',$metacourseid,'','userid,1')) {
foreach ($enrolments as $enrolment)
{ unenrol_student($enrolment->userid,$metacourseid); }}
return true;
}
// this will return a list of userids from user_student for enrolments in the metacourse that shouldn't be there.
$sql = SELECT parent.userid,max(child.course) as course
FROM {$CFG->prefix}course_meta meta
JOIN {$CFG->prefix}user_students parent
ON meta.parent_course = parent.course
LEFT OUTER JOIN {$CFG->prefix}user_students child
ON child.course = meta.child_course
AND child.userid = parent.userid
WHERE meta.parent_course = $metacourseid
GROUP BY child.course,parent.userid
ORDER BY parent.userid,child.course;
$res = $db->Execute($sql);
//iterate results
$enrolmentstodelete = array();
while( !$res->EOF && isset($res->fields) )
{ $enrolmentstodelete[] = $res->fields; $res->MoveNext(); }if (!empty($enrolmentstodelete)) {
$last->id = 0;
$last->course = 0;
foreach ($enrolmentstodelete as $enrolment) {
$enrolment = (object)$enrolment;
if (count($enrolmentstodelete) == 1 && empty($enrolment->course))
{ unenrol_student($enrolment->userid,$metacourseid); break; }if ($last->id != $enrolment->userid) { // we've changed
if (empty($last->course) && !empty($last->id))
{ unenrol_student($last->id,$metacourseid); // doing it this way for forum subscriptions etc. }$last->course = 0;
$last->id = $enrolment->userid;
}
if (!empty($enrolment->course))
{ $last->course = $enrolment->course; }}
if (!empty($last->id) && empty($last->course))
{ unenrol_student($last->id,$metacourseid); // doing it this way for forum subscriptions etc. }}
// this will return a list of userids that need to be enrolled in the metacourse
$sql = SELECT DISTINCT child.userid,1
FROM {$CFG->prefix}course_meta meta
JOIN {$CFG->prefix}user_students child
ON meta.child_course = child.course
LEFT OUTER JOIN {$CFG->prefix}user_students parent
ON meta.parent_course = parent.course
AND parent.userid = child.userid
WHERE parent.course IS NULL
AND meta.parent_course = $metacourseid;
if ($userstoadd = get_records_sql($sql)) {
foreach ($userstoadd as $user)
{ enrol_student($user->userid,$metacourseid); }}
// and next make sure that we have the right start time and end time (ie max and min) for them all.
if ($enrolments = get_records('user_students','course',$metacourseid,'','id,userid')) {
foreach ($enrolments as $enrol) {
if ($maxmin = get_record_sql(SELECT min(timestart) AS timestart, max(timeend) AS timeend
FROM {$CFG->prefix}user_students u JOIN {$CFG->prefix}course_meta mc ON u.course = mc.child_course WHERE userid = $enrol->userid
AND mc.parent_course = $metacourseid))
{ $enrol->timestart = $maxmin->timestart; $enrol->timeend = $maxmin->timeend; update_record('user_students',$enrol); }}
}
return true;
}
/**
- 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->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',