1<?php 2/** 3 * Render Plugin for XHTML without details link for internal images. 4 * 5 * @author i-net software <tools@inetsoftware.de> 6 * @author Gerry Weissbach <gweissbach@inetsoftware.de> 7 */ 8 9if (!defined('DOKU_INC')) die(); 10if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 11 12require_once DOKU_INC . 'inc/parser/xhtml.php'; 13 14 15use dokuwiki\File\PageResolver; 16use dokuwiki\File\MediaResolver; 17 18/** 19 * The Renderer 20 */ 21class renderer_plugin_siteexport_pdf extends Doku_Renderer_xhtml { 22 23 public $acronymsExchanged = null; 24 25 private $hasSeenHeader = false; 26 27 private $currentLevel = 0; 28 29 public $levels = array( '======'=>1, 30 '====='=>2, 31 '===='=>3, 32 '==='=>4, 33 '=='=>5 34 ); 35 36 public $info = array( 37 'cache' => true, // may the rendered result cached? 38 'toc' => true, // render the TOC? 39 'forceTOC' => false, // shall I force the TOC? 40 'scriptmode' => false, // In scriptmode, some tags will not be encoded => '<%', '%>' 41 ); 42 43 public $headingCount = array( 1=>0, 44 2=>0, 45 3=>0, 46 4=>0, 47 5=>0 48 ); 49 50 public function document_start() { 51 global $TOC, $ID, $INFO; 52 53 parent::document_start(); 54 55 // Cheating in again 56 $newMeta = p_get_metadata($ID, 'description tableofcontents', false); // 2010-10-23 This should be save to use 57 if (!empty($newMeta) && count($newMeta) > 1) { 58 // $TOC = $this->toc = $newMeta; // 2010-08-23 doubled the TOC 59 $TOC = $newMeta; 60 } 61 } 62 63 public function document_end() { 64 65 parent::document_end(); 66 67 // Prepare the TOC 68 global $TOC, $ID; 69 $meta = array(); 70 71 // NOTOC, and no forceTOC 72 if ($this->info['toc'] === false && !($this->info['forceTOC'] || $this->meta['forceTOC'])) { 73 $TOC = $this->toc = array(); 74 $meta['internal']['toc'] = false; 75 $meta['description']['tableofcontents'] = array(); 76 $meta['forceTOC'] = false; 77 78 } else if ($this->info['forceTOC'] || $this->meta['forceTOC'] || (utf8_strlen(strip_tags($this->doc)) >= $this->getConf('documentlengthfortoc') && count($this->toc) > 1)) { 79 $TOC = $this->toc; 80 // This is a little bit like cheating ... but this will force the TOC into the metadata 81 $meta = array(); 82 $meta['internal']['toc'] = true; 83 $meta['forceTOC'] = $this->info['forceTOC'] || $this->meta['forceTOC']; 84 $meta['description']['tableofcontents'] = $TOC; 85 } 86 87 // allways write new metadata 88 p_set_metadata($ID, $meta); 89 $this->doc = preg_replace('#<p( class=".*?")?>\s*</p>#', '', $this->doc); 90 } 91 92 public function header($text, $level, $pos, $returnonly = false) { 93 global $conf; 94 global $ID; 95 global $INFO; 96 97 if ($text) 98 { 99 $hid = $this->_headerToLink($text, true); 100 101 //only add items within configured levels 102 $this->toc_additem($hid, $text, $level); 103 104 // adjust $node to reflect hierarchy of levels 105 $this->node[$level-1]++; 106 if ($level < $this->lastlevel) { 107 for ($i = 0; $i < $this->lastlevel-$level; $i++) { 108 $this->node[$this->lastlevel-$i-1] = 0; 109 } 110 } 111 $this->lastlevel = $level; 112 113 /* There should be no class for "sectioneditX" if there is no edit perm */ 114 if ($INFO['perm'] > AUTH_READ && 115 $level <= $conf['maxseclevel'] && 116 count($this->sectionedits) > 0 && 117 $this->sectionedits[count($this->sectionedits)-1][2] === 'section') { 118 $this->finishSectionEdit($pos-1); 119 } 120 121 $headingNumber = ''; 122 $useNumbered = p_get_metadata($ID, 'usenumberedheading', true); // 2011-02-07 This should be save to use 123 if ($this->getConf('usenumberedheading') || !empty($useNumbered) || !empty($INFO['meta']['usenumberedheading']) || isset($_REQUEST['usenumberedheading'])) { 124 125 // increment the number of the heading 126 $this->headingCount[$level]++; 127 128 // build the actual number 129 for ($i = 1; $i <= 5; $i++) { 130 131 // reset the number of the subheadings 132 if ($i > $level) { 133 $this->headingCount[$i] = 0; 134 } 135 136 // build the number of the heading 137 $headingNumber .= $this->headingCount[$i] . '.'; 138 } 139 140 $headingNumber = preg_replace("/(\.0)+\.?$/", '', $headingNumber) . ' '; 141 } 142 143 // write the header 144 $this->doc .= DOKU_LF.'<h'.$level; 145 $class = array(); 146 if ($INFO['perm'] > AUTH_READ && 147 $level <= $conf['maxseclevel']) { 148 $class[] = $this->startSectionEdit($pos, array( 'target' => 'section', 'name' => $text ) ); 149 } 150 151 if ( !empty($headingNumber) ) { 152 $class[] = 'level' . trim($headingNumber); 153 if ( intval($headingNumber) > 1 ) { 154 $class[] = 'notfirst'; 155 } else { 156 $class[] = 'first'; 157 } 158 } 159 160 if ( !empty($class) ) { 161 $this->doc .= ' class="' . implode(' ', $class) . '"'; 162 } 163 164 $this->doc .= '><a name="'.$hid.'" id="'.$hid.'">'; 165 $this->doc .= $this->_xmlEntities($headingNumber . $text); 166 $this->doc .= "</a></h$level>".DOKU_LF; 167 168 } else if ( $INFO['perm'] > AUTH_READ ) { 169 170 if ( $this->hasSeenHeader ) { 171 $this->finishSectionEdit($pos); 172 } 173 174 // write the header 175 $name = rand() . $level; 176 $sectionEdit = $this->startSectionEdit($pos, array( 'target' => 'section_empty', 'name' => $name)); 177 $this->doc .= DOKU_LF.'<a name="'. $sectionEdit .'" class="' . $sectionEdit . '" ></a>'.DOKU_LF; 178 } 179 180 $this->hasSeenHeader = true; 181 } 182 183 public function section_open($level) { 184 $this->currentLevel = $level; 185 parent::section_open($level); 186 } 187 188 public function p_open() { 189 $this->doc .= DOKU_LF . '<p class="level' . $this->currentLevel . '">' . DOKU_LF; 190 } 191 192 public function listu_open($classes = null) { 193 $this->doc .= '<ul class="level' . $this->currentLevel . '">' . DOKU_LF; 194 } 195 196 public function listo_open($classes = null) { 197 $this->doc .= '<ol class="level' . $this->currentLevel . '">' . DOKU_LF; 198 } 199 200 public function finishSectionEdit($end = null, $hid = null) { 201 return ''; 202 } 203 204 /** 205 * @param string $type 206 */ 207 public function startSectionEdit($start, $data) { 208 return ''; 209 } 210 211 /** 212 * Wrap centered media in a div to center it 213 */ 214 public function _media ($src, $title=NULL, $align=NULL, $width=NULL, 215 $height=NULL, $cache=NULL, $render = true) { 216 217 $out = ''; 218 if($align == 'center'){ 219 $out .= '<div align="center" style="text-align: center">'; 220 } 221 222 $out .= parent::_media ($src, $title, $align, $width, $height, $cache, $render); 223 224 if($align == 'center'){ 225 $out .= '</div>'; 226 } 227 228 return $out; 229 } 230 231 public function internalmedia($src, $title = NULL, $align = NULL, $width = NULL, $height = NULL, $cache = NULL, $linking = NULL, $return = false) { 232 global $ID; 233 list($src,$hash) = explode('#',$src,2); 234 $src = (new MediaResolver(getNS($ID)))->resolveId($src); 235 $exists = media_exists( $src ); 236 237 $noLink = false; 238 $render = ($linking == 'linkonly') ? false : true; 239 $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 240 241 list($ext,$mime,$dl) = mimetype($src); 242 if(substr($mime,0,5) == 'image' && $render){ 243 $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct')); 244 if ( substr($mime,0,5) == 'image' && $linking='details' ) { $noLink = true;} 245 } elseif($mime == 'application/x-shockwave-flash' && $render){ 246 // don't link flash movies 247 $noLink = true; 248 } else{ 249 // add file icons 250 $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext); 251 $link['class'] .= ' mediafile mf_'.$class; 252 $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true); 253 } 254 255 if($hash) { 256 $link['url'] .= '#'.$hash; 257 } 258 259 //markup non existing files 260 if (!$exists) { 261 $link['class'] .= ' wikilink2'; 262 } 263 264 //output formatted 265 if ($linking == 'nolink' || $noLink) { 266 $this->doc .= $link['name']; 267 } else { 268 $this->doc .= $this->_formatLink($link); 269 } 270 } 271 272 /** 273 * Render an internal Wiki Link 274 * 275 * $search,$returnonly & $linktype are not for the renderer but are used 276 * elsewhere - no need to implement them in other renderers 277 * 278 * @author Andreas Gohr <andi@splitbrain.org> 279 */ 280 public function internallink($id, $name = NULL, $search = NULL, $returnonly = false, $linktype = 'content') { 281 global $conf; 282 global $ID; 283 // default name is based on $id as given 284 $default = $this->_simpleTitle($id); 285 286 // now first resolve and clean up the $id 287 $id = (new PageResolver( $ID ))->resolveId($id); 288 $exists = media_exists( $id ); 289 $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype); 290 if (!$isImage) { 291 if ($exists) { 292 $class = 'wikilink1'; 293 } else { 294 $class = 'wikilink2'; 295 $link['rel'] = 'nofollow'; 296 } 297 } else { 298 $class = 'media'; 299 } 300 301 //keep hash anchor 302 list($id, $hash) = explode('#', $id, 2); 303 if (!empty($hash)) $hash = $this->_headerToLink($hash); 304 305 //prepare for formating 306 $link['target'] = $conf['target']['wiki']; 307 $link['style'] = ''; 308 $link['pre'] = ''; 309 $link['suf'] = ''; 310 // highlight link to current page 311 if ($id == $ID) { 312 $link['pre'] = '<span class="curid">'; 313 $link['suf'] = '</span>'; 314 } 315 $link['more'] = ''; 316 $link['class'] = $class; 317 $link['url'] = wl($id); 318 $link['name'] = $name; 319 $link['title'] = $this->_getLinkTitle(null, $default, $isImage, $id, $linktype); 320 321 //add search string 322 if ($search) { 323 ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 324 if (is_array($search)) { 325 $search = array_map('rawurlencode', $search); 326 $link['url'] .= 's[]=' . join('&s[]=', $search); 327 } else { 328 $link['url'] .= 's=' . rawurlencode($search); 329 } 330 } 331 332 //keep hash 333 if ($hash) $link['url'] .= '#' . $hash; 334 335 //output formatted 336 if ($returnonly) { 337 return $this->_formatLink($link); 338 } else { 339 $this->doc .= $this->_formatLink($link); 340 } 341 } 342 343 public function acronym($acronym) { 344 345 if (empty($this->acronymsExchanged)) { 346 $this->acronymsExchanged = $this->acronyms; 347 $this->acronyms = array(); 348 349 foreach ($this->acronymsExchanged as $key => $value) { 350 $this->acronyms[str_replace('_', ' ', $key)] = $value; 351 } 352 } 353 354 parent::acronym($acronym); 355 } 356 357 /** 358 * @param string $string 359 */ 360 public function _xmlEntities($string) { 361 362 $string = parent::_xmlEntities($string); 363 $string = htmlentities($string, 8, 'UTF-8'); 364 $string = $this->superentities($string); 365 366 if ($this->info['scriptmode']) { 367 $string = str_replace(array("<%", "%>", "<?", "?>"), 368 array("<%", "%>", "<?", "?>"), 369 $string); 370 } 371 372 return $string; 373 } 374 375 // Unicode-proof htmlentities. 376 // Returns 'normal' chars as chars and weirdos as numeric html entites. 377 378 /** 379 * @param string $str 380 */ 381 public function superentities( $str ){ 382 // get rid of existing entities else double-escape 383 $str2 = ''; 384 $str = html_entity_decode(stripslashes($str),ENT_QUOTES,'UTF-8'); 385 $ar = preg_split('/(?<!^)(?!$)(?!\n)/u', $str ); // return array of every multi-byte character 386 foreach ($ar as $c){ 387 $o = ord($c); 388 if ( // (strlen($c) > 1) || /* multi-byte [unicode] */ 389 ($o > 127) // || /* <- control / latin weirdos -> */ 390 // ($o <32 || $o > 126) || /* <- control / latin weirdos -> */ 391 // ($o >33 && $o < 40) ||/* quotes + ambersand */ 392 // ($o >59 && $o < 63) /* html */ 393 394 ) { 395 // convert to numeric entity 396 $c = mb_encode_numericentity($c, array(0x0, 0xffff, 0, 0xffff), 'UTF-8'); 397 } 398 $str2 .= $c; 399 } 400 return $str2; 401 } 402 403 public function preformatted($text) { 404 $this->doc .= '<div class="pre">'; 405 parent::preformatted($text); 406 $this->doc .= '</div>'; 407 } 408 409 public function _highlight($type, $text, $language = NULL, $filename = NULL, $options = NULL) { 410 $this->doc .= '<div class="pre">'; 411 parent::_highlight($type, $text, $language, $filename, $options); 412 $this->doc .= '</div>'; 413 } 414 415 /** 416 * API of the imagereference plugin 417 * https://github.com/i-net-software/dokuwiki-plugin-imagereference 418 * 419 * Allows to specify special imagecaption tags that the renderer (mpdf) can use 420 */ 421 public function imageCaptionTags(&$imagereferenceplugin) 422 { 423 if ( !$imagereferenceplugin->accepts('table') ) { 424 return array( '<figure id="%s" class="imgcaption%s">', // $captionStart 425 '</figure>', // $captionEnd 426 '<figcaption class="undercaption">', // $underCaptionStart 427 '</figcaption>' // $underCaptionEnd 428 ); 429 } 430 431 return null; 432 } 433 434 /** 435 * Render a page local link 436 * 437 * @param string $hash hash link identifier 438 * @param string $name name for the link 439 * @param bool $returnonly whether to return html or write to doc attribute 440 * @return void|string writes to doc attribute or returns html depends on $returnonly 441 */ 442 public function locallink($hash, $name = null, $returnonly = false) { 443 global $ID; 444 $name = $this->_getLinkTitle($name, $hash, $isImage); 445 $hash = $this->_headerToLink($hash); 446 $title = $name; 447 448 $doc = '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 449 $doc .= $name; 450 $doc .= '</a>'; 451 452 if($returnonly) { 453 return $doc; 454 } else { 455 $this->doc .= $doc; 456 } 457 } 458} 459 460//Setup VIM: ex: et ts=4 enc=utf-8 : 461