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