xref: /plugin/mikioplugin/syntax/core.php (revision 2625d0cacd0f159fa4fb7c9d691615015291f7fd)
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