See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 3 /** 4 * Generic & abstract parser functions & skeleton. It has some functions & generic stuff. 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 abstract class wiki_markup_parser extends generic_parser { 13 14 protected $pretty_print = false; 15 protected $printable = false; 16 17 //page id 18 protected $wiki_page_id; 19 20 //sections 21 protected $repeated_sections; 22 23 protected $section_editing = true; 24 25 //header & ToC 26 protected $toc = array(); 27 protected $maxheaderdepth = 3; 28 29 /** 30 * function wiki_parser_link_callback($link = "") 31 * 32 * Returns array('content' => "Inside the link", 'url' => "http://url.com/Wiki/Entry", 'new' => false). 33 */ 34 private $linkgeneratorcallback = array('parser_utils', 'wiki_parser_link_callback'); 35 private $linkgeneratorcallbackargs = array(); 36 37 /** 38 * Table generator callback 39 */ 40 41 private $tablegeneratorcallback = array('parser_utils', 'wiki_parser_table_callback'); 42 43 /** 44 * Get real path from relative path 45 */ 46 private $realpathcallback = array('parser_utils', 'wiki_parser_real_path'); 47 private $realpathcallbackargs = array(); 48 49 /** 50 * Before and after parsing... 51 */ 52 53 protected function before_parsing() { 54 $this->toc = array(); 55 56 $this->string = preg_replace('/\r\n/', "\n", $this->string); 57 $this->string = preg_replace('/\r/', "\n", $this->string); 58 59 $this->string .= "\n\n"; 60 61 if (!$this->printable && $this->section_editing) { 62 $this->returnvalues['unparsed_text'] = $this->string; 63 $this->string = $this->get_repeated_sections($this->string); 64 } 65 } 66 67 protected function after_parsing() { 68 if (!$this->printable) { 69 $this->returnvalues['repeated_sections'] = array_unique($this->returnvalues['repeated_sections']); 70 } 71 72 $this->process_toc(); 73 74 $this->string = preg_replace("/\n\s/", "\n", $this->string); 75 $this->string = preg_replace("/\n{2,}/", "\n", $this->string); 76 $this->string = trim($this->string); 77 $this->string .= "\n"; 78 } 79 80 /** 81 * Set options 82 */ 83 84 protected function set_options($options) { 85 parent::set_options($options); 86 87 $this->returnvalues['link_count'] = array(); 88 $this->returnvalues['repeated_sections'] = array(); 89 $this->returnvalues['toc'] = ""; 90 91 foreach ($options as $name => $o) { 92 switch ($name) { 93 case 'link_callback': 94 $callback = explode(':', $o); 95 96 global $CFG; 97 require_once($CFG->dirroot . $callback[0]); 98 99 if (function_exists($callback[1])) { 100 $this->linkgeneratorcallback = $callback[1]; 101 } 102 break; 103 case 'link_callback_args': 104 if (is_array($o)) { 105 $this->linkgeneratorcallbackargs = $o; 106 } 107 break; 108 case 'real_path_callback': 109 $callback = explode(':', $o); 110 111 global $CFG; 112 require_once($CFG->dirroot . $callback[0]); 113 114 if (function_exists($callback[1])) { 115 $this->realpathcallback = $callback[1]; 116 } 117 break; 118 case 'real_path_callback_args': 119 if (is_array($o)) { 120 $this->realpathcallbackargs = $o; 121 } 122 break; 123 case 'table_callback': 124 $callback = explode(':', $o); 125 126 global $CFG; 127 require_once($CFG->dirroot . $callback[0]); 128 129 if (function_exists($callback[1])) { 130 $this->tablegeneratorcallback = $callback[1]; 131 } 132 break; 133 case 'pretty_print': 134 if ($o) { 135 $this->pretty_print = true; 136 } 137 break; 138 case 'pageid': 139 $this->wiki_page_id = $o; 140 break; 141 case 'printable': 142 if ($o) { 143 $this->printable = true; 144 } 145 break; 146 } 147 } 148 } 149 150 /** 151 * Generic block rules 152 */ 153 154 protected function line_break_block_rule($match) { 155 return '<hr />'; 156 } 157 158 protected function list_block_rule($match) { 159 preg_match_all("/^\ *([\*\#]{1,5})\ *((?:[^\n]|\n(?!(?:\ *[\*\#])|\n))+)/im", $match[1], $listitems, PREG_SET_ORDER); 160 161 return $this->process_block_list($listitems) . $match[2]; 162 } 163 164 protected function nowiki_block_rule($match) { 165 return parser_utils::h('pre', $this->protect($match[1])); 166 } 167 168 /** 169 * Generic tag rules 170 */ 171 172 protected function nowiki_tag_rule($match) { 173 return parser_utils::h('tt', $this->protect($match[1])); 174 } 175 176 /** 177 * Header generation 178 */ 179 180 protected function generate_header($text, $level) { 181 $toctext = $text = trim($text); 182 183 if (!$this->pretty_print && $level == 1) { 184 $editlink = '[' . get_string('editsection', 'wiki') . ']'; 185 $url = array('href' => "edit.php?pageid={$this->wiki_page_id}§ion=" . urlencode($text), 186 'class' => 'wiki_edit_section'); 187 $text .= ' ' . parser_utils::h('a', $this->protect($editlink), $url); 188 $toctext .= ' ' . parser_utils::h('a', $editlink, $url); 189 } 190 191 if ($level <= $this->maxheaderdepth) { 192 $this->toc[] = array($level, $toctext); 193 $num = count($this->toc); 194 $text = parser_utils::h('a', "", array('name' => "toc-$num")) . $text; 195 } 196 197 // Display headers as <h3> and lower for accessibility. 198 return parser_utils::h('h' . min(6, $level + 2), $text) . "\n\n"; 199 } 200 201 /** 202 * Table of contents processing after parsing 203 */ 204 protected function process_toc() { 205 if (empty($this->toc)) { 206 return; 207 } 208 209 $toc = ""; 210 $currentsection = array(0, 0, 0); 211 $i = 1; 212 foreach ($this->toc as & $header) { 213 switch ($header[0]) { 214 case 1: 215 $currentsection = array($currentsection[0] + 1, 0, 0); 216 break; 217 case 2: 218 $currentsection[1]++; 219 $currentsection[2] = 0; 220 if ($currentsection[0] == 0) { 221 $currentsection[0]++; 222 } 223 break; 224 case 3: 225 $currentsection[2]++; 226 if ($currentsection[1] == 0) { 227 $currentsection[1]++; 228 } 229 if ($currentsection[0] == 0) { 230 $currentsection[0]++; 231 } 232 break; 233 default: 234 continue 2; 235 } 236 $number = "$currentsection[0]"; 237 if (!empty($currentsection[1])) { 238 $number .= ".$currentsection[1]"; 239 if (!empty($currentsection[2])) { 240 $number .= ".$currentsection[2]"; 241 } 242 } 243 $toc .= parser_utils::h('p', $number . ". " . 244 parser_utils::h('a', str_replace(array('[[', ']]'), '', $header[1]), array('href' => "#toc-$i")), 245 array('class' => 'wiki-toc-section-' . $header[0] . " wiki-toc-section")); 246 $i++; 247 } 248 249 $this->returnvalues['toc'] = "<div class=\"wiki-toc\"><p class=\"wiki-toc-title\">" . get_string('tableofcontents', 'wiki') . "</p>$toc</div>"; 250 } 251 252 /** 253 * List helpers 254 */ 255 256 private function process_block_list($listitems) { 257 $list = array(); 258 foreach ($listitems as $li) { 259 $text = str_replace("\n", "", $li[2]); 260 $this->rules($text); 261 262 if ($li[1][0] == '*') { 263 $type = 'ul'; 264 } else { 265 $type = 'ol'; 266 } 267 268 $list[] = array(strlen($li[1]), $text, $type); 269 } 270 $type = $list[0][2]; 271 return "<$type>" . "\n" . $this->generate_list($list) . "\n" . "</$type>" . "\n"; 272 } 273 274 /** 275 * List generation function from an array of array(level, text) 276 */ 277 278 protected function generate_list($listitems) { 279 $list = ""; 280 $current_depth = 1; 281 $next_depth = 1; 282 $liststack = array(); 283 for ($lc = 0; $lc < count($listitems) && $next_depth; $lc++) { 284 $cli = $listitems[$lc]; 285 $nli = isset($listitems[$lc + 1]) ? $listitems[$lc + 1] : null; 286 287 $text = $cli[1]; 288 289 $current_depth = $next_depth; 290 $next_depth = $nli ? $nli[0] : null; 291 292 if ($next_depth == $current_depth || $next_depth == null) { 293 $list .= parser_utils::h('li', $text) . "\n"; 294 } else if ($next_depth > $current_depth) { 295 $next_depth = $current_depth + 1; 296 297 $list .= "<li>" . $text . "\n"; 298 $list .= "<" . $nli[2] . ">" . "\n"; 299 $liststack[] = $nli[2]; 300 } else { 301 $list .= parser_utils::h('li', $text) . "\n"; 302 303 for ($lv = $next_depth; $lv < $current_depth; $lv++) { 304 $type = array_pop($liststack); 305 $list .= "</$type>" . "\n" . "</li>" . "\n"; 306 } 307 } 308 } 309 310 for ($lv = 1; $lv < $current_depth; $lv++) { 311 $type = array_pop($liststack); 312 $list .= "</$type>" . "\n" . "</li>" . "\n"; 313 } 314 315 return $list; 316 } 317 318 /** 319 * Table generation functions 320 */ 321 322 protected function generate_table($table) { 323 $table_html = call_user_func_array($this->tablegeneratorcallback, array($table)); 324 325 return $table_html; 326 } 327 328 protected function format_image($src, $alt, $caption = "", $align = 'left') { 329 $src = $this->real_path($src); 330 return parser_utils::h('div', parser_utils::h('p', $caption) . '<img src="' . $src . '" alt="' . $alt . '" />', array('class' => 'wiki_image_' . $align)); 331 } 332 333 protected function real_path($url) { 334 $callbackargs = array_merge(array($url), $this->realpathcallbackargs); 335 return call_user_func_array($this->realpathcallback, $callbackargs); 336 } 337 338 /** 339 * Link internal callback 340 */ 341 342 protected function link($link, $anchor = "") { 343 $link = trim($link); 344 if (preg_match("/^(https?|s?ftp):\/\/.+$/i", $link)) { 345 $link = trim($link, ",.?!"); 346 return array('content' => $link, 'url' => $link); 347 } else { 348 $callbackargs = $this->linkgeneratorcallbackargs; 349 $callbackargs['anchor'] = $anchor; 350 $link = call_user_func_array($this->linkgeneratorcallback, array($link, $callbackargs)); 351 352 if (isset($link['link_info'])) { 353 $l = $link['link_info']['link']; 354 unset($link['link_info']['link']); 355 $this->returnvalues['link_count'][$l] = $link['link_info']; 356 } 357 return $link; 358 } 359 } 360 361 /** 362 * Format links 363 */ 364 365 protected function format_link($text) { 366 $matches = array(); 367 if (preg_match("/^([^\|]+)\|(.+)$/i", $text, $matches)) { 368 $link = $matches[1]; 369 $content = trim($matches[2]); 370 if (preg_match("/(.+)#(.*)/is", $link, $matches)) { 371 $link = $this->link($matches[1], $matches[2]); 372 } else if ($link[0] == '#') { 373 $link = array('url' => "#" . urlencode(substr($link, 1))); 374 } else { 375 $link = $this->link($link); 376 } 377 378 $link['content'] = $content; 379 } else { 380 $link = $this->link($text); 381 } 382 383 if (isset($link['new']) && $link['new']) { 384 $options = array('class' => 'wiki_newentry'); 385 } else { 386 $options = array(); 387 } 388 389 $link['content'] = $this->protect($link['content']); 390 $link['url'] = $this->protect($link['url']); 391 392 $options['href'] = $link['url']; 393 394 if ($this->printable) { 395 $options['href'] = '#'; //no target for the link 396 } 397 return array($link['content'], $options); 398 } 399 400 /** 401 * Section editing 402 */ 403 404 public function get_section($header, $text, $clean = false) { 405 if ($clean) { 406 $text = preg_replace('/\r\n/', "\n", $text); 407 $text = preg_replace('/\r/', "\n", $text); 408 $text .= "\n\n"; 409 } 410 411 $regex = "/(.*?)(=\ *".preg_quote($header, '/')."\ *=*\n.*?)((?:\n=[^=]+.*)|$)/is"; 412 preg_match($regex, $text, $match); 413 414 if (!empty($match)) { 415 return array($match[1], $match[2], $match[3]); 416 } else { 417 return false; 418 } 419 } 420 421 protected function get_repeated_sections(&$text, $repeated = array()) { 422 $this->repeated_sections = $repeated; 423 return preg_replace_callback($this->blockrules['header']['expression'], array($this, 'get_repeated_sections_callback'), $text); 424 } 425 426 protected function get_repeated_sections_callback($match) { 427 $num = strlen($match[1]); 428 $text = trim($match[2]); 429 if ($num == 1) { 430 if (in_array($text, $this->repeated_sections)) { 431 $this->returnvalues['repeated_sections'][] = $text; 432 return $text . "\n"; 433 } else { 434 $this->repeated_sections[] = $text; 435 } 436 } 437 438 return $match[0]; 439 } 440 441 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body