1<?php 2 3namespace dokuwiki\plugin\dw2pdf\src; 4 5use Mpdf\MpdfException; 6 7/** 8 * Coordinates PDF generation and delivery 9 */ 10class PdfExportService 11{ 12 /** @var Config */ 13 protected Config $config; 14 15 /** @var AbstractCollector */ 16 protected AbstractCollector $collector; 17 18 /** @var Cache */ 19 protected Cache $cache; 20 21 /** @var string */ 22 protected string $tocHeader; 23 24 /** @var string */ 25 protected string $remoteUser; 26 27 /** 28 * @param Config $config Active configuration controlling the export 29 * @param AbstractCollector $collector Collector providing the export pages and metadata 30 * @param Cache $cache Cache wrapper governing reuse and storage of the PDF file 31 * @param string $tocHeader Localized header to display above the table of contents 32 * @param string $remoteUser Authenticated user name for template context 33 */ 34 public function __construct( 35 Config $config, 36 AbstractCollector $collector, 37 Cache $cache, 38 string $tocHeader, 39 string $remoteUser = '' 40 ) { 41 $this->config = $config; 42 $this->collector = $collector; 43 $this->cache = $cache; 44 $this->tocHeader = $tocHeader; 45 $this->remoteUser = $remoteUser; 46 } 47 48 /** 49 * Build the PDF (or return cached version) and provide its filesystem path. 50 * 51 * @return string 52 * @throws MpdfException 53 */ 54 public function getPdf(): string 55 { 56 if (!$this->config->useCache() || !$this->cache->useCache() || $this->config->isDebugEnabled()) { 57 set_time_limit(0); 58 $this->buildDocument($this->cache->cache); 59 } 60 61 return $this->cache->cache; 62 } 63 64 /** 65 * Send the PDF to the browser. When $cacheFile is omitted, the PDF will be built (or loaded) first. 66 * 67 * @param string|null $cacheFile Absolute path to an already generated PDF file, if available 68 * @return void 69 * @throws MpdfException 70 */ 71 public function sendPdf(?string $cacheFile = null): void 72 { 73 $cacheFile ??= $this->getPdf(); 74 $title = $this->collector->getTitle(); 75 76 header('Content-Type: application/pdf'); 77 header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0'); 78 header('Pragma: public'); 79 http_conditionalRequest(filemtime($cacheFile)); 80 81 $outputTarget = $this->config->getOutputTarget(); 82 $filename = rawurlencode(cleanID(strtr($title, ':/;"', ' '))); 83 if ($outputTarget === 'file') { 84 header('Content-Disposition: attachment; filename="' . $filename . '.pdf";'); 85 } else { 86 header('Content-Disposition: inline; filename="' . $filename . '.pdf";'); 87 } 88 89 header('Set-Cookie: fileDownload=true; path=/'); 90 91 http_sendfile($cacheFile); 92 93 $fp = @fopen($cacheFile, 'rb'); 94 if ($fp) { 95 http_rangeRequest($fp, filesize($cacheFile), 'application/pdf'); 96 } else { 97 header('HTTP/1.0 500 Internal Server Error'); 98 echo 'Could not read file - bad permissions?'; 99 } 100 exit(); 101 } 102 103 /** 104 * Build the PDF document and write it to the cache file. 105 * 106 * @param string $cacheFile Destination path for the generated PDF file 107 * @return void 108 * @throws MpdfException 109 */ 110 protected function buildDocument(string $cacheFile): void 111 { 112 $writer = $this->renderDocument(); 113 114 if ($this->config->isDebugEnabled()) { 115 header('Content-Type: text/html; charset=utf-8'); 116 echo $writer->getDebugHTML(); 117 exit(); 118 } 119 120 $writer->outputToFile($cacheFile); 121 } 122 123 /** 124 * Build the PDF document without writing it to disk and expose the debug HTML. 125 * 126 * @return string Debug HTML collected while rendering the document 127 * @throws MpdfException 128 */ 129 public function getDebugHtml(): string 130 { 131 if (!$this->config->isDebugEnabled()) { 132 throw new \RuntimeException('Debug HTML is only available when debug mode is enabled'); 133 } 134 135 return $this->renderDocument()->getDebugHTML(); 136 } 137 138 /** 139 * Compose the document using the collector and return the writer. 140 * 141 * @return Writer Writer instance containing the rendered document 142 * @throws MpdfException 143 */ 144 protected function renderDocument(): Writer 145 { 146 $mpdf = new DokuMpdf($this->config, $this->collector->getLanguage()); 147 $styles = new Styles($this->config); 148 $template = new Template($this->config); 149 $writer = new Writer($mpdf, $this->config, $template, $styles); 150 151 $writer->startDocument($this->collector->getTitle()); 152 // initial context for placeholder replacements, before any pages are loaded 153 $template->setContext($this->collector, $this->collector->getPages()[0], null); 154 $writer->cover(); 155 156 if ($this->config->hasToC()) { 157 $writer->toc($this->tocHeader); 158 } 159 160 foreach ($this->collector->getPages() as $page) { 161 $template->setContext($this->collector, $page, $this->remoteUser); 162 $writer->renderWikiPage($this->collector, $page); 163 } 164 165 $writer->back(); 166 $writer->endDocument(); 167 return $writer; 168 } 169} 170