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