Search moodle.org's
Developer Documentation


/lib/ -> moodlelib.php (source)
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * moodlelib.php - Moodle main library
  19   *
  20   * Main library file of miscellaneous general-purpose Moodle functions.
  21   * Other main libraries:
  22   *  - weblib.php      - functions that produce web output
  23   *  - datalib.php     - functions that access the database
  24   *
  25   * @package    core
  26   * @subpackage lib
  27   * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
  28   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   */
  30  
  31  defined('MOODLE_INTERNAL') || die();
  32  
  33  // CONSTANTS (Encased in phpdoc proper comments).
  34  
  35  // Date and time constants.
  36  /**
  37   * Time constant - the number of seconds in a year
  38   */
  39  define('YEARSECS', 31536000);
  40  
  41  /**
  42   * Time constant - the number of seconds in a week
  43   */
  44  define('WEEKSECS', 604800);
  45  
  46  /**
  47   * Time constant - the number of seconds in a day
  48   */
  49  define('DAYSECS', 86400);
  50  
  51  /**
  52   * Time constant - the number of seconds in an hour
  53   */
  54  define('HOURSECS', 3600);
  55  
  56  /**
  57   * Time constant - the number of seconds in a minute
  58   */
  59  define('MINSECS', 60);
  60  
  61  /**
  62   * Time constant - the number of minutes in a day
  63   */
  64  define('DAYMINS', 1440);
  65  
  66  /**
  67   * Time constant - the number of minutes in an hour
  68   */
  69  define('HOURMINS', 60);
  70  
  71  // Parameter constants - every call to optional_param(), required_param()
  72  // or clean_param() should have a specified type of parameter.
  73  
  74  /**
  75   * PARAM_ALPHA - contains only english ascii letters a-zA-Z.
  76   */
  77  define('PARAM_ALPHA',    'alpha');
  78  
  79  /**
  80   * PARAM_ALPHAEXT the same contents as PARAM_ALPHA plus the chars in quotes: "_-" allowed
  81   * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
  82   */
  83  define('PARAM_ALPHAEXT', 'alphaext');
  84  
  85  /**
  86   * PARAM_ALPHANUM - expected numbers and letters only.
  87   */
  88  define('PARAM_ALPHANUM', 'alphanum');
  89  
  90  /**
  91   * PARAM_ALPHANUMEXT - expected numbers, letters only and _-.
  92   */
  93  define('PARAM_ALPHANUMEXT', 'alphanumext');
  94  
  95  /**
  96   * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
  97   */
  98  define('PARAM_AUTH',  'auth');
  99  
 100  /**
 101   * PARAM_BASE64 - Base 64 encoded format
 102   */
 103  define('PARAM_BASE64',   'base64');
 104  
 105  /**
 106   * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
 107   */
 108  define('PARAM_BOOL',     'bool');
 109  
 110  /**
 111   * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
 112   * checked against the list of capabilities in the database.
 113   */
 114  define('PARAM_CAPABILITY',   'capability');
 115  
 116  /**
 117   * PARAM_CLEANHTML - cleans submitted HTML code. Note that you almost never want
 118   * to use this. The normal mode of operation is to use PARAM_RAW when recieving
 119   * the input (required/optional_param or formslib) and then sanitse the HTML
 120   * using format_text on output. This is for the rare cases when you want to
 121   * sanitise the HTML on input. This cleaning may also fix xhtml strictness.
 122   */
 123  define('PARAM_CLEANHTML', 'cleanhtml');
 124  
 125  /**
 126   * PARAM_EMAIL - an email address following the RFC
 127   */
 128  define('PARAM_EMAIL',   'email');
 129  
 130  /**
 131   * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
 132   */
 133  define('PARAM_FILE',   'file');
 134  
 135  /**
 136   * PARAM_FLOAT - a real/floating point number.
 137   *
 138   * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
 139   * It does not work for languages that use , as a decimal separator.
 140   * Instead, do something like
 141   *     $rawvalue = required_param('name', PARAM_RAW);
 142   *     // ... other code including require_login, which sets current lang ...
 143   *     $realvalue = unformat_float($rawvalue);
 144   *     // ... then use $realvalue
 145   */
 146  define('PARAM_FLOAT',  'float');
 147  
 148  /**
 149   * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
 150   */
 151  define('PARAM_HOST',     'host');
 152  
 153  /**
 154   * PARAM_INT - integers only, use when expecting only numbers.
 155   */
 156  define('PARAM_INT',      'int');
 157  
 158  /**
 159   * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
 160   */
 161  define('PARAM_LANG',  'lang');
 162  
 163  /**
 164   * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the
 165   * others! Implies PARAM_URL!)
 166   */
 167  define('PARAM_LOCALURL', 'localurl');
 168  
 169  /**
 170   * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
 171   */
 172  define('PARAM_NOTAGS',   'notags');
 173  
 174  /**
 175   * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory
 176   * traversals note: the leading slash is not removed, window drive letter is not allowed
 177   */
 178  define('PARAM_PATH',     'path');
 179  
 180  /**
 181   * PARAM_PEM - Privacy Enhanced Mail format
 182   */
 183  define('PARAM_PEM',      'pem');
 184  
 185  /**
 186   * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
 187   */
 188  define('PARAM_PERMISSION',   'permission');
 189  
 190  /**
 191   * PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
 192   */
 193  define('PARAM_RAW', 'raw');
 194  
 195  /**
 196   * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
 197   */
 198  define('PARAM_RAW_TRIMMED', 'raw_trimmed');
 199  
 200  /**
 201   * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
 202   */
 203  define('PARAM_SAFEDIR',  'safedir');
 204  
 205  /**
 206   * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths, etc.
 207   */
 208  define('PARAM_SAFEPATH',  'safepath');
 209  
 210  /**
 211   * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9.  Numbers and comma only.
 212   */
 213  define('PARAM_SEQUENCE',  'sequence');
 214  
 215  /**
 216   * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
 217   */
 218  define('PARAM_TAG',   'tag');
 219  
 220  /**
 221   * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
 222   */
 223  define('PARAM_TAGLIST',   'taglist');
 224  
 225  /**
 226   * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
 227   */
 228  define('PARAM_TEXT',  'text');
 229  
 230  /**
 231   * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
 232   */
 233  define('PARAM_THEME',  'theme');
 234  
 235  /**
 236   * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but
 237   * http://localhost.localdomain/ is ok.
 238   */
 239  define('PARAM_URL',      'url');
 240  
 241  /**
 242   * PARAM_USERNAME - Clean username to only contains allowed characters. This is to be used ONLY when manually creating user
 243   * accounts, do NOT use when syncing with external systems!!
 244   */
 245  define('PARAM_USERNAME',    'username');
 246  
 247  /**
 248   * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
 249   */
 250  define('PARAM_STRINGID',    'stringid');
 251  
 252  // DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE.
 253  /**
 254   * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
 255   * It was one of the first types, that is why it is abused so much ;-)
 256   * @deprecated since 2.0
 257   */
 258  define('PARAM_CLEAN',    'clean');
 259  
 260  /**
 261   * PARAM_INTEGER - deprecated alias for PARAM_INT
 262   * @deprecated since 2.0
 263   */
 264  define('PARAM_INTEGER',  'int');
 265  
 266  /**
 267   * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
 268   * @deprecated since 2.0
 269   */
 270  define('PARAM_NUMBER',  'float');
 271  
 272  /**
 273   * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
 274   * NOTE: originally alias for PARAM_APLHA
 275   * @deprecated since 2.0
 276   */
 277  define('PARAM_ACTION',   'alphanumext');
 278  
 279  /**
 280   * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
 281   * NOTE: originally alias for PARAM_APLHA
 282   * @deprecated since 2.0
 283   */
 284  define('PARAM_FORMAT',   'alphanumext');
 285  
 286  /**
 287   * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
 288   * @deprecated since 2.0
 289   */
 290  define('PARAM_MULTILANG',  'text');
 291  
 292  /**
 293   * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
 294   * string separated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
 295   * America/Port-au-Prince)
 296   */
 297  define('PARAM_TIMEZONE', 'timezone');
 298  
 299  /**
 300   * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
 301   */
 302  define('PARAM_CLEANFILE', 'file');
 303  
 304  /**
 305   * PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
 306   * Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
 307   * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
 308   * NOTE: numbers and underscores are strongly discouraged in plugin names!
 309   */
 310  define('PARAM_COMPONENT', 'component');
 311  
 312  /**
 313   * PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
 314   * It is usually used together with context id and component.
 315   * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
 316   */
 317  define('PARAM_AREA', 'area');
 318  
 319  /**
 320   * PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'radius', 'paypal', 'completionstatus'.
 321   * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
 322   * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
 323   */
 324  define('PARAM_PLUGIN', 'plugin');
 325  
 326  
 327  // Web Services.
 328  
 329  /**
 330   * VALUE_REQUIRED - if the parameter is not supplied, there is an error
 331   */
 332  define('VALUE_REQUIRED', 1);
 333  
 334  /**
 335   * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
 336   */
 337  define('VALUE_OPTIONAL', 2);
 338  
 339  /**
 340   * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
 341   */
 342  define('VALUE_DEFAULT', 0);
 343  
 344  /**
 345   * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
 346   */
 347  define('NULL_NOT_ALLOWED', false);
 348  
 349  /**
 350   * NULL_ALLOWED - the parameter can be set to null in the database
 351   */
 352  define('NULL_ALLOWED', true);
 353  
 354  // Page types.
 355  
 356  /**
 357   * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
 358   */
 359  define('PAGE_COURSE_VIEW', 'course-view');
 360  
 361  /** Get remote addr constant */
 362  define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
 363  /** Get remote addr constant */
 364  define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
 365  
 366  // Blog access level constant declaration.
 367  define ('BLOG_USER_LEVEL', 1);
 368  define ('BLOG_GROUP_LEVEL', 2);
 369  define ('BLOG_COURSE_LEVEL', 3);
 370  define ('BLOG_SITE_LEVEL', 4);
 371  define ('BLOG_GLOBAL_LEVEL', 5);
 372  
 373  
 374  // Tag constants.
 375  /**
 376   * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
 377   * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
 378   * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
 379   *
 380   * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
 381   */
 382  define('TAG_MAX_LENGTH', 50);
 383  
 384  // Password policy constants.
 385  define ('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
 386  define ('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
 387  define ('PASSWORD_DIGITS', '0123456789');
 388  define ('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
 389  
 390  // Feature constants.
 391  // Used for plugin_supports() to report features that are, or are not, supported by a module.
 392  
 393  /** True if module can provide a grade */
 394  define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
 395  /** True if module supports outcomes */
 396  define('FEATURE_GRADE_OUTCOMES', 'outcomes');
 397  /** True if module supports advanced grading methods */
 398  define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
 399  /** True if module controls the grade visibility over the gradebook */
 400  define('FEATURE_CONTROLS_GRADE_VISIBILITY', 'controlsgradevisbility');
 401  /** True if module supports plagiarism plugins */
 402  define('FEATURE_PLAGIARISM', 'plagiarism');
 403  
 404  /** True if module has code to track whether somebody viewed it */
 405  define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
 406  /** True if module has custom completion rules */
 407  define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
 408  
 409  /** True if module has no 'view' page (like label) */
 410  define('FEATURE_NO_VIEW_LINK', 'viewlink');
 411  /** True (which is default) if the module wants support for setting the ID number for grade calculation purposes. */
 412  define('FEATURE_IDNUMBER', 'idnumber');
 413  /** True if module supports groups */
 414  define('FEATURE_GROUPS', 'groups');
 415  /** True if module supports groupings */
 416  define('FEATURE_GROUPINGS', 'groupings');
 417  /**
 418   * True if module supports groupmembersonly (which no longer exists)
 419   * @deprecated Since Moodle 2.8
 420   */
 421  define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
 422  
 423  /** Type of module */
 424  define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
 425  /** True if module supports intro editor */
 426  define('FEATURE_MOD_INTRO', 'mod_intro');
 427  /** True if module has default completion */
 428  define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
 429  
 430  define('FEATURE_COMMENT', 'comment');
 431  
 432  define('FEATURE_RATE', 'rate');
 433  /** True if module supports backup/restore of moodle2 format */
 434  define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
 435  
 436  /** True if module can show description on course main page */
 437  define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
 438  
 439  /** True if module uses the question bank */
 440  define('FEATURE_USES_QUESTIONS', 'usesquestions');
 441  
 442  /** Unspecified module archetype */
 443  define('MOD_ARCHETYPE_OTHER', 0);
 444  /** Resource-like type module */
 445  define('MOD_ARCHETYPE_RESOURCE', 1);
 446  /** Assignment module archetype */
 447  define('MOD_ARCHETYPE_ASSIGNMENT', 2);
 448  /** System (not user-addable) module archetype */
 449  define('MOD_ARCHETYPE_SYSTEM', 3);
 450  
 451  /** Return this from modname_get_types callback to use default display in activity chooser */
 452  define('MOD_SUBTYPE_NO_CHILDREN', 'modsubtypenochildren');
 453  
 454  /**
 455   * Security token used for allowing access
 456   * from external application such as web services.
 457   * Scripts do not use any session, performance is relatively
 458   * low because we need to load access info in each request.
 459   * Scripts are executed in parallel.
 460   */
 461  define('EXTERNAL_TOKEN_PERMANENT', 0);
 462  
 463  /**
 464   * Security token used for allowing access
 465   * of embedded applications, the code is executed in the
 466   * active user session. Token is invalidated after user logs out.
 467   * Scripts are executed serially - normal session locking is used.
 468   */
 469  define('EXTERNAL_TOKEN_EMBEDDED', 1);
 470  
 471  /**
 472   * The home page should be the site home
 473   */
 474  define('HOMEPAGE_SITE', 0);
 475  /**
 476   * The home page should be the users my page
 477   */
 478  define('HOMEPAGE_MY', 1);
 479  /**
 480   * The home page can be chosen by the user
 481   */
 482  define('HOMEPAGE_USER', 2);
 483  
 484  /**
 485   * Hub directory url (should be moodle.org)
 486   */
 487  define('HUB_HUBDIRECTORYURL', "http://hubdirectory.moodle.org");
 488  
 489  
 490  /**
 491   * Moodle.org url (should be moodle.org)
 492   */
 493  define('HUB_MOODLEORGHUBURL', "http://hub.moodle.org");
 494  
 495  /**
 496   * Moodle mobile app service name
 497   */
 498  define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
 499  
 500  /**
 501   * Indicates the user has the capabilities required to ignore activity and course file size restrictions
 502   */
 503  define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1);
 504  
 505  /**
 506   * Course display settings: display all sections on one page.
 507   */
 508  define('COURSE_DISPLAY_SINGLEPAGE', 0);
 509  /**
 510   * Course display settings: split pages into a page per section.
 511   */
 512  define('COURSE_DISPLAY_MULTIPAGE', 1);
 513  
 514  /**
 515   * Authentication constant: String used in password field when password is not stored.
 516   */
 517  define('AUTH_PASSWORD_NOT_CACHED', 'not cached');
 518  
 519  // PARAMETER HANDLING.
 520  
 521  /**
 522   * Returns a particular value for the named variable, taken from
 523   * POST or GET.  If the parameter doesn't exist then an error is
 524   * thrown because we require this variable.
 525   *
 526   * This function should be used to initialise all required values
 527   * in a script that are based on parameters.  Usually it will be
 528   * used like this:
 529   *    $id = required_param('id', PARAM_INT);
 530   *
 531   * Please note the $type parameter is now required and the value can not be array.
 532   *
 533   * @param string $parname the name of the page parameter we want
 534   * @param string $type expected type of parameter
 535   * @return mixed
 536   * @throws coding_exception
 537   */
 538  function required_param($parname, $type) {
 539      if (func_num_args() != 2 or empty($parname) or empty($type)) {
 540          throw new coding_exception('required_param() requires $parname and $type to be specified (parameter: '.$parname.')');
 541      }
 542      // POST has precedence.
 543      if (isset($_POST[$parname])) {
 544          $param = $_POST[$parname];
 545      } else if (isset($_GET[$parname])) {
 546          $param = $_GET[$parname];
 547      } else {
 548          print_error('missingparam', '', '', $parname);
 549      }
 550  
 551      if (is_array($param)) {
 552          debugging('Invalid array parameter detected in required_param(): '.$parname);
 553          // TODO: switch to fatal error in Moodle 2.3.
 554          return required_param_array($parname, $type);
 555      }
 556  
 557      return clean_param($param, $type);
 558  }
 559  
 560  /**
 561   * Returns a particular array value for the named variable, taken from
 562   * POST or GET.  If the parameter doesn't exist then an error is
 563   * thrown because we require this variable.
 564   *
 565   * This function should be used to initialise all required values
 566   * in a script that are based on parameters.  Usually it will be
 567   * used like this:
 568   *    $ids = required_param_array('ids', PARAM_INT);
 569   *
 570   *  Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
 571   *
 572   * @param string $parname the name of the page parameter we want
 573   * @param string $type expected type of parameter
 574   * @return array
 575   * @throws coding_exception
 576   */
 577  function required_param_array($parname, $type) {
 578      if (func_num_args() != 2 or empty($parname) or empty($type)) {
 579          throw new coding_exception('required_param_array() requires $parname and $type to be specified (parameter: '.$parname.')');
 580      }
 581      // POST has precedence.
 582      if (isset($_POST[$parname])) {
 583          $param = $_POST[$parname];
 584      } else if (isset($_GET[$parname])) {
 585          $param = $_GET[$parname];
 586      } else {
 587          print_error('missingparam', '', '', $parname);
 588      }
 589      if (!is_array($param)) {
 590          print_error('missingparam', '', '', $parname);
 591      }
 592  
 593      $result = array();
 594      foreach ($param as $key => $value) {
 595          if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
 596              debugging('Invalid key name in required_param_array() detected: '.$key.', parameter: '.$parname);
 597              continue;
 598          }
 599          $result[$key] = clean_param($value, $type);
 600      }
 601  
 602      return $result;
 603  }
 604  
 605  /**
 606   * Returns a particular value for the named variable, taken from
 607   * POST or GET, otherwise returning a given default.
 608   *
 609   * This function should be used to initialise all optional values
 610   * in a script that are based on parameters.  Usually it will be
 611   * used like this:
 612   *    $name = optional_param('name', 'Fred', PARAM_TEXT);
 613   *
 614   * Please note the $type parameter is now required and the value can not be array.
 615   *
 616   * @param string $parname the name of the page parameter we want
 617   * @param mixed  $default the default value to return if nothing is found
 618   * @param string $type expected type of parameter
 619   * @return mixed
 620   * @throws coding_exception
 621   */
 622  function optional_param($parname, $default, $type) {
 623      if (func_num_args() != 3 or empty($parname) or empty($type)) {
 624          throw new coding_exception('optional_param requires $parname, $default + $type to be specified (parameter: '.$parname.')');
 625      }
 626      if (!isset($default)) {
 627          $default = null;
 628      }
 629  
 630      // POST has precedence.
 631      if (isset($_POST[$parname])) {
 632          $param = $_POST[$parname];
 633      } else if (isset($_GET[$parname])) {
 634          $param = $_GET[$parname];
 635      } else {
 636          return $default;
 637      }
 638  
 639      if (is_array($param)) {
 640          debugging('Invalid array parameter detected in required_param(): '.$parname);
 641          // TODO: switch to $default in Moodle 2.3.
 642          return optional_param_array($parname, $default, $type);
 643      }
 644  
 645      return clean_param($param, $type);
 646  }
 647  
 648  /**
 649   * Returns a particular array value for the named variable, taken from
 650   * POST or GET, otherwise returning a given default.
 651   *
 652   * This function should be used to initialise all optional values
 653   * in a script that are based on parameters.  Usually it will be
 654   * used like this:
 655   *    $ids = optional_param('id', array(), PARAM_INT);
 656   *
 657   * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
 658   *
 659   * @param string $parname the name of the page parameter we want
 660   * @param mixed $default the default value to return if nothing is found
 661   * @param string $type expected type of parameter
 662   * @return array
 663   * @throws coding_exception
 664   */
 665  function optional_param_array($parname, $default, $type) {
 666      if (func_num_args() != 3 or empty($parname) or empty($type)) {
 667          throw new coding_exception('optional_param_array requires $parname, $default + $type to be specified (parameter: '.$parname.')');
 668      }
 669  
 670      // POST has precedence.
 671      if (isset($_POST[$parname])) {
 672          $param = $_POST[$parname];
 673      } else if (isset($_GET[$parname])) {
 674          $param = $_GET[$parname];
 675      } else {
 676          return $default;
 677      }
 678      if (!is_array($param)) {
 679          debugging('optional_param_array() expects array parameters only: '.$parname);
 680          return $default;
 681      }
 682  
 683      $result = array();
 684      foreach ($param as $key => $value) {
 685          if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
 686              debugging('Invalid key name in optional_param_array() detected: '.$key.', parameter: '.$parname);
 687              continue;
 688          }
 689          $result[$key] = clean_param($value, $type);
 690      }
 691  
 692      return $result;
 693  }
 694  
 695  /**
 696   * Strict validation of parameter values, the values are only converted
 697   * to requested PHP type. Internally it is using clean_param, the values
 698   * before and after cleaning must be equal - otherwise
 699   * an invalid_parameter_exception is thrown.
 700   * Objects and classes are not accepted.
 701   *
 702   * @param mixed $param
 703   * @param string $type PARAM_ constant
 704   * @param bool $allownull are nulls valid value?
 705   * @param string $debuginfo optional debug information
 706   * @return mixed the $param value converted to PHP type
 707   * @throws invalid_parameter_exception if $param is not of given type
 708   */
 709  function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
 710      if (is_null($param)) {
 711          if ($allownull == NULL_ALLOWED) {
 712              return null;
 713          } else {
 714              throw new invalid_parameter_exception($debuginfo);
 715          }
 716      }
 717      if (is_array($param) or is_object($param)) {
 718          throw new invalid_parameter_exception($debuginfo);
 719      }
 720  
 721      $cleaned = clean_param($param, $type);
 722  
 723      if ($type == PARAM_FLOAT) {
 724          // Do not detect precision loss here.
 725          if (is_float($param) or is_int($param)) {
 726              // These always fit.
 727          } else if (!is_numeric($param) or !preg_match('/^[\+-]?[0-9]*\.?[0-9]*(e[-+]?[0-9]+)?$/i', (string)$param)) {
 728              throw new invalid_parameter_exception($debuginfo);
 729          }
 730      } else if ((string)$param !== (string)$cleaned) {
 731          // Conversion to string is usually lossless.
 732          throw new invalid_parameter_exception($debuginfo);
 733      }
 734  
 735      return $cleaned;
 736  }
 737  
 738  /**
 739   * Makes sure array contains only the allowed types, this function does not validate array key names!
 740   *
 741   * <code>
 742   * $options = clean_param($options, PARAM_INT);
 743   * </code>
 744   *
 745   * @param array $param the variable array we are cleaning
 746   * @param string $type expected format of param after cleaning.
 747   * @param bool $recursive clean recursive arrays
 748   * @return array
 749   * @throws coding_exception
 750   */
 751  function clean_param_array(array $param = null, $type, $recursive = false) {
 752      // Convert null to empty array.
 753      $param = (array)$param;
 754      foreach ($param as $key => $value) {
 755          if (is_array($value)) {
 756              if ($recursive) {
 757                  $param[$key] = clean_param_array($value, $type, true);
 758              } else {
 759                  throw new coding_exception('clean_param_array can not process multidimensional arrays when $recursive is false.');
 760              }
 761          } else {
 762              $param[$key] = clean_param($value, $type);
 763          }
 764      }
 765      return $param;
 766  }
 767  
 768  /**
 769   * Used by {@link optional_param()} and {@link required_param()} to
 770   * clean the variables and/or cast to specific types, based on
 771   * an options field.
 772   * <code>
 773   * $course->format = clean_param($course->format, PARAM_ALPHA);
 774   * $selectedgradeitem = clean_param($selectedgradeitem, PARAM_INT);
 775   * </code>
 776   *
 777   * @param mixed $param the variable we are cleaning
 778   * @param string $type expected format of param after cleaning.
 779   * @return mixed
 780   * @throws coding_exception
 781   */
 782  function clean_param($param, $type) {
 783      global $CFG;
 784  
 785      if (is_array($param)) {
 786          throw new coding_exception('clean_param() can not process arrays, please use clean_param_array() instead.');
 787      } else if (is_object($param)) {
 788          if (method_exists($param, '__toString')) {
 789              $param = $param->__toString();
 790          } else {
 791              throw new coding_exception('clean_param() can not process objects, please use clean_param_array() instead.');
 792          }
 793      }
 794  
 795      switch ($type) {
 796          case PARAM_RAW:
 797              // No cleaning at all.
 798              $param = fix_utf8($param);
 799              return $param;
 800  
 801          case PARAM_RAW_TRIMMED:
 802              // No cleaning, but strip leading and trailing whitespace.
 803              $param = fix_utf8($param);
 804              return trim($param);
 805  
 806          case PARAM_CLEAN:
 807              // General HTML cleaning, try to use more specific type if possible this is deprecated!
 808              // Please use more specific type instead.
 809              if (is_numeric($param)) {
 810                  return $param;
 811              }
 812              $param = fix_utf8($param);
 813              // Sweep for scripts, etc.
 814              return clean_text($param);
 815  
 816          case PARAM_CLEANHTML:
 817              // Clean html fragment.
 818              $param = fix_utf8($param);
 819              // Sweep for scripts, etc.
 820              $param = clean_text($param, FORMAT_HTML);
 821              return trim($param);
 822  
 823          case PARAM_INT:
 824              // Convert to integer.
 825              return (int)$param;
 826  
 827          case PARAM_FLOAT:
 828              // Convert to float.
 829              return (float)$param;
 830  
 831          case PARAM_ALPHA:
 832              // Remove everything not `a-z`.
 833              return preg_replace('/[^a-zA-Z]/i', '', $param);
 834  
 835          case PARAM_ALPHAEXT:
 836              // Remove everything not `a-zA-Z_-` (originally allowed "/" too).
 837              return preg_replace('/[^a-zA-Z_-]/i', '', $param);
 838  
 839          case PARAM_ALPHANUM:
 840              // Remove everything not `a-zA-Z0-9`.
 841              return preg_replace('/[^A-Za-z0-9]/i', '', $param);
 842  
 843          case PARAM_ALPHANUMEXT:
 844              // Remove everything not `a-zA-Z0-9_-`.
 845              return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
 846  
 847          case PARAM_SEQUENCE:
 848              // Remove everything not `0-9,`.
 849              return preg_replace('/[^0-9,]/i', '', $param);
 850  
 851          case PARAM_BOOL:
 852              // Convert to 1 or 0.
 853              $tempstr = strtolower($param);
 854              if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
 855                  $param = 1;
 856              } else if ($tempstr === 'off' or $tempstr === 'no'  or $tempstr === 'false') {
 857                  $param = 0;
 858              } else {
 859                  $param = empty($param) ? 0 : 1;
 860              }
 861              return $param;
 862  
 863          case PARAM_NOTAGS:
 864              // Strip all tags.
 865              $param = fix_utf8($param);
 866              return strip_tags($param);
 867  
 868          case PARAM_TEXT:
 869              // Leave only tags needed for multilang.
 870              $param = fix_utf8($param);
 871              // If the multilang syntax is not correct we strip all tags because it would break xhtml strict which is required
 872              // for accessibility standards please note this cleaning does not strip unbalanced '>' for BC compatibility reasons.
 873              do {
 874                  if (strpos($param, '</lang>') !== false) {
 875                      // Old and future mutilang syntax.
 876                      $param = strip_tags($param, '<lang>');
 877                      if (!preg_match_all('/<.*>/suU', $param, $matches)) {
 878                          break;
 879                      }
 880                      $open = false;
 881                      foreach ($matches[0] as $match) {
 882                          if ($match === '</lang>') {
 883                              if ($open) {
 884                                  $open = false;
 885                                  continue;
 886                              } else {
 887                                  break 2;
 888                              }
 889                          }
 890                          if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
 891                              break 2;
 892                          } else {
 893                              $open = true;
 894                          }
 895                      }
 896                      if ($open) {
 897                          break;
 898                      }
 899                      return $param;
 900  
 901                  } else if (strpos($param, '</span>') !== false) {
 902                      // Current problematic multilang syntax.
 903                      $param = strip_tags($param, '<span>');
 904                      if (!preg_match_all('/<.*>/suU', $param, $matches)) {
 905                          break;
 906                      }
 907                      $open = false;
 908                      foreach ($matches[0] as $match) {
 909                          if ($match === '</span>') {
 910                              if ($open) {
 911                                  $open = false;
 912                                  continue;
 913                              } else {
 914                                  break 2;
 915                              }
 916                          }
 917                          if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
 918                              break 2;
 919                          } else {
 920                              $open = true;
 921                          }
 922                      }
 923                      if ($open) {
 924                          break;
 925                      }
 926                      return $param;
 927                  }
 928              } while (false);
 929              // Easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string().
 930              return strip_tags($param);
 931  
 932          case PARAM_COMPONENT:
 933              // We do not want any guessing here, either the name is correct or not
 934              // please note only normalised component names are accepted.
 935              if (!preg_match('/^[a-z]+(_[a-z][a-z0-9_]*)?[a-z0-9]+$/', $param)) {
 936                  return '';
 937              }
 938              if (strpos($param, '__') !== false) {
 939                  return '';
 940              }
 941              if (strpos($param, 'mod_') === 0) {
 942                  // Module names must not contain underscores because we need to differentiate them from invalid plugin types.
 943                  if (substr_count($param, '_') != 1) {
 944                      return '';
 945                  }
 946              }
 947              return $param;
 948  
 949          case PARAM_PLUGIN:
 950          case PARAM_AREA:
 951              // We do not want any guessing here, either the name is correct or not.
 952              if (!is_valid_plugin_name($param)) {
 953                  return '';
 954              }
 955              return $param;
 956  
 957          case PARAM_SAFEDIR:
 958              // Remove everything not a-zA-Z0-9_- .
 959              return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
 960  
 961          case PARAM_SAFEPATH:
 962              // Remove everything not a-zA-Z0-9/_- .
 963              return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
 964  
 965          case PARAM_FILE:
 966              // Strip all suspicious characters from filename.
 967              $param = fix_utf8($param);
 968              $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
 969              if ($param === '.' || $param === '..') {
 970                  $param = '';
 971              }
 972              return $param;
 973  
 974          case PARAM_PATH:
 975              // Strip all suspicious characters from file path.
 976              $param = fix_utf8($param);
 977              $param = str_replace('\\', '/', $param);
 978  
 979              // Explode the path and clean each element using the PARAM_FILE rules.
 980              $breadcrumb = explode('/', $param);
 981              foreach ($breadcrumb as $key => $crumb) {
 982                  if ($crumb === '.' && $key === 0) {
 983                      // Special condition to allow for relative current path such as ./currentdirfile.txt.
 984                  } else {
 985                      $crumb = clean_param($crumb, PARAM_FILE);
 986                  }
 987                  $breadcrumb[$key] = $crumb;
 988              }
 989              $param = implode('/', $breadcrumb);
 990  
 991              // Remove multiple current path (./././) and multiple slashes (///).
 992              $param = preg_replace('~//+~', '/', $param);
 993              $param = preg_replace('~/(\./)+~', '/', $param);
 994              return $param;
 995  
 996          case PARAM_HOST:
 997              // Allow FQDN or IPv4 dotted quad.
 998              $param = preg_replace('/[^\.\d\w-]/', '', $param );
 999              // Match ipv4 dotted quad.
