[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/ -> moodlelib.php (source)

   1  <?php // $Id$
   2  
   3  ///////////////////////////////////////////////////////////////////////////
   4  //                                                                       //
   5  // NOTICE OF COPYRIGHT                                                   //
   6  //                                                                       //
   7  // Moodle - Modular Object-Oriented Dynamic Learning Environment         //
   8  //          http://moodle.org                                            //
   9  //                                                                       //
  10  // Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
  11  //                                                                       //
  12  // This program is free software; you can redistribute it and/or modify  //
  13  // it under the terms of the GNU General Public License as published by  //
  14  // the Free Software Foundation; either version 2 of the License, or     //
  15  // (at your option) any later version.                                   //
  16  //                                                                       //
  17  // This program is distributed in the hope that it will be useful,       //
  18  // but WITHOUT ANY WARRANTY; without even the implied warranty of        //
  19  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
  20  // GNU General Public License for more details:                          //
  21  //                                                                       //
  22  //          http://www.gnu.org/copyleft/gpl.html                         //
  23  //                                                                       //
  24  ///////////////////////////////////////////////////////////////////////////
  25  
  26  /**
  27   * moodlelib.php - Moodle main library
  28   *
  29   * Main library file of miscellaneous general-purpose Moodle functions.
  30   * Other main libraries:
  31   *  - weblib.php      - functions that produce web output
  32   *  - datalib.php     - functions that access the database
  33   * @author Martin Dougiamas
  34   * @version $Id$
  35   * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  36   * @package moodlecore
  37   */
  38  
  39  /// CONSTANTS (Encased in phpdoc proper comments)/////////////////////////
  40  
  41  /**
  42   * Used by some scripts to check they are being called by Moodle
  43   */
  44  define('MOODLE_INTERNAL', true);
  45  
  46  /// Date and time constants ///
  47  /**
  48   * Time constant - the number of seconds in a year
  49   */
  50  
  51  define('YEARSECS', 31536000);
  52  
  53  /**
  54   * Time constant - the number of seconds in a week
  55   */
  56  define('WEEKSECS', 604800);
  57  
  58  /**
  59   * Time constant - the number of seconds in a day
  60   */
  61  define('DAYSECS', 86400);
  62  
  63  /**
  64   * Time constant - the number of seconds in an hour
  65   */
  66  define('HOURSECS', 3600);
  67  
  68  /**
  69   * Time constant - the number of seconds in a minute
  70   */
  71  define('MINSECS', 60);
  72  
  73  /**
  74   * Time constant - the number of minutes in a day
  75   */
  76  define('DAYMINS', 1440);
  77  
  78  /**
  79   * Time constant - the number of minutes in an hour
  80   */
  81  define('HOURMINS', 60);
  82  
  83  /// Parameter constants - every call to optional_param(), required_param()  ///
  84  /// or clean_param() should have a specified type of parameter.  //////////////
  85  
  86  /**
  87   * PARAM_RAW specifies a parameter that is not cleaned/processed in any way;
  88   * originally was 0, but changed because we need to detect unknown
  89   * parameter types and swiched order in clean_param().
  90   */
  91  define('PARAM_RAW', 666);
  92  
  93  /**
  94   * PARAM_CLEAN - obsoleted, please try to use more specific type of parameter.
  95   * It was one of the first types, that is why it is abused so much ;-)
  96   */
  97  define('PARAM_CLEAN',    0x0001);
  98  
  99  /**
 100   * PARAM_INT - integers only, use when expecting only numbers.
 101   */
 102  define('PARAM_INT',      0x0002);
 103  
 104  /**
 105   * PARAM_INTEGER - an alias for PARAM_INT
 106   */
 107  define('PARAM_INTEGER',  0x0002);
 108  
 109  /**
 110   * PARAM_NUMBER - a real/floating point number.
 111   */
 112  define('PARAM_NUMBER',  0x000a);
 113  
 114  /**
 115   * PARAM_ALPHA - contains only english letters.
 116   */
 117  define('PARAM_ALPHA',    0x0004);
 118  
 119  /**
 120   * PARAM_ACTION - an alias for PARAM_ALPHA, use for various actions in formas and urls
 121   * @TODO: should we alias it to PARAM_ALPHANUM ?
 122   */
 123  define('PARAM_ACTION',   0x0004);
 124  
 125  /**
 126   * PARAM_FORMAT - an alias for PARAM_ALPHA, use for names of plugins, formats, etc.
 127   * @TODO: should we alias it to PARAM_ALPHANUM ?
 128   */
 129  define('PARAM_FORMAT',   0x0004);
 130  
 131  /**
 132   * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
 133   */
 134  define('PARAM_NOTAGS',   0x0008);
 135  
 136   /**
 137   * PARAM_MULTILANG - alias of PARAM_TEXT.
 138   */
 139  define('PARAM_MULTILANG',  0x0009);
 140  
 141   /**
 142   * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags.
 143   */
 144  define('PARAM_TEXT',  0x0009);
 145  
 146  /**
 147   * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
 148   */
 149  define('PARAM_FILE',     0x0010);
 150  
 151  /**
 152   * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international alphanumeric with spaces
 153   */
 154  define('PARAM_TAG',   0x0011);
 155  
 156  /**
 157   * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
 158   */
 159  define('PARAM_TAGLIST',   0x0012);
 160  
 161  /**
 162   * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
 163   * note: the leading slash is not removed, window drive letter is not allowed
 164   */
 165  define('PARAM_PATH',     0x0020);
 166  
 167  /**
 168   * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
 169   */
 170  define('PARAM_HOST',     0x0040);
 171  
 172  /**
 173   * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not acceppted but http://localhost.localdomain/ is ok.
 174   */
 175  define('PARAM_URL',      0x0080);
 176  
 177  /**
 178   * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the others! Implies PARAM_URL!)
 179   */
 180  define('PARAM_LOCALURL', 0x0180);
 181  
 182  /**
 183   * PARAM_CLEANFILE - safe file name, all dangerous and regional chars are removed,
 184   * use when you want to store a new file submitted by students
 185   */
 186  define('PARAM_CLEANFILE',0x0200);
 187  
 188  /**
 189   * PARAM_ALPHANUM - expected numbers and letters only.
 190   */
 191  define('PARAM_ALPHANUM', 0x0400);
 192  
 193  /**
 194   * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
 195   */
 196  define('PARAM_BOOL',     0x0800);
 197  
 198  /**
 199   * PARAM_CLEANHTML - cleans submitted HTML code and removes slashes
 200   * note: do not forget to addslashes() before storing into database!
 201   */
 202  define('PARAM_CLEANHTML',0x1000);
 203  
 204  /**
 205   * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "/-_" allowed,
 206   * suitable for include() and require()
 207   * @TODO: should we rename this function to PARAM_SAFEDIRS??
 208   */
 209  define('PARAM_ALPHAEXT', 0x2000);
 210  
 211  /**
 212   * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
 213   */
 214  define('PARAM_SAFEDIR',  0x4000);
 215  
 216  /**
 217   * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9.  Numbers and comma only.
 218   */
 219  define('PARAM_SEQUENCE',  0x8000);
 220  
 221  /**
 222   * PARAM_PEM - Privacy Enhanced Mail format
 223   */
 224  define('PARAM_PEM',      0x10000);
 225  
 226  /**
 227   * PARAM_BASE64 - Base 64 encoded format
 228   */
 229  define('PARAM_BASE64',   0x20000);
 230  
 231  
 232  /// Page types ///
 233  /**
 234   * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
 235   */
 236  define('PAGE_COURSE_VIEW', 'course-view');
 237  
 238  /// Debug levels ///
 239  /** no warnings at all */
 240  define ('DEBUG_NONE', 0);
 241  /** E_ERROR | E_PARSE */
 242  define ('DEBUG_MINIMAL', 5);
 243  /** E_ERROR | E_PARSE | E_WARNING | E_NOTICE */
 244  define ('DEBUG_NORMAL', 15);
 245  /** E_ALL without E_STRICT for now, do show recoverable fatal errors */
 246  define ('DEBUG_ALL', 6143);
 247  /** DEBUG_ALL with extra Moodle debug messages - (DEBUG_ALL | 32768) */
 248  define ('DEBUG_DEVELOPER', 38911);
 249  
 250  /**
 251   * Blog access level constant declaration
 252   */
 253  define ('BLOG_USER_LEVEL', 1);
 254  define ('BLOG_GROUP_LEVEL', 2);
 255  define ('BLOG_COURSE_LEVEL', 3);
 256  define ('BLOG_SITE_LEVEL', 4);
 257  define ('BLOG_GLOBAL_LEVEL', 5);
 258  
 259  /**
 260   * Tag constanst
 261   */
 262  //To prevent problems with multibytes strings, this should not exceed the
 263  //length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
 264  define('TAG_MAX_LENGTH', 50);
 265  
 266  /**
 267   * Password policy constants
 268   */
 269  define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
 270  define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
 271  define ('PASSWORD_DIGITS', '0123456789');
 272  define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
 273  
 274  if (!defined('SORT_LOCALE_STRING')) { // PHP < 4.4.0 - TODO: remove in 2.0
 275      define('SORT_LOCALE_STRING', SORT_STRING);
 276  }
 277  
 278  
 279  /// PARAMETER HANDLING ////////////////////////////////////////////////////
 280  
 281  /**
 282   * Returns a particular value for the named variable, taken from
 283   * POST or GET.  If the parameter doesn't exist then an error is
 284   * thrown because we require this variable.
 285   *
 286   * This function should be used to initialise all required values
 287   * in a script that are based on parameters.  Usually it will be
 288   * used like this:
 289   *    $id = required_param('id');
 290   *
 291   * @param string $parname the name of the page parameter we want
 292   * @param int $type expected type of parameter
 293   * @return mixed
 294   */
 295  function required_param($parname, $type=PARAM_CLEAN) {
 296  
 297      // detect_unchecked_vars addition
 298      global $CFG;
 299      if (!empty($CFG->detect_unchecked_vars)) {
 300          global $UNCHECKED_VARS;
 301          unset ($UNCHECKED_VARS->vars[$parname]);
 302      }
 303  
 304      if (isset($_POST[$parname])) {       // POST has precedence
 305          $param = $_POST[$parname];
 306      } else if (isset($_GET[$parname])) {
 307          $param = $_GET[$parname];
 308      } else {
 309          error('A required parameter ('.$parname.') was missing');
 310      }
 311  
 312      return clean_param($param, $type);
 313  }
 314  
 315  /**
 316   * Returns a particular value for the named variable, taken from
 317   * POST or GET, otherwise returning a given default.
 318   *
 319   * This function should be used to initialise all optional values
 320   * in a script that are based on parameters.  Usually it will be
 321   * used like this:
 322   *    $name = optional_param('name', 'Fred');
 323   *
 324   * @param string $parname the name of the page parameter we want
 325   * @param mixed  $default the default value to return if nothing is found
 326   * @param int $type expected type of parameter
 327   * @return mixed
 328   */
 329  function optional_param($parname, $default=NULL, $type=PARAM_CLEAN) {
 330  
 331      // detect_unchecked_vars addition
 332      global $CFG;
 333      if (!empty($CFG->detect_unchecked_vars)) {
 334          global $UNCHECKED_VARS;
 335          unset ($UNCHECKED_VARS->vars[$parname]);
 336      }
 337  
 338      if (isset($_POST[$parname])) {       // POST has precedence
 339          $param = $_POST[$parname];
 340      } else if (isset($_GET[$parname])) {
 341          $param = $_GET[$parname];
 342      } else {
 343          return $default;
 344      }
 345  
 346      return clean_param($param, $type);
 347  }
 348  
 349  /**
 350   * Used by {@link optional_param()} and {@link required_param()} to
 351   * clean the variables and/or cast to specific types, based on
 352   * an options field.
 353   * <code>
 354   * $course->format = clean_param($course->format, PARAM_ALPHA);
 355   * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
 356   * </code>
 357   *
 358   * @uses $CFG
 359   * @uses PARAM_RAW
 360   * @uses PARAM_CLEAN
 361   * @uses PARAM_CLEANHTML
 362   * @uses PARAM_INT
 363   * @uses PARAM_NUMBER
 364   * @uses PARAM_ALPHA
 365   * @uses PARAM_ALPHANUM
 366   * @uses PARAM_ALPHAEXT
 367   * @uses PARAM_SEQUENCE
 368   * @uses PARAM_BOOL
 369   * @uses PARAM_NOTAGS
 370   * @uses PARAM_TEXT
 371   * @uses PARAM_SAFEDIR
 372   * @uses PARAM_CLEANFILE
 373   * @uses PARAM_FILE
 374   * @uses PARAM_PATH
 375   * @uses PARAM_HOST
 376   * @uses PARAM_URL
 377   * @uses PARAM_LOCALURL
 378   * @uses PARAM_PEM
 379   * @uses PARAM_BASE64
 380   * @uses PARAM_TAG
 381   * @uses PARAM_SEQUENCE
 382   * @param mixed $param the variable we are cleaning
 383   * @param int $type expected format of param after cleaning.
 384   * @return mixed
 385   */
 386  function clean_param($param, $type) {
 387  
 388      global $CFG;
 389  
 390      if (is_array($param)) {              // Let's loop
 391          $newparam = array();
 392          foreach ($param as $key => $value) {
 393              $newparam[$key] = clean_param($value, $type);
 394          }
 395          return $newparam;
 396      }
 397  
 398      switch ($type) {
 399          case PARAM_RAW:          // no cleaning at all
 400              return $param;
 401  
 402          case PARAM_CLEAN:        // General HTML cleaning, try to use more specific type if possible
 403              if (is_numeric($param)) {
 404                  return $param;
 405              }
 406              $param = stripslashes($param);   // Needed for kses to work fine
 407              $param = clean_text($param);     // Sweep for scripts, etc
 408              return addslashes($param);       // Restore original request parameter slashes
 409  
 410          case PARAM_CLEANHTML:    // prepare html fragment for display, do not store it into db!!
 411              $param = stripslashes($param);   // Remove any slashes
 412              $param = clean_text($param);     // Sweep for scripts, etc
 413              return trim($param);
 414  
 415          case PARAM_INT:
 416              return (int)$param;  // Convert to integer
 417  
 418          case PARAM_NUMBER:
 419              return (float)$param;  // Convert to integer
 420  
 421          case PARAM_ALPHA:        // Remove everything not a-z
 422              return eregi_replace('[^a-zA-Z]', '', $param);
 423  
 424          case PARAM_ALPHANUM:     // Remove everything not a-zA-Z0-9
 425              return eregi_replace('[^A-Za-z0-9]', '', $param);
 426  
 427          case PARAM_ALPHAEXT:     // Remove everything not a-zA-Z/_-
 428              return eregi_replace('[^a-zA-Z/_-]', '', $param);
 429  
 430          case PARAM_SEQUENCE:     // Remove everything not 0-9,
 431              return eregi_replace('[^0-9,]', '', $param);
 432  
 433          case PARAM_BOOL:         // Convert to 1 or 0
 434              $tempstr = strtolower($param);
 435              if ($tempstr == 'on' or $tempstr == 'yes' ) {
 436                  $param = 1;
 437              } else if ($tempstr == 'off' or $tempstr == 'no') {
 438                  $param = 0;
 439              } else {
 440                  $param = empty($param) ? 0 : 1;
 441              }
 442              return $param;
 443  
 444          case PARAM_NOTAGS:       // Strip all tags
 445              return strip_tags($param);
 446  
 447          case PARAM_TEXT:    // leave only tags needed for multilang
 448              return clean_param(strip_tags($param, '<lang><span>'), PARAM_CLEAN);
 449  
 450          case PARAM_SAFEDIR:      // Remove everything not a-zA-Z0-9_-
 451              return eregi_replace('[^a-zA-Z0-9_-]', '', $param);
 452  
 453          case PARAM_CLEANFILE:    // allow only safe characters
 454              return clean_filename($param);
 455  
 456          case PARAM_FILE:         // Strip all suspicious characters from filename
 457              $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
 458              $param = ereg_replace('\.\.+', '', $param);
 459              if($param == '.') {
 460                  $param = '';
 461              }
 462              return $param;
 463  
 464          case PARAM_PATH:         // Strip all suspicious characters from file path
 465              $param = str_replace('\\\'', '\'', $param);
 466              $param = str_replace('\\"', '"', $param);
 467              $param = str_replace('\\', '/', $param);
 468              $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
 469              $param = ereg_replace('\.\.+', '', $param);
 470              $param = ereg_replace('//+', '/', $param);
 471              return ereg_replace('/(\./)+', '/', $param);
 472  
 473          case PARAM_HOST:         // allow FQDN or IPv4 dotted quad
 474              $param = preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
 475              // match ipv4 dotted quad
 476              if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
 477                  // confirm values are ok
 478                  if ( $match[0] > 255
 479                       || $match[1] > 255
 480                       || $match[3] > 255
 481                       || $match[4] > 255 ) {
 482                      // hmmm, what kind of dotted quad is this?
 483                      $param = '';
 484                  }
 485              } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
 486                         && !preg_match('/^[\.-]/',  $param) // no leading dots/hyphens
 487                         && !preg_match('/[\.-]$/',  $param) // no trailing dots/hyphens
 488                         ) {
 489                  // all is ok - $param is respected
 490              } else {
 491                  // all is not ok...
 492                  $param='';
 493              }
 494              return $param;
 495  
 496          case PARAM_URL:          // allow safe ftp, http, mailto urls
 497              include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
 498              if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
 499                  // all is ok, param is respected
 500              } else {
 501                  $param =''; // not really ok
 502              }
 503              return $param;
 504  
 505          case PARAM_LOCALURL:     // allow http absolute, root relative and relative URLs within wwwroot
 506              $param = clean_param($param, PARAM_URL);
 507              if (!empty($param)) {
 508                  if (preg_match(':^/:', $param)) {
 509                      // root-relative, ok!
 510                  } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
 511                      // absolute, and matches our wwwroot
 512                  } else {
 513                      // relative - let's make sure there are no tricks
 514                      if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
 515                          // looks ok.
 516                      } else {
 517                          $param = '';
 518                      }
 519                  }
 520              }
 521              return $param;
 522  
 523          case PARAM_PEM:
 524              $param = trim($param);
 525              // PEM formatted strings may contain letters/numbers and the symbols
 526              // forward slash: /
 527              // plus sign:     +
 528              // equal sign:    =
 529              // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes
 530              if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
 531                  list($wholething, $body) = $matches;
 532                  unset($wholething, $matches);
 533                  $b64 = clean_param($body, PARAM_BASE64);
 534                  if (!empty($b64)) {
 535                      return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
 536                  } else {
 537                      return '';
 538                  }
 539              }
 540              return '';
 541  
 542          case PARAM_BASE64:
 543              if (!empty($param)) {
 544                  // PEM formatted strings may contain letters/numbers and the symbols
 545                  // forward slash: /
 546                  // plus sign:     +
 547                  // equal sign:    =
 548                  if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
 549                      return '';
 550                  }
 551                  $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
 552                  // Each line of base64 encoded data must be 64 characters in
 553                  // length, except for the last line which may be less than (or
 554                  // equal to) 64 characters long.
 555                  for ($i=0, $j=count($lines); $i < $j; $i++) {
 556                      if ($i + 1 == $j) {
 557                          if (64 < strlen($lines[$i])) {
 558                              return '';
 559                          }
 560                          continue;
 561                      }
 562  
 563                      if (64 != strlen($lines[$i])) {
 564                          return '';
 565                      }
 566                  }
 567                  return implode("\n",$lines);
 568              } else {
 569                  return '';
 570              }
 571  
 572          case PARAM_TAG:
 573              //as long as magic_quotes_gpc is used, a backslash will be a
 574              //problem, so remove *all* backslash.
 575              $param = str_replace('\\', '', $param);
 576              //convert many whitespace chars into one
 577              $param = preg_replace('/\s+/', ' ', $param);
 578              $textlib = textlib_get_instance();
 579              $param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
 580              return $param;
 581  
 582  
 583          case PARAM_TAGLIST:
 584              $tags = explode(',', $param);
 585              $result = array();
 586              foreach ($tags as $tag) {
 587                  $res = clean_param($tag, PARAM_TAG);
 588                  if ($res != '') {
 589                      $result[] = $res;
 590                  }
 591              }
 592              if ($result) {
 593                  return implode(',', $result);
 594              } else {
 595                  return '';
 596              }
 597  
 598          default:                 // throw error, switched parameters in optional_param or another serious problem
 599              error("Unknown parameter type: $type");
 600      }
 601  }
 602  
 603  /**
 604   * This function is useful for testing whether something you got back from
 605   * the HTML editor actually contains anything. Sometimes the HTML editor
 606   * appear to be empty, but actually you get back a <br> tag or something.
 607   *
 608   * @param string $string a string containing HTML.
 609   * @return boolean does the string contain any actual content - that is text,
 610   * images, objcts, etc.
 611   */
 612  function html_is_blank($string) {
 613      return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
 614  }
 615  
 616  /**
 617   * Set a key in global configuration
 618   *
 619   * Set a key/value pair in both this session's {@link $CFG} global variable
 620   * and in the 'config' database table for future sessions.
 621   *
 622   * Can also be used to update keys for plugin-scoped configs in config_plugin table.
 623   * In that case it doesn't affect $CFG.
 624   *
 625   * A NULL value will delete the entry.
 626   *
 627   * @param string $name the key to set
 628   * @param string $value the value to set (without magic quotes)
 629   * @param string $plugin (optional) the plugin scope
 630   * @uses $CFG
 631   * @return bool
 632   */
 633  function set_config($name, $value, $plugin=NULL) {
 634  /// No need for get_config because they are usually always available in $CFG
 635  
 636      global $CFG;
 637  
 638      if (empty($plugin)) {
 639          if (!array_key_exists($name, $CFG->config_php_settings)) {
 640              // So it's defined for this invocation at least
 641              if (is_null($value)) {
 642                  unset($CFG->$name);
 643              } else {
 644                  $CFG->$name = (string)$value; // settings from db are always strings
 645              }
 646          }
 647  
 648          if (get_field('config', 'name', 'name', $name)) {
 649              if ($value===null) {
 650                  return delete_records('config', 'name', $name);
 651              } else {
 652                  return set_field('config', 'value', addslashes($value), 'name', $name);
 653              }
 654          } else {
 655              if ($value===null) {
 656                  return true;
 657              }
 658              $config = new object();
 659              $config->name = $name;
 660              $config->value = addslashes($value);
 661              return insert_record('config', $config);
 662          }
 663      } else { // plugin scope
 664          if ($id = get_field('config_plugins', 'id', 'name', $name, 'plugin', $plugin)) {
 665              if ($value===null) {
 666                  return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
 667              } else {
 668                  return set_field('config_plugins', 'value', addslashes($value), 'id', $id);
 669              }
 670          } else {
 671              if ($value===null) {
 672                  return true;
 673              }
 674              $config = new object();
 675              $config->plugin = addslashes($plugin);
 676              $config->name   = $name;
 677              $config->value  = addslashes($value);
 678              return insert_record('config_plugins', $config);
 679          }
 680      }
 681  }
 682  
 683  /**
 684   * Get configuration values from the global config table
 685   * or the config_plugins table.
 686   *
 687   * If called with no parameters it will do the right thing
 688   * generating $CFG safely from the database without overwriting
 689   * existing values.
 690   *
 691   * If called with 2 parameters it will return a $string single
 692   * value or false of the value is not found.
 693   *
 694   * @param string $plugin
 695   * @param string $name
 696   * @uses $CFG
 697   * @return hash-like object or single value
 698   *
 699   */
 700  function get_config($plugin=NULL, $name=NULL) {
 701  
 702      global $CFG;
 703  
 704      if (!empty($name)) { // the user is asking for a specific value
 705          if (!empty($plugin)) {
 706              return get_field('config_plugins', 'value', 'plugin' , $plugin, 'name', $name);
 707          } else {
 708              return get_field('config', 'value', 'name', $name);
 709          }
 710      }
 711  
 712      // the user is after a recordset
 713      if (!empty($plugin)) {
 714          if ($configs=get_records('config_plugins', 'plugin', $plugin, '', 'name,value')) {
 715              $configs = (array)$configs;
 716              $localcfg = array();
 717              foreach ($configs as $config) {
 718                  $localcfg[$config->name] = $config->value;
 719              }
 720              return (object)$localcfg;
 721          } else {
 722              return false;
 723          }
 724      } else {
 725          // this was originally in setup.php
 726          if ($configs = get_records('config')) {
 727              $localcfg = (array)$CFG;
 728              foreach ($configs as $config) {
 729                  if (!isset($localcfg[$config->name])) {
 730                      $localcfg[$config->name] = $config->value;
 731                  }
 732                  // do not complain anymore if config.php overrides settings from db
 733              }
 734  
 735              $localcfg = (object)$localcfg;
 736              return $localcfg;
 737          } else {
 738              // preserve $CFG if DB returns nothing or error
 739              return $CFG;
 740          }
 741  
 742      }
 743  }
 744  
 745  /**
 746   * Removes a key from global configuration
 747   *
 748   * @param string $name the key to set
 749   * @param string $plugin (optional) the plugin scope
 750   * @uses $CFG
 751   * @return bool
 752   */
 753  function unset_config($name, $plugin=NULL) {
 754  
 755      global $CFG;
 756  
 757      unset($CFG->$name);
 758  
 759      if (empty($plugin)) {
 760          return delete_records('config', 'name', $name);
 761      } else {
 762          return delete_records('config_plugins', 'name', $name, 'plugin', $plugin);
 763      }
 764  }
 765  
 766  /**
 767   * Get volatile flags
 768   *
 769   * @param string $type
 770   * @param int    $changedsince
 771   * @return records array
 772   *
 773   */
 774  function get_cache_flags($type, $changedsince=NULL) {
 775  
 776      $type = addslashes($type);
 777  
 778      $sqlwhere = 'flagtype=\'' . $type . '\' AND expiry >= ' . time();
 779      if ($changedsince !== NULL) {
 780          $changedsince = (int)$changedsince;
 781          $sqlwhere .= ' AND timemodified > ' . $changedsince;
 782      }
 783      $cf = array();
 784      if ($flags=get_records_select('cache_flags', $sqlwhere, '', 'name,value')) {
 785          foreach ($flags as $flag) {
 786              $cf[$flag->name] = $flag->value;
 787          }
 788      }
 789      return $cf;
 790  }
 791  
 792  /**
 793   * Get volatile flags
 794   *
 795   * @param string $type
 796   * @param string $name
 797   * @param int    $changedsince
 798   * @return records array
 799   *
 800   */
 801  function get_cache_flag($type, $name, $changedsince=NULL) {
 802  
 803      $type = addslashes($type);
 804      $name = addslashes($name);
 805  
 806      $sqlwhere = 'flagtype=\'' . $type . '\' AND name=\'' . $name . '\' AND expiry >= ' . time();
 807      if ($changedsince !== NULL) {
 808          $changedsince = (int)$changedsince;
 809          $sqlwhere .= ' AND timemodified > ' . $changedsince;
 810      }
 811      return get_field_select('cache_flags', 'value', $sqlwhere);
 812  }
 813  
 814  /**
 815   * Set a volatile flag
 816   *
 817   * @param string $type the "type" namespace for the key
 818   * @param string $name the key to set
 819   * @param string $value the value to set (without magic quotes) - NULL will remove the flag
 820   * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
 821   * @return bool
 822   */
 823  function set_cache_flag($type, $name, $value, $expiry=NULL) {
 824  
 825  
 826      $timemodified = time();
 827      if ($expiry===NULL || $expiry < $timemodified) {
 828          $expiry = $timemodified + 24 * 60 * 60;
 829      } else {
 830          $expiry = (int)$expiry;
 831      }
 832  
 833      if ($value === NULL) {
 834          return unset_cache_flag($type,$name);
 835      }
 836  
 837      $type = addslashes($type);
 838      $name = addslashes($name);
 839      if ($f = get_record('cache_flags', 'name', $name, 'flagtype', $type)) { // this is a potentail problem in DEBUG_DEVELOPER
 840          if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
 841              return true; //no need to update; helps rcache too
 842          }
 843          $f->value        = addslashes($value);
 844          $f->expiry       = $expiry;
 845          $f->timemodified = $timemodified;
 846          return update_record('cache_flags', $f);
 847      } else {
 848          $f = new object();
 849          $f->flagtype     = $type;
 850          $f->name         = $name;
 851          $f->value        = addslashes($value);
 852          $f->expiry       = $expiry;
 853          $f->timemodified = $timemodified;
 854          return (bool)insert_record('cache_flags', $f);
 855      }
 856  }
 857  
 858  /**
 859   * Removes a single volatile flag
 860   *
 861   * @param string $type the "type" namespace for the key
 862   * @param string $name the key to set
 863   * @uses $CFG
 864   * @return bool
 865   */
 866  function unset_cache_flag($type, $name) {
 867  
 868      return delete_records('cache_flags',
 869                            'name', addslashes($name),
 870                            'flagtype', addslashes($type));
 871  }
 872  
 873  /**
 874   * Garbage-collect volatile flags
 875   *
 876   */
 877  function gc_cache_flags() {
 878      return delete_records_select('cache_flags', 'expiry < ' . time());
 879  }
 880  
 881  /**
 882   * Refresh current $USER session global variable with all their current preferences.
 883   * @uses $USER
 884   */
 885  function reload_user_preferences() {
 886  
 887      global $USER;
 888  
 889      //reset preference
 890      $USER->preference = array();
 891  
 892      if (!isloggedin() or isguestuser()) {
 893          // no permanent storage for not-logged-in user and guest
 894  
 895      } else if ($preferences = get_records('user_preferences', 'userid', $USER->id)) {
 896          foreach ($preferences as $preference) {
 897              $USER->preference[$preference->name] = $preference->value;
 898          }
 899      }
 900  
 901      return true;
 902  }
 903  
 904  /**
 905   * Sets a preference for the current user
 906   * Optionally, can set a preference for a different user object
 907   * @uses $USER
 908   * @todo Add a better description and include usage examples. Add inline links to $USER and user functions in above line.
 909  
 910   * @param string $name The key to set as preference for the specified user
 911   * @param string $value The value to set forthe $name key in the specified user's record
 912   * @param int $otheruserid A moodle user ID
 913   * @return bool
 914   */
 915  function set_user_preference($name, $value, $otheruserid=NULL) {
 916  
 917      global $USER;
 918  
 919      if (!isset($USER->preference)) {
 920          reload_user_preferences();
 921      }
 922  
 923      if (empty($name)) {
 924          return false;
 925      }
 926  
 927      $nostore = false;
 928  
 929      if (empty($otheruserid)){
 930          if (!isloggedin() or isguestuser()) {
 931              $nostore = true;
 932          }
 933          $userid = $USER->id;
 934      } else {
 935          if (isguestuser($otheruserid)) {
 936              $nostore = true;
 937          }
 938          $userid = $otheruserid;
 939      }
 940  
 941      $return = true;
 942      if ($nostore) {
 943          // no permanent storage for not-logged-in user and guest
 944  
 945      } else if ($preference = get_record('user_preferences', 'userid', $userid, 'name', addslashes($name))) {
 946          if ($preference->value === $value) {
 947              return true;
 948          }
 949          if (!set_field('user_preferences', 'value', addslashes((string)$value), 'id', $preference->id)) {
 950              $return = false;
 951          }
 952  
 953      } else {
 954          $preference = new object();
 955          $preference->userid = $userid;
 956          $preference->name   = addslashes($name);
 957          $preference->value  = addslashes((string)$value);
 958          if (!insert_record('user_preferences', $preference)) {
 959              $return = false;
 960          }
 961      }
 962  
 963      // update value in USER session if needed
 964      if ($userid == $USER->id) {
 965          $USER->preference[$name] = (string)$value;
 966      }
 967  
 968      return $return;
 969  }
 970  
 971  /**
 972   * Unsets a preference completely by deleting it from the database
 973   * Optionally, can set a preference for a different user id
 974   * @uses $USER
 975   * @param string  $name The key to unset as preference for the specified user
 976   * @param int $otheruserid A moodle user ID
 977   */
 978  function unset_user_preference($name, $otheruserid=NULL) {
 979  
 980      global $USER;
 981  
 982      if (!isset($USER->preference)) {
 983          reload_user_preferences();
 984      }
 985  
 986      if (empty($otheruserid)){
 987          $userid = $USER->id;
 988      } else {
 989          $userid = $otheruserid;
 990      }
 991  
 992      //Delete the preference from $USER if needed
 993      if ($userid == $USER->id) {
 994          unset($USER->preference[$name]);
 995      }
 996  
 997      //Then from DB
 998      return delete_records('user_preferences', 'userid', $userid, 'name', addslashes($name));
 999  }
