[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> weblib.php (source)

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1