- creates a figure link to an image * | Image caption> Image/Table * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Martin Heinemann * @author Gerrit Uitslag */ /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_imagereference_imgcaption extends DokuWiki_Syntax_Plugin { /* @var array $captionParam */ protected $captionParam = array(); /** * @return string Syntax type */ public function getType() { return 'formatting'; } /** * @return string Paragraph type */ public function getPType() { return 'normal'; } /** * @return int Sort order */ public function getSort() { return 196; } /** * Specify modes allowed in the imgcaption/tabcaption * Using getAllowedTypes() includes too much modes. * * @param string $mode Parser mode * @return bool true if $mode is accepted */ public function accepts($mode) { $allowedsinglemodes = array( 'media', //allowed content 'internallink', 'externallink', 'linebreak', //clickable img allowed 'emaillink', 'windowssharelink', 'filelink', 'plugin_graphviz', 'plugin_ditaa' //plugins ); if(in_array($mode, $allowedsinglemodes)) return true; return parent::accepts($mode); } /** * Connect lookup pattern to lexer. * * @param string $mode Parser mode */ public function connectTo($mode) { $this->Lexer->addEntryPattern('(?=.*?)', $mode, 'plugin_imagereference_imgcaption'); } public function postConnect() { $this->Lexer->addExitPattern('', 'plugin_imagereference_imgcaption'); } /** * Handle matches of the imgcaption/tabcaption syntax * * @param string $match The match of the syntax * @param int $state The state of the handler * @param int $pos The position in the document * @param Doku_Handler $handler The handler * @return array Data for the renderer */ public function handle($match, $state, $pos, Doku_Handler $handler) { global $ACT; switch($state) { case DOKU_LEXER_ENTER : $rawparam = trim(substr($match, 1, -1)); $param = $this->parseParam($rawparam); //store parameters for closing tag $this->captionParam = $param; //local counter for preview if($ACT == 'preview') { self::captionReferencesStorage($param['type'], $param); } return array('caption_open', $param); case DOKU_LEXER_UNMATCHED : // drop unmatched text inside imgcaption/tabcaption tag return array('data', ''); // when normal text it's usefull, then use next lines instead //$handler->_addCall('cdata', array($match), $pos); //return false; case DOKU_LEXER_EXIT : //load parameters $param = $this->captionParam; return array('caption_close', $param); } return array(); } /** * Render xhtml output, latex output or metadata * * @param string $mode Renderer mode (supported modes: xhtml, latex and metadata) * @param Doku_Renderer $renderer The renderer * @param array $indata The data from the handler function * @return bool If rendering was successful. */ public function render($mode, Doku_Renderer $renderer, $indata) { global $ID, $ACT; list($case, $data) = $indata; switch($mode) { case 'xhtml' : /** @var Doku_Renderer_xhtml $renderer */ switch($case) { case 'caption_open' : $renderer->doc .= $this->captionStart($data); return true; // $data is empty string case 'data' : $renderer->doc .= $data; return true; case 'caption_close' : //determine referencenumber if($ACT == 'preview') { $caprefs = self::getCaptionreferences($ID, $data['type']); } else { $caprefs = p_get_metadata($ID, 'captionreferences '.$data['type']); } $data['refnumber'] = array_search($data['caprefname'], $caprefs); if(!$data['refnumber']) { $data['refnumber'] = "##"; } $renderer->doc .= $this->captionEnd($data); return true; } break; case 'metadata' : /** @var Doku_Renderer_metadata $renderer */ switch($case) { case 'caption_open' : // store the image refences as metadata to expose them to the imgref/tabref and undercaption renderer //create array and add index zero entry, so stored caprefnames start counting on one. $type = $data['type']; if(!isset($renderer->meta['captionreferences'][$type])) { $renderer->meta['captionreferences'][$type][] = ''; } $renderer->meta['captionreferences'][$type][] = $data['caprefname']; //abstract if($renderer->capture && $data['caption']) $renderer->doc .= '<'; return true; case 'caption_close' : //abstract if($renderer->capture && $data['caption']) $renderer->doc .= hsc($data['caption']).'>'; return true; } break; case 'latex' : if($data['type'] == 'img') { $floattype = 'figure'; } else if ($data['type'] == 'ggb') { // no latex support for GeoGebra applets return true; } else { $floattype = 'table'; } switch($case) { case 'caption_open' : $orientation = "\\centering"; if(strpos($data['classes'], 'left') !== false) { $orientation = "\\left"; } elseif(strpos($data['classes'], 'right') !== false) { $orientation = "\\right"; } $renderer->doc .= "\\begin{".$floattype."}[!h]{".$orientation."}"; return true; case 'data' : $renderer->doc .= trim($data); return true; case 'caption_close' : $renderer->doc .= "\\caption{".$data['caption']."}\\label{".$data['caprefname']."}\\end{".$floattype."}"; return true; } break; } return false; } /** * When a array of caption data is given, this is stored. Otherwise the array is returned * * @param string $type 'img' or 'tab' * @param array $captiondata array with data of the caption * @param string $id page id * @return void|array */ static private function captionReferencesStorage($type, $captiondata = null, $id = null) { global $ID; static $captionreferences = array(); if($captiondata !== null) { //store reference names if(!isset($captionreferences[$ID][$type])) { $captionreferences[$ID][$type][] = ''; } $captionreferences[$ID][$type][] = $captiondata['caprefname']; return null; } else { //return reference names if($id === null) { $id = $ID; } return $captionreferences[$id][$type]; } } /** * Returns the captionreferences of page * * @param string $id page id * @param string $type caption type 'img' or 'tab' * @return array of stored reference names */ static public function getCaptionreferences($id, $type) { return self::captionReferencesStorage($type, null, $id); } /** * Parse parameters part of * * @param string $str space separated parameters e.g."imgref class1 class2|Caption of image" * @return array(string imgrefname, string classes, string caption) */ protected function parseParam($str) { if(empty($str)) { return array(); } // get caption, second part $parsed = explode("|", $str, 2); $caption = ''; if(isset($parsed[1])) $caption = trim($parsed[1]); // get the img ref name. Its the first word $parsed = array_pad(explode(" ", $parsed[0], 3), 3, null); $captiontype = substr($parsed[0], 0, 3); $caprefname = $parsed[1]; $tokens = preg_split('/\s+/', $parsed[2], 9); // limit is defensive $classes = ''; foreach($tokens as $token) { // restrict token (class names) characters to prevent any malicious data if(preg_match('/[^A-Za-z0-9_-]/', $token)) continue; $token = trim($token); if($token == '') continue; $classes .= ' '.$token; } return array( 'caprefname' => $caprefname, 'classes' => $classes, 'caption' => $caption, 'type' => $captiontype ); } /** * @var string $captionStart opening tag of caption, image/table dependent * @var string $captionEnd closing tag of caption, image/table dependent */ protected $captionStart = ''; protected $captionEnd = ''; /** * Create html of opening of caption wrapper * * @param array $data(caprefname, classes, ..) * @return string html start of caption wrapper */ protected function captionStart($data) { return sprintf( $this->captionStart, $data['type'].'_'.cleanID($data['caprefname']), $data['classes'], (strpos($data['classes'], 'center') == false ? '':' center') //needed for tabcaptionbox ); } /** * Create html of closing of caption wrapper * * @param array $data(caprefname, refnumber, caption, ..) Caption data * @return string html caption wrapper */ protected function captionEnd($data) { return '' .$this->getLang($data['type'].'short').' '.$data['refnumber'] .($data['caption'] ? ': ' . hsc($data['caption']) : '') .'' .'' . $this->captionEnd; } }