summaryrefslogtreecommitdiff
path: root/lib/plugins/plugin/admin.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/plugins/plugin/admin.php')
-rw-r--r--lib/plugins/plugin/admin.php686
1 files changed, 686 insertions, 0 deletions
diff --git a/lib/plugins/plugin/admin.php b/lib/plugins/plugin/admin.php
new file mode 100644
index 000000000..536dc525b
--- /dev/null
+++ b/lib/plugins/plugin/admin.php
@@ -0,0 +1,686 @@
+<?php
+/**
+ * Plugin management functions
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+
+// todo
+// - maintain a history of file modified
+// - allow a plugin to contain extras to be copied to the current template (extra/tpl/)
+// - to images (lib/images/) [ not needed, should go in lib/plugin/images/ ]
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'admin.php');
+
+ // language stuff here for now ... move to language files when complete
+
+// global $lang;
+
+ //--------------------------[ GLOBALS ]------------------------------------------------
+ // note: probably should be dokuwiki wide globals, where it can be access by pluginutils.php
+ global $common_plugin_files, $common_plugin_types;
+ $common_plugin_types = array('syntax', 'admin');
+ $common_plugin_files = array("style.css", "screen.css", "print.css", "script.js");
+
+/**
+ * All DokuWiki plugins to extend the admin function
+ * need to inherit from this class
+ */
+class admin_plugin_plugin extends DokuWiki_Admin_Plugin {
+
+ var $disabled = 0;
+ var $plugin = '';
+ var $cmd = '';
+ var $handler = NULL;
+
+ var $functions = array('delete','update','settings','info'); // require a plugin name
+ var $commands = array('manage','refresh','download'); // don't require a plugin name
+ var $plugin_list = array();
+
+ var $msg = '';
+ var $error = '';
+
+ function admin_plugin_plugin() {
+ global $conf;
+ $this->disabled = (!isset($conf['pluginmanager']) || ($conf['pluginmanager'] == 0));
+ }
+
+ /**
+ * return some info
+ */
+ function getInfo(){
+ $disabled = ($this->disabled) ? '(disabled)' : '';
+
+ return array(
+ 'author' => 'Christopher Smith',
+ 'email' => 'chris@jalakai.co.uk',
+ 'date' => '2005-08-10',
+ 'name' => 'Plugin Manager',
+ 'desc' => "Manage Plugins, including automated plugin installer $disabled",
+ 'url' => 'http://wiki.splitbrain.org/plugin:adminplugin',
+ );
+ }
+
+ /**
+ * return prompt for admin menu
+ */
+ function getMenuText($language) {
+ if (!$this->disabled)
+ return parent::getMenuText($language);
+
+ return '';
+ }
+
+ /**
+ * return sort order for position in admin menu
+ */
+ function getMenuSort() {
+ return 20;
+ }
+
+ /**
+ * handle user request
+ */
+ function handle() {
+ global $ID, $lang;
+
+ if ($this->disabled) return;
+
+ $this->plugin = $_REQUEST['plugin'];
+ $this->cmd = $_REQUEST['fn'];
+ if (is_array($this->cmd)) $this->cmd = key($this->cmd);
+
+ sort($this->plugin_list = plugin_list());
+
+ // verify $_REQUEST vars
+ if (in_array($this->cmd, $this->commands)) {
+ $this->plugin = '';
+ } else if (!in_array($this->cmd, $this->functions) || !in_array($this->plugin, $this->plugin_list)) {
+ $this->cmd = 'manage';
+ $this->plugin = '';
+ }
+
+ // create object to handle the command
+ $class = "admin_plugin_".$this->cmd;
+ if (!class_exists($class)) $class = 'admin_plugin_manage';
+
+ $this->handler = & new $class($this, $plugin);
+ $this->msg = $this->handler->process();
+ }
+
+ /**
+ * output appropriate html
+ */
+ function html() {
+
+ if ($this->disabled) return;
+
+ // enable direct access to language strings
+// if (!$this->localised) $this->setupLocale();
+ $this->setupLocale();
+
+ if ($this->handler === NULL) $this->handler = & new admin_plugin_manage();
+ if (!$this->plugin_list) sort($this->plugin_list = plugin_list());
+
+ ptln('<div id="plugin_manager">');
+ $this->handler->html();
+ ptln('</div><!-- #plugin_manager -->');
+ }
+
+}
+
+class admin_plugin_manage {
+
+ var $manager = NULL;
+ var $lang = array();
+ var $plugin = '';
+ var $downloaded = array();
+
+ function admin_plugin_manage(&$manager, $plugin) {
+ $this->manager = & $manager;
+ $this->plugin = $plugin;
+ $this->lang = & $manager->lang;
+ }
+
+ function process() {
+ return '';
+ }
+
+ function html() {
+
+ print $this->manager->plugin_locale_xhtml('admin_plugin');
+
+ // FIXME, these probably shouldn't be here any more!
+ if (!$this->manager->msg) $this->manager->msg = '&nbsp;';
+ ptln("<p>{$manager->msg}</p>");
+
+ if ($this->manager->error) {
+ ptln("<p class='error'>".str_replace("\n","<br>",$this->manager->error)."</p>");
+ }
+
+ $this->html_menu();
+ }
+
+ // build our standard menu
+ function html_menu($listPlugins = true) {
+ global $ID;
+
+ ptln('<div class="pm_menu">');
+
+ ptln('<div class="common">');
+ ptln(' <form action="'.wl($ID).'" method="post">');
+ ptln(' <fieldset class="hidden">',4);
+ ptln(' <input type="hidden" name="do" value="admin" />');
+ ptln(' <input type="hidden" name="page" value="plugin" />');
+ ptln(' </fieldset>');
+ ptln(' <fieldset>');
+ ptln(' <legend>'.$this->lang['refresh'].'</legend>');
+ ptln(' <h3 class="legend">'.$this->lang['refresh'].'</h3>');
+ ptln(' <input type="submit" class="button" name="fn[refresh]" value="'.$this->lang['btn_refresh'].'" />');
+ ptln(' <p>'.$this->lang['refresh_x'].'</p>');
+ ptln(' </fieldset>');
+ ptln(' <fieldset>');
+ ptln(' <legend>'.$this->lang['download'].'</legend>');
+ ptln(' <h3 class="legend">'.$this->lang['download'].'</h3>');
+ ptln(' <input type="submit" class="button" name="fn[download]" value="'.$this->lang['btn_download'].'" />');
+ ptln(' <label for="url">'.$this->lang['url'].'<input name="url" id="url" class="field" type="text" maxlength="200" /></label>');
+ ptln(' </fieldset>');
+ ptln(' </form>');
+ ptln('</div>');
+
+ if ($listPlugins) {
+ ptln('<h2>'.$this->lang['manage'].'</h2>');
+ ptln('<div class="plugins">');
+ $this->html_pluginlist();
+ ptln('</div>');
+ }
+
+ ptln('</div>');
+ }
+
+ function html_pluginlist() {
+
+ foreach ($this->manager->plugin_list as $plugin) {
+
+ $new = (in_array($plugin, $this->downloaded)) ? ' class="new"' : '';
+
+ ptln(' <form action="'.wl($ID).'" method="post" '.$new.'>');
+ ptln(' <fieldset>');
+ ptln(' <legend>'.$plugin.'</legend>');
+ ptln(' <h3 class="legend">'.$plugin.'</h3>');
+ ptln(' <input type="hidden" name="do" value="admin" />');
+ ptln(' <input type="hidden" name="page" value="plugin" />');
+ ptln(' <input type="hidden" name="plugin" value="'.$plugin.'" />');
+
+ $this->html_button('delete', false, 6);
+ $this->html_button('update', !$this->plugin_readlog($plugin, 'url'), 6);
+ $this->html_button('settings', !@file_exists(DOKU_PLUGIN.$plugin.'/settings.php'), 6);
+ $this->html_button('info', false, 6);
+
+ ptln(' </fieldset>');
+ ptln(' </form>');
+ }
+ }
+
+ function html_button($btn, $disabled=false, $indent=0) {
+ $disabled = ($disabled) ? 'disabled="disabled"' : '';
+ ptln('<input type="submit" class="button" '.$disabled.' name="fn['.$btn.']" value="'.$this->lang['btn_'.$btn].'" />',$indent);
+ }
+
+ /**
+ * Rebuild aggregated files & update latest plugin date
+ */
+ function refresh() {
+ global $lang;
+ global $common_plugin_files;
+
+ sort($this->manager->plugin_list = plugin_list());
+
+ foreach ($common_plugin_files as $file) {
+ $aggregate = '';
+
+ // could replace with an class/object based aggregator,
+ // that way special files could have their own aggregator
+ foreach ($this->manager->plugin_list as $plugin) {
+ if (@file_exists(DOKU_PLUGIN."$plugin/$file")) {
+ $contents = @file_get_contents(DOKU_PLUGIN."$plugin/$file")."\n";
+
+ // url conversion for css files
+ if (is_css($file)) {
+ $contents = preg_replace('/(url\([\'\"]?)([^\/](?![a-zA-Z0-9]+:\/\/).*?)([\'\"]?\))/','$1'.$plugin.'/$2$3',$contents);
+ }
+
+ $aggregate .= $contents;
+ }
+ }
+
+ if (trim($aggregate)) {
+ if (!io_savefile(DOKU_PLUGIN."plugin_$file", $aggregate)) {
+ $this->manager->error .= sprintf($this->lang['error_write'],$file);
+ }
+ }
+ }
+
+ // update latest plugin date - FIXME
+ return (!$this->manager->error);
+ }
+
+ // 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, 'w+')) return;
+ fwrite($fp, "updated=$date\n");
+ fclose($fp);
+ break;
+ }
+ }
+
+ function plugin_readlog($plugin, $field) {
+ static $log = array();
+ $file = DOKU_PLUGIN.$plugin.'/manager.dat';
+
+ if (!isset($log[$plugin])) {
+ $tmp = @file_get_contents($file);
+ if (!$tmp) return '';
+ $log[$plugin] = & $tmp;
+ }
+
+ if ($field == 'ALL') {
+ return $log[$plugin];
+ }
+
+ if (preg_match_all('/'.$field.'=(.*)$/m',$log[$plugin], $match=array()))
+ return implode("\n", $match[1]);
+
+ return '';
+ }
+ }
+
+ class admin_plugin_refresh extends admin_plugin_manage {
+
+ function process() {
+ $this->refresh();
+
+ if (!$this->manager->error) return $this->lang['refreshed'];
+ }
+
+ function html() {
+
+ parent::html();
+
+ ptln('<div class="pm_info">');
+ ptln('<h2>'.$this->lang['refreshing'].'</h2>');
+ ptln('<p>'.$this->lang['refreshed'].'</p>');
+ ptln('</div>');
+ }
+
+ }
+
+ class admin_plugin_download extends admin_plugin_manage {
+
+ var $overwrite = false;
+
+ function process() {
+ global $lang, $conf;
+
+ $plugin_url = $_REQUEST['url'];
+ if (!preg_match("/[^\/]*$/", $plugin_url, $matches = array()) || !$matches[0]) {
+ $this->manager->error = $this->lang['error_badurl'].'\n';
+ return '';
+ }
+
+ $file = $matches[0];
+ $folder = "p".md5($file.date('r')); // tmp folder name - will be empty (should really make sure it doesn't already exist)
+ $tmp = DOKU_PLUGIN."tmp/$folder";
+
+ if (!$this->manager->error && !ap_mkdir($tmp)) {
+ $this->manager->error = $this->lang['error_dir_create'].'\n';
+ $folder = '';
+ }
+
+ if (!$this->manager->error && !io_download($plugin_url, "$tmp/$file")) {
+ $this->manager->error = sprintf($this->lang['error_download'],$url)."\n";
+ }
+
+ ap_decompress("$tmp/$file", $tmp);
+
+ // search tmp/$folder for the directory that has been created
+ // move that directory to lib/plugins/
+ 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 (file_exists(DOKU_PLUGIN."/$f")) {
+ // remember our settings, ask the user to confirm overwrite, FIXME
+ continue;
+ }
+
+ ap_copy("$tmp/$f", DOKU_PLUGIN.$f);
+ $this->downloaded[] = $f;
+ $this->plugin_writelog($f, 'install', array($plugin_url));
+ }
+ closedir($dh);
+ }
+
+ // cleanup
+ if ($folder && is_dir(DOKU_PLUGIN."tmp/$folder")) ap_delete(DOKU_PLUGIN."tmp/$folder");
+
+ if (!$this->manager->error) {
+ $this->refresh();
+ }
+
+ return '';
+ }
+
+ function html() {
+ parent::html();
+
+ ptln('<div class="pm_info">');
+ ptln('<h2>'.$this->lang['downloading'].'</h2>');
+
+ if ($this->manager->error) {
+ ptln('<p class="error">'.$this->manager->error.'</p>');
+ } else if (count($this->downloaded) == 1) {
+ ptln('<p>'.sprintf($this->lang['downloaded'],$this->downloaded[0]).'</p>');
+ } else if (count($this->downloaded)) { // more than one plugin in the download
+ ptln('<p>'.$this->lang['downloads'].'</p>');
+ ptln('<ul>');
+ foreach ($this->downloaded as $plugin) {
+ ptln('<li>'.$plugin.'</li>',2);
+ }
+ ptln('</ul>');
+ } else { // none found in download
+ ptln('<p>'.$this->lang['download_none'].'</p>');
+ }
+ ptln('</div>');
+ }
+
+ }
+
+ class admin_plugin_delete extends admin_plugin_manage {
+
+ function process() {
+
+ $deleted = $this->manager->plugin;
+ ap_delete(DOKU_PLUGIN.$deleted);
+ $this->plugin = '';
+
+ $this->refresh();
+ return "Plugin $deleted deleted";
+ }
+ }
+
+ class admin_plugin_info extends admin_plugin_manage {
+
+ var $plugin_info = array(); // the plugin itself
+ var $details = array(); // any component plugins
+
+ function process() {
+
+ // sanity check
+ if (!$this->manager->plugin) { return; }
+
+ $component_list = ap_plugin_components($this->manager->plugin);
+ usort($component_list, ap_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('<div class="pm_info">');
+ ptln("<h2>Plugin: {$this->manager->plugin}</h2>");
+
+ // collect pertinent information from the log
+ $installed = $this->plugin_readlog($this->manager->plugin, 'installed');
+ $source = $this->plugin_readlog($this->manager->plugin, 'url');
+ $updated = substr(strrchr("\n".$this->plugin_readlog($this->manager->plugin, 'updated'), '\n'), 1);
+
+ ptln("<dl>",2);
+ ptln("<dt>".$this->manager->getLang('installed').'</dt><dd>'.($installed ? $installed : $this->manager->getLang('unknown'))."</dd>",4);
+ if ($updated) ptln("<dt>".$this->manager->getLang('lastupdate').'</dt><dd>'.$updated."</dd>",4);
+ ptln("<dt>".$this->manager->getLang('source').'</dt><dd>'.($source ? $source : $this->manager->getLang('unknown'))."</dd>",4);
+ ptln("</dl>",2);
+
+ if (count($this->details) == 0) {
+ ptln("<p>This plugin returned no information, it may be invalid.</p>",2);
+ } else {
+
+ ptln("<dl>",2);
+ if ($this->plugin_info['name']) ptln("<dt>Name</dt><dd>".$this->out($this->plugin_info['name'])."</dd>",4);
+ if ($this->plugin_info['type']) ptln("<dt>Type</dt><dd>".$this->out($this->plugin_info['type'])."</dd>",4);
+ if ($this->plugin_info['desc']) ptln("<dt>Description</dt><dd>".$this->out($this->plugin_info['desc'])."</dd>",4);
+ if ($this->plugin_info['author']) ptln("<dt>Author</dt><dd>".$this->manager->plugin_email($this->plugin_info['email'], $this->plugin_info['author'])."</dd>",4);
+ if ($this->plugin_info['url']) ptln("<dt>Web</dt><dd>".$this->manager->plugin_link($this->plugin_info['url'], '', 'urlextern')."</dd>",4);
+ ptln("</dl>",2);
+
+ if (count($this->details) > 1) {
+ ptln("<h3>Components</h3>",2);
+ ptln("<div>",2);
+
+ foreach ($this->details as $info) {
+
+ ptln("<dl>",4);
+ if (!$this->plugin_info['name']) ptln("<dt>Name</dt><dd>".$this->out($info['name'])."</dd>",6);
+ if (!$this->plugin_info['type']) ptln("<dt>Type</dt><dd>".$this->out($info['type'])."</dd>",6);
+ if (!$this->plugin_info['desc']) ptln("<dt>Description</dt><dd>".$this->out($info['desc'])."</dd>",6);
+ if (!$this->plugin_info['author']) ptln("<dt>Author</dt><dd>".$this->manager->plugin_email($info['email'], $info['author'])."</dd>",6);
+ if (!$this->plugin_info['url']) ptln("<dt>Web</dt><dd>".$this->manager->plugin_link($info['url'], '', 'urlextern')."</dd>",6);
+ ptln("</dl>",4);
+
+ }
+ ptln("</div>",2);
+ }
+ }
+ ptln("</div>");
+ }
+
+ // simple output filter, make html entities safe and convert new lines to <br />
+ function out($text) {
+ return str_replace("\n",'<br />',htmlentities($text));
+ }
+
+ }
+
+ //--------------[ to do ]---------------------------------------
+ class admin_plugin_update extends admin_plugin_manage {
+
+ function html() {
+ parent::html();
+
+ ptln('<div class="pm_info">');
+ ptln('<h2>'.$this->lang['updating'].'</h2>');
+
+ if ($this->manager->error) {
+ ptln('<p class="error">'.$this->manager->error.'</p>');
+ } else if (count($this->downloaded) == 1) {
+ ptln('<p>'.sprintf($this->lang['downloaded'],$this->downloaded[0]).'</p>');
+ } else if (count($this->downloaded)) { // more than one plugin in the download
+ ptln('<p>'.$this->lang['downloads'].'</p>');
+ ptln('<ul>');
+ foreach ($this->downloaded as $plugin) {
+ ptln('<li>'.$plugin.'</li>',2);
+ }
+ ptln('</ul>');
+ } else { // none found in download
+ ptln('<p>'.$this->lang['download_none'].'</p>');
+ }
+ ptln('<p>Under Construction</p>');
+ ptln('</div>');
+ }
+ }
+ class admin_plugin_settings extends admin_plugin_manage {}
+
+ //--------------[ utilities ]-----------------------------------
+
+ function is_css($f) { return (substr($f, -4) == '.css'); }
+
+ // generate an admin plugin href
+ function apl($pl, $fn) { return wl($ID,"do=admin&amp;page=plugin".($pl?"&amp;plugin=$pl":"").($fn?"&amp;fn=$fn":"")); }
+
+ // generate a complete admin plugin link (may change to button)
+ function ap_link($pl, $fn, $txt) {
+ return '<a href="'.apl($pl, $fn).'">['.$txt.']</a>';
+ }
+
+ // decompress wrapper
+ function ap_decompress($file, $target) {
+
+ // decompression library doesn't like target folders ending in "/"
+ if (substr($target, -1) == "/") $target = substr($target, 0, -1);
+
+ // .tar, .tar.bz, .tar.gz
+ if (preg_match("/\.tar(\.bz2?|\.gz)?$/", $file)) {
+
+ require_once(DOKU_PLUGIN."plugin/inc/tarlib.class.php");
+
+ $tar = new CompTar($file, COMPRESS_DETECT);
+ $ok = $tar->Extract(FULL_ARCHIVE, $target, '', 0777);
+
+ // sort something out for handling tar error messages meaningfully
+ if ($ok<0) ptln("<p>tar error:".$tar->TarErrorStr($ok)."</p>");
+ return ($ok<0?false:true);
+ }
+
+ if (substr($file, -4) == ".zip") {
+
+ require_once(DOKU_PLUGIN."plugin/inc/zip.lib.php");
+
+ $zip = new zip();
+ $ok = $zip->Extract($file, $target);
+
+ // sort something out for handling zip error messages meaningfully
+ if ($ok==-1) ptln("<p>zip error:</p>");
+ return ($ok==-1?false:true);
+ }
+
+ if (substr($file, -4) == ".rar") {
+ // not yet supported -- fix me
+ return false;
+ }
+
+ // unsupported file type
+ return false;
+ }
+
+ // possibly should use io_MakeFileDir, not sure about using its method of error handling
+ function ap_mkdir($d) {
+ global $conf;
+
+ umask($conf['dmask']);
+ $ok = io_mkdir_p($d);
+ umask($conf['umask']);
+ return $ok;
+ }
+
+ // copy with recursive sub-directory support
+ function ap_copy($src, $dst) {
+
+ if (is_dir($src)) {
+ if (!$dh = @opendir($src)) return false;
+
+ if ($ok = ap_mkdir($dst)) {
+ while ($ok && $f = readdir($dh)) {
+ if ($f == '..' || $f == '.') continue;
+ $ok = ap_copy("$src/$f", "$dst/$f");
+ }
+ }
+
+ closedir($dh);
+ return $ok;
+
+ } else {
+ if (!@copy($src,$dst)) return false;
+ touch($dst,filemtime($src));
+ }
+
+ return true;
+ }
+
+ // delete, with recursive sub-directory support
+ function ap_delete($path) {
+
+ if (!is_string($path) || $path == "") return;
+
+ if (is_dir($path)) {
+ if (!$dh = @opendir($path)) return;
+
+ while ($f = readdir($dh)) {
+ if ($f == '..' || $f == '.') continue;
+ ap_delete("$path/$f");
+ }
+
+ closedir($dh);
+ rmdir($path);
+ return;
+
+ } else {
+ unlink($path);
+ }
+ }
+
+ // return a list (name & type) of all the component plugins that make up this plugin
+ // can this move to pluginutils?
+ function ap_plugin_components($plugin) {
+
+ global $common_plugin_types;
+
+ $components = array();
+ $path = DOKU_PLUGIN.$plugin.'/';
+
+ foreach ($common_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;
+ }
+
+ function ap_component_sort($a, $b) {
+ if ($a['name'] == $b['name']) return 0;
+ return ($a['name'] < $b['name']) ? -1 : 1;
+ }
+