1<?php
2/**
3 * tIndexmenu Admin Plugin:   Indexmenu Component.
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Samuele Tognini <samuele@netsons.org>
7 * @author     Rene Hadler <rene.hadler@iteas.at>
8 */
9
10if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12require_once(DOKU_PLUGIN.'admin.php');
13require_once(DOKU_INC.'inc/HTTPClient.php');
14require_once(DOKU_PLUGIN."tindexmenu/inc/pclzip.lib.php");
15if(!defined('INDEXMENU_IMG_ABSDIR')) define('INDEXMENU_IMG_ABSDIR',DOKU_PLUGIN."tindexmenu/images");
16define('INDEXMENU_ICOS','base,folder,folderopen,folderh,folderhopen,page,plus,minus,nolines_plus,nolines_minus,minusbottom,plusbottom,join,joinbottom,line,empty');
17
18class admin_plugin_indexmenu extends DokuWiki_Admin_Plugin {
19	var $req = 'fetch';
20	var $repos = array (
21			"url"  => array(DOKU_URL),
22			"status" => array(""),
23	);
24
25	var $selected=-1;
26
27	/**
28	 * return some info
29	 */
30	function getInfo(){
31		return array(
32				'author' => 'Samuele Tognini mod. by Rene Hadler',
33				'email'  => 'samuele@netsons.org, rene.hadler@iteas.at',
34				'date'   => rtrim(io_readFile(DOKU_PLUGIN.'tindexmenu/VERSION.txt')),
35				'name'   => 'tIndexmenu (admin plugin component)',
36				'desc'   => 'tIndexmenu admin functions.',
37				'url'    => 'http://wiki.splitbrain.org/plugin:tindexmenu',
38		);
39	}
40
41	/**
42	 * return sort order for position in admin menu
43	 */
44	function getMenuSort() {
45		return 999;
46	}
47
48	/**
49	 * handle user request
50	 */
51	function handle() {
52		$url=$this->getConf('themes_url');
53		if (empty($url)) {
54			$this->repos['url'][]=$this->getLang('no_repos');
55			$this->repos['status'][]="disabled";
56			$this->repos['install'][]=-1;
57		} else {
58			$this->repos['url']= array_merge($this->repos['url'],explode(',',$url));
59		}
60
61		if (!isset($_REQUEST['req'])) return;   // first time - nothing to do
62		$this->req=$_REQUEST['req'];
63
64		if (is_numeric($_REQUEST['repo'])) $this->selected=$_REQUEST['repo'];
65	}
66
67	/**
68	 * output appropriate html
69	 */
70	function html() {
71		global $conf;
72		ptln('<div id="config__manager">');
73		ptln(' <h1>'.$this->getLang('menu').'</h1>');
74		ptln($this->_donate());
75		ptln(' <fieldset>');
76		ptln('   <legend>'.$this->getLang('checkupdates').'</legend>');
77		$this->_form_open("checkupdates");
78		$this->_form_close('check');
79		if ($this->req=='checkupdates') {
80			$this->_checkupdates();
81		}
82		ptln(' </fieldset>');
83		ptln(' <fieldset>');
84		ptln('   <legend>Themes</legend>');
85		ptln('   <table class="inline">');
86		ptln('   <tr class="default"><td class="label" colspan="2">');
87		ptln('   <span class="outkey">'.$this->getLang('infos').'</span>');
88		ptln('   </td></tr>');
89		$n=0;
90		//cycles thru repositories urls
91		foreach ($this->repos['url'] as $url) {
92			ptln('    <tr class="search_hit"><td>');
93			$legend=($n == 0) ? $conf['title'] : $this->repos['url'][$n];
94			ptln('     <span><label><strong>'.$legend.'</strong></label></span>');
95			ptln('    </td>');
96			ptln('    <td class="value">');
97			$this->_form_open("fetch",$n);
98			$this->_form_close("fetch");
99			ptln('    </td></tr>');
100			//list requested theme
101			if ($n==$this->selected) {
102				ptln('    <tr class="default"><td colspan="2">');
103				if ($this->req=='install') $this->install($this->selected,$_REQUEST['name']);
104				if ($this->req=='upload' && $_REQUEST['name']) {
105					$info="";
106					if (isset($_REQUEST['author_info'])) {
107						$obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
108						$info .= "author=".strtr($_REQUEST['author_info'], $obfuscate)."\n";
109					}
110					if (isset($_REQUEST['url_info'])) $info .= "url=".$_REQUEST['url_info']."\n";
111					if (isset($_REQUEST['author_info'])) $info .= "description=".$_REQUEST['author_info'];
112					if (!$this->upload($_REQUEST['name'],$info)) msg($this->getLang('install_no'),-1);
113				}
114				if ($this->req=='delete' && $_REQUEST['name']) $this->_delete($_REQUEST['name']);
115				ptln('    </td></tr><tr><td colspan="2">');
116				$this->dolist($n);
117				ptln('    </td></tr>');
118			}
119			$n++;
120		}
121		ptln('   </table>');
122		ptln('  </fieldset>');
123		ptln('</div>');
124	}
125
126	/**
127	 * Connect to theme repository and list themes
128	 *
129	 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
130	 * @author     Samuele Tognini <samuele@netsons.org>
131	 */
132	function dolist($n) {
133		global $INFO;
134		if ($n === false) return;
135		//info.txt keys to parse
136		$keys=array('author','url','description');
137		$icos=explode(',',INDEXMENU_ICOS);
138		$turl="";
139		$info="";
140		//get list
141		$data=$this->_remotequery($this->repos['url'][$n]."/lib/plugins/tindexmenu/ajax.php?req=local");
142		$data=explode(",",$data);
143		//print themes
144		for ($i=3;$i<count($data);$i++) {
145			$theme=$data[$i];
146			$turl=$data[1].$data[2]."/".$theme;
147			ptln('     <em>'.$theme.'</em>');
148			ptln('    <div class="indexmenu_list_themes">');
149			ptln('     <div>');
150			//print images
151			foreach (array_slice($icos,0,8) as $ico) {
152				$ext = explode(".", $theme);
153				$ext=array_pop($ext);
154				$ext=($ext == $theme) ? '.gif' : ".$ext";
155				ptln('      <img src="'.$turl."/".$ico.$ext.'" title="'.$ico.'" alt="'.$ico.'" />');
156			}
157			ptln('      </div>');
158			//get theme info.txt
159			if ($info=$this->_remotequery($turl."/info.txt",false)) {
160				foreach ($keys as $key) {
161					if (!preg_match('/'.$key.'=(.*)/',$info,$out)) continue;
162					ptln("       <div>");
163					ptln("       <strong>".hsc($key).": </strong>".hsc($out[1]));
164					ptln("       </div>");
165				}
166			}
167			if ($n == 0) {
168				$act="upload";
169				if ($theme != "default" ) {
170					$this->_form_open("delete",$n);
171					ptln('       <input type="hidden" name="name" value="'.$theme.'" />');
172					$this->_form_close("delete");
173				}
174			} else {
175				$act="install";
176				ptln('      <a href="'.$this->repos['url'][$n]."$repo/lib/plugins/tindexmenu/ajax.php?req=send&amp;t=".$theme.'">Download</a>');
177			}
178			$this->_form_open($act,$n);
179			if ($n==0 && !is_file(INDEXMENU_IMG_ABSDIR."/".$theme."/info.txt")) {
180				ptln('       <div><strong>author:</strong><input type="text" name="author_info" value="'.$INFO["userinfo"]["name"].hsc(" <".$INFO["userinfo"]["mail"].">").'" size="50" maxlength="100" /><br />');
181				ptln('       <strong>url:</strong><input type="text" name="url_info" value="'.$this->repos['url'][$n].'" size="50" maxlength="200" /><br />');
182				ptln('       <strong>description:</strong><input type="text" name="description_info" value="" size="50" maxlength="200" /></div>');
183			}
184			ptln('       <input type="hidden" name="name" value="'.$theme.'" />');
185			$this->_form_close($act);
186			ptln('     <br /><br /></div>');
187		}
188	}
189
190	/**
191	 * Download and install themes
192	 *
193	 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
194	 * @author     Samuele Tognini <samuele@netsons.org>
195	 */
196	function install ($n,$name) {
197		$repo=$this->repos['url'][$n];
198		if (!isset($name)) return false;
199		$return=true;
200		if (!$absdir=$this->checktmpsubdir()) return;
201		$tmp=$absdir."/tmp";
202
203		//send theme list request
204		if (!$zipfile=io_download($repo."/lib/plugins/tindexmenu/ajax.php?req=send&t=".$name,"$tmp/",true)) {
205			msg($this->getLang('down_err').": $name",-1);
206			$return=false;
207		} else {
208			//create zip
209			$zip=new PclZip("$tmp/$zipfile");
210			$regexp="/^".$name."\/(info.txt)|(style.css)|(".str_replace(",","|",INDEXMENU_ICOS).")\.(gif|png|jpg)$/i";
211			$status=$zip->extract(PCLZIP_OPT_PATH,$absdir."/",PCLZIP_OPT_BY_PREG,$regexp);
212			//error
213			if ($status == 0) {
214				msg($this->getLang('zip_err')." $tmp/$zipfile: ".$zip->errorName(true),-1);
215				$return=false;
216			} else {
217				msg("<strong>$name</strong> ".$this->getLang('install_ok'),1);
218			}
219		}
220		//clean tmp
221		$this->_rm_dir($tmp);
222		return $return;
223	}
224
225
226	/**
227	 * Remove a directory
228	 *
229	 */
230	function _rm_dir($path) {
231		if (!is_string($path) || $path == "") return false;
232
233		if (is_dir($path)) {
234			if (!$dh = @opendir($path)) return false;
235
236			while ($f = readdir($dh)) {
237				if ($f == '..' || $f == '.') continue;
238				$this->_rm_dir("$path/$f");
239			}
240
241			closedir($dh);
242			return @rmdir($path);
243		} else {
244			return @unlink($path);
245		}
246
247		return false;
248	}
249
250	/**
251	 * Retrive and create themes tmp directory
252	 *
253	 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
254	 * @author     Samuele Tognini <samuele@netsons.org>
255	 */
256	function checktmpsubdir () {
257		$tmp=INDEXMENU_IMG_ABSDIR."/tmp";
258		if (!io_mkdir_p($tmp)) {
259			msg($this->getLang('dir_err').": $tmp",-1);
260			return false;
261		}
262		return INDEXMENU_IMG_ABSDIR;
263	}
264
265
266	/**
267	 * Upload a theme into my site
268	 *
269	 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
270	 * @author     Samuele Tognini <samuele@netsons.org>
271	 */
272	function upload($theme,$info) {
273		$return=true;
274		$host='samuele.netsons.org';
275		$path='/dokuwiki/lib/plugins/tindexmenu/upload/index.php';
276		//TODO: merge zip creation with that in ajax.php (create a class?)
277		if (!$absdir=$this->checktmpsubdir()) return false;
278		$tmp=$absdir."/tmp";
279		$zipfile="$theme.zip";
280		$filelist="$absdir/$theme";
281		//create info
282		if (!empty($info)) {
283			io_savefile("$tmp/$theme/info.txt",$info);
284			$filelist .= ",$tmp/$theme";
285		}
286		//create zip
287		$zip=new PclZip("$tmp/$zipfile");
288		$status=$zip->create($filelist,PCLZIP_OPT_REMOVE_ALL_PATH);
289		if ($status == 0) {
290			//error
291			msg($this->getLang('zip_err').": ".$zip->errorName(true),-1);
292			$return=false;
293		} else {
294			//prepare POST headers.
295			$boundary = "---------------------------".uniqid("");
296			$data=join("", file("$tmp/$zipfile"));
297			$header="POST $path HTTP/1.0\r\n";
298			$header.= "Host: $host\r\n";
299			$header.= "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Iceweasel/2.0 (Debian-2.0+dfsg-1)\r\n";
300			$header.= "Content-type: multipart/form-data, boundary=$boundary\r\n";
301			$body="--".$boundary."\r\n";
302			$body.= "Content-Disposition: form-data; name=\"userfile\"; filename=\"$zipfile\"\r\n";
303			$body.= "Content-Type: application/x-zip-compressed\r\n\r\n";
304			$body.=$data."\r\n";
305			$body.= "--".$boundary."\r\n";
306			$body.= "Content-Disposition: form-data; name=\"upload\"\r\n\r\n";
307			$body.= "Upload\r\n";
308			$body.="--".$boundary."--\r\n";
309			$header.= "Content-Length: ".strlen($body)."\r\n\r\n";
310
311			//connect and send zip
312			if ($fp = fsockopen($host, 80)) {
313				fwrite($fp,$header.$body);
314				//reply
315				$buf="";
316				while (!feof($fp)) {
317					$buf .= fgets($fp,3200);
318				}
319				fclose($fp);
320				//parse reply
321				if (preg_match("/<!--indexmenu-->(.*)<!--\/indexmenu-->/s",$buf,$match)) {
322					$str=substr($match[1],4,7);
323					switch ($str) {
324						case "ERROR  ":
325							$mesg_type=-1;
326							break;
327						case "SUCCESS":
328							$mesg_type=1;
329							break;
330						default:
331							$mesg_type=2;
332					}
333					msg($match[1],$mesg_type);
334				} else {
335					$return=false;
336				}
337			} else {
338				$return=false;
339			}
340		}
341
342		$this->_rm_dir($tmp);
343		return $return;
344	}
345
346	/**
347	 * Check for new messages from upstream
348	 *
349	 * @author Samuele Tognini <samuele@netsons.org>
350	 */
351	function _checkupdates() {
352		// We dont have checks for updates yet
353		;
354	}
355
356
357	/**
358	 * Get url response and check it
359	 *
360	 * @author Samuele Tognini <samuele@netsons.org>
361	 */
362	function _remotequery($url,$tag=true) {
363		require_once (DOKU_INC.'inc/HTTPClient.php');
364		$http = new DokuHTTPClient();
365		$http->timeout = 8;
366		$data = $http->get($url);
367		if ($tag) {
368			if ($data===false) {
369				msg($this->getLang('conn_err'),-1);
370			} else {
371				(substr($data,0,9)==="indexmenu") ? $data=substr($data,9): $data="";
372			}
373		}
374		return $data;
375	}
376
377
378	/**
379	 * Open an html form
380	 *
381	 * @author Samuele Tognini <samuele@netsons.org>
382	 */
383	function _form_open($act,$n=-1) {
384		ptln('     <form action="'.wl($ID).'" method="post">');
385		ptln('      <input type="hidden" name="do" value="admin" />');
386		ptln('      <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
387		ptln('      <input type="hidden" name="req" value="'.$act.'" />');
388		ptln('      <input type="hidden" name="repo" value="'.$n.'" />');
389	}
390
391	/**
392	 * Close the html form
393	 *
394	 * @author Samuele Tognini <samuele@netsons.org>
395	 */
396	function _form_close($act) {
397		ptln('      <input type="submit" name="btn" '.$this->repos['status'][$n].' value="'.$this->getLang($act).'" />');
398		ptln('     </form>');
399	}
400
401	/**
402	 * Remove an installed theme
403	 *
404	 * @author Samuele Tognini <samuele@netsons.org>
405	 */
406	function _delete($theme) {
407		if ($theme=="default") return;
408		if ($this->_rm_dir(INDEXMENU_IMG_ABSDIR."/".utf8_encodeFN(basename($theme)))) {
409			msg($this->getLang('delete_ok').": $theme.",1);
410		} else {
411			msg($this->getLang('delete_no').": $theme.",-1);
412		}
413	}
414
415	/**
416	 * Print the donate button.
417	 *
418	 * @author Samuele Tognini <samuele@netsons.org>
419	 */
420	function _donate() {
421		$out = "<fieldset>\n";
422		$out .= '<p>'.$this->getLang('donation_text').'</p>';
423		$out .= '<form action="https://www.paypal.com/cgi-bin/webscr" method="post">'."\n";
424		$out .= '<input type="hidden" name="cmd" value="_s-xclick" />'."\n";
425		$out .= '<input type="hidden" name="hosted_button_id" value="102873" />'."\n";
426		$out .= '<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" name="submit" alt="" />'."\n";
427		$out .= '<img alt="" src="https://www.paypal.com/it_IT/i/scr/pixel.gif" width="1" height="1" />'."\n";
428		$out .= "</form></fieldset>\n";
429		return $out;
430	}
431
432}
433