xref: /plugin/mikioplugin/syntax/core.php (revision 4d21e14836aefa3e73fe3ba22067c4af4c1fd79a)
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            // expand text-decoration
499            if(array_key_exists('text-decoration', $list)) {
500                // Define the possible values for each property
501                $decorations = array('underline', 'overline', 'line-through');
502                $styles = array('solid', 'double', 'dotted', 'dashed', 'wavy');
503                // Split the shorthand string into parts
504                $parts = explode(' ', $list['text-decoration']);
505
506                // Initialize the variables to hold the property values
507                $decoration = '';
508                $style = '';
509                $color = '';
510                $thickness = '';
511
512                // Process each part of the shorthand string
513                foreach ($parts as $part) {
514                    if (in_array($part, $decorations)) {
515                        $decoration = $part;
516                    } elseif (in_array($part, $styles)) {
517                        $style = $part;
518                    } elseif (preg_match('/^\d+(px|em|rem|%)$/', $part)) {
519                        $thickness = $part;
520                    } elseif (preg_match('/^#[0-9a-fA-F]{6}$|^[a-zA-Z]+$/', $part)) {
521                        $color = $part;
522                    }
523                }
524
525                // Build the completed style string
526                unset($list['text-decoration']);
527                if ($decoration) $list['text-decoration'] = trim($decoration);
528                if ($style) $list['text-decoration-style'] = trim($style);
529                if ($color) $list['text-decoration-color'] = trim($color);
530                if ($thickness) $list['text-decoration-thickness'] = trim($thickness);
531            }
532
533            foreach ($list as $key => $value) {
534                if ($value != '') {
535                    $s .= $key . ':' . $value . ';';
536                }
537            }
538        }
539
540        if ($s != '' && $inclAttr) {
541            $s = ' style="' . $s . '"';
542        }
543
544        return $s;
545    }
546
547
548    public function buildTooltipString($options)
549    {
550        $dataPlacement = 'top';
551        $dataHtml = false;
552        $title = '';
553
554        if ($options != null) {
555            if (array_key_exists('tooltip-html-top', $options) && $options['tooltip-html-top'] != '') {
556                $title = $options['tooltip-html-top'];
557                $dataPlacement = 'top';
558            }
559
560            if (array_key_exists('tooltip-html-left', $options) && $options['tooltip-html-left'] != '') {
561                $title = $options['tooltip-html-left'];
562                $dataPlacement = 'left';
563            }
564
565            if (array_key_exists('tooltip-html-bottom', $options) && $options['tooltip-html-bottom'] != '') {
566                $title = $options['tooltip-html-bottom'];
567                $dataPlacement = 'bottom';
568            }
569
570            if (array_key_exists('tooltip-html-right', $options) && $options['tooltip-html-right'] != '') {
571                $title = $options['tooltip-html-right'];
572                $dataPlacement = 'right';
573            }
574
575            if (array_key_exists('tooltip-top', $options) && $options['tooltip-top'] != '') {
576                $title = $options['tooltip-top'];
577                $dataPlacement = 'top';
578            }
579
580            if (array_key_exists('tooltip-left', $options) && $options['tooltip-left'] != '') {
581                $title = $options['tooltip-left'];
582                $dataPlacement = 'left';
583            }
584
585            if (array_key_exists('tooltip-bottom', $options) && $options['tooltip-bottom'] != '') {
586                $title = $options['tooltip-bottom'];
587                $dataPlacement = 'bottom';
588            }
589
590            if (array_key_exists('tooltip-right', $options) && $options['tooltip-right'] != '') {
591                $title = $options['tooltip-right'];
592                $dataPlacement = 'right';
593            }
594
595            if (array_key_exists('tooltip-html', $options) && $options['tooltip-html'] != '') {
596                $title = $options['tooltip-html'];
597                $dataPlacement = 'top';
598            }
599
600            if (array_key_exists('tooltip', $options) && $options['tooltip'] != '') {
601                $title = $options['tooltip'];
602                $dataPlacement = 'top';
603            }
604        }
605
606        if ($title != '') {
607            return ' data-toggle="tooltip" data-placement="' . $dataPlacement . '" ' . ($dataHtml == true ? 'data-html="true" ' : '') . 'title="' . $title . '" ';
608        }
609
610        return '';
611    }
612
613    /*
614    * convert the URL to a DokuWiki media link (if required)
615    *
616    * @param $url   url to parse
617    * @return       url string
618    */
619    public function buildMediaLink($url)
620    {
621        $i = strpos($url, '?');
622        if ($i !== false) { $url = substr($url, 0, $i);
623        }
624
625        $url = preg_replace('/[^\da-zA-Z:_.-]+/', '', $url);
626
627        return (tpl_getMediaFile(array($url), false));
628    }
629
630
631    /*
632    * returns either a url or dokuwiki link
633    *
634    * @param    $url    link to build from
635    * @return           built link
636    */
637    public function buildLink($url)
638    {
639        $i = strpos($url, '://');
640        if ($i !== false || substr($url, 0, 1) == '#') { return $url;
641        }
642
643        return wl($url);
644    }
645
646    /*
647    * Call syntax renderer of mikio syntax plugin
648    *
649    * @param $renderer          DokuWiki renderer object
650    * @param $className         mikio syntax class to call
651    * @param $text              unmatched text to pass outside of lexer. Only used when $lexer=MIKIO_LEXER_AUTO
652    * @param $data              tag options to pass to syntax class. Runs through cleanOptions to validate first
653    * @param $lexer             which lexer to call
654    */
655    public function syntaxRender(Doku_Renderer $renderer, $className, $text, $data = null, $lexer = MIKIO_LEXER_AUTO)
656    {
657        $className = 'syntax_plugin_mikioplugin_' . str_replace('-', '', $className);
658
659        if (class_exists($className)) {
660            $class = new $className;
661
662            if (!is_array($data)) { $data = array();
663            }
664
665
666            if (count($class->options) > 0) {
667                $data = $class->cleanOptions($data, $class->options);
668            }
669
670            switch ($lexer) {
671            case MIKIO_LEXER_AUTO:
672                if ($class->hasEndTag) {
673                    if (method_exists($class, 'render_lexer_enter')) { $class->render_lexer_enter($renderer, $data);
674                    }
675                    $renderer->doc .= $text;
676                    if (method_exists($class, 'render_lexer_exit')) { $class->render_lexer_exit($renderer, $data);
677                    }
678                } else {
679                    if (method_exists($class, 'render_lexer_special')) { $class->render_lexer_special($renderer, $data);
680                    }
681                }
682
683                break;
684            case MIKIO_LEXER_ENTER:
685                if (method_exists($class, 'render_lexer_enter')) { $class->render_lexer_enter($renderer, $data);
686                }
687                break;
688            case MIKIO_LEXER_EXIT:
689                if (method_exists($class, 'render_lexer_exit')) { $class->render_lexer_exit($renderer, $data);
690                }
691                break;
692            case MIKIO_LEXER_SPECIAL:
693                if (method_exists($class, 'render_lexer_special')) { $class->render_lexer_special($renderer, $data);
694                }
695                break;
696            }
697        }
698    }
699
700
701    protected function callMikioTag($className, $data)
702    {
703        // $className = 'syntax_plugin_mikioplugin_'.$className;
704
705
706        // if(class_exists($className)) {
707        //$class = new $className;
708        if (!plugin_isdisabled('mikioplugin')) {
709            $class = plugin_load('syntax', 'mikioplugin_' . $className);
710            // echo '^^'.$className.'^^';
711
712
713            if (method_exists($class, 'mikioCall')) { return $class->mikioCall($data);
714            }
715        }
716
717        // }
718
719        return '';
720    }
721
722
723    protected function callMikioOptionDefault($className, $option)
724    {
725        $className = 'syntax_plugin_mikioplugin_' . $className;
726
727        if (class_exists($className)) {
728            $class = new $className;
729
730            if (array_key_exists($option, $class->options) && array_key_exists('default', $class->options[$option])) {
731                return $class->options[$option]['default'];
732            }
733        }
734
735        return '';
736    }
737
738
739    protected function buildTooltip($text)
740    {
741        if ($text != '') {
742            return ' data-tooltip="' . $text . '"';
743        }
744
745        return '';
746    }
747
748    /*
749    * Create array with passed elements and include them if their values are not empty
750    *
751    * @param ...        array items
752    */
753    protected function arrayRemoveEmpties($items)
754    {
755        $result = array();
756
757        foreach ($items as $key => $value) {
758            if ($value != '') {
759                $result[$key] = $value;
760            }
761        }
762
763        return $result;
764    }
765
766    public function getFirstArrayKey($data)
767    {
768        if (!function_exists('array_key_first')) {
769            foreach ($data as $key => $unused) {
770                return $key;
771            }
772        }
773
774        return array_key_first($data);
775    }
776
777
778    /*
779    * add common options to options
780    *
781    * @param $typelist      common option to add
782    * @param $options   save in options
783    */
784    public function addCommonOptions($typelist)
785    {
786        $types = explode(' ', $typelist);
787        foreach ($types as $type) {
788            if (strcasecmp($type, 'shadow') == 0) {
789                $this->options['shadow'] =          array(
790                    'type'     => 'choice',
791                    'data'     => array('large' => array('shadow-large', 'shadow-lg'), 'small' => array('shadow-small', 'shadow-sm'), true),
792                    'default'  => '',
793                    'class'    => true
794                );
795            }
796
797            if (strcasecmp($type, 'width') == 0) {
798                $this->options['width'] =           array(
799                    'type'     => 'size',
800                    'default'  => ''
801                );
802            }
803
804            if (strcasecmp($type, 'height') == 0) {
805                $this->options['height'] =          array(
806                    'type'     => 'size',
807                    'default'  => ''
808                );
809            }
810
811            if (strcasecmp($type, 'text-color') == 0) {
812                $this->options['text-color'] =          array(
813                    'type'      => 'color',
814                    'default'  => ''
815                );
816            }
817
818            if (strcasecmp($type, 'type') == 0) {
819                $this->options['type'] =              array(
820                    'type'     => 'text',
821                    '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'),
822                    'default'  => '',
823                    'class'    => true
824                );
825            }
826
827            if (strcasecmp($type, 'text-align') == 0) {
828                $this->options['text-align'] =      array(
829                    'type'     => 'choice',
830                    'data'     => array('left' => array('text-left'), 'center' => array('text-center'), 'right' => array('text-right')),
831                    'default'  => '',
832                    'class'    => true
833                );
834            }
835
836            if (strcasecmp($type, 'align') == 0) {
837                $this->options['align'] =           array(
838                    'type'     => 'choice',
839                    'data'     => array('left' => array('align-left'), 'center' => array('align-center'), 'right' => array('align-right')),
840                    'default'  => '',
841                    'class'    => true
842                );
843            }
844
845            if (strcasecmp($type, 'tooltip') == 0) {
846                $this->options['tooltip'] =         array(
847                    'type'     => 'text',
848                    'default'  => '',
849                    'class'    => true,
850                    'classNoSuffix'   => true
851                );
852            }
853
854            if (strcasecmp($type, 'vertical-align') == 0) {
855                $this->options['vertical-align'] =  array(
856                    'type'    => 'choice',
857                    'data'    => array('top' => array('align-top'), 'middle' => array('align-middle'), 'bottom' => array('align-bottom')),
858                    'default' => '',
859                    'class'   => true
860                );
861            }
862
863            if (strcasecmp($type, 'links-match') == 0) {
864                $this->options['links-match'] =     array(
865                    'type'    => 'boolean',
866                    'default' => 'false',
867                    'class'   => true
868                );
869            }
870        }
871    }
872
873
874    /*
875    * Find HTML tags in string. Parse tags options. Used in parsing subtags
876    *
877    * @param $tagName       tagName to search for. Name is exclusive
878    * @param $content       search within content
879    * @param $options       parse options similar to syntax element options
880    * @param $hasEndTag     tagName search also looks for an end tag
881    * @return               array of tags containing 'options' => array of 'name' => 'value', 'content' => content inside the tag
882    */
883    protected function findTags($tagName, $content, $options, $hasEndTag = true)
884    {
885        $items = array();
886        $search = '/<(?i:' . $tagName . ')(.*?)>(.*?)<\/(?i:' . $tagName . ')>/s';
887
888        if (!$hasEndTag) {
889            $search = '/<(?i:' . $tagName . ')(.*?)>/s';
890        }
891
892        if (preg_match_all($search, $content, $match)) {
893            if (count($match) >= 2) {
894                for ($i = 0; $i < count($match[1]); $i++) {
895                    $item = array('options' => array(), 'content' => $this->render_text($match[2][$i]));
896
897                    $optionlist = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', trim($match[1][$i]));
898
899                    foreach ($optionlist as $option) {
900                        $j = strpos($option, '=');
901                        if ($j !== false) {
902                            $value = substr($option, $j + 1);
903
904                            if (substr($value, 0, 1) == '"') { $value = substr($value, 1);
905                            }
906                            if (substr($value, -1) == '"') { $value = substr($value, 0, -1);
907                            }
908
909                            $item['options'][substr($option, 0, $j)] = $value;
910                        } else {
911                            $item['options'][$option] = true;
912                        }
913                    }
914
915                    $item['options'] = $this->cleanOptions($item['options'], $options);
916
917                    $items[] = $item;
918                }
919            }
920        }
921
922        return $items;
923    }
924
925    /*
926    * Check if a custom style exists in styles.less
927    *
928    * @param $name          The style name to search foe
929    * @return               true if the style name exists
930    */
931    protected function customStyleExists($name)
932    {
933        $stylePath = __DIR__.'/../styles/styles.less';
934
935        if(file_exists($stylePath)) {
936            $styleData = file_get_contents($stylePath);
937            $searchString = '._mikiop-custom-type('.$name.');';
938
939            return (strpos($styleData, $searchString) !== false);
940        }
941
942        return false;
943    }
944}
945