1<?php 2/** 3 * Plugin imagereference 4 * 5 * Syntax: <imgref linkname> - creates a figure link to an image 6 * <imgcaption linkname <orientation> | Image caption> Image/Table</imgcaption> 7 * 8 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9 * @author Martin Heinemann <martinheinemann@tudor.lu> 10 * @author Gerrit Uitslag <klapinklapin@gmail.com> 11 */ 12 13/** 14 * All DokuWiki plugins to extend the parser/rendering mechanism 15 * need to inherit from this class 16 */ 17class syntax_plugin_imagereference_imgcaption extends DokuWiki_Syntax_Plugin { 18 19 /* @var array $captionParam */ 20 protected $captionParam = array(); 21 22 /** 23 * @return string Syntax type 24 */ 25 public function getType() { 26 return 'formatting'; 27 } 28 29 /** 30 * @return string Paragraph type 31 */ 32 public function getPType() { 33 return 'normal'; 34 } 35 36 /** 37 * @return int Sort order 38 */ 39 public function getSort() { 40 return 196; 41 } 42 43 /** 44 * Specify modes allowed in the imgcaption/tabcaption 45 * Using getAllowedTypes() includes too much modes. 46 * 47 * @param string $mode Parser mode 48 * @return bool true if $mode is accepted 49 */ 50 public function accepts($mode) { 51 $allowedsinglemodes = array( 52 'media', //allowed content 53 'internallink', 'externallink', 'linebreak', //clickable img allowed 54 'emaillink', 'windowssharelink', 'filelink', 55 'plugin_graphviz', 'plugin_ditaa' //plugins 56 ); 57 if(in_array($mode, $allowedsinglemodes)) return true; 58 59 return parent::accepts($mode); 60 } 61 62 /** 63 * Connect lookup pattern to lexer. 64 * 65 * @param string $mode Parser mode 66 */ 67 public function connectTo($mode) { 68 $this->Lexer->addEntryPattern('<imgcaption.*?>(?=.*?</imgcaption>)', $mode, 'plugin_imagereference_imgcaption'); 69 70 } 71 72 public function postConnect() { 73 $this->Lexer->addExitPattern('</imgcaption>', 'plugin_imagereference_imgcaption'); 74 } 75 76 /** 77 * Handle matches of the imgcaption/tabcaption syntax 78 * 79 * @param string $match The match of the syntax 80 * @param int $state The state of the handler 81 * @param int $pos The position in the document 82 * @param Doku_Handler $handler The handler 83 * @return array Data for the renderer 84 */ 85 public function handle($match, $state, $pos, Doku_Handler $handler) { 86 global $ACT; 87 switch($state) { 88 case DOKU_LEXER_ENTER : 89 $rawparam = trim(substr($match, 1, -1)); 90 $param = $this->parseParam($rawparam); 91 92 //store parameters for closing tag 93 $this->captionParam = $param; 94 95 //local counter for preview 96 if($ACT == 'preview') { 97 self::captionReferencesStorage($param['type'], $param); 98 } 99 100 return array('caption_open', $param); 101 102 case DOKU_LEXER_UNMATCHED : 103 // drop unmatched text inside imgcaption/tabcaption tag 104 return array('data', ''); 105 106 // when normal text it's usefull, then use next lines instead 107 //$handler->_addCall('cdata', array($match), $pos); 108 //return false; 109 110 case DOKU_LEXER_EXIT : 111 //load parameters 112 $param = $this->captionParam; 113 return array('caption_close', $param); 114 } 115 116 return array(); 117 } 118 119 /** 120 * Render xhtml output, latex output or metadata 121 * 122 * @param string $mode Renderer mode (supported modes: xhtml, latex and metadata) 123 * @param Doku_Renderer $renderer The renderer 124 * @param array $indata The data from the handler function 125 * @return bool If rendering was successful. 126 */ 127 public function render($mode, Doku_Renderer $renderer, $indata) { 128 global $ID, $ACT; 129 list($case, $data) = $indata; 130 131 switch($mode) { 132 case 'xhtml' : 133 /** @var Doku_Renderer_xhtml $renderer */ 134 switch($case) { 135 case 'caption_open' : 136 $renderer->doc .= $this->captionStart($data); 137 return true; 138 139 // $data is empty string 140 case 'data' : 141 $renderer->doc .= $data; 142 return true; 143 144 case 'caption_close' : 145 //determine referencenumber 146 if($ACT == 'preview') { 147 $caprefs = self::getCaptionreferences($ID, $data['type']); 148 } else { 149 $caprefs = p_get_metadata($ID, 'captionreferences '.$data['type']); 150 } 151 $data['refnumber'] = array_search($data['caprefname'], $caprefs); 152 153 if(!$data['refnumber']) { 154 $data['refnumber'] = "##"; 155 } 156 157 $renderer->doc .= $this->captionEnd($data); 158 return true; 159 } 160 break; 161 162 case 'metadata' : 163 /** @var Doku_Renderer_metadata $renderer */ 164 switch($case) { 165 case 'caption_open' : 166 // store the image refences as metadata to expose them to the imgref/tabref and undercaption renderer 167 //create array and add index zero entry, so stored caprefnames start counting on one. 168 $type = $data['type']; 169 if(!isset($renderer->meta['captionreferences'][$type])) { 170 $renderer->meta['captionreferences'][$type][] = ''; 171 } 172 $renderer->meta['captionreferences'][$type][] = $data['caprefname']; 173 174 //abstract 175 if($renderer->capture && $data['caption']) $renderer->doc .= '<'; 176 return true; 177 178 case 'caption_close' : 179 //abstract 180 if($renderer->capture && $data['caption']) $renderer->doc .= hsc($data['caption']).'>'; 181 return true; 182 } 183 break; 184 185 case 'latex' : 186 if($data['type'] == 'img') { 187 $floattype = 'figure'; 188 } 189 else if ($data['type'] == 'ggb') { 190 // no latex support for GeoGebra applets 191 return true; 192 } else { 193 $floattype = 'table'; 194 } 195 switch($case) { 196 case 'caption_open' : 197 $orientation = "\\centering"; 198 if(strpos($data['classes'], 'left') !== false) { 199 $orientation = "\\left"; 200 } elseif(strpos($data['classes'], 'right') !== false) { 201 $orientation = "\\right"; 202 } 203 $renderer->doc .= "\\begin{".$floattype."}[!h]{".$orientation."}"; 204 return true; 205 206 case 'data' : 207 $renderer->doc .= trim($data); 208 return true; 209 210 case 'caption_close' : 211 $renderer->doc .= "\\caption{".$data['caption']."}\\label{".$data['caprefname']."}\\end{".$floattype."}"; 212 return true; 213 } 214 break; 215 } 216 return false; 217 } 218 219 /** 220 * When a array of caption data is given, this is stored. Otherwise the array is returned 221 * 222 * @param string $type 'img' or 'tab' 223 * @param array $captiondata array with data of the caption 224 * @param string $id page id 225 * @return void|array 226 */ 227 static private function captionReferencesStorage($type, $captiondata = null, $id = null) { 228 global $ID; 229 static $captionreferences = array(); 230 231 if($captiondata !== null) { 232 //store reference names 233 if(!isset($captionreferences[$ID][$type])) { 234 $captionreferences[$ID][$type][] = ''; 235 } 236 $captionreferences[$ID][$type][] = $captiondata['caprefname']; 237 return null; 238 239 } else { 240 //return reference names 241 if($id === null) { 242 $id = $ID; 243 } 244 return $captionreferences[$id][$type]; 245 } 246 } 247 248 /** 249 * Returns the captionreferences of page 250 * 251 * @param string $id page id 252 * @param string $type caption type 'img' or 'tab' 253 * @return array of stored reference names 254 */ 255 static public function getCaptionreferences($id, $type) { 256 return self::captionReferencesStorage($type, null, $id); 257 } 258 259 /** 260 * Parse parameters part of <imgcaption imgref class1 class2|Caption of image> 261 * 262 * @param string $str space separated parameters e.g."imgref class1 class2|Caption of image" 263 * @return array(string imgrefname, string classes, string caption) 264 */ 265 protected function parseParam($str) { 266 if(empty($str)) { 267 return array(); 268 } 269 270 // get caption, second part 271 $parsed = explode("|", $str, 2); 272 $caption = ''; 273 if(isset($parsed[1])) $caption = trim($parsed[1]); 274 275 // get the img ref name. Its the first word 276 $parsed = array_pad(explode(" ", $parsed[0], 3), 3, null); 277 $captiontype = substr($parsed[0], 0, 3); 278 $caprefname = $parsed[1]; 279 280 $tokens = preg_split('/\s+/', $parsed[2], 9); // limit is defensive 281 $classes = ''; 282 foreach($tokens as $token) { 283 // restrict token (class names) characters to prevent any malicious data 284 if(preg_match('/[^A-Za-z0-9_-]/', $token)) continue; 285 $token = trim($token); 286 if($token == '') continue; 287 $classes .= ' '.$token; 288 } 289 290 return array( 291 'caprefname' => $caprefname, 292 'classes' => $classes, 293 'caption' => $caption, 294 'type' => $captiontype 295 ); 296 } 297 298 /** 299 * @var string $captionStart opening tag of caption, image/table dependent 300 * @var string $captionEnd closing tag of caption, image/table dependent 301 */ 302 protected $captionStart = '<span id="%s" class="imgcaption%s">'; 303 protected $captionEnd = '</span>'; 304 305 /** 306 * Create html of opening of caption wrapper 307 * 308 * @param array $data(caprefname, classes, ..) 309 * @return string html start of caption wrapper 310 */ 311 protected function captionStart($data) { 312 return sprintf( 313 $this->captionStart, 314 $data['type'].'_'.cleanID($data['caprefname']), 315 $data['classes'], 316 (strpos($data['classes'], 'center') == false ? '':' center') //needed for tabcaptionbox 317 ); 318 } 319 320 /** 321 * Create html of closing of caption wrapper 322 * 323 * @param array $data(caprefname, refnumber, caption, ..) Caption data 324 * @return string html caption wrapper 325 */ 326 protected function captionEnd($data) { 327 return '<span class="undercaption">' 328 .$this->getLang($data['type'].'short').' '.$data['refnumber'] 329 .($data['caption'] ? ': ' . hsc($data['caption']) : '') 330 .'<a href=" "><span></span></a>' 331 .'</span>' 332 . $this->captionEnd; 333 } 334} 335