1<?php 2/** 3 * dw2Pdf Plugin: Conversion from dokuwiki content to pdf. 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Luigi Micco <l.micco@tiscali.it> 7 * @author Andreas Gohr <andi@splitbrain.org> 8 */ 9 10// must be run within Dokuwiki 11if(!defined('DOKU_INC')) die(); 12 13/** 14 * Class action_plugin_dw2pdf 15 * 16 * Export hmtl content to pdf, for different url parameter configurations 17 * DokuPDF which extends mPDF is used for generating the pdf from html. 18 */ 19class action_plugin_dw2pdf extends DokuWiki_Action_Plugin { 20 /** 21 * Settings for current export, collected from url param, plugin config, global config 22 * 23 * @var array 24 */ 25 protected $exportConfig = null; 26 protected $tpl; 27 protected $title; 28 protected $list = array(); 29 30 /** 31 * Constructor. Sets the correct template 32 * 33 * @param string $title 34 */ 35 public function __construct($title=null) { 36 $this->tpl = $this->getExportConfig('template'); 37 $this->title = $title ? $title : ''; 38 } 39 40 /** 41 * Register the events 42 * 43 * @param Doku_Event_Handler $controller 44 */ 45 public function register(Doku_Event_Handler $controller) { 46 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'convert', array()); 47 $controller->register_hook('TEMPLATE_PAGETOOLS_DISPLAY', 'BEFORE', $this, 'addbutton', array()); 48 $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'addsvgbutton', array()); 49 } 50 51 /** 52 * Do the HTML to PDF conversion work 53 * 54 * @param Doku_Event $event 55 * @return bool 56 */ 57 public function convert(Doku_Event $event) { 58 global $ID, $REV, $DATE_AT; 59 global $conf, $INPUT; 60 61 // our event? 62 if(($event->data != 'export_pdfbook') && ($event->data != 'export_pdf') && ($event->data != 'export_pdfns')) return false; 63 64 // check user's rights 65 if(auth_quickaclcheck($ID) < AUTH_READ) return false; 66 67 if($data = $this->collectExportPages($event)) { 68 list($this->title, $this->list) = $data; 69 } else { 70 return false; 71 } 72 73 if($event->data === 'export_pdf' && ($REV || $DATE_AT)) { 74 $tempFilename = tempnam($conf['tmpdir'], 'dw2pdf_'); 75 $generateNewPdf = true; 76 $isTempFile = true; 77 } else { 78 // prepare cache and its dependencies 79 $depends = array(); 80 $cache = $this->prepareCache($depends); 81 $tempFilename = $cache->cache; 82 $generateNewPdf = !$this->getConf('usecache') 83 || $this->getExportConfig('isDebug') 84 || !$cache->useCache($depends); 85 $isTempFile = false; 86 } 87 88 // hard work only when no cache available or needed for debugging 89 if($generateNewPdf) { 90 // generating the pdf may take a long time for larger wikis / namespaces with many pages 91 set_time_limit(0); 92 try { 93 $this->generatePDF($tempFilename, $event); 94 } catch(Mpdf\MpdfException $e) { 95 if($INPUT->has('selection')) { 96 http_status(400); 97 print $e->getMessage(); 98 exit(); 99 } else { 100 //prevent act_export() 101 $event->data = 'show'; 102 msg($e->getMessage(), -1); 103 $_SERVER['REQUEST_METHOD'] = 'POST'; //clears url 104 return false; 105 } 106 } 107 } 108 109 $event->preventDefault(); // after prevent, $event->data cannot be changed 110 111 // deliver the file 112 $this->sendPDFFile($tempFilename); 113 if($isTempFile) { 114 unlink($tempFilename); 115 } 116 117 return true; 118 } 119 120 /** 121 * Obtain list of pages and title, based on url parameters 122 * 123 * @param Doku_Event $event 124 * @return string|bool 125 */ 126 protected function collectExportPages(Doku_Event $event) { 127 global $ID, $REV; 128 global $INPUT; 129 global $conf; 130 131 // list of one or multiple pages 132 $list = array(); 133 134 if($event->data == 'export_pdf') { 135 $list[0] = $ID; 136 $this->title = $INPUT->str('pdftitle'); //DEPRECATED 137 $this->title = $INPUT->str('book_title', $this->title, true); 138 if(empty($this->title)) { 139 $this->title = p_get_first_heading($ID); 140 } 141 142 $filename = wikiFN($ID, $REV); 143 if(!file_exists($filename)) { 144 $this->showPageWithErrorMsg($event, 'notexist'); 145 return false; 146 } 147 148 } elseif($event->data == 'export_pdfns') { 149 //check input for title and ns 150 if(!$this->title = $INPUT->str('book_title')) { 151 $this->showPageWithErrorMsg($event, 'needtitle'); 152 return false; 153 } 154 $pdfnamespace = cleanID($INPUT->str('book_ns')); 155 if(!@is_dir(dirname(wikiFN($pdfnamespace . ':dummy')))) { 156 $this->showPageWithErrorMsg($event, 'needns'); 157 return false; 158 } 159 160 //sort order 161 $order = $INPUT->str('book_order', 'natural', true); 162 $sortoptions = array('pagename', 'date', 'natural'); 163 if(!in_array($order, $sortoptions)) { 164 $order = 'natural'; 165 } 166 167 //search depth 168 $depth = $INPUT->int('book_nsdepth', 0); 169 if($depth < 0) { 170 $depth = 0; 171 } 172 173 //page search 174 $result = array(); 175 $opts = array('depth' => $depth); //recursive all levels 176 $dir = utf8_encodeFN(str_replace(':', '/', $pdfnamespace)); 177 search($result, $conf['datadir'], 'search_allpages', $opts, $dir); 178 179 //sorting 180 if(count($result) > 0) { 181 if($order == 'date') { 182 usort($result, array($this, '_datesort')); 183 } elseif($order == 'pagename') { 184 usort($result, array($this, '_pagenamesort')); 185 } 186 } 187 188 foreach($result as $item) { 189 $list[] = $item['id']; 190 } 191 192 if ($pdfnamespace !== '') { 193 if (!in_array($pdfnamespace . ':' . $conf['start'], $list, true)) { 194 if (file_exists(wikiFN(rtrim($pdfnamespace,':')))) { 195 array_unshift($list,rtrim($pdfnamespace,':')); 196 } 197 } 198 } 199 200 } elseif(isset($_COOKIE['list-pagelist']) && !empty($_COOKIE['list-pagelist'])) { 201 /** @deprecated April 2016 replaced by localStorage version of Bookcreator*/ 202 //is in Bookmanager of bookcreator plugin a title given? 203 $this->title = $INPUT->str('pdfbook_title'); //DEPRECATED 204 $this->title = $INPUT->str('book_title', $this->title, true); 205 if(empty($this->title)) { 206 $this->showPageWithErrorMsg($event, 'needtitle'); 207 return false; 208 } else { 209 $list = explode("|", $_COOKIE['list-pagelist']); 210 } 211 212 } elseif($INPUT->has('selection')) { 213 //handle Bookcreator requests based at localStorage 214// if(!checkSecurityToken()) { 215// http_status(403); 216// print $this->getLang('empty'); 217// exit(); 218// } 219 220 $json = new JSON(JSON_LOOSE_TYPE); 221 $list = $json->decode($INPUT->post->str('selection', '', true)); 222 if(!is_array($list) || empty($list)) { 223 http_status(400); 224 print $this->getLang('empty'); 225 exit(); 226 } 227 228 $this->title = $INPUT->str('pdfbook_title'); //DEPRECATED 229 $this->title = $INPUT->str('book_title', $this->title, true); 230 if(empty($this->title)) { 231 http_status(400); 232 print $this->getLang('needtitle'); 233 exit(); 234 } 235 236 } else { 237 //show empty bookcreator message 238 $this->showPageWithErrorMsg($event, 'empty'); 239 return false; 240 } 241 242 $list = array_map('cleanID', $list); 243 244 $skippedpages = array(); 245 foreach($list as $index => $pageid) { 246 if(auth_quickaclcheck($pageid) < AUTH_READ) { 247 $skippedpages[] = $pageid; 248 unset($list[$index]); 249 } 250 } 251 $list = array_filter($list); //removes also pages mentioned '0' 252 253 //if selection contains forbidden pages throw (overridable) warning 254 if(!$INPUT->bool('book_skipforbiddenpages') && !empty($skippedpages)) { 255 $msg = hsc(join(', ', $skippedpages)); 256 if($INPUT->has('selection')) { 257 http_status(400); 258 print sprintf($this->getLang('forbidden'), $msg); 259 exit(); 260 } else { 261 $this->showPageWithErrorMsg($event, 'forbidden', $msg); 262 return false; 263 } 264 265 } 266 267 return array($this->title, $list); 268 } 269 270 /** 271 * Prepare cache 272 * 273 * @param array $depends (reference) array with dependencies 274 * @return cache 275 */ 276 protected function prepareCache(&$depends) { 277 global $REV; 278 279 $cachekey = join(',', $this->list) 280 . $REV 281 . $this->getExportConfig('template') 282 . $this->getExportConfig('pagesize') 283 . $this->getExportConfig('orientation') 284 . $this->getExportConfig('font-size') 285 . $this->getExportConfig('doublesided') 286 . ($this->getExportConfig('hasToC') ? join('-', $this->getExportConfig('levels')) : '0') 287 . $this->title; 288 $cache = new cache($cachekey, '.dw2.pdf'); 289 290 $dependencies = array(); 291 foreach($this->list as $pageid) { 292 $relations = p_get_metadata($pageid, 'relation'); 293 294 if(is_array($relations)) { 295 if(array_key_exists('media', $relations) && is_array($relations['media'])) { 296 foreach($relations['media'] as $mediaid => $exists) { 297 if($exists) { 298 $dependencies[] = mediaFN($mediaid); 299 } 300 } 301 } 302 303 if(array_key_exists('haspart', $relations) && is_array($relations['haspart'])) { 304 foreach($relations['haspart'] as $part_pageid => $exists) { 305 if($exists) { 306 $dependencies[] = wikiFN($part_pageid); 307 } 308 } 309 } 310 } 311 312 $dependencies[] = metaFN($pageid, '.meta'); 313 } 314 315 $depends['files'] = array_map('wikiFN', $this->list); 316 $depends['files'][] = __FILE__; 317 $depends['files'][] = dirname(__FILE__) . '/renderer.php'; 318 $depends['files'][] = dirname(__FILE__) . '/mpdf/mpdf.php'; 319 $depends['files'] = array_merge( 320 $depends['files'], 321 $dependencies, 322 getConfigFiles('main') 323 ); 324 return $cache; 325 } 326 327 /** 328 * Set error notification and reload page again 329 * 330 * @param Doku_Event $event 331 * @param string $msglangkey key of translation key 332 * @param string $replacement 333 */ 334 private function showPageWithErrorMsg(Doku_Event $event, $msglangkey, $replacement=null) { 335 if(empty($replacement)) { 336 $msg = $this->getLang($msglangkey); 337 } else { 338 $msg = sprintf($this->getLang($msglangkey), $replacement); 339 } 340 msg($msg, -1); 341 342 $event->data = 'show'; 343 $_SERVER['REQUEST_METHOD'] = 'POST'; //clears url 344 } 345 346 /** 347 * Returns the parsed Wikitext in dw2pdf for the given id and revision 348 * 349 * @param string $id page id 350 * @param string|int $rev revision timestamp or empty string 351 * @param string $date_at 352 * @return null|string 353 */ 354 protected function p_wiki_dw2pdf($id, $rev = '', $date_at = '') { 355 $file = wikiFN($id, $rev); 356 357 if(!file_exists($file)) return ''; 358 359 //ensure $id is in global $ID (needed for parsing) 360 global $ID; 361 $keep = $ID; 362 $ID = $id; 363 364 if($rev || $date_at) { 365 $ret = p_render('dw2pdf', p_get_instructions(io_readWikiPage($file, $id, $rev)), $info, $date_at); //no caching on old revisions 366 } else { 367 $ret = p_cached_output($file, 'dw2pdf', $id); 368 } 369 370 //restore ID (just in case) 371 $ID = $keep; 372 373 return $ret; 374 } 375 376 /** 377 * Build a pdf from the html 378 * 379 * @param string $cachefile 380 */ 381 protected function generatePDF($cachefile, $event) { 382 global $REV, $INPUT, $DATE_AT; 383 384 if ($event->data == 'export_pdf') { //only one page is exported 385 $rev = $REV; 386 $date_at = $DATE_AT; 387 } else { //we are exporting entre namespace, ommit revisions 388 $rev = $date_at = ''; 389 } 390 391 //some shortcuts to export settings 392 $hasToC = $this->getExportConfig('hasToC'); 393 $levels = $this->getExportConfig('levels'); 394 $isDebug = $this->getExportConfig('isDebug'); 395 396 // initialize PDF library 397 require_once(dirname(__FILE__) . "/DokuPDF.class.php"); 398 399 $mpdf = new DokuPDF($this->getExportConfig('pagesize'), 400 $this->getExportConfig('orientation'), 401 $this->getExportConfig('font-size')); 402 403 // let mpdf fix local links 404 $self = parse_url(DOKU_URL); 405 $url = $self['scheme'] . '://' . $self['host']; 406 if($self['port']) { 407 $url .= ':' . $self['port']; 408 } 409 $mpdf->setBasePath($url); 410 411 // Set the title 412 $mpdf->SetTitle($this->title); 413 414 // some default document settings 415 //note: double-sided document, starts at an odd page (first page is a right-hand side page) 416 // single-side document has only odd pages 417 $mpdf->mirrorMargins = $this->getExportConfig('doublesided'); 418 $mpdf->setAutoTopMargin = 'stretch'; 419 $mpdf->setAutoBottomMargin = 'stretch'; 420// $mpdf->pagenumSuffix = '/'; //prefix for {nbpg} 421 if($hasToC) { 422 $mpdf->PageNumSubstitutions[] = array('from' => 1, 'reset' => 0, 'type' => 'i', 'suppress' => 'off'); //use italic pageno until ToC 423 $mpdf->h2toc = $levels; 424 } else { 425 $mpdf->PageNumSubstitutions[] = array('from' => 1, 'reset' => 0, 'type' => '1', 'suppress' => 'off'); 426 } 427 428 // load the template 429 $template = $this->load_template(); 430 431 // prepare HTML header styles 432 $html = ''; 433 if($isDebug) { 434 $html .= '<html><head>'; 435 $html .= '<style type="text/css">'; 436 } 437 438 $styles = '@page { size:auto; ' . $template['page'] . '}'; 439 $styles .= '@page :first {' . $template['first'] . '}'; 440 441 $styles .= '@page landscape-page { size:landscape }'; 442 $styles .= 'div.dw2pdf-landscape { page:landscape-page }'; 443 $styles .= '@page portrait-page { size:portrait }'; 444 $styles .= 'div.dw2pdf-portrait { page:portrait-page }'; 445 $styles .= $this->load_css(); 446 447 $mpdf->WriteHTML($styles, 1); 448 449 if($isDebug) { 450 $html .= $styles; 451 $html .= '</style>'; 452 $html .= '</head><body>'; 453 } 454 455 $body_start = $template['html']; 456 $body_start .= '<div class="dokuwiki">'; 457 458 // insert the cover page 459 $body_start .= $template['cover']; 460 461 $mpdf->WriteHTML($body_start, 2, true, false); //start body html 462 if($isDebug) { 463 $html .= $body_start; 464 } 465 if($hasToC) { 466 //Note: - for double-sided document the ToC is always on an even number of pages, so that the following content is on a correct odd/even page 467 // - first page of ToC starts always at odd page (so eventually an additional blank page is included before) 468 // - there is no page numbering at the pages of the ToC 469 $mpdf->TOCpagebreakByArray( 470 array( 471 'toc-preHTML' => '<h2>' . $this->getLang('tocheader') . '</h2>', 472 'toc-bookmarkText' => $this->getLang('tocheader'), 473 'links' => true, 474 'outdent' => '1em', 475 'resetpagenum' => true, //start pagenumbering after ToC 476 'pagenumstyle' => '1' 477 ) 478 ); 479 $html .= '<tocpagebreak>'; 480 } 481 482 // loop over all pages 483 $counter = 0; 484 $no_pages = count($this->list); 485 foreach($this->list as $page) { 486 $counter++; 487 488 $pagehtml = $this->p_wiki_dw2pdf($page, $rev, $date_at); 489 //file doesn't exists 490 if($pagehtml == '') { 491 continue; 492 } 493 $pagehtml .= $this->page_depend_replacements($template['cite'], $page); 494 if($counter < $no_pages) { 495 $pagehtml .= '<pagebreak />'; 496 } 497 498 $mpdf->WriteHTML($pagehtml, 2, false, false); //intermediate body html 499 if($isDebug) { 500 $html .= $pagehtml; 501 } 502 } 503 504 // insert the back page 505 $body_end = $template['back']; 506 507 $body_end .= '</div>'; 508 509 $mpdf->WriteHTML($body_end, 2, false, true); // finish body html 510 if($isDebug) { 511 $html .= $body_end; 512 $html .= '</body>'; 513 $html .= '</html>'; 514 } 515 516 //Return html for debugging 517 if($isDebug) { 518 if($INPUT->str('debughtml', 'text', true) == 'html') { 519 echo $html; 520 } else { 521 header('Content-Type: text/plain; charset=utf-8'); 522 echo $html; 523 } 524 exit(); 525 }; 526 527 // write to cache file 528 $mpdf->Output($cachefile, 'F'); 529 } 530 531 /** 532 * @param string $cachefile 533 */ 534 protected function sendPDFFile($cachefile) { 535 header('Content-Type: application/pdf'); 536 header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0'); 537 header('Pragma: public'); 538 http_conditionalRequest(filemtime($cachefile)); 539 global $INPUT; 540 $outputTarget = $INPUT->str('outputTarget', $this->getConf('output')); 541 542 $filename = rawurlencode(cleanID(strtr($this->title, ':/;"', ' '))); 543 if($outputTarget === 'file') { 544 header('Content-Disposition: attachment; filename="' . $filename . '.pdf";'); 545 } else { 546 header('Content-Disposition: inline; filename="' . $filename . '.pdf";'); 547 } 548 549 //Bookcreator uses jQuery.fileDownload.js, which requires a cookie. 550 header('Set-Cookie: fileDownload=true; path=/'); 551 552 //try to send file, and exit if done 553 http_sendfile($cachefile); 554 555 $fp = @fopen($cachefile, "rb"); 556 if($fp) { 557 http_rangeRequest($fp, filesize($cachefile), 'application/pdf'); 558 } else { 559 header("HTTP/1.0 500 Internal Server Error"); 560 print "Could not read file - bad permissions?"; 561 } 562 exit(); 563 } 564 565 /** 566 * Load the various template files and prepare the HTML/CSS for insertion 567 * 568 * @return array 569 */ 570 protected function load_template() { 571 global $ID; 572 global $conf; 573 574 // this is what we'll return 575 $output = array( 576 'cover' => '', 577 'html' => '', 578 'page' => '', 579 'first' => '', 580 'cite' => '', 581 ); 582 583 // prepare header/footer elements 584 $html = ''; 585 foreach(array('header', 'footer') as $section) { 586 foreach(array('', '_odd', '_even', '_first') as $order) { 587 $file = DOKU_PLUGIN . 'dw2pdf/tpl/' . $this->tpl . '/' . $section . $order . '.html'; 588 if(file_exists($file)) { 589 $html .= '<htmlpage' . $section . ' name="' . $section . $order . '">' . DOKU_LF; 590 $html .= file_get_contents($file) . DOKU_LF; 591 $html .= '</htmlpage' . $section . '>' . DOKU_LF; 592 593 // register the needed pseudo CSS 594 if($order == '_first') { 595 $output['first'] .= $section . ': html_' . $section . $order . ';' . DOKU_LF; 596 } elseif($order == '_even') { 597 $output['page'] .= 'even-' . $section . '-name: html_' . $section . $order . ';' . DOKU_LF; 598 } elseif($order == '_odd') { 599 $output['page'] .= 'odd-' . $section . '-name: html_' . $section . $order . ';' . DOKU_LF; 600 } else { 601 $output['page'] .= $section . ': html_' . $section . $order . ';' . DOKU_LF; 602 } 603 } 604 } 605 } 606 607 // prepare replacements 608 $replace = array( 609 '@PAGE@' => '{PAGENO}', 610 '@PAGES@' => '{nbpg}', //see also $mpdf->pagenumSuffix = ' / ' 611 '@TITLE@' => hsc($this->title), 612 '@WIKI@' => $conf['title'], 613 '@WIKIURL@' => DOKU_URL, 614 '@DATE@' => dformat(time()), 615 '@BASE@' => DOKU_BASE, 616 '@INC@' => DOKU_INC, 617 '@TPLBASE@' => DOKU_BASE . 'lib/plugins/dw2pdf/tpl/' . $this->tpl . '/', 618 '@TPLINC@' => DOKU_INC . 'lib/plugins/dw2pdf/tpl/' . $this->tpl . '/' 619 ); 620 621 // set HTML element 622 $html = str_replace(array_keys($replace), array_values($replace), $html); 623 //TODO For bookcreator $ID (= bookmanager page) makes no sense 624 $output['html'] = $this->page_depend_replacements($html, $ID); 625 626 // cover page 627 $coverfile = DOKU_PLUGIN . 'dw2pdf/tpl/' . $this->tpl . '/cover.html'; 628 if(file_exists($coverfile)) { 629 $output['cover'] = file_get_contents($coverfile); 630 $output['cover'] = str_replace(array_keys($replace), array_values($replace), $output['cover']); 631 $output['cover'] = $this->page_depend_replacements($output['cover'], $ID); 632 $output['cover'] .= '<pagebreak />'; 633 } 634 635 // cover page 636 $backfile = DOKU_PLUGIN . 'dw2pdf/tpl/' . $this->tpl . '/back.html'; 637 if(file_exists($backfile)) { 638 $output['back'] = '<pagebreak />'; 639 $output['back'] .= file_get_contents($backfile); 640 $output['back'] = str_replace(array_keys($replace), array_values($replace), $output['back']); 641 $output['back'] = $this->page_depend_replacements($output['back'], $ID); 642 } 643 644 // citation box 645 $citationfile = DOKU_PLUGIN . 'dw2pdf/tpl/' . $this->tpl . '/citation.html'; 646 if(file_exists($citationfile)) { 647 $output['cite'] = file_get_contents($citationfile); 648 $output['cite'] = str_replace(array_keys($replace), array_values($replace), $output['cite']); 649 } 650 651 return $output; 652 } 653 654 /** 655 * @param string $raw code with placeholders 656 * @param string $id pageid 657 * @return string 658 */ 659 protected function page_depend_replacements($raw, $id) { 660 global $REV, $DATE_AT; 661 662 // generate qr code for this page using google infographics api 663 $qr_code = ''; 664 if($this->getConf('qrcodesize')) { 665 $url = urlencode(wl($id, '', '&', true)); 666 $qr_code = '<img src="https://chart.googleapis.com/chart?chs=' . 667 $this->getConf('qrcodesize') . '&cht=qr&chl=' . $url . '" />'; 668 } 669 // prepare replacements 670 $replace['@ID@'] = $id; 671 $replace['@UPDATE@'] = dformat(filemtime(wikiFN($id, $REV))); 672 673 $params = array(); 674 if($DATE_AT) { 675 $params['at'] = $DATE_AT; 676 } elseif($REV) { 677 $params['rev'] = $REV; 678 } 679 $replace['@PAGEURL@'] = wl($id, $params, true, "&"); 680 $replace['@QRCODE@'] = $qr_code; 681 682 $content = str_replace(array_keys($replace), array_values($replace), $raw); 683 684 // @DATE(<date>[, <format>])@ 685 $content = preg_replace_callback( 686 '/@DATE\((.*?)(?:,\s*(.*?))?\)@/', 687 array($this, 'replacedate'), 688 $content 689 ); 690 691 return $content; 692 } 693 694 695 /** 696 * (callback) Replace date by request datestring 697 * e.g. '%m(30-11-1975)' is replaced by '11' 698 * 699 * @param array $match with [0]=>whole match, [1]=> first subpattern, [2] => second subpattern 700 * @return string 701 */ 702 function replacedate($match) { 703 global $conf; 704 //no 2nd argument for default date format 705 if($match[2] == null) { 706 $match[2] = $conf['dformat']; 707 } 708 return strftime($match[2], strtotime($match[1])); 709 } 710 711 /** 712 * Load all the style sheets and apply the needed replacements 713 */ 714 protected function load_css() { 715 global $conf; 716 //reusue the CSS dispatcher functions without triggering the main function 717 define('SIMPLE_TEST', 1); 718 require_once(DOKU_INC . 'lib/exe/css.php'); 719 720 // prepare CSS files 721 $files = array_merge( 722 array( 723 DOKU_INC . 'lib/styles/screen.css' 724 => DOKU_BASE . 'lib/styles/', 725 DOKU_INC . 'lib/styles/print.css' 726 => DOKU_BASE . 'lib/styles/', 727 ), 728 $this->css_pluginPDFstyles(), 729 array( 730 DOKU_PLUGIN . 'dw2pdf/conf/style.css' 731 => DOKU_BASE . 'lib/plugins/dw2pdf/conf/', 732 DOKU_PLUGIN . 'dw2pdf/tpl/' . $this->tpl . '/style.css' 733 => DOKU_BASE . 'lib/plugins/dw2pdf/tpl/' . $this->tpl . '/', 734 DOKU_PLUGIN . 'dw2pdf/conf/style.local.css' 735 => DOKU_BASE . 'lib/plugins/dw2pdf/conf/', 736 ) 737 ); 738 $css = ''; 739 foreach($files as $file => $location) { 740 $display = str_replace(fullpath(DOKU_INC), '', fullpath($file)); 741 $css .= "\n/* XXXXXXXXX $display XXXXXXXXX */\n"; 742 $css .= css_loadfile($file, $location); 743 } 744 745 if(function_exists('css_parseless')) { 746 // apply pattern replacements 747 $styleini = css_styleini($conf['template']); 748 $css = css_applystyle($css, $styleini['replacements']); 749 750 // parse less 751 $css = css_parseless($css); 752 } else { 753 // @deprecated 2013-12-19: fix backward compatibility 754 $css = css_applystyle($css, DOKU_INC . 'lib/tpl/' . $conf['template'] . '/'); 755 } 756 757 return $css; 758 } 759 760 /** 761 * Returns a list of possible Plugin PDF Styles 762 * 763 * Checks for a pdf.css, falls back to print.css 764 * 765 * @author Andreas Gohr <andi@splitbrain.org> 766 */ 767 protected function css_pluginPDFstyles() { 768 $list = array(); 769 $plugins = plugin_list(); 770 771 $usestyle = explode(',', $this->getConf('usestyles')); 772 foreach($plugins as $p) { 773 if(in_array($p, $usestyle)) { 774 $list[DOKU_PLUGIN . "$p/screen.css"] = DOKU_BASE . "lib/plugins/$p/"; 775 $list[DOKU_PLUGIN . "$p/screen.less"] = DOKU_BASE . "lib/plugins/$p/"; 776 777 $list[DOKU_PLUGIN . "$p/style.css"] = DOKU_BASE . "lib/plugins/$p/"; 778 $list[DOKU_PLUGIN . "$p/style.less"] = DOKU_BASE . "lib/plugins/$p/"; 779 } 780 781 $list[DOKU_PLUGIN . "$p/all.css"] = DOKU_BASE . "lib/plugins/$p/"; 782 $list[DOKU_PLUGIN . "$p/all.less"] = DOKU_BASE . "lib/plugins/$p/"; 783 784 if(file_exists(DOKU_PLUGIN . "$p/pdf.css")) { 785 $list[DOKU_PLUGIN . "$p/pdf.css"] = DOKU_BASE . "lib/plugins/$p/"; 786 $list[DOKU_PLUGIN . "$p/pdf.less"] = DOKU_BASE . "lib/plugins/$p/"; 787 } else { 788 $list[DOKU_PLUGIN . "$p/print.css"] = DOKU_BASE . "lib/plugins/$p/"; 789 $list[DOKU_PLUGIN . "$p/print.less"] = DOKU_BASE . "lib/plugins/$p/"; 790 } 791 } 792 return $list; 793 } 794 795 /** 796 * Returns array of pages which will be included in the exported pdf 797 * 798 * @return array 799 */ 800 public function getExportedPages() { 801 return $this->list; 802 } 803 804 /** 805 * usort callback to sort by file lastmodified time 806 * 807 * @param array $a 808 * @param array $b 809 * @return int 810 */ 811 public function _datesort($a, $b) { 812 if($b['rev'] < $a['rev']) return -1; 813 if($b['rev'] > $a['rev']) return 1; 814 return strcmp($b['id'], $a['id']); 815 } 816 817 /** 818 * usort callback to sort by page id 819 * @param array $a 820 * @param array $b 821 * @return int 822 */ 823 public function _pagenamesort($a, $b) { 824 // do not sort numbers before namespace separators 825 $aID = str_replace(':', '/', $a['id']); 826 $bID = str_replace(':', '/', $b['id']); 827 if($aID <= $bID) return -1; 828 if($aID > $bID) return 1; 829 return 0; 830 } 831 832 /** 833 * Return settings read from: 834 * 1. url parameters 835 * 2. plugin config 836 * 3. global config 837 * 838 * @return array 839 */ 840 protected function loadExportConfig() { 841 global $INPUT; 842 global $conf; 843 844 $this->exportConfig = array(); 845 846 // decide on the paper setup from param or config 847 $this->exportConfig['pagesize'] = $INPUT->str('pagesize', $this->getConf('pagesize'), true); 848 $this->exportConfig['orientation'] = $INPUT->str('orientation', $this->getConf('orientation'), true); 849 850 // decide on the font-size from param or config 851 $this->exportConfig['font-size'] = $INPUT->str('font-size', $this->getConf('font-size'), true); 852 853 $doublesided = $INPUT->bool('doublesided', (bool) $this->getConf('doublesided')); 854 $this->exportConfig['doublesided'] = $doublesided ? '1' : '0'; 855 856 $hasToC = $INPUT->bool('toc', (bool) $this->getConf('toc')); 857 $levels = array(); 858 if($hasToC) { 859 $toclevels = $INPUT->str('toclevels', $this->getConf('toclevels'), true); 860 list($top_input, $max_input) = explode('-', $toclevels, 2); 861 list($top_conf, $max_conf) = explode('-', $this->getConf('toclevels'), 2); 862 $bounds_input = array( 863 'top' => array( 864 (int) $top_input, 865 (int) $top_conf 866 ), 867 'max' => array( 868 (int) $max_input, 869 (int) $max_conf 870 ) 871 ); 872 $bounds = array( 873 'top' => $conf['toptoclevel'], 874 'max' => $conf['maxtoclevel'] 875 876 ); 877 foreach($bounds_input as $bound => $values) { 878 foreach($values as $value) { 879 if($value > 0 && $value <= 5) { 880 //stop at valid value and store 881 $bounds[$bound] = $value; 882 break; 883 } 884 } 885 } 886 887 if($bounds['max'] < $bounds['top']) { 888 $bounds['max'] = $bounds['top']; 889 } 890 891 for($level = $bounds['top']; $level <= $bounds['max']; $level++) { 892 $levels["H$level"] = $level - 1; 893 } 894 } 895 $this->exportConfig['hasToC'] = $hasToC; 896 $this->exportConfig['levels'] = $levels; 897 898 $this->exportConfig['maxbookmarks'] = $INPUT->int('maxbookmarks', $this->getConf('maxbookmarks'), true); 899 900 $tplconf = $this->getConf('template'); 901 $tpl = $INPUT->str('tpl', $tplconf, true); 902 if(!is_dir(DOKU_PLUGIN . 'dw2pdf/tpl/' . $tpl)) { 903 $tpl = $tplconf; 904 } 905 if(!$tpl){ 906 $tpl = 'default'; 907 } 908 $this->exportConfig['template'] = $tpl; 909 910 $this->exportConfig['isDebug'] = $conf['allowdebug'] && $INPUT->has('debughtml'); 911 } 912 913 /** 914 * Returns requested config 915 * 916 * @param string $name 917 * @param mixed $notset 918 * @return mixed|bool 919 */ 920 public function getExportConfig($name, $notset = false) { 921 if ($this->exportConfig === null){ 922 $this->loadExportConfig(); 923 } 924 925 if(isset($this->exportConfig[$name])){ 926 return $this->exportConfig[$name]; 927 }else{ 928 return $notset; 929 } 930 } 931 932 /** 933 * Add 'export pdf'-button to pagetools 934 * 935 * @param Doku_Event $event 936 */ 937 public function addbutton(Doku_Event $event) { 938 global $ID, $REV, $DATE_AT; 939 940 if($this->getConf('showexportbutton') && $event->data['view'] == 'main') { 941 $params = array('do' => 'export_pdf'); 942 if($DATE_AT) { 943 $params['at'] = $DATE_AT; 944 } elseif($REV) { 945 $params['rev'] = $REV; 946 } 947 948 // insert button at position before last (up to top) 949 $event->data['items'] = array_slice($event->data['items'], 0, -1, true) + 950 array('export_pdf' => 951 '<li>' 952 . '<a href="' . wl($ID, $params) . '" class="action export_pdf" rel="nofollow" title="' . $this->getLang('export_pdf_button') . '">' 953 . '<span>' . $this->getLang('export_pdf_button') . '</span>' 954 . '</a>' 955 . '</li>' 956 ) + 957 array_slice($event->data['items'], -1, 1, true); 958 } 959 } 960 961 /** 962 * Add 'export pdf' button to page tools, new SVG based mechanism 963 * 964 * @param Doku_Event $event 965 */ 966 public function addsvgbutton(Doku_Event $event) { 967 global $INFO; 968 if($event->data['view'] != 'page' || !$this->getConf('showexportbutton')) { 969 return; 970 } 971 972 if(!$INFO['exists']) { 973 return; 974 } 975 976 array_splice($event->data['items'], -1, 0, [new \dokuwiki\plugin\dw2pdf\MenuItem()]); 977 } 978} 979