[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 use dokuwiki\Extension\Event; 4 use dokuwiki\Extension\SyntaxPlugin; 5 use dokuwiki\Parsing\Handler\Block; 6 use dokuwiki\Parsing\Handler\CallWriter; 7 use dokuwiki\Parsing\Handler\CallWriterInterface; 8 use dokuwiki\Parsing\Handler\Lists; 9 use dokuwiki\Parsing\Handler\Nest; 10 use dokuwiki\Parsing\Handler\Preformatted; 11 use dokuwiki\Parsing\Handler\Quote; 12 use dokuwiki\Parsing\Handler\Table; 13 14 /** 15 * Class Doku_Handler 16 */ 17 class Doku_Handler { 18 /** @var CallWriterInterface */ 19 protected $callWriter = null; 20 21 /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */ 22 public $calls = array(); 23 24 /** @var array internal status holders for some modes */ 25 protected $status = array( 26 'section' => false, 27 'doublequote' => 0, 28 ); 29 30 /** @var bool should blocks be rewritten? FIXME seems to always be true */ 31 protected $rewriteBlocks = true; 32 33 /** 34 * Doku_Handler constructor. 35 */ 36 public function __construct() { 37 $this->callWriter = new CallWriter($this); 38 } 39 40 /** 41 * Add a new call by passing it to the current CallWriter 42 * 43 * @param string $handler handler method name (see mode handlers below) 44 * @param mixed $args arguments for this call 45 * @param int $pos byte position in the original source file 46 */ 47 public function addCall($handler, $args, $pos) { 48 $call = array($handler,$args, $pos); 49 $this->callWriter->writeCall($call); 50 } 51 52 /** 53 * Accessor for the current CallWriter 54 * 55 * @return CallWriterInterface 56 */ 57 public function getCallWriter() { 58 return $this->callWriter; 59 } 60 61 /** 62 * Set a new CallWriter 63 * 64 * @param CallWriterInterface $callWriter 65 */ 66 public function setCallWriter($callWriter) { 67 $this->callWriter = $callWriter; 68 } 69 70 /** 71 * Return the current internal status of the given name 72 * 73 * @param string $status 74 * @return mixed|null 75 */ 76 public function getStatus($status) { 77 if (!isset($this->status[$status])) return null; 78 return $this->status[$status]; 79 } 80 81 /** 82 * Set a new internal status 83 * 84 * @param string $status 85 * @param mixed $value 86 */ 87 public function setStatus($status, $value) { 88 $this->status[$status] = $value; 89 } 90 91 /** @deprecated 2019-10-31 use addCall() instead */ 92 public function _addCall($handler, $args, $pos) { 93 dbg_deprecated('addCall'); 94 $this->addCall($handler, $args, $pos); 95 } 96 97 /** 98 * Similar to addCall, but adds a plugin call 99 * 100 * @param string $plugin name of the plugin 101 * @param mixed $args arguments for this call 102 * @param int $state a LEXER_STATE_* constant 103 * @param int $pos byte position in the original source file 104 * @param string $match matched syntax 105 */ 106 public function addPluginCall($plugin, $args, $state, $pos, $match) { 107 $call = array('plugin',array($plugin, $args, $state, $match), $pos); 108 $this->callWriter->writeCall($call); 109 } 110 111 /** 112 * Finishes handling 113 * 114 * Called from the parser. Calls finalise() on the call writer, closes open 115 * sections, rewrites blocks and adds document_start and document_end calls. 116 * 117 * @triggers PARSER_HANDLER_DONE 118 */ 119 public function finalize(){ 120 $this->callWriter->finalise(); 121 122 if ( $this->status['section'] ) { 123 $last_call = end($this->calls); 124 array_push($this->calls,array('section_close',array(), $last_call[2])); 125 } 126 127 if ( $this->rewriteBlocks ) { 128 $B = new Block(); 129 $this->calls = $B->process($this->calls); 130 } 131 132 Event::createAndTrigger('PARSER_HANDLER_DONE',$this); 133 134 array_unshift($this->calls,array('document_start',array(),0)); 135 $last_call = end($this->calls); 136 array_push($this->calls,array('document_end',array(),$last_call[2])); 137 } 138 139 /** 140 * fetch the current call and advance the pointer to the next one 141 * 142 * @fixme seems to be unused? 143 * @return bool|mixed 144 */ 145 public function fetch() { 146 $call = current($this->calls); 147 if($call !== false) { 148 next($this->calls); //advance the pointer 149 return $call; 150 } 151 return false; 152 } 153 154 155 /** 156 * Internal function for parsing highlight options. 157 * $options is parsed for key value pairs separated by commas. 158 * A value might also be missing in which case the value will simple 159 * be set to true. Commas in strings are ignored, e.g. option="4,56" 160 * will work as expected and will only create one entry. 161 * 162 * @param string $options space separated list of key-value pairs, 163 * e.g. option1=123, option2="456" 164 * @return array|null Array of key-value pairs $array['key'] = 'value'; 165 * or null if no entries found 166 */ 167 protected function parse_highlight_options($options) { 168 $result = array(); 169 preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER); 170 foreach ($matches as $match) { 171 $equal_sign = strpos($match [0], '='); 172 if ($equal_sign === false) { 173 $key = trim($match[0]); 174 $result [$key] = 1; 175 } else { 176 $key = substr($match[0], 0, $equal_sign); 177 $value = substr($match[0], $equal_sign+1); 178 $value = trim($value, '"'); 179 if (strlen($value) > 0) { 180 $result [$key] = $value; 181 } else { 182 $result [$key] = 1; 183 } 184 } 185 } 186 187 // Check for supported options 188 $result = array_intersect_key( 189 $result, 190 array_flip(array( 191 'enable_line_numbers', 192 'start_line_numbers_at', 193 'highlight_lines_extra', 194 'enable_keyword_links') 195 ) 196 ); 197 198 // Sanitize values 199 if(isset($result['enable_line_numbers'])) { 200 if($result['enable_line_numbers'] === 'false') { 201 $result['enable_line_numbers'] = false; 202 } 203 $result['enable_line_numbers'] = (bool) $result['enable_line_numbers']; 204 } 205 if(isset($result['highlight_lines_extra'])) { 206 $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra'])); 207 $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']); 208 $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']); 209 } 210 if(isset($result['start_line_numbers_at'])) { 211 $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at']; 212 } 213 if(isset($result['enable_keyword_links'])) { 214 if($result['enable_keyword_links'] === 'false') { 215 $result['enable_keyword_links'] = false; 216 } 217 $result['enable_keyword_links'] = (bool) $result['enable_keyword_links']; 218 } 219 if (count($result) == 0) { 220 return null; 221 } 222 223 return $result; 224 } 225 226 /** 227 * Simplifies handling for the formatting tags which all behave the same 228 * 229 * @param string $match matched syntax 230 * @param int $state a LEXER_STATE_* constant 231 * @param int $pos byte position in the original source file 232 * @param string $name actual mode name 233 */ 234 protected function nestingTag($match, $state, $pos, $name) { 235 switch ( $state ) { 236 case DOKU_LEXER_ENTER: 237 $this->addCall($name.'_open', array(), $pos); 238 break; 239 case DOKU_LEXER_EXIT: 240 $this->addCall($name.'_close', array(), $pos); 241 break; 242 case DOKU_LEXER_UNMATCHED: 243 $this->addCall('cdata', array($match), $pos); 244 break; 245 } 246 } 247 248 249 /** 250 * The following methods define the handlers for the different Syntax modes 251 * 252 * The handlers are called from dokuwiki\Parsing\Lexer\Lexer\invokeParser() 253 * 254 * @todo it might make sense to move these into their own class or merge them with the 255 * ParserMode classes some time. 256 */ 257 // region mode handlers 258 259 /** 260 * Special plugin handler 261 * 262 * This handler is called for all modes starting with 'plugin_'. 263 * An additional parameter with the plugin name is passed. The plugin's handle() 264 * method is called here 265 * 266 * @author Andreas Gohr <andi@splitbrain.org> 267 * 268 * @param string $match matched syntax 269 * @param int $state a LEXER_STATE_* constant 270 * @param int $pos byte position in the original source file 271 * @param string $pluginname name of the plugin 272 * @return bool mode handled? 273 */ 274 public function plugin($match, $state, $pos, $pluginname){ 275 $data = array($match); 276 /** @var SyntaxPlugin $plugin */ 277 $plugin = plugin_load('syntax',$pluginname); 278 if($plugin != null){ 279 $data = $plugin->handle($match, $state, $pos, $this); 280 } 281 if ($data !== false) { 282 $this->addPluginCall($pluginname,$data,$state,$pos,$match); 283 } 284 return true; 285 } 286 287 /** 288 * @param string $match matched syntax 289 * @param int $state a LEXER_STATE_* constant 290 * @param int $pos byte position in the original source file 291 * @return bool mode handled? 292 */ 293 public function base($match, $state, $pos) { 294 switch ( $state ) { 295 case DOKU_LEXER_UNMATCHED: 296 $this->addCall('cdata', array($match), $pos); 297 return true; 298 break; 299 } 300 return false; 301 } 302 303 /** 304 * @param string $match matched syntax 305 * @param int $state a LEXER_STATE_* constant 306 * @param int $pos byte position in the original source file 307 * @return bool mode handled? 308 */ 309 public function header($match, $state, $pos) { 310 // get level and title 311 $title = trim($match); 312 $level = 7 - strspn($title,'='); 313 if($level < 1) $level = 1; 314 $title = trim($title,'='); 315 $title = trim($title); 316 317 if ($this->status['section']) $this->addCall('section_close', array(), $pos); 318 319 $this->addCall('header', array($title, $level, $pos), $pos); 320 321 $this->addCall('section_open', array($level), $pos); 322 $this->status['section'] = true; 323 return true; 324 } 325 326 /** 327 * @param string $match matched syntax 328 * @param int $state a LEXER_STATE_* constant 329 * @param int $pos byte position in the original source file 330 * @return bool mode handled? 331 */ 332 public function notoc($match, $state, $pos) { 333 $this->addCall('notoc', array(), $pos); 334 return true; 335 } 336 337 /** 338 * @param string $match matched syntax 339 * @param int $state a LEXER_STATE_* constant 340 * @param int $pos byte position in the original source file 341 * @return bool mode handled? 342 */ 343 public function nocache($match, $state, $pos) { 344 $this->addCall('nocache', array(), $pos); 345 return true; 346 } 347 348 /** 349 * @param string $match matched syntax 350 * @param int $state a LEXER_STATE_* constant 351 * @param int $pos byte position in the original source file 352 * @return bool mode handled? 353 */ 354 public function linebreak($match, $state, $pos) { 355 $this->addCall('linebreak', array(), $pos); 356 return true; 357 } 358 359 /** 360 * @param string $match matched syntax 361 * @param int $state a LEXER_STATE_* constant 362 * @param int $pos byte position in the original source file 363 * @return bool mode handled? 364 */ 365 public function eol($match, $state, $pos) { 366 $this->addCall('eol', array(), $pos); 367 return true; 368 } 369 370 /** 371 * @param string $match matched syntax 372 * @param int $state a LEXER_STATE_* constant 373 * @param int $pos byte position in the original source file 374 * @return bool mode handled? 375 */ 376 public function hr($match, $state, $pos) { 377 $this->addCall('hr', array(), $pos); 378 return true; 379 } 380 381 /** 382 * @param string $match matched syntax 383 * @param int $state a LEXER_STATE_* constant 384 * @param int $pos byte position in the original source file 385 * @return bool mode handled? 386 */ 387 public function strong($match, $state, $pos) { 388 $this->nestingTag($match, $state, $pos, 'strong'); 389 return true; 390 } 391 392 /** 393 * @param string $match matched syntax 394 * @param int $state a LEXER_STATE_* constant 395 * @param int $pos byte position in the original source file 396 * @return bool mode handled? 397 */ 398 public function emphasis($match, $state, $pos) { 399 $this->nestingTag($match, $state, $pos, 'emphasis'); 400 return true; 401 } 402 403 /** 404 * @param string $match matched syntax 405 * @param int $state a LEXER_STATE_* constant 406 * @param int $pos byte position in the original source file 407 * @return bool mode handled? 408 */ 409 public function underline($match, $state, $pos) { 410 $this->nestingTag($match, $state, $pos, 'underline'); 411 return true; 412 } 413 414 /** 415 * @param string $match matched syntax 416 * @param int $state a LEXER_STATE_* constant 417 * @param int $pos byte position in the original source file 418 * @return bool mode handled? 419 */ 420 public function monospace($match, $state, $pos) { 421 $this->nestingTag($match, $state, $pos, 'monospace'); 422 return true; 423 } 424 425 /** 426 * @param string $match matched syntax 427 * @param int $state a LEXER_STATE_* constant 428 * @param int $pos byte position in the original source file 429 * @return bool mode handled? 430 */ 431 public function subscript($match, $state, $pos) { 432 $this->nestingTag($match, $state, $pos, 'subscript'); 433 return true; 434 } 435 436 /** 437 * @param string $match matched syntax 438 * @param int $state a LEXER_STATE_* constant 439 * @param int $pos byte position in the original source file 440 * @return bool mode handled? 441 */ 442 public function superscript($match, $state, $pos) { 443 $this->nestingTag($match, $state, $pos, 'superscript'); 444 return true; 445 } 446 447 /** 448 * @param string $match matched syntax 449 * @param int $state a LEXER_STATE_* constant 450 * @param int $pos byte position in the original source file 451 * @return bool mode handled? 452 */ 453 public function deleted($match, $state, $pos) { 454 $this->nestingTag($match, $state, $pos, 'deleted'); 455 return true; 456 } 457 458 /** 459 * @param string $match matched syntax 460 * @param int $state a LEXER_STATE_* constant 461 * @param int $pos byte position in the original source file 462 * @return bool mode handled? 463 */ 464 public function footnote($match, $state, $pos) { 465 if (!isset($this->_footnote)) $this->_footnote = false; 466 467 switch ( $state ) { 468 case DOKU_LEXER_ENTER: 469 // footnotes can not be nested - however due to limitations in lexer it can't be prevented 470 // we will still enter a new footnote mode, we just do nothing 471 if ($this->_footnote) { 472 $this->addCall('cdata', array($match), $pos); 473 break; 474 } 475 $this->_footnote = true; 476 477 $this->callWriter = new Nest($this->callWriter, 'footnote_close'); 478 $this->addCall('footnote_open', array(), $pos); 479 break; 480 case DOKU_LEXER_EXIT: 481 // check whether we have already exitted the footnote mode, can happen if the modes were nested 482 if (!$this->_footnote) { 483 $this->addCall('cdata', array($match), $pos); 484 break; 485 } 486 487 $this->_footnote = false; 488 $this->addCall('footnote_close', array(), $pos); 489 490 /** @var Nest $reWriter */ 491 $reWriter = $this->callWriter; 492 $this->callWriter = $reWriter->process(); 493 break; 494 case DOKU_LEXER_UNMATCHED: 495 $this->addCall('cdata', array($match), $pos); 496 break; 497 } 498 return true; 499 } 500 501 /** 502 * @param string $match matched syntax 503 * @param int $state a LEXER_STATE_* constant 504 * @param int $pos byte position in the original source file 505 * @return bool mode handled? 506 */ 507 public function listblock($match, $state, $pos) { 508 switch ( $state ) { 509 case DOKU_LEXER_ENTER: 510 $this->callWriter = new Lists($this->callWriter); 511 $this->addCall('list_open', array($match), $pos); 512 break; 513 case DOKU_LEXER_EXIT: 514 $this->addCall('list_close', array(), $pos); 515 /** @var Lists $reWriter */ 516 $reWriter = $this->callWriter; 517 $this->callWriter = $reWriter->process(); 518 break; 519 case DOKU_LEXER_MATCHED: 520 $this->addCall('list_item', array($match), $pos); 521 break; 522 case DOKU_LEXER_UNMATCHED: 523 $this->addCall('cdata', array($match), $pos); 524 break; 525 } 526 return true; 527 } 528 529 /** 530 * @param string $match matched syntax 531 * @param int $state a LEXER_STATE_* constant 532 * @param int $pos byte position in the original source file 533 * @return bool mode handled? 534 */ 535 public function unformatted($match, $state, $pos) { 536 if ( $state == DOKU_LEXER_UNMATCHED ) { 537 $this->addCall('unformatted', array($match), $pos); 538 } 539 return true; 540 } 541 542 /** 543 * @param string $match matched syntax 544 * @param int $state a LEXER_STATE_* constant 545 * @param int $pos byte position in the original source file 546 * @return bool mode handled? 547 */ 548 public function preformatted($match, $state, $pos) { 549 switch ( $state ) { 550 case DOKU_LEXER_ENTER: 551 $this->callWriter = new Preformatted($this->callWriter); 552 $this->addCall('preformatted_start', array(), $pos); 553 break; 554 case DOKU_LEXER_EXIT: 555 $this->addCall('preformatted_end', array(), $pos); 556 /** @var Preformatted $reWriter */ 557 $reWriter = $this->callWriter; 558 $this->callWriter = $reWriter->process(); 559 break; 560 case DOKU_LEXER_MATCHED: 561 $this->addCall('preformatted_newline', array(), $pos); 562 break; 563 case DOKU_LEXER_UNMATCHED: 564 $this->addCall('preformatted_content', array($match), $pos); 565 break; 566 } 567 568 return true; 569 } 570 571 /** 572 * @param string $match matched syntax 573 * @param int $state a LEXER_STATE_* constant 574 * @param int $pos byte position in the original source file 575 * @return bool mode handled? 576 */ 577 public function quote($match, $state, $pos) { 578 579 switch ( $state ) { 580 581 case DOKU_LEXER_ENTER: 582 $this->callWriter = new Quote($this->callWriter); 583 $this->addCall('quote_start', array($match), $pos); 584 break; 585 586 case DOKU_LEXER_EXIT: 587 $this->addCall('quote_end', array(), $pos); 588 /** @var Lists $reWriter */ 589 $reWriter = $this->callWriter; 590 $this->callWriter = $reWriter->process(); 591 break; 592 593 case DOKU_LEXER_MATCHED: 594 $this->addCall('quote_newline', array($match), $pos); 595 break; 596 597 case DOKU_LEXER_UNMATCHED: 598 $this->addCall('cdata', array($match), $pos); 599 break; 600 601 } 602 603 return true; 604 } 605 606 /** 607 * @param string $match matched syntax 608 * @param int $state a LEXER_STATE_* constant 609 * @param int $pos byte position in the original source file 610 * @return bool mode handled? 611 */ 612 public function file($match, $state, $pos) { 613 return $this->code($match, $state, $pos, 'file'); 614 } 615 616 /** 617 * @param string $match matched syntax 618 * @param int $state a LEXER_STATE_* constant 619 * @param int $pos byte position in the original source file 620 * @param string $type either 'code' or 'file' 621 * @return bool mode handled? 622 */ 623 public function code($match, $state, $pos, $type='code') { 624 if ( $state == DOKU_LEXER_UNMATCHED ) { 625 $matches = sexplode('>',$match,2,''); 626 // Cut out variable options enclosed in [] 627 preg_match('/\[.*\]/', $matches[0], $options); 628 if (!empty($options[0])) { 629 $matches[0] = str_replace($options[0], '', $matches[0]); 630 } 631 $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY); 632 while(count($param) < 2) array_push($param, null); 633 // We shortcut html here. 634 if ($param[0] == 'html') $param[0] = 'html4strict'; 635 if ($param[0] == '-') $param[0] = null; 636 array_unshift($param, $matches[1]); 637 if (!empty($options[0])) { 638 $param [] = $this->parse_highlight_options ($options[0]); 639 } 640 $this->addCall($type, $param, $pos); 641 } 642 return true; 643 } 644 645 /** 646 * @param string $match matched syntax 647 * @param int $state a LEXER_STATE_* constant 648 * @param int $pos byte position in the original source file 649 * @return bool mode handled? 650 */ 651 public function acronym($match, $state, $pos) { 652 $this->addCall('acronym', array($match), $pos); 653 return true; 654 } 655 656 /** 657 * @param string $match matched syntax 658 * @param int $state a LEXER_STATE_* constant 659 * @param int $pos byte position in the original source file 660 * @return bool mode handled? 661 */ 662 public function smiley($match, $state, $pos) { 663 $this->addCall('smiley', array($match), $pos); 664 return true; 665 } 666 667 /** 668 * @param string $match matched syntax 669 * @param int $state a LEXER_STATE_* constant 670 * @param int $pos byte position in the original source file 671 * @return bool mode handled? 672 */ 673 public function wordblock($match, $state, $pos) { 674 $this->addCall('wordblock', array($match), $pos); 675 return true; 676 } 677 678 /** 679 * @param string $match matched syntax 680 * @param int $state a LEXER_STATE_* constant 681 * @param int $pos byte position in the original source file 682 * @return bool mode handled? 683 */ 684 public function entity($match, $state, $pos) { 685 $this->addCall('entity', array($match), $pos); 686 return true; 687 } 688 689 /** 690 * @param string $match matched syntax 691 * @param int $state a LEXER_STATE_* constant 692 * @param int $pos byte position in the original source file 693 * @return bool mode handled? 694 */ 695 public function multiplyentity($match, $state, $pos) { 696 preg_match_all('/\d+/',$match,$matches); 697 $this->addCall('multiplyentity', array($matches[0][0], $matches[0][1]), $pos); 698 return true; 699 } 700 701 /** 702 * @param string $match matched syntax 703 * @param int $state a LEXER_STATE_* constant 704 * @param int $pos byte position in the original source file 705 * @return bool mode handled? 706 */ 707 public function singlequoteopening($match, $state, $pos) { 708 $this->addCall('singlequoteopening', array(), $pos); 709 return true; 710 } 711 712 /** 713 * @param string $match matched syntax 714 * @param int $state a LEXER_STATE_* constant 715 * @param int $pos byte position in the original source file 716 * @return bool mode handled? 717 */ 718 public function singlequoteclosing($match, $state, $pos) { 719 $this->addCall('singlequoteclosing', array(), $pos); 720 return true; 721 } 722 723 /** 724 * @param string $match matched syntax 725 * @param int $state a LEXER_STATE_* constant 726 * @param int $pos byte position in the original source file 727 * @return bool mode handled? 728 */ 729 public function apostrophe($match, $state, $pos) { 730 $this->addCall('apostrophe', array(), $pos); 731 return true; 732 } 733 734 /** 735 * @param string $match matched syntax 736 * @param int $state a LEXER_STATE_* constant 737 * @param int $pos byte position in the original source file 738 * @return bool mode handled? 739 */ 740 public function doublequoteopening($match, $state, $pos) { 741 $this->addCall('doublequoteopening', array(), $pos); 742 $this->status['doublequote']++; 743 return true; 744 } 745 746 /** 747 * @param string $match matched syntax 748 * @param int $state a LEXER_STATE_* constant 749 * @param int $pos byte position in the original source file 750 * @return bool mode handled? 751 */ 752 public function doublequoteclosing($match, $state, $pos) { 753 if ($this->status['doublequote'] <= 0) { 754 $this->doublequoteopening($match, $state, $pos); 755 } else { 756 $this->addCall('doublequoteclosing', array(), $pos); 757 $this->status['doublequote'] = max(0, --$this->status['doublequote']); 758 } 759 return true; 760 } 761 762 /** 763 * @param string $match matched syntax 764 * @param int $state a LEXER_STATE_* constant 765 * @param int $pos byte position in the original source file 766 * @return bool mode handled? 767 */ 768 public function camelcaselink($match, $state, $pos) { 769 $this->addCall('camelcaselink', array($match), $pos); 770 return true; 771 } 772 773 /** 774 * @param string $match matched syntax 775 * @param int $state a LEXER_STATE_* constant 776 * @param int $pos byte position in the original source file 777 * @return bool mode handled? 778 */ 779 public function internallink($match, $state, $pos) { 780 // Strip the opening and closing markup 781 $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match); 782 783 // Split title from URL 784 $link = sexplode('|',$link,2); 785 if ( $link[1] === null ) { 786 $link[1] = null; 787 } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) { 788 // If the title is an image, convert it to an array containing the image details 789 $link[1] = Doku_Handler_Parse_Media($link[1]); 790 } 791 $link[0] = trim($link[0]); 792 793 //decide which kind of link it is 794 795 if ( link_isinterwiki($link[0]) ) { 796 // Interwiki 797 $interwiki = sexplode('>',$link[0],2,''); 798 $this->addCall( 799 'interwikilink', 800 array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]), 801 $pos 802 ); 803 }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) { 804 // Windows Share 805 $this->addCall( 806 'windowssharelink', 807 array($link[0],$link[1]), 808 $pos 809 ); 810 }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) { 811 // external link (accepts all protocols) 812 $this->addCall( 813 'externallink', 814 array($link[0],$link[1]), 815 $pos 816 ); 817 }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) { 818 // E-Mail (pattern above is defined in inc/mail.php) 819 $this->addCall( 820 'emaillink', 821 array($link[0],$link[1]), 822 $pos 823 ); 824 }elseif ( preg_match('!^#.+!',$link[0]) ){ 825 // local link 826 $this->addCall( 827 'locallink', 828 array(substr($link[0],1),$link[1]), 829 $pos 830 ); 831 }else{ 832 // internal link 833 $this->addCall( 834 'internallink', 835 array($link[0],$link[1]), 836 $pos 837 ); 838 } 839 840 return true; 841 } 842 843 /** 844 * @param string $match matched syntax 845 * @param int $state a LEXER_STATE_* constant 846 * @param int $pos byte position in the original source file 847 * @return bool mode handled? 848 */ 849 public function filelink($match, $state, $pos) { 850 $this->addCall('filelink', array($match, null), $pos); 851 return true; 852 } 853 854 /** 855 * @param string $match matched syntax 856 * @param int $state a LEXER_STATE_* constant 857 * @param int $pos byte position in the original source file 858 * @return bool mode handled? 859 */ 860 public function windowssharelink($match, $state, $pos) { 861 $this->addCall('windowssharelink', array($match, null), $pos); 862 return true; 863 } 864 865 /** 866 * @param string $match matched syntax 867 * @param int $state a LEXER_STATE_* constant 868 * @param int $pos byte position in the original source file 869 * @return bool mode handled? 870 */ 871 public function media($match, $state, $pos) { 872 $p = Doku_Handler_Parse_Media($match); 873 874 $this->addCall( 875 $p['type'], 876 array($p['src'], $p['title'], $p['align'], $p['width'], 877 $p['height'], $p['cache'], $p['linking']), 878 $pos 879 ); 880 return true; 881 } 882 883 /** 884 * @param string $match matched syntax 885 * @param int $state a LEXER_STATE_* constant 886 * @param int $pos byte position in the original source file 887 * @return bool mode handled? 888 */ 889 public function rss($match, $state, $pos) { 890 $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match); 891 892 // get params 893 list($link, $params) = sexplode(' ', $link, 2, ''); 894 895 $p = array(); 896 if(preg_match('/\b(\d+)\b/',$params,$match)){ 897 $p['max'] = $match[1]; 898 }else{ 899 $p['max'] = 8; 900 } 901 $p['reverse'] = (preg_match('/rev/',$params)); 902 $p['author'] = (preg_match('/\b(by|author)/',$params)); 903 $p['date'] = (preg_match('/\b(date)/',$params)); 904 $p['details'] = (preg_match('/\b(desc|detail)/',$params)); 905 $p['nosort'] = (preg_match('/\b(nosort)\b/',$params)); 906 907 if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) { 908 $period = array('d' => 86400, 'h' => 3600, 'm' => 60); 909 $p['refresh'] = max(600,$match[1]*$period[$match[2]]); // n * period in seconds, minimum 10 minutes 910 } else { 911 $p['refresh'] = 14400; // default to 4 hours 912 } 913 914 $this->addCall('rss', array($link, $p), $pos); 915 return true; 916 } 917 918 /** 919 * @param string $match matched syntax 920 * @param int $state a LEXER_STATE_* constant 921 * @param int $pos byte position in the original source file 922 * @return bool mode handled? 923 */ 924 public function externallink($match, $state, $pos) { 925 $url = $match; 926 $title = null; 927 928 // add protocol on simple short URLs 929 if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){ 930 $title = $url; 931 $url = 'ftp://'.$url; 932 } 933 if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){ 934 $title = $url; 935 $url = 'http://'.$url; 936 } 937 938 $this->addCall('externallink', array($url, $title), $pos); 939 return true; 940 } 941 942 /** 943 * @param string $match matched syntax 944 * @param int $state a LEXER_STATE_* constant 945 * @param int $pos byte position in the original source file 946 * @return bool mode handled? 947 */ 948 public function emaillink($match, $state, $pos) { 949 $email = preg_replace(array('/^</','/>$/'),'',$match); 950 $this->addCall('emaillink', array($email, null), $pos); 951 return true; 952 } 953 954 /** 955 * @param string $match matched syntax 956 * @param int $state a LEXER_STATE_* constant 957 * @param int $pos byte position in the original source file 958 * @return bool mode handled? 959 */ 960 public function table($match, $state, $pos) { 961 switch ( $state ) { 962 963 case DOKU_LEXER_ENTER: 964 965 $this->callWriter = new Table($this->callWriter); 966 967 $this->addCall('table_start', array($pos + 1), $pos); 968 if ( trim($match) == '^' ) { 969 $this->addCall('tableheader', array(), $pos); 970 } else { 971 $this->addCall('tablecell', array(), $pos); 972 } 973 break; 974 975 case DOKU_LEXER_EXIT: 976 $this->addCall('table_end', array($pos), $pos); 977 /** @var Table $reWriter */ 978 $reWriter = $this->callWriter; 979 $this->callWriter = $reWriter->process(); 980 break; 981 982 case DOKU_LEXER_UNMATCHED: 983 if ( trim($match) != '' ) { 984 $this->addCall('cdata', array($match), $pos); 985 } 986 break; 987 988 case DOKU_LEXER_MATCHED: 989 if ( $match == ' ' ){ 990 $this->addCall('cdata', array($match), $pos); 991 } else if ( preg_match('/:::/',$match) ) { 992 $this->addCall('rowspan', array($match), $pos); 993 } else if ( preg_match('/\t+/',$match) ) { 994 $this->addCall('table_align', array($match), $pos); 995 } else if ( preg_match('/ {2,}/',$match) ) { 996 $this->addCall('table_align', array($match), $pos); 997 } else if ( $match == "\n|" ) { 998 $this->addCall('table_row', array(), $pos); 999 $this->addCall('tablecell', array(), $pos); 1000 } else if ( $match == "\n^" ) { 1001 $this->addCall('table_row', array(), $pos); 1002 $this->addCall('tableheader', array(), $pos); 1003 } else if ( $match == '|' ) { 1004 $this->addCall('tablecell', array(), $pos); 1005 } else if ( $match == '^' ) { 1006 $this->addCall('tableheader', array(), $pos); 1007 } 1008 break; 1009 } 1010 return true; 1011 } 1012 1013 // endregion modes 1014 } 1015 1016 //------------------------------------------------------------------------ 1017 function Doku_Handler_Parse_Media($match) { 1018 1019 // Strip the opening and closing markup 1020 $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match); 1021 1022 // Split title from URL 1023 $link = sexplode('|', $link, 2); 1024 1025 // Check alignment 1026 $ralign = (bool)preg_match('/^ /',$link[0]); 1027 $lalign = (bool)preg_match('/ $/',$link[0]); 1028 1029 // Logic = what's that ;)... 1030 if ( $lalign & $ralign ) { 1031 $align = 'center'; 1032 } else if ( $ralign ) { 1033 $align = 'right'; 1034 } else if ( $lalign ) { 1035 $align = 'left'; 1036 } else { 1037 $align = null; 1038 } 1039 1040 // The title... 1041 if ( !isset($link[1]) ) { 1042 $link[1] = null; 1043 } 1044 1045 //remove aligning spaces 1046 $link[0] = trim($link[0]); 1047 1048 //split into src and parameters (using the very last questionmark) 1049 $pos = strrpos($link[0], '?'); 1050 if($pos !== false){ 1051 $src = substr($link[0],0,$pos); 1052 $param = substr($link[0],$pos+1); 1053 }else{ 1054 $src = $link[0]; 1055 $param = ''; 1056 } 1057 1058 //parse width and height 1059 if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){ 1060 !empty($size[1]) ? $w = $size[1] : $w = null; 1061 !empty($size[3]) ? $h = $size[3] : $h = null; 1062 } else { 1063 $w = null; 1064 $h = null; 1065 } 1066 1067 //get linking command 1068 if(preg_match('/nolink/i',$param)){ 1069 $linking = 'nolink'; 1070 }else if(preg_match('/direct/i',$param)){ 1071 $linking = 'direct'; 1072 }else if(preg_match('/linkonly/i',$param)){ 1073 $linking = 'linkonly'; 1074 }else{ 1075 $linking = 'details'; 1076 } 1077 1078 //get caching command 1079 if (preg_match('/(nocache|recache)/i',$param,$cachemode)){ 1080 $cache = $cachemode[1]; 1081 }else{ 1082 $cache = 'cache'; 1083 } 1084 1085 // Check whether this is a local or remote image or interwiki 1086 if (media_isexternal($src) || link_isinterwiki($src)){ 1087 $call = 'externalmedia'; 1088 } else { 1089 $call = 'internalmedia'; 1090 } 1091 1092 $params = array( 1093 'type'=>$call, 1094 'src'=>$src, 1095 'title'=>$link[1], 1096 'align'=>$align, 1097 'width'=>$w, 1098 'height'=>$h, 1099 'cache'=>$cache, 1100 'linking'=>$linking, 1101 ); 1102 1103 return $params; 1104 } 1105
title
Description
Body
title
Description
Body
title
Description
Body
title
Body