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