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