xref: /plugin/dw2pdf/src/PdfExportService.php (revision f2000117f688f925c0f5fdc1ec614c297f3d0398) !
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->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 DokuPdf($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        $writer->cover();
153
154        if ($this->config->hasToC()) {
155            $writer->toc($this->tocHeader);
156        }
157
158        foreach ($this->collector->getPages() as $page) {
159            $template->setContext($this->collector, $page, $this->remoteUser);
160            $writer->renderWikiPage($this->collector, $page);
161        }
162
163        $writer->back();
164        $writer->endDocument();
165        return $writer;
166    }
167}
168