1<?php
2/**
3 * Helper Component for the Wrap Plugin
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Anika Henke <anika@selfthinker.org>
7 */
8
9class helper_plugin_wrap extends DokuWiki_Plugin {
10    static protected $boxes = array ('wrap_box', 'wrap_danger', 'wrap_warning', 'wrap_caution', 'wrap_notice', 'wrap_safety',
11                                     'wrap_info', 'wrap_important', 'wrap_alert', 'wrap_tip', 'wrap_help', 'wrap_todo',
12                                     'wrap_download', 'wrap_hi', 'wrap_spoiler');
13    static protected $paragraphs = array ('wrap_leftalign', 'wrap_rightalign', 'wrap_centeralign', 'wrap_justify');
14
15	/* list of languages which normally use RTL scripts */
16	static protected $rtllangs = array('ar','dv','fa','ha','he','ks','ku','ps','ur','yi','arc');
17	/* list of right-to-left scripts (may override the language defaults to rtl) */
18	static protected $rtlscripts = array('arab','thaa','hebr','deva','shrd');
19	/* selection of left-to-right scripts (may override the language defaults to ltr) */
20	static protected $ltrscripts = array('latn','cyrl','grek','hebr','cyrs','armn');
21
22    static $box_left_pos = 0;
23    static $box_right_pos = 0;
24    static $box_first = true;
25    static $table_entr = 0;
26
27    protected $column_count = 0;
28
29    /**
30     * get attributes (pull apart the string between '<wrap' and '>')
31     *  and identify classes, width, lang and dir
32     *
33     * @author Anika Henke <anika@selfthinker.org>
34     * @author Christopher Smith <chris@jalakai.co.uk>
35     *   (parts taken from http://www.dokuwiki.org/plugin:box)
36     */
37    function getAttributes($data, $useNoPrefix=true) {
38
39        $attr = array(
40            'lang' => null,
41            'class' => null,
42            'width' => null,
43            'id' => null,
44            'dir' => null
45        );
46        $tokens = preg_split('/\s+/', $data, 9);
47
48        // anonymous function to convert inclusive comma separated items to regex pattern
49        $pattern = function ($csv) {
50            return '/^(?:'. str_replace(['?','*',' ',','],
51                                        ['.','.*','','|'], $csv) .')$/';
52        };
53
54        // noPrefix: comma separated class names that should be excluded from
55        //   being prefixed with "wrap_",
56        //   each item may contain wildcard (*, ?)
57        $noPrefix = ($this->getConf('noPrefix') && $useNoPrefix) ? $pattern($this->getConf('noPrefix')) : '';
58
59        // restrictedClasses : comma separated class names that should be checked
60        //   based on restriction type (whitelist or blacklist),
61        //   each item may contain wildcard (*, ?)
62        $restrictedClasses = ($this->getConf('restrictedClasses')) ?
63                            $pattern($this->getConf('restrictedClasses')) : '';
64        $restrictionType = $this->getConf('restrictionType');
65
66        foreach ($tokens as $token) {
67
68            //get width
69            if (preg_match('/^\d*\.?\d+(%|px|em|rem|ex|ch|vw|vh|pt|pc|cm|mm|in)$/', $token)) {
70                $attr['width'] = $token;
71                continue;
72            }
73
74            //get lang
75            if (preg_match('/:([a-z\-]+)/', $token)) {
76                $attr['lang'] = trim($token,':');
77                continue;
78            }
79
80            //get id
81            if (preg_match('/#([A-Za-z0-9_-]+)/', $token)) {
82                $attr['id'] = trim($token,'#');
83                continue;
84            }
85
86            //get classes
87            //restrict token (class names) characters to prevent any malicious data
88            if (preg_match('/[^A-Za-z0-9_-]/',$token)) continue;
89            if ($restrictedClasses) {
90                $classIsInList = preg_match($restrictedClasses, $token);
91                // either allow only certain classes or disallow certain classes
92                if ($restrictionType xor $classIsInList) continue;
93            }
94            // prefix adjustment of class name
95            $prefix = (!empty($noPrefix) && preg_match($noPrefix, $token)) ? '' : 'wrap_';
96            $attr['class'] = (isset($attr['class']) ? $attr['class'].' ' : '').$prefix.$token;
97        }
98        if ($this->getConf('darkTpl')) {
99            $attr['class'] = (isset($attr['class']) ? $attr['class'].' ' : '').'wrap__dark';
100        }
101        if ($this->getConf('emulatedHeadings')) {
102            $attr['class'] = (isset($attr['class']) ? $attr['class'].' ' : '').'wrap__emuhead';
103        }
104
105        /* improved RTL detection to make sure it covers more cases: */
106		if($attr['lang'] && $attr['lang'] !== '') {
107
108			// turn the language code into an array of components:
109			$arr = explode('-', $attr['lang']);
110
111			// is the language iso code (first field) in the list of RTL languages?
112			$rtl = in_array($arr[0], self::$rtllangs);
113
114			// is there a Script specified somewhere which overrides the text direction?
115			$rtl = ($rtl xor (bool) array_intersect( $rtl ? self::$ltrscripts : self::$rtlscripts, $arr));
116
117			$attr['dir'] = ( $rtl ? 'rtl' : 'ltr' );
118		}
119
120        return $attr;
121    }
122
123    /**
124     * build attributes (write out classes, width, lang and dir)
125     */
126    function buildAttributes($data, $addClass='', $mode='xhtml') {
127
128        $attr = $this->getAttributes($data);
129        $out = '';
130
131        if ($mode=='xhtml') {
132            if($attr['class']) $out .= ' class="'.hsc($attr['class']).' '.$addClass.'"';
133            // if used in other plugins, they might want to add their own class(es)
134            elseif($addClass)  $out .= ' class="'.$addClass.'"';
135            if($attr['id'])    $out .= ' id="'.hsc($attr['id']).'"';
136            // width on spans normally doesn't make much sense, but in the case of floating elements it could be used
137            if($attr['width']) {
138                if (strpos($attr['width'],'%') !== false) {
139                    $out .= ' style="width: '.hsc($attr['width']).';"';
140                } else {
141                    // anything but % should be 100% when the screen gets smaller
142                    $out .= ' style="width: '.hsc($attr['width']).'; max-width: 100%;"';
143                }
144            }
145            // only write lang if it's a language in lang2dir.conf
146            if($attr['dir'])   $out .= ' lang="'.$attr['lang'].'" xml:lang="'.$attr['lang'].'" dir="'.$attr['dir'].'"';
147        }
148
149        return $out;
150    }
151
152    /**
153     * render ODT element, Open
154     * (get Attributes, select ODT element that fits, render it, return element name)
155     */
156    function renderODTElementOpen($renderer, $HTMLelement, $data) {
157        $attr = $this->getAttributes($data, false);
158        $attr_string = $this->buildAttributes($data);
159        $classes = explode (' ', $attr['class']);
160
161        // Get language
162        $language = $attr['lang'];
163
164        $is_indent    = in_array ('wrap_indent', $classes);
165        $is_outdent   = in_array ('wrap_outdent', $classes);
166        $is_column    = in_array ('wrap_column', $classes);
167        $is_group     = in_array ('wrap_group', $classes);
168        $is_pagebreak = in_array ('wrap_pagebreak', $classes);
169
170        // Check for multicolumns
171        $columns = 0;
172        preg_match ('/wrap_col\d/', $attr ['class'], $matches);
173        if ( empty ($matches [0]) === false ) {
174            $columns = $matches [0] [strlen($matches [0])-1];
175        }
176
177        // Check for boxes
178        $is_box = false;
179        foreach (self::$boxes as $box) {
180            if ( strpos ($attr ['class'], $box) !== false ) {
181                $is_box = true;
182                break;
183            }
184        }
185
186        // Check for paragraphs
187        $is_paragraph = false;
188        if ( empty($language) === false ) {
189            $is_paragraph = true;
190        } else {
191            foreach (self::$paragraphs as $paragraph) {
192                if ( strpos ($attr ['class'], $paragraph) !== false ) {
193                    $is_paragraph = true;
194                    break;
195                }
196            }
197        }
198
199        $style = null;
200        if ( empty($attr['width']) === false ) {
201            $style = 'width: '.$attr['width'].';';
202        }
203        $attr ['class'] = 'dokuwiki '.$attr ['class'];
204
205        // Call corresponding functions for current wrap class
206        if ( $HTMLelement == 'span' ) {
207            if ( $is_indent === false && $is_outdent === false ) {
208                $this->renderODTOpenSpan ($renderer, $attr ['class'], $style, $language, $attr_string);
209                return 'span';
210            } else {
211                $this->renderODTOpenParagraph ($renderer, $attr ['class'], $style, $attr ['dir'], $language, $is_indent, $is_outdent, true, $attr_string);
212                return 'paragraph';
213            }
214        } else if ( $HTMLelement == 'div' ) {
215            if ( $is_box === true ) {
216                $wrap = $this->loadHelper('wrap');
217                $fullattr = $wrap->buildAttributes($data, 'plugin_wrap');
218
219                if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
220                    $this->renderODTOpenBox ($renderer, $attr ['class'], $style, $fullattr);
221                } else {
222                    $this->renderODTOpenTable ($renderer, $attr, $style,  $attr_string);
223                }
224                return 'box';
225            } else if ( $columns > 0 ) {
226                $this->renderODTOpenColumns ($renderer, $attr ['class'], $style);
227                return 'multicolumn';
228            } else if ( $is_paragraph === true || $is_indent === true || $is_outdent === true ) {
229                $this->renderODTOpenParagraph ($renderer, $attr ['class'], $style, $attr ['dir'], $language, $is_indent, $is_outdent, false, $attr_string);
230                return 'paragraph';
231            } else if ( $is_pagebreak === true ) {
232                $renderer->pagebreak ();
233                // Pagebreak hasn't got a closing stack so we return/push 'other' on the stack
234                return 'other';
235            } else if ( $is_column === true ) {
236                $this->renderODTOpenColumn ($renderer, $attr ['class'], $style, $attr_string);
237                return 'column';
238            } else if ( $is_group === true ) {
239                $this->renderODTOpenGroup ($renderer, $attr ['class'], $style);
240                return 'group';
241            } else if (strpos ($attr ['class'], 'wrap_clear') !== false ) {
242                $renderer->linebreak();
243                $renderer->p_close();
244                $renderer->p_open();
245
246                self::$box_left_pos = 0;
247                self::$box_right_pos = 0;
248                self::$box_first = true;
249            }
250        }
251        return 'other';
252    }
253
254    /**
255     * render ODT element, Close
256     */
257    function renderODTElementClose($renderer, $element) {
258        switch ($element) {
259            case 'box':
260                if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
261                    $this->renderODTCloseBox ($renderer);
262                } else {
263                    $this->renderODTCloseTable ($renderer);
264                }
265            break;
266            case 'multicolumn':
267                $this->renderODTCloseColumns($renderer);
268            break;
269            case 'paragraph':
270                $this->renderODTCloseParagraph($renderer);
271            break;
272            case 'column':
273                $this->renderODTCloseColumn($renderer);
274            break;
275            case 'group':
276                $this->renderODTCloseGroup($renderer);
277            break;
278            case 'span':
279                $this->renderODTCloseSpan($renderer);
280            break;
281            // No default by intention.
282        }
283    }
284
285    function renderODTOpenBox ($renderer, $class, $style, $fullattr) {
286        $properties = array ();
287
288        if ( method_exists ($renderer, 'getODTProperties') === false ) {
289            // Function is not supported by installed ODT plugin version, return.
290            return;
291        }
292
293        // Get CSS properties for ODT export.
294        $renderer->getODTProperties ($properties, 'div', $class, $style);
295
296        if ( empty($properties ['background-image']) === false ) {
297            $properties ['background-image'] =
298                $renderer->replaceURLPrefix ($properties ['background-image'], DOKU_INC);
299        }
300
301        if ( empty($properties ['float']) === true ) {
302            // If the float property is not set, set it to 'left' becuase the ODT plugin
303            // would default to 'center' which is diffeent to the XHTML behaviour.
304            if ( strpos ($class, 'wrap_center') === false ) {
305                $properties ['float'] = 'left';
306            } else {
307                $properties ['float'] = 'center';
308            }
309        }
310
311        // The display property has differing usage in CSS. So we better overwrite it.
312        $properties ['display'] = 'always';
313        if ( stripos ($class, 'wrap_noprint') !== false ) {
314            $properties ['display'] = 'screen';
315        }
316        if ( stripos ($class, 'wrap_onlyprint') !== false ) {
317            $properties ['display'] = 'printer';
318        }
319
320        $renderer->_odtDivOpenAsFrameUseProperties ($properties);
321    }
322
323    function renderODTCloseBox ($renderer) {
324        if ( method_exists ($renderer, '_odtDivCloseAsFrame') === false ) {
325            // Function is not supported by installed ODT plugin version, return.
326            return;
327        }
328        $renderer->_odtDivCloseAsFrame ();
329    }
330
331    function renderODTOpenColumns ($renderer, $class, $style) {
332        $properties = array ();
333
334        if ( method_exists ($renderer, 'getODTProperties') === false ) {
335            // Function is not supported by installed ODT plugin version, return.
336            return;
337        }
338
339        // Get CSS properties for ODT export.
340        $renderer->getODTProperties ($properties, 'div', $class, $style);
341
342        $renderer->_odtOpenMultiColumnFrame($properties);
343    }
344
345    function renderODTCloseColumns ($renderer) {
346        if ( method_exists ($renderer, '_odtCloseMultiColumnFrame') === false ) {
347            // Function is not supported by installed ODT plugin version, return.
348            return;
349        }
350        $renderer->_odtCloseMultiColumnFrame();
351    }
352
353    function renderODTOpenParagraph ($renderer, $class, $style, $dir, $language, $is_indent, $is_outdent, $indent_first, $attr=null) {
354        $properties = array ();
355
356        if ( method_exists ($renderer, 'getODTPropertiesFromElement') === true ) {
357            // Get CSS properties for ODT export.
358            // Set parameter $inherit=false to prevent changiung the font-size and family!
359            $renderer->getODTPropertiesNew ($properties, 'p', $attr, null, false);
360        } else if ( method_exists ($renderer, 'getODTProperties') === true ) {
361            // Get CSS properties for ODT export (deprecated version).
362            $renderer->getODTProperties ($properties, 'p', $class, $style);
363
364            if ( empty($properties ['background-image']) === false ) {
365                $properties ['background-image'] =
366                    $renderer->replaceURLPrefix ($properties ['background-image'], DOKU_INC);
367            }
368        } else {
369            // To old ODT plugin version.
370            return;
371        }
372
373        if ( empty($properties ['text-align']) )
374        {
375            if ($dir == 'ltr') {
376                $properties ['text-align'] = 'left';
377                $properties ['writing-mode'] = 'lr';
378            }
379            if ($dir == 'rtl') {
380                $properties ['text-align'] = 'right';
381                $properties ['writing-mode'] = 'rl';
382            }
383        }
384
385        $name = '';
386        if ( empty($language) === false ) {
387            $properties ['lang'] = $language;
388            $name .= 'Language: '.$language;
389        }
390
391        if ( $indent_first === true ) {
392            // Eventually indent or outdent first line only...
393            if ( $is_indent === true ) {
394                // FIXME: Has to be adjusted if test direction will be supported.
395                // See all.css
396                $properties ['text-indent'] = $properties ['padding-left'];
397                $properties ['padding-left'] = 0;
398                $name .= 'Indent first';
399            }
400            if ( $is_outdent === true ) {
401                // FIXME: Has to be adjusted if text (RTL, LTR) direction will be supported.
402                // See all.css
403                $properties ['text-indent'] = $properties ['margin-left'];
404                $properties ['margin-left'] = 0;
405                $name .= 'Outdent first';
406            }
407        } else {
408            // Eventually indent or outdent the whole paragraph...
409            if ( $is_indent === true ) {
410                // FIXME: Has to be adjusted if test direction will be supported.
411                // See all.css
412                $properties ['margin-left'] = $properties ['padding-left'] ?? null;
413                $properties ['padding-left'] = 0;
414                $name .= 'Indent';
415            }
416            if ( $is_outdent === true ) {
417                // Nothing to change: keep left margin property.
418                // FIXME: Has to be adjusted if text (RTL, LTR) direction will be supported.
419                // See all.css
420                $name .= 'Outdent';
421            }
422        }
423
424        $renderer->p_close();
425        if ( method_exists ($renderer, 'createParagraphStyle') === false ) {
426            // Older ODT plugin version.
427            $renderer->_odtParagraphOpenUseProperties($properties);
428        } else {
429            // Newer version create our own common styles.
430
431            // Create parent style to group the others beneath it
432            if (!$renderer->styleExists('Plugin_Wrap_Paragraphs')) {
433                $parent_properties = array();
434                $parent_properties ['style-parent'] = null;
435                $parent_properties ['style-class'] = 'Plugin Wrap Paragraphs';
436                $parent_properties ['style-name'] = 'Plugin_Wrap_Paragraphs';
437                $parent_properties ['style-display-name'] = 'Plugin Wrap';
438                $renderer->createParagraphStyle($parent_properties);
439            }
440
441            $name .= $this->getODTCommonStyleName($class);
442            $style_name = 'Plugin_Wrap_Paragraph_'.$name;
443            if (!$renderer->styleExists($style_name)) {
444                $properties ['style-parent'] = 'Plugin_Wrap_Paragraphs';
445                $properties ['style-class'] = null;
446                $properties ['style-name'] = $style_name;
447                $properties ['style-display-name'] = $name;
448                $renderer->createParagraphStyle($properties);
449            }
450
451            $renderer->p_open($style_name);
452        }
453    }
454
455    function renderODTCloseParagraph ($renderer) {
456        if ( method_exists ($renderer, 'p_close') === false ) {
457            // Function is not supported by installed ODT plugin version, return.
458            return;
459        }
460        $renderer->p_close();
461    }
462
463    function renderODTOpenColumn ($renderer, $class, $style, $attr) {
464        $properties = array ();
465
466        if ( method_exists ($renderer, 'getODTPropertiesFromElement') === true ) {
467            // Get CSS properties for ODT export.
468            $renderer->getODTPropertiesNew ($properties, 'div', $attr);
469        } else if ( method_exists ($renderer, 'getODTProperties') === true ) {
470            // Get CSS properties for ODT export (deprecated version).
471            $renderer->getODTProperties ($properties, null, $class, $style);
472        } else {
473            // To old ODT plugin version.
474            return;
475        }
476
477        // Frames/Textboxes still have some issues with formatting (at least in LibreOffice)
478        // So as a workaround we implement columns as a table.
479        // This is why we now use the margin of the div as the padding for the ODT table.
480        $properties ['padding-left'] = $properties ['margin-left'] ?? null;
481        $properties ['padding-right'] = $properties ['margin-right'] ?? null;
482        $properties ['padding-top'] = $properties ['margin-top'] ?? null;
483        $properties ['padding-bottom'] = $properties ['margin-bottom'] ?? null;
484        $properties ['margin-left'] = null;
485        $properties ['margin-right'] = null;
486        $properties ['margin-top'] = null;
487        $properties ['margin-bottom'] = null;
488
489        // Percentage values are not supported for the padding. Convert to absolute values.
490        $length = strlen ($properties ['padding-left']);
491        if ( $length > 0 && $properties ['padding-left'] [$length-1] == '%' ) {
492            $properties ['padding-left'] = trim ($properties ['padding-left'], '%');
493            $properties ['padding-left'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-left']).'cm';
494        }
495        $length = strlen ($properties ['padding-right']);
496        if ( $length > 0 && $properties ['padding-right'] [$length-1] == '%' ) {
497            $properties ['padding-right'] = trim ($properties ['padding-right'], '%');
498            $properties ['padding-right'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-right']).'cm';
499        }
500        $length = strlen ($properties ['padding-top']);
501        if ( $length > 0 && $properties ['padding-top'] [$length-1] == '%' ) {
502            $properties ['padding-top'] = trim ($properties ['padding-top'], '%');
503            $properties ['padding-top'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-top']).'cm';
504        }
505        $length = strlen ($properties ['padding-bottom']);
506        if ( $length > 0 && $properties ['padding-bottom'] [$length-1] == '%' ) {
507            $properties ['padding-bottom'] = trim ($properties ['padding-bottom'], '%');
508            $properties ['padding-bottom'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-bottom']).'cm';
509        }
510
511        $this->column_count++;
512        if ( $this->column_count == 1 ) {
513            // If this is the first column opened since the group was opened
514            // then we have to open the table and a (single) row first.
515            $properties ['width'] = '100%';
516            $renderer->_odtTableOpenUseProperties($properties);
517            $renderer->_odtTableRowOpenUseProperties($properties);
518        }
519
520        // We did not specify any max column value when we opened the table.
521        // So we have to tell the renderer to add a column just now.
522        unset($properties ['width']);
523        $renderer->_odtTableAddColumnUseProperties($properties);
524
525        // Open the cell.
526        $renderer->_odtTableCellOpenUseProperties($properties);
527    }
528
529    function renderODTCloseColumn ($renderer) {
530        if ( method_exists ($renderer, '_odtTableAddColumnUseProperties') === false ) {
531            // Function is not supported by installed ODT plugin version, return.
532            return;
533        }
534
535        $renderer->tablecell_close();
536    }
537
538    function renderODTOpenGroup ($renderer, $class, $style) {
539        // Nothing to do for now.
540    }
541
542    function renderODTCloseGroup ($renderer) {
543        // If a table has been opened in the group we close it now.
544        if ( $this->column_count > 0 ) {
545            // At last we need to close the row and the table!
546            $renderer->tablerow_close();
547            //$renderer->table_close();
548            $renderer->_odtTableClose();
549        }
550        $this->column_count = 0;
551    }
552
553    function renderODTOpenSpan ($renderer, $class, $style, $language, $attr) {
554        $properties = array ();
555
556        if ( method_exists ($renderer, 'getODTPropertiesFromElement') === true ) {
557            // Get CSS properties for ODT export.
558            // Set parameter $inherit=false to prevent changiung the font-size and family!
559            $renderer->getODTPropertiesNew ($properties, 'span', $attr, null, false);
560        } else if ( method_exists ($renderer, 'getODTProperties') === true ) {
561            // Get CSS properties for ODT export (deprecated version).
562            $renderer->getODTProperties ($properties, 'span', $class, $style);
563
564            if ( empty($properties ['background-image']) === false ) {
565                $properties ['background-image'] =
566                    $renderer->replaceURLPrefix ($properties ['background-image'], DOKU_INC);
567            }
568        } else {
569            // To old ODT plugin version.
570            return;
571        }
572
573        $name = '';
574        if ( empty($language) === false ) {
575            $properties ['lang'] = $language;
576            $name .= 'Language: '.$language;
577        }
578
579        if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
580            // Older ODT plugin version.
581            $renderer->_odtSpanOpenUseProperties($properties);
582        } else {
583            // Newer version create our own common styles.
584            $properties ['font-size'] = null;
585
586            // Create parent style to group the others beneath it
587            if (!$renderer->styleExists('Plugin_Wrap_Spans')) {
588                $parent_properties = array();
589                $parent_properties ['style-parent'] = null;
590                $parent_properties ['style-class'] = 'Plugin Wrap Spans';
591                $parent_properties ['style-name'] = 'Plugin_Wrap_Spans';
592                $parent_properties ['style-display-name'] = 'Plugin Wrap';
593                $renderer->createTextStyle($parent_properties);
594            }
595
596            $name .= $this->getODTCommonStyleName($class);
597            $style_name = 'Plugin_Wrap_Span_'.$name;
598            if (!$renderer->styleExists($style_name)) {
599                $properties ['style-parent'] = 'Plugin_Wrap_Spans';
600                $properties ['style-class'] = null;
601                $properties ['style-name'] = $style_name;
602                $properties ['style-display-name'] = $name;
603                $renderer->createTextStyle($properties);
604            }
605
606            if (!empty($properties ['background-image'])) {
607                if (method_exists ($renderer, '_odtAddImageUseProperties') === true) {
608                    $size = null;
609                    if (!empty($properties ['font-size'])) {
610                        $size = $properties ['font-size'];
611                        $size = $renderer->addToValue($size, '2pt');
612                    }
613                    $properties ['width'] = $size;
614                    $properties ['height'] = $size;
615                    $properties ['title'] = null;
616                    $renderer->_odtAddImageUseProperties ($properties ['background-image'],$properties);
617                } else {
618                    $renderer->_odtAddImage ($properties ['background-image'],null,null,null,null,null);
619                }
620            }
621            $renderer->_odtSpanOpen($style_name);
622        }
623    }
624
625    function renderODTCloseSpan ($renderer) {
626        if ( method_exists ($renderer, '_odtSpanClose') === false ) {
627            // Function is not supported by installed ODT plugin version, return.
628            return;
629        }
630        $renderer->_odtSpanClose();
631    }
632
633    function renderODTOpenTable ($renderer, $attr, $style, $attr_string) {
634        self::$table_entr += 1;
635
636        $class = $attr ['class'];
637        $css_properties = array ();
638
639        if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
640            // Function is not supported by installed ODT plugin version, return.
641            return;
642        }
643
644        // Get CSS properties for ODT export.
645        $renderer->getODTPropertiesNew ($css_properties, 'div', $attr_string, null, true);
646
647        if ( empty($css_properties ['float']) === true ) {
648            // If the float property is not set, set it to 'left' becuase the ODT plugin
649            // would default to 'center' which is diffeent to the XHTML behaviour.
650            //$css_properties ['float'] = 'left';
651            if (strpos ($class, 'wrap_left') !== false ) {
652                $css_properties ['float'] = 'left';
653            } else if (strpos ($class, 'wrap_center') !== false ) {
654                $css_properties ['float'] = 'center';
655            } else if (strpos ($class, 'wrap_right') !== false) {
656                $css_properties ['float'] = 'right';
657            }
658        }
659
660        // The display property has differing usage in CSS. So we better overwrite it.
661        $css_properties ['display'] = 'always';
662        if ( stripos ($class, 'wrap_noprint') !== false ) {
663            $css_properties ['display'] = 'screen';
664        }
665        if ( stripos ($class, 'wrap_onlyprint') !== false ) {
666            $css_properties ['display'] = 'printer';
667        }
668
669        $background_color = $css_properties ['background-color'];
670        $image = $css_properties ['background-image'] ?? null;
671        $margin_top = $css_properties ['margin-top'];
672        $margin_right = $css_properties ['margin-right'];
673        $margin_bottom = $css_properties ['margin-bottom'];
674        $margin_left = $css_properties ['margin-left'];
675        $width = $attr ['width'];
676
677        // Open 2x1 table if image is present
678        // otherwise only a 1x1 table
679        $properties = array();
680        $properties ['width'] = '100%';
681        $properties ['align'] = 'center';
682        $properties ['margin-top'] = $margin_top;
683        $properties ['margin-right'] = $margin_right;
684        $properties ['margin-bottom'] = $margin_bottom;
685        $properties ['margin-left'] = $margin_left;
686
687        $frame_props = array();
688        if (!empty($css_properties ['border'])) {
689            $frame_props ['border'] = $css_properties ['border'];
690        } else {
691            $frame_props ['border'] = 'none';
692        }
693        $frame_props ['min-height'] = '1cm';
694        $frame_props ['width'] = $attr ['width'];
695        $frame_props ['float'] = $css_properties ['float'];
696        if ( self::$table_entr > 1 ) {
697            $frame_props ['anchor-type'] = 'as-char';
698        } else {
699            $frame_props ['anchor-type'] = 'paragraph';
700        }
701        $frame_props ['textarea-horizontal-align'] = 'left';
702        $frame_props ['run-through'] = 'foreground';
703        $frame_props ['vertical-pos'] = 'from-top';
704        $frame_props ['vertical-rel'] = 'paragraph';
705        $frame_props ['horizontal-pos'] = 'from-left';
706        $frame_props ['horizontal-rel'] = 'paragraph';
707        $frame_props ['wrap'] = 'parallel';
708        $frame_props ['number-wrapped-paragraphs'] = 'no-limit';
709        if (!empty($frame_props ['float']) &&
710            $frame_props ['float'] != 'center') {
711            $frame_props ['margin-top'] = '0cm';
712            $frame_props ['margin-right'] = '0cm';
713            $frame_props ['margin-bottom'] = '0cm';
714            $frame_props ['margin-left'] = '0cm';
715            $frame_props ['padding-top'] = '0cm';
716            $frame_props ['padding-bottom'] = '0cm';
717        } else {
718            // No wrapping on not floating divs
719            $frame_props ['wrap'] = 'none';
720        }
721
722        switch ($frame_props ['float']) {
723            case 'left':
724                if ( self::$table_entr == 1 ) {
725                    $frame_props ['y'] = '0cm';
726                    $frame_props ['x'] = self::$box_left_pos.'cm';
727                    self::$box_left_pos += trim($frame_props ['width'], 'cm');
728                }
729                $frame_props ['padding-left'] = '0cm';
730            break;
731            case 'right':
732                $frame_props ['horizontal-rel'] = 'paragraph';
733                $frame_props ['horizontal-pos'] = 'right';
734                $frame_props ['padding-right'] = '0cm';
735            break;
736            case 'center':
737                $frame_props ['horizontal-pos'] = 'center';
738            break;
739            default:
740                $frame_props ['padding-left'] = '0cm';
741            break;
742        }
743        $renderer->_odtOpenTextBoxUseProperties($frame_props);
744
745        $renderer->_odtTableOpenUseProperties($properties);
746
747        if (!empty($image)) {
748            $properties = array();
749            $properties ['width'] = '2cm';
750            $renderer->_odtTableAddColumnUseProperties($properties);
751        }
752
753        $properties = array();
754        $renderer->_odtTableAddColumnUseProperties($properties);
755
756        $renderer->tablerow_open();
757
758        if (!empty($image)) {
759            $properties = array();
760            $properties ['vertical-align'] = 'middle';
761            $properties ['text-align'] = 'center';
762            $properties ['padding'] = '0.1cm';
763            $properties ['background-color'] = $background_color;
764
765            $renderer->_odtTableCellOpenUseProperties($properties);
766            $renderer->_odtAddImage($image);
767            $renderer->tablecell_close();
768        }
769
770        $properties = array();
771        $properties ['vertical-align'] = 'middle';
772        $properties ['padding'] = '0.3cm';
773        $properties ['background-color'] = $background_color;
774        $properties ['border'] = 'none';
775        $renderer->_odtTableCellOpenUseProperties($properties);
776    }
777
778    function renderODTCloseTable ($renderer) {
779        $renderer->tablecell_close();
780        $renderer->tablerow_close();
781        $renderer->_odtTableClose();
782        $renderer->_odtCloseTextBox ();
783        $renderer->p_open();
784        $renderer->p_close();
785
786        self::$table_entr -= 1;
787    }
788
789    protected function getODTCommonStyleName ($class_string) {
790        static $map = array (
791            'wrap_box' => 'Box', 'wrap_danger' => 'Danger', 'wrap_warning' => 'Warning',
792            'wrap_caution' => 'Caution', 'wrap_notice' => 'Notice', 'wrap_safety' => 'Safety',
793            'wrap_info' => 'Info', 'wrap_important' => 'Important', 'wrap_alert' => 'Alert',
794            'wrap_tip' => 'Tip', 'wrap_help' => 'Help', 'wrap_todo' => 'To do',
795            'wrap_download' => 'Download', 'wrap_hi' => 'Highlighted', 'wrap_spoiler' => 'Spoiler',
796            'wrap_leftalign' => 'Left aligned', 'wrap_rightalign' => 'Right aligned',
797            'wrap_centeralign' => 'Centered', 'wrap_justify' => 'Justify', 'wrap_em' => 'Emphasised',
798            'wrap_lo' => 'Less significant');
799        $classes = explode(' ', $class_string);
800        $name = '';
801        foreach ($classes as $class) {
802            if (array_key_exists($class, $map)) {
803                $name .= $map [$class];
804            }
805        }
806        return ($name);
807    }
808}
809