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