1000              if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/', $param, $match)) {
1001                  // Confirm values are ok.
1002                  if ( $match[0] > 255
1003                       || $match[1] > 255
1004                       || $match[3] > 255
1005                       || $match[4] > 255 ) {
1006                      // Hmmm, what kind of dotted quad is this?
1007                      $param = '';
1008                  }
1009              } else if ( preg_match('/^[\w\d\.-]+$/', $param) // Dots, hyphens, numbers.
1010                         && !preg_match('/^[\.-]/',  $param) // No leading dots/hyphens.
1011                         && !preg_match('/[\.-]$/',  $param) // No trailing dots/hyphens.
1012                         ) {
1013                  // All is ok - $param is respected.
1014              } else {
1015                  // All is not ok...
1016                  $param='';
1017              }
1018              return $param;
1019  
1020          case PARAM_URL:          // Allow safe ftp, http, mailto urls.
1021              $param = fix_utf8($param);
1022              include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
1023              if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
1024                  // All is ok, param is respected.
1025              } else {
1026                  // Not really ok.
1027                  $param ='';
1028              }
1029              return $param;
1030  
1031          case PARAM_LOCALURL:
1032              // Allow http absolute, root relative and relative URLs within wwwroot.
1033              $param = clean_param($param, PARAM_URL);
1034              if (!empty($param)) {
1035  
1036                  // Simulate the HTTPS version of the site.
1037                  $httpswwwroot = str_replace('http://', 'https://', $CFG->wwwroot);
1038  
1039                  if ($param === $CFG->wwwroot) {
1040                      // Exact match;
1041                  } else if (!empty($CFG->loginhttps) && $param === $httpswwwroot) {
1042                      // Exact match;
1043                  } else if (preg_match(':^/:', $param)) {
1044                      // Root-relative, ok!
1045                  } else if (preg_match('/^' . preg_quote($CFG->wwwroot . '/', '/') . '/i', $param)) {
1046                      // Absolute, and matches our wwwroot.
1047                  } else if (!empty($CFG->loginhttps) && preg_match('/^' . preg_quote($httpswwwroot . '/', '/') . '/i', $param)) {
1048                      // Absolute, and matches our httpswwwroot.
1049                  } else {
1050                      // Relative - let's make sure there are no tricks.
1051                      if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
1052                          // Looks ok.
1053                      } else {
1054                          $param = '';
1055                      }
1056                  }
1057              }
1058              return $param;
1059  
1060          case PARAM_PEM:
1061              $param = trim($param);
1062              // PEM formatted strings may contain letters/numbers and the symbols:
1063              //   forward slash: /
1064              //   plus sign:     +
1065              //   equal sign:    =
1066              //   , surrounded by BEGIN and END CERTIFICATE prefix and suffixes.
1067              if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
1068                  list($wholething, $body) = $matches;
1069                  unset($wholething, $matches);
1070                  $b64 = clean_param($body, PARAM_BASE64);
1071                  if (!empty($b64)) {
1072                      return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
1073                  } else {
1074                      return '';
1075                  }
1076              }
1077              return '';
1078  
1079          case PARAM_BASE64:
1080              if (!empty($param)) {
1081                  // PEM formatted strings may contain letters/numbers and the symbols
1082                  //   forward slash: /
1083                  //   plus sign:     +
1084                  //   equal sign:    =.
1085                  if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
1086                      return '';
1087                  }
1088                  $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
1089                  // Each line of base64 encoded data must be 64 characters in length, except for the last line which may be less
1090                  // than (or equal to) 64 characters long.
1091                  for ($i=0, $j=count($lines); $i < $j; $i++) {
1092                      if ($i + 1 == $j) {
1093                          if (64 < strlen($lines[$i])) {
1094                              return '';
1095                          }
1096                          continue;
1097                      }
1098  
1099                      if (64 != strlen($lines[$i])) {
1100                          return '';
1101                      }
1102                  }
1103                  return implode("\n", $lines);
1104              } else {
1105                  return '';
1106              }
1107  
1108          case PARAM_TAG:
1109              $param = fix_utf8($param);
1110              // Please note it is not safe to use the tag name directly anywhere,
1111              // it must be processed with s(), urlencode() before embedding anywhere.
1112              // Remove some nasties.
1113              $param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
1114              // Convert many whitespace chars into one.
1115              $param = preg_replace('/\s+/u', ' ', $param);
1116              $param = core_text::substr(trim($param), 0, TAG_MAX_LENGTH);
1117              return $param;
1118  
1119          case PARAM_TAGLIST:
1120              $param = fix_utf8($param);
1121              $tags = explode(',', $param);
1122              $result = array();
1123              foreach ($tags as $tag) {
1124                  $res = clean_param($tag, PARAM_TAG);
1125                  if ($res !== '') {
1126                      $result[] = $res;
1127                  }
1128              }
1129              if ($result) {
1130                  return implode(',', $result);
1131              } else {
1132                  return '';
1133              }
1134  
1135          case PARAM_CAPABILITY:
1136              if (get_capability_info($param)) {
1137                  return $param;
1138              } else {
1139                  return '';
1140              }
1141  
1142          case PARAM_PERMISSION:
1143              $param = (int)$param;
1144              if (in_array($param, array(CAP_INHERIT, CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT))) {
1145                  return $param;
1146              } else {
1147                  return CAP_INHERIT;
1148              }
1149  
1150          case PARAM_AUTH:
1151              $param = clean_param($param, PARAM_PLUGIN);
1152              if (empty($param)) {
1153                  return '';
1154              } else if (exists_auth_plugin($param)) {
1155                  return $param;
1156              } else {
1157                  return '';
1158              }
1159  
1160          case PARAM_LANG:
1161              $param = clean_param($param, PARAM_SAFEDIR);
1162              if (get_string_manager()->translation_exists($param)) {
1163                  return $param;
1164              } else {
1165                  // Specified language is not installed or param malformed.
1166                  return '';
1167              }
1168  
1169          case PARAM_THEME:
1170              $param = clean_param($param, PARAM_PLUGIN);
1171              if (empty($param)) {
1172                  return '';
1173              } else if (file_exists("$CFG->dirroot/theme/$param/config.php")) {
1174                  return $param;
1175              } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$param/config.php")) {
1176                  return $param;
1177              } else {
1178                  // Specified theme is not installed.
1179                  return '';
1180              }
1181  
1182          case PARAM_USERNAME:
1183              $param = fix_utf8($param);
1184              $param = trim($param);
1185              // Convert uppercase to lowercase MDL-16919.
1186              $param = core_text::strtolower($param);
1187              if (empty($CFG->extendedusernamechars)) {
1188                  $param = str_replace(" " , "", $param);
1189                  // Regular expression, eliminate all chars EXCEPT:
1190                  // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
1191                  $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
1192              }
1193              return $param;
1194  
1195          case PARAM_EMAIL:
1196              $param = fix_utf8($param);
1197              if (validate_email($param)) {
1198                  return $param;
1199              } else {
1200                  return '';
1201              }
1202  
1203          case PARAM_STRINGID:
1204              if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
1205                  return $param;
1206              } else {
1207                  return '';
1208              }
1209  
1210          case PARAM_TIMEZONE:
1211              // Can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'.
1212              $param = fix_utf8($param);
1213              $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3](\.0)?|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
1214              if (preg_match($timezonepattern, $param)) {
1215                  return $param;
1216              } else {
1217                  return '';
1218              }
1219  
1220          default:
1221              // Doh! throw error, switched parameters in optional_param or another serious problem.
1222              print_error("unknownparamtype", '', '', $type);
1223      }
1224  }
1225  
1226  /**
1227   * Makes sure the data is using valid utf8, invalid characters are discarded.
1228   *
1229   * Note: this function is not intended for full objects with methods and private properties.
1230   *
1231   * @param mixed $value
1232   * @return mixed with proper utf-8 encoding
1233   */
1234  function fix_utf8($value) {
1235      if (is_null($value) or $value === '') {
1236          return $value;
1237  
1238      } else if (is_string($value)) {
1239          if ((string)(int)$value === $value) {
1240              // Shortcut.
1241              return $value;
1242          }
1243          // No null bytes expected in our data, so let's remove it.
1244          $value = str_replace("\0", '', $value);
1245  
1246          // Note: this duplicates min_fix_utf8() intentionally.
1247          static $buggyiconv = null;
1248          if ($buggyiconv === null) {
1249              $buggyiconv = (!function_exists('iconv') or @iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
1250          }
1251  
1252          if ($buggyiconv) {
1253              if (function_exists('mb_convert_encoding')) {
1254                  $subst = mb_substitute_character();
1255                  mb_substitute_character('');
1256                  $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
1257                  mb_substitute_character($subst);
1258  
1259              } else {
1260                  // Warn admins on admin/index.php page.
1261                  $result = $value;
1262              }
1263  
1264          } else {
1265              $result = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
1266          }
1267  
1268          return $result;
1269  
1270      } else if (is_array($value)) {
1271          foreach ($value as $k => $v) {
1272              $value[$k] = fix_utf8($v);
1273          }
1274          return $value;
1275  
1276      } else if (is_object($value)) {
1277          // Do not modify original.
1278          $value = clone($value);
1279          foreach ($value as $k => $v) {
1280              $value->$k = fix_utf8($v);
1281          }
1282          return $value;
1283  
1284      } else {
1285          // This is some other type, no utf-8 here.
1286          return $value;
1287      }
1288  }
1289  
1290  /**
1291   * Return true if given value is integer or string with integer value
1292   *
1293   * @param mixed $value String or Int
1294   * @return bool true if number, false if not
1295   */
1296  function is_number($value) {
1297      if (is_int($value)) {
1298          return true;
1299      } else if (is_string($value)) {
1300          return ((string)(int)$value) === $value;
1301      } else {
1302          return false;
1303      }
1304  }
1305  
1306  /**
1307   * Returns host part from url.
1308   *
1309   * @param string $url full url
1310   * @return string host, null if not found
1311   */
1312  function get_host_from_url($url) {
1313      preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
1314      if ($matches) {
1315          return $matches[1];
1316      }
1317      return null;
1318  }
1319  
1320  /**
1321   * Tests whether anything was returned by text editor
1322   *
1323   * This function is useful for testing whether something you got back from
1324   * the HTML editor actually contains anything. Sometimes the HTML editor
1325   * appear to be empty, but actually you get back a <br> tag or something.
1326   *
1327   * @param string $string a string containing HTML.
1328   * @return boolean does the string contain any actual content - that is text,
1329   * images, objects, etc.
1330   */
1331  function html_is_blank($string) {
1332      return trim(strip_tags($string, '<img><object><applet><input><select><textarea><hr>')) == '';
1333  }
1334  
1335  /**
1336   * Set a key in global configuration
1337   *
1338   * Set a key/value pair in both this session's {@link $CFG} global variable
1339   * and in the 'config' database table for future sessions.
1340   *
1341   * Can also be used to update keys for plugin-scoped configs in config_plugin table.
1342   * In that case it doesn't affect $CFG.
1343   *
1344   * A NULL value will delete the entry.
1345   *
1346   * NOTE: this function is called from lib/db/upgrade.php
1347   *
1348   * @param string $name the key to set
1349   * @param string $value the value to set (without magic quotes)
1350   * @param string $plugin (optional) the plugin scope, default null
1351   * @return bool true or exception
1352   */
1353  function set_config($name, $value, $plugin=null) {
1354      global $CFG, $DB;
1355  
1356      if (empty($plugin)) {
1357          if (!array_key_exists($name, $CFG->config_php_settings)) {
1358              // So it's defined for this invocation at least.
1359              if (is_null($value)) {
1360                  unset($CFG->$name);
1361              } else {
1362                  // Settings from db are always strings.
1363                  $CFG->$name = (string)$value;
1364              }
1365          }
1366  
1367          if ($DB->get_field('config', 'name', array('name' => $name))) {
1368              if ($value === null) {
1369                  $DB->delete_records('config', array('name' => $name));
1370              } else {
1371                  $DB->set_field('config', 'value', $value, array('name' => $name));
1372              }
1373          } else {
1374              if ($value !== null) {
1375                  $config = new stdClass();
1376                  $config->name  = $name;
1377                  $config->value = $value;
1378                  $DB->insert_record('config', $config, false);
1379              }
1380          }
1381          if ($name === 'siteidentifier') {
1382              cache_helper::update_site_identifier($value);
1383          }
1384          cache_helper::invalidate_by_definition('core', 'config', array(), 'core');
1385      } else {
1386          // Plugin scope.
1387          if ($id = $DB->get_field('config_plugins', 'id', array('name' => $name, 'plugin' => $plugin))) {
1388              if ($value===null) {
1389                  $DB->delete_records('config_plugins', array('name' => $name, 'plugin' => $plugin));
1390              } else {
1391                  $DB->set_field('config_plugins', 'value', $value, array('id' => $id));
1392              }
1393          } else {
1394              if ($value !== null) {
1395                  $config = new stdClass();
1396                  $config->plugin = $plugin;
1397                  $config->name   = $name;
1398                  $config->value  = $value;
1399                  $DB->insert_record('config_plugins', $config, false);
1400              }
1401          }
1402          cache_helper::invalidate_by_definition('core', 'config', array(), $plugin);
1403      }
1404  
1405      return true;
1406  }
1407  
1408  /**
1409   * Get configuration values from the global config table
1410   * or the config_plugins table.
1411   *
1412   * If called with one parameter, it will load all the config
1413   * variables for one plugin, and return them as an object.
1414   *
1415   * If called with 2 parameters it will return a string single
1416   * value or false if the value is not found.
1417   *
1418   * NOTE: this function is called from lib/db/upgrade.php
1419   *
1420   * @static string|false $siteidentifier The site identifier is not cached. We use this static cache so
1421   *     that we need only fetch it once per request.
1422   * @param string $plugin full component name
1423   * @param string $name default null
1424   * @return mixed hash-like object or single value, return false no config found
1425   * @throws dml_exception
1426   */
1427  function get_config($plugin, $name = null) {
1428      global $CFG, $DB;
1429  
1430      static $siteidentifier = null;
1431  
1432      if ($plugin === 'moodle' || $plugin === 'core' || empty($plugin)) {
1433          $forced =& $CFG->config_php_settings;
1434          $iscore = true;
1435          $plugin = 'core';
1436      } else {
1437          if (array_key_exists($plugin, $CFG->forced_plugin_settings)) {
1438              $forced =& $CFG->forced_plugin_settings[$plugin];
1439          } else {
1440              $forced = array();
1441          }
1442          $iscore = false;
1443      }
1444  
1445      if ($siteidentifier === null) {
1446          try {
1447              // This may fail during installation.
1448              // If you have a look at {@link initialise_cfg()} you will see that this is how we detect the need to
1449              // install the database.
1450              $siteidentifier = $DB->get_field('config', 'value', array('name' => 'siteidentifier'));
1451          } catch (dml_exception $ex) {
1452              // Set siteidentifier to false. We don't want to trip this continually.
1453              $siteidentifier = false;
1454              throw $ex;
1455          }
1456      }
1457  
1458      if (!empty($name)) {
1459          if (array_key_exists($name, $forced)) {
1460              return (string)$forced[$name];
1461          } else if ($name === 'siteidentifier' && $plugin == 'core') {
1462              return $siteidentifier;
1463          }
1464      }
1465  
1466      $cache = cache::make('core', 'config');
1467      $result = $cache->get($plugin);
1468      if ($result === false) {
1469          // The user is after a recordset.
1470          if (!$iscore) {
1471              $result = $DB->get_records_menu('config_plugins', array('plugin' => $plugin), '', 'name,value');
1472          } else {
1473              // This part is not really used any more, but anyway...
1474              $result = $DB->get_records_menu('config', array(), '', 'name,value');;
1475          }
1476          $cache->set($plugin, $result);
1477      }
1478  
1479      if (!empty($name)) {
1480          if (array_key_exists($name, $result)) {
1481              return $result[$name];
1482          }
1483          return false;
1484      }
1485  
1486      if ($plugin === 'core') {
1487          $result['siteidentifier'] = $siteidentifier;
1488      }
1489  
1490      foreach ($forced as $key => $value) {
1491          if (is_null($value) or is_array($value) or is_object($value)) {
1492              // We do not want any extra mess here, just real settings that could be saved in db.
1493              unset($result[$key]);
1494          } else {
1495              // Convert to string as if it went through the DB.
1496              $result[$key] = (string)$value;
1497          }
1498      }
1499  
1500      return (object)$result;
1501  }
1502  
1503  /**
1504   * Removes a key from global configuration.
1505   *
1506   * NOTE: this function is called from lib/db/upgrade.php
1507   *
1508   * @param string $name the key to set
1509   * @param string $plugin (optional) the plugin scope
1510   * @return boolean whether the operation succeeded.
1511   */
1512  function unset_config($name, $plugin=null) {
1513      global $CFG, $DB;
1514  
1515      if (empty($plugin)) {
1516          unset($CFG->$name);
1517          $DB->delete_records('config', array('name' => $name));
1518          cache_helper::invalidate_by_definition('core', 'config', array(), 'core');
1519      } else {
1520          $DB->delete_records('config_plugins', array('name' => $name, 'plugin' => $plugin));
1521          cache_helper::invalidate_by_definition('core', 'config', array(), $plugin);
1522      }
1523  
1524      return true;
1525  }
1526  
1527  /**
1528   * Remove all the config variables for a given plugin.
1529   *
1530   * NOTE: this function is called from lib/db/upgrade.php
1531   *
1532   * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
1533   * @return boolean whether the operation succeeded.
1534   */
1535  function unset_all_config_for_plugin($plugin) {
1536      global $DB;
1537      // Delete from the obvious config_plugins first.
1538      $DB->delete_records('config_plugins', array('plugin' => $plugin));
1539      // Next delete any suspect settings from config.
1540      $like = $DB->sql_like('name', '?', true, true, false, '|');
1541      $params = array($DB->sql_like_escape($plugin.'_', '|') . '%');
1542      $DB->delete_records_select('config', $like, $params);
1543      // Finally clear both the plugin cache and the core cache (suspect settings now removed from core).
1544      cache_helper::invalidate_by_definition('core', 'config', array(), array('core', $plugin));
1545  
1546      return true;
1547  }
1548  
1549  /**
1550   * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
1551   *
1552   * All users are verified if they still have the necessary capability.
1553   *
1554   * @param string $value the value of the config setting.
1555   * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
1556   * @param bool $includeadmins include administrators.
1557   * @return array of user objects.
1558   */
1559  function get_users_from_config($value, $capability, $includeadmins = true) {
1560      if (empty($value) or $value === '$@NONE@$') {
1561          return array();
1562      }
1563  
1564      // We have to make sure that users still have the necessary capability,
1565      // it should be faster to fetch them all first and then test if they are present
1566      // instead of validating them one-by-one.
1567      $users = get_users_by_capability(context_system::instance(), $capability);
1568      if ($includeadmins) {
1569          $admins = get_admins();
1570          foreach ($admins as $admin) {
1571              $users[$admin->id] = $admin;
1572          }
1573      }
1574  
1575      if ($value === '$@ALL@$') {
1576          return $users;
1577      }
1578  
1579      $result = array(); // Result in correct order.
1580      $allowed = explode(',', $value);
1581      foreach ($allowed as $uid) {
1582          if (isset($users[$uid])) {
1583              $user = $users[$uid];
1584              $result[$user->id] = $user;
1585          }
1586      }
1587  
1588      return $result;
1589  }
1590  
1591  
1592  /**
1593   * Invalidates browser caches and cached data in temp.
1594   *
1595   * IMPORTANT - If you are adding anything here to do with the cache directory you should also have a look at
1596   * {@link phpunit_util::reset_dataroot()}
1597   *
1598   * @return void
1599   */
1600  function purge_all_caches() {
1601      global $CFG, $DB;
1602  
1603      reset_text_filters_cache();
1604      js_reset_all_caches();
1605      theme_reset_all_caches();
1606      get_string_manager()->reset_caches();
1607      core_text::reset_caches();
1608      if (class_exists('core_plugin_manager')) {
1609          core_plugin_manager::reset_caches();
1610      }
1611  
1612      // Bump up cacherev field for all courses.
1613      try {
1614          increment_revision_number('course', 'cacherev', '');
1615      } catch (moodle_exception $e) {
1616          // Ignore exception since this function is also called before upgrade script when field course.cacherev does not exist yet.
1617      }
1618  
1619      $DB->reset_caches();
1620      cache_helper::purge_all();
1621  
1622      // Purge all other caches: rss, simplepie, etc.
1623      remove_dir($CFG->cachedir.'', true);
1624  
1625      // Make sure cache dir is writable, throws exception if not.
1626      make_cache_directory('');
1627  
1628      // This is the only place where we purge local caches, we are only adding files there.
1629      // The $CFG->localcachedirpurged flag forces local directories to be purged on cluster nodes.
1630      remove_dir($CFG->localcachedir, true);
1631      set_config('localcachedirpurged', time());
1632      make_localcache_directory('', true);
1633      \core\task\manager::clear_static_caches();
1634  }
1635  
1636  /**
1637   * Get volatile flags
1638   *
1639   * @param string $type
1640   * @param int $changedsince default null
1641   * @return array records array
1642   */
1643  function get_cache_flags($type, $changedsince = null) {
1644      global $DB;
1645  
1646      $params = array('type' => $type, 'expiry' => time());
1647      $sqlwhere = "flagtype = :type AND expiry >= :expiry";
1648      if ($changedsince !== null) {
1649          $params['changedsince'] = $changedsince;
1650          $sqlwhere .= " AND timemodified > :changedsince";
1651      }
1652      $cf = array();
1653      if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
1654          foreach ($flags as $flag) {
1655              $cf[$flag->name] = $flag->value;
1656          }
1657      }
1658      return $cf;
1659  }
1660  
1661  /**
1662   * Get volatile flags
1663   *
1664   * @param string $type
1665   * @param string $name
1666   * @param int $changedsince default null
1667   * @return string|false The cache flag value or false
1668   */
1669  function get_cache_flag($type, $name, $changedsince=null) {
1670      global $DB;
1671  
1672      $params = array('type' => $type, 'name' => $name, 'expiry' => time());
1673  
1674      $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
1675      if ($changedsince !== null) {
1676          $params['changedsince'] = $changedsince;
1677          $sqlwhere .= " AND timemodified > :changedsince";
1678      }
1679  
1680      return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
1681  }
1682  
1683  /**
1684   * Set a volatile flag
1685   *
1686   * @param string $type the "type" namespace for the key
1687   * @param string $name the key to set
1688   * @param string $value the value to set (without magic quotes) - null will remove the flag
1689   * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
1690   * @return bool Always returns true
1691   */
1692  function set_cache_flag($type, $name, $value, $expiry = null) {
1693      global $DB;
1694  
1695      $timemodified = time();
1696      if ($expiry === null || $expiry < $timemodified) {
1697          $expiry = $timemodified + 24 * 60 * 60;
1698      } else {
1699          $expiry = (int)$expiry;
1700      }
1701  
1702      if ($value === null) {
1703          unset_cache_flag($type, $name);
1704          return true;
1705      }
1706  
1707      if ($f = $DB->get_record('cache_flags', array('name' => $name, 'flagtype' => $type), '*', IGNORE_MULTIPLE)) {
1708          // This is a potential problem in DEBUG_DEVELOPER.
1709          if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
1710              return true; // No need to update.
1711          }
1712          $f->value        = $value;
1713          $f->expiry       = $expiry;
1714          $f->timemodified = $timemodified;
1715          $DB->update_record('cache_flags', $f);
1716      } else {
1717          $f = new stdClass();
1718          $f->flagtype     = $type;
1719          $f->name         = $name;
1720          $f->value        = $value;
1721          $f->expiry       = $expiry;
1722          $f->timemodified = $timemodified;
1723          $DB->insert_record('cache_flags', $f);
1724      }
1725      return true;
1726  }
1727  
1728  /**
1729   * Removes a single volatile flag
1730   *
1731   * @param string $type the "type" namespace for the key
1732   * @param string $name the key to set
1733   * @return bool
1734   */
1735  function unset_cache_flag($type, $name) {
1736      global $DB;
1737      $DB->delete_records('cache_flags', array('name' => $name, 'flagtype' => $type));
1738      return true;
1739  }
1740  
1741  /**
1742   * Garbage-collect volatile flags
1743   *
1744   * @return bool Always returns true
1745   */
1746  function gc_cache_flags() {
1747      global $DB;
1748      $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
1749      return true;
1750  }
1751  
1752  // USER PREFERENCE API.
1753  
1754  /**
1755   * Refresh user preference cache. This is used most often for $USER
1756   * object that is stored in session, but it also helps with performance in cron script.
1757   *
1758   * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
1759   *
1760   * @package  core
1761   * @category preference
1762   * @access   public
1763   * @param    stdClass         $user          User object. Preferences are preloaded into 'preference' property
1764   * @param    int              $cachelifetime Cache life time on the current page (in seconds)
1765   * @throws   coding_exception
1766   * @return   null
1767   */
1768  function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120) {
1769      global $DB;
1770      // Static cache, we need to check on each page load, not only every 2 minutes.
1771      static $loadedusers = array();
1772  
1773      if (!isset($user->id)) {
1774          throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
1775      }
1776  
1777      if (empty($user->id) or isguestuser($user->id)) {
1778          // No permanent storage for not-logged-in users and guest.
1779          if (!isset($user->preference)) {
1780              $user->preference = array();
1781          }
1782          return;
1783      }
1784  
1785      $timenow = time();
1786  
1787      if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
1788          // Already loaded at least once on this page. Are we up to date?
1789          if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
1790              // No need to reload - we are on the same page and we loaded prefs just a moment ago.
1791              return;
1792  
1793          } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
1794              // No change since the lastcheck on this page.
1795              $user->preference['_lastloaded'] = $timenow;
1796              return;
1797          }
1798      }
1799  
1800      // OK, so we have to reload all preferences.
1801      $loadedusers[$user->id] = true;
1802      $user->preference = $DB->get_records_menu('user_preferences', array('userid' => $user->id), '', 'name,value'); // All values.
1803      $user->preference['_lastloaded'] = $timenow;
1804  }
1805  
1806  /**
1807   * Called from set/unset_user_preferences, so that the prefs can be correctly reloaded in different sessions.
1808   *
1809   * NOTE: internal function, do not call from other code.
1810   *
1811   * @package core
1812   * @access private
1813   * @param integer $userid the user whose prefs were changed.
1814   */
1815  function mark_user_preferences_changed($userid) {
1816      global $CFG;
1817  
1818      if (empty($userid) or isguestuser($userid)) {
1819          // No cache flags for guest and not-logged-in users.
1820          return;
1821      }
1822  
1823      set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
1824  }
1825  
1826  /**
1827   * Sets a preference for the specified user.
1828   *
1829   * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1830   *
1831   * @package  core
1832   * @category preference
1833   * @access   public
1834   * @param    string            $name  The key to set as preference for the specified user
1835   * @param    string            $value The value to set for the $name key in the specified user's
1836   *                                    record, null means delete current value.
1837   * @param    stdClass|int|null $user  A moodle user object or id, null means current user
1838   * @throws   coding_exception
1839   * @return   bool                     Always true or exception
1840   */
1841  function set_user_preference($name, $value, $user = null) {
1842      global $USER, $DB;
1843  
1844      if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1845          throw new coding_exception('Invalid preference name in set_user_preference() call');
1846      }
1847  
1848      if (is_null($value)) {
1849          // Null means delete current.
1850          return unset_user_preference($name, $user);
1851      } else if (is_object($value)) {
1852          throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
1853      } else if (is_array($value)) {
1854          throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
1855      }
1856      // Value column maximum length is 1333 characters.
1857      $value = (string)$value;
1858      if (core_text::strlen($value) > 1333) {
1859          throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
1860      }
1861  
1862      if (is_null($user)) {
1863          $user = $USER;
1864      } else if (isset($user->id)) {
1865          // It is a valid object.
1866      } else if (is_numeric($user)) {
1867          $user = (object)array('id' => (int)$user);
1868      } else {
1869          throw new coding_exception('Invalid $user parameter in set_user_preference() call');
1870      }
1871  
1872      check_user_preferences_loaded($user);
1873  
1874      if (empty($user->id) or isguestuser($user->id)) {
1875          // No permanent storage for not-logged-in users and guest.
1876          $user->preference[$name] = $value;
1877          return true;
1878      }
1879  
1880      if ($preference = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => $name))) {
1881          if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
1882              // Preference already set to this value.
1883              return true;
1884          }
1885          $DB->set_field('user_preferences', 'value', $value, array('id' => $preference->id));
1886  
1887      } else {
1888          $preference = new stdClass();
1889          $preference->userid = $user->id;
1890          $preference->name   = $name;
1891          $preference->value  = $value;
1892          $DB->insert_record('user_preferences', $preference);
1893      }
1894  
1895      // Update value in cache.
1896      $user->preference[$name] = $value;
1897  
1898      // Set reload flag for other sessions.
1899      mark_user_preferences_changed($user->id);
1900  
1901      return true;
1902  }
1903  
1904  /**
1905   * Sets a whole array of preferences for the current user
1906   *
1907   * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1908   *
1909   * @package  core
1910   * @category preference
1911   * @access   public
1912   * @param    array             $prefarray An array of key/value pairs to be set
1913   * @param    stdClass|int|null $user      A moodle user object or id, null means current user
1914   * @return   bool                         Always true or exception
1915   */
1916  function set_user_preferences(array $prefarray, $user = null) {
1917      foreach ($prefarray as $name => $value) {
1918          set_user_preference($name, $value, $user);
1919      }
1920      return true;
1921  }
1922  
1923  /**
1924   * Unsets a preference completely by deleting it from the database
1925   *
1926   * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1927   *
1928   * @package  core
1929   * @category preference
1930   * @access   public
1931   * @param    string            $name The key to unset as preference for the specified user
1932   * @param    stdClass|int|null $user A moodle user object or id, null means current user
1933   * @throws   coding_exception
1934   * @return   bool                    Always true or exception
1935   */
1936  function unset_user_preference($name, $user = null) {
1937      global $USER, $DB;
1938  
1939      if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
1940          throw new coding_exception('Invalid preference name in unset_user_preference() call');
1941      }
1942  
1943      if (is_null($user)) {
1944          $user = $USER;
1945      } else if (isset($user->id)) {
1946          // It is a valid object.
1947      } else if (is_numeric($user)) {
1948          $user = (object)array('id' => (int)$user);
1949      } else {
1950          throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
1951      }
1952  
1953      check_user_preferences_loaded($user);
1954  
1955      if (empty($user->id) or isguestuser($user->id)) {
1956          // No permanent storage for not-logged-in user and guest.
1957          unset($user->preference[$name]);
1958          return true;
1959      }
1960  
1961      // Delete from DB.
1962      $DB->delete_records('user_preferences', array('userid' => $user->id, 'name' => $name));
1963  
1964      // Delete the preference from cache.
1965      unset($user->preference[$name]);
1966  
1967      // Set reload flag for other sessions.
1968      mark_user_preferences_changed($user->id);
1969  
1970      return true;
1971  }
1972  
1973  /**
1974   * Used to fetch user preference(s)
1975   *
1976   * If no arguments are supplied this function will return
1977   * all of the current user preferences as an array.
1978   *
1979   * If a name is specified then this function
1980   * attempts to return that particular preference value.  If
1981   * none is found, then the optional value $default is returned,
1982   * otherwise null.
1983   *
1984   * If a $user object is submitted it's 'preference' property is used for the preferences cache.
1985   *
1986   * @package  core
1987   * @category preference
1988   * @access   public
1989   * @param    string            $name    Name of the key to use in finding a preference value
1990   * @param    mixed|null        $default Value to be returned if the $name key is not set in the user preferences
1991   * @param    stdClass|int|null $user    A moodle user object or id, null means current user
1992   * @throws   coding_exception
1993   * @return   string|mixed|null          A string containing the value of a single preference. An
1994   *                                      array with all of the preferences or null
1995   */
1996  function get_user_preferences($name = null, $default = null, $user = null) {
1997      global $USER;
1998  
1999      if (is_null($name)) {
2000          // All prefs.
2001      } else if (is_numeric($name) or $name === '_lastloaded') {
2002          throw new coding_exception('Invalid preference name in get_user_preferences() call');
2003      }
2004  
2005      if (is_null($user)) {
2006          $user = $USER;
2007      } else if (isset($user->id)) {
2008          // Is a valid object.
2009      } else if (is_numeric($user)) {
2010          $user = (object)array('id' => (int)$user);
2011      } else {
2012          throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
2013      }
2014  
2015      check_user_preferences_loaded($user);
2016  
2017      if (empty($name)) {
2018          // All values.
2019          return $user->preference;
2020      } else if (isset($user->preference[$name])) {
2021          // The single string value.
2022          return $user->preference[$name];
2023      } else {
2024          // Default value (null if not specified).
2025          return $default;
2026      }
2027  }
2028  
2029  // FUNCTIONS FOR HANDLING TIME.
2030  
2031  /**
2032   * Given date parts in user time produce a GMT timestamp.
2033   *
2034   * @package core
2035   * @category time
2036   * @param int $year The year part to create timestamp of
2037   * @param int $month The month part to create timestamp of
2038   * @param int $day The day part to create timestamp of
2039   * @param int $hour The hour part to create timestamp of
2040   * @param int $minute The minute part to create timestamp of
2041   * @param int $second The second part to create timestamp of
2042   * @param int|float|string $timezone Timezone modifier, used to calculate GMT time offset.
2043   *             if 99 then default user's timezone is used {@link http://docs.moodle.org/dev/Time_API#Timezone}
2044   * @param bool $applydst Toggle Daylight Saving Time, default true, will be
2045   *             applied only if timezone is 99 or string.
2046   * @return int GMT timestamp
2047   */
2048  function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
2049  
2050      // Save input timezone, required for dst offset check.
2051      $passedtimezone = $timezone;
2052  
2053      $timezone = get_user_timezone_offset($timezone);
2054  
2055      if (abs($timezone) > 13) {
2056          // Server time.
2057          $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
2058      } else {
2059          $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
2060          $time = usertime($time, $timezone);
2061  
2062          // Apply dst for string timezones or if 99 then try dst offset with user's default timezone.
2063          if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
2064              $time -= dst_offset_on($time, $passedtimezone);
2065          }
2066      }
2067  
2068      return $time;
2069  
2070  }
2071  
2072  /**
2073   * Format a date/time (seconds) as weeks, days, hours etc as needed
2074   *
2075   * Given an amount of time in seconds, returns string
2076   * formatted nicely as weeks, days, hours etc as needed
2077   *
2078   * @package core
2079   * @category time
2080   * @uses MINSECS
2081   * @uses HOURSECS
2082   * @uses DAYSECS
2083   * @uses YEARSECS
2084   * @param int $totalsecs Time in seconds
2085   * @param stdClass $str Should be a time object
2086   * @return string A nicely formatted date/time string
2087   */
2088  function format_time($totalsecs, $str = null) {
2089  
2090      $totalsecs = abs($totalsecs);
2091  
2092      if (!$str) {
2093          // Create the str structure the slow way.
2094          $str = new stdClass();
2095          $str->day   = get_string('day');
2096          $str->days  = get_string('days');
2097          $str->hour  = get_string('hour');
2098          $str->hours = get_string('hours');
2099          $str->min   = get_string('min');
2100          $str->mins  = get_string('mins');
2101          $str->sec   = get_string('sec');
2102          $str->secs  = get_string('secs');
2103          $str->year  = get_string('year');
2104          $str->years = get_string('years');
2105      }
2106  
2107      $years     = floor($totalsecs/YEARSECS);
2108      $remainder = $totalsecs - ($years*YEARSECS);
2109      $days      = floor($remainder/DAYSECS);
2110      $remainder = $totalsecs - ($days*DAYSECS);
2111      $hours     = floor($remainder/HOURSECS);
2112      $remainder = $remainder - ($hours*HOURSECS);
2113      $mins      = floor($remainder/MINSECS);
2114      $secs      = $remainder - ($mins*MINSECS);
2115  
2116      $ss = ($secs == 1)  ? $str->sec  : $str->secs;
2117      $sm = ($mins == 1)  ? $str->min  : $str->mins;
2118      $sh = ($hours == 1) ? $str->hour : $str->hours;
2119      $sd = ($days == 1)  ? $str->day  : $str->days;
2120      $sy = ($years == 1)  ? $str->year  : $str->years;
2121  
2122      $oyears = '';
2123      $odays = '';
2124      $ohours = '';
2125      $omins = '';
2126      $osecs = '';
2127  
2128      if ($years) {
2129          $oyears  = $years .' '. $sy;
2130      }
2131      if ($days) {
2132          $odays  = $days .' '. $sd;
2133      }
2134      if ($hours) {
2135          $ohours = $hours .' '. $sh;
2136      }
2137      if ($mins) {
2138          $omins  = $mins .' '. $sm;
2139      }
2140      if ($secs) {
2141          $osecs  = $secs .' '. $ss;
2142      }
2143  
2144      if ($years) {
2145          return trim($oyears .' '. $odays);
2146      }
2147      if ($days) {
2148          return trim($odays .' '. $ohours);
2149      }
2150      if ($hours) {
2151          return trim($ohours .' '. $omins);
2152      }
2153      if ($mins) {
2154          return trim($omins .' '. $osecs);
2155      }
2156      if ($secs) {
2157          return $osecs;
2158      }
2159      return get_string('now');
2160  }
2161  
2162  /**
2163   * Returns a formatted string that represents a date in user time.
2164   *
2165   * @package core
2166   * @category time
2167   * @param int $date the timestamp in UTC, as obtained from the database.
2168   * @param string $format strftime format. You should probably get this using
2169   *        get_string('strftime...', 'langconfig');
2170   * @param int|float|string $timezone by default, uses the user's time zone. if numeric and
2171   *        not 99 then daylight saving will not be added.
2172   *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
2173   * @param bool $fixday If true (default) then the leading zero from %d is removed.
2174   *        If false then the leading zero is maintained.
2175   * @param bool $fixhour If true (default) then the leading zero from %I is removed.
2176   * @return string the formatted date/time.
2177   */
2178  function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true) {
2179      $calendartype = \core_calendar\type_factory::get_calendar_instance();
2180      return $calendartype->timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour);
2181  }
2182  
2183  /**
2184   * Returns a formatted date ensuring it is UTF-8.
2185   *
2186   * If we are running under Windows convert to Windows encoding and then back to UTF-8
2187   * (because it's impossible to specify UTF-8 to fetch locale info in Win32).
2188   *
2189   * This function does not do any calculation regarding the user preferences and should
2190   * therefore receive the final date timestamp, format and timezone. Timezone being only used
2191   * to differentiate the use of server time or not (strftime() against gmstrftime()).
2192   *
2193   * @param int $date the timestamp.
2194   * @param string $format strftime format.
2195   * @param int|float $tz the numerical timezone, typically returned by {@link get_user_timezone_offset()}.
2196   * @return string the formatted date/time.
2197   * @since Moodle 2.3.3
2198   */
2199  function date_format_string($date, $format, $tz = 99) {
2200      global $CFG;
2201  
2202      $localewincharset = null;
2203      // Get the calendar type user is using.
2204      if ($CFG->ostype == 'WINDOWS') {
2205          $calendartype = \core_calendar\type_factory::get_calendar_instance();
2206          $localewincharset = $calendartype->locale_win_charset();
2207      }
2208  
2209      if (abs($tz) > 13) {
2210          if ($localewincharset) {
2211              $format = core_text::convert($format, 'utf-8', $localewincharset);
2212              $datestring = strftime($format, $date);
2213              $datestring = core_text::convert($datestring, $localewincharset, 'utf-8');
2214          } else {
2215              $datestring = strftime($format, $date);
2216          }
2217      } else {
2218          if ($localewincharset) {
2219              $format = core_text::convert($format, 'utf-8', $localewincharset);
2220              $datestring = gmstrftime($format, $date);
2221              $datestring = core_text::convert($datestring, $localewincharset, 'utf-8');
2222          } else {
2223              $datestring = gmstrftime($format, $date);
2224          }
2225      }
2226      return $datestring;
2227  }
2228  
2229  /**
2230   * Given a $time timestamp in GMT (seconds since epoch),
2231   * returns an array that represents the date in user time
2232   *
2233   * @package core
2234   * @category time
2235   * @uses HOURSECS
2236   * @param int $time Timestamp in GMT
2237   * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
2238   *        dst offset is applied {@link http://docs.moodle.org/dev/Time_API#Timezone}
2239   * @return array An array that represents the date in user time
2240   */
2241  function usergetdate($time, $timezone=99) {
2242  
2243      // Save input timezone, required for dst offset check.
2244      $passedtimezone = $timezone;
2245  
2246      $timezone = get_user_timezone_offset($timezone);
2247  
2248      if (abs($timezone) > 13) {
2249          // Server time.
2250          return getdate($time);
2251      }
2252  
2253      // Add daylight saving offset for string timezones only, as we can't get dst for
2254      // float values. if timezone is 99 (user default timezone), then try update dst.
2255      if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
2256          $time += dst_offset_on($time, $passedtimezone);
2257      }
2258  
2259      $time += intval((float)$timezone * HOURSECS);
2260  
2261      $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
2262  
2263      // Be careful to ensure the returned array matches that produced by getdate() above.
2264      list(
2265          $getdate['month'],
2266          $getdate['weekday'],
2267          $getdate['yday'],
2268          $getdate['year'],
2269          $getdate['mon'],
2270          $getdate['wday'],
2271          $getdate['mday'],
2272          $getdate['hours'],
2273          $getdate['minutes'],
2274          $getdate['seconds']
2275      ) = explode('_', $datestring);
2276  
2277      // Set correct datatype to match with getdate().
2278      $getdate['seconds'] = (int)$getdate['seconds'];
2279      $getdate['yday'] = (int)$getdate['yday'] - 1; // The function gmstrftime returns 0 through 365.
2280      $getdate['year'] = (int)$getdate['year'];
2281      $getdate['mon'] = (int)$getdate['mon'];
2282      $getdate['wday'] = (int)$getdate['wday'];
2283      $getdate['mday'] = (int)$getdate['mday'];
2284      $getdate['hours'] = (int)$getdate['hours'];
2285      $getdate['minutes'] = (int)$getdate['minutes'];
2286      return $getdate;
2287  }
2288  
2289  /**
2290   * Given a GMT timestamp (seconds since epoch), offsets it by
2291   * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
2292   *
2293   * @package core
2294   * @category time
2295   * @uses HOURSECS
2296   * @param int $date Timestamp in GMT
2297   * @param float|int|string $timezone timezone to calculate GMT time offset before
2298   *        calculating user time, 99 is default user timezone
2299   *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
2300   * @return int
2301   */
2302  function usertime($date, $timezone=99) {
2303  
2304      $timezone = get_user_timezone_offset($timezone);
2305  
2306      if (abs($timezone) > 13) {
2307          return $date;
2308      }
2309      return $date - (int)($timezone * HOURSECS);
2310  }
2311  
2312  /**
2313   * Given a time, return the GMT timestamp of the most recent midnight
2314   * for the current user.
2315   *
2316   * @package core
2317   * @category time
2318   * @param int $date Timestamp in GMT
2319   * @param float|int|string $timezone timezone to calculate GMT time offset before
2320   *        calculating user midnight time, 99 is default user timezone
2321   *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
2322   * @return int Returns a GMT timestamp
2323   */
2324  function usergetmidnight($date, $timezone=99) {
2325  
2326      $userdate = usergetdate($date, $timezone);
2327  
2328      // Time of midnight of this user's day, in GMT.
2329      return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
2330  
2331  }
2332  
2333  /**
2334   * Returns a string that prints the user's timezone
2335   *
2336   * @package core
2337   * @category time
2338   * @param float|int|string $timezone timezone to calculate GMT time offset before
2339   *        calculating user timezone, 99 is default user timezone
2340   *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
2341   * @return string
2342   */
2343  function usertimezone($timezone=99) {
2344  
2345      $tz = get_user_timezone($timezone);
2346  
2347      if (!is_float($tz)) {
2348          return $tz;
2349      }
2350  
2351      if (abs($tz) > 13) {
2352          // Server time.
2353          return get_string('serverlocaltime');
2354      }
2355  
2356      if ($tz == intval($tz)) {
2357          // Don't show .0 for whole hours.
2358          $tz = intval($tz);
2359      }
2360  
2361      if ($tz == 0) {
2362          return 'UTC';
2363      } else if ($tz > 0) {
2364          return 'UTC+'.$tz;
2365      } else {
2366          return 'UTC'.$tz;
2367      }
2368  
2369  }
2370  
2371  /**
2372   * Returns a float which represents the user's timezone difference from GMT in hours
2373   * Checks various settings and picks the most dominant of those which have a value
2374   *
2375   * @package core
2376   * @category time
2377   * @param float|int|string $tz timezone to calculate GMT time offset for user,
2378   *        99 is default user timezone
2379   *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
2380   * @return float
2381   */
2382  function get_user_timezone_offset($tz = 99) {
2383      $tz = get_user_timezone($tz);
2384  
2385      if (is_float($tz)) {
2386          return $tz;
2387      } else {
2388          $tzrecord = get_timezone_record($tz);
2389          if (empty($tzrecord)) {
2390              return 99.0;
2391          }
2392          return (float)$tzrecord->gmtoff / HOURMINS;
2393      }
2394  }
2395  
2396  /**
2397   * Returns an int which represents the systems's timezone difference from GMT in seconds
2398   *
2399   * @package core
2400   * @category time
2401   * @param float|int|string $tz timezone for which offset is required.
2402   *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
2403   * @return int|bool if found, false is timezone 99 or error
2404   */
2405  function get_timezone_offset($tz) {
2406      if ($tz == 99) {
2407          return false;
2408      }
2409  
2410      if (is_numeric($tz)) {
2411          return intval($tz * 60*60);
2412      }
2413  
2414      if (!$tzrecord = get_timezone_record($tz)) {
2415          return false;
2416      }
2417      return intval($tzrecord->gmtoff * 60);
2418  }
2419  
2420  /**
2421   * Returns a float or a string which denotes the user's timezone
2422   * 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)
2423   * means that for this timezone there are also DST rules to be taken into account
2424   * Checks various settings and picks the most dominant of those which have a value
2425   *
2426   * @package core
2427   * @category time
2428   * @param float|int|string $tz timezone to calculate GMT time offset before
2429   *        calculating user timezone, 99 is default user timezone
2430   *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
2431   * @return float|string
2432   */
2433  function get_user_timezone($tz = 99) {
2434      global $USER, $CFG;
2435  
2436      $timezones = array(
2437          $tz,
2438          isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
2439          isset($USER->timezone) ? $USER->timezone : 99,
2440          isset($CFG->timezone) ? $CFG->timezone : 99,
2441          );
2442  
2443      $tz = 99;
2444  
2445      // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array.
2446      while (((empty($tz) && !is_numeric($tz)) || $tz == 99) && $next = each($timezones)) {
2447          $tz = $next['value'];
2448      }
2449      return is_numeric($tz) ? (float) $tz : $tz;
2450  }
2451  
2452  /**
2453   * Returns cached timezone record for given $timezonename
2454   *
2455   * @package core
2456   * @param string $timezonename name of the timezone
2457   * @return stdClass|bool timezonerecord or false
2458   */
2459  function get_timezone_record($timezonename) {
2460      global $DB;
2461      static $cache = null;
2462  
2463      if ($cache === null) {
2464          $cache = array();
2465      }
2466  
2467      if (isset($cache[$timezonename])) {
2468          return $cache[$timezonename];
2469      }
2470  
2471      return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
2472                                                          WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
2473  }
2474  
2475  /**
2476   * Build and store the users Daylight Saving Time (DST) table
2477   *
2478   * @package core
2479   * @param int $fromyear Start year for the table, defaults to 1971
2480   * @param int $toyear End year for the table, defaults to 2035
2481   * @param int|float|string $strtimezone timezone to check if dst should be applied.
2482   * @return bool
2483   */
2484  function calculate_user_dst_table($fromyear = null, $toyear = null, $strtimezone = null) {
2485      global $SESSION, $DB;
2486  
2487      $usertz = get_user_timezone($strtimezone);
2488  
2489      if (is_float($usertz)) {
2490          // Trivial timezone, no DST.
2491          return false;
2492      }
2493  
2494      if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
2495          // We have pre-calculated values, but the user's effective TZ has changed in the meantime, so reset.
2496          unset($SESSION->dst_offsets);
2497          unset($SESSION->dst_range);
2498      }
2499  
2500      if (!empty($SESSION->dst_offsets) && empty($fromyear) && empty($toyear)) {
2501          // Repeat calls which do not request specific year ranges stop here, we have already calculated the table.
2502          // This will be the return path most of the time, pretty light computationally.
2503          return true;
2504      }
2505  
2506      // Reaching here means we either need to extend our table or create it from scratch.
2507  
2508      // Remember which TZ we calculated these changes for.
2509      $SESSION->dst_offsettz = $usertz;
2510  
2511      if (empty($SESSION->dst_offsets)) {
2512          // If we 're creating from scratch, put the two guard elements in there.
2513          $SESSION->dst_offsets = array(1 => null, 0 => null);
2514      }
2515      if (empty($SESSION->dst_range)) {
2516          // If creating from scratch.
2517          $from = max((empty($fromyear) ? intval(date('Y')) - 3 : $fromyear), 1971);
2518          $to   = min((empty($toyear)   ? intval(date('Y')) + 3 : $toyear),   2035);
2519  
2520          // Fill in the array with the extra years we need to process.
2521          $yearstoprocess = array();
2522          for ($i = $from; $i <= $to; ++$i) {
2523              $yearstoprocess[] = $i;
2524          }
2525  
2526          // Take note of which years we have processed for future calls.
2527          $SESSION->dst_range = array($from, $to);
2528      } else {
2529          // If needing to extend the table, do the same.
2530          $yearstoprocess = array();
2531  
2532          $from = max((empty($fromyear) ? $SESSION->dst_range[0] : $fromyear), 1971);
2533          $to   = min((empty($toyear)   ? $SESSION->dst_range[1] : $toyear),   2035);
2534  
2535          if ($from < $SESSION->dst_range[0]) {
2536              // Take note of which years we need to process and then note that we have processed them for future calls.
2537              for ($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
2538                  $yearstoprocess[] = $i;
2539              }
2540              $SESSION->dst_range[0] = $from;
2541          }
2542          if ($to > $SESSION->dst_range[1]) {
2543              // Take note of which years we need to process and then note that we have processed them for future calls.
2544              for ($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
2545                  $yearstoprocess[] = $i;
2546              }
2547              $SESSION->dst_range[1] = $to;
2548          }
2549      }
2550  
2551      if (empty($yearstoprocess)) {
2552          // This means that there was a call requesting a SMALLER range than we have already calculated.
2553          return true;
2554      }
2555  
2556      // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
2557      // Also, the array is sorted in descending timestamp order!
2558  
2559      // Get DB data.
2560  
2561      static $presetscache = array();
2562      if (!isset($presetscache[$usertz])) {
2563          $presetscache[$usertz] = $DB->get_records('timezone', array('name' => $usertz),
2564              'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, '.
2565              'std_startday, std_weekday, std_skipweeks, std_time');
2566      }
2567      if (empty($presetscache[$usertz])) {
2568          return false;
2569      }
2570  
2571      // Remove ending guard (first element of the array).
2572      reset($SESSION->dst_offsets);
2573      unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
2574  
2575      // Add all required change timestamps.
2576      foreach ($yearstoprocess as $y) {
2577          // Find the record which is in effect for the year $y.
2578          foreach ($presetscache[$usertz] as $year => $preset) {
2579              if ($year <= $y) {
2580                  break;
2581              }
2582          }
2583  
2584          $changes = dst_changes_for_year($y, $preset);
2585  
2586          if ($changes === null) {
2587              continue;
2588          }
2589          if ($changes['dst'] != 0) {
2590              $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
2591          }
2592          if ($changes['std'] != 0) {
2593              $SESSION->dst_offsets[$changes['std']] = 0;
2594          }
2595      }
2596  
2597      // Put in a guard element at the top.
2598      $maxtimestamp = max(array_keys($SESSION->dst_offsets));
2599      $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = null; // DAYSECS is arbitrary, any "small" number will do.
2600  
2601      // Sort again.
2602      krsort($SESSION->dst_offsets);
2603  
2604      return true;
2605  }
2606  
2607  /**
2608   * Calculates the required DST change and returns a Timestamp Array
2609   *
2610   * @package core
2611   * @category time
2612   * @uses HOURSECS
2613   * @uses MINSECS
2614   * @param int|string $year Int or String Year to focus on
2615   * @param object $timezone Instatiated Timezone object
2616   * @return array|null Array dst => xx, 0 => xx, std => yy, 1 => yy or null
2617   */
2618  function dst_changes_for_year($year, $timezone) {
2619  
2620      if ($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 &&
2621          $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
2622          return null;
2623      }
2624  
2625      $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
2626      $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
2627  
2628      list($dsthour, $dstmin) = explode(':', $timezone->dst_time);
2629      list($stdhour, $stdmin) = explode(':', $timezone->std_time);
2630  
2631      $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
2632      $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
2633  
2634      // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
2635      // This has the advantage of being able to have negative values for hour, i.e. for timezones
2636      // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
2637  
2638      $timedst += $dsthour * HOURSECS + $dstmin * MINSECS;
2639      $timestd += $stdhour * HOURSECS + $stdmin * MINSECS;
2640  
2641      return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
2642  }
2643  
2644  /**
2645   * Calculates the Daylight Saving Offset for a given date/time (timestamp)
2646   * - Note: Daylight saving only works for string timezones and not for float.
2647   *
2648   * @package core
2649   * @category time
2650   * @param int $time must NOT be compensated at all, it has to be a pure timestamp
2651   * @param int|float|string $strtimezone timezone for which offset is expected, if 99 or null
2652   *        then user's default timezone is used. {@link http://docs.moodle.org/dev/Time_API#Timezone}
2653   * @return int
2654   */
2655  function dst_offset_on($time, $strtimezone = null) {
2656      global $SESSION;
2657  
2658      if (!calculate_user_dst_table(null, null, $strtimezone) || empty($SESSION->dst_offsets)) {
2659          return 0;
2660      }
2661  
2662      reset($SESSION->dst_offsets);
2663      while (list($from, $offset) = each($SESSION->dst_offsets)) {
2664          if ($from <= $time) {
2665              break;
2666          }
2667      }
2668  
2669      // This is the normal return path.
2670      if ($offset !== null) {
2671          return $offset;
2672      }
2673  
2674      // Reaching this point means we haven't calculated far enough, do it now:
2675      // Calculate extra DST changes if needed and recurse. The recursion always
2676      // moves toward the stopping condition, so will always end.
2677  
2678      if ($from == 0) {
2679          // We need a year smaller than $SESSION->dst_range[0].
2680          if ($SESSION->dst_range[0] == 1971) {
2681              return 0;
2682          }
2683          calculate_user_dst_table($SESSION->dst_range[0] - 5, null, $strtimezone);
2684          return dst_offset_on($time, $strtimezone);
2685      } else {
2686          // We need a year larger than $SESSION->dst_range[1].
2687          if ($SESSION->dst_range[1] == 2035) {
2688              return 0;
2689          }
2690          calculate_user_dst_table(null, $SESSION->dst_range[1] + 5, $strtimezone);
2691          return dst_offset_on($time, $strtimezone);
2692      }
2693  }
2694  
2695  /**
2696   * Calculates when the day appears in specific month
2697   *
2698   * @package core
2699   * @category time
2700   * @param int $startday starting day of the month
2701   * @param int $weekday The day when week starts (normally taken from user preferences)
2702   * @param int $month The month whose day is sought
2703   * @param int $year The year of the month whose day is sought
2704   * @return int
2705   */
2706  function find_day_in_month($startday, $weekday, $month, $year) {
2707      $calendartype = \core_calendar\type_factory::get_calendar_instance();
2708  
2709      $daysinmonth = days_in_month($month, $year);
2710      $daysinweek = count($calendartype->get_weekdays());
2711  
2712      if ($weekday == -1) {
2713          // Don't care about weekday, so return:
2714          //    abs($startday) if $startday != -1
2715          //    $daysinmonth otherwise.
2716          return ($startday == -1) ? $daysinmonth : abs($startday);
2717      }
2718  
2719      // From now on we 're looking for a specific weekday.
2720      // Give "end of month" its actual value, since we know it.
2721      if ($startday == -1) {
2722          $startday = -1 * $daysinmonth;
2723      }
2724  
2725      // Starting from day $startday, the sign is the direction.
2726      if ($startday < 1) {
2727          $startday = abs($startday);
2728          $lastmonthweekday = dayofweek($daysinmonth, $month, $year);
2729  
2730          // This is the last such weekday of the month.
2731          $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
2732          if ($lastinmonth > $daysinmonth) {
2733              $lastinmonth -= $daysinweek;
2734          }
2735  
2736          // Find the first such weekday <= $startday.
2737          while ($lastinmonth > $startday) {
2738              $lastinmonth -= $daysinweek;
2739          }
2740  
2741          return $lastinmonth;
2742      } else {
2743          $indexweekday = dayofweek($startday, $month, $year);
2744  
2745          $diff = $weekday - $indexweekday;
2746          if ($diff < 0) {
2747              $diff += $daysinweek;
2748          }
2749  
2750          // This is the first such weekday of the month equal to or after $startday.
2751          $firstfromindex = $startday + $diff;
2752  
2753          return $firstfromindex;
2754      }
2755  }
2756  
2757  /**
2758   * Calculate the number of days in a given month
2759   *
2760   * @package core
2761   * @category time
2762   * @param int $month The month whose day count is sought
2763   * @param int $year The year of the month whose day count is sought
2764   * @return int
2765   */
2766  function days_in_month($month, $year) {
2767      $calendartype = \core_calendar\type_factory::get_calendar_instance();
2768      return $calendartype->get_num_days_in_month($year, $month);
2769  }
2770  
2771  /**
2772   * Calculate the position in the week of a specific calendar day
2773   *
2774   * @package core
2775   * @category time
2776   * @param int $day The day of the date whose position in the week is sought
2777   * @param int $month The month of the date whose position in the week is sought
2778   * @param int $year The year of the date whose position in the week is sought
2779   * @return int
2780   */
2781  function dayofweek($day, $month, $year) {
2782      $calendartype = \core_calendar\type_factory::get_calendar_instance();
2783      return $calendartype->get_weekday($year, $month, $day);
2784  }
2785  
2786  // USER AUTHENTICATION AND LOGIN.
2787  
2788  /**
2789   * Returns full login url.
2790   *
2791   * @return string login url
2792   */
2793  function get_login_url() {
2794      global $CFG;
2795  
2796      $url = "$CFG->wwwroot/login/index.php";
2797  
2798      if (!empty($CFG->loginhttps)) {
2799          $url = str_replace('http:', 'https:', $url);
2800      }
2801  
2802      return $url;
2803  }
2804  
2805  /**
2806   * This function checks that the current user is logged in and has the
2807   * required privileges
2808   *
2809   * This function checks that the current user is logged in, and optionally
2810   * whether they are allowed to be in a particular course and view a particular
2811   * course module.
2812   * If they are not logged in, then it redirects them to the site login unless
2813   * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
2814   * case they are automatically logged in as guests.
2815   * If $courseid is given and the user is not enrolled in that course then the
2816   * user is redirected to the course enrolment page.
2817   * If $cm is given and the course module is hidden and the user is not a teacher
2818   * in the course then the user is redirected to the course home page.
2819   *
2820   * When $cm parameter specified, this function sets page layout to 'module'.
2821   * You need to change it manually later if some other layout needed.
2822   *
2823   * @package    core_access
2824   * @category   access
2825   *
2826   * @param mixed $courseorid id of the course or course object
2827   * @param bool $autologinguest default true
2828   * @param object $cm course module object
2829   * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
2830   *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
2831   *             in order to keep redirects working properly. MDL-14495
2832   * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
2833   * @return mixed Void, exit, and die depending on path
2834   * @throws coding_exception
2835   * @throws require_login_exception
2836   */
2837  function require_login($courseorid = null, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false) {
2838      global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
2839  
2840      // Must not redirect when byteserving already started.
2841      if (!empty($_SERVER['HTTP_RANGE'])) {
2842          $preventredirect = true;
2843      }
2844  
2845      if (AJAX_SCRIPT) {
2846          // We cannot redirect for AJAX scripts either.
2847          $preventredirect = true;
2848      }
2849  
2850      // Setup global $COURSE, themes, language and locale.
2851      if (!empty($courseorid)) {
2852          if (is_object($courseorid)) {
2853              $course = $courseorid;
2854          } else if ($courseorid == SITEID) {
2855              $course = clone($SITE);
2856          } else {
2857              $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
2858          }
2859          if ($cm) {
2860              if ($cm->course != $course->id) {
2861                  throw new coding_exception('course and cm parameters in require_login() call do not match!!');
2862              }
2863              // Make sure we have a $cm from get_fast_modinfo as this contains activity access details.
2864              if (!($cm instanceof cm_info)) {
2865                  // Note: nearly all pages call get_fast_modinfo anyway and it does not make any
2866                  // db queries so this is not really a performance concern, however it is obviously
2867                  // better if you use get_fast_modinfo to get the cm before calling this.
2868                  $modinfo = get_fast_modinfo($course);
2869                  $cm = $modinfo->get_cm($cm->id);
2870              }
2871          }
2872      } else {
2873          // Do not touch global $COURSE via $PAGE->set_course(),
2874          // the reasons is we need to be able to call require_login() at any time!!
2875          $course = $SITE;
2876          if ($cm) {
2877              throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
2878          }
2879      }
2880  
2881      // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
2882      // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
2883      // risk leading the user back to the AJAX request URL.
2884      if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
2885          $setwantsurltome = false;
2886      }
2887  
2888      // Redirect to the login page if session has expired, only with dbsessions enabled (MDL-35029) to maintain current behaviour.
2889      if ((!isloggedin() or isguestuser()) && !empty($SESSION->has_timed_out) && !empty($CFG->dbsessions)) {
2890          if ($preventredirect) {
2891              throw new require_login_session_timeout_exception();
2892          } else {
2893              if ($setwantsurltome) {
2894                  $SESSION->wantsurl = qualified_me();
2895              }
2896              redirect(get_login_url());
2897          }
2898      }
2899  
2900      // If the user is not even logged in yet then make sure they are.
2901      if (!isloggedin()) {
2902          if ($autologinguest and !empty($CFG->guestloginbutton) and !empty($CFG->autologinguests)) {
2903              if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
2904                  // Misconfigured site guest, just redirect to login page.
2905                  redirect(get_login_url());
2906                  exit; // Never reached.
2907              }
2908              $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
2909              complete_user_login($guest);
2910              $USER->autologinguest = true;
2911              $SESSION->lang = $lang;
2912          } else {
2913              // NOTE: $USER->site check was obsoleted by session test cookie, $USER->confirmed test is in login/index.php.
2914              if ($preventredirect) {
2915                  throw new require_login_exception('You are not logged in');
2916              }
2917  
2918              if ($setwantsurltome) {
2919                  $SESSION->wantsurl = qualified_me();
2920              }
2921  
2922              $referer = get_local_referer(false);
2923              if (!empty($referer)) {
2924                  $SESSION->fromurl = $referer;
2925              }
2926              redirect(get_login_url());
2927              exit; // Never reached.
2928          }
2929      }
2930  
2931      // Loginas as redirection if needed.
2932      if ($course->id != SITEID and \core\session\manager::is_loggedinas()) {
2933          if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
2934              if ($USER->loginascontext->instanceid != $course->id) {
2935                  print_error('loginasonecourse', '', $CFG->wwwroot.'/course/view.php?id='.$USER->loginascontext->instanceid);
2936              }
2937          }
2938      }
2939  
2940      // Check whether the user should be changing password (but only if it is REALLY them).
2941      if (get_user_preferences('auth_forcepasswordchange') && !\core\session\manager::is_loggedinas()) {
2942          $userauth = get_auth_plugin($USER->auth);
2943          if ($userauth->can_change_password() and !$preventredirect) {
2944              if ($setwantsurltome) {
2945                  $SESSION->wantsurl = qualified_me();
2946              }
2947              if ($changeurl = $userauth->change_password_url()) {
2948                  // Use plugin custom url.
2949                  redirect($changeurl);
2950              } else {
2951                  // Use moodle internal method.
2952                  if (empty($CFG->loginhttps)) {
2953                      redirect($CFG->wwwroot .'/login/change_password.php');
2954                  } else {
2955                      $wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
2956                      redirect($wwwroot .'/login/change_password.php');
2957                  }
2958              }
2959          } else {
2960              print_error('nopasswordchangeforced', 'auth');
2961          }
2962      }
2963  
2964      // Check that the user account is properly set up.
2965      if (user_not_fully_set_up($USER)) {
2966          if ($preventredirect) {
2967              throw new require_login_exception('User not fully set-up');
2968          }
2969          if ($setwantsurltome) {
2970              $SESSION->wantsurl = qualified_me();
2971          }
2972          redirect($CFG->wwwroot .'/user/edit.php?id='. $USER->id .'&amp;course='. SITEID);
2973      }
2974  
2975      // Make sure the USER has a sesskey set up. Used for CSRF protection.
2976      sesskey();
2977  
2978      // Do not bother admins with any formalities.
2979      if (is_siteadmin()) {
2980          // Set the global $COURSE.
2981          if ($cm) {
2982              $PAGE->set_cm($cm, $course);
2983              $PAGE->set_pagelayout('incourse');
2984          } else if (!empty($courseorid)) {
2985              $PAGE->set_course($course);
2986          }
2987          // Set accesstime or the user will appear offline which messes up messaging.
2988          user_accesstime_log($course->id);
2989          return;
2990      }
2991  
2992      // Check that the user has agreed to a site policy if there is one - do not test in case of admins.
2993      if (!$USER->policyagreed and !is_siteadmin()) {
2994          if (!empty($CFG->sitepolicy) and !isguestuser()) {
2995              if ($preventredirect) {
2996                  throw new require_login_exception('Policy not agreed');
2997              }
2998              if ($setwantsurltome) {
2999                  $SESSION->wantsurl = qualified_me();
3000              }
3001              redirect($CFG->wwwroot .'/user/policy.php');
3002          } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
3003              if ($preventredirect) {
3004                  throw new require_login_exception('Policy not agreed');
3005              }
3006              if ($setwantsurltome) {
3007                  $SESSION->wantsurl = qualified_me();
3008              }
3009              redirect($CFG->wwwroot .'/user/policy.php');
3010          }
3011      }
3012  
3013      // Fetch the system context, the course context, and prefetch its child contexts.
3014      $sysctx = context_system::instance();
3015      $coursecontext = context_course::instance($course->id, MUST_EXIST);
3016      if ($cm) {
3017          $cmcontext = context_module::instance($cm->id, MUST_EXIST);
3018      } else {
3019          $cmcontext = null;
3020      }
3021  
3022      // If the site is currently under maintenance, then print a message.
3023      if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:config', $sysctx)) {
3024          if ($preventredirect) {
3025              throw new require_login_exception('Maintenance in progress');
3026          }
3027  
3028          print_maintenance_message();
3029      }
3030  
3031      // Make sure the course itself is not hidden.
3032      if ($course->id == SITEID) {
3033          // Frontpage can not be hidden.
3034      } else {
3035          if (is_role_switched($course->id)) {
3036              // When switching roles ignore the hidden flag - user had to be in course to do the switch.
3037          } else {
3038              if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
3039                  // Originally there was also test of parent category visibility, BUT is was very slow in complex queries
3040                  // involving "my courses" now it is also possible to simply hide all courses user is not enrolled in :-).
3041                  if ($preventredirect) {
3042                      throw new require_login_exception('Course is hidden');
3043                  }
3044                  $PAGE->set_context(null);
3045                  // We need to override the navigation URL as the course won't have been added to the navigation and thus
3046                  // the navigation will mess up when trying to find it.
3047                  navigation_node::override_active_url(new moodle_url('/'));
3048                  notice(get_string('coursehidden'), $CFG->wwwroot .'/');
3049              }
3050          }
3051      }
3052  
3053      // Is the user enrolled?
3054      if ($course->id == SITEID) {
3055          // Everybody is enrolled on the frontpage.
3056      } else {
3057          if (\core\session\manager::is_loggedinas()) {
3058              // Make sure the REAL person can access this course first.
3059              $realuser = \core\session\manager::get_realuser();
3060              if (!is_enrolled($coursecontext, $realuser->id, '', true) and
3061                  !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)) {
3062                  if ($preventredirect) {
3063                      throw new require_login_exception('Invalid course login-as access');
3064                  }
3065                  $PAGE->set_context(null);
3066                  echo $OUTPUT->header();
3067                  notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot .'/');
3068              }
3069          }
3070  
3071          $access = false;
3072  
3073          if (is_role_switched($course->id)) {
3074              // Ok, user had to be inside this course before the switch.
3075              $access = true;
3076  
3077          } else if (is_viewing($coursecontext, $USER)) {
3078              // Ok, no need to mess with enrol.
3079              $access = true;
3080  
3081          } else {
3082              if (isset($USER->enrol['enrolled'][$course->id])) {
3083                  if ($USER->enrol['enrolled'][$course->id] > time()) {
3084                      $access = true;
3085                      if (isset($USER->enrol['tempguest'][$course->id])) {
3086                          unset($USER->enrol['tempguest'][$course->id]);
3087                          remove_temp_course_roles($coursecontext);
3088                      }
3089                  } else {
3090                      // Expired.
3091                      unset($USER->enrol['enrolled'][$course->id]);
3092                  }
3093              }
3094              if (isset($USER->enrol['tempguest'][$course->id])) {
3095                  if ($USER->enrol['tempguest'][$course->id] == 0) {
3096                      $access = true;
3097                  } else if ($USER->enrol['tempguest'][$course->id] > time()) {
3098                      $access = true;
3099                  } else {
3100                      // Expired.
3101                      unset($USER->enrol['tempguest'][$course->id]);
3102                      remove_temp_course_roles($coursecontext);
3103                  }
3104              }
3105  
3106              if (!$access) {
3107                  // Cache not ok.
3108                  $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
3109                  if ($until !== false) {
3110                      // Active participants may always access, a timestamp in the future, 0 (always) or false.
3111                      if ($until == 0) {
3112                          $until = ENROL_MAX_TIMESTAMP;
3113                      }
3114                      $USER->enrol['enrolled'][$course->id] = $until;
3115                      $access = true;
3116  
3117                  } else {
3118                      $params = array('courseid' => $course->id, 'status' => ENROL_INSTANCE_ENABLED);
3119                      $instances = $DB->get_records('enrol', $params, 'sortorder, id ASC');
3120                      $enrols = enrol_get_plugins(true);
3121                      // First ask all enabled enrol instances in course if they want to auto enrol user.
3122                      foreach ($instances as $instance) {
3123                          if (!isset($enrols[$instance->enrol])) {
3124                              continue;
3125                          }
3126                          // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
3127                          $until = $enrols[$instance->enrol]->try_autoenrol($instance);
3128                          if ($until !== false) {
3129                              if ($until == 0) {
3130                                  $until = ENROL_MAX_TIMESTAMP;
3131                              }
3132                              $USER->enrol['enrolled'][$course->id] = $until;
3133                              $access = true;
3134                              break;
3135                          }
3136                      }
3137                      // If not enrolled yet try to gain temporary guest access.
3138                      if (!$access) {
3139                          foreach ($instances as $instance) {
3140                              if (!isset($enrols[$instance->enrol])) {
3141                                  continue;
3142                              }
3143                              // Get a duration for the guest access, a timestamp in the future or false.
3144                              $until = $enrols[$instance->enrol]->try_guestaccess($instance);
3145                              if ($until !== false and $until > time()) {
3146                                  $USER->enrol['tempguest'][$course->id] = $until;
3147                                  $access = true;
3148                                  break;
3149                              }
3150                          }
3151                      }
3152                  }
3153              }
3154          }
3155  
3156          if (!$access) {
3157              if ($preventredirect) {
3158                  throw new require_login_exception('Not enrolled');
3159              }
3160              if ($setwantsurltome) {
3161                  $SESSION->wantsurl = qualified_me();
3162              }
3163              redirect($CFG->wwwroot .'/enrol/index.php?id='. $course->id);
3164          }
3165      }
3166  
3167      // Set the global $COURSE.
3168      // TODO MDL-49434: setting current course/cm should be after the check $cm->uservisible .
3169      if ($cm) {
3170          $PAGE->set_cm($cm, $course);
3171          $PAGE->set_pagelayout('incourse');
3172      } else if (!empty($courseorid)) {
3173          $PAGE->set_course($course);
3174      }
3175  
3176      // Check visibility of activity to current user; includes visible flag, conditional availability, etc.
3177      if ($cm && !$cm->uservisible) {
3178          if ($preventredirect) {
3179              throw new require_login_exception('Activity is hidden');
3180          }
3181          if ($course->id != SITEID) {
3182              $url = new moodle_url('/course/view.php', array('id' => $course->id));
3183          } else {
3184              $url = new moodle_url('/');
3185          }
3186          redirect($url, get_string('activityiscurrentlyhidden'));
3187      }
3188  
3189      // Finally access granted, update lastaccess times.
3190      user_accesstime_log($course->id);
3191  }
3192  
3193  
3194  /**
3195   * This function just makes sure a user is logged out.
3196   *
3197   * @package    core_access
3198   * @category   access
3199   */
3200  function require_logout() {
3201      global $USER, $DB;
3202  
3203      if (!isloggedin()) {
3204          // This should not happen often, no need for hooks or events here.
3205          \core\session\manager::terminate_current();
3206          return;
3207      }
3208  
3209      // Execute hooks before action.
3210      $authplugins = array();
3211      $authsequence = get_enabled_auth_plugins();
3212      foreach ($authsequence as $authname) {
3213          $authplugins[$authname] = get_auth_plugin($authname);
3214          $authplugins[$authname]->prelogout_hook();
3215      }
3216  
3217      // Store info that gets removed during logout.
3218      $sid = session_id();
3219      $event = \core\event\user_loggedout::create(
3220          array(
3221              'userid' => $USER->id,
3222              'objectid' => $USER->id,
3223              'other' => array('sessionid' => $sid),
3224          )
3225      );
3226      if ($session = $DB->get_record('sessions', array('sid'=>$sid))) {
3227          $event->add_record_snapshot('sessions', $session);
3228      }
3229  
3230      // Clone of $USER object to be used by auth plugins.
3231      $user = fullclone($USER);
3232  
3233      // Delete session record and drop $_SESSION content.
3234      \core\session\manager::terminate_current();
3235  
3236      // Trigger event AFTER action.
3237      $event->trigger();
3238  
3239      // Hook to execute auth plugins redirection after event trigger.
3240      foreach ($authplugins as $authplugin) {
3241          $authplugin->postlogout_hook($user);
3242      }
3243  }
3244  
3245  /**
3246   * Weaker version of require_login()
3247   *
3248   * This is a weaker version of {@link require_login()} which only requires login
3249   * when called from within a course rather than the site page, unless
3250   * the forcelogin option is turned on.
3251   * @see require_login()
3252   *
3253   * @package    core_access
3254   * @category   access
3255   *
3256   * @param mixed $courseorid The course object or id in question
3257   * @param bool $autologinguest Allow autologin guests if that is wanted
3258   * @param object $cm Course activity module if known
3259   * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
3260   *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
3261   *             in order to keep redirects working properly. MDL-14495
3262   * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
3263   * @return void
3264   * @throws coding_exception
3265   */
3266  function require_course_login($courseorid, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false) {
3267      global $CFG, $PAGE, $SITE;
3268      $issite = ((is_object($courseorid) and $courseorid->id == SITEID)
3269            or (!is_object($courseorid) and $courseorid == SITEID));
3270      if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
3271          // Note: nearly all pages call get_fast_modinfo anyway and it does not make any
3272          // db queries so this is not really a performance concern, however it is obviously
3273          // better if you use get_fast_modinfo to get the cm before calling this.
3274          if (is_object($courseorid)) {
3275              $course = $courseorid;
3276          } else {
3277              $course = clone($SITE);
3278          }
3279          $modinfo = get_fast_modinfo($course);
3280          $cm = $modinfo->get_cm($cm->id);
3281      }
3282      if (!empty($CFG->forcelogin)) {
3283          // Login required for both SITE and courses.
3284          require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3285  
3286      } else if ($issite && !empty($cm) and !$cm->uservisible) {
3287          // Always login for hidden activities.
3288          require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3289  
3290      } else if ($issite) {
3291          // Login for SITE not required.
3292          // We still need to instatiate PAGE vars properly so that things that rely on it like navigation function correctly.
3293          if (!empty($courseorid)) {
3294              if (is_object($courseorid)) {
3295                  $course = $courseorid;
3296              } else {
3297                  $course = clone $SITE;
3298              }
3299              if ($cm) {
3300                  if ($cm->course != $course->id) {
3301                      throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
3302                  }
3303                  $PAGE->set_cm($cm, $course);
3304                  $PAGE->set_pagelayout('incourse');
3305              } else {
3306                  $PAGE->set_course($course);
3307              }
3308          } else {
3309              // If $PAGE->course, and hence $PAGE->context, have not already been set up properly, set them up now.
3310              $PAGE->set_course($PAGE->course);
3311          }
3312          user_accesstime_log(SITEID);
3313          return;
3314  
3315      } else {
3316          // Course login always required.
3317          require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
3318      }
3319  }
3320  
3321  /**
3322   * Require key login. Function terminates with error if key not found or incorrect.
3323   *
3324   * @uses NO_MOODLE_COOKIES
3325   * @uses PARAM_ALPHANUM
3326   * @param string $script unique script identifier
3327   * @param int $instance optional instance id
3328   * @return int Instance ID
3329   */
3330  function require_user_key_login($script, $instance=null) {
3331      global $DB;
3332  
3333      if (!NO_MOODLE_COOKIES) {
3334          print_error('sessioncookiesdisable');
3335      }
3336  
3337      // Extra safety.
3338      \core\session\manager::write_close();
3339  
3340      $keyvalue = required_param('key', PARAM_ALPHANUM);
3341  
3342      if (!$key = $DB->get_record('user_private_key', array('script' => $script, 'value' => $keyvalue, 'instance' => $instance))) {
3343          print_error('invalidkey');
3344      }
3345  
3346      if (!empty($key->validuntil) and $key->validuntil < time()) {
3347          print_error('expiredkey');
3348      }
3349  
3350      if ($key->iprestriction) {
3351          $remoteaddr = getremoteaddr(null);
3352          if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
3353              print_error('ipmismatch');
3354          }
3355      }
3356  
3357      if (!$user = $DB->get_record('user', array('id' => $key->userid))) {
3358          print_error('invaliduserid');
3359      }
3360  
3361      // Emulate normal session.
3362      enrol_check_plugins($user);
3363      \core\session\manager::set_user($user);
3364  
3365      // Note we are not using normal login.
3366      if (!defined('USER_KEY_LOGIN')) {
3367          define('USER_KEY_LOGIN', true);
3368      }
3369  
3370      // Return instance id - it might be empty.
3371      return $key->instance;
3372  }
3373  
3374  /**
3375   * Creates a new private user access key.
3376   *
3377   * @param string $script unique target identifier
3378   * @param int $userid
3379   * @param int $instance optional instance id
3380   * @param string $iprestriction optional ip restricted access
3381   * @param timestamp $validuntil key valid only until given data
3382   * @return string access key value
3383   */
3384  function create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3385      global $DB;
3386  
3387      $key = new stdClass();
3388      $key->script        = $script;
3389      $key->userid        = $userid;
3390      $key->instance      = $instance;
3391      $key->iprestriction = $iprestriction;
3392      $key->validuntil    = $validuntil;
3393      $key->timecreated   = time();
3394  
3395      // Something long and unique.
3396      $key->value         = md5($userid.'_'.time().random_string(40));
3397      while ($DB->record_exists('user_private_key', array('value' => $key->value))) {
3398          // Must be unique.
3399          $key->value     = md5($userid.'_'.time().random_string(40));
3400      }
3401      $DB->insert_record('user_private_key', $key);
3402      return $key->value;
3403  }
3404  
3405  /**
3406   * Delete the user's new private user access keys for a particular script.
3407   *
3408   * @param string $script unique target identifier
3409   * @param int $userid
3410   * @return void
3411   */
3412  function delete_user_key($script, $userid) {
3413      global $DB;
3414      $DB->delete_records('user_private_key', array('script' => $script, 'userid' => $userid));
3415  }
3416  
3417  /**
3418   * Gets a private user access key (and creates one if one doesn't exist).
3419   *
3420   * @param string $script unique target identifier
3421   * @param int $userid
3422   * @param int $instance optional instance id
3423   * @param string $iprestriction optional ip restricted access
3424   * @param timestamp $validuntil key valid only until given data
3425   * @return string access key value
3426   */
3427  function get_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null) {
3428      global $DB;
3429  
3430      if ($key = $DB->get_record('user_private_key', array('script' => $script, 'userid' => $userid,
3431                                                           'instance' => $instance, 'iprestriction' => $iprestriction,
3432                                                           'validuntil' => $validuntil))) {
3433          return $key->value;
3434      } else {
3435          return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
3436      }
3437  }
3438  
3439  
3440  /**
3441   * Modify the user table by setting the currently logged in user's last login to now.
3442   *
3443   * @return bool Always returns true
3444   */
3445  function update_user_login_times() {
3446      global $USER, $DB;
3447  
3448      if (isguestuser()) {
3449          // Do not update guest access times/ips for performance.
3450          return true;
3451      }
3452  
3453      $now = time();
3454  
3455      $user = new stdClass();
3456      $user->id = $USER->id;
3457  
3458      // Make sure all users that logged in have some firstaccess.
3459      if ($USER->firstaccess == 0) {
3460          $USER->firstaccess = $user->firstaccess = $now;
3461      }
3462  
3463      // Store the previous current as lastlogin.
3464      $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
3465  
3466      $USER->currentlogin = $user->currentlogin = $now;
3467  
3468      // Function user_accesstime_log() may not update immediately, better do it here.
3469      $USER->lastaccess = $user->lastaccess = $now;
3470      $USER->lastip = $user->lastip = getremoteaddr();
3471  
3472      // Note: do not call user_update_user() here because this is part of the login process,
3473      //       the login event means that these fields were updated.
3474      $DB->update_record('user', $user);
3475      return true;
3476  }
3477  
3478  /**
3479   * Determines if a user has completed setting up their account.
3480   *
3481   * @param stdClass $user A {@link $USER} object to test for the existence of a valid name and email
3482   * @return bool
3483   */
3484  function user_not_fully_set_up($user) {
3485      if (isguestuser($user)) {
3486          return false;
3487      }
3488      return (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user));
3489  }
3490  
3491  /**
3492   * Check whether the user has exceeded the bounce threshold
3493   *
3494   * @param stdClass $user A {@link $USER} object
3495   * @return bool true => User has exceeded bounce threshold
3496   */
3497  function over_bounce_threshold($user) {
3498      global $CFG, $DB;
3499  
3500      if (empty($CFG->handlebounces)) {
3501          return false;
3502      }
3503  
3504      if (empty($user->id)) {
3505          // No real (DB) user, nothing to do here.
3506          return false;
3507      }
3508  
3509      // Set sensible defaults.
3510      if (empty($CFG->minbounces)) {
3511          $CFG->minbounces = 10;
3512      }
3513      if (empty($CFG->bounceratio)) {
3514          $CFG->bounceratio = .20;
3515      }
3516      $bouncecount = 0;
3517      $sendcount = 0;
3518      if ($bounce = $DB->get_record('user_preferences', array ('userid' => $user->id, 'name' => 'email_bounce_count'))) {
3519          $bouncecount = $bounce->value;
3520      }
3521      if ($send = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_send_count'))) {
3522          $sendcount = $send->value;
3523      }
3524      return ($bouncecount >= $CFG->minbounces && $bouncecount/$sendcount >= $CFG->bounceratio);
3525  }
3526  
3527  /**
3528   * Used to increment or reset email sent count
3529   *
3530   * @param stdClass $user object containing an id
3531   * @param bool $reset will reset the count to 0
3532   * @return void
3533   */
3534  function set_send_count($user, $reset=false) {
3535      global $DB;
3536  
3537      if (empty($user->id)) {
3538          // No real (DB) user, nothing to do here.
3539          return;
3540      }
3541  
3542      if ($pref = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_send_count'))) {
3543          $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3544          $DB->update_record('user_preferences', $pref);
3545      } else if (!empty($reset)) {
3546          // If it's not there and we're resetting, don't bother. Make a new one.
3547          $pref = new stdClass();
3548          $pref->name   = 'email_send_count';
3549          $pref->value  = 1;
3550          $pref->userid = $user->id;
3551          $DB->insert_record('user_preferences', $pref, false);
3552      }
3553  }
3554  
3555  /**
3556   * Increment or reset user's email bounce count
3557   *
3558   * @param stdClass $user object containing an id
3559   * @param bool $reset will reset the count to 0
3560   */
3561  function set_bounce_count($user, $reset=false) {
3562      global $DB;
3563  
3564      if ($pref = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_bounce_count'))) {
3565          $pref->value = (!empty($reset)) ? 0 : $pref->value+1;
3566          $DB->update_record('user_preferences', $pref);
3567      } else if (!empty($reset)) {
3568          // If it's not there and we're resetting, don't bother. Make a new one.
3569          $pref = new stdClass();
3570          $pref->name   = 'email_bounce_count';
3571          $pref->value  = 1;
3572          $pref->userid = $user->id;
3573          $DB->insert_record('user_preferences', $pref, false);
3574      }
3575  }
3576  
3577  /**
3578   * Determines if the logged in user is currently moving an activity
3579   *
3580   * @param int $courseid The id of the course being tested
3581   * @return bool
3582   */
3583  function ismoving($courseid) {
3584      global $USER;
3585  
3586      if (!empty($USER->activitycopy)) {
3587          return ($USER->activitycopycourse == $courseid);
3588      }
3589      return false;
3590  }
3591  
3592  /**
3593   * Returns a persons full name
3594   *
3595   * Given an object containing all of the users name values, this function returns a string with the full name of the person.
3596   * The result may depend on system settings or language.  'override' will force both names to be used even if system settings
3597   * specify one.
3598   *
3599   * @param stdClass $user A {@link $USER} object to get full name of.
3600   * @param bool $override If true then the name will be firstname followed by lastname rather than adhering to fullnamedisplay.
3601   * @return string
3602   */
3603  function fullname($user, $override=false) {
3604      global $CFG, $SESSION;
3605  
3606      if (!isset($user->firstname) and !isset($user->lastname)) {
3607          return '';
3608      }
3609  
3610      // Get all of the name fields.
3611      $allnames = get_all_user_name_fields();
3612      if ($CFG->debugdeveloper) {
3613          foreach ($allnames as $allname) {
3614              if (!array_key_exists($allname, $user)) {
3615                  // If all the user name fields are not set in the user object, then notify the programmer that it needs to be fixed.
3616                  debugging('You need to update your sql to include additional name fields in the user object.', DEBUG_DEVELOPER);
3617                  // Message has been sent, no point in sending the message multiple times.
3618                  break;
3619              }
3620          }
3621      }
3622  
3623      if (!$override) {
3624          if (!empty($CFG->forcefirstname)) {
3625              $user->firstname = $CFG->forcefirstname;
3626          }
3627          if (!empty($CFG->forcelastname)) {
3628              $user->lastname = $CFG->forcelastname;
3629          }
3630      }
3631  
3632      if (!empty($SESSION->fullnamedisplay)) {
3633          $CFG->fullnamedisplay = $SESSION->fullnamedisplay;
3634      }
3635  
3636      $template = null;
3637      // If the fullnamedisplay setting is available, set the template to that.
3638      if (isset($CFG->fullnamedisplay)) {
3639          $template = $CFG->fullnamedisplay;
3640      }
3641      // If the template is empty, or set to language, return the language string.
3642      if ((empty($template) || $template == 'language') && !$override) {
3643          return get_string('fullnamedisplay', null, $user);
3644      }
3645  
3646      // Check to see if we are displaying according to the alternative full name format.
3647      if ($override) {
3648          if (empty($CFG->alternativefullnameformat) || $CFG->alternativefullnameformat == 'language') {
3649              // Default to show just the user names according to the fullnamedisplay string.
3650              return get_string('fullnamedisplay', null, $user);
3651          } else {
3652              // If the override is true, then change the template to use the complete name.
3653              $template = $CFG->alternativefullnameformat;
3654          }
3655      }
3656  
3657      $requirednames = array();
3658      // With each name, see if it is in the display name template, and add it to the required names array if it is.
3659      foreach ($allnames as $allname) {
3660          if (strpos($template, $allname) !== false) {
3661              $requirednames[] = $allname;
3662          }
3663      }
3664  
3665      $displayname = $template;
3666      // Switch in the actual data into the template.
3667      foreach ($requirednames as $altname) {
3668          if (isset($user->$altname)) {
3669              // Using empty() on the below if statement causes breakages.
3670              if ((string)$user->$altname == '') {
3671                  $displayname = str_replace($altname, 'EMPTY', $displayname);
3672              } else {
3673                  $displayname = str_replace($altname, $user->$altname, $displayname);
3674              }
3675          } else {
3676              $displayname = str_replace($altname, 'EMPTY', $displayname);
3677          }
3678      }
3679      // Tidy up any misc. characters (Not perfect, but gets most characters).
3680      // Don't remove the "u" at the end of the first expression unless you want garbled characters when combining hiragana or
3681      // katakana and parenthesis.
3682      $patterns = array();
3683      // This regular expression replacement is to fix problems such as 'James () Kirk' Where 'Tiberius' (middlename) has not been
3684      // filled in by a user.
3685      // The special characters are Japanese brackets that are common enough to make allowances for them (not covered by :punct:).
3686      $patterns[] = '/[[:punct:]「」]*EMPTY[[:punct:]「」]*/u';
3687      // This regular expression is to remove any double spaces in the display name.
3688      $patterns[] = '/\s{2,}/u';
3689      foreach ($patterns as $pattern) {
3690          $displayname = preg_replace($pattern, ' ', $displayname);
3691      }
3692  
3693      // Trimming $displayname will help the next check to ensure that we don't have a display name with spaces.
3694      $displayname = trim($displayname);
3695      if (empty($displayname)) {
3696          // Going with just the first name if no alternate fields are filled out. May be changed later depending on what
3697          // people in general feel is a good setting to fall back on.
3698          $displayname = $user->firstname;
3699      }
3700      return $displayname;
3701  }
3702  
3703  /**
3704   * A centralised location for the all name fields. Returns an array / sql string snippet.
3705   *
3706   * @param bool $returnsql True for an sql select field snippet.
3707   * @param string $tableprefix table query prefix to use in front of each field.
3708   * @param string $prefix prefix added to the name fields e.g. authorfirstname.
3709   * @param string $fieldprefix sql field prefix e.g. id AS userid.
3710   * @param bool $order moves firstname and lastname to the top of the array / start of the string.
3711   * @return array|string All name fields.
3712   */
3713  function get_all_user_name_fields($returnsql = false, $tableprefix = null, $prefix = null, $fieldprefix = null, $order = false) {
3714      // This array is provided in this order because when called by fullname() (above) if firstname is before
3715      // firstnamephonetic str_replace() will change the wrong placeholder.
3716      $alternatenames = array('firstnamephonetic' => 'firstnamephonetic',
3717                              'lastnamephonetic' => 'lastnamephonetic',
3718                              'middlename' => 'middlename',
3719                              'alternatename' => 'alternatename',
3720                              'firstname' => 'firstname',
3721                              'lastname' => 'lastname');
3722  
3723      // Let's add a prefix to the array of user name fields if provided.
3724      if ($prefix) {
3725          foreach ($alternatenames as $key => $altname) {
3726              $alternatenames[$key] = $prefix . $altname;
3727          }
3728      }
3729  
3730      // If we want the end result to have firstname and lastname at the front / top of the result.
3731      if ($order) {
3732          // Move the last two elements (firstname, lastname) off the array and put them at the top.
3733          for ($i = 0; $i < 2; $i++) {
3734              // Get the last element.
3735              $lastelement = end($alternatenames);
3736              // Remove it from the array.
3737              unset($alternatenames[$lastelement]);
3738              // Put the element back on the top of the array.
3739              $alternatenames = array_merge(array($lastelement => $lastelement), $alternatenames);
3740          }
3741      }
3742  
3743      // Create an sql field snippet if requested.
3744      if ($returnsql) {
3745          if ($tableprefix) {
3746              if ($fieldprefix) {
3747                  foreach ($alternatenames as $key => $altname) {
3748                      $alternatenames[$key] = $tableprefix . '.' . $altname . ' AS ' . $fieldprefix . $altname;
3749                  }
3750              } else {
3751                  foreach ($alternatenames as $key => $altname) {
3752                      $alternatenames[$key] = $tableprefix . '.' . $altname;
3753                  }
3754              }
3755          }
3756          $alternatenames = implode(',', $alternatenames);
3757      }
3758      return $alternatenames;
3759  }
3760  
3761  /**
3762   * Reduces lines of duplicated code for getting user name fields.
3763   *
3764   * See also {@link user_picture::unalias()}
3765   *
3766   * @param object $addtoobject Object to add user name fields to.
3767   * @param object $secondobject Object that contains user name field information.
3768   * @param string $prefix prefix to be added to all fields (including $additionalfields) e.g. authorfirstname.
3769   * @param array $additionalfields Additional fields to be matched with data in the second object.
3770   * The key can be set to the user table field name.
3771   * @return object User name fields.
3772   */
3773  function username_load_fields_from_object($addtoobject, $secondobject, $prefix = null, $additionalfields = null) {
3774      $fields = get_all_user_name_fields(false, null, $prefix);
3775      if ($additionalfields) {
3776          // Additional fields can specify their own 'alias' such as 'id' => 'userid'. This checks to see if
3777          // the key is a number and then sets the key to the array value.
3778          foreach ($additionalfields as $key => $value) {
3779              if (is_numeric($key)) {
3780                  $additionalfields[$value] = $prefix . $value;
3781                  unset($additionalfields[$key]);
3782              } else {
3783                  $additionalfields[$key] = $prefix . $value;
3784              }
3785          }
3786          $fields = array_merge($fields, $additionalfields);
3787      }
3788      foreach ($fields as $key => $field) {
3789          // Important that we have all of the user name fields present in the object that we are sending back.
3790          $addtoobject->$key = '';
3791          if (isset($secondobject->$field)) {
3792              $addtoobject->$key = $secondobject->$field;
3793          }
3794      }
3795      return $addtoobject;
3796  }
3797  
3798  /**
3799   * Returns an array of values in order of occurance in a provided string.
3800   * The key in the result is the character postion in the string.
3801   *
3802   * @param array $values Values to be found in the string format
3803   * @param string $stringformat The string which may contain values being searched for.
3804   * @return array An array of values in order according to placement in the string format.
3805   */
3806  function order_in_string($values, $stringformat) {
3807      $valuearray = array();
3808      foreach ($values as $value) {
3809          $pattern = "/$value\b/";
3810          // Using preg_match as strpos() may match values that are similar e.g. firstname and firstnamephonetic.
3811          if (preg_match($pattern, $stringformat)) {
3812              $replacement = "thing";
3813              // Replace the value with something more unique to ensure we get the right position when using strpos().
3814              $newformat = preg_replace($pattern, $replacement, $stringformat);
3815              $position = strpos($newformat, $replacement);
3816              $valuearray[$position] = $value;
3817          }
3818      }
3819      ksort($valuearray);
3820      return $valuearray;
3821  }
3822  
3823  /**
3824   * Checks if current user is shown any extra fields when listing users.
3825   *
3826   * @param object $context Context
3827   * @param array $already Array of fields that we're going to show anyway
3828   *   so don't bother listing them
3829   * @return array Array of field names from user table, not including anything
3830   *   listed in $already
3831   */
3832  function get_extra_user_fields($context, $already = array()) {
3833      global $CFG;
3834  
3835      // Only users with permission get the extra fields.
3836      if (!has_capability('moodle/site:viewuseridentity', $context)) {
3837          return array();
3838      }
3839  
3840      // Split showuseridentity on comma.
3841      if (empty($CFG->showuseridentity)) {
3842          // Explode gives wrong result with empty string.
3843          $extra = array();
3844      } else {
3845          $extra =  explode(',', $CFG->showuseridentity);
3846      }
3847      $renumber = false;
3848      foreach ($extra as $key => $field) {
3849          if (in_array($field, $already)) {
3850              unset($extra[$key]);
3851              $renumber = true;
3852          }
3853      }
3854      if ($renumber) {
3855          // For consistency, if entries are removed from array, renumber it
3856          // so they are numbered as you would expect.
3857          $extra = array_merge($extra);
3858      }
3859      return $extra;
3860  }
3861  
3862  /**
3863   * If the current user is to be shown extra user fields when listing or
3864   * selecting users, returns a string suitable for including in an SQL select
3865   * clause to retrieve those fields.
3866   *
3867   * @param context $context Context
3868   * @param string $alias Alias of user table, e.g. 'u' (default none)
3869   * @param string $prefix Prefix for field names using AS, e.g. 'u_' (default none)
3870   * @param array $already Array of fields that we're going to include anyway so don't list them (default none)
3871   * @return string Partial SQL select clause, beginning with comma, for example ',u.idnumber,u.department' unless it is blank
3872   */
3873  function get_extra_user_fields_sql($context, $alias='', $prefix='', $already = array()) {
3874      $fields = get_extra_user_fields($context, $already);
3875      $result = '';
3876      // Add punctuation for alias.
3877      if ($alias !== '') {
3878          $alias .= '.';
3879      }
3880      foreach ($fields as $field) {
3881          $result .= ', ' . $alias . $field;
3882          if ($prefix) {
3883              $result .= ' AS ' . $prefix . $field;
3884          }
3885      }
3886      return $result;
3887  }
3888  
3889  /**
3890   * Returns the display name of a field in the user table. Works for most fields that are commonly displayed to users.
3891   * @param string $field Field name, e.g. 'phone1'
3892   * @return string Text description taken from language file, e.g. 'Phone number'
3893   */
3894  function get_user_field_name($field) {
3895      // Some fields have language strings which are not the same as field name.
3896      switch ($field) {
3897          case 'phone1' : {
3898              return get_string('phone');
3899          }
3900          case 'url' : {
3901              return get_string('webpage');
3902          }
3903          case 'icq' : {
3904              return get_string('icqnumber');
3905          }
3906          case 'skype' : {
3907              return get_string('skypeid');
3908          }
3909          case 'aim' : {
3910              return get_string('aimid');
3911          }
3912          case 'yahoo' : {
3913              return get_string('yahooid');
3914          }
3915          case 'msn' : {
3916              return get_string('msnid');
3917          }
3918      }
3919      // Otherwise just use the same lang string.
3920      return get_string($field);
3921  }
3922  
3923  /**
3924   * Returns whether a given authentication plugin exists.
3925   *
3926   * @param string $auth Form of authentication to check for. Defaults to the global setting in {@link $CFG}.
3927   * @return boolean Whether the plugin is available.
3928   */
3929  function exists_auth_plugin($auth) {
3930      global $CFG;
3931  
3932      if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
3933          return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
3934      }
3935      return false;
3936  }
3937  
3938  /**
3939   * Checks if a given plugin is in the list of enabled authentication plugins.
3940   *
3941   * @param string $auth Authentication plugin.
3942   * @return boolean Whether the plugin is enabled.
3943   */
3944  function is_enabled_auth($auth) {
3945      if (empty($auth)) {
3946          return false;
3947      }
3948  
3949      $enabled = get_enabled_auth_plugins();
3950  
3951      return in_array($auth, $enabled);
3952  }
3953  
3954  /**
3955   * Returns an authentication plugin instance.
3956   *
3957   * @param string $auth name of authentication plugin
3958   * @return auth_plugin_base An instance of the required authentication plugin.
3959   */
3960  function get_auth_plugin($auth) {
3961      global $CFG;
3962  
3963      // Check the plugin exists first.
3964      if (! exists_auth_plugin($auth)) {
3965          print_error('authpluginnotfound', 'debug', '', $auth);
3966      }
3967  
3968      // Return auth plugin instance.
3969      require_once("{$CFG->dirroot}/auth/$auth/auth.php");
3970      $class = "auth_plugin_$auth";
3971      return new $class;
3972  }
3973  
3974  /**
3975   * Returns array of active auth plugins.
3976   *
3977   * @param bool $fix fix $CFG->auth if needed
3978   * @return array
3979   */
3980  function get_enabled_auth_plugins($fix=false) {
3981      global $CFG;
3982  
3983      $default = array('manual', 'nologin');
3984  
3985      if (empty($CFG->auth)) {
3986          $auths = array();
3987      } else {
3988          $auths = explode(',', $CFG->auth);
3989      }
3990  
3991      if ($fix) {
3992          $auths = array_unique($auths);
3993          foreach ($auths as $k => $authname) {
3994              if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
3995                  unset($auths[$k]);
3996              }
3997          }
3998          $newconfig = implode(',', $auths);
3999          if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
4000              set_config('auth', $newconfig);
4001          }
4002      }
4003  
4004      return (array_merge($default, $auths));
4005  }
4006  
4007  /**
4008   * Returns true if an internal authentication method is being used.
4009   * if method not specified then, global default is assumed
4010   *
4011   * @param string $auth Form of authentication required
4012   * @return bool
4013   */
4014  function is_internal_auth($auth) {
4015      // Throws error if bad $auth.
4016      $authplugin = get_auth_plugin($auth);
4017      return $authplugin->is_internal();
4018  }
4019  
4020  /**
4021   * Returns true if the user is a 'restored' one.
4022   *
4023   * Used in the login process to inform the user and allow him/her to reset the password
4024   *
4025   * @param string $username username to be checked
4026   * @return bool
4027   */
4028  function is_restored_user($username) {
4029      global $CFG, $DB;
4030  
4031      return $DB->record_exists('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id, 'password' => 'restored'));
4032  }
4033  
4034  /**
4035   * Returns an array of user fields
4036   *
4037   * @return array User field/column names
4038   */
4039  function get_user_fieldnames() {
4040      global $DB;
4041  
4042      $fieldarray = $DB->get_columns('user');
4043      unset($fieldarray['id']);
4044      $fieldarray = array_keys($fieldarray);
4045  
4046      return $fieldarray;
4047  }
4048  
4049  /**
4050   * Creates a bare-bones user record
4051   *
4052   * @todo Outline auth types and provide code example
4053   *
4054   * @param string $username New user's username to add to record
4055   * @param string $password New user's password to add to record
4056   * @param string $auth Form of authentication required
4057   * @return stdClass A complete user object
4058   */
4059  function create_user_record($username, $password, $auth = 'manual') {
4060      global $CFG, $DB;
4061      require_once($CFG->dirroot.'/user/profile/lib.php');
4062      require_once($CFG->dirroot.'/user/lib.php');
4063  
4064      // Just in case check text case.
4065      $username = trim(core_text::strtolower($username));
4066  
4067      $authplugin = get_auth_plugin($auth);
4068      $customfields = $authplugin->get_custom_user_profile_fields();
4069      $newuser = new stdClass();
4070      if ($newinfo = $authplugin->get_userinfo($username)) {
4071          $newinfo = truncate_userinfo($newinfo);
4072          foreach ($newinfo as $key => $value) {
4073              if (in_array($key, $authplugin->userfields) || (in_array($key, $customfields))) {
4074                  $newuser->$key = $value;
4075              }
4076          }
4077      }
4078  
4079      if (!empty($newuser->email)) {
4080          if (email_is_not_allowed($newuser->email)) {
4081              unset($newuser->email);
4082          }
4083      }
4084  
4085      if (!isset($newuser->city)) {
4086          $newuser->city = '';
4087      }
4088  
4089      $newuser->auth = $auth;
4090      $newuser->username = $username;
4091  
4092      // Fix for MDL-8480
4093      // user CFG lang for user if $newuser->lang is empty
4094      // or $user->lang is not an installed language.
4095      if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
4096          $newuser->lang = $CFG->lang;
4097      }
4098      $newuser->confirmed = 1;
4099      $newuser->lastip = getremoteaddr();
4100      $newuser->timecreated = time();
4101      $newuser->timemodified = $newuser->timecreated;
4102      $newuser->mnethostid = $CFG->mnet_localhost_id;
4103  
4104      $newuser->id = user_create_user($newuser, false, false);
4105  
4106      // Save user profile data.
4107      profile_save_data($newuser);
4108  
4109      $user = get_complete_user_data('id', $newuser->id);
4110      if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})) {
4111          set_user_preference('auth_forcepasswordchange', 1, $user);
4112      }
4113      // Set the password.
4114      update_internal_user_password($user, $password);
4115  
4116      // Trigger event.
4117      \core\event\user_created::create_from_userid($newuser->id)->trigger();
4118  
4119      return $user;
4120  }
4121  
4122  /**
4123   * Will update a local user record from an external source (MNET users can not be updated using this method!).
4124   *
4125   * @param string $username user's username to update the record
4126   * @return stdClass A complete user object
4127   */
4128  function update_user_record($username) {
4129      global $DB, $CFG;
4130      // Just in case check text case.
4131      $username = trim(core_text::strtolower($username));
4132  
4133      $oldinfo = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id), '*', MUST_EXIST);
4134      return update_user_record_by_id($oldinfo->id);
4135  }
4136  
4137  /**
4138   * Will update a local user record from an external source (MNET users can not be updated using this method!).
4139   *
4140   * @param int $id user id
4141   * @return stdClass A complete user object
4142   */
4143  function update_user_record_by_id($id) {
4144      global $DB, $CFG;
4145      require_once($CFG->dirroot."/user/profile/lib.php");
4146      require_once($CFG->dirroot.'/user/lib.php');
4147  
4148      $params = array('mnethostid' => $CFG->mnet_localhost_id, 'id' => $id, 'deleted' => 0);
4149      $oldinfo = $DB->get_record('user', $params, '*', MUST_EXIST);
4150  
4151      $newuser = array();
4152      $userauth = get_auth_plugin($oldinfo->auth);
4153  
4154      if ($newinfo = $userauth->get_userinfo($oldinfo->username)) {
4155          $newinfo = truncate_userinfo($newinfo);
4156          $customfields = $userauth->get_custom_user_profile_fields();
4157  
4158          foreach ($newinfo as $key => $value) {
4159              $iscustom = in_array($key, $customfields);
4160              if (!$iscustom) {
4161                  $key = strtolower($key);
4162              }
4163              if ((!property_exists($oldinfo, $key) && !$iscustom) or $key === 'username' or $key === 'id'
4164                      or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted') {
4165                  // Unknown or must not be changed.
4166                  continue;
4167              }
4168              $confval = $userauth->config->{'field_updatelocal_' . $key};
4169              $lockval = $userauth->config->{'field_lock_' . $key};
4170              if (empty($confval) || empty($lockval)) {
4171                  continue;
4172              }
4173              if ($confval === 'onlogin') {
4174                  // MDL-4207 Don't overwrite modified user profile values with
4175                  // empty LDAP values when 'unlocked if empty' is set. The purpose
4176                  // of the setting 'unlocked if empty' is to allow the user to fill
4177                  // in a value for the selected field _if LDAP is giving
4178                  // nothing_ for this field. Thus it makes sense to let this value
4179                  // stand in until LDAP is giving a value for this field.
4180                  if (!(empty($value) && $lockval === 'unlockedifempty')) {
4181                      if ($iscustom || (in_array($key, $userauth->userfields) &&
4182                              ((string)$oldinfo->$key !== (string)$value))) {
4183                          $newuser[$key] = (string)$value;
4184                      }
4185                  }
4186              }
4187          }
4188          if ($newuser) {
4189              $newuser['id'] = $oldinfo->id;
4190              $newuser['timemodified'] = time();
4191              user_update_user((object) $newuser, false, false);
4192  
4193              // Save user profile data.
4194              profile_save_data((object) $newuser);
4195  
4196              // Trigger event.
4197              \core\event\user_updated::create_from_userid($newuser['id'])->trigger();
4198          }
4199      }
4200  
4201      return get_complete_user_data('id', $oldinfo->id);
4202  }
4203  
4204  /**
4205   * Will truncate userinfo as it comes from auth_get_userinfo (from external auth) which may have large fields.
4206   *
4207   * @param array $info Array of user properties to truncate if needed
4208   * @return array The now truncated information that was passed in
4209   */
4210  function truncate_userinfo(array $info) {
4211      // Define the limits.
4212      $limit = array(
4213          'username'    => 100,
4214          'idnumber'    => 255,
4215          'firstname'   => 100,
4216          'lastname'    => 100,
4217          'email'       => 100,
4218          'icq'         =>  15,
4219          'phone1'      =>  20,
4220          'phone2'      =>  20,
4221          'institution' => 255,
4222          'department'  => 255,
4223          'address'     => 255,
4224          'city'        => 120,
4225          'country'     =>   2,
4226          'url'         => 255,
4227      );
4228  
4229      // Apply where needed.
4230      foreach (array_keys($info) as $key) {
4231          if (!empty($limit[$key])) {
4232              $info[$key] = trim(core_text::substr($info[$key], 0, $limit[$key]));
4233          }
4234      }
4235  
4236      return $info;
4237  }
4238  
4239  /**
4240   * Marks user deleted in internal user database and notifies the auth plugin.
4241   * Also unenrols user from all roles and does other cleanup.
4242   *
4243   * Any plugin that needs to purge user data should register the 'user_deleted' event.
4244   *
4245   * @param stdClass $user full user object before delete
4246   * @return boolean success
4247   * @throws coding_exception if invalid $user parameter detected
4248   */
4249  function delete_user(stdClass $user) {
4250      global $CFG, $DB;
4251      require_once($CFG->libdir.'/grouplib.php');
4252      require_once($CFG->libdir.'/gradelib.php');
4253      require_once($CFG->dirroot.'/message/lib.php');
4254      require_once($CFG->dirroot.'/tag/lib.php');
4255      require_once($CFG->dirroot.'/user/lib.php');
4256  
4257      // Make sure nobody sends bogus record type as parameter.
4258      if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
4259          throw new coding_exception('Invalid $user parameter in delete_user() detected');
4260      }
4261  
4262      // Better not trust the parameter and fetch the latest info this will be very expensive anyway.
4263      if (!$user = $DB->get_record('user', array('id' => $user->id))) {
4264          debugging('Attempt to delete unknown user account.');
4265          return false;
4266      }
4267  
4268      // There must be always exactly one guest record, originally the guest account was identified by username only,
4269      // now we use $CFG->siteguest for performance reasons.
4270      if ($user->username === 'guest' or isguestuser($user)) {
4271          debugging('Guest user account can not be deleted.');
4272          return false;
4273      }
4274  
4275      // Admin can be theoretically from different auth plugin, but we want to prevent deletion of internal accoutns only,
4276      // if anything goes wrong ppl may force somebody to be admin via config.php setting $CFG->siteadmins.
4277      if ($user->auth === 'manual' and is_siteadmin($user)) {
4278          debugging('Local administrator accounts can not be deleted.');
4279          return false;
4280      }
4281  
4282      // Keep user record before updating it, as we have to pass this to user_deleted event.
4283      $olduser = clone $user;
4284  
4285      // Keep a copy of user context, we need it for event.
4286      $usercontext = context_user::instance($user->id);
4287  
4288      // Delete all grades - backup is kept in grade_grades_history table.
4289      grade_user_delete($user->id);
4290  
4291      // Move unread messages from this user to read.
4292      message_move_userfrom_unread2read($user->id);
4293  
4294      // TODO: remove from cohorts using standard API here.
4295  
4296      // Remove user tags.
4297      tag_set('user', $user->id, array(), 'core', $usercontext->id);
4298  
4299      // Unconditionally unenrol from all courses.
4300      enrol_user_delete($user);
4301  
4302      // Unenrol from all roles in all contexts.
4303      // This might be slow but it is really needed - modules might do some extra cleanup!
4304      role_unassign_all(array('userid' => $user->id));
4305  
4306      // Now do a brute force cleanup.
4307  
4308      // Remove from all cohorts.
4309      $DB->delete_records('cohort_members', array('userid' => $user->id));
4310  
4311      // Remove from all groups.
4312      $DB->delete_records('groups_members', array('userid' => $user->id));
4313  
4314      // Brute force unenrol from all courses.
4315      $DB->delete_records('user_enrolments', array('userid' => $user->id));
4316  
4317      // Purge user preferences.
4318      $DB->delete_records('user_preferences', array('userid' => $user->id));
4319  
4320      // Purge user extra profile info.
4321      $DB->delete_records('user_info_data', array('userid' => $user->id));
4322  
4323      // Last course access not necessary either.
4324      $DB->delete_records('user_lastaccess', array('userid' => $user->id));
4325      // Remove all user tokens.
4326      $DB->delete_records('external_tokens', array('userid' => $user->id));
4327  
4328      // Unauthorise the user for all services.
4329      $DB->delete_records('external_services_users', array('userid' => $user->id));
4330  
4331      // Remove users private keys.
4332      $DB->delete_records('user_private_key', array('userid' => $user->id));
4333  
4334      // Remove users customised pages.
4335      $DB->delete_records('my_pages', array('userid' => $user->id, 'private' => 1));
4336  
4337      // Force logout - may fail if file based sessions used, sorry.
4338      \core\session\manager::kill_user_sessions($user->id);
4339  
4340      // Generate username from email address, or a fake email.
4341      $delemail = !empty($user->email) ? $user->email : $user->username . '.' . $user->id . '@unknownemail.invalid';
4342      $delname = clean_param($delemail . "." . time(), PARAM_USERNAME);
4343  
4344      // Workaround for bulk deletes of users with the same email address.
4345      while ($DB->record_exists('user', array('username' => $delname))) { // No need to use mnethostid here.
4346          $delname++;
4347      }
4348  
4349      // Mark internal user record as "deleted".
4350      $updateuser = new stdClass();
4351      $updateuser->id           = $user->id;
4352      $updateuser->deleted      = 1;
4353      $updateuser->username     = $delname;            // Remember it just in case.
4354      $updateuser->email        = md5($user->username);// Store hash of username, useful importing/restoring users.
4355      $updateuser->idnumber     = '';                  // Clear this field to free it up.
4356      $updateuser->picture      = 0;
4357      $updateuser->timemodified = time();
4358  
4359      // Don't trigger update event, as user is being deleted.
4360      user_update_user($updateuser, false, false);
4361  
4362      // Now do a final accesslib cleanup - removes all role assignments in user context and context itself.
4363      context_helper::delete_instance(CONTEXT_USER, $user->id);
4364  
4365      // Any plugin that needs to cleanup should register this event.
4366      // Trigger event.
4367      $event = \core\event\user_deleted::create(
4368              array(
4369                  'objectid' => $user->id,
4370                  'relateduserid' => $user->id,
4371                  'context' => $usercontext,
4372                  'other' => array(
4373                      'username' => $user->username,
4374                      'email' => $user->email,
4375                      'idnumber' => $user->idnumber,
4376                      'picture' => $user->picture,
4377                      'mnethostid' => $user->mnethostid
4378                      )
4379                  )
4380              );
4381      $event->add_record_snapshot('user', $olduser);
4382      $event->trigger();
4383  
4384      // We will update the user's timemodified, as it will be passed to the user_deleted event, which
4385      // should know about this updated property persisted to the user's table.
4386      $user->timemodified = $updateuser->timemodified;
4387  
4388      // Notify auth plugin - do not block the delete even when plugin fails.
4389      $authplugin = get_auth_plugin($user->auth);
4390      $authplugin->user_delete($user);
4391  
4392      return true;
4393  }
4394  
4395  /**
4396   * Retrieve the guest user object.
4397   *
4398   * @return stdClass A {@link $USER} object
4399   */
4400  function guest_user() {
4401      global $CFG, $DB;
4402  
4403      if ($newuser = $DB->get_record('user', array('id' => $CFG->siteguest))) {
4404          $newuser->confirmed = 1;
4405          $newuser->lang = $CFG->lang;
4406          $newuser->lastip = getremoteaddr();
4407      }
4408  
4409      return $newuser;
4410  }
4411  
4412  /**
4413   * Authenticates a user against the chosen authentication mechanism
4414   *
4415   * Given a username and password, this function looks them
4416   * up using the currently selected authentication mechanism,
4417   * and if the authentication is successful, it returns a
4418   * valid $user object from the 'user' table.
4419   *
4420   * Uses auth_ functions from the currently active auth module
4421   *
4422   * After authenticate_user_login() returns success, you will need to
4423   * log that the user has logged in, and call complete_user_login() to set
4424   * the session up.
4425   *
4426   * Note: this function works only with non-mnet accounts!
4427   *
4428   * @param string $username  User's username (or also email if $CFG->authloginviaemail enabled)
4429   * @param string $password  User's password
4430   * @param bool $ignorelockout useful when guessing is prevented by other mechanism such as captcha or SSO
4431   * @param int $failurereason login failure reason, can be used in renderers (it may disclose if account exists)
4432   * @return stdClass|false A {@link $USER} object or false if error
4433   */
4434  function authenticate_user_login($username, $password, $ignorelockout=false, &$failurereason=null) {
4435      global $CFG, $DB;
4436      require_once("$CFG->libdir/authlib.php");
4437  
4438      if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
4439          // we have found the user
4440  
4441      } else if (!empty($CFG->authloginviaemail)) {
4442          if ($email = clean_param($username, PARAM_EMAIL)) {
4443              $select = "mnethostid = :mnethostid AND LOWER(email) = LOWER(:email) AND deleted = 0";
4444              $params = array('mnethostid' => $CFG->mnet_localhost_id, 'email' => $email);
4445              $users = $DB->get_records_select('user', $select, $params, 'id', 'id', 0, 2);
4446              if (count($users) === 1) {
4447                  // Use email for login only if unique.
4448                  $user = reset($users);
4449                  $user = get_complete_user_data('id', $user->id);
4450                  $username = $user->username;
4451              }
4452              unset($users);
4453          }
4454      }
4455  
4456      $authsenabled = get_enabled_auth_plugins();
4457  
4458      if ($user) {
4459          // Use manual if auth not set.
4460          $auth = empty($user->auth) ? 'manual' : $user->auth;
4461          if (!empty($user->suspended)) {
4462              $failurereason = AUTH_LOGIN_SUSPENDED;
4463  
4464              // Trigger login failed event.
4465              $event = \core\event\user_login_failed::create(array('userid' => $user->id,
4466                      'other' => array('username' => $username, 'reason' => $failurereason)));
4467              $event->trigger();
4468              error_log('[client '.getremotead