- 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;
}
}