(Hopefully) finished new plugin manager and implemented the utilization of it. Still HIGHLY experimental.
--- a/includes/clientside/static/ajax.js Wed Apr 09 19:27:02 2008 -0400
+++ b/includes/clientside/static/ajax.js Wed Apr 09 22:37:37 2008 -0400
@@ -1568,6 +1568,12 @@
return true;
}
action = action.replace(/_confirm$/, '');
+ // white-out the plugin info box
+ if ( btnobj )
+ {
+ var td = btnobj.parentNode.parentNode.parentNode.parentNode;
+ var blackbox = whiteOutElement(td);
+ }
var request = toJSONString({
mode: action,
plugin: plugin_filename
@@ -1576,52 +1582,53 @@
{
if ( ajax.readyState == 4 && ajax.status == 200 )
{
- if ( ajax.responseText == 'good' )
+ var response = String(ajax.responseText + '');
+ if ( response.substr(0, 1) != '{' )
{
- ajaxPage( namespace_list['Admin'] + 'PluginManager' );
+ handle_invalid_json(response);
+ return false;
}
- else
+ response = parseJSON(response);
+ if ( response.success )
{
- var response = String(ajax.responseText + '');
- if ( response.substr(0, 1) != '{' )
+ if ( blackbox )
{
- handle_invalid_json(response);
- return false;
+ blackbox.parentNode.removeChild(blackbox);
}
- response = parseJSON(response);
- if ( response.mode != 'error' )
+ ajaxPage( namespace_list['Admin'] + 'PluginManager' );
+ return true;
+ }
+ // wait for fade effect to finish its run
+ setTimeout(function()
{
- console.debug(response);
- return false;
- }
- // wait for fade effect to finish its run
- setTimeout(function()
- {
- miniPrompt(function(div)
+ miniPrompt(function(div)
+ {
+ if ( blackbox )
{
- var txtholder = document.createElement('div');
- txtholder.style.textAlign = 'center';
- txtholder.appendChild(document.createTextNode(response.error));
- txtholder.appendChild(document.createElement('br'));
- txtholder.appendChild(document.createElement('br'));
-
- // close button
- var btn_cancel = document.createElement('a');
- btn_cancel.className = 'abutton abutton_red';
- btn_cancel.href = '#';
- btn_cancel.appendChild(document.createTextNode($lang.get('etc_ok')));
-
- txtholder.appendChild(btn_cancel);
- div.appendChild(txtholder);
-
- btn_cancel.onclick = function()
- {
- miniPromptDestroy(this);
- return false;
- }
- });
- }, 750);
- }
+ blackbox.parentNode.removeChild(blackbox);
+ }
+ var txtholder = document.createElement('div');
+ txtholder.style.textAlign = 'center';
+ txtholder.appendChild(document.createTextNode(response.error));
+ txtholder.appendChild(document.createElement('br'));
+ txtholder.appendChild(document.createElement('br'));
+
+ // close button
+ var btn_cancel = document.createElement('a');
+ btn_cancel.className = 'abutton abutton_red';
+ btn_cancel.href = '#';
+ btn_cancel.appendChild(document.createTextNode($lang.get('etc_ok')));
+
+ txtholder.appendChild(btn_cancel);
+ div.appendChild(txtholder);
+
+ btn_cancel.onclick = function()
+ {
+ miniPromptDestroy(this);
+ return false;
+ }
+ });
+ }, 750);
}
});
}
--- a/includes/common.php Wed Apr 09 19:27:02 2008 -0400
+++ b/includes/common.php Wed Apr 09 22:37:37 2008 -0400
@@ -308,8 +308,8 @@
// Load plugins from common because we can't give plugins full abilities in object context
foreach ( $plugins->load_list as $f )
{
- if ( file_exists($f) )
- include_once $f;
+ if ( file_exists(ENANO_ROOT . '/plugins/' . $f) )
+ include_once ENANO_ROOT . '/plugins/' . $f;
}
profiler_log('Loaded plugins');
--- a/includes/plugins.php Wed Apr 09 19:27:02 2008 -0400
+++ b/includes/plugins.php Wed Apr 09 22:37:37 2008 -0400
@@ -63,52 +63,35 @@
function loadAll()
{
+ global $db, $session, $paths, $template, $plugins; // Common objects
$dir = ENANO_ROOT.'/plugins/';
- $this->load_list = Array();
-
- $plugins = Array();
+ $this->load_list = $this->system_plugins;
+ $q = $db->sql_query('SELECT plugin_filename, plugin_version FROM ' . table_prefix . 'plugins WHERE plugin_flags & ~' . PLUGIN_DISABLED . ' = plugin_flags;');
+ if ( !$q )
+ $db->_die();
- // Open a known directory, and proceed to read its contents
+ while ( $row = $db->fetchrow() )
+ {
+ $this->load_list[] = $row['plugin_filename'];
+ }
+
+ $this->loaded_plugins = $this->get_plugin_list($this->load_list);
- if (is_dir($dir))
+ // check for out-of-date plugins
+ foreach ( $this->load_list as $i => $plugin )
{
- if ($dh = opendir($dir))
+ if ( in_array($plugin, $this->system_plugins) )
+ continue;
+ if ( $this->loaded_plugins[$plugin]['status'] & PLUGIN_OUTOFDATE )
{
- while (($file = readdir($dh)) !== false)
- {
- if(preg_match('#^(.*?)\.php$#is', $file))
- {
- if(getConfig('plugin_'.$file) == '1' || in_array($file, $this->system_plugins))
- {
- $this->load_list[] = $dir . $file;
- $plugid = substr($file, 0, strlen($file)-4);
- $f = @file_get_contents($dir . $file);
- if ( empty($f) )
- continue;
- $f = explode("\n", $f);
- $f = array_slice($f, 2, 7);
- $f[0] = substr($f[0], 13);
- $f[1] = substr($f[1], 12);
- $f[2] = substr($f[2], 13);
- $f[3] = substr($f[3], 8 );
- $f[4] = substr($f[4], 9 );
- $f[5] = substr($f[5], 12);
- $plugins[$plugid] = Array();
- $plugins[$plugid]['name'] = $f[0];
- $plugins[$plugid]['uri'] = $f[1];
- $plugins[$plugid]['desc'] = $f[2];
- $plugins[$plugid]['auth'] = $f[3];
- $plugins[$plugid]['vers'] = $f[4];
- $plugins[$plugid]['aweb'] = $f[5];
- }
- }
- }
- closedir($dh);
+ // it's out of date, don't load
+ unset($this->load_list[$i]);
+ unset($this->loaded_plugins[$plugin]);
}
}
- $this->loaded_plugins = $plugins;
- //die('<pre>'.htmlspecialchars(print_r($plugins, true)).'</pre>');
+
+ $this->load_list = array_unique($this->load_list);
}
/**
@@ -215,7 +198,6 @@
. '#m';
// Match out all blocks
-
$results = preg_match_all($regexp, $contents, $blocks);
$return = array();
@@ -256,6 +238,549 @@
}
return $return;
}
+
+ /**
+ * Reads all plugins in the filesystem and cross-references them with the database, providing a very complete summary of plugins
+ * on the site.
+ * @param array If specified, will restrict scanned files to this list. Defaults to null, which means all PHP files will be scanned.
+ * @return array
+ */
+
+ function get_plugin_list($restrict = null)
+ {
+ global $db, $session, $paths, $template, $plugins; // Common objects
+
+ // Scan all plugins
+ $plugin_list = array();
+
+ if ( $dirh = @opendir( ENANO_ROOT . '/plugins' ) )
+ {
+ while ( $dh = @readdir($dirh) )
+ {
+ if ( !preg_match('/\.php$/i', $dh) )
+ continue;
+
+ if ( is_array($restrict) )
+ if ( !in_array($dh, $restrict) )
+ continue;
+
+ $fullpath = ENANO_ROOT . "/plugins/$dh";
+ // it's a PHP file, attempt to read metadata
+ // pass 1: try to read a !info block
+ $blockdata = $this->parse_plugin_blocks($fullpath, 'info');
+ if ( empty($blockdata) )
+ {
+ // no !info block, check for old header
+ $fh = @fopen($fullpath, 'r');
+ if ( !$fh )
+ // can't read, bail out
+ continue;
+ $plugin_data = array();
+ for ( $i = 0; $i < 8; $i++ )
+ {
+ $plugin_data[] = @fgets($fh, 8096);
+ }
+ // close our file handle
+ fclose($fh);
+ // is the header correct?
+ if ( trim($plugin_data[0]) != '<?php' || trim($plugin_data[1]) != '/*' )
+ {
+ // nope. get out.
+ continue;
+ }
+ // parse all the variables
+ $plugin_meta = array();
+ for ( $i = 2; $i <= 7; $i++ )
+ {
+ if ( !preg_match('/^([A-z0-9 ]+?): (.+?)$/', trim($plugin_data[$i]), $match) )
+ continue 2;
+ $plugin_meta[ strtolower($match[1]) ] = $match[2];
+ }
+ }
+ else
+ {
+ // parse JSON block
+ $plugin_data =& $blockdata[0]['value'];
+ $plugin_data = enano_clean_json(enano_trim_json($plugin_data));
+ try
+ {
+ $plugin_meta_uc = enano_json_decode($plugin_data);
+ }
+ catch ( Exception $e )
+ {
+ continue;
+ }
+ // convert all the keys to lowercase
+ $plugin_meta = array();
+ foreach ( $plugin_meta_uc as $key => $value )
+ {
+ $plugin_meta[ strtolower($key) ] = $value;
+ }
+ }
+ if ( !isset($plugin_meta) || !is_array(@$plugin_meta) )
+ {
+ // parsing didn't work.
+ continue;
+ }
+ // check for required keys
+ $required_keys = array('plugin name', 'plugin uri', 'description', 'author', 'version', 'author uri');
+ foreach ( $required_keys as $key )
+ {
+ if ( !isset($plugin_meta[$key]) )
+ // not set, skip this plugin
+ continue 2;
+ }
+ // decide if it's a system plugin
+ $plugin_meta['system plugin'] = in_array($dh, $this->system_plugins);
+ // reset installed variable
+ $plugin_meta['installed'] = false;
+ $plugin_meta['status'] = 0;
+ // all checks passed
+ $plugin_list[$dh] = $plugin_meta;
+ }
+ }
+ // gather info about installed plugins
+ $q = $db->sql_query('SELECT plugin_id, plugin_filename, plugin_version, plugin_flags FROM ' . table_prefix . 'plugins;');
+ if ( !$q )
+ $db->_die();
+ while ( $row = $db->fetchrow() )
+ {
+ if ( !isset($plugin_list[ $row['plugin_filename'] ]) )
+ {
+ // missing plugin file, don't report (for now)
+ continue;
+ }
+ $filename =& $row['plugin_filename'];
+ $plugin_list[$filename]['installed'] = true;
+ $plugin_list[$filename]['status'] = PLUGIN_INSTALLED;
+ $plugin_list[$filename]['plugin id'] = $row['plugin_id'];
+ if ( $row['plugin_version'] != $plugin_list[$filename]['version'] )
+ {
+ $plugin_list[$filename]['status'] |= PLUGIN_OUTOFDATE;
+ $plugin_list[$filename]['version installed'] = $row['plugin_version'];
+ }
+ if ( $row['plugin_flags'] & PLUGIN_DISABLED )
+ {
+ $plugin_list[$filename]['status'] |= PLUGIN_DISABLED;
+ }
+ }
+ $db->free_result();
+
+ // sort it all out by filename
+ ksort($plugin_list);
+
+ // done
+ return $plugin_list;
+ }
+
+ /**
+ * Installs a plugin.
+ * @param string Filename of plugin.
+ * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time.
+ * @return array JSON-formatted but not encoded response
+ */
+
+ function install_plugin($filename, $plugin_list = null)
+ {
+ global $db, $session, $paths, $template, $plugins; // Common objects
+ global $lang;
+
+ if ( !$plugin_list )
+ $plugin_list = $this->get_plugin_list();
+
+ // we're gonna need this
+ require_once ( ENANO_ROOT . '/includes/sql_parse.php' );
+
+ switch ( true ): case true:
+
+ // is the plugin in the directory and awaiting installation?
+ if ( !isset($plugin_list[$filename]) || (
+ isset($plugin_list[$filename]) && $plugin_list[$filename]['installed']
+ ))
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'Invalid plugin specified.',
+ 'debug' => $filename
+ );
+ break;
+ }
+
+ $dataset =& $plugin_list[$filename];
+
+ // load up the installer schema
+ $schema = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'install' );
+
+ $sql = array();
+ if ( !empty($schema) )
+ {
+ // parse SQL
+ $parser = new SQL_Parser($schema[0]['value'], true);
+ $parser->assign_vars(array(
+ 'TABLE_PREFIX' => table_prefix
+ ));
+ $sql = $parser->parse();
+ }
+
+ // schema is final, check queries
+ foreach ( $sql as $query )
+ {
+ if ( !$db->check_query($query) )
+ {
+ // aww crap, a query is bad
+ $return = array(
+ 'mode' => 'error',
+ 'error' => $lang->get('acppm_err_upgrade_bad_query'),
+ );
+ break 2;
+ }
+ }
+
+ // this is it, perform installation
+ foreach ( $sql as $query )
+ {
+ if ( substr($query, 0, 1) == '@' )
+ {
+ $query = substr($query, 1);
+ $db->sql_query($query);
+ }
+ else
+ {
+ if ( !$db->sql_query($query) )
+ $db->die_json();
+ }
+ }
+
+ // register plugin
+ $version_db = $db->escape($dataset['version']);
+ $filename_db = $db->escape($filename);
+ $flags = PLUGIN_INSTALLED;
+
+ $q = $db->sql_query('INSERT INTO ' . table_prefix . "plugins ( plugin_version, plugin_filename, plugin_flags )\n"
+ . " VALUES ( '$version_db', '$filename_db', $flags );");
+ if ( !$q )
+ $db->die_json();
+
+ $return = array(
+ 'success' => true
+ );
+
+ endswitch;
+
+ return $return;
+ }
+
+ /**
+ * Uninstalls a plugin, removing it completely from the database and calling any custom uninstallation code the plugin specifies.
+ * @param string Filename of plugin.
+ * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time.
+ * @return array JSON-formatted but not encoded response
+ */
+
+ function uninstall_plugin($filename, $plugin_list = null)
+ {
+ global $db, $session, $paths, $template, $plugins; // Common objects
+ global $lang;
+
+ if ( !$plugin_list )
+ $plugin_list = $this->get_plugin_list();
+
+ // we're gonna need this
+ require_once ( ENANO_ROOT . '/includes/sql_parse.php' );
+
+ switch ( true ): case true:
+
+ // is the plugin in the directory and already installed?
+ if ( !isset($plugin_list[$filename]) || (
+ isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed']
+ ))
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'Invalid plugin specified.',
+ );
+ break;
+ }
+ // get plugin id
+ $dataset =& $plugin_list[$filename];
+ if ( empty($dataset['plugin id']) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'Couldn\'t retrieve plugin ID.',
+ );
+ break;
+ }
+
+ // load up the installer schema
+ $schema = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'uninstall' );
+
+ $sql = array();
+ if ( !empty($schema) )
+ {
+ // parse SQL
+ $parser = new SQL_Parser($schema[0]['value'], true);
+ $parser->assign_vars(array(
+ 'TABLE_PREFIX' => table_prefix
+ ));
+ $sql = $parser->parse();
+ }
+
+ // schema is final, check queries
+ foreach ( $sql as $query )
+ {
+ if ( !$db->check_query($query) )
+ {
+ // aww crap, a query is bad
+ $return = array(
+ 'mode' => 'error',
+ 'error' => $lang->get('acppm_err_upgrade_bad_query'),
+ );
+ break 2;
+ }
+ }
+
+ // this is it, perform uninstallation
+ foreach ( $sql as $query )
+ {
+ if ( substr($query, 0, 1) == '@' )
+ {
+ $query = substr($query, 1);
+ $db->sql_query($query);
+ }
+ else
+ {
+ if ( !$db->sql_query($query) )
+ $db->die_json();
+ }
+ }
+
+ // deregister plugin
+ $q = $db->sql_query('DELETE FROM ' . table_prefix . "plugins WHERE plugin_id = {$dataset['plugin id']};");
+ if ( !$q )
+ $db->die_json();
+
+ $return = array(
+ 'success' => true
+ );
+
+ endswitch;
+
+ return $return;
+ }
+
+ /**
+ * Very intelligently upgrades a plugin to the version specified in the filesystem.
+ * @param string Filename of plugin.
+ * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time.
+ * @return array JSON-formatted but not encoded response
+ */
+
+ function upgrade_plugin($filename, $plugin_list = null)
+ {
+ global $db, $session, $paths, $template, $plugins; // Common objects
+ global $lang;
+
+ if ( !$plugin_list )
+ $plugin_list = $this->get_plugin_list();
+
+ // we're gonna need this
+ require_once ( ENANO_ROOT . '/includes/sql_parse.php' );
+
+ switch ( true ): case true:
+
+ // is the plugin in the directory and already installed?
+ if ( !isset($plugin_list[$filename]) || (
+ isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed']
+ ))
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'Invalid plugin specified.',
+ );
+ break;
+ }
+ // get plugin id
+ $dataset =& $plugin_list[$filename];
+ if ( empty($dataset['plugin id']) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'Couldn\'t retrieve plugin ID.',
+ );
+ break;
+ }
+
+ //
+ // Here we go with the main upgrade process. This is the same logic that the
+ // Enano official upgrader uses, in fact it's the same SQL parser. We need
+ // list of all versions of the plugin to continue, though.
+ //
+
+ if ( !isset($dataset['version list']) || ( isset($dataset['version list']) && !is_array($dataset['version list']) ) )
+ {
+ // no version list - update the version number but leave the rest alone
+ $version = $db->escape($dataset['version']);
+ $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_version = '$version' WHERE plugin_id = {$dataset['plugin id']};");
+ if ( !$q )
+ $db->die_json();
+
+ // send an error and notify the user even though it was technically a success
+ $return = array(
+ 'mode' => 'error',
+ 'error' => $lang->get('acppm_err_upgrade_not_supported'),
+ );
+ break;
+ }
+
+ // build target list
+ $versions = $dataset['version list'];
+ $indices = array_flip($versions);
+ $installed = $dataset['version installed'];
+
+ // is the current version upgradeable?
+ if ( !isset($indices[$installed]) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => $lang->get('acppm_err_upgrade_bad_version'),
+ );
+ break;
+ }
+
+ // does the plugin support upgrading to its own version?
+ if ( !isset($indices[$installed]) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => $lang->get('acppm_err_upgrade_bad_target_version'),
+ );
+ break;
+ }
+
+ // list out which versions to do
+ $index_start = @$indices[$installed] + 1;
+ $index_stop = @$indices[$dataset['version']];
+
+ // Are we trying to go backwards?
+ if ( $index_stop <= $index_start )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => $lang->get('acppm_err_upgrade_to_older'),
+ );
+ break;
+ }
+
+ // build the list of version sets
+ $ver_previous = $installed;
+ $targets = array();
+ for ( $i = $index_start; $i <= $index_stop; $i++ )
+ {
+ $targets[] = array($ver_previous, $versions[$i]);
+ $ver_previous = $versions[$i];
+ }
+
+ // parse out upgrade sections in plugin file
+ $plugin_blocks = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'upgrade' );
+ $sql_blocks = array();
+ foreach ( $plugin_blocks as $block )
+ {
+ if ( !isset($block['from']) || !isset($block['to']) )
+ {
+ continue;
+ }
+ $key = "{$block['from']} TO {$block['to']}";
+ $sql_blocks[$key] = $block['value'];
+ }
+
+ // do version list check
+ // for now we won't fret if a specific version set isn't found, we'll just
+ // not do that version and assume there were no DB changes.
+ foreach ( $targets as $i => $target )
+ {
+ list($from, $to) = $target;
+ $key = "$from TO $to";
+ if ( !isset($sql_blocks[$key]) )
+ {
+ unset($targets[$i]);
+ }
+ }
+ $targets = array_values($targets);
+
+ // parse and finalize schema
+ $schema = array();
+ foreach ( $targets as $i => $target )
+ {
+ list($from, $to) = $target;
+ $key = "$from TO $to";
+ try
+ {
+ $parser = new SQL_Parser($sql_blocks[$key], true);
+ }
+ catch ( Exception $e )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'SQL parser init exception',
+ 'debug' => "$e"
+ );
+ break 2;
+ }
+ $parser->assign_vars(array(
+ 'TABLE_PREFIX' => table_prefix
+ ));
+ $parsed = $parser->parse();
+ foreach ( $parsed as $query )
+ {
+ $schema[] = $query;
+ }
+ }
+
+ // schema is final, check queries
+ foreach ( $schema as $query )
+ {
+ if ( !$db->check_query($query) )
+ {
+ // aww crap, a query is bad
+ $return = array(
+ 'mode' => 'error',
+ 'error' => $lang->get('acppm_err_upgrade_bad_query'),
+ );
+ break 2;
+ }
+ }
+
+ // this is it, perform upgrade
+ foreach ( $schema as $query )
+ {
+ if ( substr($query, 0, 1) == '@' )
+ {
+ $query = substr($query, 1);
+ $db->sql_query($query);
+ }
+ else
+ {
+ if ( !$db->sql_query($query) )
+ $db->die_json();
+ }
+ }
+
+ // update version number
+ $version = $db->escape($dataset['version']);
+ $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_version = '$version' WHERE plugin_id = {$dataset['plugin id']};");
+ if ( !$q )
+ $db->die_json();
+
+ // all done :-)
+ $return = array(
+ 'success' => true
+ );
+
+ endswitch;
+
+ return $return;
+ }
}
?>
--- a/includes/sql_parse.php Wed Apr 09 19:27:02 2008 -0400
+++ b/includes/sql_parse.php Wed Apr 09 22:37:37 2008 -0400
@@ -50,11 +50,12 @@
/**
* Constructor.
* @param string If this contains newlines, it will be treated as the target SQL. If not, will be treated as a filename.
+ * @param string If true, force as raw SQL, i.e. don't treat as a filename no matter what
*/
- public function __construct($sql)
+ public function __construct($sql, $force_file = false)
{
- if ( strpos($sql, "\n") )
+ if ( strpos($sql, "\n") || $force_file )
{
$this->sql_string = $sql;
}
--- a/language/english/admin.json Wed Apr 09 19:27:02 2008 -0400
+++ b/language/english/admin.json Wed Apr 09 22:37:37 2008 -0400
@@ -400,8 +400,18 @@
btn_upgrade: 'Upgrade',
btn_uninstall: 'Uninstall',
- msg_confirm_uninstall: 'Please confirm that you want to uninstall this plugin and that it doesn\'t provide any shared functions that other plugins depend on.',
+ msg_confirm_uninstall: 'Uninstalling this plugin may cause the loss of data that was created with it. You should only uninstall a plugin if you are certain you\'ll have no further use for it; in fact, you don\'t even need to uninstall a plugin if you\'re deleting it from the filesystem.',
msg_confirm_install: 'Plugins are not supported by the Enano project and could harm your site if malicious. You should only install plugins from sources that you trust.',
+
+ err_upgrade_not_supported: 'This plugin doesn\'t support automatic upgrades. The version number has been updated so the plugin will be re-enabled, but you should check the plugin file to see if the author provided instructions for finishing the upgrade.',
+ err_upgrade_bad_version: 'This plugin cannot be upgraded because you are running a version of the plugin that is not listed in the plugin\'s version list.',
+ err_upgrade_bad_target_version: 'This plugin cannot be upgraded because it does not support its own version. Please contact the author and ask them to fix this.',
+ err_upgrade_to_older: 'You are trying to upgrade to an older release of this plugin. This is unsupported and must be done manually.',
+ err_upgrade_bad_query: 'There is a problem with one of the SQL queries the plugin is trying to make.',
+
+ msg_old_entries_title: 'Import old plugin installation data',
+ msg_old_entries_body: 'There is still data from the old plugin structure in your database. You can import this to the new structure automatically using the button below.',
+ btn_import_old: 'Import old plugin settings',
},
acppm: {
heading_main: 'Edit page properties',
--- a/plugins/admin/PluginManager.php Wed Apr 09 19:27:02 2008 -0400
+++ b/plugins/admin/PluginManager.php Wed Apr 09 22:37:37 2008 -0400
@@ -26,7 +26,7 @@
*
* The format for the special comment blocks is:
<code>
- /**!blocktype( param1 = "value1" [ param2 = "value2" ... ] )**
+ /**!blocktype( param1 = "value1"; [ param2 = "value2"; ... ] )**
... block content ...
@@ -58,7 +58,9 @@
<code>
/**!language**
{
+ // each entry at this level should be an ISO-639-1 language code.
eng: {
+ // from here on in is the standard langauge file format
categories: [ 'meta', 'foo', 'bar' ],
strings: {
meta: {
@@ -86,13 +88,17 @@
</code>
* And finally, the format for upgrade schemas:
<code>
- /**!upgrade from = "0.1-alpha1" to = "0.1-alpha2" **
+ /**!upgrade from = "0.1-alpha1"; to = "0.1-alpha2"; **
**!* /
</code>
+ * As a courtesy to your users, we ask that you also include an "uninstall" block that reverses any changes your plugin makes
+ * to the database upon installation. The syntax is identical to that of the install block.
+ *
* Remember that upgrades will always be done incrementally, so if the user is upgrading 0.1-alpha2 to 0.1, Enano's plugin
* engine will run the 0.1-alpha2 to 0.1-beta1 upgrader, then the 0.1-beta1 to 0.1 upgrader, going by the versions listed in
- * the example metadata block above.
+ * the example metadata block above. As with the standard Enano installer, prefixing a query with '@' will cause it to be
+ * performed "blindly", e.g. not checked for errors.
*
* All of this information is effective as of Enano 1.1.4.
*/
@@ -111,6 +117,8 @@
return;
}
+ $plugin_list = $plugins->get_plugin_list();
+
// Are we processing an AJAX request from the smartform?
if ( $paths->getParam(0) == 'action.json' )
{
@@ -133,6 +141,120 @@
{
switch ( $request['mode'] )
{
+ case 'install':
+ // did they specify a plugin to operate on?
+ if ( !isset($request['plugin']) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'No plugin specified.',
+ );
+ break;
+ }
+
+ $return = $plugins->install_plugin($request['plugin'], $plugin_list);
+ break;
+ case 'upgrade':
+ // did they specify a plugin to operate on?
+ if ( !isset($request['plugin']) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'No plugin specified.',
+ );
+ break;
+ }
+
+ $return = $plugins->upgrade_plugin($request['plugin'], $plugin_list);
+ break;
+ case 'uninstall':
+ // did they specify a plugin to operate on?
+ if ( !isset($request['plugin']) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'No plugin specified.',
+ );
+ break;
+ }
+
+ $return = $plugins->uninstall_plugin($request['plugin'], $plugin_list);
+ break;
+ case 'disable':
+ case 'enable':
+ $flags_col = ( $request['mode'] == 'disable' ) ?
+ "plugin_flags | " . PLUGIN_DISABLED :
+ "plugin_flags & ~" . PLUGIN_DISABLED;
+ // did they specify a plugin to operate on?
+ if ( !isset($request['plugin']) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'No plugin specified.',
+ );
+ break;
+ }
+ // is the plugin in the directory and already installed?
+ if ( !isset($plugin_list[$request['plugin']]) || (
+ isset($plugin_list[$request['plugin']]) && !$plugin_list[$request['plugin']]['installed']
+ ))
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'Invalid plugin specified.',
+ );
+ break;
+ }
+ // get plugin id
+ $dataset =& $plugin_list[$request['plugin']];
+ if ( empty($dataset['plugin id']) )
+ {
+ $return = array(
+ 'mode' => 'error',
+ 'error' => 'Couldn\'t retrieve plugin ID.',
+ );
+ break;
+ }
+ // perform update
+ $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_flags = $flags_col WHERE plugin_id = {$dataset['plugin id']};");
+ if ( !$q )
+ $db->die_json();
+
+ $return = array(
+ 'success' => true
+ );
+ break;
+ case 'import':
+ // import all of the plugin_* config entries
+ $q = $db->sql_query('SELECT config_name, config_value FROM ' . table_prefix . "config WHERE config_name LIKE 'plugin_%';");
+ if ( !$q )
+ $db->die_json();
+
+ while ( $row = $db->fetchrow($q) )
+ {
+ $plugin_filename = preg_replace('/^plugin_/', '', $row['config_name']);
+ if ( isset($plugin_list[$plugin_filename]) && !@$plugin_list[$plugin_filename]['installed'] )
+ {
+ $return = $plugins->install_plugin($plugin_filename, $plugin_list);
+ if ( !$return['success'] )
+ break 2;
+ if ( $row['config_value'] == '0' )
+ {
+ $fn_db = $db->escape($plugin_filename);
+ $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_flags = plugin_flags | " . PLUGIN_DISABLED . " WHERE plugin_filename = '$fn_db';");
+ if ( !$q )
+ $db->die_json();
+ }
+ }
+ }
+ $db->free_result($q);
+
+ $q = $db->sql_query('DELETE FROM ' . table_prefix . "config WHERE config_name LIKE 'plugin_%';");
+ if ( !$q )
+ $db->die_json();
+
+ $return = array('success' => true);
+ break;
default:
// The requested action isn't something this script knows how to do
$return = array(
@@ -178,121 +300,7 @@
// Not a JSON request, output normal HTML interface
//
- // Scan all plugins
- $plugin_list = array();
-
- if ( $dirh = @opendir( ENANO_ROOT . '/plugins' ) )
- {
- while ( $dh = @readdir($dirh) )
- {
- if ( !preg_match('/\.php$/i', $dh) )
- continue;
- $fullpath = ENANO_ROOT . "/plugins/$dh";
- // it's a PHP file, attempt to read metadata
- // pass 1: try to read a !info block
- $blockdata = $plugins->parse_plugin_blocks($fullpath, 'info');
- if ( empty($blockdata) )
- {
- // no !info block, check for old header
- $fh = @fopen($fullpath, 'r');
- if ( !$fh )
- // can't read, bail out
- continue;
- $plugin_data = array();
- for ( $i = 0; $i < 8; $i++ )
- {
- $plugin_data[] = @fgets($fh, 8096);
- }
- // close our file handle
- fclose($fh);
- // is the header correct?
- if ( trim($plugin_data[0]) != '<?php' || trim($plugin_data[1]) != '/*' )
- {
- // nope. get out.
- continue;
- }
- // parse all the variables
- $plugin_meta = array();
- for ( $i = 2; $i <= 7; $i++ )
- {
- if ( !preg_match('/^([A-z0-9 ]+?): (.+?)$/', trim($plugin_data[$i]), $match) )
- continue 2;
- $plugin_meta[ strtolower($match[1]) ] = $match[2];
- }
- }
- else
- {
- // parse JSON block
- $plugin_data =& $blockdata[0]['value'];
- $plugin_data = enano_clean_json(enano_trim_json($plugin_data));
- try
- {
- $plugin_meta_uc = enano_json_decode($plugin_data);
- }
- catch ( Exception $e )
- {
- continue;
- }
- // convert all the keys to lowercase
- $plugin_meta = array();
- foreach ( $plugin_meta_uc as $key => $value )
- {
- $plugin_meta[ strtolower($key) ] = $value;
- }
- }
- if ( !isset($plugin_meta) || !is_array(@$plugin_meta) )
- {
- // parsing didn't work.
- continue;
- }
- // check for required keys
- $required_keys = array('plugin name', 'plugin uri', 'description', 'author', 'version', 'author uri');
- foreach ( $required_keys as $key )
- {
- if ( !isset($plugin_meta[$key]) )
- // not set, skip this plugin
- continue 2;
- }
- // decide if it's a system plugin
- $plugin_meta['system plugin'] = in_array($dh, $plugins->system_plugins);
- // reset installed variable
- $plugin_meta['installed'] = false;
- $plugin_meta['status'] = 0;
- // all checks passed
- $plugin_list[$dh] = $plugin_meta;
- }
- }
- // gather info about installed plugins
- $q = $db->sql_query('SELECT plugin_filename, plugin_version, plugin_flags FROM ' . table_prefix . 'plugins;');
- if ( !$q )
- $db->_die();
- while ( $row = $db->fetchrow() )
- {
- if ( !isset($plugin_list[ $row['plugin_filename'] ]) )
- {
- // missing plugin file, don't report (for now)
- continue;
- }
- $filename =& $row['plugin_filename'];
- $plugin_list[$filename]['installed'] = true;
- $plugin_list[$filename]['status'] = PLUGIN_INSTALLED;
- if ( $row['plugin_version'] != $plugin_list[$filename]['version'] )
- {
- $plugin_list[$filename]['status'] |= PLUGIN_OUTOFDATE;
- $plugin_list[$filename]['version installed'] = $row['plugin_version'];
- }
- if ( $row['plugin_flags'] & PLUGIN_DISABLED )
- {
- $plugin_list[$filename]['status'] |= PLUGIN_DISABLED;
- }
- }
- $db->free_result();
-
- // sort it all out by filename
- ksort($plugin_list);
-
// start printing things out
- acp_start_form();
?>
<div class="tblholder">
<table border="0" cellspacing="1" cellpadding="5">
@@ -324,7 +332,7 @@
{
$color = '_red';
$status = $lang->get('acppl_lbl_status_need_upgrade');
- $buttons = 'uninstall|update';
+ $buttons = 'uninstall|upgrade';
}
else if ( $data['installed'] && $data['status'] & PLUGIN_DISABLED )
{
@@ -389,7 +397,7 @@
<div style=\"float: right;\">
<b>$status</b>
</div>
- <div style=\"cursor: pointer;\" onclick=\"if ( !this.fx ) this.fx = new Spry.Effect.Blind('plugininfo_$uuid', { duration: 500, from: '0%', to: '100%', toggle: true }); this.fx.start();\"
+ <div style=\"cursor: pointer;\" onclick=\"if ( !this.fx ) this.fx = new Spry.Effect.Blind('plugininfo_$uuid', { duration: 500, from: '0%', to: '100%', toggle: true }); this.fx.start();\">
$plugin_basics
</div>
<span class=\"menuclear\"></span>
@@ -410,5 +418,18 @@
</table>
</div>
<?php
- echo '</form>';
+ // are there still old style plugin entries?
+ $q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "config WHERE config_name LIKE 'plugin_%';");
+ if ( !$q )
+ $db->_die();
+
+ $count = $db->numrows();
+ $db->free_result($q);
+
+ if ( $count > 0 )
+ {
+ echo '<h3>' . $lang->get('acppl_msg_old_entries_title') . '</h3>';
+ echo '<p>' . $lang->get('acppl_msg_old_entries_body') . '</p>';
+ echo '<p><a class="abutton abutton_green" href="#" onclick="ajaxPluginAction(\'import\', \'\', false); return false;">' . $lang->get('acppl_btn_import_old') . '</a></p>';
+ }
}