1<?php
2/**
3 * Admin for LaTeX plugin.
4 *
5 * @license	GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author	 Mark Lundeberg <nanite@gmail.com>
7 */
8
9if(!defined('DOKU_INC')) die();
10if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
11require_once(DOKU_PLUGIN.'admin.php');
12
13require_once(dirname(__FILE__).'/latexinc.php');
14
15/**
16 * All DokuWiki plugins to extend the admin function
17 * need to inherit from this class
18 */
19class admin_plugin_latex extends DokuWiki_Admin_Plugin {
20	var $output;
21	/**
22	 * return some info
23	 */
24	 function getInfo(){
25		$a = '';
26		if(method_exists(DokuWiki_Admin_Plugin,"getInfo")) {
27			 $a = parent::getInfo(); /// this will grab the data from the plugin.info.txt
28			 $a['name'] = 'LaTeX plugin administration';
29			 return $a;
30		} else
31			// Otherwise return some hardcoded data for old dokuwikis
32			return array(
33				'author' => 'Alexander Kraus, Michael Boyle, and Mark Lundeberg)',
34				'email'  => '.',
35				'date'   => '???',
36				'name'   => 'LaTeX plugin',
37				'desc'   => 'LaTeX rendering plugin; requires LaTeX, dvips, ImageMagick.',
38				'url'	=> 'http://www.dokuwiki.org/plugin:latex'
39			);
40	}
41
42	/**
43	 * return sort order for position in admin menu
44	 */
45	function getMenuSort() {
46	  return 999;
47	}
48
49	// Purgers.
50	function vio_atime($fname) {
51		if(time() - fileatime($fname) - $this->_timelimit > 0)
52		{
53			unlink($fname);
54			return $this->_timelimit;
55		}
56		return false;
57	}
58	function vio_mtime($fname) {
59		if(time() - filemtime($fname) - $this->_timelimit > 0)
60		{
61			unlink($fname);
62			return true;
63		}
64		return false;
65	}
66	function vio_all($fname) {
67		unlink($fname);
68		return true;
69	}
70
71
72	// purge all files older than $timelimit (in seconds)
73	// $mode =
74	//	  atime: age based on fileatime().
75	//	  mtime: age based on filemtime().
76	//	  all: delete all cached files.
77	function latexpurge($mode, $timelimit)
78	{
79		global $conf, $config_cascade;
80		$meddir = $conf['mediadir'] . '/' . strtr($this->getConf('latex_namespace'),':','/');
81		$images = glob($meddir.'/img*');
82		$this->_timelimit = $timelimit;
83		switch($mode) {
84			case 'atime':
85				$vio = array_map(array($this,'vio_atime'),$images);
86				break;
87			case 'mtime':
88				$vio = array_map(array($this,'vio_mtime'),$images);
89				break;
90			case 'all':
91				$vio = array_map(array($this,'vio_all'),$images);
92				break;
93			default:
94				return false;
95		}
96		return array_combine($images,$vio);
97	}
98
99	/**
100	 * handle user request
101	 */
102	function handle() {
103	  global $conf, $config_cascade;
104	  $this->output = "";
105	  if(isset($_POST['latexpurge']))
106	  {
107			$mode = $_POST['purgemode'];
108			$days = $_POST['purgedays'];
109			if(is_numeric($days) && $days == 0)
110				$mode = 'all';
111			if($mode == 'all') {
112				// If the admin wants to delete all of the images, probably it's good to print this reminder
113				//   since they are likely doing it after changing the colour or something.
114				// (I don't know how many hours I spent trying to fix LaTeX heisenbugs that were just cached... grr.)
115				$this->output .= '<div class="info">'.$this->getLang('refresh_note').'</div>';
116			}
117			$numdeleted = 0;
118			$numkept = 0;
119			$this->output .= "<pre>Purge result ([x] = deleted):\n";
120			if($mode == 'all' || (is_numeric($days) && $days >= 0)) {
121				$res = $this->latexpurge($mode, $days*86400);
122				foreach($res as $img => $vio){
123					if($vio) {
124						$this->output .= '[x] '.$img . "\n";
125						$numdeleted += 1;
126					} else {
127						// $this->output .= '[ ] '.$img . "\n";
128						$numkept += 1;
129					}
130				}
131			} else {
132				$this->output = "<div class=\"error\">Purger: Bad form inputs. No action taken.</div>".$this->output;
133			}
134			$this->output .= "Totals: $numdeleted deleted, $numkept kept (kept files not shown).\n";
135			if ($numdeleted > 0) {
136				touch($config_cascade['main']['local']);
137			}
138			$this->output .= "</pre>";
139	  }
140	}
141
142
143	/**
144	 * output appropriate html
145	 */
146	function html() {
147		global $ID,$INFO;
148		ptln('<p>'.$this->output.'</p>');
149		ptln('<h1>LaTeX plugin administrator tasks</h1>');
150		ptln('<h2>'.$this->getLang('legend_purge').'</h2>');
151		ptln('<div class="level2">');
152
153		////////////// PURGE FORM
154		ptln('<form action="'.wl($INFO['id']).'?do=admin&page='.$this->getPluginName().'" method="post">');
155		ptln('<table class="inline"><tr>');
156		ptln('<td rowspan="2"><input type="submit" class="button" name="latexpurge"  value="'.$this->getLang('btn_purge').'" /></td>');
157		ptln('<TD>');
158		$labtimes = $this->getLang('label_times');
159		ptln('(<LABEL><INPUT type="radio" name="purgemode" value="atime" checked />'.$labtimes['atime'].'</LABEL>');
160		ptln(' | <LABEL><INPUT type="radio" name="purgemode" value="mtime" />'.$labtimes['mtime'].'</LABEL>)');
161		echo $this->getLang('label_olderthan');
162		echo '<input type="text" name="purgedays" size="3" value="30">';
163		echo $this->getLang('label_days');
164		ptln('</TD><TR><TD>');
165		echo '<LABEL><INPUT type="radio" name="purgemode" value="all" />'.$this->getLang('label_all').'</LABEL>';
166		ptln('</TD></TR></TABLE>');
167		ptln('</form>');
168
169		ptln('</div>');
170
171		/////////////// DIAGNOSER
172		ptln('<h2>LaTeX troubleshooter</h2>');
173		ptln('<div class="level2">');
174		ptln('<form action="'.wl($INFO['id']).'" method="get">');
175		ptln('  <input type="hidden" name="do"   value="admin" />');
176		ptln('  <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
177		ptln('Push this button to diagnose your LaTeX/ImageMagick installation: <input type="submit" class="button" name="dotest"  value="Test" /><br/>');
178		ptln('<input type="checkbox" name="keep_tmp">Check this button to keep the temporary files used during compilation.</input><br/>');
179		ptln('The following latex code will be inserted into the template and compiled:');
180		ptln('<br />');
181		if(isset($_REQUEST['testformula']))
182			$testformula = $_REQUEST['testformula'];
183		else
184			$testformula = '$$\underbrace{{\it f}({\rm DokuWiki}) = \overbrace{[a+b=c]}^\textrm{\LaTeX}}_{Success!}$$';
185		ptln('  <textarea cols=70 rows=6 type="text" name="testformula">'.htmlspecialchars($testformula).'</textarea>');
186		ptln('</form>');
187		ptln('</div>');
188		if($_REQUEST['dotest']) {
189			ptln('<h3>Versions</h3>');
190			ptln('<div class="level3">');
191			ptln('This is a test of the acessibility of your programs and their versions.');
192			ptln('<table class="inline">');
193			ptln('<tr><th>command</th><th>output</th></tr>');
194			foreach(array($this->getConf("latex_path"),$this->getConf("dvips_path"),
195					$this->getConf("convert_path"),$this->getConf("identify_path")) as $path) {
196				ptln('<tr><td><pre>');
197				$cmd = $path." --version 2>&1";
198				echo htmlspecialchars($cmd);
199				ptln('</pre></td><td>');
200				unset($execout);
201				exec($cmd,$execout,$statuscode);
202				if($statuscode == 0)
203					echo '<pre>';
204				else
205					echo '<pre style="background-color:#FCC;">'; //pink for error status
206				echo htmlspecialchars(implode(PHP_EOL,$execout));
207				ptln('</pre></td></tr>');
208			}
209			ptln('</table>');
210			ptln('</div>');
211
212			ptln('<h3>Test run</h3>');
213			$plug = new syntax_plugin_latex_common();
214
215			/// Directory sanity checks
216			if(is_writable($plug->_latex->getPicturePath()) && is_dir($plug->_latex->getPicturePath()))
217				ptln('<div class="success">Media directory is writable: <code>'.$plug->_latex->getPicturePath().'</code></div>');
218			else
219				ptln('<div class="error">Media directory not writable or nonexistant! <code>'.$plug->_latex->getPicturePath().'</code>
220						<br />Recommendation: This media namespace must be writable on the file system.</div>');
221			if(is_writable($plug->_latex->_tmp_dir) && is_dir($plug->_latex->_tmp_dir))
222				ptln('<div class="success">Temporary directory is writable: <code>'.$plug->_latex->_tmp_dir.'</code></div>');
223			else
224				ptln('<div class="error">Temporary directory not writable or nonexistant! <code>'.$plug->_latex->_tmp_dir.'</code>
225						<br />Recommendation: This media namespace must be writable on the file system.</div>');
226
227			// simulate a call to the syntax plugin; force render, keep temp files.
228			$md5 = md5($testformula);
229			$outname = $plug->_latex->getPicturePath()."/img".$md5.'.'.$plug->_latex->_image_format;
230			if(file_exists($outname)) {
231				if(unlink($outname))
232					ptln('<div class="info">Removed cache file for test: <code>'.$outname.'</code><br/>
233					<b>WARNING: You may need to refresh your browser\'s cache to see changes in the resulting image.</b></div>');
234				else
235					ptln('<div class="error">Could not remove cached file for test! <code>'.$outname.'</code><br />
236									the following tests will not work (renderer will just reuse the cached file)</div>');
237			}
238			ptln('<div class="info">Attempting to render to target <code>'.$outname.'</code></div>');
239			$plug->_latex->_keep_tmp = true;
240			$plug->_latex->_cmdoutput = ''; // activate command log.
241			$data = array($testformula,DOKU_LEXER_UNMATCHED,'class'=>"latex_inline", 'title'=>"Math", NULL);
242			$this->doc = '';
243			$plug->render('xhtml', $this, $data);
244			$tmpw = $this->getConf('latex_namespace').':tmp:'.$plug->_latex->_tmp_filename;
245			$tmpf = $plug->_latex->_tmp_dir."/".$plug->_latex->_tmp_filename;
246			$tmpext = array('tex','log','aux','dvi','ps',$plug->_latex->_image_format);
247			foreach($tmpext as $ext) {
248				$fname = $tmpf.'.'.$ext;
249				if(is_file($fname)) {
250					if(isset($_REQUEST['keep_tmp'])) {
251						$rendstr = $this->render('{{'.$tmpw.'.'.$ext.'?linkonly&nocache|'.$fname.'}}');
252						$rendstr = preg_replace('/<\\/?p>/','',$rendstr);
253					} else
254						$rendstr = $fname;
255					ptln('<div class="success">File created: <code>'.$rendstr.'</code></div>');
256				} else
257					ptln('<div class="error">File missing! <code>'.$fname.'</code></div>');
258			}
259			if(! isset($_REQUEST['keep_tmp']))
260				ptln('<div class="info">These files <code>'.$tmpf.'.*</code> will be deleted at the end of this script.</div>');
261			if(is_file($outname))
262				ptln('<div class="success">Successfully moved to media: <code>'.$outname.'</code></div>');
263			else
264				ptln('<div class="error">File missing from media! <code>'.$outname.'</code></div>');
265
266			ptln('<div class="level3">');
267			ptln('<table class="inline"><tr><th>Input LaTeX file</th><th>Final result</th></tr>');
268			ptln('<tr><td><pre>');
269			if(is_readable($tmpf.'.tex') && is_file($tmpf.'.tex'))
270				echo htmlspecialchars(file_get_contents($tmpf.'.tex'));
271			else
272				echo 'MISSING';
273			ptln('</pre></td><td>');
274//			ptln(htmlspecialchars($plug->_url));
275//			ptln('<br /><br />');
276			ptln('<center>');
277			ptln($this->doc);
278			ptln('</center></td></tr>');
279			ptln('</table>');
280
281			ptln('Command log:');
282			echo '<pre>';
283			echo $plug->_latex->_cmdoutput;
284			echo '</pre>';
285
286			ptln('Contents of '.$tmpf.'.log:');
287			echo '<pre>';
288			echo htmlspecialchars(file_get_contents($tmpf.'.log'));
289			echo '</pre>';
290
291			if(! isset($_REQUEST['keep_tmp']))
292				$plug->_latex->cleanTemporaryDirectory();
293			ptln('</div>');
294		}
295	}
296}