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