xref: /plugin/dw2pdf/src/Template.php (revision 3ef754f46672030ac230d13ff1c0bc6a66e9aed6)
1<?php
2
3namespace dokuwiki\plugin\dw2pdf\src;
4
5use dokuwiki\Extension\Event;
6use Mpdf\HTMLParserMode;
7use Mpdf\Mpdf;
8use Mpdf\MpdfException;
9
10class Template
11{
12    /** @var string The name of the template */
13    protected string $name = 'default';
14
15    /** @var string The directory where the template is stored */
16    protected string $dir = '';
17
18    /** @var float The scale of the QR code to generate (0.0 to disable) */
19    protected float $qrScale = 0.0;
20
21    /** @var bool Whether we are processing the first page */
22    protected bool $isFirstPage = true;
23
24    /** @var array The context of the currently processed page. Used for placeholder replacements */
25    protected array $context = [
26        'id' => '',
27        'rev' => '',
28        'at' => '',
29        'title' => '',
30        'username' => '',
31    ];
32
33
34    /**
35     * Constructor
36     *
37     * @param Config $config The DW2PDF configuration
38     */
39    public function __construct(Config $config)
40    {
41        $this->name = $config->getTemplateName();
42        $this->qrScale = $config->getQRScale();
43        $this->dir = DOKU_PLUGIN . 'dw2pdf/tpl/' . $this->name;
44        if (!is_dir($this->dir)) {
45            throw new \RuntimeException("Template directory $this->dir does not exist");
46        }
47    }
48
49    /**
50     * Set the context for the current page. For books / namespace export, initially
51     * seed the context with the first page in collection.
52     *
53     * This will be used in placeholders in headers/footers/cover/back pages.
54     *
55     * @param AbstractCollector $collector
56     * @param string $id The page ID. For ns/book export, first page in collection.
57     * @param string|null $username The username of the user generating the PDF (if any)
58     * @return void
59     */
60    public function setContext(AbstractCollector $collector, string $id, ?string $username): void
61    {
62        $this->context = [
63            'title' => $collector->getTitle(),
64            'id' => $id,
65            'rev' => $collector->getRev() ?? '',
66            'at' => $collector->getAt() ?? '',
67            'username' => $username ?? '',
68        ];
69    }
70
71    /**
72     * Get the HTML content for the given type and order
73     *
74     * Will fall back to non-ordered version if ordered version is not found. Placeholders
75     * will be replaced.
76     *
77     * @param string $type header, footer, cover, back, citation
78     * @param string $order first, even, odd or empty string for default
79     * @return string
80     */
81    public function getHTML(string $type, string $order = ''): string
82    {
83        if ($order) $order = "_$order";
84
85        $file = $this->dir . '/' . $type . $order . '.html';
86        if (!is_file($file)) $file = $this->dir . '/' . $type . '.html';
87        if (!is_file($file)) return '';
88
89        $html = file_get_contents($file);
90        $html = $this->replacePlaceholders($html);
91        return $html;
92    }
93
94    /**
95     * Applies the placeholder replacements to the given HTML
96     *
97     * Called for headers, footers, cover and back pages on each call.
98     *
99     * Accesses global DokuWiki variables to fill in page specific data.
100     *
101     * @triggers PLUGIN_DW2PDF_REPLACE
102     * @param string $html The template's HTML content
103     * @return string The HTML with placeholders replaced
104     */
105    protected function replacePlaceholders(string $html): string
106    {
107        global $conf;
108
109        $params = [];
110        if (!empty($this->context['at'])) {
111            $params['at'] = $this->context['at'];
112        } elseif (!empty($this->context['rev'])) {
113            $params['rev'] = $this->context['rev'];
114        }
115        $url = wl($this->context['id'], $params, true, "&");
116
117        $replace = [
118            '@PAGE@' => '{PAGENO}',
119            '@PAGES@' => '{nbpg}', //see also $mpdf->pagenumSuffix = ' / '
120            '@TITLE@' => hsc($this->context['title'] ?? ''),
121            '@WIKI@' => $conf['title'],
122            '@WIKIURL@' => DOKU_URL,
123            '@DATE@' => dformat(time()),
124            '@USERNAME@' => hsc($this->context['username'] ?? ''),
125            '@BASE@' => DOKU_BASE,
126            '@INC@' => DOKU_INC,
127            '@TPLBASE@' => DOKU_BASE . 'lib/plugins/dw2pdf/tpl/' . $this->name . '/',
128            '@TPLINC@' => DOKU_INC . 'lib/plugins/dw2pdf/tpl/' . $this->name . '/',
129            // page dependent placeholders
130            '@ID' => $this->context['id'] ?? '',
131            '@UPDATE@' => dformat(filemtime(wikiFN($this->context['id'], $this->context['rev'] ?? ''))),
132            '@PAGEURL@' => $url,
133            '@QRCODE@' => $this->generateQRCode($url),
134        ];
135
136        // let other plugins define their own replacements
137        $evdata = [
138            'id' => $this->context['id'],
139            'replace' => &$replace,
140            'content' => &$html,
141            'context' => $this->context
142        ];
143        $event = new Event('PLUGIN_DW2PDF_REPLACE', $evdata);
144        if ($event->advise_before()) {
145            $html = str_replace(array_keys($replace), array_values($replace), $html);
146        }
147        // plugins may post-process HTML, e.g to clean up unused replacements
148        $event->advise_after();
149
150        // @DATE(<date>[, <format>])@
151        $html = preg_replace_callback(
152            '/@DATE\((.*?)(?:,\s*(.*?))?\)@/',
153            function ($match) {
154                global $conf;
155                //no 2nd argument for default date format
156                if ($match[2] == null) {
157                    $match[2] = $conf['dformat'];
158                }
159                return strftime($match[2], strtotime($match[1]));
160            },
161            $html
162        );
163
164        return $html;
165    }
166
167    /**
168     * Generate QR code pseudo-HTML
169     *
170     * @param string $url The URL to encode
171     * @return string
172     */
173    protected function generateQRCode($url): string
174    {
175        if ($this->qrScale <= 0.0) return '';
176
177        $url = hsc($url);
178        return sprintf(
179            '<barcode type="QR" code="%s" error="Q" disableborder="1" class="qrcode" size="%s" />',
180            $url,
181            $this->qrScale
182        );
183    }
184}
185