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