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&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