Search moodle.org's
Developer Documentation


Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
  • /lib/ -> weblib.php (source)

    Differences Between: [Versions 32 and 39] [Versions 33 and 39] [Versions 34 and 39] [Versions 35 and 39] [Versions 36 and 39] [Versions 37 and 39] [Versions 38 and 39]

       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   * Library of functions for web output
      19   *
      20   * Library of all general-purpose Moodle PHP functions and constants
      21   * that produce HTML output
      22   *
      23   * Other main libraries:
      24   * - datalib.php - functions that access the database.
      25   * - moodlelib.php - general-purpose Moodle functions.
      26   *
      27   * @package    core
      28   * @subpackage lib
      29   * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
      30   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      31   */
      32  
      33  defined('MOODLE_INTERNAL') || die();
      34  
      35  // Constants.
      36  
      37  // Define text formatting types ... eventually we can add Wiki, BBcode etc.
      38  
      39  /**
      40   * Does all sorts of transformations and filtering.
      41   */
      42  define('FORMAT_MOODLE',   '0');
      43  
      44  /**
      45   * Plain HTML (with some tags stripped).
      46   */
      47  define('FORMAT_HTML',     '1');
      48  
      49  /**
      50   * Plain text (even tags are printed in full).
      51   */
      52  define('FORMAT_PLAIN',    '2');
      53  
      54  /**
      55   * Wiki-formatted text.
      56   * Deprecated: left here just to note that '3' is not used (at the moment)
      57   * and to catch any latent wiki-like text (which generates an error)
      58   * @deprecated since 2005!
      59   */
      60  define('FORMAT_WIKI',     '3');
      61  
      62  /**
      63   * Markdown-formatted text http://daringfireball.net/projects/markdown/
      64   */
      65  define('FORMAT_MARKDOWN', '4');
      66  
      67  /**
      68   * A moodle_url comparison using this flag will return true if the base URLs match, params are ignored.
      69   */
      70  define('URL_MATCH_BASE', 0);
      71  
      72  /**
      73   * A moodle_url comparison using this flag will return true if the base URLs match and the params of url1 are part of url2.
      74   */
      75  define('URL_MATCH_PARAMS', 1);
      76  
      77  /**
      78   * A moodle_url comparison using this flag will return true if the two URLs are identical, except for the order of the params.
      79   */
      80  define('URL_MATCH_EXACT', 2);
      81  
      82  // Functions.
      83  
      84  /**
      85   * Add quotes to HTML characters.
      86   *
      87   * Returns $var with HTML characters (like "<", ">", etc.) properly quoted.
      88   * Related function {@link p()} simply prints the output of this function.
      89   *
      90   * @param string $var the string potentially containing HTML characters
      91   * @return string
      92   */
      93  function s($var) {
      94  
      95      if ($var === false) {
      96          return '0';
      97      }
      98  
      99      return preg_replace('/&amp;#(\d+|x[0-9a-f]+);/i', '&#$1;',
     100              htmlspecialchars($var, ENT_QUOTES | ENT_HTML401 | ENT_SUBSTITUTE));
     101  }
     102  
     103  /**
     104   * Add quotes to HTML characters.
     105   *
     106   * Prints $var with HTML characters (like "<", ">", etc.) properly quoted.
     107   * This function simply calls & displays {@link s()}.
     108   * @see s()
     109   *
     110   * @param string $var the string potentially containing HTML characters
     111   * @return string
     112   */
     113  function p($var) {
     114      echo s($var);
     115  }
     116  
     117  /**
     118   * Does proper javascript quoting.
     119   *
     120   * Do not use addslashes anymore, because it does not work when magic_quotes_sybase is enabled.
     121   *
     122   * @param mixed $var String, Array, or Object to add slashes to
     123   * @return mixed quoted result
     124   */
     125  function addslashes_js($var) {
     126      if (is_string($var)) {
     127          $var = str_replace('\\', '\\\\', $var);
     128          $var = str_replace(array('\'', '"', "\n", "\r", "\0"), array('\\\'', '\\"', '\\n', '\\r', '\\0'), $var);
     129          $var = str_replace('</', '<\/', $var);   // XHTML compliance.
     130      } else if (is_array($var)) {
     131          $var = array_map('addslashes_js', $var);
     132      } else if (is_object($var)) {
     133          $a = get_object_vars($var);
     134          foreach ($a as $key => $value) {
     135              $a[$key] = addslashes_js($value);
     136          }
     137          $var = (object)$a;
     138      }
     139      return $var;
     140  }
     141  
     142  /**
     143   * Remove query string from url.
     144   *
     145   * Takes in a URL and returns it without the querystring portion.
     146   *
     147   * @param string $url the url which may have a query string attached.
     148   * @return string The remaining URL.
     149   */
     150  function strip_querystring($url) {
     151  
     152      if ($commapos = strpos($url, '?')) {
     153          return substr($url, 0, $commapos);
     154      } else {
     155          return $url;
     156      }
     157  }
     158  
     159  /**
     160   * Returns the name of the current script, WITH the querystring portion.
     161   *
     162   * This function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
     163   * return different things depending on a lot of things like your OS, Web
     164   * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.)
     165   * <b>NOTE:</b> This function returns false if the global variables needed are not set.
     166   *
     167   * @return mixed String or false if the global variables needed are not set.
     168   */
     169  function me() {
     170      global $ME;
     171      return $ME;
     172  }
     173  
     174  /**
     175   * Guesses the full URL of the current script.
     176   *
     177   * This function is using $PAGE->url, but may fall back to $FULLME which
     178   * is constructed from  PHP_SELF and REQUEST_URI or SCRIPT_NAME
     179   *
     180   * @return mixed full page URL string or false if unknown
     181   */
     182  function qualified_me() {
     183      global $FULLME, $PAGE, $CFG;
     184  
     185      if (isset($PAGE) and $PAGE->has_set_url()) {
     186          // This is the only recommended way to find out current page.
     187          return $PAGE->url->out(false);
     188  
     189      } else {
     190          if ($FULLME === null) {
     191              // CLI script most probably.
     192              return false;
     193          }
     194          if (!empty($CFG->sslproxy)) {
     195              // Return only https links when using SSL proxy.
     196              return preg_replace('/^http:/', 'https:', $FULLME, 1);
     197          } else {
     198              return $FULLME;
     199          }
     200      }
     201  }
     202  
     203  /**
     204   * Determines whether or not the Moodle site is being served over HTTPS.
     205   *
     206   * This is done simply by checking the value of $CFG->wwwroot, which seems
     207   * to be the only reliable method.
     208   *
     209   * @return boolean True if site is served over HTTPS, false otherwise.
     210   */
     211  function is_https() {
     212      global $CFG;
     213  
     214      return (strpos($CFG->wwwroot, 'https://') === 0);
     215  }
     216  
     217  /**
     218   * Returns the cleaned local URL of the HTTP_REFERER less the URL query string parameters if required.
     219   *
     220   * @param bool $stripquery if true, also removes the query part of the url.
     221   * @return string The resulting referer or empty string.
     222   */
     223  function get_local_referer($stripquery = true) {
     224      if (isset($_SERVER['HTTP_REFERER'])) {
     225          $referer = clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL);
     226          if ($stripquery) {
     227              return strip_querystring($referer);
     228          } else {
     229              return $referer;
     230          }
     231      } else {
     232          return '';
     233      }
     234  }
     235  
     236  /**
     237   * Class for creating and manipulating urls.
     238   *
     239   * It can be used in moodle pages where config.php has been included without any further includes.
     240   *
     241   * It is useful for manipulating urls with long lists of params.
     242   * One situation where it will be useful is a page which links to itself to perform various actions
     243   * and / or to process form data. A moodle_url object :
     244   * can be created for a page to refer to itself with all the proper get params being passed from page call to
     245   * page call and methods can be used to output a url including all the params, optionally adding and overriding
     246   * params and can also be used to
     247   *     - output the url without any get params
     248   *     - and output the params as hidden fields to be output within a form
     249   *
     250   * @copyright 2007 jamiesensei
     251   * @link http://docs.moodle.org/dev/lib/weblib.php_moodle_url See short write up here
     252   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     253   * @package core
     254   */
     255  class moodle_url {
     256  
     257      /**
     258       * Scheme, ex.: http, https
     259       * @var string
     260       */
     261      protected $scheme = '';
     262  
     263      /**
     264       * Hostname.
     265       * @var string
     266       */
     267      protected $host = '';
     268  
     269      /**
     270       * Port number, empty means default 80 or 443 in case of http.
     271       * @var int
     272       */
     273      protected $port = '';
     274  
     275      /**
     276       * Username for http auth.
     277       * @var string
     278       */
     279      protected $user = '';
     280  
     281      /**
     282       * Password for http auth.
     283       * @var string
     284       */
     285      protected $pass = '';
     286  
     287      /**
     288       * Script path.
     289       * @var string
     290       */
     291      protected $path = '';
     292  
     293      /**
     294       * Optional slash argument value.
     295       * @var string
     296       */
     297      protected $slashargument = '';
     298  
     299      /**
     300       * Anchor, may be also empty, null means none.
     301       * @var string
     302       */
     303      protected $anchor = null;
     304  
     305      /**
     306       * Url parameters as associative array.
     307       * @var array
     308       */
     309      protected $params = array();
     310  
     311      /**
     312       * Create new instance of moodle_url.
     313       *
     314       * @param moodle_url|string $url - moodle_url means make a copy of another
     315       *      moodle_url and change parameters, string means full url or shortened
     316       *      form (ex.: '/course/view.php'). It is strongly encouraged to not include
     317       *      query string because it may result in double encoded values. Use the
     318       *      $params instead. For admin URLs, just use /admin/script.php, this
     319       *      class takes care of the $CFG->admin issue.
     320       * @param array $params these params override current params or add new
     321       * @param string $anchor The anchor to use as part of the URL if there is one.
     322       * @throws moodle_exception
     323       */
     324      public function __construct($url, array $params = null, $anchor = null) {
     325          global $CFG;
     326  
     327          if ($url instanceof moodle_url) {
     328              $this->scheme = $url->scheme;
     329              $this->host = $url->host;
     330              $this->port = $url->port;
     331              $this->user = $url->user;
     332              $this->pass = $url->pass;
     333              $this->path = $url->path;
     334              $this->slashargument = $url->slashargument;
     335              $this->params = $url->params;
     336              $this->anchor = $url->anchor;
     337  
     338          } else {
     339              // Detect if anchor used.
     340              $apos = strpos($url, '#');
     341              if ($apos !== false) {
     342                  $anchor = substr($url, $apos);
     343                  $anchor = ltrim($anchor, '#');
     344                  $this->set_anchor($anchor);
     345                  $url = substr($url, 0, $apos);
     346              }
     347  
     348              // Normalise shortened form of our url ex.: '/course/view.php'.
     349              if (strpos($url, '/') === 0) {
     350                  $url = $CFG->wwwroot.$url;
     351              }
     352  
     353              if ($CFG->admin !== 'admin') {
     354                  if (strpos($url, "$CFG->wwwroot/admin/") === 0) {
     355                      $url = str_replace("$CFG->wwwroot/admin/", "$CFG->wwwroot/$CFG->admin/", $url);
     356                  }
     357              }
     358  
     359              // Parse the $url.
     360              $parts = parse_url($url);
     361              if ($parts === false) {
     362                  throw new moodle_exception('invalidurl');
     363              }
     364              if (isset($parts['query'])) {
     365                  // Note: the values may not be correctly decoded, url parameters should be always passed as array.
     366                  parse_str(str_replace('&amp;', '&', $parts['query']), $this->params);
     367              }
     368              unset($parts['query']);
     369              foreach ($parts as $key => $value) {
     370                  $this->$key = $value;
     371              }
     372  
     373              // Detect slashargument value from path - we do not support directory names ending with .php.
     374              $pos = strpos($this->path, '.php/');
     375              if ($pos !== false) {
     376                  $this->slashargument = substr($this->path, $pos + 4);
     377                  $this->path = substr($this->path, 0, $pos + 4);
     378              }
     379          }
     380  
     381          $this->params($params);
     382          if ($anchor !== null) {
     383              $this->anchor = (string)$anchor;
     384          }
     385      }
     386  
     387      /**
     388       * Add an array of params to the params for this url.
     389       *
     390       * The added params override existing ones if they have the same name.
     391       *
     392       * @param array $params Defaults to null. If null then returns all params.
     393       * @return array Array of Params for url.
     394       * @throws coding_exception
     395       */
     396      public function params(array $params = null) {
     397          $params = (array)$params;
     398  
     399          foreach ($params as $key => $value) {
     400              if (is_int($key)) {
     401                  throw new coding_exception('Url parameters can not have numeric keys!');
     402              }
     403              if (!is_string($value)) {
     404                  if (is_array($value)) {
     405                      throw new coding_exception('Url parameters values can not be arrays!');
     406                  }
     407                  if (is_object($value) and !method_exists($value, '__toString')) {
     408                      throw new coding_exception('Url parameters values can not be objects, unless __toString() is defined!');
     409                  }
     410              }
     411              $this->params[$key] = (string)$value;
     412          }
     413          return $this->params;
     414      }
     415  
     416      /**
     417       * Remove all params if no arguments passed.
     418       * Remove selected params if arguments are passed.
     419       *
     420       * Can be called as either remove_params('param1', 'param2')
     421       * or remove_params(array('param1', 'param2')).
     422       *
     423       * @param string[]|string $params,... either an array of param names, or 1..n string params to remove as args.
     424       * @return array url parameters
     425       */
     426      public function remove_params($params = null) {
     427          if (!is_array($params)) {
     428              $params = func_get_args();
     429          }
     430          foreach ($params as $param) {
     431              unset($this->params[$param]);
     432          }
     433          return $this->params;
     434      }
     435  
     436      /**
     437       * Remove all url parameters.
     438       *
     439       * @todo remove the unused param.
     440       * @param array $params Unused param
     441       * @return void
     442       */
     443      public function remove_all_params($params = null) {
     444          $this->params = array();
     445          $this->slashargument = '';
     446      }
     447  
     448      /**
     449       * Add a param to the params for this url.
     450       *
     451       * The added param overrides existing one if they have the same name.
     452       *
     453       * @param string $paramname name
     454       * @param string $newvalue Param value. If new value specified current value is overriden or parameter is added
     455       * @return mixed string parameter value, null if parameter does not exist
     456       */
     457      public function param($paramname, $newvalue = '') {
     458          if (func_num_args() > 1) {
     459              // Set new value.
     460              $this->params(array($paramname => $newvalue));
     461          }
     462          if (isset($this->params[$paramname])) {
     463              return $this->params[$paramname];
     464          } else {
     465              return null;
     466          }
     467      }
     468  
     469      /**
     470       * Merges parameters and validates them
     471       *
     472       * @param array $overrideparams
     473       * @return array merged parameters
     474       * @throws coding_exception
     475       */
     476      protected function merge_overrideparams(array $overrideparams = null) {
     477          $overrideparams = (array)$overrideparams;
     478          $params = $this->params;
     479          foreach ($overrideparams as $key => $value) {
     480              if (is_int($key)) {
     481                  throw new coding_exception('Overridden parameters can not have numeric keys!');
     482              }
     483              if (is_array($value)) {
     484                  throw new coding_exception('Overridden parameters values can not be arrays!');
     485              }
     486              if (is_object($value) and !method_exists($value, '__toString')) {
     487                  throw new coding_exception('Overridden parameters values can not be objects, unless __toString() is defined!');
     488              }
     489              $params[$key] = (string)$value;
     490          }
     491          return $params;
     492      }
     493  
     494      /**
     495       * Get the params as as a query string.
     496       *
     497       * This method should not be used outside of this method.
     498       *
     499       * @param bool $escaped Use &amp; as params separator instead of plain &
     500       * @param array $overrideparams params to add to the output params, these
     501       *      override existing ones with the same name.
     502       * @return string query string that can be added to a url.
     503       */
     504      public function get_query_string($escaped = true, array $overrideparams = null) {
     505          $arr = array();
     506          if ($overrideparams !== null) {
     507              $params = $this->merge_overrideparams($overrideparams);
     508          } else {
     509              $params = $this->params;
     510          }
     511          foreach ($params as $key => $val) {
     512              if (is_array($val)) {
     513                  foreach ($val as $index => $value) {
     514                      $arr[] = rawurlencode($key.'['.$index.']')."=".rawurlencode($value);
     515                  }
     516              } else {
     517                  if (isset($val) && $val !== '') {
     518                      $arr[] = rawurlencode($key)."=".rawurlencode($val);
     519                  } else {
     520                      $arr[] = rawurlencode($key);
     521                  }
     522              }
     523          }
     524          if ($escaped) {
     525              return implode('&amp;', $arr);
     526          } else {
     527              return implode('&', $arr);
     528          }
     529      }
     530  
     531      /**
     532       * Shortcut for printing of encoded URL.
     533       *
     534       * @return string
     535       */
     536      public function __toString() {
     537          return $this->out(true);
     538      }
     539  
     540      /**
     541       * Output url.
     542       *
     543       * If you use the returned URL in HTML code, you want the escaped ampersands. If you use
     544       * the returned URL in HTTP headers, you want $escaped=false.
     545       *
     546       * @param bool $escaped Use &amp; as params separator instead of plain &
     547       * @param array $overrideparams params to add to the output url, these override existing ones with the same name.
     548       * @return string Resulting URL
     549       */
     550      public function out($escaped = true, array $overrideparams = null) {
     551  
     552          global $CFG;
     553  
     554          if (!is_bool($escaped)) {
     555              debugging('Escape parameter must be of type boolean, '.gettype($escaped).' given instead.');
     556          }
     557  
     558          $url = $this;
     559  
     560          // Allow url's to be rewritten by a plugin.
     561          if (isset($CFG->urlrewriteclass) && !isset($CFG->upgraderunning)) {
     562              $class = $CFG->urlrewriteclass;
     563              $pluginurl = $class::url_rewrite($url);
     564              if ($pluginurl instanceof moodle_url) {
     565                  $url = $pluginurl;
     566              }
     567          }
     568  
     569          return $url->raw_out($escaped, $overrideparams);
     570  
     571      }
     572  
     573      /**
     574       * Output url without any rewrites
     575       *
     576       * This is identical in signature and use to out() but doesn't call the rewrite handler.
     577       *
     578       * @param bool $escaped Use &amp; as params separator instead of plain &
     579       * @param array $overrideparams params to add to the output url, these override existing ones with the same name.
     580       * @return string Resulting URL
     581       */
     582      public function raw_out($escaped = true, array $overrideparams = null) {
     583          if (!is_bool($escaped)) {
     584              debugging('Escape parameter must be of type boolean, '.gettype($escaped).' given instead.');
     585          }
     586  
     587          $uri = $this->out_omit_querystring().$this->slashargument;
     588  
     589          $querystring = $this->get_query_string($escaped, $overrideparams);
     590          if ($querystring !== '') {
     591              $uri .= '?' . $querystring;
     592          }
     593          if (!is_null($this->anchor)) {
     594              $uri .= '#'.$this->anchor;
     595          }
     596  
     597          return $uri;
     598      }
     599  
     600      /**
     601       * Returns url without parameters, everything before '?'.
     602       *
     603       * @param bool $includeanchor if {@link self::anchor} is defined, should it be returned?
     604       * @return string
     605       */
     606      public function out_omit_querystring($includeanchor = false) {
     607  
     608          $uri = $this->scheme ? $this->scheme.':'.((strtolower($this->scheme) == 'mailto') ? '':'//'): '';
     609          $uri .= $this->user ? $this->user.($this->pass? ':'.$this->pass:'').'@':'';
     610          $uri .= $this->host ? $this->host : '';
     611          $uri .= $this->port ? ':'.$this->port : '';
     612          $uri .= $this->path ? $this->path : '';
     613          if ($includeanchor and !is_null($this->anchor)) {
     614              $uri .= '#' . $this->anchor;
     615          }
     616  
     617          return $uri;
     618      }
     619  
     620      /**
     621       * Compares this moodle_url with another.
     622       *
     623       * See documentation of constants for an explanation of the comparison flags.
     624       *
     625       * @param moodle_url $url The moodle_url object to compare
     626       * @param int $matchtype The type of comparison (URL_MATCH_BASE, URL_MATCH_PARAMS, URL_MATCH_EXACT)
     627       * @return bool
     628       */
     629      public function compare(moodle_url $url, $matchtype = URL_MATCH_EXACT) {
     630  
     631          $baseself = $this->out_omit_querystring();
     632          $baseother = $url->out_omit_querystring();
     633  
     634          // Append index.php if there is no specific file.
     635          if (substr($baseself, -1) == '/') {
     636              $baseself .= 'index.php';
     637          }
     638          if (substr($baseother, -1) == '/') {
     639              $baseother .= 'index.php';
     640          }
     641  
     642          // Compare the two base URLs.
     643          if ($baseself != $baseother) {
     644              return false;
     645          }
     646  
     647          if ($matchtype == URL_MATCH_BASE) {
     648              return true;
     649          }
     650  
     651          $urlparams = $url->params();
     652          foreach ($this->params() as $param => $value) {
     653              if ($param == 'sesskey') {
     654                  continue;
     655              }
     656              if (!array_key_exists($param, $urlparams) || $urlparams[$param] != $value) {
     657                  return false;
     658              }
     659          }
     660  
     661          if ($matchtype == URL_MATCH_PARAMS) {
     662              return true;
     663          }
     664  
     665          foreach ($urlparams as $param => $value) {
     666              if ($param == 'sesskey') {
     667                  continue;
     668              }
     669              if (!array_key_exists($param, $this->params()) || $this->param($param) != $value) {
     670                  return false;
     671              }
     672          }
     673  
     674          if ($url->anchor !== $this->anchor) {
     675              return false;
     676          }
     677  
     678          return true;
     679      }
     680  
     681      /**
     682       * Sets the anchor for the URI (the bit after the hash)
     683       *
     684       * @param string $anchor null means remove previous
     685       */
     686      public function set_anchor($anchor) {
     687          if (is_null($anchor)) {
     688              // Remove.
     689              $this->anchor = null;
     690          } else if ($anchor === '') {
     691              // Special case, used as empty link.
     692              $this->anchor = '';
     693          } else if (preg_match('|[a-zA-Z\_\:][a-zA-Z0-9\_\-\.\:]*|', $anchor)) {
     694              // Match the anchor against the NMTOKEN spec.
     695              $this->anchor = $anchor;
     696          } else {
     697              // Bad luck, no valid anchor found.
     698              $this->anchor = null;
     699          }
     700      }
     701  
     702      /**
     703       * Sets the scheme for the URI (the bit before ://)
     704       *
     705       * @param string $scheme
     706       */
     707      public function set_scheme($scheme) {
     708          // See http://www.ietf.org/rfc/rfc3986.txt part 3.1.
     709          if (preg_match('/^[a-zA-Z][a-zA-Z0-9+.-]*$/', $scheme)) {
     710              $this->scheme = $scheme;
     711          } else {
     712              throw new coding_exception('Bad URL scheme.');
     713          }
     714      }
     715  
     716      /**
     717       * Sets the url slashargument value.
     718       *
     719       * @param string $path usually file path
     720       * @param string $parameter name of page parameter if slasharguments not supported
     721       * @param bool $supported usually null, then it depends on $CFG->slasharguments, use true or false for other servers
     722       * @return void
     723       */
     724      public function set_slashargument($path, $parameter = 'file', $supported = null) {
     725          global $CFG;
     726          if (is_null($supported)) {
     727              $supported = !empty($CFG->slasharguments);
     728          }
     729  
     730          if ($supported) {
     731              $parts = explode('/', $path);
     732              $parts = array_map('rawurlencode', $parts);
     733              $path  = implode('/', $parts);
     734              $this->slashargument = $path;
     735              unset($this->params[$parameter]);
     736  
     737          } else {
     738              $this->slashargument = '';
     739              $this->params[$parameter] = $path;
     740          }
     741      }
     742  
     743      // Static factory methods.
     744  
     745      /**
     746       * General moodle file url.
     747       *
     748       * @param string $urlbase the script serving the file
     749       * @param string $path
     750       * @param bool $forcedownload
     751       * @return moodle_url
     752       */
     753      public static function make_file_url($urlbase, $path, $forcedownload = false) {
     754          $params = array();
     755          if ($forcedownload) {
     756              $params['forcedownload'] = 1;
     757          }
     758          $url = new moodle_url($urlbase, $params);
     759          $url->set_slashargument($path);
     760          return $url;
     761      }
     762  
     763      /**
     764       * Factory method for creation of url pointing to plugin file.
     765       *
     766       * Please note this method can be used only from the plugins to
     767       * create urls of own files, it must not be used outside of plugins!
     768       *
     769       * @param int $contextid
     770       * @param string $component
     771       * @param string $area
     772       * @param int $itemid
     773       * @param string $pathname
     774       * @param string $filename
     775       * @param bool $forcedownload
     776       * @param mixed $includetoken Whether to use a user token when displaying this group image.
     777       *                True indicates to generate a token for current user, and integer value indicates to generate a token for the
     778       *                user whose id is the value indicated.
     779       *                If the group picture is included in an e-mail or some other location where the audience is a specific
     780       *                user who will not be logged in when viewing, then we use a token to authenticate the user.
     781       * @return moodle_url
     782       */
     783      public static function make_pluginfile_url($contextid, $component, $area, $itemid, $pathname, $filename,
     784                                                 $forcedownload = false, $includetoken = false) {
     785          global $CFG, $USER;
     786  
     787          $path = [];
     788  
     789          if ($includetoken) {
     790              $urlbase = "$CFG->wwwroot/tokenpluginfile.php";
     791              $userid = $includetoken === true ? $USER->id : $includetoken;
     792              $token = get_user_key('core_files', $userid);
     793              if ($CFG->slasharguments) {
     794                  $path[] = $token;
     795              }
     796          } else {
     797              $urlbase = "$CFG->wwwroot/pluginfile.php";
     798          }
     799          $path[] = $contextid;
     800          $path[] = $component;
     801          $path[] = $area;
     802  
     803          if ($itemid !== null) {
     804              $path[] = $itemid;
     805          }
     806  
     807          $path = "/" . implode('/', $path) . "{$pathname}{$filename}";
     808  
     809          $url = self::make_file_url($urlbase, $path, $forcedownload, $includetoken);
     810          if ($includetoken && empty($CFG->slasharguments)) {
     811              $url->param('token', $token);
     812          }
     813          return $url;
     814      }
     815  
     816      /**
     817       * Factory method for creation of url pointing to plugin file.
     818       * This method is the same that make_pluginfile_url but pointing to the webservice pluginfile.php script.
     819       * It should be used only in external functions.
     820       *
     821       * @since  2.8
     822       * @param int $contextid
     823       * @param string $component
     824       * @param string $area
     825       * @param int $itemid
     826       * @param string $pathname
     827       * @param string $filename
     828       * @param bool $forcedownload
     829       * @return moodle_url
     830       */
     831      public static function make_webservice_pluginfile_url($contextid, $component, $area, $itemid, $pathname, $filename,
     832                                                 $forcedownload = false) {
     833          global $CFG;
     834          $urlbase = "$CFG->wwwroot/webservice/pluginfile.php";
     835          if ($itemid === null) {
     836              return self::make_file_url($urlbase, "/$contextid/$component/$area".$pathname.$filename, $forcedownload);
     837          } else {
     838              return self::make_file_url($urlbase, "/$contextid/$component/$area/$itemid".$pathname.$filename, $forcedownload);
     839          }
     840      }
     841  
     842      /**
     843       * Factory method for creation of url pointing to draft file of current user.
     844       *
     845       * @param int $draftid draft item id
     846       * @param string $pathname
     847       * @param string $filename
     848       * @param bool $forcedownload
     849       * @return moodle_url
     850       */
     851      public static function make_draftfile_url($draftid, $pathname, $filename, $forcedownload = false) {
     852          global $CFG, $USER;
     853          $urlbase = "$CFG->wwwroot/draftfile.php";
     854          $context = context_user::instance($USER->id);
     855  
     856          return self::make_file_url($urlbase, "/$context->id/user/draft/$draftid".$pathname.$filename, $forcedownload);
     857      }
     858  
     859      /**
     860       * Factory method for creating of links to legacy course files.
     861       *
     862       * @param int $courseid
     863       * @param string $filepath
     864       * @param bool $forcedownload
     865       * @return moodle_url
     866       */
     867      public static function make_legacyfile_url($courseid, $filepath, $forcedownload = false) {
     868          global $CFG;
     869  
     870          $urlbase = "$CFG->wwwroot/file.php";
     871          return self::make_file_url($urlbase, '/'.$courseid.'/'.$filepath, $forcedownload);
     872      }
     873  
     874      /**
     875       * Returns URL a relative path from $CFG->wwwroot
     876       *
     877       * Can be used for passing around urls with the wwwroot stripped
     878       *
     879       * @param boolean $escaped Use &amp; as params separator instead of plain &
     880       * @param array $overrideparams params to add to the output url, these override existing ones with the same name.
     881       * @return string Resulting URL
     882       * @throws coding_exception if called on a non-local url
     883       */
     884      public function out_as_local_url($escaped = true, array $overrideparams = null) {
     885          global $CFG;
     886  
     887          $url = $this->out($escaped, $overrideparams);
     888  
     889          // Url should be equal to wwwroot. If not then throw exception.
     890          if (($url === $CFG->wwwroot) || (strpos($url, $CFG->wwwroot.'/') === 0)) {
     891              $localurl = substr($url, strlen($CFG->wwwroot));
     892              return !empty($localurl) ? $localurl : '';
     893          } else {
     894              throw new coding_exception('out_as_local_url called on a non-local URL');
     895          }
     896      }
     897  
     898      /**
     899       * Returns the 'path' portion of a URL. For example, if the URL is
     900       * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
     901       * return '/my/file/is/here.txt'.
     902       *
     903       * By default the path includes slash-arguments (for example,
     904       * '/myfile.php/extra/arguments') so it is what you would expect from a
     905       * URL path. If you don't want this behaviour, you can opt to exclude the
     906       * slash arguments. (Be careful: if the $CFG variable slasharguments is
     907       * disabled, these URLs will have a different format and you may need to
     908       * look at the 'file' parameter too.)
     909       *
     910       * @param bool $includeslashargument If true, includes slash arguments
     911       * @return string Path of URL
     912       */
     913      public function get_path($includeslashargument = true) {
     914          return $this->path . ($includeslashargument ? $this->slashargument : '');
     915      }
     916  
     917      /**
     918       * Returns a given parameter value from the URL.
     919       *
     920       * @param string $name Name of parameter
     921       * @return string Value of parameter or null if not set
     922       */
     923      public function get_param($name) {
     924          if (array_key_exists($name, $this->params)) {
     925              return $this->params[$name];
     926          } else {
     927              return null;
     928          }
     929      }
     930  
     931      /**
     932       * Returns the 'scheme' portion of a URL. For example, if the URL is
     933       * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
     934       * return 'http' (without the colon).
     935       *
     936       * @return string Scheme of the URL.
     937       */
     938      public function get_scheme() {
     939          return $this->scheme;
     940      }
     941  
     942      /**
     943       * Returns the 'host' portion of a URL. For example, if the URL is
     944       * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
     945       * return 'www.example.org'.
     946       *
     947       * @return string Host of the URL.
     948       */
     949      public function get_host() {
     950          return $this->host;
     951      }
     952  
     953      /**
     954       * Returns the 'port' portion of a URL. For example, if the URL is
     955       * http://www.example.org:447/my/file/is/here.txt?really=1 then this will
     956       * return '447'.
     957       *
     958       * @return string Port of the URL.
     959       */
     960      public function get_port() {
     961          return $this->port;
     962      }
     963  }
     964  
     965  /**
     966   * Determine if there is data waiting to be processed from a form
     967   *
     968   * Used on most forms in Moodle to check for data
     969   * Returns the data as an object, if it's found.
     970   * This object can be used in foreach loops without
     971   * casting because it's cast to (array) automatically
     972   *
     973   * Checks that submitted POST data exists and returns it as object.
     974   *
     975   * @return mixed false or object
     976   */
     977  function data_submitted() {
     978  
     979      if (empty($_POST)) {
     980          return false;
     981      } else {
     982          return (object)fix_utf8($_POST);
     983      }
     984  }
     985  
     986  /**
     987   * Given some normal text this function will break up any
     988   * long words to a given size by inserting the given character
     989   *
     990   * It's multibyte savvy and doesn't change anything inside html tags.
     991   *
     992   * @param string $string the string to be modified
     993   * @param int $maxsize maximum length of the string to be returned
     994   * @param string $cutchar the string used to represent word breaks
     995   * @return string
     996   */
     997  function break_up_long_words($string, $maxsize=20, $cutchar=' ') {
     998  
     999      // First of all, save all the tags inside the text to skip them.
    1000      $tags = array();
    1001      filter_save_tags($string, $tags);
    1002  
    1003      // Process the string adding the cut when necessary.
    1004      $output = '';
    1005      $length = core_text::strlen($string);
    1006      $wordlength = 0;
    1007  
    1008      for ($i=0; $i<$length; $i++) {
    1009          $char = core_text::substr($string, $i, 1);
    1010          if ($char == ' ' or $char == "\t" or $char == "\n" or $char == "\r" or $char == "<" or $char == ">") {
    1011              $wordlength = 0;
    1012          } else {
    1013              $wordlength++;
    1014              if ($wordlength > $maxsize) {
    1015                  $output .= $cutchar;
    1016                  $wordlength = 0;
    1017              }
    1018          }
    1019          $output .= $char;
    1020      }
    1021  
    1022      // Finally load the tags back again.
    1023      if (!empty($tags)) {
    1024          $output = str_replace(array_keys($tags), $tags, $output);
    1025      }
    1026  
    1027      return $output;
    1028  }
    1029  
    1030  /**
    1031   * Try and close the current window using JavaScript, either immediately, or after a delay.
    1032   *
    1033   * Echo's out the resulting XHTML & javascript
    1034   *
    1035   * @param integer $delay a delay in seconds before closing the window. Default 0.
    1036   * @param boolean $reloadopener if true, we will see if this window was a pop-up, and try
    1037   *      to reload the parent window before this one closes.
    1038   */
    1039  function close_window($delay = 0, $reloadopener = false) {
    1040      global $PAGE, $OUTPUT;
    1041  
    1042      if (!$PAGE->headerprinted) {
    1043          $PAGE->set_title(get_string('closewindow'));
    1044          echo $OUTPUT->header();
    1045      } else {
    1046          $OUTPUT->container_end_all(false);
    1047      }
    1048  
    1049      if ($reloadopener) {
    1050          // Trigger the reload immediately, even if the reload is after a delay.
    1051          $PAGE->requires->js_function_call('window.opener.location.reload', array(true));
    1052      }
    1053      $OUTPUT->notification(get_string('windowclosing'), 'notifysuccess');
    1054  
    1055      $PAGE->requires->js_function_call('close_window', array(new stdClass()), false, $delay);
    1056  
    1057      echo $OUTPUT->footer();
    1058      exit;
    1059  }
    1060  
    1061  /**
    1062   * Returns a string containing a link to the user documentation for the current page.
    1063   *
    1064   * Also contains an icon by default. Shown to teachers and admin only.
    1065   *
    1066   * @param string $text The text to be displayed for the link
    1067   * @return string The link to user documentation for this current page
    1068   */
    1069  function page_doc_link($text='') {
    1070      global $OUTPUT, $PAGE;
    1071      $path = page_get_doc_link_path($PAGE);
    1072      if (!$path) {
    1073          return '';
    1074      }
    1075      return $OUTPUT->doc_link($path, $text);
    1076  }
    1077  
    1078  /**
    1079   * Returns the path to use when constructing a link to the docs.
    1080   *
    1081   * @since Moodle 2.5.1 2.6
    1082   * @param moodle_page $page
    1083   * @return string
    1084   */
    1085  function page_get_doc_link_path(moodle_page $page) {
    1086      global $CFG;
    1087  
    1088      if (empty($CFG->docroot) || during_initial_install()) {
    1089          return '';
    1090      }
    1091      if (!has_capability('moodle/site:doclinks', $page->context)) {
    1092          return '';
    1093      }
    1094  
    1095      $path = $page->docspath;
    1096      if (!$path) {
    1097          return '';
    1098      }
    1099      return $path;
    1100  }
    1101  
    1102  
    1103  /**
    1104   * Validates an email to make sure it makes sense.
    1105   *
    1106   * @param string $address The email address to validate.
    1107   * @return boolean
    1108   */
    1109  function validate_email($address) {
    1110      global $CFG;
    1111      require_once($CFG->libdir.'/phpmailer/moodle_phpmailer.php');
    1112  
    1113      return moodle_phpmailer::validateAddress($address) && !preg_match('/[<>]/', $address);
    1114  }
    1115  
    1116  /**
    1117   * Extracts file argument either from file parameter or PATH_INFO
    1118   *
    1119   * Note: $scriptname parameter is not needed anymore
    1120   *
    1121   * @return string file path (only safe characters)
    1122   */
    1123  function get_file_argument() {
    1124      global $SCRIPT;
    1125  
    1126      $relativepath = false;
    1127      $hasforcedslashargs = false;
    1128  
    1129      if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
    1130          // Checks whether $_SERVER['REQUEST_URI'] contains '/pluginfile.php/'
    1131          // instead of '/pluginfile.php?', when serving a file from e.g. mod_imscp or mod_scorm.
    1132          if ((strpos($_SERVER['REQUEST_URI'], '/pluginfile.php/') !== false)
    1133                  && isset($_SERVER['PATH_INFO']) && !empty($_SERVER['PATH_INFO'])) {
    1134              // Exclude edge cases like '/pluginfile.php/?file='.
    1135              $args = explode('/', ltrim($_SERVER['PATH_INFO'], '/'));
    1136              $hasforcedslashargs = (count($args) > 2); // Always at least: context, component and filearea.
    1137          }
    1138      }
    1139      if (!$hasforcedslashargs) {
    1140          $relativepath = optional_param('file', false, PARAM_PATH);
    1141      }
    1142  
    1143      if ($relativepath !== false and $relativepath !== '') {
    1144          return $relativepath;
    1145      }
    1146      $relativepath = false;
    1147  
    1148      // Then try extract file from the slasharguments.
    1149      if (stripos($_SERVER['SERVER_SOFTWARE'], 'iis') !== false) {
    1150          // NOTE: IIS tends to convert all file paths to single byte DOS encoding,
    1151          //       we can not use other methods because they break unicode chars,
    1152          //       the only ways are to use URL rewriting
    1153          //       OR
    1154          //       to properly set the 'FastCGIUtf8ServerVariables' registry key.
    1155          if (isset($_SERVER['PATH_INFO']) and $_SERVER['PATH_INFO'] !== '') {
    1156              // Check that PATH_INFO works == must not contain the script name.
    1157              if (strpos($_SERVER['PATH_INFO'], $SCRIPT) === false) {
    1158                  $relativepath = clean_param(urldecode($_SERVER['PATH_INFO']), PARAM_PATH);
    1159              }
    1160          }
    1161      } else {
    1162          // All other apache-like servers depend on PATH_INFO.
    1163          if (isset($_SERVER['PATH_INFO'])) {
    1164              if (isset($_SERVER['SCRIPT_NAME']) and strpos($_SERVER['PATH_INFO'], $_SERVER['SCRIPT_NAME']) === 0) {
    1165                  $relativepath = substr($_SERVER['PATH_INFO'], strlen($_SERVER['SCRIPT_NAME']));
    1166              } else {
    1167                  $relativepath = $_SERVER['PATH_INFO'];
    1168              }
    1169              $relativepath = clean_param($relativepath, PARAM_PATH);
    1170          }
    1171      }
    1172  
    1173      return $relativepath;
    1174  }
    1175  
    1176  /**
    1177   * Just returns an array of text formats suitable for a popup menu
    1178   *
    1179   * @return array
    1180   */
    1181  function format_text_menu() {
    1182      return array (FORMAT_MOODLE => get_string('formattext'),
    1183                    FORMAT_HTML => get_string('formathtml'),
    1184                    FORMAT_PLAIN => get_string('formatplain'),
    1185                    FORMAT_MARKDOWN => get_string('formatmarkdown'));
    1186  }
    1187  
    1188  /**
    1189   * Given text in a variety of format codings, this function returns the text as safe HTML.
    1190   *
    1191   * This function should mainly be used for long strings like posts,
    1192   * answers, glossary items etc. For short strings {@link format_string()}.
    1193   *
    1194   * <pre>
    1195   * Options:
    1196   *      trusted     :   If true the string won't be cleaned. Default false required noclean=true.
    1197   *      noclean     :   If true the string won't be cleaned, unless $CFG->forceclean is set. Default false required trusted=true.
    1198   *      nocache     :   If true the strign will not be cached and will be formatted every call. Default false.
    1199   *      filter      :   If true the string will be run through applicable filters as well. Default true.
    1200   *      para        :   If true then the returned string will be wrapped in div tags. Default true.
    1201   *      newlines    :   If true then lines newline breaks will be converted to HTML newline breaks. Default true.
    1202   *      context     :   The context that will be used for filtering.
    1203   *      overflowdiv :   If set to true the formatted text will be encased in a div
    1204   *                      with the class no-overflow before being returned. Default false.
    1205   *      allowid     :   If true then id attributes will not be removed, even when
    1206   *                      using htmlpurifier. Default false.
    1207   *      blanktarget :   If true all <a> tags will have target="_blank" added unless target is explicitly specified.
    1208   * </pre>
    1209   *
    1210   * @staticvar array $croncache
    1211   * @param string $text The text to be formatted. This is raw text originally from user input.
    1212   * @param int $format Identifier of the text format to be used
    1213   *            [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_MARKDOWN]
    1214   * @param object/array $options text formatting options
    1215   * @param int $courseiddonotuse deprecated course id, use context option instead
    1216   * @return string
    1217   */
    1218  function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseiddonotuse = null) {
    1219      global $CFG, $DB, $PAGE;
    1220  
    1221      if ($text === '' || is_null($text)) {
    1222          // No need to do any filters and cleaning.
    1223          return '';
    1224      }
    1225  
    1226      // Detach object, we can not modify it.
    1227      $options = (array)$options;
    1228  
    1229      if (!isset($options['trusted'])) {
    1230          $options['trusted'] = false;
    1231      }
    1232      if (!isset($options['noclean'])) {
    1233          if ($options['trusted'] and trusttext_active()) {
    1234              // No cleaning if text trusted and noclean not specified.
    1235              $options['noclean'] = true;
    1236          } else {
    1237              $options['noclean'] = false;
    1238          }
    1239      }
    1240      if (!empty($CFG->forceclean)) {
    1241          // Whatever the caller claims, the admin wants all content cleaned anyway.
    1242          $options['noclean'] = false;
    1243      }
    1244      if (!isset($options['nocache'])) {
    1245          $options['nocache'] = false;
    1246      }
    1247      if (!isset($options['filter'])) {
    1248          $options['filter'] = true;
    1249      }
    1250      if (!isset($options['para'])) {
    1251          $options['para'] = true;
    1252      }
    1253      if (!isset($options['newlines'])) {
    1254          $options['newlines'] = true;
    1255      }
    1256      if (!isset($options['overflowdiv'])) {
    1257          $options['overflowdiv'] = false;
    1258      }
    1259      $options['blanktarget'] = !empty($options['blanktarget']);
    1260  
    1261      // Calculate best context.
    1262      if (empty($CFG->version) or $CFG->version < 2013051400 or during_initial_install()) {
    1263          // Do not filter anything during installation or before upgrade completes.
    1264          $context = null;
    1265  
    1266      } else if (isset($options['context'])) { // First by explicit passed context option.
    1267          if (is_object($options['context'])) {
    1268              $context = $options['context'];
    1269          } else {
    1270              $context = context::instance_by_id($options['context']);
    1271          }
    1272      } else if ($courseiddonotuse) {
    1273          // Legacy courseid.
    1274          $context = context_course::instance($courseiddonotuse);
    1275      } else {
    1276          // Fallback to $PAGE->context this may be problematic in CLI and other non-standard pages :-(.
    1277          $context = $PAGE->context;
    1278      }
    1279  
    1280      if (!$context) {
    1281          // Either install/upgrade or something has gone really wrong because context does not exist (yet?).
    1282          $options['nocache'] = true;
    1283          $options['filter']  = false;
    1284      }
    1285  
    1286      if ($options['filter']) {
    1287          $filtermanager = filter_manager::instance();
    1288          $filtermanager->setup_page_for_filters($PAGE, $context); // Setup global stuff filters may have.
    1289          $filteroptions = array(
    1290              'originalformat' => $format,
    1291              'noclean' => $options['noclean'],
    1292          );
    1293      } else {
    1294          $filtermanager = new null_filter_manager();
    1295          $filteroptions = array();
    1296      }
    1297  
    1298      switch ($format) {
    1299          case FORMAT_HTML:
    1300              if (!$options['noclean']) {
    1301                  $text = clean_text($text, FORMAT_HTML, $options);
    1302              }
    1303              $text = $filtermanager->filter_text($text, $context, $filteroptions);
    1304              break;
    1305  
    1306          case FORMAT_PLAIN:
    1307              $text = s($text); // Cleans dangerous JS.
    1308              $text = rebuildnolinktag($text);
    1309              $text = str_replace('  ', '&nbsp; ', $text);
    1310              $text = nl2br($text);
    1311              break;
    1312  
    1313          case FORMAT_WIKI:
    1314              // This format is deprecated.
    1315              $text = '<p>NOTICE: Wiki-like formatting has been removed from Moodle.  You should not be seeing
    1316                       this message as all texts should have been converted to Markdown format instead.
    1317                       Please post a bug report to http://moodle.org/bugs with information about where you
    1318                       saw this message.</p>'.s($text);
    1319              break;
    1320  
    1321          case FORMAT_MARKDOWN:
    1322              $text = markdown_to_html($text);
    1323              if (!$options['noclean']) {
    1324                  $text = clean_text($text, FORMAT_HTML, $options);
    1325              }
    1326              $text = $filtermanager->filter_text($text, $context, $filteroptions);
    1327              break;
    1328  
    1329          default:  // FORMAT_MOODLE or anything else.
    1330              $text = text_to_html($text, null, $options['para'], $options['newlines']);
    1331              if (!$options['noclean']) {
    1332                  $text = clean_text($text, FORMAT_HTML, $options);
    1333              }
    1334              $text = $filtermanager->filter_text($text, $context, $filteroptions);
    1335              break;
    1336      }
    1337      if ($options['filter']) {
    1338          // At this point there should not be any draftfile links any more,
    1339          // this happens when developers forget to post process the text.
    1340          // The only potential problem is that somebody might try to format
    1341          // the text before storing into database which would be itself big bug..
    1342          $text = str_replace("\"$CFG->wwwroot/draftfile.php", "\"$CFG->wwwroot/brokenfile.php#", $text);
    1343  
    1344          if ($CFG->debugdeveloper) {
    1345              if (strpos($text, '@@PLUGINFILE@@/') !== false) {
    1346                  debugging('Before calling format_text(), the content must be processed with file_rewrite_pluginfile_urls()',
    1347                      DEBUG_DEVELOPER);
    1348              }
    1349          }
    1350      }
    1351  
    1352      if (!empty($options['overflowdiv'])) {
    1353          $text = html_writer::tag('div', $text, array('class' => 'no-overflow'));
    1354      }
    1355  
    1356      if ($options['blanktarget']) {
    1357          $domdoc = new DOMDocument();
    1358          libxml_use_internal_errors(true);
    1359          $domdoc->loadHTML('<?xml version="1.0" encoding="UTF-8" ?>' . $text);
    1360          libxml_clear_errors();
    1361          foreach ($domdoc->getElementsByTagName('a') as $link) {
    1362              if ($link->hasAttribute('target') && strpos($link->getAttribute('target'), '_blank') === false) {
    1363                  continue;
    1364              }
    1365              $link->setAttribute('target', '_blank');
    1366              if (strpos($link->getAttribute('rel'), 'noreferrer') === false) {
    1367                  $link->setAttribute('rel', trim($link->getAttribute('rel') . ' noreferrer'));
    1368              }
    1369          }
    1370  
    1371          // This regex is nasty and I don't like it. The correct way to solve this is by loading the HTML like so:
    1372          // $domdoc->loadHTML($text, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); however it seems like the libxml
    1373          // version that travis uses doesn't work properly and ends up leaving <html><body>, so I'm forced to use
    1374          // this regex to remove those tags.
    1375          $text = trim(preg_replace('~<(?:!DOCTYPE|/?(?:html|body))[^>]*>\s*~i', '', $domdoc->saveHTML($domdoc->documentElement)));
    1376      }
    1377  
    1378      return $text;
    1379  }
    1380  
    1381  /**
    1382   * Resets some data related to filters, called during upgrade or when general filter settings change.
    1383   *
    1384   * @param bool $phpunitreset true means called from our PHPUnit integration test reset
    1385   * @return void
    1386   */
    1387  function reset_text_filters_cache($phpunitreset = false) {
    1388      global $CFG, $DB;
    1389  
    1390      if ($phpunitreset) {
    1391          // HTMLPurifier does not change, DB is already reset to defaults,
    1392          // nothing to do here, the dataroot was cleared too.
    1393          return;
    1394      }
    1395  
    1396      // The purge_all_caches() deals with cachedir and localcachedir purging,
    1397      // the individual filter caches are invalidated as necessary elsewhere.
    1398  
    1399      // Update $CFG->filterall cache flag.
    1400      if (empty($CFG->stringfilters)) {
    1401          set_config('filterall', 0);
    1402          return;
    1403      }
    1404      $installedfilters = core_component::get_plugin_list('filter');
    1405      $filters = explode(',', $CFG->stringfilters);
    1406      foreach ($filters as $filter) {
    1407          if (isset($installedfilters[$filter])) {
    1408              set_config('filterall', 1);
    1409              return;
    1410          }
    1411      }
    1412      set_config('filterall', 0);
    1413  }
    1414  
    1415  /**
    1416   * Given a simple string, this function returns the string
    1417   * processed by enabled string filters if $CFG->filterall is enabled
    1418   *
    1419   * This function should be used to print short strings (non html) that
    1420   * need filter processing e.g. activity titles, post subjects,
    1421   * glossary concepts.
    1422   *
    1423   * @staticvar bool $strcache
    1424   * @param string $string The string to be filtered. Should be plain text, expect
    1425   * possibly for multilang tags.
    1426   * @param boolean $striplinks To strip any link in the result text. Moodle 1.8 default changed from false to true! MDL-8713
    1427   * @param array $options options array/object or courseid
    1428   * @return string
    1429   */
    1430  function format_string($string, $striplinks = true, $options = null) {
    1431      global $CFG, $PAGE;
    1432  
    1433      // We'll use a in-memory cache here to speed up repeated strings.
    1434      static $strcache = false;
    1435  
    1436      if (empty($CFG->version) or $CFG->version < 2013051400 or during_initial_install()) {
    1437          // Do not filter anything during installation or before upgrade completes.
    1438          return $string = strip_tags($string);
    1439      }
    1440  
    1441      if ($strcache === false or count($strcache) > 2000) {
    1442          // This number might need some tuning to limit memory usage in cron.
    1443          $strcache = array();
    1444      }
    1445  
    1446      if (is_numeric($options)) {
    1447          // Legacy courseid usage.
    1448          $options  = array('context' => context_course::instance($options));
    1449      } else {
    1450          // Detach object, we can not modify it.
    1451          $options = (array)$options;
    1452      }
    1453  
    1454      if (empty($options['context'])) {
    1455          // Fallback to $PAGE->context this may be problematic in CLI and other non-standard pages :-(.
    1456          $options['context'] = $PAGE->context;
    1457      } else if (is_numeric($options['context'])) {
    1458          $options['context'] = context::instance_by_id($options['context']);
    1459      }
    1460      if (!isset($options['filter'])) {
    1461          $options['filter'] = true;
    1462      }
    1463  
    1464      $options['escape'] = !isset($options['escape']) || $options['escape'];
    1465  
    1466      if (!$options['context']) {
    1467          // We did not find any context? weird.
    1468          return $string = strip_tags($string);
    1469      }
    1470  
    1471      // Calculate md5.
    1472      $cachekeys = array($string, $striplinks, $options['context']->id,
    1473          $options['escape'], current_language(), $options['filter']);
    1474      $md5 = md5(implode('<+>', $cachekeys));
    1475  
    1476      // Fetch from cache if possible.
    1477      if (isset($strcache[$md5])) {
    1478          return $strcache[$md5];
    1479      }
    1480  
    1481      // First replace all ampersands not followed by html entity code
    1482      // Regular expression moved to its own method for easier unit testing.
    1483      $string = $options['escape'] ? replace_ampersands_not_followed_by_entity($string) : $string;
    1484  
    1485      if (!empty($CFG->filterall) && $options['filter']) {
    1486          $filtermanager = filter_manager::instance();
    1487          $filtermanager->setup_page_for_filters($PAGE, $options['context']); // Setup global stuff filters may have.
    1488          $string = $filtermanager->filter_string($string, $options['context']);
    1489      }
    1490  
    1491      // If the site requires it, strip ALL tags from this string.
    1492      if (!empty($CFG->formatstringstriptags)) {
    1493          if ($options['escape']) {
    1494              $string = str_replace(array('<', '>'), array('&lt;', '&gt;'), strip_tags($string));
    1495          } else {
    1496              $string = strip_tags($string);
    1497          }
    1498      } else {
    1499          // Otherwise strip just links if that is required (default).
    1500          if ($striplinks) {
    1501              // Strip links in string.
    1502              $string = strip_links($string);
    1503          }
    1504          $string = clean_text($string);
    1505      }
    1506  
    1507      // Store to cache.
    1508      $strcache[$md5] = $string;
    1509  
    1510      return $string;
    1511  }
    1512  
    1513  /**
    1514   * Given a string, performs a negative lookahead looking for any ampersand character
    1515   * that is not followed by a proper HTML entity. If any is found, it is replaced
    1516   * by &amp;. The string is then returned.
    1517   *
    1518   * @param string $string
    1519   * @return string
    1520   */
    1521  function replace_ampersands_not_followed_by_entity($string) {
    1522      return preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&amp;", $string);
    1523  }
    1524  
    1525  /**
    1526   * Given a string, replaces all <a>.*</a> by .* and returns the string.
    1527   *
    1528   * @param string $string
    1529   * @return string
    1530   */
    1531  function strip_links($string) {
    1532      return preg_replace('/(<a\s[^>]+?>)(.+?)(<\/a>)/is', '$2', $string);
    1533  }
    1534  
    1535  /**
    1536   * This expression turns links into something nice in a text format. (Russell Jungwirth)
    1537   *
    1538   * @param string $string
    1539   * @return string
    1540   */
    1541  function wikify_links($string) {
    1542      return preg_replace('~(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)~i', '$3 [ $2 ]', $string);
    1543  }
    1544  
    1545  /**
    1546   * Given text in a variety of format codings, this function returns the text as plain text suitable for plain email.
    1547   *
    1548   * @param string $text The text to be formatted. This is raw text originally from user input.
    1549   * @param int $format Identifier of the text format to be used
    1550   *            [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN]
    1551   * @return string
    1552   */
    1553  function format_text_email($text, $format) {
    1554  
    1555      switch ($format) {
    1556  
    1557          case FORMAT_PLAIN:
    1558              return $text;
    1559              break;
    1560  
    1561          case FORMAT_WIKI:
    1562              // There should not be any of these any more!
    1563              $text = wikify_links($text);
    1564              return core_text::entities_to_utf8(strip_tags($text), true);
    1565              break;
    1566  
    1567          case FORMAT_HTML:
    1568              return html_to_text($text);
    1569              break;
    1570  
    1571          case FORMAT_MOODLE:
    1572          case FORMAT_MARKDOWN:
    1573          default:
    1574              $text = wikify_links($text);
    1575              return core_text::entities_to_utf8(strip_tags($text), true);
    1576              break;
    1577      }
    1578  }
    1579  
    1580  /**
    1581   * Formats activity intro text
    1582   *
    1583   * @param string $module name of module
    1584   * @param object $activity instance of activity
    1585   * @param int $cmid course module id
    1586   * @param bool $filter filter resulting html text
    1587   * @return string
    1588   */
    1589  function format_module_intro($module, $activity, $cmid, $filter=true) {
    1590      global $CFG;
    1591      require_once("$CFG->libdir/filelib.php");
    1592      $context = context_module::instance($cmid);
    1593      $options = array('noclean' => true, 'para' => false, 'filter' => $filter, 'context' => $context, 'overflowdiv' => true);
    1594      $intro = file_rewrite_pluginfile_urls($activity->intro, 'pluginfile.php', $context->id, 'mod_'.$module, 'intro', null);
    1595      return trim(format_text($intro, $activity->introformat, $options, null));
    1596  }
    1597  
    1598  /**
    1599   * Removes the usage of Moodle files from a text.
    1600   *
    1601   * In some rare cases we need to re-use a text that already has embedded links
    1602   * to some files hosted within Moodle. But the new area in which we will push
    1603   * this content does not support files... therefore we need to remove those files.
    1604   *
    1605   * @param string $source The text
    1606   * @return string The stripped text
    1607   */
    1608  function strip_pluginfile_content($source) {
    1609      $baseurl = '@@PLUGINFILE@@';
    1610      // Looking for something like < .* "@@pluginfile@@.*" .* >
    1611      $pattern = '$<[^<>]+["\']' . $baseurl . '[^"\']*["\'][^<>]*>$';
    1612      $stripped = preg_replace($pattern, '', $source);
    1613      // Use purify html to rebalence potentially mismatched tags and generally cleanup.
    1614      return purify_html($stripped);
    1615  }
    1616  
    1617  /**
    1618   * Legacy function, used for cleaning of old forum and glossary text only.
    1619   *
    1620   * @param string $text text that may contain legacy TRUSTTEXT marker
    1621   * @return string text without legacy TRUSTTEXT marker
    1622   */
    1623  function trusttext_strip($text) {
    1624      if (!is_string($text)) {
    1625          // This avoids the potential for an endless loop below.
    1626          throw new coding_exception('trusttext_strip parameter must be a string');
    1627      }
    1628      while (true) { // Removing nested TRUSTTEXT.
    1629          $orig = $text;
    1630          $text = str_replace('#####TRUSTTEXT#####', '', $text);
    1631          if (strcmp($orig, $text) === 0) {
    1632              return $text;
    1633          }
    1634      }
    1635  }
    1636  
    1637  /**
    1638   * Must be called before editing of all texts with trust flag. Removes all XSS nasties from texts stored in database if needed.
    1639   *
    1640   * @param stdClass $object data object with xxx, xxxformat and xxxtrust fields
    1641   * @param string $field name of text field
    1642   * @param context $context active context
    1643   * @return stdClass updated $object
    1644   */
    1645  function trusttext_pre_edit($object, $field, $context) {
    1646      $trustfield  = $field.'trust';
    1647      $formatfield = $field.'format';
    1648  
    1649      if (!$object->$trustfield or !trusttext_trusted($context)) {
    1650          $object->$field = clean_text($object->$field, $object->$formatfield);
    1651      }
    1652  
    1653      return $object;
    1654  }
    1655  
    1656  /**
    1657   * Is current user trusted to enter no dangerous XSS in this context?
    1658   *
    1659   * Please note the user must be in fact trusted everywhere on this server!!
    1660   *
    1661   * @param context $context
    1662   * @return bool true if user trusted
    1663   */
    1664  function trusttext_trusted($context) {
    1665      return (trusttext_active() and has_capability('moodle/site:trustcontent', $context));
    1666  }
    1667  
    1668  /**
    1669   * Is trusttext feature active?
    1670   *
    1671   * @return bool
    1672   */
    1673  function trusttext_active() {
    1674      global $CFG;
    1675  
    1676      return !empty($CFG->enabletrusttext);
    1677  }
    1678  
    1679  /**
    1680   * Cleans raw text removing nasties.
    1681   *
    1682   * Given raw text (eg typed in by a user) this function cleans it up and removes any nasty tags that could mess up
    1683   * Moodle pages through XSS attacks.
    1684   *
    1685   * The result must be used as a HTML text fragment, this function can not cleanup random
    1686   * parts of html tags such as url or src attributes.
    1687   *
    1688   * NOTE: the format parameter was deprecated because we can safely clean only HTML.
    1689   *
    1690   * @param string $text The text to be cleaned
    1691   * @param int|string $format deprecated parameter, should always contain FORMAT_HTML or FORMAT_MOODLE
    1692   * @param array $options Array of options; currently only option supported is 'allowid' (if true,
    1693   *   does not remove id attributes when cleaning)
    1694   * @return string The cleaned up text
    1695   */
    1696  function clean_text($text, $format = FORMAT_HTML, $options = array()) {
    1697      $text = (string)$text;
    1698  
    1699      if ($format != FORMAT_HTML and $format != FORMAT_HTML) {
    1700          // TODO: we need to standardise cleanup of text when loading it into editor first.
    1701          // debugging('clean_text() is designed to work only with html');.
    1702      }
    1703  
    1704      if ($format == FORMAT_PLAIN) {
    1705          return $text;
    1706      }
    1707  
    1708      if (is_purify_html_necessary($text)) {
    1709          $text = purify_html($text, $options);
    1710      }
    1711  
    1712      // Originally we tried to neutralise some script events here, it was a wrong approach because
    1713      // it was trivial to work around that (for example using style based XSS exploits).
    1714      // We must not give false sense of security here - all developers MUST understand how to use
    1715      // rawurlencode(), htmlentities(), htmlspecialchars(), p(), s(), moodle_url, html_writer and friends!!!
    1716  
    1717      return $text;
    1718  }
    1719  
    1720  /**
    1721   * Is it necessary to use HTMLPurifier?
    1722   *
    1723   * @private
    1724   * @param string $text
    1725   * @return bool false means html is safe and valid, true means use HTMLPurifier
    1726   */
    1727  function is_purify_html_necessary($text) {
    1728      if ($text === '') {
    1729          return false;
    1730      }
    1731  
    1732      if ($text === (string)((int)$text)) {
    1733          return false;
    1734      }
    1735  
    1736      if (strpos($text, '&') !== false or preg_match('|<[^pesb/]|', $text)) {
    1737          // We need to normalise entities or other tags except p, em, strong and br present.
    1738          return true;
    1739      }
    1740  
    1741      $altered = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8', true);
    1742      if ($altered === $text) {
    1743          // No < > or other special chars means this must be safe.
    1744          return false;
    1745      }
    1746  
    1747      // Let's try to convert back some safe html tags.
    1748      $altered = preg_replace('|&lt;p&gt;(.*?)&lt;/p&gt;|m', '<p>$1</p>', $altered);
    1749      if ($altered === $text) {
    1750          return false;
    1751      }
    1752      $altered = preg_replace('|&lt;em&gt;([^<>]+?)&lt;/em&gt;|m', '<em>$1</em>', $altered);
    1753      if ($altered === $text) {
    1754          return false;
    1755      }
    1756      $altered = preg_replace('|&lt;strong&gt;([^<>]+?)&lt;/strong&gt;|m', '<strong>$1</strong>', $altered);
    1757      if ($altered === $text) {
    1758          return false;
    1759      }
    1760      $altered = str_replace('&lt;br /&gt;', '<br />', $altered);
    1761      if ($altered === $text) {
    1762          return false;
    1763      }
    1764  
    1765      return true;
    1766  }
    1767  
    1768  /**
    1769   * KSES replacement cleaning function - uses HTML Purifier.
    1770   *
    1771   * @param string $text The (X)HTML string to purify
    1772   * @param array $options Array of options; currently only option supported is 'allowid' (if set,
    1773   *   does not remove id attributes when cleaning)
    1774   * @return string
    1775   */
    1776  function purify_html($text, $options = array()) {
    1777      global $CFG;
    1778  
    1779      $text = (string)$text;
    1780  
    1781      static $purifiers = array();
    1782      static $caches = array();
    1783  
    1784      // Purifier code can change only during major version upgrade.
    1785      $version = empty($CFG->version) ? 0 : $CFG->version;
    1786      $cachedir = "$CFG->localcachedir/htmlpurifier/$version";
    1787      if (!file_exists($cachedir)) {
    1788          // Purging of caches may remove the cache dir at any time,
    1789          // luckily file_exists() results should be cached for all existing directories.
    1790          $purifiers = array();
    1791          $caches = array();
    1792          gc_collect_cycles();
    1793  
    1794          make_localcache_directory('htmlpurifier', false);
    1795          check_dir_exists($cachedir);
    1796      }
    1797  
    1798      $allowid = empty($options['allowid']) ? 0 : 1;
    1799      $allowobjectembed = empty($CFG->allowobjectembed) ? 0 : 1;
    1800  
    1801      $type = 'type_'.$allowid.'_'.$allowobjectembed;
    1802  
    1803      if (!array_key_exists($type, $caches)) {
    1804          $caches[$type] = cache::make('core', 'htmlpurifier', array('type' => $type));
    1805      }
    1806      $cache = $caches[$type];
    1807  
    1808      // Add revision number and all options to the text key so that it is compatible with local cluster node caches.
    1809      $key = "|$version|$allowobjectembed|$allowid|$text";
    1810      $filteredtext = $cache->get($key);
    1811  
    1812      if ($filteredtext === true) {
    1813          // The filtering did not change the text last time, no need to filter anything again.
    1814          return $text;
    1815      } else if ($filteredtext !== false) {
    1816          return $filteredtext;
    1817      }
    1818  
    1819      if (empty($purifiers[$type])) {
    1820          require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.safe-includes.php';
    1821          require_once $CFG->libdir.'/htmlpurifier/locallib.php';
    1822          $config = HTMLPurifier_Config::createDefault();
    1823  
    1824          $config->set('HTML.DefinitionID', 'moodlehtml');
    1825          $config->set('HTML.DefinitionRev', 6);
    1826          $config->set('Cache.SerializerPath', $cachedir);
    1827          $config->set('Cache.SerializerPermissions', $CFG->directorypermissions);
    1828          $config->set('Core.NormalizeNewlines', false);
    1829          $config->set('Core.ConvertDocumentToFragment', true);
    1830          $config->set('Core.Encoding', 'UTF-8');
    1831          $config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
    1832          $config->set('URI.AllowedSchemes', array(
    1833              'http' => true,
    1834              'https' => true,
    1835              'ftp' => true,
    1836              'irc' => true,
    1837              'nntp' => true,
    1838              'news' => true,
    1839              'rtsp' => true,
    1840              'rtmp' => true,
    1841              'teamspeak' => true,
    1842              'gopher' => true,
    1843              'mms' => true,
    1844              'mailto' => true
    1845          ));
    1846          $config->set('Attr.AllowedFrameTargets', array('_blank'));
    1847  
    1848          if ($allowobjectembed) {
    1849              $config->set('HTML.SafeObject', true);
    1850              $config->set('Output.FlashCompat', true);
    1851              $config->set('HTML.SafeEmbed', true);
    1852          }
    1853  
    1854          if ($allowid) {
    1855              $config->set('Attr.EnableID', true);
    1856          }
    1857  
    1858          if ($def = $config->maybeGetRawHTMLDefinition()) {
    1859              $def->addElement('nolink', 'Inline', 'Flow', array());                      // Skip our filters inside.
    1860              $def->addElement('tex', 'Inline', 'Inline', array());                       // Tex syntax, equivalent to $$xx$$.
    1861              $def->addElement('algebra', 'Inline', 'Inline', array());                   // Algebra syntax, equivalent to @@xx@@.
    1862              $def->addElement('lang', 'Block', 'Flow', array(), array('lang'=>'CDATA')); // Original multilang style - only our hacked lang attribute.
    1863              $def->addAttribute('span', 'xxxlang', 'CDATA');                             // Current very problematic multilang.
    1864  
    1865              // Media elements.
    1866              // https://html.spec.whatwg.org/#the-video-element
    1867              $def->addElement('video', 'Block', 'Optional: #PCDATA | Flow | source | track', 'Common', [
    1868                  'src' => 'URI',
    1869                  'crossorigin' => 'Enum#anonymous,use-credentials',
    1870                  'poster' => 'URI',
    1871                  'preload' => 'Enum#auto,metadata,none',
    1872                  'autoplay' => 'Bool',
    1873                  'playsinline' => 'Bool',
    1874                  'loop' => 'Bool',
    1875                  'muted' => 'Bool',
    1876                  'controls' => 'Bool',
    1877                  'width' => 'Length',
    1878                  'height' => 'Length',
    1879              ]);
    1880              // https://html.spec.whatwg.org/#the-audio-element
    1881              $def->addElement('audio', 'Block', 'Optional: #PCDATA | Flow | source | track', 'Common', [
    1882                  'src' => 'URI',
    1883                  'crossorigin' => 'Enum#anonymous,use-credentials',
    1884                  'preload' => 'Enum#auto,metadata,none',
    1885                  'autoplay' => 'Bool',
    1886                  'loop' => 'Bool',
    1887                  'muted' => 'Bool',
    1888                  'controls' => 'Bool'
    1889              ]);
    1890              // https://html.spec.whatwg.org/#the-source-element
    1891              $def->addElement('source', false, 'Empty', null, [
    1892                  'src' => 'URI',
    1893                  'type' => 'Text'
    1894              ]);
    1895              // https://html.spec.whatwg.org/#the-track-element
    1896              $def->addElement('track', false, 'Empty', null, [
    1897                  'src' => 'URI',
    1898                  'kind' => 'Enum#subtitles,captions,descriptions,chapters,metadata',
    1899                  'srclang' => 'Text',
    1900                  'label' => 'Text',
    1901                  'default' => 'Bool',
    1902              ]);
    1903  
    1904              // Use the built-in Ruby module to add annotation support.
    1905              $def->manager->addModule(new HTMLPurifier_HTMLModule_Ruby());
    1906          }
    1907  
    1908          $purifier = new HTMLPurifier($config);
    1909          $purifiers[$type] = $purifier;
    1910      } else {
    1911          $purifier = $purifiers[$type];
    1912      }
    1913  
    1914      $multilang = (strpos($text, 'class="multilang"') !== false);
    1915  
    1916      $filteredtext = $text;
    1917      if ($multilang) {
    1918          $filteredtextregex = '/<span(\s+lang="([a-zA-Z0-9_-]+)"|\s+class="multilang"){2}\s*>/';
    1919          $filteredtext = preg_replace($filteredtextregex, '<span xxxlang="$2}">', $filteredtext);
    1920      }
    1921      $filteredtext = (string)$purifier->purify($filteredtext);
    1922      if ($multilang) {
    1923          $filteredtext = preg_replace('/<span xxxlang="([a-zA-Z0-9_-]+)">/', '<span lang="$1}" class="multilang">', $filteredtext);
    1924      }
    1925  
    1926      if ($text === $filteredtext) {
    1927          // No need to store the filtered text, next time we will just return unfiltered text
    1928          // because it was not changed by purifying.
    1929          $cache->set($key, true);
    1930      } else {
    1931          $cache->set($key, $filteredtext);
    1932      }
    1933  
    1934      return $filteredtext;
    1935  }
    1936  
    1937  /**
    1938   * Given plain text, makes it into HTML as nicely as possible.
    1939   *
    1940   * May contain HTML tags already.
    1941   *
    1942   * Do not abuse this function. It is intended as lower level formatting feature used
    1943   * by {@link format_text()} to convert FORMAT_MOODLE to HTML. You are supposed
    1944   * to call format_text() in most of cases.
    1945   *
    1946   * @param string $text The string to convert.
    1947   * @param boolean $smileyignored Was used to determine if smiley characters should convert to smiley images, ignored now
    1948   * @param boolean $para If true then the returned string will be wrapped in div tags
    1949   * @param boolean $newlines If true then lines newline breaks will be converted to HTML newline breaks.
    1950   * @return string
    1951   */
    1952  function text_to_html($text, $smileyignored = null, $para = true, $newlines = true) {
    1953      // Remove any whitespace that may be between HTML tags.
    1954      $text = preg_replace("~>([[:space:]]+)<~i", "><", $text);
    1955  
    1956      // Remove any returns that precede or follow HTML tags.
    1957      $text = preg_replace("~([\n\r])<~i", " <", $text);
    1958      $text = preg_replace("~>([\n\r])~i", "> ", $text);
    1959  
    1960      // Make returns into HTML newlines.
    1961      if ($newlines) {
    1962          $text = nl2br($text);
    1963      }
    1964  
    1965      // Wrap the whole thing in a div if required.
    1966      if ($para) {
    1967          // In 1.9 this was changed from a p => div.
    1968          return '<div class="text_to_html">'.$text.'</div>';
    1969      } else {
    1970          return $text;
    1971      }
    1972  }
    1973  
    1974  /**
    1975   * Given Markdown formatted text, make it into XHTML using external function
    1976   *
    1977   * @param string $text The markdown formatted text to be converted.
    1978   * @return string Converted text
    1979   */
    1980  function markdown_to_html($text) {
    1981      global $CFG;
    1982  
    1983      if ($text === '' or $text === null) {
    1984          return $text;
    1985      }
    1986  
    1987      require_once($CFG->libdir .'/markdown/MarkdownInterface.php');
    1988      require_once($CFG->libdir .'/markdown/Markdown.php');
    1989      require_once($CFG->libdir .'/markdown/MarkdownExtra.php');
    1990  
    1991      return \Michelf\MarkdownExtra::defaultTransform($text);
    1992  }
    1993  
    1994  /**
    1995   * Given HTML text, make it into plain text using external function
    1996   *
    1997   * @param string $html The text to be converted.
    1998   * @param integer $width Width to wrap the text at. (optional, default 75 which
    1999   *      is a good value for email. 0 means do not limit line length.)
    2000   * @param boolean $dolinks By default, any links in the HTML are collected, and
    2001   *      printed as a list at the end of the HTML. If you don't want that, set this
    2002   *      argument to false.
    2003   * @return string plain text equivalent of the HTML.
    2004   */
    2005  function html_to_text($html, $width = 75, $dolinks = true) {
    2006      global $CFG;
    2007  
    2008      require_once($CFG->libdir .'/html2text/lib.php');
    2009  
    2010      $options = array(
    2011          'width'     => $width,
    2012          'do_links'  => 'table',
    2013      );
    2014  
    2015      if (empty($dolinks)) {
    2016          $options['do_links'] = 'none';
    2017      }
    2018      $h2t = new core_html2text($html, $options);
    2019      $result = $h2t->getText();
    2020  
    2021      return $result;
    2022  }
    2023  
    2024  /**
    2025   * Converts texts or strings to plain text.
    2026   *
    2027   * - When used to convert user input introduced in an editor the text format needs to be passed in $contentformat like we usually
    2028   *   do in format_text.
    2029   * - When this function is used for strings that are usually passed through format_string before displaying them
    2030   *   we need to set $contentformat to false. This will execute html_to_text as these strings can contain multilang tags if
    2031   *   multilang filter is applied to headings.
    2032   *
    2033   * @param string $content The text as entered by the user
    2034   * @param int|false $contentformat False for strings or the text format: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN
    2035   * @return string Plain text.
    2036   */
    2037  function content_to_text($content, $contentformat) {
    2038  
    2039      switch ($contentformat) {
    2040          case FORMAT_PLAIN:
    2041              // Nothing here.
    2042              break;
    2043          case FORMAT_MARKDOWN:
    2044              $content = markdown_to_html($content);
    2045              $content = html_to_text($content, 75, false);
    2046              break;
    2047          default:
    2048              // FORMAT_HTML, FORMAT_MOODLE and $contentformat = false, the later one are strings usually formatted through
    2049              // format_string, we need to convert them from html because they can contain HTML (multilang filter).
    2050              $content = html_to_text($content, 75, false);
    2051      }
    2052  
    2053      return trim($content, "\r\n ");
    2054  }
    2055  
    2056  /**
    2057   * Factory method for extracting draft file links from arbitrary text using regular expressions. Only text
    2058   * is required; other file fields may be passed to filter.
    2059   *
    2060   * @param string $text Some html content.
    2061   * @param bool $forcehttps force https urls.
    2062   * @param int $contextid This parameter and the next three identify the file area to save to.
    2063   * @param string $component The component name.
    2064   * @param string $filearea The filearea.
    2065   * @param int $itemid The item id for the filearea.
    2066   * @param string $filename The specific filename of the file.
    2067   * @return array
    2068   */
    2069  function extract_draft_file_urls_from_text($text, $forcehttps = false, $contextid = null, $component = null,
    2070                                             $filearea = null, $itemid = null, $filename = null) {
    2071      global $CFG;
    2072  
    2073      $wwwroot = $CFG->wwwroot;
    2074      if ($forcehttps) {
    2075          $wwwroot = str_replace('http://', 'https://', $wwwroot);
    2076      }
    2077      $urlstring = '/' . preg_quote($wwwroot, '/');
    2078  
    2079      $urlbase = preg_quote('draftfile.php');
    2080      $urlstring .= "\/(?<urlbase>{$urlbase})";
    2081  
    2082      if (is_null($contextid)) {
    2083          $contextid = '[0-9]+';
    2084      }
    2085      $urlstring .= "\/(?<contextid>{$contextid})";
    2086  
    2087      if (is_null($component)) {
    2088          $component = '[a-z_]+';
    2089      }
    2090      $urlstring .= "\/(?<component>{$component})";
    2091  
    2092      if (is_null($filearea)) {
    2093          $filearea = '[a-z_]+';
    2094      }
    2095      $urlstring .= "\/(?<filearea>{$filearea})";
    2096  
    2097      if (is_null($itemid)) {
    2098          $itemid = '[0-9]+';
    2099      }
    2100      $urlstring .= "\/(?<itemid>{$itemid})";
    2101  
    2102      // Filename matching magic based on file_rewrite_urls_to_pluginfile().
    2103      if (is_null($filename)) {
    2104          $filename = '[^\'\",&<>|`\s:\\\\]+';
    2105      }
    2106      $urlstring .= "\/(?<filename>{$filename})/";
    2107  
    2108      // Regular expression which matches URLs and returns their components.
    2109      preg_match_all($urlstring, $text, $urls, PREG_SET_ORDER);
    2110      return $urls;
    2111  }
    2112  
    2113  /**
    2114   * This function will highlight search words in a given string
    2115   *
    2116   * It cares about HTML and will not ruin links.  It's best to use
    2117   * this function after performing any conversions to HTML.
    2118   *
    2119   * @param string $needle The search string. Syntax like "word1 +word2 -word3" is dealt with correctly.
    2120   * @param string $haystack The string (HTML) within which to highlight the search terms.
    2121   * @param boolean $matchcase whether to do case-sensitive. Default case-insensitive.
    2122   * @param string $prefix the string to put before each search term found.
    2123   * @param string $suffix the string to put after each search term found.
    2124   * @return string The highlighted HTML.
    2125   */
    2126  function highlight($needle, $haystack, $matchcase = false,
    2127          $prefix = '<span class="highlight">', $suffix = '</span>') {
    2128  
    2129      // Quick bail-out in trivial cases.
    2130      if (empty($needle) or empty($haystack)) {
    2131          return $haystack;
    2132      }
    2133  
    2134      // Break up the search term into words, discard any -words and build a regexp.
    2135      $words = preg_split('/ +/', trim($needle));
    2136      foreach ($words as $index => $word) {
    2137          if (strpos($word, '-') === 0) {
    2138              unset($words[$index]);
    2139          } else if (strpos($word, '+') === 0) {
    2140              $words[$index] = '\b' . preg_quote(ltrim($word, '+'), '/') . '\b'; // Match only as a complete word.
    2141          } else {
    2142              $words[$index] = preg_quote($word, '/');
    2143          }
    2144      }
    2145      $regexp = '/(' . implode('|', $words) . ')/u'; // Char u is to do UTF-8 matching.
    2146      if (!$matchcase) {
    2147          $regexp .= 'i';
    2148      }
    2149  
    2150      // Another chance to bail-out if $search was only -words.
    2151      if (empty($words)) {
    2152          return $haystack;
    2153      }
    2154  
    2155      // Split the string into HTML tags and real content.
    2156      $chunks = preg_split('/((?:<[^>]*>)+)/', $haystack, -1, PREG_SPLIT_DELIM_CAPTURE);
    2157  
    2158      // We have an array of alternating blocks of text, then HTML tags, then text, ...
    2159      // Loop through replacing search terms in the text, and leaving the HTML unchanged.
    2160      $ishtmlchunk = false;
    2161      $result = '';
    2162      foreach ($chunks as $chunk) {
    2163          if ($ishtmlchunk) {
    2164              $result .= $chunk;
    2165          } else {
    2166              $result .= preg_replace($regexp, $prefix . '$1' . $suffix, $chunk);
    2167          }
    2168          $ishtmlchunk = !$ishtmlchunk;
    2169      }
    2170  
    2171      return $result;
    2172  }
    2173  
    2174  /**
    2175   * This function will highlight instances of $needle in $haystack
    2176   *
    2177   * It's faster that the above function {@link highlight()} and doesn't care about
    2178   * HTML or anything.
    2179   *
    2180   * @param string $needle The string to search for
    2181   * @param string $haystack The string to search for $needle in
    2182   * @return string The highlighted HTML
    2183   */
    2184  function highlightfast($needle, $haystack) {
    2185  
    2186      if (empty($needle) or empty($haystack)) {
    2187          return $haystack;
    2188      }
    2189  
    2190      $parts = explode(core_text::strtolower($needle), core_text::strtolower($haystack));
    2191  
    2192      if (count($parts) === 1) {
    2193          return $haystack;
    2194      }
    2195  
    2196      $pos = 0;
    2197  
    2198      foreach ($parts as $key => $part) {
    2199          $parts[$key] = substr($haystack, $pos, strlen($part));
    2200          $pos += strlen($part);
    2201  
    2202          $parts[$key] .= '<span class="highlight">'.substr($haystack, $pos, strlen($needle)).'</span>';
    2203          $pos += strlen($needle);
    2204      }
    2205  
    2206      return str_replace('<span class="highlight"></span>', '', join('', $parts));
    2207  }
    2208  
    2209  /**
    2210   * Return a string containing 'lang', xml:lang and optionally 'dir' HTML attributes.
    2211   *
    2212   * Internationalisation, for print_header and backup/restorelib.
    2213   *
    2214   * @param bool $dir Default false
    2215   * @return string Attributes
    2216   */
    2217  function get_html_lang($dir = false) {
    2218      $direction = '';
    2219      if ($dir) {
    2220          if (right_to_left()) {
    2221              $direction = ' dir="rtl"';
    2222          } else {
    2223              $direction = ' dir="ltr"';
    2224          }
    2225      }
    2226      // Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag.
    2227      $language = str_replace('_', '-', current_language());
    2228      @header('Content-Language: '.$language);
    2229      return ($direction.' lang="'.$language.'" xml:lang="'.$language.'"');
    2230  }
    2231  
    2232  
    2233  // STANDARD WEB PAGE PARTS.
    2234  
    2235  /**
    2236   * Send the HTTP headers that Moodle requires.
    2237   *
    2238   * There is a backwards compatibility hack for legacy code
    2239   * that needs to add custom IE compatibility directive.
    2240   *
    2241   * Example:
    2242   * <code>
    2243   * if (!isset($CFG->additionalhtmlhead)) {
    2244   *     $CFG->additionalhtmlhead = '';
    2245   * }
    2246   * $CFG->additionalhtmlhead .= '<meta http-equiv="X-UA-Compatible" content="IE=8" />';
    2247   * header('X-UA-Compatible: IE=8');
    2248   * echo $OUTPUT->header();
    2249   * </code>
    2250   *
    2251   * Please note the $CFG->additionalhtmlhead alone might not work,
    2252   * you should send the IE compatibility header() too.
    2253   *
    2254   * @param string $contenttype
    2255   * @param bool $cacheable Can this page be cached on back?
    2256   * @return void, sends HTTP headers
    2257   */
    2258  function send_headers($contenttype, $cacheable = true) {
    2259      global $CFG;
    2260  
    2261      @header('Content-Type: ' . $contenttype);
    2262      @header('Content-Script-Type: text/javascript');
    2263      @header('Content-Style-Type: text/css');
    2264  
    2265      if (empty($CFG->additionalhtmlhead) or stripos($CFG->additionalhtmlhead, 'X-UA-Compatible') === false) {
    2266          @header('X-UA-Compatible: IE=edge');
    2267      }
    2268  
    2269      if ($cacheable) {
    2270          // Allow caching on "back" (but not on normal clicks).
    2271          @header('Cache-Control: private, pre-check=0, post-check=0, max-age=0, no-transform');
    2272          @header('Pragma: no-cache');
    2273          @header('Expires: ');
    2274      } else {
    2275          // Do everything we can to always prevent clients and proxies caching.
    2276          @header('Cache-Control: no-store, no-cache, must-revalidate');
    2277          @header('Cache-Control: post-check=0, pre-check=0, no-transform', false);
    2278          @header('Pragma: no-cache');
    2279          @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
    2280          @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
    2281      }
    2282      @header('Accept-Ranges: none');
    2283  
    2284      // The Moodle app must be allowed to embed content always.
    2285      if (empty($CFG->allowframembedding) && !core_useragent::is_moodle_app()) {
    2286          @header('X-Frame-Options: sameorigin');
    2287      }
    2288  }
    2289  
    2290  /**
    2291   * Return the right arrow with text ('next'), and optionally embedded in a link.
    2292   *
    2293   * @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases).
    2294   * @param string $url An optional link to use in a surrounding HTML anchor.
    2295   * @param bool $accesshide True if text should be hidden (for screen readers only).
    2296   * @param string $addclass Additional class names for the link, or the arrow character.
    2297   * @return string HTML string.
    2298   */
    2299  function link_arrow_right($text, $url='', $accesshide=false, $addclass='', $addparams = []) {
    2300      global $OUTPUT; // TODO: move to output renderer.
    2301      $arrowclass = 'arrow ';
    2302      if (!$url) {
    2303          $arrowclass .= $addclass;
    2304      }
    2305      $arrow = '<span class="'.$arrowclass.'">'.$OUTPUT->rarrow().'</span>';
    2306      $htmltext = '';
    2307      if ($text) {
    2308          $htmltext = '<span class="arrow_text">'.$text.'</span>&nbsp;';
    2309          if ($accesshide) {
    2310              $htmltext = get_accesshide($htmltext);
    2311          }
    2312      }
    2313      if ($url) {
    2314          $class = 'arrow_link';
    2315          if ($addclass) {
    2316              $class .= ' '.$addclass;
    2317          }
    2318  
    2319          $linkparams = [
    2320              'class' => $class,
    2321              'href' => $url,
    2322              'title' => preg_replace('/<.*?>/', '', $text),
    2323          ];
    2324  
    2325          $linkparams += $addparams;
    2326  
    2327          return html_writer::link($url, $htmltext . $arrow, $linkparams);
    2328      }
    2329      return $htmltext.$arrow;
    2330  }
    2331  
    2332  /**
    2333   * Return the left arrow with text ('previous'), and optionally embedded in a link.
    2334   *
    2335   * @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases).
    2336   * @param string $url An optional link to use in a surrounding HTML anchor.
    2337   * @param bool $accesshide True if text should be hidden (for screen readers only).
    2338   * @param string $addclass Additional class names for the link, or the arrow character.
    2339   * @return string HTML string.
    2340   */
    2341  function link_arrow_left($text, $url='', $accesshide=false, $addclass='', $addparams = []) {
    2342      global $OUTPUT; // TODO: move to utput renderer.
    2343      $arrowclass = 'arrow ';
    2344      if (! $url) {
    2345          $arrowclass .= $addclass;
    2346      }
    2347      $arrow = '<span class="'.$arrowclass.'">'.$OUTPUT->larrow().'</span>';
    2348      $htmltext = '';
    2349      if ($text) {
    2350          $htmltext = '&nbsp;<span class="arrow_text">'.$text.'</span>';
    2351          if ($accesshide) {
    2352              $htmltext = get_accesshide($htmltext);
    2353          }
    2354      }
    2355      if ($url) {
    2356          $class = 'arrow_link';
    2357          if ($addclass) {
    2358              $class .= ' '.$addclass;
    2359          }
    2360  
    2361          $linkparams = [
    2362              'class' => $class,
    2363              'href' => $url,
    2364              'title' => preg_replace('/<.*?>/', '', $text),
    2365          ];
    2366  
    2367          $linkparams += $addparams;
    2368  
    2369          return html_writer::link($url, $arrow . $htmltext, $linkparams);
    2370      }
    2371      return $arrow.$htmltext;
    2372  }
    2373  
    2374  /**
    2375   * Return a HTML element with the class "accesshide", for accessibility.
    2376   *
    2377   * Please use cautiously - where possible, text should be visible!
    2378   *
    2379   * @param string $text Plain text.
    2380   * @param string $elem Lowercase element name, default "span".
    2381   * @param string $class Additional classes for the element.
    2382   * @param string $attrs Additional attributes string in the form, "name='value' name2='value2'"
    2383   * @return string HTML string.
    2384   */
    2385  function get_accesshide($text, $elem='span', $class='', $attrs='') {
    2386      return "<$elem class=\"accesshide $class\" $attrs>$text</$elem>";
    2387  }
    2388  
    2389  /**
    2390   * Return the breadcrumb trail navigation separator.
    2391   *
    2392   * @return string HTML string.
    2393   */
    2394  function get_separator() {
    2395      // Accessibility: the 'hidden' slash is preferred for screen readers.
    2396      return ' '.link_arrow_right($text='/', $url='', $accesshide=true, 'sep').' ';
    2397  }
    2398  
    2399  /**
    2400   * Print (or return) a collapsible region, that has a caption that can be clicked to expand or collapse the region.
    2401   *
    2402   * If JavaScript is off, then the region will always be expanded.
    2403   *
    2404   * @param string $contents the contents of the box.
    2405   * @param string $classes class names added to the div that is output.
    2406   * @param string $id id added to the div that is output. Must not be blank.
    2407   * @param string $caption text displayed at the top. Clicking on this will cause the region to expand or contract.
    2408   * @param string $userpref the name of the user preference that stores the user's preferred default state.
    2409   *      (May be blank if you do not wish the state to be persisted.
    2410   * @param boolean $default Initial collapsed state to use if the user_preference it not set.
    2411   * @param boolean $return if true, return the HTML as a string, rather than printing it.
    2412   * @return string|void If $return is false, returns nothing, otherwise returns a string of HTML.
    2413   */
    2414  function print_collapsible_region($contents, $classes, $id, $caption, $userpref = '', $default = false, $return = false) {
    2415      $output  = print_collapsible_region_start($classes, $id, $caption, $userpref, $default, true);
    2416      $output .= $contents;
    2417      $output .= print_collapsible_region_end(true);
    2418  
    2419      if ($return) {
    2420          return $output;
    2421      } else {
    2422          echo $output;
    2423      }
    2424  }
    2425  
    2426  /**
    2427   * Print (or return) the start of a collapsible region
    2428   *
    2429   * The collapsibleregion has a caption that can be clicked to expand or collapse the region. If JavaScript is off, then the region
    2430   * will always be expanded.
    2431   *
    2432   * @param string $classes class names added to the div that is output.
    2433   * @param string $id id added to the div that is output. Must not be blank.
    2434   * @param string $caption text displayed at the top. Clicking on this will cause the region to expand or contract.
    2435   * @param string $userpref the name of the user preference that stores the user's preferred default state.
    2436   *      (May be blank if you do not wish the state to be persisted.
    2437   * @param boolean $default Initial collapsed state to use if the user_preference it not set.
    2438   * @param boolean $return if true, return the HTML as a string, rather than printing it.
    2439   * @return string|void if $return is false, returns nothing, otherwise returns a string of HTML.
    2440   */
    2441  function print_collapsible_region_start($classes, $id, $caption, $userpref = '', $default = false, $return = false) {
    2442      global $PAGE;
    2443  
    2444      // Work out the initial state.
    2445      if (!empty($userpref) and is_string($userpref)) {
    2446          user_preference_allow_ajax_update($userpref, PARAM_BOOL);
    2447          $collapsed = get_user_preferences($userpref, $default);
    2448      } else {
    2449          $collapsed = $default;
    2450          $userpref = false;
    2451      }
    2452  
    2453      if ($collapsed) {
    2454          $classes .= ' collapsed';
    2455      }
    2456  
    2457      $output = '';
    2458      $output .= '<div id="' . $id . '" class="collapsibleregion ' . $classes . '">';
    2459      $output .= '<div id="' . $id . '_sizer">';
    2460      $output .= '<div id="' . $id . '_caption" class="collapsibleregioncaption">';
    2461      $output .= $caption . ' ';
    2462      $output .= '</div><div id="' . $id . '_inner" class="collapsibleregioninner">';
    2463      $PAGE->requires->js_init_call('M.util.init_collapsible_region', array($id, $userpref, get_string('clicktohideshow')));
    2464  
    2465      if ($return) {
    2466          return $output;
    2467      } else {
    2468          echo $output;
    2469      }
    2470  }
    2471  
    2472  /**
    2473   * Close a region started with print_collapsible_region_start.
    2474   *
    2475   * @param boolean $return if true, return the HTML as a string, rather than printing it.
    2476   * @return string|void if $return is false, returns nothing, otherwise returns a string of HTML.
    2477   */
    2478  function print_collapsible_region_end($return = false) {
    2479      $output = '</div></div></div>';
    2480  
    2481      if ($return) {
    2482          return $output;
    2483      } else {
    2484          echo $output;
    2485      }
    2486  }
    2487  
    2488  /**
    2489   * Print a specified group's avatar.
    2490   *
    2491   * @param array|stdClass $group A single {@link group} object OR array of groups.
    2492   * @param int $courseid The course ID.
    2493   * @param boolean $large Default small picture, or large.
    2494   * @param boolean $return If false print picture, otherwise return the output as string
    2495   * @param boolean $link Enclose image in a link to view specified course?
    2496   * @param boolean $includetoken Whether to use a user token when displaying this group image.
    2497   *                True indicates to generate a token for current user, and integer value indicates to generate a token for the
    2498   *                user whose id is the value indicated.
    2499   *                If the group picture is included in an e-mail or some other location where the audience is a specific
    2500   *                user who will not be logged in when viewing, then we use a token to authenticate the user.
    2501   * @return string|void Depending on the setting of $return
    2502   */
    2503  function print_group_picture($group, $courseid, $large = false, $return = false, $link = true, $includetoken = false) {
    2504      global $CFG;
    2505  
    2506      if (is_array($group)) {
    2507          $output = '';
    2508          foreach ($group as $g) {
    2509              $output .= print_group_picture($g, $courseid, $large, true, $link, $includetoken);
    2510          }
    2511          if ($return) {
    2512              return $output;
    2513          } else {
    2514              echo $output;
    2515              return;
    2516          }
    2517      }
    2518  
    2519      $pictureurl = get_group_picture_url($group, $courseid, $large, $includetoken);
    2520  
    2521      // If there is no picture, do nothing.
    2522      if (!isset($pictureurl)) {
    2523          return;
    2524      }
    2525  
    2526      $context = context_course::instance($courseid);
    2527  
    2528      $groupname = s($group->name);
    2529      $pictureimage = html_writer::img($pictureurl, $groupname, ['title' => $groupname]);
    2530  
    2531      $output = '';
    2532      if ($link or has_capability('moodle/site:accessallgroups', $context)) {
    2533          $linkurl = new moodle_url('/user/index.php', ['id' => $courseid, 'group' => $group->id]);
    2534          $output .= html_writer::link($linkurl, $pictureimage);
    2535      } else {
    2536          $output .= $pictureimage;
    2537      }
    2538  
    2539      if ($return) {
    2540          return $output;
    2541      } else {
    2542          echo $output;
    2543      }
    2544  }
    2545  
    2546  /**
    2547   * Return the url to the group picture.
    2548   *
    2549   * @param  stdClass $group A group object.
    2550   * @param  int $courseid The course ID for the group.
    2551   * @param  bool $large A large or small group picture? Default is small.
    2552   * @param  boolean $includetoken Whether to use a user token when displaying this group image.
    2553   *                 True indicates to generate a token for current user, and integer value indicates to generate a token for the
    2554   *                 user whose id is the value indicated.
    2555   *                 If the group picture is included in an e-mail or some other location where the audience is a specific
    2556   *                 user who will not be logged in when viewing, then we use a token to authenticate the user.
    2557   * @return moodle_url Returns the url for the group picture.
    2558   */
    2559  function get_group_picture_url($group, $courseid, $large = false, $includetoken = false) {
    2560      global $CFG;
    2561  
    2562      $context = context_course::instance($courseid);
    2563  
    2564      // If there is no picture, do nothing.
    2565      if (!$group->picture) {
    2566          return;
    2567      }
    2568  
    2569      // If picture is hidden, only show to those with course:managegroups.
    2570      if ($group->hidepicture and !has_capability('moodle/course:managegroups', $context)) {
    2571          return;
    2572      }
    2573  
    2574      if ($large) {
    2575          $file = 'f1';
    2576      } else {
    2577          $file = 'f2';
    2578      }
    2579  
    2580      $grouppictureurl = moodle_url::make_pluginfile_url(
    2581              $context->id, 'group', 'icon', $group->id, '/', $file, false, $includetoken);
    2582      $grouppictureurl->param('rev', $group->picture);
    2583      return $grouppictureurl;
    2584  }
    2585  
    2586  
    2587  /**
    2588   * Display a recent activity note
    2589   *
    2590   * @staticvar string $strftimerecent
    2591   * @param int $time A timestamp int.
    2592   * @param stdClass $user A user object from the database.
    2593   * @param string $text Text for display for the note
    2594   * @param string $link The link to wrap around the text
    2595   * @param bool $return If set to true the HTML is returned rather than echo'd
    2596   * @param string $viewfullnames
    2597   * @return string If $retrun was true returns HTML for a recent activity notice.
    2598   */
    2599  function print_recent_activity_note($time, $user, $text, $link, $return=false, $viewfullnames=null) {
    2600      static $strftimerecent = null;
    2601      $output = '';
    2602  
    2603      if (is_null($viewfullnames)) {
    2604          $context = context_system::instance();
    2605          $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
    2606      }
    2607  
    2608      if (is_null($strftimerecent)) {
    2609          $strftimerecent = get_string('strftimerecent');
    2610      }
    2611  
    2612      $output .= '<div class="head">';
    2613      $output .= '<div class="date">'.userdate($time, $strftimerecent).'</div>';
    2614      $output .= '<div class="name">'.fullname($user, $viewfullnames).'</div>';
    2615      $output .= '</div>';
    2616      $output .= '<div class="info"><a href="'.$link.'">'.format_string($text, true).'</a></div>';
    2617  
    2618      if ($return) {
    2619          return $output;
    2620      } else {
    2621          echo $output;
    2622      }
    2623  }
    2624  
    2625  /**
    2626   * Returns a popup menu with course activity modules
    2627   *
    2628   * Given a course this function returns a small popup menu with all the course activity modules in it, as a navigation menu
    2629   * outputs a simple list structure in XHTML.
    2630   * The data is taken from the serialised array stored in the course record.
    2631   *
    2632   * @param course $course A {@link $COURSE} object.
    2633   * @param array $sections
    2634   * @param course_modinfo $modinfo
    2635   * @param string $strsection
    2636   * @param string $strjumpto
    2637   * @param int $width
    2638   * @param string $cmid
    2639   * @return string The HTML block
    2640   */
    2641  function navmenulist($course, $sections, $modinfo, $strsection, $strjumpto, $width=50, $cmid=0) {
    2642  
    2643      global $CFG, $OUTPUT;
    2644  
    2645      $section = -1;
    2646      $menu = array();
    2647      $doneheading = false;
    2648  
    2649      $courseformatoptions = course_get_format($course)->get_format_options();
    2650      $coursecontext = context_course::instance($course->id);
    2651  
    2652      $menu[] = '<ul class="navmenulist"><li class="jumpto section"><span>'.$strjumpto.'</span><ul>';
    2653      foreach ($modinfo->cms as $mod) {
    2654          if (!$mod->has_view()) {
    2655              // Don't show modules which you can't link to!
    2656              continue;
    2657          }
    2658  
    2659          // For course formats using 'numsections' do not show extra sections.
    2660          if (isset($courseformatoptions['numsections']) && $mod->sectionnum > $courseformatoptions['numsections']) {
    2661              break;
    2662          }
    2663  
    2664          if (!$mod->uservisible) { // Do not icnlude empty sections at all.
    2665              continue;
    2666          }
    2667  
    2668          if ($mod->sectionnum >= 0 and $section != $mod->sectionnum) {
    2669              $thissection = $sections[$mod->sectionnum];
    2670  
    2671              if ($thissection->visible or
    2672                      (isset($courseformatoptions['hiddensections']) and !$courseformatoptions['hiddensections']) or
    2673                      has_capability('moodle/course:viewhiddensections', $coursecontext)) {
    2674                  $thissection->summary = strip_tags(format_string($thissection->summary, true));
    2675                  if (!$doneheading) {
    2676                      $menu[] = '</ul></li>';
    2677                  }
    2678                  if ($course->format == 'weeks' or empty($thissection->summary)) {
    2679                      $item = $strsection ." ". $mod->sectionnum;
    2680                  } else {
    2681                      if (core_text::strlen($thissection->summary) < ($width-3)) {
    2682                          $item = $thissection->summary;
    2683                      } else {
    2684                          $item = core_text::substr($thissection->summary, 0, $width).'...';
    2685                      }
    2686                  }
    2687                  $menu[] = '<li class="section"><span>'.$item.'</span>';
    2688                  $menu[] = '<ul>';
    2689                  $doneheading = true;
    2690  
    2691                  $section = $mod->sectionnum;
    2692              } else {
    2693                  // No activities from this hidden section shown.
    2694                  continue;
    2695              }
    2696          }
    2697  
    2698          $url = $mod->modname .'/view.php?id='. $mod->id;
    2699          $mod->name = strip_tags(format_string($mod->name ,true));
    2700          if (core_text::strlen($mod->name) > ($width+5)) {
    2701              $mod->name = core_text::substr($mod->name, 0, $width).'...';
    2702          }
    2703          if (!$mod->visible) {
    2704              $mod->name = '('.$mod->name.')';
    2705          }
    2706          $class = 'activity '.$mod->modname;
    2707          $class .= ($cmid == $mod->id) ? ' selected' : '';
    2708          $menu[] = '<li class="'.$class.'">'.
    2709                    $OUTPUT->image_icon('icon', '', $mod->modname).
    2710                    '<a href="'.$CFG->wwwroot.'/mod/'.$url.'">'.$mod->name.'</a></li>';
    2711      }
    2712  
    2713      if ($doneheading) {
    2714          $menu[] = '</ul></li>';
    2715      }
    2716      $menu[] = '</ul></li></ul>';
    2717  
    2718      return implode("\n", $menu);
    2719  }
    2720  
    2721  /**
    2722   * Prints a grade menu (as part of an existing form) with help showing all possible numerical grades and scales.
    2723   *
    2724   * @todo Finish documenting this function
    2725   * @todo Deprecate: this is only used in a few contrib modules
    2726   *
    2727   * @param int $courseid The course ID
    2728   * @param string $name
    2729   * @param string $current
    2730   * @param boolean $includenograde Include those with no grades
    2731   * @param boolean $return If set to true returns rather than echo's
    2732   * @return string|bool Depending on value of $return
    2733   */
    2734  function print_grade_menu($courseid, $name, $current, $includenograde=true, $return=false) {
    2735      global $OUTPUT;
    2736  
    2737      $output = '';
    2738      $strscale = get_string('scale');
    2739      $strscales = get_string('scales');
    2740  
    2741      $scales = get_scales_menu($courseid);
    2742      foreach ($scales as $i => $scalename) {
    2743          $grades[-$i] = $strscale .': '. $scalename;
    2744      }
    2745      if ($includenograde) {
    2746          $grades[0] = get_string('nograde');
    2747      }
    2748      for ($i=100; $i>=1; $i--) {
    2749          $grades[$i] = $i;
    2750      }
    2751      $output .= html_writer::select($grades, $name, $current, false);
    2752  
    2753      $linkobject = '<span class="helplink">' . $OUTPUT->pix_icon('help', $strscales) . '</span>';
    2754      $link = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => 1));
    2755      $action = new popup_action('click', $link, 'ratingscales', array('height' => 400, 'width' => 500));
    2756      $output .= $OUTPUT->action_link($link, $linkobject, $action, array('title' => $strscales));
    2757  
    2758      if ($return) {
    2759          return $output;
    2760      } else {
    2761          echo $output;
    2762      }
    2763  }
    2764  
    2765  /**
    2766   * Print an error to STDOUT and exit with a non-zero code. For commandline scripts.
    2767   *
    2768   * Default errorcode is 1.
    2769   *
    2770   * Very useful for perl-like error-handling:
    2771   * do_somethting() or mdie("Something went wrong");
    2772   *
    2773   * @param string  $msg       Error message
    2774   * @param integer $errorcode Error code to emit
    2775   */
    2776  function mdie($msg='', $errorcode=1) {
    2777      trigger_error($msg);
    2778      exit($errorcode);
    2779  }
    2780  
    2781  /**
    2782   * Print a message and exit.
    2783   *
    2784   * @param string $message The message to print in the notice
    2785   * @param moodle_url|string $link The link to use for the continue button
    2786   * @param object $course A course object. Unused.
    2787   * @return void This function simply exits
    2788   */
    2789  function notice ($message, $link='', $course=null) {
    2790      global $PAGE, $OUTPUT;
    2791  
    2792      $message = clean_text($message);   // In case nasties are in here.
    2793  
    2794      if (CLI_SCRIPT) {
    2795          echo("!!$message!!\n");
    2796          exit(1); // No success.
    2797      }
    2798  
    2799      if (!$PAGE->headerprinted) {
    2800          // Header not yet printed.
    2801          $PAGE->set_title(get_string('notice'));
    2802          echo $OUTPUT->header();
    2803      } else {
    2804          echo $OUTPUT->container_end_all(false);
    2805      }
    2806  
    2807      echo $OUTPUT->box($message, 'generalbox', 'notice');
    2808      echo $OUTPUT->continue_button($link);
    2809  
    2810      echo $OUTPUT->footer();
    2811      exit(1); // General error code.
    2812  }
    2813  
    2814  /**
    2815   * Redirects the user to another page, after printing a notice.
    2816   *
    2817   * This function calls the OUTPUT redirect method, echo's the output and then dies to ensure nothing else happens.
    2818   *
    2819   * <strong>Good practice:</strong> You should call this method before starting page
    2820   * output by using any of the OUTPUT methods.
    2821   *
    2822   * @param moodle_url|string $url A moodle_url to redirect to. Strings are not to be trusted!
    2823   * @param string $message The message to display to the user
    2824   * @param int $delay The delay before redirecting
    2825   * @param string $messagetype The type of notification to show the message in. See constants on \core\output\notification.
    2826   * @throws moodle_exception
    2827   */
    2828  function redirect($url, $message='', $delay=null, $messagetype = \core\output\notification::NOTIFY_INFO) {
    2829      global $OUTPUT, $PAGE, $CFG;
    2830  
    2831      if (CLI_SCRIPT or AJAX_SCRIPT) {
    2832          // This is wrong - developers should not use redirect in these scripts but it should not be very likely.
    2833          throw new moodle_exception('redirecterrordetected', 'error');
    2834      }
    2835  
    2836      if ($delay === null) {
    2837          $delay = -1;
    2838      }
    2839  
    2840      // Prevent debug errors - make sure context is properly initialised.
    2841      if ($PAGE) {
    2842          $PAGE->set_context(null);
    2843          $PAGE->set_pagelayout('redirect');  // No header and footer needed.
    2844          $PAGE->set_title(get_string('pageshouldredirect', 'moodle'));
    2845      }
    2846  
    2847      if ($url instanceof moodle_url) {
    2848          $url = $url->out(false);
    2849      }
    2850  
    2851      $debugdisableredirect = false;
    2852      do {
    2853          if (defined('DEBUGGING_PRINTED')) {
    2854              // Some debugging already printed, no need to look more.
    2855              $debugdisableredirect = true;
    2856              break;
    2857          }
    2858  
    2859          if (core_useragent::is_msword()) {
    2860              // Clicking a URL from MS Word sends a request to the server without cookies. If that
    2861              // causes a redirect Word will open a browser pointing the new URL. If not, the URL that
    2862              // was clicked is opened. Because the request from Word is without cookies, it almost
    2863              // always results in a redirect to the login page, even if the user is logged in in their
    2864              // browser. This is not what we want, so prevent the redirect for requests from Word.
    2865              $debugdisableredirect = true;
    2866              break;
    2867          }
    2868  
    2869          if (empty($CFG->debugdisplay) or empty($CFG->debug)) {
    2870              // No errors should be displayed.
    2871              break;
    2872          }
    2873  
    2874          if (!function_exists('error_get_last') or !$lasterror = error_get_last()) {
    2875              break;
    2876          }
    2877  
    2878          if (!($lasterror['type'] & $CFG->debug)) {
    2879              // Last error not interesting.
    2880              break;
    2881          }
    2882  
    2883          // Watch out here, @hidden() errors are returned from error_get_last() too.
    2884          if (headers_sent()) {
    2885              // We already started printing something - that means errors likely printed.
    2886              $debugdisableredirect = true;
    2887              break;
    2888          }
    2889  
    2890          if (ob_get_level() and ob_get_contents()) {
    2891              // There is something waiting to be printed, hopefully it is the errors,
    2892              // but it might be some error hidden by @ too - such as the timezone mess from setup.php.
    2893              $debugdisableredirect = true;
    2894              break;
    2895          }
    2896      } while (false);
    2897  
    2898      // Technically, HTTP/1.1 requires Location: header to contain the absolute path.
    2899      // (In practice browsers accept relative paths - but still, might as well do it properly.)
    2900      // This code turns relative into absolute.
    2901      if (!preg_match('|^[a-z]+:|i', $url)) {
    2902          // Get host name http://www.wherever.com.
    2903          $hostpart = preg_replace('|^(.*?[^:/])/.*$|', '$1', $CFG->wwwroot);
    2904          if (preg_match('|^/|', $url)) {
    2905              // URLs beginning with / are relative to web server root so we just add them in.
    2906              $url = $hostpart.$url;
    2907          } else {
    2908              // URLs not beginning with / are relative to path of current script, so add that on.
    2909              $url = $hostpart.preg_replace('|\?.*$|', '', me()).'/../'.$url;
    2910          }
    2911          // Replace all ..s.
    2912          while (true) {
    2913              $newurl = preg_replace('|/(?!\.\.)[^/]*/\.\./|', '/', $url);
    2914              if ($newurl == $url) {
    2915                  break;
    2916              }
    2917              $url = $newurl;
    2918          }
    2919      }
    2920  
    2921      // Sanitise url - we can not rely on moodle_url or our URL cleaning
    2922      // because they do not support all valid external URLs.
    2923      $url = preg_replace('/[\x00-\x1F\x7F]/', '', $url);
    2924      $url = str_replace('"', '%22', $url);
    2925      $encodedurl = preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&amp;", $url);
    2926      $encodedurl = preg_replace('/^.*href="([^"]*)".*$/', "\\1", clean_text('<a href="'.$encodedurl.'" />', FORMAT_HTML));
    2927      $url = str_replace('&amp;', '&', $encodedurl);
    2928  
    2929      if (!empty($message)) {
    2930          if (!$debugdisableredirect && !headers_sent()) {
    2931              // A message has been provided, and the headers have not yet been sent.
    2932              // Display the message as a notification on the subsequent page.
    2933              \core\notification::add($message, $messagetype);
    2934              $message = null;
    2935              $delay = 0;
    2936          } else {
    2937              if ($delay === -1 || !is_numeric($delay)) {
    2938                  $delay = 3;
    2939              }
    2940              $message = clean_text($message);
    2941          }
    2942      } else {
    2943          $message = get_string('pageshouldredirect');
    2944          $delay = 0;
    2945      }
    2946  
    2947      // Make sure the session is closed properly, this prevents problems in IIS
    2948      // and also some potential PHP shutdown issues.
    2949      \core\session\manager::write_close();
    2950  
    2951      if ($delay == 0 && !$debugdisableredirect && !headers_sent()) {
    2952          // This helps when debugging redirect issues like loops and it is not clear
    2953          // which layer in the stack sent the redirect header.
    2954          @header('X-Redirect-By: Moodle');
    2955          // 302 might not work for POST requests, 303 is ignored by obsolete clients.
    2956          @header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other');
    2957          @header('Location: '.$url);
    2958          echo bootstrap_renderer::plain_redirect_message($encodedurl);
    2959          exit;
    2960      }
    2961  
    2962      // Include a redirect message, even with a HTTP redirect, because that is recommended practice.
    2963      if ($PAGE) {
    2964          $CFG->docroot = false; // To prevent the link to moodle docs from being displayed on redirect page.
    2965          echo $OUTPUT->redirect_message($encodedurl, $message, $delay, $debugdisableredirect, $messagetype);
    2966          exit;
    2967      } else {
    2968          echo bootstrap_renderer::early_redirect_message($encodedurl, $message, $delay);
    2969          exit;
    2970      }
    2971  }
    2972  
    2973  /**
    2974   * Given an email address, this function will return an obfuscated version of it.
    2975   *
    2976   * @param string $email The email address to obfuscate
    2977   * @return string The obfuscated email address
    2978   */
    2979  function obfuscate_email($email) {
    2980      $i = 0;
    2981      $length = strlen($email);
    2982      $obfuscated = '';
    2983      while ($i < $length) {
    2984          if (rand(0, 2) && $email[$i]!='@') { // MDL-20619 some browsers have problems unobfuscating @.
    2985              $obfuscated.='%'.dechex(ord($email[$i]));
    2986          } else {
    2987              $obfuscated.=$email[$i];
    2988          }
    2989          $i++;
    2990      }
    2991      return $obfuscated;
    2992  }
    2993  
    2994  /**
    2995   * This function takes some text and replaces about half of the characters
    2996   * with HTML entity equivalents.   Return string is obviously longer.
    2997   *
    2998   * @param string $plaintext The text to be obfuscated
    2999   * @return string The obfuscated text
    3000   */
    3001  function obfuscate_text($plaintext) {
    3002      $i=0;
    3003      $length = core_text::strlen($plaintext);
    3004      $obfuscated='';
    3005      $prevobfuscated = false;
    3006      while ($i < $length) {
    3007          $char = core_text::substr($plaintext, $i, 1);
    3008          $ord = core_text::utf8ord($char);
    3009          $numerical = ($ord >= ord('0')) && ($ord <= ord('9'));
    3010          if ($prevobfuscated and $numerical ) {
    3011              $obfuscated.='&#'.$ord.';';
    3012          } else if (rand(0, 2)) {
    3013              $obfuscated.='&#'.$ord.';';
    3014              $prevobfuscated = true;
    3015          } else {
    3016              $obfuscated.=$char;
    3017              $prevobfuscated = false;
    3018          }
    3019          $i++;
    3020      }
    3021      return $obfuscated;
    3022  }
    3023  
    3024  /**
    3025   * This function uses the {@link obfuscate_email()} and {@link obfuscate_text()}
    3026   * to generate a fully obfuscated email link, ready to use.
    3027   *
    3028   * @param string $email The email address to display
    3029   * @param string $label The text to displayed as hyperlink to $email
    3030   * @param boolean $dimmed If true then use css class 'dimmed' for hyperlink
    3031   * @param string $subject The subject of the email in the mailto link
    3032   * @param string $body The content of the email in the mailto link
    3033   * @return string The obfuscated mailto link
    3034   */
    3035  function obfuscate_mailto($email, $label='', $dimmed=false, $subject = '', $body = '') {
    3036  
    3037      if (empty($label)) {
    3038          $label = $email;
    3039      }
    3040  
    3041      $label = obfuscate_text($label);
    3042      $email = obfuscate_email($email);
    3043      $mailto = obfuscate_text('mailto');
    3044      $url = new moodle_url("mailto:$email");
    3045      $attrs = array();
    3046  
    3047      if (!empty($subject)) {
    3048          $url->param('subject', format_string($subject));
    3049      }
    3050      if (!empty($body)) {
    3051          $url->param('body', format_string($body));
    3052      }
    3053  
    3054      // Use the obfuscated mailto.
    3055      $url = preg_replace('/^mailto/', $mailto, $url->out());
    3056  
    3057      if ($dimmed) {
    3058          $attrs['title'] = get_string('emaildisable');
    3059          $attrs['class'] = 'dimmed';
    3060      }
    3061  
    3062      return html_writer::link($url, $label, $attrs);
    3063  }
    3064  
    3065  /**
    3066   * This function is used to rebuild the <nolink> tag because some formats (PLAIN and WIKI)
    3067   * will transform it to html entities
    3068   *
    3069   * @param string $text Text to search for nolink tag in
    3070   * @return string
    3071   */
    3072  function rebuildnolinktag($text) {
    3073  
    3074      $text = preg_replace('/&lt;(\/*nolink)&gt;/i', '<$1>', $text);
    3075  
    3076      return $text;
    3077  }
    3078  
    3079  /**
    3080   * Prints a maintenance message from $CFG->maintenance_message or default if empty.
    3081   */
    3082  function print_maintenance_message() {
    3083      global $CFG, $SITE, $PAGE, $OUTPUT;
    3084  
    3085      header($_SERVER['SERVER_PROTOCOL'] . ' 503 Moodle under maintenance');
    3086      header('Status: 503 Moodle under maintenance');
    3087      header('Retry-After: 300');
    3088  
    3089      $PAGE->set_pagetype('maintenance-message');
    3090      $PAGE->set_pagelayout('maintenance');
    3091      $PAGE->set_title(strip_tags($SITE->fullname));
    3092      $PAGE->set_heading($SITE->fullname);
    3093      echo $OUTPUT->header();
    3094      echo $OUTPUT->heading(get_string('sitemaintenance', 'admin'));
    3095      if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
    3096          echo $OUTPUT->box_start('maintenance_message generalbox boxwidthwide boxaligncenter');
    3097          echo $CFG->maintenance_message;
    3098          echo $OUTPUT->box_end();
    3099      }
    3100      echo $OUTPUT->footer();
    3101      die;
    3102  }
    3103  
    3104  /**
    3105   * Returns a string containing a nested list, suitable for formatting into tabs with CSS.
    3106   *
    3107   * It is not recommended to use this function in Moodle 2.5 but it is left for backward
    3108   * compartibility.
    3109   *
    3110   * Example how to print a single line tabs:
    3111   * $rows = array(
    3112   *    new tabobject(...),
    3113   *    new tabobject(...)
    3114   * );
    3115   * echo $OUTPUT->tabtree($rows, $selectedid);
    3116   *
    3117   * Multiple row tabs may not look good on some devices but if you want to use them
    3118   * you can specify ->subtree for the active tabobject.
    3119   *
    3120   * @param array $tabrows An array of rows where each row is an array of tab objects
    3121   * @param string $selected  The id of the selected tab (whatever row it's on)
    3122   * @param array  $inactive  An array of ids of inactive tabs that are not selectable.
    3123   * @param array  $activated An array of ids of other tabs that are currently activated
    3124   * @param bool $return If true output is returned rather then echo'd
    3125   * @return string HTML output if $return was set to true.
    3126   */
    3127  function print_tabs($tabrows, $selected = null, $inactive = null, $activated = null, $return = false) {
    3128      global $OUTPUT;
    3129  
    3130      $tabrows = array_reverse($tabrows);
    3131      $subtree = array();
    3132      foreach ($tabrows as $row) {
    3133          $tree = array();
    3134  
    3135          foreach ($row as $tab) {
    3136              $tab->inactive = is_array($inactive) && in_array((string)$tab->id, $inactive);
    3137              $tab->activated = is_array($activated) && in_array((string)$tab->id, $activated);
    3138              $tab->selected = (string)$tab->id == $selected;
    3139  
    3140              if ($tab->activated || $tab->selected) {
    3141                  $tab->subtree = $subtree;
    3142              }
    3143              $tree[] = $tab;
    3144          }
    3145          $subtree = $tree;
    3146      }
    3147      $output = $OUTPUT->tabtree($subtree);
    3148      if ($return) {
    3149          return $output;
    3150      } else {
    3151          print $output;
    3152          return !empty($output);
    3153      }
    3154  }
    3155  
    3156  /**
    3157   * Alter debugging level for the current request,
    3158   * the change is not saved in database.
    3159   *
    3160   * @param int $level one of the DEBUG_* constants
    3161   * @param bool $debugdisplay
    3162   */
    3163  function set_debugging($level, $debugdisplay = null) {
    3164      global $CFG;
    3165  
    3166      $CFG->debug = (int)$level;
    3167      $CFG->debugdeveloper = (($CFG->debug & DEBUG_DEVELOPER) === DEBUG_DEVELOPER);
    3168  
    3169      if ($debugdisplay !== null) {
    3170          $CFG->debugdisplay = (bool)$debugdisplay;
    3171      }
    3172  }
    3173  
    3174  /**
    3175   * Standard Debugging Function
    3176   *
    3177   * Returns true if the current site debugging settings are equal or above specified level.
    3178   * If passed a parameter it will emit a debugging notice similar to trigger_error(). The
    3179   * routing of notices is controlled by $CFG->debugdisplay
    3180   * eg use like this:
    3181   *
    3182   * 1)  debugging('a normal debug notice');
    3183   * 2)  debugging('something really picky', DEBUG_ALL);
    3184   * 3)  debugging('annoying debug message only for developers', DEBUG_DEVELOPER);
    3185   * 4)  if (debugging()) { perform extra debugging operations (do not use print or echo) }
    3186   *
    3187   * In code blocks controlled by debugging() (such as example 4)
    3188   * any output should be routed via debugging() itself, or the lower-level
    3189   * trigger_error() or error_log(). Using echo or print will break XHTML
    3190   * JS and HTTP headers.
    3191   *
    3192   * It is also possible to define NO_DEBUG_DISPLAY which redirects the message to error_log.
    3193   *
    3194   * @param string $message a message to print
    3195   * @param int $level the level at which this debugging statement should show
    3196   * @param array $backtrace use different backtrace
    3197   * @return bool
    3198   */
    3199  function debugging($message = '', $level = DEBUG_NORMAL, $backtrace = null) {
    3200      global $CFG, $USER;
    3201  
    3202      $forcedebug = false;
    3203      if (!empty($CFG->debugusers) && $USER) {
    3204          $debugusers = explode(',', $CFG->debugusers);
    3205          $forcedebug = in_array($USER->id, $debugusers);
    3206      }
    3207  
    3208      if (!$forcedebug and (empty($CFG->debug) || ($CFG->debug != -1 and $CFG->debug < $level))) {
    3209          return false;
    3210      }
    3211  
    3212      if (!isset($CFG->debugdisplay)) {
    3213          $CFG->debugdisplay = ini_get_bool('display_errors');
    3214      }
    3215  
    3216      if ($message) {
    3217          if (!$backtrace) {
    3218              $backtrace = debug_backtrace();
    3219          }
    3220          $from = format_backtrace($backtrace, CLI_SCRIPT || NO_DEBUG_DISPLAY);
    3221          if (PHPUNIT_TEST) {
    3222              if (phpunit_util::debugging_triggered($message, $level, $from)) {
    3223                  // We are inside test, the debug message was logged.
    3224                  return true;
    3225              }
    3226          }
    3227  
    3228          if (NO_DEBUG_DISPLAY) {
    3229              // Script does not want any errors or debugging in output,
    3230              // we send the info to error log instead.
    3231              error_log('Debugging: ' . $message . ' in '. PHP_EOL . $from);
    3232  
    3233          } else if ($forcedebug or $CFG->debugdisplay) {
    3234              if (!defined('DEBUGGING_PRINTED')) {
    3235                  define('DEBUGGING_PRINTED', 1); // Indicates we have printed something.
    3236              }
    3237              if (CLI_SCRIPT) {
    3238                  echo "++ $message ++\n$from";
    3239              } else {
    3240                  echo '<div class="notifytiny debuggingmessage" data-rel="debugging">' , $message , $from , '</div>';
    3241              }
    3242  
    3243          } else {
    3244              trigger_error($message . $from, E_USER_NOTICE);
    3245          }
    3246      }
    3247      return true;
    3248  }
    3249  
    3250  /**
    3251   * Outputs a HTML comment to the browser.
    3252   *
    3253   * This is used for those hard-to-debug pages that use bits from many different files in very confusing ways (e.g. blocks).
    3254   *
    3255   * <code>print_location_comment(__FILE__, __LINE__);</code>
    3256   *
    3257   * @param string $file
    3258   * @param integer $line
    3259   * @param boolean $return Whether to return or print the comment
    3260   * @return string|void Void unless true given as third parameter
    3261   */
    3262  function print_location_comment($file, $line, $return = false) {
    3263      if ($return) {
    3264          return "<!-- $file at line $line -->\n";
    3265      } else {
    3266          echo "<!-- $file at line $line -->\n";
    3267      }
    3268  }
    3269  
    3270  
    3271  /**
    3272   * Returns true if the user is using a right-to-left language.
    3273   *
    3274   * @return boolean true if the current language is right-to-left (Hebrew, Arabic etc)
    3275   */
    3276  function right_to_left() {
    3277      return (get_string('thisdirection', 'langconfig') === 'rtl');
    3278  }
    3279  
    3280  
    3281  /**
    3282   * Returns swapped left<=> right if in RTL environment.
    3283   *
    3284   * Part of RTL Moodles support.
    3285   *
    3286   * @param string $align align to check
    3287   * @return string
    3288   */
    3289  function fix_align_rtl($align) {
    3290      if (!right_to_left()) {
    3291          return $align;
    3292      }
    3293      if ($align == 'left') {
    3294          return 'right';
    3295      }
    3296      if ($align == 'right') {
    3297          return 'left';
    3298      }
    3299      return $align;
    3300  }
    3301  
    3302  
    3303  /**
    3304   * Returns true if the page is displayed in a popup window.
    3305   *
    3306   * Gets the information from the URL parameter inpopup.
    3307   *
    3308   * @todo Use a central function to create the popup calls all over Moodle and
    3309   * In the moment only works with resources and probably questions.
    3310   *
    3311   * @return boolean
    3312   */
    3313  function is_in_popup() {
    3314      $inpopup = optional_param('inpopup', '', PARAM_BOOL);
    3315  
    3316      return ($inpopup);
    3317  }
    3318  
    3319  /**
    3320   * Progress trace class.
    3321   *
    3322   * Use this class from long operations where you want to output occasional information about
    3323   * what is going on, but don't know if, or in what format, the output should be.
    3324   *
    3325   * @copyright 2009 Tim Hunt
    3326   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3327   * @package core
    3328   */
    3329  abstract class progress_trace {
    3330      /**
    3331       * Output an progress message in whatever format.
    3332       *
    3333       * @param string $message the message to output.
    3334       * @param integer $depth indent depth for this message.
    3335       */
    3336      abstract public function output($message, $depth = 0);
    3337  
    3338      /**
    3339       * Called when the processing is finished.
    3340       */
    3341      public function finished() {
    3342      }
    3343  }
    3344  
    3345  /**
    3346   * This subclass of progress_trace does not ouput anything.
    3347   *
    3348   * @copyright 2009 Tim Hunt
    3349   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3350   * @package core
    3351   */
    3352  class null_progress_trace extends progress_trace {
    3353      /**
    3354       * Does Nothing
    3355       *
    3356       * @param string $message
    3357       * @param int $depth
    3358       * @return void Does Nothing
    3359       */
    3360      public function output($message, $depth = 0) {
    3361      }
    3362  }
    3363  
    3364  /**
    3365   * This subclass of progress_trace outputs to plain text.
    3366   *
    3367   * @copyright 2009 Tim Hunt
    3368   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3369   * @package core
    3370   */
    3371  class text_progress_trace extends progress_trace {
    3372      /**
    3373       * Output the trace message.
    3374       *
    3375       * @param string $message
    3376       * @param int $depth
    3377       * @return void Output is echo'd
    3378       */
    3379      public function output($message, $depth = 0) {
    3380          mtrace(str_repeat('  ', $depth) . $message);
    3381      }
    3382  }
    3383  
    3384  /**
    3385   * This subclass of progress_trace outputs as HTML.
    3386   *
    3387   * @copyright 2009 Tim Hunt
    3388   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3389   * @package core
    3390   */
    3391  class html_progress_trace extends progress_trace {
    3392      /**
    3393       * Output the trace message.
    3394       *
    3395       * @param string $message
    3396       * @param int $depth
    3397       * @return void Output is echo'd
    3398       */
    3399      public function output($message, $depth = 0) {
    3400          echo '<p>', str_repeat('&#160;&#160;', $depth), htmlspecialchars($message), "</p>\n";
    3401          flush();
    3402      }
    3403  }
    3404  
    3405  /**
    3406   * HTML List Progress Tree
    3407   *
    3408   * @copyright 2009 Tim Hunt
    3409   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3410   * @package core
    3411   */
    3412  class html_list_progress_trace extends progress_trace {
    3413      /** @var int */
    3414      protected $currentdepth = -1;
    3415  
    3416      /**
    3417       * Echo out the list
    3418       *
    3419       * @param string $message The message to display
    3420       * @param int $depth
    3421       * @return void Output is echoed
    3422       */
    3423      public function output($message, $depth = 0) {
    3424          $samedepth = true;
    3425          while ($this->currentdepth > $depth) {
    3426              echo "</li>\n</ul>\n";
    3427              $this->currentdepth -= 1;
    3428              if ($this->currentdepth == $depth) {
    3429                  echo '<li>';
    3430              }
    3431              $samedepth = false;
    3432          }
    3433          while ($this->currentdepth < $depth) {
    3434              echo "<ul>\n<li>";
    3435              $this->currentdepth += 1;
    3436              $samedepth = false;
    3437          }
    3438          if ($samedepth) {
    3439              echo "</li>\n<li>";
    3440          }
    3441          echo htmlspecialchars($message);
    3442          flush();
    3443      }
    3444  
    3445      /**
    3446       * Called when the processing is finished.
    3447       */
    3448      public function finished() {
    3449          while ($this->currentdepth >= 0) {
    3450              echo "</li>\n</ul>\n";
    3451              $this->currentdepth -= 1;
    3452          }
    3453      }
    3454  }
    3455  
    3456  /**
    3457   * This subclass of progress_trace outputs to error log.
    3458   *
    3459   * @copyright Petr Skoda {@link http://skodak.org}
    3460   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3461   * @package core
    3462   */
    3463  class error_log_progress_trace extends progress_trace {
    3464      /** @var string log prefix */
    3465      protected $prefix;
    3466  
    3467      /**
    3468       * Constructor.
    3469       * @param string $prefix optional log prefix
    3470       */
    3471      public function __construct($prefix = '') {
    3472          $this->prefix = $prefix;
    3473      }
    3474  
    3475      /**
    3476       * Output the trace message.
    3477       *
    3478       * @param string $message
    3479       * @param int $depth
    3480       * @return void Output is sent to error log.
    3481       */
    3482      public function output($message, $depth = 0) {
    3483          error_log($this->prefix . str_repeat('  ', $depth) . $message);
    3484      }
    3485  }
    3486  
    3487  /**
    3488   * Special type of trace that can be used for catching of output of other traces.
    3489   *
    3490   * @copyright Petr Skoda {@link http://skodak.org}
    3491   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3492   * @package core
    3493   */
    3494  class progress_trace_buffer extends progress_trace {
    3495      /** @var progres_trace */
    3496      protected $trace;
    3497      /** @var bool do we pass output out */
    3498      protected $passthrough;
    3499      /** @var string output buffer */
    3500      protected $buffer;
    3501  
    3502      /**
    3503       * Constructor.
    3504       *
    3505       * @param progress_trace $trace
    3506       * @param bool $passthrough true means output and buffer, false means just buffer and no output
    3507       */
    3508      public function __construct(progress_trace $trace, $passthrough = true) {
    3509          $this->trace       = $trace;
    3510          $this->passthrough = $passthrough;
    3511          $this->buffer      = '';
    3512      }
    3513  
    3514      /**
    3515       * Output the trace message.
    3516       *
    3517       * @param string $message the message to output.
    3518       * @param int $depth indent depth for this message.
    3519       * @return void output stored in buffer
    3520       */
    3521      public function output($message, $depth = 0) {
    3522          ob_start();
    3523          $this->trace->output($message, $depth);
    3524          $this->buffer .= ob_get_contents();
    3525          if ($this->passthrough) {
    3526              ob_end_flush();
    3527          } else {
    3528              ob_end_clean();
    3529          }
    3530      }
    3531  
    3532      /**
    3533       * Called when the processing is finished.
    3534       */
    3535      public function finished() {
    3536          ob_start();
    3537          $this->trace->finished();
    3538          $this->buffer .= ob_get_contents();
    3539          if ($this->passthrough) {
    3540              ob_end_flush();
    3541          } else {
    3542              ob_end_clean();
    3543          }
    3544      }
    3545  
    3546      /**
    3547       * Reset internal text buffer.
    3548       */
    3549      public function reset_buffer() {
    3550          $this->buffer = '';
    3551      }
    3552  
    3553      /**
    3554       * Return internal text buffer.
    3555       * @return string buffered plain text
    3556       */
    3557      public function get_buffer() {
    3558          return $this->buffer;
    3559      }
    3560  }
    3561  
    3562  /**
    3563   * Special type of trace that can be used for redirecting to multiple other traces.
    3564   *
    3565   * @copyright Petr Skoda {@link http://skodak.org}
    3566   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    3567   * @package core
    3568   */
    3569  class combined_progress_trace extends progress_trace {
    3570  
    3571      /**
    3572       * An array of traces.
    3573       * @var array
    3574       */
    3575      protected $traces;
    3576  
    3577      /**
    3578       * Constructs a new instance.
    3579       *
    3580       * @param array $traces multiple traces
    3581       */
    3582      public function __construct(array $traces) {
    3583          $this->traces = $traces;
    3584      }
    3585  
    3586      /**
    3587       * Output an progress message in whatever format.
    3588       *
    3589       * @param string $message the message to output.
    3590       * @param integer $depth indent depth for this message.
    3591       */
    3592      public function output($message, $depth = 0) {
    3593          foreach ($this->traces as $trace) {
    3594              $trace->output($message, $depth);
    3595          }
    3596      }
    3597  
    3598      /**
    3599       * Called when the processing is finished.
    3600       */
    3601      public function finished() {
    3602          foreach ($this->traces as $trace) {
    3603              $trace->finished();
    3604          }
    3605      }
    3606  }
    3607  
    3608  /**
    3609   * Returns a localized sentence in the current language summarizing the current password policy
    3610   *
    3611   * @todo this should be handled by a function/method in the language pack library once we have a support for it
    3612   * @uses $CFG
    3613   * @return string
    3614   */
    3615  function print_password_policy() {
    3616      global $CFG;
    3617  
    3618      $message = '';
    3619      if (!empty($CFG->passwordpolicy)) {
    3620          $messages = array();
    3621          if (!empty($CFG->minpasswordlength)) {
    3622              $messages[] = get_string('informminpasswordlength', 'auth', $CFG->minpasswordlength);
    3623          }
    3624          if (!empty($CFG->minpassworddigits)) {
    3625              $messages[] = get_string('informminpassworddigits', 'auth', $CFG->minpassworddigits);
    3626          }
    3627          if (!empty($CFG->minpasswordlower)) {
    3628              $messages[] = get_string('informminpasswordlower', 'auth', $CFG->minpasswordlower);
    3629          }
    3630          if (!empty($CFG->minpasswordupper)) {
    3631              $messages[] = get_string('informminpasswordupper', 'auth', $CFG->minpasswordupper);
    3632          }
    3633          if (!empty($CFG->minpasswordnonalphanum)) {
    3634              $messages[] = get_string('informminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum);
    3635          }
    3636  
    3637          // Fire any additional password policy functions from plugins.
    3638          // Callbacks must return an array of message strings.
    3639          $pluginsfunction = get_plugins_with_function('print_password_policy');
    3640          foreach ($pluginsfunction as $plugintype => $plugins) {
    3641              foreach ($plugins as $pluginfunction) {
    3642                  $messages = array_merge($messages, $pluginfunction());
    3643              }
    3644          }
    3645  
    3646          $messages = join(', ', $messages); // This is ugly but we do not have anything better yet...
    3647          // Check if messages is empty before outputting any text.
    3648          if ($messages != '') {
    3649              $message = get_string('informpasswordpolicy', 'auth', $messages);
    3650          }
    3651      }
    3652      return $message;
    3653  }
    3654  
    3655  /**
    3656   * Get the value of a help string fully prepared for display in the current language.
    3657   *
    3658   * @param string $identifier The identifier of the string to search for.
    3659   * @param string $component The module the string is associated with.
    3660   * @param boolean $ajax Whether this help is called from an AJAX script.
    3661   *                This is used to influence text formatting and determines
    3662   *                which format to output the doclink in.
    3663   * @param string|object|array $a An object, string or number that can be used
    3664   *      within translation strings
    3665   * @return Object An object containing:
    3666   * - heading: Any heading that there may be for this help string.
    3667   * - text: The wiki-formatted help string.
    3668   * - doclink: An object containing a link, the linktext, and any additional
    3669   *            CSS classes to apply to that link. Only present if $ajax = false.
    3670   * - completedoclink: A text representation of the doclink. Only present if $ajax = true.
    3671   */
    3672  function get_formatted_help_string($identifier, $component, $ajax = false, $a = null) {
    3673      global $CFG, $OUTPUT;
    3674      $sm = get_string_manager();
    3675  
    3676      // Do not rebuild caches here!
    3677      // Devs need to learn to purge all caches after any change or disable $CFG->langstringcache.
    3678  
    3679      $data = new stdClass();
    3680  
    3681      if ($sm->string_exists($identifier, $component)) {
    3682          $data->heading = format_string(get_string($identifier, $component));
    3683      } else {
    3684          // Gracefully fall back to an empty string.
    3685          $data->heading = '';
    3686      }
    3687  
    3688      if ($sm->string_exists($identifier . '_help', $component)) {
    3689          $options = new stdClass();
    3690          $options->trusted = false;
    3691          $options->noclean = false;
    3692          $options->smiley = false;
    3693          $options->filter = false;
    3694          $options->para = true;
    3695          $options->newlines = false;
    3696          $options->overflowdiv = !$ajax;
    3697  
    3698          // Should be simple wiki only MDL-21695.
    3699          $data->text = format_text(get_string($identifier.'_help', $component, $a), FORMAT_MARKDOWN, $options);
    3700  
    3701          $helplink = $identifier . '_link';
    3702          if ($sm->string_exists($helplink, $component)) {  // Link to further info in Moodle docs.
    3703              $link = get_string($helplink, $component);
    3704              $linktext = get_string('morehelp');
    3705  
    3706              $data->doclink = new stdClass();
    3707              $url = new moodle_url(get_docs_url($link));
    3708              if ($ajax) {
    3709                  $data->doclink->link = $url->out();
    3710                  $data->doclink->linktext = $linktext;
    3711                  $data->doclink->class = ($CFG->doctonewwindow) ? 'helplinkpopup' : '';
    3712              } else {
    3713                  $data->completedoclink = html_writer::tag('div', $OUTPUT->doc_link($link, $linktext),
    3714                      array('class' => 'helpdoclink'));
    3715              }
    3716          }
    3717      } else {
    3718          $data->text = html_writer::tag('p',
    3719              html_writer::tag('strong', 'TODO') . ": missing help string [{$identifier}_help, {$component}]");
    3720      }
    3721      return $data;
    3722  }