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.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  
   3  /**
   4   * Creole parser implementation
   5   *
   6   * @author Josep ArĂºs
   7   *
   8   * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
   9   * @package mod_wiki
  10   */
  11  
  12  include_once ("wikimarkup.php");
  13  
  14  class creole_parser extends wiki_markup_parser {
  15  
  16      protected $blockrules = array(
  17          'nowiki' => array(
  18              'expression' => "/^\{\{\{(.*?)\}\}\}/ims",
  19              'tags' => array(),
  20              'token' => array('{{{', '}}}')
  21          ),
  22          'header' => array(
  23              'expression' => "/^\ *(={1,6})\ *(.+?)=*\ *$/ims",
  24              'tags' => array(), //none
  25              'token' => '='
  26          ),
  27          'table' => array(
  28              'expression' => "/^(?:\|.*?\|\ *\n)+/ims"
  29          ),
  30          'line_break' => array(
  31              'expression' => "/^----\s*$/im",
  32              'token' => '----',
  33              'tags' => array()
  34          ),
  35          'list' => array(
  36              'expression' => "/((?:^\ *[\*#][^\*#]\ *.+?)(?:^\ *[\*#]{1,5}\ *.+?)*)(\n\s*(?:\n|<(?:h\d|pre|table|tbody|thead|tr|th|td|ul|li|ol|hr)))/ims",
  37              'tags' => array(),
  38              'token' => array('*', '#')
  39          ),
  40          'paragraph' => array(
  41              'expression' => "/^\ *((?:<(?!\ *\/?(?:h\d|pre|table|tbody|thead|tr|th|td|ul|li|ol|hr)\ *\/?>)|[^<\s]).+?)\n\s*\n/ims",
  42              //not specified -> all tags (null or unset)
  43              'tag' => 'p'
  44          )
  45      );
  46  
  47      protected $tagrules = array(
  48          'nowiki' => array(
  49              'expression' => "/\{\{\{(.*?)\}\}\}/is",
  50              'token' => array('{{{', '}}}')
  51          ),
  52          'image' => array(
  53              'expression' => "/\~?\{\{(.+)\|(.+)\}\}/i",
  54              'tags' => array(),
  55              'token' => array('{{', '|Alt}}')
  56          ),
  57          'link' => array(
  58              'expression' => "/\~?\[\[(.+?)\]\]/is",
  59              'tag' => 'a',
  60              'token' => array('[[', ']]')
  61          ),
  62          'url' => array(
  63              'expression' => "/\~?(?<!=\")((?:https?|ftp):\/\/[^\s\n]+[^,\.\?!:;\"\'\n\ ])/is",
  64              'tag' => 'a',
  65              'token' => "http://"
  66          ),
  67          'line_break' => array(
  68              'expression' => "/\\\\\\\\/",
  69              'tag' => 'br',
  70              'simple' => true,
  71              'token' => '----'
  72          ),
  73          'bold' => array(
  74              'expression' => "/\*\*(.*?)(?:\*\*|$)/is",
  75              'tag' => 'strong',
  76              'token' => array('**', '**')
  77          ),
  78          'italic' => array(
  79              'expression' => "#(?<!http:|https:|ftp:)//(.+?)(?<!http:|https:|ftp:)//#is",
  80              'tag' => 'em',
  81              'token' => array('//', '//')
  82          )
  83      );
  84  
  85      /**
  86       * Block hooks
  87       */
  88  
  89      protected function before_parsing() {
  90          $this->string = htmlspecialchars($this->string, ENT_COMPAT);
  91          parent::before_parsing();
  92      }
  93  
  94      public function get_section($header, $text, $clean = false) {
  95          // The requested header is likely to have been passed to htmlspecialchars in
  96          // self::before_parsing(), therefore we should decode it when looking for it.
  97          return parent::get_section(htmlspecialchars_decode($header, ENT_COMPAT), $text, $clean);
  98      }
  99  
 100      protected function header_block_rule($match) {
 101          $num = strlen($match[1]);
 102  
 103          $text = trim($match[2]);
 104  
 105          $text = preg_replace("/\s*={1,$num}$/im", "", $text);
 106  
 107          return $this->generate_header($text, $num);
 108      }
 109  
 110      /**
 111       * Table generation
 112       */
 113  
 114      protected function table_block_rule($match) {
 115  
 116          $rows = explode("\n", $match[0]);
 117          $table = array();
 118          foreach($rows as $r) {
 119              if(empty($r)) {
 120                  continue;
 121              }
 122              $rawcells = explode("|", $r);
 123              $cells = array();
 124  
 125              array_shift($rawcells);
 126              array_pop($rawcells);
 127  
 128              foreach($rawcells as $c) {
 129                  if(!empty($c)) {
 130                      if($c[0] == "=") {
 131                          $type = 'header';
 132                          $c = substr($c, 1);
 133                      }
 134                      else {
 135                          $type = 'normal';
 136                      }
 137                      $this->rules($c);
 138                      $cells[] = array($type, $c);
 139                  }
 140              }
 141              $table[] = $cells;
 142          }
 143  
 144          return $this->generate_table($table);
 145      }
 146  
 147      protected function paragraph_block_rule($match) {
 148          $text = $match[1];
 149          foreach($this->tagrules as $tr) {
 150              if(isset($tr['token'])) {
 151                  if(is_array($tr['token'])) {
 152                      $this->escape_token_string($text, $tr['token'][0]);
 153                      $this->escape_token_string($text, $tr['token'][1]);
 154                  }
 155                  else {
 156                      $this->escape_token_string($text, $tr['token']);
 157                  }
 158              }
 159          }
 160          $this->escape_token_string($text, "~");
 161  
 162          return $text;
 163      }
 164  
 165      /**
 166       * Escape token when it is "negated"
 167       */
 168      private function escape_token_string(&$text, $token) {
 169          $text = str_replace("~".$token, $this->protect($token), $text);
 170      }
 171  
 172      /**
 173       * Tag functions
 174       */
 175  
 176      protected function url_tag_rule($match) {
 177          if(strpos($match[0], "~") === 0) {
 178              return substr($match[0], 1);
 179          }
 180          else {
 181              $text = trim($match[0]);
 182              $options = array('href' => $text);
 183  
 184              return array($text, $options);
 185          }
 186      }
 187  
 188      protected function link_tag_rule($match) {
 189          $text = trim($match[1]);
 190  
 191          if(strpos($match[0], "~") === 0) {
 192              return substr($match[0], 1);
 193          }
 194          else {
 195              return $this->format_link($text);
 196          }
 197      }
 198  
 199      /**
 200       * Special treatment of // ** // ** //
 201       */
 202      protected function bold_tag_rule($match) {
 203          $text = $match[1];
 204          $this->rules($text, array('only' => array('italic')));
 205          if(strpos($text, "//") !== false) {
 206              $text = str_replace("//", $this->protect("//"), $text);
 207          }
 208          return array($text, array());
 209      }
 210  
 211      protected function image_tag_rule($match) {
 212          if(strpos($match[0], "~") === 0) {
 213              return substr($match[0], 1);
 214          }
 215  
 216          return $this->format_image($match[1], $match[2]);
 217      }
 218  }