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 } else { 189 $floattype = 'table'; 190 } 191 switch($case) { 192 case 'caption_open' : 193 $orientation = "\\centering"; 194 if(strpos($data['classes'], 'left') !== false) { 195 $orientation = "\\left"; 196 } elseif(strpos($data['classes'], 'right') !== false) { 197 $orientation = "\\right"; 198 } 199 $renderer->doc .= "\\begin{".$floattype."}[!h]{".$orientation."}"; 200 return true; 201 202 case 'data' : 203 $renderer->doc .= trim($data); 204 return true; 205 206 case 'caption_close' : 207 $renderer->doc .= "\\caption{".$data['caption']."}\\label{".$data['caprefname']."}\\end{".$floattype."}"; 208 return true; 209 } 210 break; 211 } 212 return false; 213 } 214 215 /** 216 * When a array of caption data is given, this is stored. Otherwise the array is returned 217 * 218 * @param string $type 'img' or 'tab' 219 * @param array $captiondata array with data of the caption 220 * @param string $id page id 221 * @return void|array 222 */ 223 static private function captionReferencesStorage($type, $captiondata = null, $id = null) { 224 global $ID; 225 static $captionreferences = array(); 226 227 if($captiondata !== null) { 228 //store reference names 229 if(!isset($captionreferences[$ID][$type])) { 230 $captionreferences[$ID][$type][] = ''; 231 } 232 $captionreferences[$ID][$type][] = $captiondata['caprefname']; 233 return null; 234 235 } else { 236 //return reference names 237 if($id === null) { 238 $id = $ID; 239 } 240 return $captionreferences[$id][$type]; 241 } 242 } 243 244 /** 245 * Returns the captionreferences of page 246 * 247 * @param string $id page id 248 * @param string $type caption type 'img' or 'tab' 249 * @return array of stored reference names 250 */ 251 static public function getCaptionreferences($id, $type) { 252 return self::captionReferencesStorage($type, null, $id); 253 } 254 255 /** 256 * Parse parameters part of <imgcaption imgref class1 class2|Caption of image> 257 * 258 * @param string $str space separated parameters e.g."imgref class1 class2|Caption of image" 259 * @return array(string imgrefname, string classes, string caption) 260 */ 261 protected function parseParam($str) { 262 if(empty($str)) { 263 return array(); 264 } 265 266 // get caption, second part 267 $parsed = explode("|", $str, 2); 268 $caption = ''; 269 if(isset($parsed[1])) $caption = trim($parsed[1]); 270 271 // get the img ref name. Its the first word 272 $parsed = explode(" ", $parsed[0], 3); 273 $captiontype = substr($parsed[0], 0, 3); 274 $caprefname = $parsed[1]; 275 276 $tokens = preg_split('/\s+/', $parsed[2], 9); // limit is defensive 277 $classes = ''; 278 foreach($tokens as $token) { 279 // restrict token (class names) characters to prevent any malicious data 280 if(preg_match('/[^A-Za-z0-9_-]/', $token)) continue; 281 $token = trim($token); 282 if($token == '') continue; 283 $classes .= ' '.$token; 284 } 285 286 return array( 287 'caprefname' => $caprefname, 288 'classes' => $classes, 289 'caption' => $caption, 290 'type' => $captiontype 291 ); 292 } 293 294 /** 295 * @var string $captionStart opening tag of caption, image/table dependent 296 * @var string $captionEnd closing tag of caption, image/table dependent 297 */ 298 protected $captionStart = '<span id="%s" class="imgcaption%s">'; 299 protected $captionEnd = '</span>'; 300 301 /** 302 * Create html of opening of caption wrapper 303 * 304 * @param array $data(caprefname, classes, ..) 305 * @return string html start of caption wrapper 306 */ 307 protected function captionStart($data) { 308 return sprintf( 309 $this->captionStart, 310 $data['type'].'_'.cleanID($data['caprefname']), 311 $data['classes'], 312 (strpos($data['classes'], 'center') == false ? '':' center') //needed for tabcaptionbox 313 ); 314 } 315 316 /** 317 * Create html of closing of caption wrapper 318 * 319 * @param array $data(caprefname, refnumber, caption, ..) Caption data 320 * @return string html caption wrapper 321 */ 322 protected function captionEnd($data) { 323 return '<span class="undercaption">' 324 .$this->getLang($data['type'].'short').' '.$data['refnumber'] 325 .($data['caption'] ? ': ' . hsc($data['caption']) : '') 326 .'<a href=" "><span></span></a>' 327 .'</span>' 328 . $this->captionEnd; 329 } 330} 331