From 0ef95f3d2c295b225c3d3e09786d7e28abcdfe95 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 25 Jul 2009 17:21:05 +0200 Subject: restructured plugin manager Ignore-this: 4007248aa01f09990612c844c8a83900 This patch moves the different classes of the plugin manager into their own files and moves formerly global utility functions into the appropriate class scopes. darcs-hash:20090725152105-7ad00-89801e811b7eb0d0db25a825d6065aed8ef95c33.gz --- lib/plugins/plugin/classes/ap_delete.class.php | 28 ++++ lib/plugins/plugin/classes/ap_download.class.php | 201 ++++++++++++++++++++++ lib/plugins/plugin/classes/ap_enable.class.php | 49 ++++++ lib/plugins/plugin/classes/ap_info.class.php | 129 ++++++++++++++ lib/plugins/plugin/classes/ap_manage.class.php | 203 +++++++++++++++++++++++ lib/plugins/plugin/classes/ap_update.class.php | 38 +++++ 6 files changed, 648 insertions(+) create mode 100644 lib/plugins/plugin/classes/ap_delete.class.php create mode 100644 lib/plugins/plugin/classes/ap_download.class.php create mode 100644 lib/plugins/plugin/classes/ap_enable.class.php create mode 100644 lib/plugins/plugin/classes/ap_info.class.php create mode 100644 lib/plugins/plugin/classes/ap_manage.class.php create mode 100644 lib/plugins/plugin/classes/ap_update.class.php (limited to 'lib/plugins/plugin/classes') diff --git a/lib/plugins/plugin/classes/ap_delete.class.php b/lib/plugins/plugin/classes/ap_delete.class.php new file mode 100644 index 000000000..231147479 --- /dev/null +++ b/lib/plugins/plugin/classes/ap_delete.class.php @@ -0,0 +1,28 @@ +dir_delete(DOKU_PLUGIN.plugin_directory($this->manager->plugin))) { + $this->manager->error = sprintf($this->lang['error_delete'],$this->manager->plugin); + } else { + msg("Plugin {$this->manager->plugin} successfully deleted."); + $this->refresh(); + } + } + + function html() { + parent::html(); + + ptln('
'); + ptln('

'.$this->lang['deleting'].'

'); + + if ($this->manager->error) { + ptln('
'.str_replace("\n","
",$this->manager->error).'
'); + } else { + ptln('

'.sprintf($this->lang['deleted'],$this->plugin).'

'); + } + ptln('
'); + } +} + diff --git a/lib/plugins/plugin/classes/ap_download.class.php b/lib/plugins/plugin/classes/ap_download.class.php new file mode 100644 index 000000000..465cb2da1 --- /dev/null +++ b/lib/plugins/plugin/classes/ap_download.class.php @@ -0,0 +1,201 @@ +download($plugin_url, $this->overwrite); + return ''; + } + + /** + * Print results of the download + */ + function html() { + parent::html(); + + ptln('
'); + ptln('

'.$this->lang['downloading'].'

'); + + if ($this->manager->error) { + ptln('
'.str_replace("\n","
",$this->manager->error).'
'); + } else if (count($this->downloaded) == 1) { + ptln('

'.sprintf($this->lang['downloaded'],$this->downloaded[0]).'

'); + } else if (count($this->downloaded)) { // more than one plugin in the download + ptln('

'.$this->lang['downloads'].'

'); + ptln(''); + } else { // none found in download + ptln('

'.$this->lang['download_none'].'

'); + } + ptln('
'); + } + + /** + * Process the downloaded file + */ + function download($url, $overwrite=false) { + global $lang; + + // check the url + $matches = array(); + if (!preg_match("/[^\/]*$/", $url, $matches) || !$matches[0]) { + $this->manager->error = $this->lang['error_badurl']."\n"; + return false; + } + + $file = $matches[0]; + + if (!($tmp = io_mktmpdir())) { + $this->manager->error = $this->lang['error_dircreate']."\n"; + return false; + } + + if (!$file = io_download($url, "$tmp/", true, $file)) { + $this->manager->error = sprintf($this->lang['error_download'],$url)."\n"; + } + + if (!$this->manager->error && !$this->decompress("$tmp/$file", $tmp)) { + $this->manager->error = sprintf($this->lang['error_decompress'],$file)."\n"; + } + + // search $tmp for the folder(s) that has been created + // move the folder(s) to lib/plugins/ + if (!$this->manager->error) { + if ($dh = @opendir("$tmp/")) { + while (false !== ($f = readdir($dh))) { + if ($f == '.' || $f == '..' || $f == 'tmp') continue; + if (!is_dir("$tmp/$f")) continue; + + // check to make sure we aren't overwriting anything + if (!$overwrite && @file_exists(DOKU_PLUGIN.$f)) { + // remember our settings, ask the user to confirm overwrite, FIXME + continue; + } + + $instruction = @file_exists(DOKU_PLUGIN.$f) ? 'update' : 'install'; + + if ($this->dircopy("$tmp/$f", DOKU_PLUGIN.$f)) { + $this->downloaded[] = $f; + $this->plugin_writelog($f, $instruction, array($url)); + } else { + $this->manager->error .= sprintf($this->lang['error_copy']."\n", $f); + } + } + closedir($dh); + } else { + $this->manager->error = $this->lang['error']."\n"; + } + } + + // cleanup + if ($tmp) $this->dir_delete($tmp); + + if (!$this->manager->error) { + msg('Plugin package ('.count($this->downloaded).' plugin'.(count($this->downloaded) != 1?'s':'').': '.join(',',$this->downloaded).') successfully installed.',1); + $this->refresh(); + return true; + } + + return false; + } + + + /** + * Decompress a given file to the given target directory + * + * Determines the compression type from the file extension + */ + function decompress($file, $target) { + global $conf; + + // decompression library doesn't like target folders ending in "/" + if (substr($target, -1) == "/") $target = substr($target, 0, -1); + $ext = substr($file, strrpos($file,'.')+1); + + // .tar, .tar.bz, .tar.gz, .tgz + if (in_array($ext, array('tar','bz','bz2','gz','tgz'))) { + + require_once(DOKU_INC."inc/TarLib.class.php"); + + if (strpos($ext, 'bz') !== false) $compress_type = COMPRESS_BZIP; + else if (strpos($ext,'gz') !== false) $compress_type = COMPRESS_GZIP; + else $compress_type = COMPRESS_NONE; + + $tar = new TarLib($file, $compress_type); + if($tar->_initerror < 0){ + if($conf['allowdebug']){ + msg('TarLib Error: '.$tar->TarErrorStr($tar->_initerror),-1); + } + return false; + } + $ok = $tar->Extract(FULL_ARCHIVE, $target, '', 0777); + + if($ok<1){ + if($conf['allowdebug']){ + msg('TarLib Error: '.$tar->TarErrorStr($ok),-1); + } + return false; + } + return true; + } else if ($ext == 'zip') { + + require_once(DOKU_INC."inc/ZipLib.class.php"); + + $zip = new ZipLib(); + $ok = $zip->Extract($file, $target); + + // FIXME sort something out for handling zip error messages meaningfully + return ($ok==-1?false:true); + + } else if ($ext == "rar") { + // not yet supported -- fix me + return false; + } + + // unsupported file type + return false; + } + + /** + * Copy with recursive sub-directory support + */ + function dircopy($src, $dst) { + global $conf; + + if (is_dir($src)) { + if (!$dh = @opendir($src)) return false; + + if ($ok = io_mkdir_p($dst)) { + while ($ok && (false !== ($f = readdir($dh)))) { + if ($f == '..' || $f == '.') continue; + $ok = $this->dircopy("$src/$f", "$dst/$f"); + } + } + + closedir($dh); + return $ok; + + } else { + $exists = @file_exists($dst); + + if (!@copy($src,$dst)) return false; + if (!$exists && !empty($conf['fperm'])) chmod($dst, $conf['fperm']); + @touch($dst,filemtime($src)); + } + + return true; + } + + +} + diff --git a/lib/plugins/plugin/classes/ap_enable.class.php b/lib/plugins/plugin/classes/ap_enable.class.php new file mode 100644 index 000000000..35450a907 --- /dev/null +++ b/lib/plugins/plugin/classes/ap_enable.class.php @@ -0,0 +1,49 @@ +enabled = isset($_REQUEST['enabled']) ? $_REQUEST['enabled'] : array(); + + foreach ($this->manager->plugin_list as $plugin) { + if (in_array($plugin, $plugin_protected)) continue; + + $new = in_array($plugin, $this->enabled); + $old = !plugin_isdisabled($plugin); + + if ($new != $old) { + switch ($new) { + // enable plugin + case true : + if(plugin_enable($plugin)){ + msg(sprintf($this->lang['enabled'],$plugin),1); + $count_enabled++; + }else{ + msg(sprintf($this->lang['notenabled'],$plugin),-1); + } + break; + case false: + if(plugin_disable($plugin)){ + msg(sprintf($this->lang['disabled'],$plugin),1); + $count_disabled++; + }else{ + msg(sprintf($this->lang['notdisabled'],$plugin),-1); + } + break; + } + } + } + + // refresh plugins, including expiring any dokuwiki cache(s) + if ($count_enabled || $count_disabled) { + $this->refresh(); + } + } + +} + diff --git a/lib/plugins/plugin/classes/ap_info.class.php b/lib/plugins/plugin/classes/ap_info.class.php new file mode 100644 index 000000000..dd1765f6c --- /dev/null +++ b/lib/plugins/plugin/classes/ap_info.class.php @@ -0,0 +1,129 @@ +manager->plugin) { return; } + + $component_list = $this->get_plugin_components($this->manager->plugin); + usort($component_list, array($this,'component_sort')); + + foreach ($component_list as $component) { + if ($obj = & plugin_load($component['type'],$component['name']) === NULL) continue; + + $this->details[] = array_merge($obj->getInfo(), array('type' => $component['type'])); + unset($obj); + } + + // review details to simplify things + foreach($this->details as $info) { + foreach($info as $item => $value) { + if (!isset($this->plugin_info[$item])) { $this->plugin_info[$item] = $value; continue; } + if ($this->plugin_info[$item] != $value) $this->plugin_info[$item] = ''; + } + } + } + + function html() { + + // output the standard menu stuff + parent::html(); + + // sanity check + if (!$this->manager->plugin) { return; } + + ptln('
'); + ptln("

".$this->manager->getLang('plugin')." {$this->manager->plugin}

"); + + // collect pertinent information from the log + $installed = $this->plugin_readlog($this->manager->plugin, 'installed'); + $source = $this->plugin_readlog($this->manager->plugin, 'url'); + $updated = $this->plugin_readlog($this->manager->plugin, 'updated'); + if (strrpos($updated, "\n") !== false) $updated = substr($updated, strrpos($updated, "\n")+1); + + ptln("
",2); + ptln("
".$this->manager->getLang('source').'
'.($source ? $source : $this->manager->getLang('unknown'))."
",4); + ptln("
".$this->manager->getLang('installed').'
'.($installed ? $installed : $this->manager->getLang('unknown'))."
",4); + if ($updated) ptln("
".$this->manager->getLang('lastupdate').'
'.$updated."
",4); + ptln("
",2); + + if (count($this->details) == 0) { + ptln("

".$this->manager->getLang('noinfo')."

",2); + } else { + + ptln("
",2); + if ($this->plugin_info['name']) ptln("
".$this->manager->getLang('name')."
".$this->out($this->plugin_info['name'])."
",4); + if ($this->plugin_info['date']) ptln("
".$this->manager->getLang('date')."
".$this->out($this->plugin_info['date'])."
",4); + if ($this->plugin_info['type']) ptln("
".$this->manager->getLang('type')."
".$this->out($this->plugin_info['type'])."
",4); + if ($this->plugin_info['desc']) ptln("
".$this->manager->getLang('desc')."
".$this->out($this->plugin_info['desc'])."
",4); + if ($this->plugin_info['author']) ptln("
".$this->manager->getLang('author')."
".$this->manager->email($this->plugin_info['email'], $this->plugin_info['author'])."
",4); + if ($this->plugin_info['url']) ptln("
".$this->manager->getLang('www')."
".$this->manager->external_link($this->plugin_info['url'], '', 'urlextern')."
",4); + ptln("
",2); + + if (count($this->details) > 1) { + ptln("

".$this->manager->getLang('components')."

",2); + ptln("
",2); + + foreach ($this->details as $info) { + + ptln("
",4); + ptln("
".$this->manager->getLang('name')."
".$this->out($info['name'])."
",6); + if (!$this->plugin_info['date']) ptln("
".$this->manager->getLang('date')."
".$this->out($info['date'])."
",6); + if (!$this->plugin_info['type']) ptln("
".$this->manager->getLang('type')."
".$this->out($info['type'])."
",6); + if (!$this->plugin_info['desc']) ptln("
".$this->manager->getLang('desc')."
".$this->out($info['desc'])."
",6); + if (!$this->plugin_info['author']) ptln("
".$this->manager->getLang('author')."
".$this->manager->email($info['email'], $info['author'])."
",6); + if (!$this->plugin_info['url']) ptln("
".$this->manager->getLang('www')."
".$this->manager->external_link($info['url'], '', 'urlextern')."
",6); + ptln("
",4); + + } + ptln("
",2); + } + } + ptln("
"); + } + + // simple output filter, make html entities safe and convert new lines to
+ function out($text) { + return str_replace("\n",'
',htmlspecialchars($text)); + } + + + /** + * return a list (name & type) of all the component plugins that make up this plugin + * + * @todo can this move to pluginutils? + */ + function get_plugin_components($plugin) { + + global $plugin_types; + + $components = array(); + $path = DOKU_PLUGIN.plugin_directory($plugin).'/'; + + foreach ($plugin_types as $type) { + if (@file_exists($path.$type.'.php')) { $components[] = array('name'=>$plugin, 'type'=>$type); continue; } + + if ($dh = @opendir($path.$type.'/')) { + while (false !== ($cp = readdir($dh))) { + if ($cp == '.' || $cp == '..' || strtolower(substr($cp,-4)) != '.php') continue; + $components[] = array('name'=>$plugin.'_'.substr($cp, 0, -4), 'type'=>$type); + } + closedir($dh); + } + } + return $components; + } + + /** + * usort callback to sort plugin components + */ + function component_sort($a, $b) { + if ($a['name'] == $b['name']) return 0; + return ($a['name'] < $b['name']) ? -1 : 1; + } +} diff --git a/lib/plugins/plugin/classes/ap_manage.class.php b/lib/plugins/plugin/classes/ap_manage.class.php new file mode 100644 index 000000000..aea04f487 --- /dev/null +++ b/lib/plugins/plugin/classes/ap_manage.class.php @@ -0,0 +1,203 @@ +manager = & $manager; + $this->plugin = $plugin; + $this->lang = & $manager->lang; + } + + function process() { + return ''; + } + + function html() { + print $this->manager->locale_xhtml('admin_plugin'); + $this->html_menu(); + } + + // build our standard menu + function html_menu($listPlugins = true) { + global $ID; + + ptln('
'); + + ptln('
'); + ptln('

'.$this->lang['download'].'

'); + ptln('
'); + ptln(' '); + ptln('
'); + ptln(' '.$this->lang['download'].''); + ptln(' '); + ptln(' '); + ptln('
'); + ptln('
'); + ptln('
'); + + if ($listPlugins) { + ptln('

'.$this->lang['manage'].'

'); + + ptln('
'); + + ptln(' '); + + $this->html_pluginlist(); + + ptln('
'); + ptln(' '); + ptln('
'); + + // ptln('
'); + ptln(''); + } + + ptln(''); + } + + function html_pluginlist() { + global $ID; + global $plugin_protected; + + foreach ($this->manager->plugin_list as $plugin) { + + $disabled = plugin_isdisabled($plugin); + $protected = in_array($plugin,$plugin_protected); + + $checked = ($disabled) ? '' : ' checked="checked"'; + $check_disabled = ($protected) ? ' disabled="disabled"' : ''; + + // determine display class(es) + $class = array(); + if (in_array($plugin, $this->downloaded)) $class[] = 'new'; + if ($disabled) $class[] = 'disabled'; + if ($protected) $class[] = 'protected'; + + $class = count($class) ? ' class="'.join(' ', $class).'"' : ''; + + ptln(' '); + ptln(' '.$plugin.''); + ptln(' '); + ptln('

'.$plugin.'

'); + + $this->html_button($plugin, 'info', false, 6); + if (in_array('settings', $this->manager->functions)) { + $this->html_button($plugin, 'settings', !@file_exists(DOKU_PLUGIN.$plugin.'/settings.php'), 6); + } + $this->html_button($plugin, 'update', !$this->plugin_readlog($plugin, 'url'), 6); + $this->html_button($plugin, 'delete', $protected, 6); + + ptln(' '); + } + } + + function html_button($plugin, $btn, $disabled=false, $indent=0) { + $disabled = ($disabled) ? 'disabled="disabled"' : ''; + ptln('',$indent); + } + + /** + * Refresh plugin list + */ + function refresh() { + global $MSG,$config_cascade; + + //are there any undisplayed messages? keep them in session for display + if (isset($MSG) && count($MSG)){ + //reopen session, store data and close session again + @session_start(); + $_SESSION[DOKU_COOKIE]['msg'] = $MSG; + session_write_close(); + } + + // expire dokuwiki caches + // touching local.php expires wiki page, JS and CSS caches + @touch(reset($config_cascade['main']['local'])); + + // update latest plugin date - FIXME + header('Location: '.wl($ID).'?do=admin&page=plugin'); + exit(); + } + + // log + function plugin_writelog($plugin, $cmd, $data) { + + $file = DOKU_PLUGIN.$plugin.'/manager.dat'; + + switch ($cmd) { + case 'install' : + $url = $data[0]; + $date = date('r'); + if (!$fp = @fopen($file, 'w')) return; + fwrite($fp, "installed=$date\nurl=$url\n"); + fclose($fp); + break; + + case 'update' : + $date = date('r'); + if (!$fp = @fopen($file, 'a')) return; + fwrite($fp, "updated=$date\n"); + fclose($fp); + break; + } + } + + function plugin_readlog($plugin, $field) { + static $log = array(); + $file = DOKU_PLUGIN.plugin_directory($plugin).'/manager.dat'; + + if (!isset($log[$plugin])) { + $tmp = @file_get_contents($file); + if (!$tmp) return ''; + $log[$plugin] = & $tmp; + } + + if ($field == 'ALL') { + return $log[$plugin]; + } + + $match = array(); + if (preg_match_all('/'.$field.'=(.*)$/m',$log[$plugin], $match)) + return implode("\n", $match[1]); + + return ''; + } + + /** + * delete, with recursive sub-directory support + */ + function dir_delete($path) { + if (!is_string($path) || $path == "") return false; + + if (is_dir($path)) { + if (!$dh = @opendir($path)) return false; + + while ($f = readdir($dh)) { + if ($f == '..' || $f == '.') continue; + $this->dir_delete("$path/$f"); + } + + closedir($dh); + return @rmdir($path); + } else { + return @unlink($path); + } + + return false; + } + + +} diff --git a/lib/plugins/plugin/classes/ap_update.class.php b/lib/plugins/plugin/classes/ap_update.class.php new file mode 100644 index 000000000..9ac002991 --- /dev/null +++ b/lib/plugins/plugin/classes/ap_update.class.php @@ -0,0 +1,38 @@ +plugin_readlog($this->plugin, 'url'); + $this->download($plugin_url, $this->overwrite); + return ''; + } + + function html() { + parent::html(); + + ptln('
'); + ptln('

'.$this->lang['updating'].'

'); + + if ($this->manager->error) { + ptln('
'.str_replace("\n","
", $this->manager->error).'
'); + } else if (count($this->downloaded) == 1) { + ptln('

'.sprintf($this->lang['updated'],$this->downloaded[0]).'

'); + } else if (count($this->downloaded)) { // more than one plugin in the download + ptln('

'.$this->lang['updates'].'

'); + ptln('
    '); + foreach ($this->downloaded as $plugin) { + ptln('
  • '.$plugin.'
  • ',2); + } + ptln('
'); + } else { // none found in download + ptln('

'.$this->lang['update_none'].'

'); + } + ptln('
'); + } +} + -- cgit v1.2.3