1000  
1001  
1002  /**
1003   * Sets a whole array of preferences for the current user
1004   * @param array $prefarray An array of key/value pairs to be set
1005   * @param int $otheruserid A moodle user ID
1006   * @return bool
1007   */
1008  function set_user_preferences($prefarray, $otheruserid=NULL) {
1009  
1010      if (!is_array($prefarray) or empty($prefarray)) {
1011          return false;
1012      }
1013  
1014      $return = true;
1015      foreach ($prefarray as $name => $value) {
1016          // The order is important; test for return is done first
1017          $return = (set_user_preference($name, $value, $otheruserid) && $return);
1018      }
1019      return $return;
1020  }
1021  
1022  /**
1023   * If no arguments are supplied this function will return
1024   * all of the current user preferences as an array.
1025   * If a name is specified then this function
1026   * attempts to return that particular preference value.  If
1027   * none is found, then the optional value $default is returned,
1028   * otherwise NULL.
1029   * @param string $name Name of the key to use in finding a preference value
1030   * @param string $default Value to be returned if the $name key is not set in the user preferences
1031   * @param int $otheruserid A moodle user ID
1032   * @uses $USER
1033   * @return string
1034   */
1035  function get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL) {
1036      global $USER;
1037  
1038      if (!isset($USER->preference)) {
1039          reload_user_preferences();
1040      }
1041  
1042      if (empty($otheruserid)){
1043          $userid = $USER->id;
1044      } else {
1045          $userid = $otheruserid;
1046      }
1047  
1048      if ($userid == $USER->id) {
1049          $preference = $USER->preference;
1050  
1051      } else {
1052          $preference = array();
1053          if ($prefdata = get_records('user_preferences', 'userid', $userid)) {
1054              foreach ($prefdata as $pref) {
1055                  $preference[$pref->name] = $pref->value;
1056              }
1057          }
1058      }
1059  
1060      if (empty($name)) {
1061          return $preference;            // All values
1062  
1063      } else if (array_key_exists($name, $preference)) {
1064          return $preference[$name];    // The single value
1065  
1066      } else {
1067          return $default;              // Default value (or NULL)
1068      }
1069  }
1070  
1071  
1072  /// FUNCTIONS FOR HANDLING TIME ////////////////////////////////////////////
1073  
1074  /**
1075   * Given date parts in user time produce a GMT timestamp.
1076   *
1077   * @param int $year The year part to create timestamp of
1078   * @param int $month The month part to create timestamp of
1079   * @param int $day The day part to create timestamp of
1080   * @param int $hour The hour part to create timestamp of
1081   * @param int $minute The minute part to create timestamp of
1082   * @param int $second The second part to create timestamp of
1083   * @param float $timezone ?
1084   * @param bool $applydst ?
1085   * @return int timestamp
1086   * @todo Finish documenting this function
1087   */
1088  function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
1089  
1090      $strtimezone = NULL;
1091      if (!is_numeric($timezone)) {
1092          $strtimezone = $timezone;
1093      }
1094  
1095      $timezone = get_user_timezone_offset($timezone);
1096  
1097      if (abs($timezone) > 13) {
1098          $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1099      } else {
1100          $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
1101          $time = usertime($time, $timezone);
1102          if($applydst) {
1103              $time -= dst_offset_on($time, $strtimezone);
1104          }
1105      }
1106  
1107      return $time;
1108  
1109  }
1110  
1111  /**
1112   * Given an amount of time in seconds, returns string
1113   * formatted nicely as weeks, days, hours etc as needed
1114   *
1115   * @uses MINSECS
1116   * @uses HOURSECS
1117   * @uses DAYSECS
1118   * @uses YEARSECS
1119   * @param int $totalsecs ?
1120   * @param array $str ?
1121   * @return string
1122   */
1123   function format_time($totalsecs, $str=NULL) {
1124  
1125      $totalsecs = abs($totalsecs);
1126  
1127      if (!$str) {  // Create the str structure the slow way
1128          $str->day   = get_string('day');
1129          $str->days  = get_string('days');
1130          $str->hour  = get_string('hour');
1131          $str->hours = get_string('hours');
1132          $str->min   = get_string('min');
1133          $str->mins  = get_string('mins');
1134          $str->sec   = get_string('sec');
1135          $str->secs  = get_string('secs');
1136          $str->year  = get_string('year');
1137          $str->years = get_string('years');
1138      }
1139  
1140  
1141      $years     = floor($totalsecs/YEARSECS);
1142      $remainder = $totalsecs - ($years*YEARSECS);
1143      $days      = floor($remainder/DAYSECS);
1144      $remainder = $totalsecs - ($days*DAYSECS);
1145      $hours     = floor($remainder/HOURSECS);
1146      $remainder = $remainder - ($hours*HOURSECS);
1147      $mins      = floor($remainder/MINSECS);
1148      $secs      = $remainder - ($mins*MINSECS);
1149  
1150      $ss = ($secs == 1)  ? $str->sec  : $str->secs;
1151      $sm = ($mins == 1)  ? $str->min  : $str->mins;
1152      $sh = ($hours == 1) ? $str->hour : $str->hours;
1153      $sd = ($days == 1)  ? $str->day  : $str->days;
1154      $sy = ($years == 1)  ? $str->year  : $str->years;
1155  
1156      $oyears = '';
1157      $odays = '';
1158      $ohours = '';
1159      $omins = '';
1160      $osecs = '';
1161  
1162      if ($years)  $oyears  = $years .' '. $sy;
1163      if ($days)  $odays  = $days .' '. $sd;
1164      if ($hours) $ohours = $hours .' '. $sh;
1165      if ($mins)  $omins  = $mins .' '. $sm;
1166      if ($secs)  $osecs  = $secs .' '. $ss;
1167  
1168      if ($years) return trim($oyears .' '. $odays);
1169      if ($days)  return trim($odays .' '. $ohours);
1170      if ($hours) return trim($ohours .' '. $omins);
1171      if ($mins)  return trim($omins .' '. $osecs);
1172      if ($secs)  return $osecs;
1173      return get_string('now');
1174  }
1175  
1176  /**
1177   * Returns a formatted string that represents a date in user time
1178   * <b>WARNING: note that the format is for strftime(), not date().</b>
1179   * Because of a bug in most Windows time libraries, we can't use
1180   * the nicer %e, so we have to use %d which has leading zeroes.
1181   * A lot of the fuss in the function is just getting rid of these leading
1182   * zeroes as efficiently as possible.
1183   *
1184   * If parameter fixday = true (default), then take off leading
1185   * zero from %d, else mantain it.
1186   *
1187   * @uses HOURSECS
1188   * @param  int $date timestamp in GMT
1189   * @param string $format strftime format
1190   * @param float $timezone
1191   * @param bool $fixday If true (default) then the leading
1192   * zero from %d is removed. If false then the leading zero is mantained.
1193   * @return string
1194   */
1195  function userdate($date, $format='', $timezone=99, $fixday = true) {
1196  
1197      global $CFG;
1198  
1199      $strtimezone = NULL;
1200      if (!is_numeric($timezone)) {
1201          $strtimezone = $timezone;
1202      }
1203  
1204      if (empty($format)) {
1205          $format = get_string('strftimedaydatetime');
1206      }
1207  
1208      if (!empty($CFG->nofixday)) {  // Config.php can force %d not to be fixed.
1209          $fixday = false;
1210      } else if ($fixday) {
1211          $formatnoday = str_replace('%d', 'DD', $format);
1212          $fixday = ($formatnoday != $format);
1213      }
1214  
1215      $date += dst_offset_on($date, $strtimezone);
1216  
1217      $timezone = get_user_timezone_offset($timezone);
1218  
1219      if (abs($timezone) > 13) {   /// Server time
1220          if ($fixday) {
1221              $datestring = strftime($formatnoday, $date);
1222              $daystring  = str_replace(' 0', '', strftime(' %d', $date));
1223              $datestring = str_replace('DD', $daystring, $datestring);
1224          } else {
1225              $datestring = strftime($format, $date);
1226          }
1227      } else {
1228          $date += (int)($timezone * 3600);
1229          if ($fixday) {
1230              $datestring = gmstrftime($formatnoday, $date);
1231              $daystring  = str_replace(' 0', '', gmstrftime(' %d', $date));
1232              $datestring = str_replace('DD', $daystring, $datestring);
1233          } else {
1234              $datestring = gmstrftime($format, $date);
1235          }
1236      }
1237  
1238  /// If we are running under Windows convert from windows encoding to UTF-8
1239  /// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
1240  
1241     if ($CFG->ostype == 'WINDOWS') {
1242         if ($localewincharset = get_string('localewincharset')) {
1243             $textlib = textlib_get_instance();
1244             $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
1245         }
1246     }
1247  
1248      return $datestring;
1249  }
1250  
1251  /**
1252   * Given a $time timestamp in GMT (seconds since epoch),
1253   * returns an array that represents the date in user time
1254   *
1255   * @uses HOURSECS
1256   * @param int $time Timestamp in GMT
1257   * @param float $timezone ?
1258   * @return array An array that represents the date in user time
1259   * @todo Finish documenting this function
1260   */
1261  function usergetdate($time, $timezone=99) {
1262  
1263      $strtimezone = NULL;
1264      if (!is_numeric($timezone)) {
1265          $strtimezone = $timezone;
1266      }
1267  
1268      $timezone = get_user_timezone_offset($timezone);
1269  
1270      if (abs($timezone) > 13) {    // Server time
1271          return getdate($time);
1272      }
1273  
1274      // There is no gmgetdate so we use gmdate instead
1275      $time += dst_offset_on($time, $strtimezone);
1276      $time += intval((float)$timezone * HOURSECS);
1277  
1278      $datestring = gmstrftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
1279  
1280      list(
1281          $getdate['seconds'],
1282          $getdate['minutes'],
1283          $getdate['hours'],
1284          $getdate['mday'],
1285          $getdate['mon'],
1286          $getdate['year'],
1287          $getdate['wday'],
1288          $getdate['yday'],
1289          $getdate['weekday'],
1290          $getdate['month']
1291      ) = explode('_', $datestring);
1292  
1293      return $getdate;
1294  }
1295  
1296  /**
1297   * Given a GMT timestamp (seconds since epoch), offsets it by
1298   * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
1299   *
1300   * @uses HOURSECS
1301   * @param  int $date Timestamp in GMT
1302   * @param float $timezone
1303   * @return int
1304   */
1305  function usertime($date, $timezone=99) {
1306  
1307      $timezone = get_user_timezone_offset($timezone);
1308  
1309      if (abs($timezone) > 13) {
1310          return $date;
1311      }
1312      return $date - (int)($timezone * HOURSECS);
1313  }
1314  
1315  /**
1316   * Given a time, return the GMT timestamp of the most recent midnight
1317   * for the current user.
1318   *
1319   * @param int $date Timestamp in GMT
1320   * @param float $timezone ?
1321   * @return ?
1322   */
1323  function usergetmidnight($date, $timezone=99) {
1324  
1325      $userdate = usergetdate($date, $timezone);
1326  
1327      // Time of midnight of this user's day, in GMT
1328      return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
1329  
1330  }
1331  
1332  /**
1333   * Returns a string that prints the user's timezone
1334   *
1335   * @param float $timezone The user's timezone
1336   * @return string
1337   */
1338  function usertimezone($timezone=99) {
1339  
1340      $tz = get_user_timezone($timezone);
1341  
1342      if (!is_float($tz)) {
1343          return $tz;
1344      }
1345  
1346      if(abs($tz) > 13) { // Server time
1347          return get_string('serverlocaltime');
1348      }
1349  
1350      if($tz == intval($tz)) {
1351          // Don't show .0 for whole hours
1352          $tz = intval($tz);
1353      }
1354  
1355      if($tz == 0) {
1356          return 'UTC';
1357      }
1358      else if($tz > 0) {
1359          return 'UTC+'.$tz;
1360      }
1361      else {
1362          return 'UTC'.$tz;
1363      }
1364  
1365  }
1366  
1367  /**
1368   * Returns a float which represents the user's timezone difference from GMT in hours
1369   * Checks various settings and picks the most dominant of those which have a value
1370   *
1371   * @uses $CFG
1372   * @uses $USER
1373   * @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
1374   * @return int
1375   */
1376  function get_user_timezone_offset($tz = 99) {
1377  
1378      global $USER, $CFG;
1379  
1380      $tz = get_user_timezone($tz);
1381  
1382      if (is_float($tz)) {
1383          return $tz;
1384      } else {
1385          $tzrecord = get_timezone_record($tz);
1386          if (empty($tzrecord)) {
1387              return 99.0;
1388          }
1389          return (float)$tzrecord->gmtoff / HOURMINS;
1390      }
1391  }
1392  
1393  /**
1394   * Returns an int which represents the systems's timezone difference from GMT in seconds
1395   * @param mixed $tz timezone
1396   * @return int if found, false is timezone 99 or error
1397   */
1398  function get_timezone_offset($tz) {
1399      global $CFG;
1400  
1401      if ($tz == 99) {
1402          return false;
1403      }
1404  
1405      if (is_numeric($tz)) {
1406          return intval($tz * 60*60);
1407      }
1408  
1409      if (!$tzrecord = get_timezone_record($tz)) {
1410          return false;
1411      }
1412      return intval($tzrecord->gmtoff * 60);
1413  }
1414  
1415  /**
1416   * Returns a float or a string which denotes the user's timezone
1417   * 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)
1418   * means that for this timezone there are also DST rules to be taken into account
1419   * Checks various settings and picks the most dominant of those which have a value
1420   *
1421   * @uses $USER
1422   * @uses $CFG
1423   * @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
1424   * @return mixed
1425   */
1426  function get_user_timezone($tz = 99) {
1427      global $USER, $CFG;
1428  
1429      $timezones = array(
1430          $tz,
1431          isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
1432          isset($USER->timezone) ? $USER->timezone : 99,
1433          isset($CFG->timezone) ? $CFG->timezone : 99,
1434          );
1435  
1436      $tz = 99;
1437  
1438      while(($tz == '' || $tz == 99 || $tz == NULL) && $next = each($timezones)) {
1439          $tz = $next['value'];
1440      }
1441  
1442      return is_numeric($tz) ? (float) $tz : $tz;
1443  }
1444  
1445  /**
1446   * ?
1447   *
1448   * @uses $CFG
1449   * @uses $db
1450   * @param string $timezonename ?
1451   * @return object
1452   */
1453  function get_timezone_record($timezonename) {
1454      global $CFG, $db;
1455      static $cache = NULL;
1456  
1457      if ($cache === NULL) {
1458          $cache = array();
1459      }
1460  
1461      if (isset($cache[$timezonename])) {
1462          return $cache[$timezonename];
1463      }
1464  
1465      return $cache[$timezonename] = get_record_sql('SELECT * FROM '.$CFG->prefix.'timezone
1466                                        WHERE name = '.$db->qstr($timezonename).' ORDER BY year DESC', true);
1467  }
1468  
1469  /**
1470   * ?
1471   *
1472   * @uses $CFG
1473   * @uses $USER
1474   * @param ? $fromyear ?
1475   * @param ? $to_year ?
1476   * @return bool
1477   */
1478  function calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL) {
1479      global $CFG, $SESSION;
1480  
1481      $usertz = get_user_timezone($strtimezone);
1482  
1483      if (is_float($usertz)) {
1484          // Trivial timezone, no DST
1485          return false;
1486      }
1487  
1488      if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
1489          // We have precalculated values, but the user's effective TZ has changed in the meantime, so reset
1490          unset($SESSION->dst_offsets);
1491          unset($SESSION->dst_range);
1492      }
1493  
1494      if (!empty($SESSION->dst_offsets) && empty($from_year) && empty($to_year)) {
1495          // Repeat calls which do not request specific year ranges stop here, we have already calculated the table
1496          // This will be the return path most of the time, pretty light computationally
1497          return true;
1498      }
1499  
1500      // Reaching here means we either need to extend our table or create it from scratch
1501  
1502      // Remember which TZ we calculated these changes for
1503      $SESSION->dst_offsettz = $usertz;
1504  
1505      if(empty($SESSION->dst_offsets)) {
1506          // If we 're creating from scratch, put the two guard elements in there
1507          $SESSION->dst_offsets = array(1 => NULL, 0 => NULL);
1508      }
1509      if(empty($SESSION->dst_range)) {
1510          // If creating from scratch
1511          $from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
1512          $to   = min((empty($to_year)   ? intval(date('Y')) + 3 : $to_year),   2035);
1513  
1514          // Fill in the array with the extra years we need to process
1515          $yearstoprocess = array();
1516          for($i = $from; $i <= $to; ++$i) {
1517              $yearstoprocess[] = $i;
1518          }
1519  
1520          // Take note of which years we have processed for future calls
1521          $SESSION->dst_range = array($from, $to);
1522      }
1523      else {
1524          // If needing to extend the table, do the same
1525          $yearstoprocess = array();
1526  
1527          $from = max((empty($from_year) ? $SESSION->dst_range[0] : $from_year), 1971);
1528          $to   = min((empty($to_year)   ? $SESSION->dst_range[1] : $to_year),   2035);
1529  
1530          if($from < $SESSION->dst_range[0]) {
1531              // Take note of which years we need to process and then note that we have processed them for future calls
1532              for($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
1533                  $yearstoprocess[] = $i;
1534              }
1535              $SESSION->dst_range[0] = $from;
1536          }
1537          if($to > $SESSION->dst_range[1]) {
1538              // Take note of which years we need to process and then note that we have processed them for future calls
1539              for($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
1540                  $yearstoprocess[] = $i;
1541              }
1542              $SESSION->dst_range[1] = $to;
1543          }
1544      }
1545  
1546      if(empty($yearstoprocess)) {
1547          // This means that there was a call requesting a SMALLER range than we have already calculated
1548          return true;
1549      }
1550  
1551      // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
1552      // Also, the array is sorted in descending timestamp order!
1553  
1554      // Get DB data
1555  
1556      static $presets_cache = array();
1557      if (!isset($presets_cache[$usertz])) {
1558          $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');
1559      }
1560      if(empty($presets_cache[$usertz])) {
1561          return false;
1562      }
1563  
1564      // Remove ending guard (first element of the array)
1565      reset($SESSION->dst_offsets);
1566      unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
1567  
1568      // Add all required change timestamps
1569      foreach($yearstoprocess as $y) {
1570          // Find the record which is in effect for the year $y
1571          foreach($presets_cache[$usertz] as $year => $preset) {
1572              if($year <= $y) {
1573                  break;
1574              }
1575          }
1576  
1577          $changes = dst_changes_for_year($y, $preset);
1578  
1579          if($changes === NULL) {
1580              continue;
1581          }
1582          if($changes['dst'] != 0) {
1583              $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
1584          }
1585          if($changes['std'] != 0) {
1586              $SESSION->dst_offsets[$changes['std']] = 0;
1587          }
1588      }
1589  
1590      // Put in a guard element at the top
1591      $maxtimestamp = max(array_keys($SESSION->dst_offsets));
1592      $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
1593  
1594      // Sort again
1595      krsort($SESSION->dst_offsets);
1596  
1597      return true;
1598  }
1599  
1600  function dst_changes_for_year($year, $timezone) {
1601  
1602      if($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 && $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
1603          return NULL;
1604      }
1605  
1606      $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
1607      $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
1608  
1609      list($dst_hour, $dst_min) = explode(':', $timezone->dst_time);
1610      list($std_hour, $std_min) = explode(':', $timezone->std_time);
1611  
1612      $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
1613      $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
1614  
1615      // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
1616      // This has the advantage of being able to have negative values for hour, i.e. for timezones
1617      // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
1618  
1619      $timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
1620      $timestd += $std_hour * HOURSECS + $std_min * MINSECS;
1621  
1622      return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
1623  }
1624  
1625  // $time must NOT be compensated at all, it has to be a pure timestamp
1626  function dst_offset_on($time, $strtimezone = NULL) {
1627      global $SESSION;
1628  
1629      if(!calculate_user_dst_table(NULL, NULL, $strtimezone) || empty($SESSION->dst_offsets)) {
1630          return 0;
1631      }
1632  
1633      reset($SESSION->dst_offsets);
1634      while(list($from, $offset) = each($SESSION->dst_offsets)) {
1635          if($from <= $time) {
1636              break;
1637          }
1638      }
1639  
1640      // This is the normal return path
1641      if($offset !== NULL) {
1642          return $offset;
1643      }
1644  
1645      // Reaching this point means we haven't calculated far enough, do it now:
1646      // Calculate extra DST changes if needed and recurse. The recursion always
1647      // moves toward the stopping condition, so will always end.
1648  
1649      if($from == 0) {
1650          // We need a year smaller than $SESSION->dst_range[0]
1651          if($SESSION->dst_range[0] == 1971) {
1652              return 0;
1653          }
1654          calculate_user_dst_table($SESSION->dst_range[0] - 5, NULL, $strtimezone);
1655          return dst_offset_on($time, $strtimezone);
1656      }
1657      else {
1658          // We need a year larger than $SESSION->dst_range[1]
1659          if($SESSION->dst_range[1] == 2035) {
1660              return 0;
1661          }
1662          calculate_user_dst_table(NULL, $SESSION->dst_range[1] + 5, $strtimezone);
1663          return dst_offset_on($time, $strtimezone);
1664      }
1665  }
1666  
1667  function find_day_in_month($startday, $weekday, $month, $year) {
1668  
1669      $daysinmonth = days_in_month($month, $year);
1670  
1671      if($weekday == -1) {
1672          // Don't care about weekday, so return:
1673          //    abs($startday) if $startday != -1
1674          //    $daysinmonth otherwise
1675          return ($startday == -1) ? $daysinmonth : abs($startday);
1676      }
1677  
1678      // From now on we 're looking for a specific weekday
1679  
1680      // Give "end of month" its actual value, since we know it
1681      if($startday == -1) {
1682          $startday = -1 * $daysinmonth;
1683      }
1684  
1685      // Starting from day $startday, the sign is the direction
1686  
1687      if($startday < 1) {
1688  
1689          $startday = abs($startday);
1690          $lastmonthweekday  = strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1691  
1692          // This is the last such weekday of the month
1693          $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
1694          if($lastinmonth > $daysinmonth) {
1695              $lastinmonth -= 7;
1696          }
1697  
1698          // Find the first such weekday <= $startday
1699          while($lastinmonth > $startday) {
1700              $lastinmonth -= 7;
1701          }
1702  
1703          return $lastinmonth;
1704  
1705      }
1706      else {
1707  
1708          $indexweekday = strftime('%w', mktime(12, 0, 0, $month, $startday, $year, 0));
1709  
1710          $diff = $weekday - $indexweekday;
1711          if($diff < 0) {
1712              $diff += 7;
1713          }
1714  
1715          // This is the first such weekday of the month equal to or after $startday
1716          $firstfromindex = $startday + $diff;
1717  
1718          return $firstfromindex;
1719  
1720      }
1721  }
1722  
1723  /**
1724   * Calculate the number of days in a given month
1725   *
1726   * @param int $month The month whose day count is sought
1727   * @param int $year The year of the month whose day count is sought
1728   * @return int
1729   */
1730  function days_in_month($month, $year) {
1731     return intval(date('t', mktime(12, 0, 0, $month, 1, $year, 0)));
1732  }
1733  
1734  /**
1735   * Calculate the position in the week of a specific calendar day
1736   *
1737   * @param int $day The day of the date whose position in the week is sought
1738   * @param int $month The month of the date whose position in the week is sought
1739   * @param int $year The year of the date whose position in the week is sought
1740   * @return int
1741   */
1742  function dayofweek($day, $month, $year) {
1743      // I wonder if this is any different from
1744      // strftime('%w', mktime(12, 0, 0, $month, $daysinmonth, $year, 0));
1745      return intval(date('w', mktime(12, 0, 0, $month, $day, $year, 0)));
1746  }
1747  
1748  /// USER AUTHENTICATION AND LOGIN ////////////////////////////////////////
1749  
1750  /**
1751   * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
1752   * if one does not already exist, but does not overwrite existing sesskeys. Returns the
1753   * sesskey string if $USER exists, or boolean false if not.
1754   *
1755   * @uses $USER
1756   * @return string
1757   */
1758  function sesskey() {
1759      global $USER;
1760  
1761      if(!isset($USER)) {
1762          return false;
1763      }
1764  
1765      if (empty($USER->sesskey)) {
1766          $USER->sesskey = random_string(10);
1767      }
1768  
1769      return $USER->sesskey;
1770  }
1771  
1772  
1773  /**
1774   * For security purposes, this function will check that the currently
1775   * given sesskey (passed as a parameter to the script or this function)
1776   * matches that of the current user.
1777   *
1778   * @param string $sesskey optionally provided sesskey
1779   * @return bool
1780   */
1781  function confirm_sesskey($sesskey=NULL) {
1782      global $USER;
1783  
1784      if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
1785          return true;
1786      }
1787  
1788      if (empty($sesskey)) {
1789          $sesskey = required_param('sesskey', PARAM_RAW);  // Check script parameters
1790      }
1791  
1792      if (!isset($USER->sesskey)) {
1793          return false;
1794      }
1795  
1796      return ($USER->sesskey === $sesskey);
1797  }
1798  
1799  /**
1800   * Setup all global $CFG course variables, set locale and also themes
1801   * This function can be used on pages that do not require login instead of require_login()
1802   *
1803   * @param mixed $courseorid id of the course or course object
1804   */
1805  function course_setup($courseorid=0) {
1806      global $COURSE, $CFG, $SITE;
1807  
1808  /// Redefine global $COURSE if needed
1809      if (empty($courseorid)) {
1810          // no change in global $COURSE - for backwards compatibiltiy
1811          // if require_rogin() used after require_login($courseid);
1812      } else if (is_object($courseorid)) {
1813          $COURSE = clone($courseorid);
1814      } else {
1815          global $course; // used here only to prevent repeated fetching from DB - may be removed later
1816          if ($courseorid == SITEID) {
1817              $COURSE = clone($SITE);
1818          } else if (!empty($course->id) and $course->id == $courseorid) {
1819              $COURSE = clone($course);
1820          } else {
1821              if (!$COURSE = get_record('course', 'id', $courseorid)) {
1822                  error('Invalid course ID');
1823              }
1824          }
1825      }
1826  
1827  /// set locale and themes
1828      moodle_setlocale();
1829      theme_setup();
1830  
1831  }
1832  
1833  /**
1834   * This function checks that the current user is logged in and has the
1835   * required privileges
1836   *
1837   * This function checks that the current user is logged in, and optionally
1838   * whether they are allowed to be in a particular course and view a particular
1839   * course module.
1840   * If they are not logged in, then it redirects them to the site login unless
1841   * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
1842   * case they are automatically logged in as guests.
1843   * If $courseid is given and the user is not enrolled in that course then the
1844   * user is redirected to the course enrolment page.
1845   * If $cm is given and the coursemodule is hidden and the user is not a teacher
1846   * in the course then the user is redirected to the course home page.
1847   *
1848   * @uses $CFG
1849   * @uses $SESSION
1850   * @uses $USER
1851   * @uses $FULLME
1852   * @uses SITEID
1853   * @uses $COURSE
1854   * @param mixed $courseorid id of the course or course object
1855   * @param bool $autologinguest
1856   * @param object $cm course module object
1857   * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
1858   *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
1859   *             in order to keep redirects working properly. MDL-14495
1860   */
1861  function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) {
1862  
1863      global $CFG, $SESSION, $USER, $COURSE, $FULLME;
1864  
1865  /// setup global $COURSE, themes, language and locale
1866      course_setup($courseorid);
1867  
1868  /// If the user is not even logged in yet then make sure they are
1869      if (!isloggedin()) {
1870          //NOTE: $USER->site check was obsoleted by session test cookie,
1871          //      $USER->confirmed test is in login/index.php
1872          if ($setwantsurltome) {
1873              $SESSION->wantsurl = $FULLME;
1874          }
1875          if (!empty($_SERVER['HTTP_REFERER'])) {
1876              $SESSION->fromurl  = $_SERVER['HTTP_REFERER'];
1877          }
1878          if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests) and ($COURSE->id == SITEID or $COURSE->guest) ) {
1879              $loginguest = '?loginguest=true';
1880          } else {
1881              $loginguest = '';
1882          }
1883          if (empty($CFG->loginhttps) or $loginguest) { //do not require https for guest logins
1884              redirect($CFG->wwwroot .'/login/index.php'. $loginguest);
1885          } else {
1886              $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1887              redirect($wwwroot .'/login/index.php');
1888          }
1889          exit;
1890      }
1891  
1892  /// loginas as redirection if needed
1893      if ($COURSE->id != SITEID and !empty($USER->realuser)) {
1894          if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
1895              if ($USER->loginascontext->instanceid != $COURSE->id) {
1896                  print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
1897              }
1898          }
1899      }
1900  
1901  /// check whether the user should be changing password (but only if it is REALLY them)
1902      if (get_user_preferences('auth_forcepasswordchange') && empty($USER->realuser)) {
1903          $userauth = get_auth_plugin($USER->auth);
1904          if ($userauth->can_change_password()) {
1905              $SESSION->wantsurl = $FULLME;
1906              if ($changeurl = $userauth->change_password_url()) {
1907                  //use plugin custom url
1908                  redirect($changeurl);
1909              } else {
1910                  //use moodle internal method
1911                  if (empty($CFG->loginhttps)) {
1912                      redirect($CFG->wwwroot .'/login/change_password.php');
1913                  } else {
1914                      $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
1915                      redirect($wwwroot .'/login/change_password.php');
1916                  }
1917              }
1918          } else {
1919              print_error('nopasswordchangeforced', 'auth');
1920          }
1921      }
1922  
1923  /// Check that the user account is properly set up
1924      if (user_not_fully_set_up($USER)) {
1925          $SESSION->wantsurl = $FULLME;
1926          redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
1927      }
1928  
1929  /// Make sure current IP matches the one for this session (if required)
1930      if (!empty($CFG->tracksessionip)) {
1931          if ($USER->sessionIP != md5(getremoteaddr())) {
1932              print_error('sessionipnomatch', 'error');
1933          }
1934      }
1935  
1936  /// Make sure the USER has a sesskey set up.  Used for checking script parameters.
1937      sesskey();
1938  
1939      // Check that the user has agreed to a site policy if there is one
1940      if (!empty($CFG->sitepolicy)) {
1941          if (!$USER->policyagreed) {
1942              $SESSION->wantsurl = $FULLME;
1943              redirect($CFG->wwwroot .'/user/policy.php');
1944          }
1945      }
1946  
1947      // Fetch the system context, we are going to use it a lot.
1948      $sysctx = get_context_instance(CONTEXT_SYSTEM);
1949  
1950  /// If the site is currently under maintenance, then print a message
1951      if (!has_capability('moodle/site:config', $sysctx)) {
1952          if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
1953              print_maintenance_message();
1954              exit;
1955          }
1956      }
1957  
1958  /// groupmembersonly access control
1959      if (!empty($CFG->enablegroupings) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
1960          if (isguestuser() or !groups_has_membership($cm)) {
1961              print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
1962          }
1963      }
1964  
1965      // Fetch the course context, and prefetch its child contexts
1966      if (!isset($COURSE->context)) {
1967          if ( ! $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id) ) {
1968              print_error('nocontext');
1969          }
1970      }
1971      if ($COURSE->id == SITEID) {
1972          /// Eliminate hidden site activities straight away
1973          if (!empty($cm) && !$cm->visible
1974              && !has_capability('moodle/course:viewhiddenactivities', $COURSE->context)) {
1975              redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
1976          }
1977          user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
1978          return;
1979  
1980      } else {
1981  
1982          /// Check if the user can be in a particular course
1983          if (empty($USER->access['rsw'][$COURSE->context->path])) {
1984              //
1985              // MDL-13900 - If the course or the parent category are hidden
1986              // and the user hasn't the 'course:viewhiddencourses' capability, prevent access
1987              //
1988              if ( !($COURSE->visible && course_parent_visible($COURSE)) &&
1989                     !has_capability('moodle/course:viewhiddencourses', $COURSE->context)) {
1990                  print_header_simple();
1991                  notice(get_string('coursehidden'), $CFG->wwwroot .'/');
1992              }
1993          }
1994  
1995      /// Non-guests who don't currently have access, check if they can be allowed in as a guest
1996  
1997          if ($USER->username != 'guest' and !has_capability('moodle/course:view', $COURSE->context)) {
1998              if ($COURSE->guest == 1) {
1999                   // Temporarily assign them guest role for this context, if it fails later user is asked to enrol
2000                   $USER->access = load_temp_role($COURSE->context, $CFG->guestroleid, $USER->access);
2001              }
2002          }
2003  
2004      /// If the user is a guest then treat them according to the course policy about guests
2005  
2006          if (has_capability('moodle/legacy:guest', $COURSE->context, NULL, false)) {
2007              if (has_capability('moodle/site:doanything', $sysctx)) {
2008                  // administrators must be able to access any course - even if somebody gives them guest access
2009                  user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2010                  return;
2011              }
2012  
2013              switch ($COURSE->guest) {    /// Check course policy about guest access
2014  
2015                  case 1:    /// Guests always allowed
2016                      if (!has_capability('moodle/course:view', $COURSE->context)) {    // Prohibited by capability
2017                          print_header_simple();
2018                          notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2019                      }
2020                      if (!empty($cm) and !$cm->visible) { // Not allowed to see module, send to course page
2021                          redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course,
2022                                   get_string('activityiscurrentlyhidden'));
2023                      }
2024  
2025                      user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2026                      return;   // User is allowed to see this course
2027  
2028                      break;
2029  
2030                  case 2:    /// Guests allowed with key
2031                      if (!empty($USER->enrolkey[$COURSE->id])) {   // Set by enrol/manual/enrol.php
2032                          user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2033                          return true;
2034                      }
2035                      //  otherwise drop through to logic below (--> enrol.php)
2036                      break;
2037  
2038                  default:    /// Guests not allowed
2039                      $strloggedinasguest = get_string('loggedinasguest');
2040                      print_header_simple('', '',
2041                              build_navigation(array(array('name' => $strloggedinasguest, 'link' => null, 'type' => 'misc'))));
2042                      if (empty($USER->access['rsw'][$COURSE->context->path])) {  // Normal guest
2043                          notice(get_string('guestsnotallowed', '', format_string($COURSE->fullname)), "$CFG->wwwroot/login/index.php");
2044                      } else {
2045                          notify(get_string('guestsnotallowed', '', format_string($COURSE->fullname)));
2046                          echo '<div class="notifyproblem">'.switchroles_form($COURSE->id).'</div>';
2047                          print_footer($COURSE);
2048                          exit;
2049                      }
2050                      break;
2051              }
2052  
2053      /// For non-guests, check if they have course view access
2054  
2055          } else if (has_capability('moodle/course:view', $COURSE->context)) {
2056              if (!empty($USER->realuser)) {   // Make sure the REAL person can also access this course
2057                  if (!has_capability('moodle/course:view', $COURSE->context, $USER->realuser)) {
2058                      print_header_simple();
2059                      notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
2060                  }
2061              }
2062  
2063          /// Make sure they can read this activity too, if specified
2064  
2065              if (!empty($cm) and !$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $COURSE->context)) {
2066                  redirect($CFG->wwwroot.'/course/view.php?id='.$cm->course, get_string('activityiscurrentlyhidden'));
2067              }
2068              user_accesstime_log($COURSE->id); /// Access granted, update lastaccess times
2069              return;   // User is allowed to see this course
2070  
2071          }
2072  
2073  
2074      /// Currently not enrolled in the course, so see if they want to enrol
2075          $SESSION->wantsurl = $FULLME;
2076          redirect($CFG->wwwroot .'/course/enrol.php?id='. $COURSE->id);
2077          die;
2078      }
2079  }
2080  
2081  
2082  
2083  /**
2084   * This function just makes sure a user is logged out.
2085   *
2086   * @uses $CFG
2087   * @uses $USER
2088   */
2089  function require_logout() {
2090  
2091      global $USER, $CFG, $SESSION;
2092  
2093      if (isloggedin()) {
2094          add_to_log(SITEID, "user", "logout", "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
2095  
2096          $authsequence = get_enabled_auth_plugins(); // auths, in sequence
2097          foreach($authsequence as $authname) {
2098              $authplugin = get_auth_plugin($authname);
2099              $authplugin->prelogout_hook();
2100          }
2101      }
2102  
2103      if (ini_get_bool("register_globals") and check_php_version("4.3.0")) {
2104          // This method is just to try to avoid silly warnings from PHP 4.3.0
2105          session_unregister("USER");
2106          session_unregister("SESSION");
2107      }
2108  
2109      // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
2110      $file = $line = null;
2111      if (headers_sent($file, $line)) {
2112          error_log('MoodleSessionTest cookie could not be set in moodlelib.php:'.__LINE__);
2113          error_log('Headers were already sent in file: '.$file.' on line '.$line);
2114      } else {
2115          if (check_php_version('5.2.0')) {
2116              setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure, $CFG->cookiehttponly);
2117          } else {
2118              setcookie('MoodleSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->sessioncookiepath, '', $CFG->cookiesecure);
2119          }
2120      }
2121  
2122      unset($_SESSION['USER']);
2123      unset($_SESSION['SESSION']);
2124  
2125      unset($SESSION);
2126      unset($USER);
2127  
2128  }
2129  
2130  /**
2131   * This is a weaker version of {@link require_login()} which only requires login
2132   * when called from within a course rather than the site page, unless
2133   * the forcelogin option is turned on.
2134   *
2135   * @uses $CFG
2136   * @param mixed $courseorid The course object or id in question
2137   * @param bool $autologinguest Allow autologin guests if that is wanted
2138   * @param object $cm Course activity module if known
2139   * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2140   *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2141   *             in order to keep redirects working properly. MDL-14495
2142   */
2143  function require_course_login($courseorid, $autologinguest=true, $cm=null, $setwantsurltome=true) {
2144      global $CFG;
2145      if (!empty($CFG->forcelogin)) {
2146          // login required for both SITE and courses
2147          require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2148  
2149      } else if (!empty($cm) and !$cm->visible) {
2150          // always login for hidden activities
2151          require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2152  
2153      } else if ((is_object($courseorid) and $courseorid->id == SITEID)
2154            or (!is_object($courseorid) and $courseorid == SITEID)) {
2155          //login for SITE not required
2156          user_accesstime_log(SITEID);
2157          return;
2158  
2159      } else {
2160          // course login always required
2161          require_login($courseorid, $autologinguest, $cm, $setwantsurltome);
2162      }
2163  }
2164  
2165  /**
2166   * Require key login. Function terminates with error if key not found or incorrect.
2167   * @param string $script unique script identifier
2168   * @param int $instance optional instance id
2169   */
2170  function require_user_key_login($script, $instance=null) {
2171      global $nomoodlecookie, $USER, $SESSION, $CFG;
2172  
2173      if (empty($nomoodlecookie)) {
2174          error('Incorrect use of require_key_login() - session cookies must be disabled!');
2175      }
2176  
2177  /// extra safety
2178      @session_write_close();
2179  
2180      $keyvalue = required_param('key', PARAM_ALPHANUM);
2181  
2182      if (!$key = get_record('user_private_key', 'script', $script, 'value', $keyvalue, 'instance', $instance)) {
2183          error('Incorrect key');
2184      }
2185  
2186      if (!empty($key->validuntil) and $key->validuntil < time()) {
2187          error('Expired key');
2188      }
2189  
2190      if ($key->iprestriction) {
2191          $remoteaddr = getremoteaddr();
2192          if ($remoteaddr == '' or !address_in_subnet($remoteaddr, $key->iprestriction)) {
2193              error('Client IP address mismatch');
2194          }
2195      }
2196  
2197      if (!$user = get_record('user', 'id', $key->userid)) {
2198          error('Incorrect user record');
2199      }
2200  
2201  /// emulate normal session
2202      $SESSION = new object();
2203      $USER    = $user;
2204  
2205  /// note we are not using normal login
2206      if (!defined('USER_KEY_LOGIN')) {
2207          define('USER_KEY_LOGIN', true);
2208      }
2209  
2210      load_all_capabilities();
2211  
2212  /// return isntance id - it might be empty
2213      return $key->instance;
2214  }
2215  
2216  /**
2217   * Creates a new private user access key.
2218   * @param string $script unique target identifier
2219   * @param int $userid
2220   * @param instance $int optional instance id
2221   * @param string $iprestriction optional ip restricted access
2222   * @param timestamp $validuntil key valid only until given data
2223   * @return string access key value
2224   */
2225  function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
2226      $key = new object();
2227      $key->script        = $script;
2228      $key->userid        = $userid;
2229      $key->instance      = $instance;
2230      $key->iprestriction = $iprestriction;
2231      $key->validuntil    = $validuntil;
2232      $key->timecreated   = time();
2233  
2234      $key->value         = md5($userid.'_'.time().random_string(40)); // something long and unique
2235      while (record_exists('user_private_key', 'value', $key->value)) {
2236          // must be unique
2237