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