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