Moodle PHP Cross Reference Learning Management Systems

Source: /lib/weblib.php - 3505 lines - 117137 bytes - Summary - Text - Print

Description: Library of functions for web output Library of all general-purpose Moodle PHP functions and constants that produce HTML output

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

title

Description

title

Description

title

Description

title

title

Body