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