1<?php 2/** 3 * Admin for LaTeX plugin. 4 * 5 * Primary Author: Wizardry and Steamworks <wizardry.steamworks@outlook.com> 6 * Original Authors: Mark Lundeberg, Alexander Kraus, Michael Boyle 7 * 8 * @license GPL 2 9 */ 10 11if(!defined('DOKU_INC')) die(); 12require_once(__DIR__ . '/latexinc.php'); 13 14class admin_plugin_latexwas extends DokuWiki_Admin_Plugin { 15 protected $output = ''; 16 17 /** 18 * DokuWiki Admin Menu Position 19 */ 20 public function getMenuSort() { 21 return 40; 22 } 23 24 /** 25 * DOM Generator Helper 26 */ 27 protected function el($tag, $attr = [], $content = '', $selfClosing = false) { 28 $html = "<{$tag}"; 29 foreach ($attr as $k => $v) { 30 $html .= " {$k}=\"" . htmlspecialchars($v, ENT_QUOTES, 'UTF-8') . "\""; 31 } 32 if ($selfClosing) return $html . ' />'; 33 // Allow content to be an array of elements 34 $inner = is_array($content) ? implode('', $content) : $content; 35 return $html . ">" . $inner . "</{$tag}>"; 36 } 37 38 /** 39 * Handle form submissions 40 */ 41 public function handle() { 42 if (!isset($_POST['latexpurge'])) return; 43 44 $mode = $_POST['purgemode']; 45 $days = (int)$_POST['purgedays']; 46 $timelimit = $days * 86400; 47 48 global $conf; 49 $meddir = $conf['mediadir'] . '/' . strtr($this->getConf('latex_namespace'), ':', '/'); 50 $images = glob($meddir . '/img*'); 51 $deleted = 0; 52 53 foreach ($images as $img) { 54 $age = time() - ($mode === 'atime' ? fileatime($img) : filemtime($img)); 55 if ($mode === 'all' || $age > $timelimit) { 56 if (@unlink($img)) $deleted++; 57 } 58 } 59 60 $this->output = $this->el('div', ['class' => 'success'], "Purge Result: $deleted files deleted from cache."); 61 62 // Invalidate DokuWiki cache 63 if ($deleted > 0) { 64 global $config_cascade; 65 @touch($config_cascade['main']['local']); 66 } 67 } 68 69 /** 70 * Generate Admin Interface 71 */ 72 public function html() { 73 echo $this->el('h1', [], 'LaTeX Administration'); 74 echo $this->output; 75 76 // --- SECTION 1: PURGE CACHE --- 77 echo $this->el('h2', [], $this->getLang('legend_purge') ?: 'Purge Image Cache'); 78 79 $purgeForm = $this->el('div', ['class' => 'no'], [ 80 $this->el('p', [], [ 81 $this->el('label', [], [ 82 $this->el('input', ['type' => 'radio', 'name' => 'purgemode', 'value' => 'atime', 'checked' => 'checked']), 83 ' Access Time ' 84 ]), 85 $this->el('label', [], [ 86 $this->el('input', ['type' => 'radio', 'name' => 'purgemode', 'value' => 'all']), 87 ' Delete All ' 88 ]) 89 ]), 90 $this->el('p', [], [ 91 'Older than: ', 92 $this->el('input', ['type' => 'text', 'name' => 'purgedays', 'value' => '30', 'size' => '3']), 93 ' days' 94 ]), 95 $this->el('input', ['type' => 'submit', 'name' => 'latexpurge', 'class' => 'button', 'value' => $this->getLang('btn_purge') ?: 'Purge Now']) 96 ]); 97 98 echo $this->el('form', ['method' => 'post', 'action' => wl()], $purgeForm); 99 100 // --- SECTION 2: TROUBLESHOOTER --- 101 echo $this->el('h2', [], 'LaTeX Troubleshooter'); 102 103 $diagForm = $this->el('div', ['class' => 'no'], [ 104 $this->el('p', [], 'Use this section to test your LaTeX and ImageMagick paths.'), 105 $this->el('textarea', [ 106 'name' => 'testformula', 107 'rows' => '3', 108 'cols' => '60', 109 'style' => 'display:block; margin-bottom:10px;' 110 ], '$$\sum_{i=0}^n i^2 = \frac{(n^2+n)(2n+1)}{6}$$'), 111 $this->el('input', ['type' => 'submit', 'name' => 'dotest', 'class' => 'button', 'value' => 'Run Diagnostics']) 112 ]); 113 114 echo $this->el('form', ['method' => 'get', 'action' => wl()], [ 115 $this->el('input', ['type' => 'hidden', 'name' => 'do', 'value' => 'admin']), 116 $this->el('input', ['type' => 'hidden', 'name' => 'page', 'value' => 'latexwas']), 117 $diagForm 118 ]); 119 120 if (isset($_REQUEST['dotest'])) { 121 $this->runDiagnostics(); 122 } 123 } 124 125 /** 126 * System diagnostic logic using DOM Generator 127 */ 128 protected function runDiagnostics() { 129 $binaries = [ 130 'LaTeX' => $this->getConf("latex_path"), 131 'Dvips' => $this->getConf("dvips_path"), 132 'Convert' => $this->getConf("convert_path"), 133 'Identify' => $this->getConf("identify_path") 134 ]; 135 136 echo $this->el('h3', [], 'Binary Version Check'); 137 138 $rows = []; 139 foreach ($binaries as $name => $path) { 140 unset($out); 141 exec($path . " --version 2>&1", $out, $status); 142 143 $isOk = ($status === 0); 144 $msg = $isOk ? ($out[0] ?? 'Found') : 'ERROR: Binary not found or returned error code ' . $status; 145 146 $rows[] = $this->el('tr', [], [ 147 $this->el('td', ['style' => 'font-weight:bold;'], $name), 148 $this->el('td', ['style' => 'font-family:monospace; color:' . ($isOk ? 'green' : 'red')], $msg) 149 ]); 150 } 151 152 echo $this->el('table', ['class' => 'inline'], [ 153 $this->el('thead', [], $this->el('tr', [], [ 154 $this->el('th', [], 'Tool'), 155 $this->el('th', [], 'Status / Version Output') 156 ])), 157 $this->el('tbody', [], $rows) 158 ]); 159 160 // Rendering Test 161 echo $this->el('h3', [], 'Rendering Test'); 162 $formula = $_REQUEST['testformula'] ?? 'E=mc^2'; 163 164 // We use a temporary syntax object to trigger a render 165 $plug = new syntax_plugin_latexwas_common_proxy(); 166 $url = $plug->_latex->getFormulaURL($formula); 167 168 if ($url) { 169 echo $this->el('div', ['class' => 'success'], [ 170 $this->el('p', [], 'Success! Rendered image:'), 171 $this->el('img', ['src' => $url, 'alt' => 'test render', 'style' => 'background:white; padding:10px; border:1px solid #ccc;']) 172 ]); 173 } else { 174 echo $this->el('div', ['class' => 'error'], [ 175 $this->el('p', [], 'Rendering failed.'), 176 $this->el('p', [], 'Error Code: ' . $plug->_latex->_errorcode), 177 $this->el('pre', [], $plug->_latex->_cmdoutput) 178 ]); 179 } 180 } 181} 182 183/** 184 * Proxy class to access the common renderer logic 185 */ 186class syntax_plugin_latexwas_common_proxy extends syntax_plugin_latexwas_common { 187 public function getType() { return 'protected'; } 188 public function getSort() { return 0; } 189 public function connectTo($mode) {} 190 public function handle($match, $state, $pos, Doku_Handler $handler) { return []; } 191} 192