Search moodle.org's
Developer Documentation


Long Term Support Release

  • Bug fixes for general core bugs in 3.1.x ended 8 May 2017 (12 months).
  • Bug fixes for security issues in 3.1.x ended 13 May 2019 (36 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x , 5.5.x or 5.6.x on Windows - http://windows.php.net/download/). PHP 7.0.x is supported but has some engine limitations. PHP 7.1.x is not supported.
  • /lib/ -> weblib.php (source)

    Differences Between: [Versions 29 and 31] [Versions 30 and 31] [Versions 31 and 32] [Versions 31 and 33] [Versions 31 and 34] [Versions 31 and 35] [Versions 31 and 36] [Versions 31 and 37] [Versions 31 and 38]

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

    Search This Site: