1<?php 2 3/** 4 * Mikio Core Syntax Plugin 5 * 6 * @link http://github.com/nomadjimbob/mikioplugin 7 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 8 * @author James Collins <james.collins@outlook.com.au> 9 */ 10if (!defined('DOKU_INC')) { die(); 11} 12if (!defined('DOKU_PLUGIN')) { define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 13} 14 15define('MIKIO_LEXER_AUTO', 0); 16define('MIKIO_LEXER_ENTER', 1); 17define('MIKIO_LEXER_EXIT', 2); 18define('MIKIO_LEXER_SPECIAL', 3); 19 20class syntax_plugin_mikioplugin_core extends DokuWiki_Syntax_Plugin 21{ 22 public $pattern_entry = ''; 23 public $pattern = ''; 24 public $pattern_exit = ''; 25 public $tag = ''; 26 public $hasEndTag = true; 27 public $options = array(); 28 29 protected $tagPrefix = ''; //'mikio-'; 30 protected $classPrefix = 'mikiop-'; 31 protected $elemClass = 'mikiop'; 32 33 private $values = array(); 34 35 36 function __construct() 37 { 38 } 39 public function getType() 40 { 41 return 'formatting'; 42 } 43 public function getAllowedTypes() 44 { 45 return array('formatting', 'substition', 'disabled', 'paragraphs'); 46 } 47 // public function getAllowedTypes() { return array('formatting', 'substition', 'disabled'); } 48 public function getSort() 49 { 50 return 32; 51 } 52 public function getPType() 53 { 54 return 'stack'; 55 } 56 57 58 public function connectTo($mode) 59 { 60 if ($this->pattern_entry == '' && $this->tag != '') { 61 if ($this->hasEndTag) { 62 $this->pattern_entry = '<(?i:' . $this->tagPrefix . $this->tag . ')(?=[ >]).*?>(?=.*?</(?i:' . $this->tagPrefix . $this->tag . ')>)'; 63 } else { 64 $this->pattern_entry = '<(?i:' . $this->tagPrefix . $this->tag . ').*?>'; 65 } 66 } 67 68 if ($this->pattern_entry != '') { 69 if ($this->hasEndTag) { 70 $this->Lexer->addEntryPattern($this->pattern_entry, $mode, 'plugin_mikioplugin_' . $this->getPluginComponent()); 71 } else { 72 $this->Lexer->addSpecialPattern($this->pattern_entry, $mode, 'plugin_mikioplugin_' . $this->getPluginComponent()); 73 } 74 } 75 } 76 77 78 public function postConnect() 79 { 80 if ($this->hasEndTag) { 81 if ($this->pattern_exit == '' && $this->tag != '') { 82 $this->pattern_exit = '</(?i:' . $this->tagPrefix . $this->tag . ')>'; 83 } 84 85 if ($this->pattern_exit != '') { 86 $this->Lexer->addExitPattern($this->pattern_exit, 'plugin_mikioplugin_' . $this->getPluginComponent()); 87 } 88 } 89 } 90 91 public function handle($match, $state, $pos, Doku_Handler $handler) 92 { 93 switch ($state) { 94 case DOKU_LEXER_ENTER: 95 case DOKU_LEXER_SPECIAL: 96 $match_fix = preg_replace('/\s*=\s*/', '=', trim(substr($match, strlen($this->tagPrefix . $this->tag) + 1, -1))); 97 $optionlist = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', $match_fix); 98 99 $options = array(); 100 foreach ($optionlist as $item) { 101 $i = strpos($item, '='); 102 if ($i !== false) { 103 $value = substr($item, $i + 1); 104 105 if (substr($value, 0, 1) == '"') { $value = substr($value, 1); 106 } 107 if (substr($value, -1) == '"') { $value = substr($value, 0, -1); 108 } 109 110 $options[substr($item, 0, $i)] = $value; 111 } else { 112 $options[$item] = true; 113 } 114 } 115 116 if (count($this->options) > 0) { 117 $options_clean = $this->cleanOptions($options); 118 } else { 119 $options_clean = $options; 120 } 121 122 $this->values = $options_clean; 123 124 return array($state, $options_clean); 125 126 case DOKU_LEXER_MATCHED: 127 return array($state, $match); 128 129 case DOKU_LEXER_UNMATCHED: 130 return array($state, $match); 131 132 case DOKU_LEXER_EXIT: 133 return array($state, $this->values); 134 } 135 136 return array(); 137 } 138 139 140 /* 141 * clean element options to only supported attributes, setting defaults if required 142 * 143 * @param $options options passed to element 144 * @return array of options supported with default set 145 */ 146 protected function cleanOptions($data, $options = null) 147 { 148 $optionsCleaned = array(); 149 150 if ($options == null) { $options = $this->options; 151 } 152 153 // Match DokuWiki passed options to syntax options 154 foreach ($data as $optionKey => $optionValue) { 155 foreach ($options as $syntaxKey => $syntaxValue) { 156 if (strcasecmp($optionKey, $syntaxKey) == 0) { 157 if (array_key_exists('type', $options[$syntaxKey])) { 158 $type = $options[$syntaxKey]['type']; 159 160 switch ($type) { 161 case 'boolean': 162 $optionsCleaned[$syntaxKey] = filter_var($optionValue, FILTER_VALIDATE_BOOLEAN); 163 break; 164 case 'number': 165 $optionsCleaned[$syntaxKey] = filter_var($optionValue, FILTER_VALIDATE_INT); 166 break; 167 case 'float': 168 $optionsCleaned[$syntaxKey] = filter_var($optionValue, FILTER_VALIDATE_FLOAT); 169 break; 170 case 'text': 171 $optionsCleaned[$syntaxKey] = $optionValue; 172 break; 173 case 'size': 174 $s = strtolower($optionValue); 175 $i = ''; 176 if (substr($s, -3) == 'rem') { 177 $i = substr($s, 0, -3); 178 $s = 'rem'; 179 } elseif (substr($s, -2) == 'em') { 180 $i = substr($s, 0, -2); 181 $s = 'em'; 182 } elseif (substr($s, -2) == 'px') { 183 $i = substr($s, 0, -2); 184 $s = 'px'; 185 } elseif (substr($s, -1) == '%') { 186 $i = substr($s, 0, -1); 187 $s = '%'; 188 } else { 189 if ($s != 'auto') { 190 $i = filter_var($s, FILTER_VALIDATE_INT); 191 if ($i == '') { $i = '1'; 192 } 193 $s = 'rem'; 194 } 195 } 196 197 $optionsCleaned[$syntaxKey] = $i . $s; 198 break; 199 case 'multisize': 200 $val = ''; 201 $parts = explode(' ', $optionValue); 202 foreach ($parts as &$part) { 203 $s = strtolower($part); 204 $i = ''; 205 if (substr($s, -3) == 'rem') { 206 $i = substr($s, 0, -3); 207 $s = 'rem'; 208 } elseif (substr($s, -2) == 'em') { 209 $i = substr($s, 0, -2); 210 $s = 'em'; 211 } elseif (substr($s, -2) == 'px') { 212 $i = substr($s, 0, -2); 213 $s = 'px'; 214 } elseif (substr($s, -2) == 'fr') { 215 $i = substr($s, 0, -2); 216 $s = 'fr'; 217 } elseif (substr($s, -1) == '%') { 218 $i = substr($s, 0, -1); 219 $s = '%'; 220 } else { 221 if ($s != 'auto') { 222 $i = filter_var($s, FILTER_VALIDATE_INT); 223 if ($i === '') { $i = '1'; 224 } 225 if ($i != 0) { 226 $s = 'rem'; 227 } else { 228 $s = ''; 229 } 230 } 231 } 232 233 $part = $i . $s; 234 } 235 236 $optionsCleaned[$syntaxKey] = implode(' ', $parts); 237 break; 238 case 'color': 239 if (strlen($optionValue) == 3 || strlen($optionValue) == 6) { 240 preg_match('/([[:xdigit:]]{3}){1,2}/', $optionValue, $matches); 241 if (count($matches) > 1) { 242 $optionsCleaned[$syntaxKey] = '#' . $matches[0]; 243 } else { 244 $optionsCleaned[$syntaxKey] = $optionValue; 245 } 246 } else { 247 $optionsCleaned[$syntaxKey] = $optionValue; 248 } 249 break; 250 case 'url': 251 $optionsCleaned[$syntaxKey] = $this->buildLink($optionValue); 252 break; 253 case 'media': 254 $optionsCleaned[$syntaxKey] = $this->buildMediaLink($optionValue); 255 break; 256 case 'choice': 257 if (array_key_exists('data', $options[$syntaxKey])) { 258 foreach ($options[$syntaxKey]['data'] as $choiceKey => $choiceValue) { 259 if (strcasecmp($optionValue, $choiceKey) == 0) { 260 $optionsCleaned[$syntaxKey] = $choiceKey; 261 break; 262 } 263 264 if (is_array($choiceValue)) { 265 foreach ($choiceValue as $choiceItem) { 266 if (strcasecmp($optionValue, $choiceItem) == 0) { 267 $optionsCleaned[$syntaxKey] = $choiceKey; 268 break 2; 269 } 270 } 271 } else { 272 if (strcasecmp($optionValue, $choiceValue) == 0) { 273 $optionsCleaned[$syntaxKey] = $choiceValue; 274 break; 275 } 276 } 277 } 278 } 279 break; 280 case 'set': 281 if (array_key_exists('option', $options[$syntaxKey]) && array_key_exists('data', $options[$syntaxKey])) { 282 $optionsCleaned[$options[$syntaxKey]['option']] = $options[$syntaxKey]['data']; 283 } 284 break; 285 } 286 } 287 288 break; 289 } 290 } 291 } 292 293 294 foreach ($data as $optionKey => $optionValue) { 295 if (!array_key_exists($optionKey, $optionsCleaned)) { 296 foreach ($options as $syntaxKey => $syntaxValue) { 297 if (array_key_exists('type', $options[$syntaxKey])) { 298 if (array_key_exists('data', $options[$syntaxKey]) && is_array($options[$syntaxKey]['data'])) { 299 foreach ($options[$syntaxKey]['data'] as $choiceKey => $choiceValue) { 300 if (is_array($choiceValue)) { 301 if (in_array($optionKey, $choiceValue)) { 302 $optionsCleaned[$syntaxKey] = $choiceKey; 303 } 304 } else { 305 if (strcasecmp($choiceValue, $optionKey) == 0) { 306 $optionsCleaned[$syntaxKey] = $choiceValue; 307 } 308 } 309 } 310 } 311 } 312 } 313 } 314 } 315 316 // Add in syntax options that are missing 317 foreach ($options as $optionKey => $optionValue) { 318 if (!array_key_exists($optionKey, $optionsCleaned)) { 319 if (array_key_exists('default', $options[$optionKey])) { 320 switch ($options[$optionKey]['type']) { 321 case 'boolean': 322 $optionsCleaned[$optionKey] = filter_var($options[$optionKey]['default'], FILTER_VALIDATE_BOOLEAN); 323 break; 324 case 'number': 325 $optionsCleaned[$optionKey] = filter_var($options[$optionKey]['default'], FILTER_VALIDATE_INT); 326 break; 327 default: 328 $optionsCleaned[$optionKey] = $options[$optionKey]['default']; 329 break; 330 } 331 } 332 } 333 } 334 335 return $optionsCleaned; 336 } 337 338 /* Lexer renderers */ 339 protected function render_lexer_enter(Doku_Renderer $renderer, $data) 340 { 341 } 342 protected function render_lexer_unmatched(Doku_Renderer $renderer, $data) 343 { 344 $renderer->doc .= $renderer->_xmlEntities($data); 345 } 346 protected function render_lexer_exit(Doku_Renderer $renderer, $data) 347 { 348 } 349 protected function render_lexer_special(Doku_Renderer $renderer, $data) 350 { 351 } 352 protected function render_lexer_match(Doku_Renderer $renderer, $data) 353 { 354 } 355 356 /* Renderer */ 357 public function render($mode, Doku_Renderer $renderer, $data) 358 { 359 if ($mode == 'xhtml') { 360 list($state, $match) = $data; 361 362 switch ($state) { 363 case DOKU_LEXER_ENTER: 364 $this->render_lexer_enter($renderer, $match); 365 return true; 366 367 case DOKU_LEXER_UNMATCHED: 368 $this->render_lexer_unmatched($renderer, $match); 369 return true; 370 371 case DOKU_LEXER_MATCHED: 372 $this->render_lexer_match($renderer, $match); 373 return true; 374 375 case DOKU_LEXER_EXIT: 376 $this->render_lexer_exit($renderer, $match); 377 return true; 378 379 case DOKU_LEXER_SPECIAL: 380 $this->render_lexer_special($renderer, $match); 381 return true; 382 } 383 384 return true; 385 } 386 387 return false; 388 } 389 390 /* 391 * return a class list with mikiop- prefix 392 * 393 * @param $options options of syntax element. Options with key 'class'=true are automatically added 394 * @param $classes classes to build from options as array 395 * @param $inclAttr include class="" in the return string 396 * @param $optionsTemplate allow a different options template instead of $this->options (for findTags) 397 * @return a string of classes from options/classes variable 398 */ 399 public function buildClass($options = null, $classes = null, $inclAttr = false, $optionsTemplate = null) 400 { 401 $s = array(); 402 403 if (is_array($options)) { 404 if ($classes == null) { $classes = array(); 405 } 406 if ($optionsTemplate == null) { $optionsTemplate = $this->options; 407 } 408 409 foreach ($optionsTemplate as $key => $value) { 410 if (array_key_exists('class', $value) && $value['class'] == true) { 411 array_push($classes, $key); 412 } 413 } 414 415 foreach ($classes as $class) { 416 if (array_key_exists($class, $options) && $options[$class] !== false && $options[$class] != '') { 417 $prefix = $this->classPrefix; 418 419 if (array_key_exists($class, $optionsTemplate) && array_key_exists('prefix', $optionsTemplate[$class])) { 420 $prefix .= $optionsTemplate[$class]['prefix']; 421 } 422 423 if (array_key_exists($class, $optionsTemplate) && array_key_exists('classNoSuffix', $optionsTemplate[$class]) && $optionsTemplate[$class]['classNoSuffix'] == true) { 424 $s[] = $prefix . $class; 425 } else { 426 $s[] = $prefix . $class . ($options[$class] !== true ? '-' . $options[$class] : ''); 427 } 428 } 429 } 430 } 431 432 $s = implode(' ', $s); 433 if ($s != '') { $s = ' ' . $s; 434 } 435 436 if ($inclAttr) { $s = ' classes="' . $s . '"'; 437 } 438 439 return $s; 440 } 441 442 443 444 445 /* 446 * build style string 447 * 448 * @param $list style list as key => value. Empty values are not included 449 * @param $inclAttr include style="" in the return string 450 * @return style list string 451 */ 452 public function buildStyle($list, $inclAttr = false) 453 { 454 $s = ''; 455 456 if (is_array($list) && count($list) > 0) { 457 foreach ($list as $key => $value) { 458 if ($value != '') { 459 $s .= $key . ':' . $value . ';'; 460 } 461 } 462 } 463 464 if ($s != '' && $inclAttr) { 465 $s = ' style="' . $s . '"'; 466 } 467 468 return $s; 469 } 470 471 472 public function buildTooltipString($options) 473 { 474 $dataPlacement = 'top'; 475 $dataHtml = false; 476 $title = ''; 477 478 if ($options != null) { 479 if (array_key_exists('tooltip-html-top', $options) && $options['tooltip-html-top'] != '') { 480 $title = $options['tooltip-html-top']; 481 $dataPlacement = 'top'; 482 } 483 484 if (array_key_exists('tooltip-html-left', $options) && $options['tooltip-html-left'] != '') { 485 $title = $options['tooltip-html-left']; 486 $dataPlacement = 'left'; 487 } 488 489 if (array_key_exists('tooltip-html-bottom', $options) && $options['tooltip-html-bottom'] != '') { 490 $title = $options['tooltip-html-bottom']; 491 $dataPlacement = 'bottom'; 492 } 493 494 if (array_key_exists('tooltip-html-right', $options) && $options['tooltip-html-right'] != '') { 495 $title = $options['tooltip-html-right']; 496 $dataPlacement = 'right'; 497 } 498 499 if (array_key_exists('tooltip-top', $options) && $options['tooltip-top'] != '') { 500 $title = $options['tooltip-top']; 501 $dataPlacement = 'top'; 502 } 503 504 if (array_key_exists('tooltip-left', $options) && $options['tooltip-left'] != '') { 505 $title = $options['tooltip-left']; 506 $dataPlacement = 'left'; 507 } 508 509 if (array_key_exists('tooltip-bottom', $options) && $options['tooltip-bottom'] != '') { 510 $title = $options['tooltip-bottom']; 511 $dataPlacement = 'bottom'; 512 } 513 514 if (array_key_exists('tooltip-right', $options) && $options['tooltip-right'] != '') { 515 $title = $options['tooltip-right']; 516 $dataPlacement = 'right'; 517 } 518 519 if (array_key_exists('tooltip-html', $options) && $options['tooltip-html'] != '') { 520 $title = $options['tooltip-html']; 521 $dataPlacement = 'top'; 522 } 523 524 if (array_key_exists('tooltip', $options) && $options['tooltip'] != '') { 525 $title = $options['tooltip']; 526 $dataPlacement = 'top'; 527 } 528 } 529 530 if ($title != '') { 531 return ' data-toggle="tooltip" data-placement="' . $dataPlacement . '" ' . ($dataHtml == true ? 'data-html="true" ' : '') . 'title="' . $title . '" '; 532 } 533 534 return ''; 535 } 536 537 /* 538 * convert the URL to a DokuWiki media link (if required) 539 * 540 * @param $url url to parse 541 * @return url string 542 */ 543 public function buildMediaLink($url) 544 { 545 $i = strpos($url, '?'); 546 if ($i !== false) { $url = substr($url, 0, $i); 547 } 548 549 $url = preg_replace('/[^\da-zA-Z:_.-]+/', '', $url); 550 551 return (tpl_getMediaFile(array($url), false)); 552 } 553 554 555 /* 556 * returns either a url or dokuwiki link 557 * 558 * @param $url link to build from 559 * @return built link 560 */ 561 public function buildLink($url) 562 { 563 $i = strpos($url, '://'); 564 if ($i !== false || substr($url, 0, 1) == '#') { return $url; 565 } 566 567 return wl($url); 568 } 569 570 /* 571 * Call syntax renderer of mikio syntax plugin 572 * 573 * @param $renderer DokuWiki renderer object 574 * @param $className mikio syntax class to call 575 * @param $text unmatched text to pass outside of lexer. Only used when $lexer=MIKIO_LEXER_AUTO 576 * @param $data tag options to pass to syntax class. Runs through cleanOptions to validate first 577 * @param $lexer which lexer to call 578 */ 579 public function syntaxRender(Doku_Renderer $renderer, $className, $text, $data = null, $lexer = MIKIO_LEXER_AUTO) 580 { 581 $className = 'syntax_plugin_mikioplugin_' . str_replace('-', '', $className); 582 583 if (class_exists($className)) { 584 $class = new $className; 585 586 if (!is_array($data)) { $data = array(); 587 } 588 589 590 if (count($class->options) > 0) { 591 $data = $class->cleanOptions($data, $class->options); 592 } 593 594 switch ($lexer) { 595 case MIKIO_LEXER_AUTO: 596 if ($class->hasEndTag) { 597 if (method_exists($class, 'render_lexer_enter')) { $class->render_lexer_enter($renderer, $data); 598 } 599 $renderer->doc .= $text; 600 if (method_exists($class, 'render_lexer_exit')) { $class->render_lexer_exit($renderer, $data); 601 } 602 } else { 603 if (method_exists($class, 'render_lexer_special')) { $class->render_lexer_special($renderer, $data); 604 } 605 } 606 607 break; 608 case MIKIO_LEXER_ENTER: 609 if (method_exists($class, 'render_lexer_enter')) { $class->render_lexer_enter($renderer, $data); 610 } 611 break; 612 case MIKIO_LEXER_EXIT: 613 if (method_exists($class, 'render_lexer_exit')) { $class->render_lexer_exit($renderer, $data); 614 } 615 break; 616 case MIKIO_LEXER_SPECIAL: 617 if (method_exists($class, 'render_lexer_special')) { $class->render_lexer_special($renderer, $data); 618 } 619 break; 620 } 621 } 622 } 623 624 625 protected function callMikioTag($className, $data) 626 { 627 // $className = 'syntax_plugin_mikioplugin_'.$className; 628 629 630 // if(class_exists($className)) { 631 //$class = new $className; 632 if (!plugin_isdisabled('mikioplugin')) { 633 $class = plugin_load('syntax', 'mikioplugin_' . $className); 634 // echo '^^'.$className.'^^'; 635 636 637 if (method_exists($class, 'mikioCall')) { return $class->mikioCall($data); 638 } 639 } 640 641 // } 642 643 return ''; 644 } 645 646 647 protected function callMikioOptionDefault($className, $option) 648 { 649 $className = 'syntax_plugin_mikioplugin_' . $className; 650 651 if (class_exists($className)) { 652 $class = new $className; 653 654 if (array_key_exists($option, $class->options) && array_key_exists('default', $class->options[$option])) { 655 return $class->options[$option]['default']; 656 } 657 } 658 659 return ''; 660 } 661 662 663 protected function buildTooltip($text) 664 { 665 if ($text != '') { 666 return ' data-tooltip="' . $text . '"'; 667 } 668 669 return ''; 670 } 671 672 /* 673 * Create array with passed elements and include them if their values are not empty 674 * 675 * @param ... array items 676 */ 677 protected function arrayRemoveEmpties($items) 678 { 679 $result = array(); 680 681 foreach ($items as $key => $value) { 682 if ($value != '') { 683 $result[$key] = $value; 684 } 685 } 686 687 return $result; 688 } 689 690 public function getFirstArrayKey($data) 691 { 692 if (!function_exists('array_key_first')) { 693 foreach ($data as $key => $unused) { 694 return $key; 695 } 696 } 697 698 return array_key_first($data); 699 } 700 701 702 /* 703 * add common options to options 704 * 705 * @param $typelist common option to add 706 * @param $options save in options 707 */ 708 public function addCommonOptions($typelist) 709 { 710 $types = explode(' ', $typelist); 711 foreach ($types as $type) { 712 if (strcasecmp($type, 'shadow') == 0) { 713 $this->options['shadow'] = array( 714 'type' => 'choice', 715 'data' => array('large' => array('shadow-large', 'shadow-lg'), 'small' => array('shadow-small', 'shadow-sm'), true), 716 'default' => '', 717 'class' => true 718 ); 719 } 720 721 if (strcasecmp($type, 'width') == 0) { 722 $this->options['width'] = array( 723 'type' => 'size', 724 'default' => '' 725 ); 726 } 727 728 if (strcasecmp($type, 'height') == 0) { 729 $this->options['height'] = array( 730 'type' => 'size', 731 'default' => '' 732 ); 733 } 734 735 if (strcasecmp($type, 'text-color') == 0) { 736 $this->options['text-color'] = array( 737 'type' => 'color', 738 'default' => '' 739 ); 740 } 741 742 if (strcasecmp($type, 'type') == 0) { 743 $this->options['type'] = array( 744 'type' => 'text', 745 'data' => array('primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'outline-primary', 'outline-secondary', 'outline-success', 'outline-danger', 'outline-warning', 'outline-info', 'outline-light', 'outline-dark'), 746 'default' => '', 747 'class' => true 748 ); 749 } 750 751 if (strcasecmp($type, 'text-align') == 0) { 752 $this->options['text-align'] = array( 753 'type' => 'choice', 754 'data' => array('left' => array('text-left'), 'center' => array('text-center'), 'right' => array('text-right')), 755 'default' => '', 756 'class' => true 757 ); 758 } 759 760 if (strcasecmp($type, 'align') == 0) { 761 $this->options['align'] = array( 762 'type' => 'choice', 763 'data' => array('left' => array('align-left'), 'center' => array('align-center'), 'right' => array('align-right')), 764 'default' => '', 765 'class' => true 766 ); 767 } 768 769 if (strcasecmp($type, 'tooltip') == 0) { 770 $this->options['tooltip'] = array( 771 'type' => 'text', 772 'default' => '', 773 'class' => true, 774 'classNoSuffix' => true 775 ); 776 } 777 778 if (strcasecmp($type, 'vertical-align') == 0) { 779 $this->options['vertical-align'] = array( 780 'type' => 'choice', 781 'data' => array('top' => array('align-top'), 'middle' => array('align-middle'), 'bottom' => array('align-bottom')), 782 'default' => '', 783 'class' => true 784 ); 785 } 786 787 if (strcasecmp($type, 'links-match') == 0) { 788 $this->options['links-match'] = array( 789 'type' => 'boolean', 790 'default' => 'false', 791 'class' => true 792 ); 793 } 794 } 795 } 796 797 798 /* 799 * Find HTML tags in string. Parse tags options. Used in parsing subtags 800 * 801 * @param $tagName tagName to search for. Name is exclusive 802 * @param $content search within content 803 * @param $options parse options similar to syntax element options 804 * @param $hasEndTag tagName search also looks for an end tag 805 * @return array of tags containing 'options' => array of 'name' => 'value', 'content' => content inside the tag 806 */ 807 protected function findTags($tagName, $content, $options, $hasEndTag = true) 808 { 809 $items = array(); 810 $search = '/<(?i:' . $tagName . ')(.*?)>(.*?)<\/(?i:' . $tagName . ')>/s'; 811 812 if (!$hasEndTag) { 813 $search = '/<(?i:' . $tagName . ')(.*?)>/s'; 814 } 815 816 if (preg_match_all($search, $content, $match)) { 817 if (count($match) >= 2) { 818 for ($i = 0; $i < count($match[1]); $i++) { 819 $item = array('options' => array(), 'content' => $this->render_text($match[2][$i])); 820 821 $optionlist = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', trim($match[1][$i])); 822 823 foreach ($optionlist as $option) { 824 $j = strpos($option, '='); 825 if ($j !== false) { 826 $value = substr($option, $j + 1); 827 828 if (substr($value, 0, 1) == '"') { $value = substr($value, 1); 829 } 830 if (substr($value, -1) == '"') { $value = substr($value, 0, -1); 831 } 832 833 $item['options'][substr($option, 0, $j)] = $value; 834 } else { 835 $item['options'][$option] = true; 836 } 837 } 838 839 $item['options'] = $this->cleanOptions($item['options'], $options); 840 841 $items[] = $item; 842 } 843 } 844 } 845 846 return $items; 847 } 848} 849