summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <LukeShu@sbcglobal.net>2010-12-25 01:02:11 -0700
committerLuke Shumaker <LukeShu@sbcglobal.net>2010-12-25 01:02:11 -0700
commit7e5c79095f86d5d92237ec32ced05316518c6e22 (patch)
tree323f8193177f0a2a4cbed292fcad3365375d4dad
parent37c03d68a82fb978d46d3a9e7c9051983de38b3d (diff)
Load data for all plugins, not just loaded ones, on the plugin management page; making it actually useful.
2010-11-20: Luke Shumaker <lukeshu@sbcglobal.net> Load data for all plugins, not just loaded ones, on the plugin management page; making it actually useful. * include.php: file to include to make creation of entry points easy (taken from index.php) * index.php: move most of this file into include.php (and include it) * plugindata.php: a separate entry point using include.php; load ALL plugins found, and write data gathered to plugindata.out.php * .gitignore: add plugindata.out.php * actions/pluginsadminpanel.php: o use plugindata.out.php's common_plugindata() instead of StatusNet::getPlugins() o give a button linking to plugindata.php, to refresh plugin data o rename showDefaultPlugins() to showPlugins() * lib/pluginlist.php: o use plugindata.out.php's common_plugindata() instead of thowing the 'PluginVersion' event to currently enabled plugins o for the enable/disable forms: (pseudo diff) - if (!$disabled) + if ($enabled && (!$disabled)) o fix metaInfo(): "@fixme pull structured data from plugin source": we can do that now I feel that adding a separate entry point is a fairly controversial change, and that it requires justification. First, let me note that even if you do not agree with adding another entry point, moving much of index.php intp include.php to make creation of entry points easy is a good idea because this makes debugging _way_ easier. As Ian Zenhack put it, "I'm not sure I like the idea of introducing a second entry point, especially for a smallish feature enhancement such as this." I initially agreed with this, however, after experimenting with several different options, I decided that creating the separate entry point was the best option. The biggest requirement for my efforts was 1. don't require revamping of the entire plugin system which is what Brion Vibber seems to think is necessary on the StatusNet wiki (http://status.net/wiki/Plugin_installation_interface). There are simply too many valuable plugins already, breaking compatibility would be a Bad Thing. Since the plugin data is gathered from a usually non-static function of the plugin object, and instantiating the object loads the plugin, this essentially gives us the requirement 2. get the output of a non-static function without instantiating the object The obvious solution would be to load the object in a sandbox environment, and save the output somewhere. This is what the separate entry point is, a sandbox. A cool perk of my method is that it allows us to process the data in an orderly way, such as "keying" the array that the data is in, allowing for orderly plugin lookup. There are a lot of possiblities that this gives us, I have limited myself to using this to address the @fixme in lib/pluginlist.php, in order to keep diff size small, and changes obvious. A neat feature that we can add is a collapsible tree in the plugin management page, based on class hierarchy. I have done this, but it is glitzy, and more of a proof of concept. To address security and server load concerns, I have implemented security around plugindata.php (the separate entry point that refreshes plugin data). In order to run the file, you must either run it from the command line as a script, or be logged in as a user with rights to configure the site. This prevents lusers from spamming this entry point.
-rw-r--r--.gitignore1
-rw-r--r--actions/pluginsadminpanel.php17
-rw-r--r--include.php217
-rw-r--r--index.php178
-rw-r--r--lib/pluginlist.php40
-rw-r--r--plugindata.php104
6 files changed, 340 insertions, 217 deletions
diff --git a/.gitignore b/.gitignore
index bf491ec3c..6680ea539 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@ php.log
.DS_Store
nbproject
*.mo
+plugindata.out.php
diff --git a/actions/pluginsadminpanel.php b/actions/pluginsadminpanel.php
index c1f4fe253..f095906fa 100644
--- a/actions/pluginsadminpanel.php
+++ b/actions/pluginsadminpanel.php
@@ -68,6 +68,9 @@ class PluginsadminpanelAction extends AdminPanelAction
return _('Additional plugins can be enabled and configured manually. ' .
'See the <a href="http://status.net/wiki/Plugins">online plugin ' .
'documentation</a> for more details.');
+ '<form action="'.common_config('site','path').'/plugindata.php">'.
+ '<input type="submit" value="'._('Refresh Plugin Data').'" />'.
+ '</form>';
}
/**
@@ -83,19 +86,18 @@ class PluginsadminpanelAction extends AdminPanelAction
// TRANS: Admin form section header
$this->element('legend', null, _('Default plugins'), 'default');
- $this->showDefaultPlugins();
+ $this->showPlugins();
$this->elementEnd('fieldset');
}
/**
- * Until we have a general plugin metadata infrastructure, for now
- * we'll just list up the ones we know from the global default
- * plugins list.
+ * Show a list of all plugins listed in 'plugindata.out.php'
*/
- protected function showDefaultPlugins()
+ protected function showPlugins()
{
- $plugins = array_keys(StatusNet::getPlugins());
+ require_once INSTALLDIR.'/plugindata.out.php';
+ $plugins = array_keys(common_plugindata());
natsort($plugins);
if ($plugins) {
@@ -103,8 +105,7 @@ class PluginsadminpanelAction extends AdminPanelAction
$list->show();
} else {
$this->element('p', null,
- _('All default plugins have been disabled from the ' .
- 'site\'s configuration file.'));
+ _('No plugins found.'));
}
}
}
diff --git a/include.php b/include.php
new file mode 100644
index 000000000..47b1d19e6
--- /dev/null
+++ b/include.php
@@ -0,0 +1,217 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category StatusNet
+ * @package StatusNet
+ * @author Brenda Wallace <shiny@cpan.org>
+ * @author Brion Vibber <brion@pobox.com>
+ * @author Christopher Vollick <psycotica0@gmail.com>
+ * @author CiaranG <ciaran@ciarang.com>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Evan Prodromou <evan@controlezvous.ca>
+ * @author Gina Haeussge <osd@foosel.net>
+ * @author James Walker <walkah@walkah.net>
+ * @author Jeffery To <jeffery.to@gmail.com>
+ * @author Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author Robin Millette <millette@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @author Tom Adams <tom@holizz.com>
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ *
+ * @license GNU Affero General Public License http://www.gnu.org/licenses/
+ */
+
+define('INSTALLDIR', dirname(__FILE__));
+define('STATUSNET', true);
+define('LACONICA', true); // compatibility
+
+$user = null;
+$action = null;
+
+function getPath($req)
+{
+ if ((common_config('site', 'fancy') || !array_key_exists('PATH_INFO', $_SERVER))
+ && array_key_exists('p', $req)
+ ) {
+ return $req['p'];
+ } else if (array_key_exists('PATH_INFO', $_SERVER)) {
+ $path = $_SERVER['PATH_INFO'];
+ $script = $_SERVER['SCRIPT_NAME'];
+ if (substr($path, 0, mb_strlen($script)) == $script) {
+ return substr($path, mb_strlen($script));
+ } else {
+ return $path;
+ }
+ } else {
+ return null;
+ }
+}
+
+/**
+ * logs and then displays error messages
+ *
+ * @return void
+ */
+function handleError($error)
+{
+ try {
+
+ if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
+ return;
+ }
+
+ $logmsg = "PEAR error: " . $error->getMessage();
+ if ($error instanceof PEAR_Exception && common_config('site', 'logdebug')) {
+ $logmsg .= " : ". $error->toText();
+ }
+ // DB queries often end up with a lot of newlines; merge to a single line
+ // for easier grepability...
+ $logmsg = str_replace("\n", " ", $logmsg);
+ common_log(LOG_ERR, $logmsg);
+
+ // @fixme backtrace output should be consistent with exception handling
+ if (common_config('site', 'logdebug')) {
+ $bt = $error->getTrace();
+ foreach ($bt as $n => $line) {
+ common_log(LOG_ERR, formatBacktraceLine($n, $line));
+ }
+ }
+ if ($error instanceof DB_DataObject_Error
+ || $error instanceof DB_Error
+ || ($error instanceof PEAR_Exception && $error->getCode() == -24)
+ ) {
+ //If we run into a DB error, assume we can't connect to the DB at all
+ //so set the current user to null, so we don't try to access the DB
+ //while rendering the error page.
+ global $_cur;
+ $_cur = null;
+
+ $msg = sprintf(
+ _(
+ 'The database for %s isn\'t responding correctly, '.
+ 'so the site won\'t work properly. '.
+ 'The site admins probably know about the problem, '.
+ 'but you can contact them at %s to make sure. '.
+ 'Otherwise, wait a few minutes and try again.'
+ ),
+ common_config('site', 'name'),
+ common_config('site', 'email')
+ );
+ } else {
+ $msg = _(
+ 'An important error occured, probably related to email setup. '.
+ 'Check logfiles for more info..'
+ );
+ }
+
+ $dac = new DBErrorAction($msg, 500);
+ $dac->showPage();
+
+ } catch (Exception $e) {
+ echo _('An error occurred.');
+ }
+ exit(-1);
+}
+
+set_exception_handler('handleError');
+
+require_once INSTALLDIR . '/lib/common.php';
+
+/**
+ * Format a backtrace line for debug output roughly like debug_print_backtrace() does.
+ * Exceptions already have this built in, but PEAR error objects just give us the array.
+ *
+ * @param int $n line number
+ * @param array $line per-frame array item from debug_backtrace()
+ * @return string
+ */
+function formatBacktraceLine($n, $line)
+{
+ $out = "#$n ";
+ if (isset($line['class'])) $out .= $line['class'];
+ if (isset($line['type'])) $out .= $line['type'];
+ if (isset($line['function'])) $out .= $line['function'];
+ $out .= '(';
+ if (isset($line['args'])) {
+ $args = array();
+ foreach ($line['args'] as $arg) {
+ // debug_print_backtrace seems to use var_export
+ // but this gets *very* verbose!
+ $args[] = gettype($arg);
+ }
+ $out .= implode(',', $args);
+ }
+ $out .= ')';
+ $out .= ' called at [';
+ if (isset($line['file'])) $out .= $line['file'];
+ if (isset($line['line'])) $out .= ':' . $line['line'];
+ $out .= ']';
+ return $out;
+}
+
+function setupRW()
+{
+ global $config;
+
+ static $alwaysRW = array('session', 'remember_me');
+
+ // We ensure that these tables always are used
+ // on the master DB
+
+ $config['db']['database_rw'] = $config['db']['database'];
+ $config['db']['ini_rw'] = INSTALLDIR.'/classes/statusnet.ini';
+
+ foreach ($alwaysRW as $table) {
+ $config['db']['table_'.$table] = 'rw';
+ }
+}
+
+function checkMirror($action_obj, $args)
+{
+ global $config;
+
+ if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
+ if (is_array(common_config('db', 'mirror'))) {
+ // "load balancing", ha ha
+ $arr = common_config('db', 'mirror');
+ $k = array_rand($arr);
+ $mirror = $arr[$k];
+ } else {
+ $mirror = common_config('db', 'mirror');
+ }
+
+ // everyone else uses the mirror
+
+ $config['db']['database'] = $mirror;
+ }
+}
+
+function isLoginAction($action)
+{
+ static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd', 'hostmeta');
+
+ $login = null;
+
+ if (Event::handle('LoginAction', array($action, &$login))) {
+ $login = in_array($action, $loginActions);
+ }
+
+ return $login;
+}
+
diff --git a/index.php b/index.php
index 5a08aa078..8ab68be44 100644
--- a/index.php
+++ b/index.php
@@ -37,183 +37,7 @@
* @license GNU Affero General Public License http://www.gnu.org/licenses/
*/
-define('INSTALLDIR', dirname(__FILE__));
-define('STATUSNET', true);
-define('LACONICA', true); // compatibility
-
-$user = null;
-$action = null;
-
-function getPath($req)
-{
- if ((common_config('site', 'fancy') || !array_key_exists('PATH_INFO', $_SERVER))
- && array_key_exists('p', $req)
- ) {
- return $req['p'];
- } else if (array_key_exists('PATH_INFO', $_SERVER)) {
- $path = $_SERVER['PATH_INFO'];
- $script = $_SERVER['SCRIPT_NAME'];
- if (substr($path, 0, mb_strlen($script)) == $script) {
- return substr($path, mb_strlen($script));
- } else {
- return $path;
- }
- } else {
- return null;
- }
-}
-
-/**
- * logs and then displays error messages
- *
- * @return void
- */
-function handleError($error)
-{
- try {
-
- if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
- return;
- }
-
- $logmsg = "PEAR error: " . $error->getMessage();
- if ($error instanceof PEAR_Exception && common_config('site', 'logdebug')) {
- $logmsg .= " : ". $error->toText();
- }
- // DB queries often end up with a lot of newlines; merge to a single line
- // for easier grepability...
- $logmsg = str_replace("\n", " ", $logmsg);
- common_log(LOG_ERR, $logmsg);
-
- // @fixme backtrace output should be consistent with exception handling
- if (common_config('site', 'logdebug')) {
- $bt = $error->getTrace();
- foreach ($bt as $n => $line) {
- common_log(LOG_ERR, formatBacktraceLine($n, $line));
- }
- }
- if ($error instanceof DB_DataObject_Error
- || $error instanceof DB_Error
- || ($error instanceof PEAR_Exception && $error->getCode() == -24)
- ) {
- //If we run into a DB error, assume we can't connect to the DB at all
- //so set the current user to null, so we don't try to access the DB
- //while rendering the error page.
- global $_cur;
- $_cur = null;
-
- $msg = sprintf(
- _(
- 'The database for %s isn\'t responding correctly, '.
- 'so the site won\'t work properly. '.
- 'The site admins probably know about the problem, '.
- 'but you can contact them at %s to make sure. '.
- 'Otherwise, wait a few minutes and try again.'
- ),
- common_config('site', 'name'),
- common_config('site', 'email')
- );
- } else {
- $msg = _(
- 'An important error occured, probably related to email setup. '.
- 'Check logfiles for more info..'
- );
- }
-
- $dac = new DBErrorAction($msg, 500);
- $dac->showPage();
-
- } catch (Exception $e) {
- echo _('An error occurred.');
- }
- exit(-1);
-}
-
-set_exception_handler('handleError');
-
-require_once INSTALLDIR . '/lib/common.php';
-
-/**
- * Format a backtrace line for debug output roughly like debug_print_backtrace() does.
- * Exceptions already have this built in, but PEAR error objects just give us the array.
- *
- * @param int $n line number
- * @param array $line per-frame array item from debug_backtrace()
- * @return string
- */
-function formatBacktraceLine($n, $line)
-{
- $out = "#$n ";
- if (isset($line['class'])) $out .= $line['class'];
- if (isset($line['type'])) $out .= $line['type'];
- if (isset($line['function'])) $out .= $line['function'];
- $out .= '(';
- if (isset($line['args'])) {
- $args = array();
- foreach ($line['args'] as $arg) {
- // debug_print_backtrace seems to use var_export
- // but this gets *very* verbose!
- $args[] = gettype($arg);
- }
- $out .= implode(',', $args);
- }
- $out .= ')';
- $out .= ' called at [';
- if (isset($line['file'])) $out .= $line['file'];
- if (isset($line['line'])) $out .= ':' . $line['line'];
- $out .= ']';
- return $out;
-}
-
-function setupRW()
-{
- global $config;
-
- static $alwaysRW = array('session', 'remember_me');
-
- // We ensure that these tables always are used
- // on the master DB
-
- $config['db']['database_rw'] = $config['db']['database'];
- $config['db']['ini_rw'] = INSTALLDIR.'/classes/statusnet.ini';
-
- foreach ($alwaysRW as $table) {
- $config['db']['table_'.$table] = 'rw';
- }
-}
-
-function checkMirror($action_obj, $args)
-{
- global $config;
-
- if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
- if (is_array(common_config('db', 'mirror'))) {
- // "load balancing", ha ha
- $arr = common_config('db', 'mirror');
- $k = array_rand($arr);
- $mirror = $arr[$k];
- } else {
- $mirror = common_config('db', 'mirror');
- }
-
- // everyone else uses the mirror
-
- $config['db']['database'] = $mirror;
- }
-}
-
-function isLoginAction($action)
-{
- static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp', 'opensearch', 'rsd', 'hostmeta');
-
- $login = null;
-
- if (Event::handle('LoginAction', array($action, &$login))) {
- $login = in_array($action, $loginActions);
- }
-
- return $login;
-}
+require 'include.php';
function main()
{
diff --git a/lib/pluginlist.php b/lib/pluginlist.php
index 07a17ba39..48ef1cb81 100644
--- a/lib/pluginlist.php
+++ b/lib/pluginlist.php
@@ -154,47 +154,23 @@ class PluginListItem extends Widget
*/
protected function getControlForm()
{
- $key = 'disable-' . $this->plugin;
- if (common_config('plugins', $key)) {
- return new PluginEnableForm($this->out, $this->plugin);
- } else {
+ $enabled = array_key_exists($this->plugin, StatusNet::getPlugins());
+ $disabled = common_config('plugins', 'disable-'.$this->plugin);
+
+ if ( $enabled && (!$disabled) ) {
return new PluginDisableForm($this->out, $this->plugin);
+ } else {
+ return new PluginEnableForm($this->out, $this->plugin);
}
}
+
/**
* Grab metadata about this plugin...
- * Warning: horribly inefficient and may explode!
- * Doesn't work for disabled plugins either.
- *
- * @fixme pull structured data from plugin source
*/
function metaInfo()
{
- $versions = self::getPluginVersions();
- $found = false;
-
- foreach ($versions as $info) {
- // hack for URL shorteners... "LilUrl (ur1.ca)" etc
- list($name, ) = explode(' ', $info['name']);
-
- if ($name == $this->plugin) {
- if ($found) {
- // hack for URL shorteners...
- $found['rawdescription'] .= "<br />\n" . $info['rawdescription'];
- } else {
- $found = $info;
- }
- }
- }
-
- if ($found) {
- return $found;
- } else {
- return array('name' => $this->plugin,
- 'rawdescription' => _m('plugin-description',
- '(Plugin descriptions unavailable when disabled.)'));
- }
+ return $versions[$this->plugin];
}
/**
diff --git a/plugindata.php b/plugindata.php
new file mode 100644
index 000000000..cbabe7f7a
--- /dev/null
+++ b/plugindata.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010 Free Software Foundation, Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category StatusNet
+ * @package StatusNet
+ * @author Luke Shumaker <lukeshu@sbcglobal.net>
+ * @copyright 2010 Free Software Foundation, Inc http://www.fsf.org
+ *
+ * @license GNU Affero General Public License http://www.gnu.org/licenses/
+ */
+
+require 'include.php' ;
+
+function main() {
+ // Check permissions
+
+ if (!isset($argv[0])) {
+ if (!common_logged_in()) {
+ // TRANS: Client error message thrown when trying to load plugin data while not logged in.
+ echo(_('Not logged in.')."\n");
+ return false;
+ }
+ $user = common_current_user();
+ assert(!empty($user));
+ if (!$user->hasRight(Right::CONFIGURESITE)) {
+ // TRANS: Client error message thrown when a user tries to load plugin data but has no access rights.
+ echo(_('You cannot make changes to this site.')."\n");
+ return false;
+ }
+ }
+
+ // Do stuff
+
+ $possibleFiles = StatusNet::pluginFiles('*');
+ foreach ($possibleFiles as $pattern) {
+ $regex = '/'.str_replace('/','\/',str_replace('*','(.*)',$pattern)).'/';
+ $matches = glob($pattern);
+ foreach ($matches as $file) {
+ $name = preg_replace($regex,'\1',$file);
+ loadPlugin($name,$file,$data);
+ }
+ }
+ $redirect = isset($_SERVER['HTTP_REFERER']);
+
+ $fileName = 'plugindata.out.php';
+ $fh = fopen($fileName, 'w');
+ if (!$fh) {
+ $redirect = false;
+ // TRANS: Client error message thrown when plugindata.out.php can't be opened
+ echo(_("ERROR: cannot open 'plugindata.out.php' for writing.")."\n");
+ }
+ fwrite($fh, "<?php\n".
+ "function common_plugindata() {\n".
+ " return ".var_export($data,true).";\n".
+ "}\n"
+ );
+ fclose($fh);
+ if ($redirect) {
+ header('Location: '.$_SERVER['HTTP_REFERER']);
+ } else {
+ header('Content-type: text/plain');
+ // TRANS: Plain text divider between mesages and plugin data.
+ echo("\n"._('---- Raw Plugin Data ----')."\n\n");
+ var_export($data);
+ }
+}
+
+function loadPlugin($pluginName,$file,&$data) {
+ if (!class_exists($pluginName.'Plugin')) {
+ if (@file_exists($file)) {
+ include_once($file);
+ }
+ }
+ if (class_exists($pluginName.'Plugin')) {
+ $className = $pluginName.'Plugin';
+ $obj = new $className;
+
+ $newData = array();
+ $obj->onPluginVersion($newData);
+ foreach ($newData as $pluginInfo) {
+ $data[$pluginName] = $pluginInfo;
+ }
+ } else {
+ echo 'ERROR: Class "'.$pluginName."Plugin\" still does not exist\n";
+ }
+}
+
+main();
+