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