1<?php
2
3if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
4if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
5require_once(DOKU_PLUGIN.'syntax.php');
6
7/**
8 * All DokuWiki plugins to extend the parser/rendering mechanism
9 * need to inherit from this class
10 */
11class syntax_plugin_revealjs_background extends DokuWiki_Syntax_Plugin {
12
13    public function getType() { return 'substition'; }
14    public function getSort() { return 32; }
15    public function getPType() { return 'block'; }
16
17
18    /**
19     * Connect lookup pattern to lexer.
20     *
21     * @param $aMode String The desired rendermode.
22     * @return none
23     * @public
24     * @see render()
25     */
26    public function connectTo($mode) {
27        $this->Lexer->addSpecialPattern('----+>>?|----+[^\n]*?----+>>?|<<?----+|{{background>.+?}}', $mode, 'plugin_revealjs_background');
28    }
29
30
31    /**
32     * Handler to prepare matched data for the rendering process.
33     *
34     * @param $aMatch String The text matched by the patterns.
35     * @param $aState Integer The lexer state for the match.
36     * @param $aPos Integer The character position of the matched text.
37     * @param $aHandler Object Reference to the Doku_Handler object.
38     * @return Integer The current lexer state for the match.
39     * @public
40     * @see render()
41     * @static
42     */
43    public function handle($match, $state, $pos, Doku_Handler $handler) {
44        $transition_count = 0;
45        $position_count = 0;
46        $data = array();
47        $data['position'] = $pos;
48        $data['first_chars'] = substr($match, 0, 2);
49        $data['last_chars'] = substr($match, -2);
50        $params = preg_split("/\s+/",
51            ($data['first_chars'] == '{{' ?
52                substr($match, 13, -2) :
53                trim($match,'-<> ')
54            ), 12);
55        foreach ($params as $param) {
56            if (!$data['background_color'] && $this->_is_valid_color($param)) {
57                $data['background_color'] = $param;
58            }
59            elseif (!$data['background_image'] && $this->_is_valid_image($param)) {
60                $data['background_image'] = $param;
61            }
62            elseif (!$data['background_size'] && $this->_is_valid_size($param)) {
63                $data['background_size'] = $param;
64            }
65            elseif ($position_count == 0 && $this->_is_valid_position($param)) {
66                $position_count += 2;
67                $data['background_position'] = $param;
68            }
69            elseif ($position_count < 2 && in_array($param, array('top','bottom','left','right','center'))) {
70                $position_count += 1;
71                if (!$data['background_position']) $data['background_position'] = $param;
72                else $data['background_position'] .= ' '.$param;
73            }
74            elseif (!$data['background_repeat'] && in_array($param, array('repeat','no-repeat'))) {
75                $data['background_repeat'] = $param;
76            }
77            elseif (!$data['background_transition'] && $this->_is_valid_bg_transition($param)) {
78                $data['background_transition'] = $param;
79            }
80            elseif ($transition_count < 2 && $this->_is_valid_transition($param)) {
81                $transition_count += 1;
82                if (!$data['transition']) $data['transition'] = $param;
83                else $data['transition'] .= ' '.$param;
84            }
85            elseif (!$data['transition_speed'] && in_array($param, array('default','fast','slow'))) {
86                $data['transition_speed'] = $param;
87            }
88            elseif (!$data['no_footer'] && $param == 'no-footer') {
89                $data['no_footer'] = true;
90            }
91        }
92        return $data;
93    }
94
95    /**
96     * Handle the actual output creation.
97     *
98     * @param $aFormat String The output format to generate.
99     * @param $aRenderer Object A reference to the renderer object.
100     * @param $aData Array The data created by the <tt>handle()</tt>
101     * method.
102     * @return Boolean <tt>TRUE</tt> if rendered successfully, or
103     * <tt>FALSE</tt> otherwise.
104     * @public
105     * @see handle()
106     */
107    public function render($mode, Doku_Renderer $renderer, $data) {
108        global $conf;
109        if($mode == 'xhtml') {
110
111            // rendering the slideshow
112            if (is_a($renderer, 'renderer_plugin_revealjs')){
113                $renderer->next_slide_background_color = $data['background_color'];
114                $renderer->next_slide_background_image =
115                    $data['background_image'] && substr($data['background_image'], 0, 4) == 'http' ?
116                    $data['background_image'] :
117                    ml($data['background_image']); /*DokuWiki build link to media file*/
118                $renderer->next_slide_background_size = $data['background_size'];
119                $renderer->next_slide_background_position = str_replace(',', ' ', $data['background_position']); // we replace "," with " ", because we needed this to distinguish between image size and image position
120                $renderer->next_slide_background_repeat = $data['background_repeat'];
121                $renderer->next_slide_background_transition = substr($data['background_transition'],3); // we cut off "bg-" for Reveal.js (we had "bg-" only to distinguish between background transition and slide transition)
122                $renderer->next_slide_transition = $data['transition'];
123                $renderer->next_slide_transition_speed = $data['transition_speed'];
124                /* could be, that {{no-footer}} is used before a {{background>xxx}} definition and
125                $renderer->next_slide_no_footer is already set to true, so we merge here both with a logical or */
126                $renderer->next_slide_no_footer = ($data['no_footer'] || $renderer->next_slide_no_footer);
127                if ($data['last_chars'] == '->') {
128                    $renderer->open_slide();
129                    $renderer->slide_indicator_headers = false;
130                }
131                elseif ($data['last_chars'] == '>>') {
132                    $renderer->open_slide_container();
133                    $renderer->open_slide();
134                    $renderer->slide_indicator_headers = false;
135                }
136                elseif ($data['first_chars'] == '<-') {
137                    $renderer->close_slide();
138                    $renderer->slide_indicator_headers = true;
139                }
140                elseif ($data['first_chars'] == '<<') {
141                    $renderer->close_slide_container();
142                    $renderer->slide_indicator_headers = true;
143                }
144            }
145
146            // rendering the normal wiki page
147            elseif ($this->getConf('revealjs_active')) {
148                /* could be, that {{no-footer}} is used before and we need to align the
149                start position definition for the section editing */
150                if ($renderer->wikipage_next_slide_no_footer_position > 0) {
151                    $data['position'] = $renderer->wikipage_next_slide_no_footer_position;
152                    $renderer->wikipage_next_slide_no_footer_position = 0;
153                }
154                // process slide details view
155                if ($data['background_color']) {
156                    $slide_details_text .= ' '.$data['background_color'];
157                    $slide_details_background .= 'background-color:'.$data['background_color'].';';
158                }
159                if ($data['background_image']) {
160                    $slide_details_text .= ' '.$data['background_image'];
161                    $slide_details_background .= 'background-image: url("'.
162                        (substr($data['background_image'], 0, 4) == 'http' ?
163                            $data['background_image'] :
164                            ml($data['background_image'])).
165                        '");';
166                }
167                if ($data['background_size']) {
168                    $slide_details_text .= ' '.$data['background_size'];
169                    $slide_details_background .= 'background-size:'.$data['background_size'].';';
170                }
171                if ($data['background_position']) {
172                    $slide_details_text .= ' '.$data['background_position'];
173                    $slide_details_background .= 'background-position:'.
174                        str_replace(',', ' ', $data['background_position']).';';
175                }
176                if ($data['background_repeat']) {
177                    $slide_details_text .= ' '.$data['background_repeat'];
178                    $slide_details_background .= 'background-repeat:'.$data['background_repeat'].';';
179                }
180                if ($data['background_transition']) {
181                    $slide_details_text .= ' '.$data['background_transition'];
182                }
183                if ($data['transition']) {
184                    $slide_details_text .= ' '.$data['transition'];
185                }
186                if ($data['transition_speed']) {
187                    $slide_details_text .= ' '.$data['transition_speed'];
188                }
189                /* could be, that {{no-footer}} is used before a {{background>xxx}} definition and
190                $renderer->next_slide_no_footer is already set to true, so we merge here both with a logical or */
191                if ($data['no_footer'] || $renderer->wikipage_next_slide_no_footer) {
192                    $slide_details_text .= ' no-footer';
193                    $renderer->wikipage_next_slide_no_footer = false;
194                }
195                // handle section editing
196                if (in_array($data['last_chars'], array('->','>>','}}'))) {
197                    $renderer->wikipage_slide_number += 1;
198                    // close edit section, if open
199                    if($renderer->wikipage_slide_edit_section_open) {
200                        $renderer->wikipage_slide_edit_section_open = false;
201                        $renderer->doc .= DOKU_LF.'</div>'.DOKU_LF;
202                        $renderer->finishSectionEdit($data['position']- 1);
203                    }
204                    // calculate slide direction
205                    if ($data['last_chars'] == '>>') {
206                        $slide_direction = '→';
207                    }
208                    elseif ($data['last_chars'] == '->') {
209                        $slide_direction = '↓';
210                    }
211                    else {
212                        $slide_direction = '';
213                        $conf['plugin']['revealjs']['slides_with_unknown_direction'] = true;
214                    }
215
216		    $sectionEditStartData = ['target' => 'section'];
217		    if (!defined('SEC_EDIT_PATTERN')) {
218			// backwards-compatibility for Frusterick Manners (2017-02-19)
219			$sectionEditStartData = 'section';
220		    }
221
222                    /* write slide details to page - we need to use a fake header (<h1 style="display:none...) here
223                    to force dokuwiki to show correct section edit highlighting by hoovering the edit button */
224                    $renderer->doc .= DOKU_LF.DOKU_LF.'<h2 style="display:none;" class="' .
225                        $renderer->startSectionEdit($data['position'], $sectionEditStartData, 'Slide '.$renderer->wikipage_slide_number).'"></h2>' . ($this->getConf('show_slide_details') ?
226                        '<div class="slide-details-hr'.($renderer->wikipage_slide_number == 1 ? ' first-slide' : '').'"></div>' .
227                        ($data['background_color'] || $data['background_image'] ?
228                            '<div class="slide-details-background" style='."'".$slide_details_background."'".'></div>' :
229                            '') .
230                        '<div class="slide-details-text'.($slide_direction==''?' fix-my-direction':'').'">'.$slide_direction .
231                        ' Slide '.$renderer->wikipage_slide_number.$slide_details_text.'</div>' : '');
232                    // open new edit section
233                    $renderer->wikipage_slide_edit_section_open = true;
234                    $renderer->doc .= DOKU_LF.'<div class="level2">'.DOKU_LF;
235                    /* Only the special horizontal row slide indicator changes the
236                    indicator mode */
237                    if (in_array($data['last_chars'], array('->','>>'))) {
238                        $renderer->wikipage_slide_indicator_headers = false;
239                    }
240                    /* for slide indicator mode "headers" we signaling here the
241                    header function that a section is already open */
242                    if ($data['last_chars'] == '}}') {
243                        $renderer->wikipage_slide_background_defined = true;
244                    }
245                }
246                elseif (in_array($data['first_chars'], array('<-','<<'))) {
247                    $renderer->wikipage_slide_indicator_headers = true;
248                }
249            }
250            return true;
251        }
252        return false;
253    }
254
255    /**
256     * Validate slide transition
257     */
258    private function _is_valid_transition($val) {
259        $pattern = '/^(?:none|fade|slide|convex|concave|zoom)(?:-in|-out)?$/';
260        if (preg_match($pattern, $val)) return $val;
261        return '';
262    }
263
264
265    /**
266     * Validate background transition
267     */
268    private function _is_valid_bg_transition($val) {
269        $pattern = '/^bg-(?:none|fade|slide|convex|concave|zoom)$/';
270        if (preg_match($pattern, $val)) return $val;
271        return '';
272    }
273
274
275    /**
276     * Validate HTML color
277     */
278    private function _is_valid_color($val) {
279        $named = array('aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgreen', 'lightgray', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen');
280
281        if (in_array($val, $named)) {
282            return $val;
283        }
284        else {
285            $pattern = '/^(#([\da-f]{3}){1,2}|(rgb|hsl)a\((\d{1,3}%?,){3}(1|0?\.\d+)\)|(rgb|hsl)\(\d{1,3}%?(,\d{1,3}%?){2}\))$/';
286            if (preg_match($pattern, $val)) return $val;
287            return '';
288        }
289    }
290
291    /**
292     * Validate image
293     */
294    private function _is_valid_image($val) {
295        $pattern = '/^.+\.(?:gif|png|jpg|jpeg|svg)$/i';
296        if (preg_match($pattern, $val)) return $val;
297        return '';
298    }
299
300    /**
301     * Validate size
302     */
303    private function _is_valid_size($val) {
304        $pattern = '/^\d+(?:px|%)|auto|contain|cover$/';
305        if (preg_match($pattern, $val)) return $val;
306        return '';
307    }
308
309    /**
310     * Validate position
311     */
312    private function _is_valid_position($val) {
313        $pattern = '/^\d+(?:px|%),\d+(?:px|%)$/';
314        if (preg_match($pattern, $val)) return $val;
315        return '';
316    }
317}
318