1<?php
2/**
3 * DokuWiki Plugin netlogo (Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Rik Blok <rik.blok@ubc.ca>
7 *
8 * Download:
9 * <https://github.com/rikblok/dokuwiki-plugin-netlogo/zipball/master>
10 *
11 * ToDo:
12 *	* automatically add "nlogo    !application/octet-stream" to conf/mime.local.conf? [Rik, 2012-10-19]
13 *	* language support [Rik, 2012-10-19]
14 *	* better error messages [Rik, 2012-10-19]
15 *	* config options (eg. download url) [Rik, 2012-10-19]
16 *	* make it work with local media files.  Currently reports error: "Unable to load NetLogo model from ..., please ensure: * That you can download the resource at this link * That the server containing the resource has Cross-Origin Resource Sharing configured appropriately".  Tried corsharing plugin but didn't help. [Rik, 2016-11-27]
17
18 *
19 * Documentation:
20 * NetLogo model file format <https://github.com/NetLogo/NetLogo/wiki/Model-file-format>
21 *
22 * Acknowledgements:
23 * Thanks to Stylianos Dritsas for the applet plugin
24 *   <https://www.dokuwiki.org/plugin:applet>.
25 */
26
27// must be run within Dokuwiki
28if (!defined('DOKU_INC')) die();
29
30if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
31if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
32if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
33
34require_once DOKU_PLUGIN.'syntax.php';
35require_once DOKU_PLUGIN.'netlogo/inc/support.php';
36
37class syntax_plugin_netlogo_applet extends DokuWiki_Syntax_Plugin {
38    public function getType() {
39        return 'substition';
40    }
41
42/* // don't override getPType()
43    public function getPType() {
44        return 'FIXME: normal|block|stack';
45    }
46*/
47
48    public function getSort() {
49		/* Should be less than 320 as defined in
50		 * /inc/parser/parser.php:class Doku_Parser_Mode_media
51		 * http://xref.dokuwiki.org/reference/dokuwiki/_classes/doku_parser_mode_media.html
52		 * After plugin:applet (316), before media (320).  See https://www.dokuwiki.org/devel:parser:getsort_list
53		*/
54        return 317;
55    }
56
57
58    public function connectTo($mode) {
59		// make regex less greedy so it doesn't include pipe in filename, eg. only match first ugh.nlogo in {{ugh.nlogo|Download ugh.nlogo}} [Rik, 2013-11-28]]
60		$this->Lexer->addSpecialPattern('\{\{[^\}\|]+\.nlogo\?[^\} ]*do=download[^\} ]* ?\}\}',$mode,'media');		// with do=download parameter
61		$this->Lexer->addSpecialPattern('\{\{[^\}\|]+\.nlogo ?\}\}',$mode,'plugin_netlogo_applet');									// without parameters
62		$this->Lexer->addSpecialPattern('\{\{[^\}\|]+\.nlogo\?[^\} ]+ ?\}\}',$mode,'plugin_netlogo_applet');				// with other parameters
63		$this->Lexer->addSpecialPattern('\{\{[^\}\|]+\.nlogo ?\|\}\}',$mode,'plugin_netlogo_applet');									// with empty title [Rik, 2013-11-16]
64		// here are some test cases [Rik, 2012-10-12]
65		/*
66			// should work
67			{{ugh.nlogo|}}
68			{{ugh.nlogo}}
69			{{ugh.nlogo }}
70			{{ ugh.nlogo }}
71			{{ ugh.nlogo}}
72
73			// should work
74			{{ugh.nlogo?818x611}}
75			{{ugh.nlogo?818x611 }}
76			{{ ugh.nlogo?818x611 }}
77			{{ ugh.nlogo?818x611}}
78
79			// should fail
80			{{ugh.nlogo.x}}
81			{{ugh.nlogo.x }}
82			{{ ugh.nlogo.x }}
83			{{ ugh.nlogo.x}}
84
85			// should fail
86			{{ugh.nlogo.x?818x611}}
87			{{ugh.nlogo.x?818x611 }}
88			{{ ugh.nlogo.x?818x611 }}
89			{{ ugh.nlogo.x?818x611}}
90		*/
91//        $this->Lexer->addEntryPattern('<FIXME>',$mode,'plugin_netlogo_applet');
92    }
93
94//    public function postConnect() {
95//        $this->Lexer->addExitPattern('</FIXME>','plugin_netlogo_applet');
96//    }
97
98    public function handle($match, $state, $pos, Doku_Handler $handler){
99		/*
100		 * Copied from DokuWiki media handler in
101		 * http://xref.dokuwiki.org/reference/dokuwiki/_functions/doku_handler_parse_media.html
102		*/
103		// Strip the opening and closing markup
104		$link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
105
106		// Split title from URL
107		$link = explode('|',$link,2);
108
109		// Check alignment
110		$ralign = (bool)preg_match('/^ /',$link[0]);
111		$lalign = (bool)preg_match('/ $/',$link[0]);
112
113		// Logic = what's that ;)...
114		if ( $lalign & $ralign ) {
115			$align = 'center';
116		} else if ( $ralign ) {
117			$align = 'right';
118		} else if ( $lalign ) {
119			$align = 'left';
120		} else {
121			$align = NULL;
122		}
123
124		// The title...
125		if ( !isset($link[1]) ) {
126			$link[1] = NULL;
127		}
128
129		//remove aligning spaces
130		$link[0] = trim($link[0]);
131
132		//split into src and parameters (using the very last questionmark)
133		$pos = strrpos($link[0], '?');
134		if($pos !== false){
135			$src   = substr($link[0],0,$pos);
136			$param = substr($link[0],$pos+1);
137		}else{
138			$src   = $link[0];
139			$param = '';
140		}
141
142		// parse width and height (must be first parameter)
143		if (preg_match('#^(\d+)(x(\d+))?#i',$param,$size)){
144			($size[1]) ? $w = $size[1] : $w = NULL;
145			($size[3]) ? $h = $size[3] : $h = NULL;
146		} else {
147			$w = NULL;
148			$h = NULL;
149		}
150
151		// parse 'do' action
152		if (preg_match('#do=([a-z]+)#',$param,$action)){
153			// specified by user
154			$do = $action[1];
155		} else {
156			$do = "interface";	// default
157		}
158
159		// check for nlogo fileicon
160		$nlogoiconsrc = DOKU_PLUGIN.'netlogo/fileicons/nlogo.png';
161		$nlogoicondest = DOKU_INC.'lib/images/fileicons/nlogo.png';
162		if (!file_exists($nlogoicondest))	copy($nlogoiconsrc, $nlogoicondest);
163
164		$params = array(
165			'src'=>$src,
166			'title'=>$link[1],
167			'align'=>$align,
168			'width'=>$w,
169			'height'=>$h,
170			'do'=>$do,
171		);
172
173		return $params;
174    }
175
176    public function render($mode, Doku_Renderer $renderer, $data) {
177		global $ID, $conf;
178
179        if($mode != 'xhtml') return false;
180
181		// check .nlogo file read permission
182		$src = $data['src'];
183		/* testing: disable filetype checking.  Does this allow remote download of file, eg. from github? [Rik, 2016-11-25]
184		resolve_mediaid(getNS($ID),$src,$exists);
185		if(auth_quickaclcheck(getNS($src).':X') < AUTH_READ){ // auth_quickaclcheck() mimicked from http://xref.dokuwiki.org/reference/dokuwiki/_functions/checkfilestatus.html
186			$renderer->doc .= '<div class="error">NetLogo: File not allowed: ' . $src . '</div>';
187			return true;
188		}
189		$src = mediaFN($src);
190		if (!$exists) {
191			$renderer->doc .= '<div class="error">NetLogo: File not found: ' . $src . '</div>';
192			return true;
193		}
194		*/
195
196		// parse file to get contents
197		if (is_null($data['width']) || is_null($data['height']) || $data['do']==='code' || $data['do']==='info' || $data['do']==='mdinfo') {
198			$nlogo = file_get_contents($src);
199			$nlogoparts = explode('@#$#@#$#@', $nlogo);
200			/*
201				[0] => code
202				[1] => interface
203				[2] => info
204				[3] => turtle shapes
205				[4] => NetLogo version
206				[5] => preview commands
207				[6] => system dynamics modeler
208				[7] => BehaviorSpace
209				[8] => HubNet client
210				[9] => link shapes
211				[10] =>model settings
212				[11] =>reserved by Michelle
213				[12] => (empty)
214			*/
215
216			// show code
217			if ($data['do']==='code') {
218				$renderer->doc .= p_render('xhtml',p_get_instructions('<code netlogo>' . $nlogoparts[0] . '</code>'),$info);
219				return true;
220			}
221
222			// show info
223			if ($data['do']==='info') {
224				$renderer->doc .= p_render('xhtml',p_get_instructions($nlogoparts[2]),$info);
225				return true;
226			}
227			// show info wrapped in '<markdown>...</markdown>' tags
228			if ($data['do']==='mdinfo') {
229				$renderer->doc .= p_render('xhtml',p_get_instructions('<markdown>' . $nlogoparts[2] . '</markdown>'),$info);
230				return true;
231			}
232
233			// width & height?
234			if (is_null($data['width']) || is_null($data['height'])) {
235				// store x,y coordinates of bottom right corner in $rightbottom[2] & $rightbottom[3], respectively
236				preg_match_all('/(^|\n)\n[A-Z\-]+\n[0-9]+\n[0-9]+\n([0-9]+)\n([0-9]+)\n/',$nlogoparts[1],$rightbottom);
237				if (is_null($data['width']))	$data['width'] = max($rightbottom[2])+50;
238				if (is_null($data['height']))	$data['height'] = max($rightbottom[3])+300;
239			}
240		}
241
242
243		// download libraries? Todo: move root url to config option
244		$urlroot = 'http://ccl.northwestern.edu/netlogo/';
245
246		// $src is currently realpath.  Turn into relative path from DokuWiki media folder
247		// temporarily disabled while testing remote urls [Rik, 2016-11-26]
248		//$src = relativePath(DOKU_INC.'data/media/',$src);
249
250		// Will pass token to servefile.php to authorize.  First generate secret uuid if not found.
251		$uuidfile = 'data/tmp/plugin_netlogo_uuid';
252		if (!file_exists($uuidfile)) {
253			if (!$handle = fopen($uuidfile, 'w')) {
254				$renderer->doc .= '<div class="error">NetLogo: Cannot create UUID ' . $uuidfile . '</div>';
255				return true;
256			}
257			// Write uuid to our opened file.
258			if (fwrite($handle, uuid4()) === FALSE) {
259				$renderer->doc .= '<div class="error">NetLogo: Cannot write UUID to ' . $uuidfile . '</div>';
260				return true;
261			}
262			fclose($handle);
263		}
264		// read uuid from file
265		$uuid = file_get_contents($uuidfile);
266
267		// when should the servefile.php link expire?
268		$expires = time()+min(max($conf['cachetime'],60), 3600); // expires in cachetime, but no less than 1 minute or more than 1 hour
269
270		// disable caching of this page to ensure parameters passed to servefile.php are always fresh [Rik, 2012-10-06]
271        $renderer->info['cache'] = false;
272
273		// generate token for servefile.php to authorize, use $uuid as salt.  servefile.php must be able to generate same token or it won't serve file.
274		$token=hash('sha256',$uuid.$src.$expires); // replace crypt() for more than first 8 chars [Rik, 2012-10-06]
275
276		// special handling for center
277		$pcenter = false;
278		if (!is_null($data['align']) && $data['align']==='center') {
279			$pcenter = true;
280			$data['align']=null;
281		}
282
283		/*
284			old servefile method: '"lib/plugins/netlogo/inc/servefile.php?src='.urlencode($src).'&expires='.$expires.'&token='.urlencode($token).'"'
285			may still be needed because fetch.php throws Cross-Origin Resource Sharing error
286			[Rik, 2016-11-27]
287		*/
288		if ($pcenter) $renderer->doc .= '<p align="center">';
289		$renderer->doc .= '<iframe title="" src="https://netlogoweb.org/web?'.$src.'" style="width:'.$data['width'].'px; height:'.$data['height'].'px"></iframe>';
290		if ($pcenter) $renderer->doc .= '</p>';
291        return true;
292    }
293}
294
295// vim:ts=4:sw=4:et:
296