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