Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   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 and constants for module wiki
  19   *
  20   * It contains the great majority of functions defined by Moodle
  21   * that are mandatory to develop a module.
  22   *
  23   * @package mod_wiki
  24   * @copyright 2009 Marc Alier, Jordi Piguillem marc.alier@upc.edu
  25   * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
  26   *
  27   * @author Jordi Piguillem
  28   * @author Marc Alier
  29   * @author David Jimenez
  30   * @author Josep Arus
  31   * @author Kenneth Riba
  32   *
  33   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  
  36  defined('MOODLE_INTERNAL') || die();
  37  
  38  /**
  39   * Generic parser implementation
  40   *
  41   * @author Josep ArĂºs
  42   *
  43   * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  44   * @package mod_wiki
  45   */
  46  class wiki_parser_proxy {
  47      private static $parsers = array();
  48      private static $basepath = "";
  49  
  50      public static function parse(&$string, $type, $options = array()) {
  51  
  52          if (empty(self::$basepath)) {
  53              global $CFG;
  54              self::$basepath = $CFG->dirroot . '/mod/wiki/parser/';
  55          }
  56  
  57          $type = strtolower($type);
  58          self::$parsers[$type] = null; // Reset the current parser because it may have other options.
  59          if (self::create_parser_instance($type)) {
  60              return self::$parsers[$type]->parse($string, $options);
  61          } else {
  62              return false;
  63          }
  64      }
  65  
  66      public static function get_token($name, $type) {
  67          if (self::create_parser_instance($type)) {
  68              return self::$parsers[$type]->get_token($name);
  69          } else {
  70              return false;
  71          }
  72      }
  73  
  74      public static function get_section(&$string, $type, $section, $allcontent = false) {
  75          if (self::create_parser_instance($type)) {
  76              $content = self::$parsers[$type]->get_section($section, $string, true);
  77  
  78              if ($allcontent) {
  79                  return $content;
  80              } else {
  81                  return is_array($content) ? $content[1] : null;
  82              }
  83          } else {
  84              return false;
  85          }
  86      }
  87  
  88      private static function create_parser_instance($type) {
  89          if (empty(self::$parsers[$type])) {
  90              include_once(self::$basepath . "markups/$type.php");
  91              $class = strtolower($type) . "_parser";
  92              if (class_exists($class)) {
  93                  self::$parsers[$type] = new $class;
  94                  return true;
  95              } else {
  96                  return false;
  97              }
  98          } else {
  99              return true;
 100          }
 101      }
 102  }
 103  
 104  require_once ('utils.php');
 105  
 106  abstract class generic_parser {
 107      protected $string;
 108  
 109      protected $blockrules = array();
 110      protected $tagrules = array();
 111  
 112      private $rulestack = array();
 113  
 114      protected $parserstatus = 'Before';
 115  
 116      /**
 117       * Dynamic return values
 118       */
 119  
 120      protected $returnvalues = array();
 121  
 122      private $nowikiindex = array();
 123  
 124      protected $nowikitoken = "%!";
 125  
 126      public function __construct() {
 127      }
 128  
 129      /**
 130       * Parse function
 131       */
 132      public function parse(&$string, $options = array()) {
 133          if (!is_string($string)) {
 134              return false;
 135          }
 136  
 137          $this->string =& $string;
 138  
 139          $this->set_options(is_array($options) ? $options : array());
 140  
 141          $this->initialize_nowiki_index();
 142  
 143          if (method_exists($this, 'before_parsing')) {
 144              $this->before_parsing();
 145          }
 146  
 147          $this->parserstatus = 'Parsing';
 148  
 149          foreach ($this->blockrules as $name => $block) {
 150              $this->process_block_rule($name, $block);
 151          }
 152  
 153          $this->commit_nowiki_index();
 154  
 155          $this->parserstatus = 'After';
 156  
 157          if (method_exists($this, 'after_parsing')) {
 158              $this->after_parsing();
 159          }
 160  
 161          return array('parsed_text' => $this->string) + $this->returnvalues;
 162      }
 163  
 164      /**
 165       * Initialize options
 166       */
 167      protected function set_options($options) {
 168      }
 169  
 170      /**
 171       * Block processing function & callbacks
 172       */
 173      protected function process_block_rule($name, $block) {
 174          $this->rulestack[] = array('callback' => method_exists($this, $name . "_block_rule") ? $name . "_block_rule" : null,
 175              'rule' => $block);
 176  
 177          $this->string = preg_replace_callback($block['expression'], array($this, 'block_callback'), $this->string);
 178  
 179          array_pop($this->rulestack);
 180      }
 181  
 182      private function block_callback($match) {
 183          $rule = end($this->rulestack);
 184          if (!empty($rule['callback'])) {
 185              $stuff = $this->{$rule['callback']}($match);
 186          } else {
 187              $stuff = $match[1];
 188          }
 189  
 190          if (is_array($stuff) && $rule['rule']['tag']) {
 191              $this->rules($stuff[0], $rule['rule']['tags']);
 192              $stuff = "\n" . parser_utils::h($rule['rule']['tag'], $stuff[0], $stuff[1]) . "\n";
 193          } else {
 194              if (!isset($rule['rule']['tags'])) {
 195                  $rule['rule']['tags'] = null;
 196              }
 197              $this->rules($stuff, $rule['rule']['tags']);
 198              if (isset($rule['rule']['tag']) && is_string($rule['rule']['tag'])) {
 199                  $stuff = "\n" . parser_utils::h($rule['rule']['tag'], $stuff) . "\n";
 200              }
 201          }
 202  
 203          return $stuff;
 204      }
 205  
 206      /**
 207       * Rules processing function & callback
 208       */
 209  
 210      protected final function rules(&$text, $rules = null) {
 211          if ($rules === null) {
 212              $rules = array('except' => array());
 213          } else if (is_array($rules) && count($rules) > 1) {
 214              $rules = array('only' => $rules);
 215          }
 216  
 217          if (isset($rules['only']) && is_array($rules['only'])) {
 218              $rules = $rules['only'];
 219              foreach ($rules as $r) {
 220                  if (!empty($this->tagrules[$r])) {
 221                      $this->process_tag_rule($r, $this->tagrules[$r], $text);
 222                  }
 223              }
 224          } else if (isset($rules['except']) && is_array($rules['except'])) {
 225              $rules = $rules['except'];
 226              foreach ($this->tagrules as $r => $tr) {
 227                  if (!in_array($r, $rules)) {
 228                      $this->process_tag_rule($r, $tr, $text);
 229                  }
 230              }
 231          }
 232      }
 233  
 234      private function process_tag_rule($name, $rule, &$text) {
 235          if (method_exists($this, $name . "_tag_rule")) {
 236              $this->rulestack[] = array('callback' => $name . "_tag_rule", 'rule' => $rule);
 237              $text = preg_replace_callback($rule['expression'], array($this, 'tag_callback'), $text);
 238              array_pop($this->rulestack);
 239          } else {
 240              if (isset($rule['simple'])) {
 241                  $replace = "<{$rule['tag']} />";
 242              } else {
 243                  $replace = parser_utils::h($rule['tag'], "$1");
 244              }
 245  
 246              $text = preg_replace($rule['expression'], $replace, $text);
 247          }
 248      }
 249  
 250      private function tag_callback($match) {
 251          $rule = end($this->rulestack);
 252          $stuff = $this->{$rule['callback']}($match);
 253  
 254          if (is_array($stuff)) {
 255              return parser_utils::h($rule['rule']['tag'], $stuff[0], $stuff[1]);
 256          } else {
 257              return $stuff;
 258          }
 259      }
 260  
 261      /**
 262       * Special nowiki parser index
 263       */
 264  
 265      private function initialize_nowiki_index() {
 266          $token = "\Q" . $this->nowikitoken . "\E";
 267          $this->string = preg_replace_callback("/" . $token . "\d+" . $token . "/",
 268              array($this, "initialize_nowiki_index_callback"), $this->string);
 269      }
 270  
 271      private function initialize_nowiki_index_callback($match) {
 272          return $this->protect($match[0]);
 273      }
 274  
 275      protected function protect($text) {
 276          $this->nowikiindex[] = $text;
 277  
 278          return $this->nowikitoken . (count($this->nowikiindex) - 1) . $this->nowikitoken;
 279      }
 280  
 281      private function commit_nowiki_index() {
 282          $token = "\Q" . $this->nowikitoken . "\E";
 283          $this->string = preg_replace_callback("/" . $token . "(\d+)" . $token . "/",
 284              array($this, "commit_nowiki_index_callback"), $this->string);
 285      }
 286  
 287      private function commit_nowiki_index_callback($match) {
 288          return $this->nowikiindex[intval($match[1])];
 289      }
 290  
 291      /**
 292       * Get token of the parsable element $name.
 293       */
 294      public function get_token($name) {
 295          foreach (array_merge($this->blockrules, $this->tagrules) as $n => $v) {
 296              if ($name == $n && isset($v['token'])) {
 297                  return $v['token'] ? $v['token'] : false;
 298              }
 299          }
 300  
 301          return false;
 302      }
 303  }