PageRenderTime 44ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/plugins/plugin/classes/ap_download.class.php

https://gitlab.com/michield/dokuwiki
PHP | 288 lines | 184 code | 43 blank | 61 comment | 64 complexity | 25745d8abfd595c30b23d5d915d77db8 MD5 | raw file
  1. <?php
  2. class ap_download extends ap_manage {
  3. var $overwrite = true;
  4. /**
  5. * Initiate the plugin download
  6. */
  7. function process() {
  8. global $INPUT;
  9. $plugin_url = $INPUT->str('url');
  10. $this->download($plugin_url, $this->overwrite);
  11. return '';
  12. }
  13. /**
  14. * Print results of the download
  15. */
  16. function html() {
  17. parent::html();
  18. ptln('<div class="pm_info">');
  19. ptln('<h2>'.$this->lang['downloading'].'</h2>');
  20. if ($this->manager->error) {
  21. ptln('<div class="error">'.str_replace("\n","<br />",$this->manager->error).'</div>');
  22. } else if (count($this->downloaded) == 1) {
  23. ptln('<p>'.sprintf($this->lang['downloaded'],$this->downloaded[0]).'</p>');
  24. } else if (count($this->downloaded)) { // more than one plugin in the download
  25. ptln('<p>'.$this->lang['downloads'].'</p>');
  26. ptln('<ul>');
  27. foreach ($this->downloaded as $plugin) {
  28. ptln('<li><div class="li">'.$plugin.'</div></li>',2);
  29. }
  30. ptln('</ul>');
  31. } else { // none found in download
  32. ptln('<p>'.$this->lang['download_none'].'</p>');
  33. }
  34. ptln('</div>');
  35. }
  36. /**
  37. * Process the downloaded file
  38. */
  39. function download($url, $overwrite=false) {
  40. // check the url
  41. $matches = array();
  42. if (!preg_match("/[^\/]*$/", $url, $matches) || !$matches[0]) {
  43. $this->manager->error = $this->lang['error_badurl']."\n";
  44. return false;
  45. }
  46. $file = $matches[0];
  47. if (!($tmp = io_mktmpdir())) {
  48. $this->manager->error = $this->lang['error_dircreate']."\n";
  49. return false;
  50. }
  51. if (!$file = io_download($url, "$tmp/", true, $file, 0)) {
  52. $this->manager->error = sprintf($this->lang['error_download'],$url)."\n";
  53. }
  54. if (!$this->manager->error && !$this->decompress("$tmp/$file", $tmp)) {
  55. $this->manager->error = sprintf($this->lang['error_decompress'],$file)."\n";
  56. }
  57. // search $tmp for the folder(s) that has been created
  58. // move the folder(s) to lib/plugins/
  59. if (!$this->manager->error) {
  60. $result = array('old'=>array(), 'new'=>array());
  61. if($this->find_folders($result,$tmp)){
  62. // choose correct result array
  63. if(count($result['new'])){
  64. $install = $result['new'];
  65. }else{
  66. $install = $result['old'];
  67. }
  68. // now install all found items
  69. foreach($install as $item){
  70. // where to install?
  71. if($item['type'] == 'template'){
  72. $target = DOKU_INC.'lib/tpl/'.$item['base'];
  73. }else{
  74. $target = DOKU_INC.'lib/plugins/'.$item['base'];
  75. }
  76. // check to make sure we aren't overwriting anything
  77. if (!$overwrite && @file_exists($target)) {
  78. // remember our settings, ask the user to confirm overwrite, FIXME
  79. continue;
  80. }
  81. $instruction = @file_exists($target) ? 'update' : 'install';
  82. // copy action
  83. if ($this->dircopy($item['tmp'], $target)) {
  84. $this->downloaded[] = $item['base'];
  85. $this->plugin_writelog($target, $instruction, array($url));
  86. } else {
  87. $this->manager->error .= sprintf($this->lang['error_copy']."\n", $item['base']);
  88. }
  89. }
  90. } else {
  91. $this->manager->error = $this->lang['error']."\n";
  92. }
  93. }
  94. // cleanup
  95. if ($tmp) $this->dir_delete($tmp);
  96. if (!$this->manager->error) {
  97. msg(sprintf($this->lang['packageinstalled'], count($this->downloaded), join(',',$this->downloaded)),1);
  98. $this->refresh();
  99. return true;
  100. }
  101. return false;
  102. }
  103. /**
  104. * Find out what was in the extracted directory
  105. *
  106. * Correct folders are searched recursively using the "*.info.txt" configs
  107. * as indicator for a root folder. When such a file is found, it's base
  108. * setting is used (when set). All folders found by this method are stored
  109. * in the 'new' key of the $result array.
  110. *
  111. * For backwards compatibility all found top level folders are stored as
  112. * in the 'old' key of the $result array.
  113. *
  114. * When no items are found in 'new' the copy mechanism should fall back
  115. * the 'old' list.
  116. *
  117. * @author Andreas Gohr <andi@splitbrain.org>
  118. * @param arrayref $result - results are stored here
  119. * @param string $base - the temp directory where the package was unpacked to
  120. * @param string $dir - a subdirectory. do not set. used by recursion
  121. * @return bool - false on error
  122. */
  123. function find_folders(&$result,$base,$dir=''){
  124. $dh = @opendir("$base/$dir");
  125. if(!$dh) return false;
  126. while (false !== ($f = readdir($dh))) {
  127. if ($f == '.' || $f == '..' || $f == 'tmp') continue;
  128. if(!is_dir("$base/$dir/$f")){
  129. // it's a file -> check for config
  130. if($f == 'plugin.info.txt'){
  131. $info = array();
  132. $info['type'] = 'plugin';
  133. $info['tmp'] = "$base/$dir";
  134. $conf = confToHash("$base/$dir/$f");
  135. $info['base'] = utf8_basename($conf['base']);
  136. if(!$info['base']) $info['base'] = utf8_basename("$base/$dir");
  137. $result['new'][] = $info;
  138. }elseif($f == 'template.info.txt'){
  139. $info = array();
  140. $info['type'] = 'template';
  141. $info['tmp'] = "$base/$dir";
  142. $conf = confToHash("$base/$dir/$f");
  143. $info['base'] = utf8_basename($conf['base']);
  144. if(!$info['base']) $info['base'] = utf8_basename("$base/$dir");
  145. $result['new'][] = $info;
  146. }
  147. }else{
  148. // it's a directory -> add to dir list for old method, then recurse
  149. if(!$dir){
  150. $info = array();
  151. $info['type'] = 'plugin';
  152. $info['tmp'] = "$base/$dir/$f";
  153. $info['base'] = $f;
  154. $result['old'][] = $info;
  155. }
  156. $this->find_folders($result,$base,"$dir/$f");
  157. }
  158. }
  159. closedir($dh);
  160. return true;
  161. }
  162. /**
  163. * Decompress a given file to the given target directory
  164. *
  165. * Determines the compression type from the file extension
  166. */
  167. function decompress($file, $target) {
  168. global $conf;
  169. // decompression library doesn't like target folders ending in "/"
  170. if (substr($target, -1) == "/") $target = substr($target, 0, -1);
  171. $ext = $this->guess_archive($file);
  172. if (in_array($ext, array('tar','bz','gz'))) {
  173. switch($ext){
  174. case 'bz':
  175. $compress_type = Tar::COMPRESS_BZIP;
  176. break;
  177. case 'gz':
  178. $compress_type = Tar::COMPRESS_GZIP;
  179. break;
  180. default:
  181. $compress_type = Tar::COMPRESS_NONE;
  182. }
  183. $tar = new Tar();
  184. try {
  185. $tar->open($file, $compress_type);
  186. $tar->extract($target);
  187. return true;
  188. }catch(Exception $e){
  189. if($conf['allowdebug']){
  190. msg('Tar Error: '.$e->getMessage().' ['.$e->getFile().':'.$e->getLine().']',-1);
  191. }
  192. return false;
  193. }
  194. } else if ($ext == 'zip') {
  195. $zip = new ZipLib();
  196. $ok = $zip->Extract($file, $target);
  197. // FIXME sort something out for handling zip error messages meaningfully
  198. return ($ok==-1?false:true);
  199. }
  200. // unsupported file type
  201. return false;
  202. }
  203. /**
  204. * Determine the archive type of the given file
  205. *
  206. * Reads the first magic bytes of the given file for content type guessing,
  207. * if neither bz, gz or zip are recognized, tar is assumed.
  208. *
  209. * @author Andreas Gohr <andi@splitbrain.org>
  210. * @returns boolean|string false if the file can't be read, otherwise an "extension"
  211. */
  212. function guess_archive($file){
  213. $fh = fopen($file,'rb');
  214. if(!$fh) return false;
  215. $magic = fread($fh,5);
  216. fclose($fh);
  217. if(strpos($magic,"\x42\x5a") === 0) return 'bz';
  218. if(strpos($magic,"\x1f\x8b") === 0) return 'gz';
  219. if(strpos($magic,"\x50\x4b\x03\x04") === 0) return 'zip';
  220. return 'tar';
  221. }
  222. /**
  223. * Copy with recursive sub-directory support
  224. */
  225. function dircopy($src, $dst) {
  226. global $conf;
  227. if (is_dir($src)) {
  228. if (!$dh = @opendir($src)) return false;
  229. if ($ok = io_mkdir_p($dst)) {
  230. while ($ok && (false !== ($f = readdir($dh)))) {
  231. if ($f == '..' || $f == '.') continue;
  232. $ok = $this->dircopy("$src/$f", "$dst/$f");
  233. }
  234. }
  235. closedir($dh);
  236. return $ok;
  237. } else {
  238. $exists = @file_exists($dst);
  239. if (!@copy($src,$dst)) return false;
  240. if (!$exists && !empty($conf['fperm'])) chmod($dst, $conf['fperm']);
  241. @touch($dst,filemtime($src));
  242. }
  243. return true;
  244. }
  245. }