From ca22279797f58400ec62ffcd5dedcdf10d298652 Mon Sep 17 00:00:00 2001 From: Diogo Cordeiro Date: Fri, 16 Aug 2019 15:06:27 +0100 Subject: [PATCH] [MODULES] List all available plugins and allow enabling them via UI Yet another revision of the Admin Plugin Management tool --- actions/pluginenable.php | 21 ++-- actions/pluginsadminpanel.php | 29 ++---- actions/version.php | 2 +- lib/plugindisableform.php | 6 +- lib/pluginenableform.php | 8 +- lib/pluginlist.php | 185 +++++++++++++++++++++++++++------- 6 files changed, 176 insertions(+), 75 deletions(-) diff --git a/actions/pluginenable.php b/actions/pluginenable.php index 6977bcdd7d..5aa452c302 100644 --- a/actions/pluginenable.php +++ b/actions/pluginenable.php @@ -69,7 +69,7 @@ class PluginenableAction extends Action if (!$token || $token != common_session_token()) { // TRANS: Client error displayed when the session token does not match or is not given. - $this->clientError(_('There was a problem with your session token.'. + $this->clientError(_m('There was a problem with your session token.'. ' Try again, please.')); } @@ -79,19 +79,18 @@ class PluginenableAction extends Action if (empty($this->user)) { // TRANS: Error message displayed when trying to perform an action that requires a logged in user. - $this->clientError(_('Not logged in.')); + $this->clientError(_m('Not logged in.')); } if (!AdminPanelAction::canAdmin('plugins')) { // TRANS: Client error displayed when trying to enable or disable a plugin without access rights. - $this->clientError(_('You cannot administer plugins.')); + $this->clientError(_m('You cannot administer plugins.')); } $this->plugin = $this->arg('plugin'); - $defaultPlugins = common_config('plugins', 'default'); - if (!array_key_exists($this->plugin, $defaultPlugins)) { + if (!array_key_exists($this->plugin, array_flip(PluginList::grabAllPluginNames()))) { // TRANS: Client error displayed when trying to enable or disable a non-existing plugin. - $this->clientError(_('No such plugin.')); + $this->clientError(_m('No such plugin.')); } return true; @@ -107,6 +106,16 @@ class PluginenableAction extends Action */ function handle() { + if (!PluginList::isPluginLoaded($this->plugin)) { + $config_file = INSTALLDIR . DIRECTORY_SEPARATOR . 'config.php'; + $handle = fopen($config_file, 'a'); + if (!$handle) { + $this->clientError(_m('No permissions for writing to config.php')); + } + $data = PHP_EOL.'addPlugin(\''.$this->plugin.'\'); // Added by sysadmin\'s Plugin UI.'; + fwrite($handle, $data); + fclose($handle); + } $key = 'disable-' . $this->plugin; Config::save('plugins', $key, $this->overrideValue()); diff --git a/actions/pluginsadminpanel.php b/actions/pluginsadminpanel.php index 11bad8ecf2..a01afea93f 100644 --- a/actions/pluginsadminpanel.php +++ b/actions/pluginsadminpanel.php @@ -47,7 +47,7 @@ class PluginsadminpanelAction extends AdminPanelAction function getInstructions() { // TRANS: Instructions at top of plugin admin page. - return _('Additional plugins can be enabled and configured manually. ' . + return _m('Additional plugins can be enabled and configured manually. ' . 'See the online plugin ' . 'documentation for more details.'); } @@ -59,34 +59,19 @@ class PluginsadminpanelAction extends AdminPanelAction */ function showForm() { - $this->elementStart('fieldset', array('id' => 'settings_plugins_default')); + $this->elementStart('fieldset', ['id' => 'settings_plugins_default']); // TRANS: Admin form section header - $this->element('legend', null, _('Default plugins')); + $this->element('legend', null, _m('Available Plugins')); - $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. - */ - protected function showDefaultPlugins() + protected function showPlugins() { - $plugins = array_keys(common_config('plugins', 'default')); - natsort($plugins); - - if ($plugins) { - $list = new PluginList($plugins, $this); - $list->show(); - } else { - $this->element('p', null, - // TRANS: Text displayed on plugin admin page when no plugin are enabled. - _('All default plugins have been disabled from the ' . - 'site\'s configuration file.')); - } + $list = new PluginList($this); + $list->show(); } } diff --git a/actions/version.php b/actions/version.php index 5179a302f2..3a188416e9 100644 --- a/actions/version.php +++ b/actions/version.php @@ -73,7 +73,7 @@ class VersionAction extends Action { parent::prepare($args); - Event::handle('PluginVersion', [&$this->pluginVersions]); + $this->pluginVersions = PluginList::getActivePluginVersions(); return true; } diff --git a/lib/plugindisableform.php b/lib/plugindisableform.php index 3fc65163fe..85f221a200 100644 --- a/lib/plugindisableform.php +++ b/lib/plugindisableform.php @@ -17,7 +17,7 @@ defined('STATUSNET') || die(); /** - * Form for joining a group + * Form for disabling a plugin * * @category Form * @package StatusNet @@ -34,7 +34,6 @@ class PluginDisableForm extends PluginEnableForm * * @return string ID of the form */ - public function id() { return 'plugin-disable-' . $this->plugin; @@ -45,7 +44,6 @@ class PluginDisableForm extends PluginEnableForm * * @return string of the form class */ - public function formClass() { return 'form_plugin_disable'; @@ -56,7 +54,6 @@ class PluginDisableForm extends PluginEnableForm * * @return string URL of the action */ - public function action() { return common_local_url( @@ -71,7 +68,6 @@ class PluginDisableForm extends PluginEnableForm * @return void * @throws Exception */ - public function formActions() { // TRANS: Plugin admin panel controls diff --git a/lib/pluginenableform.php b/lib/pluginenableform.php index a38a19376a..e099886f70 100644 --- a/lib/pluginenableform.php +++ b/lib/pluginenableform.php @@ -19,7 +19,7 @@ defined('STATUSNET') || die(); require_once INSTALLDIR . '/lib/form.php'; /** - * Form for joining a group + * Form for enabling a plugin * * @category Form * @package StatusNet @@ -34,7 +34,6 @@ class PluginEnableForm extends Form /** * Plugin to enable/disable */ - public $plugin = null; /** @@ -43,7 +42,6 @@ class PluginEnableForm extends Form * @param HTMLOutputter $out output channel * @param string $plugin plugin to enable/disable */ - public function __construct($out = null, $plugin = null) { parent::__construct($out); @@ -56,7 +54,6 @@ class PluginEnableForm extends Form * * @return string ID of the form */ - public function id() { return 'plugin-enable-' . $this->plugin; @@ -67,7 +64,6 @@ class PluginEnableForm extends Form * * @return string of the form class */ - public function formClass() { return 'form_plugin_enable'; @@ -78,7 +74,6 @@ class PluginEnableForm extends Form * * @return string URL of the action */ - public function action() { return common_local_url( @@ -93,7 +88,6 @@ class PluginEnableForm extends Form * @return void * @throws Exception */ - public function formActions() { // TRANS: Plugin admin panel controls diff --git a/lib/pluginlist.php b/lib/pluginlist.php index e9e1c99b53..c41d5d8f5e 100644 --- a/lib/pluginlist.php +++ b/lib/pluginlist.php @@ -32,30 +32,68 @@ class PluginList extends Widget { public $plugins = []; - public function __construct($plugins, $out) + /** + * PluginList constructor. + * @param Action $out + * @param array|null $plugins + */ + public function __construct(Action $out, ?array $plugins = null) { parent::__construct($out); - $this->plugins = $plugins; + $this->plugins = is_null($plugins) ? $this->grabAllPluginNames() : $plugins; + } + + /** + * List of names of all available plugins (distribution and third parties). + * Warning: Plugin not modules, it doesn't include core modules. + * + * @return array + */ + public static function grabAllPluginNames(): array + { + $plugins = []; + $distribution_plugins = glob(INSTALLDIR . '/plugins/*'); + foreach ($distribution_plugins as $plugin) { + $plugin_name = ltrim($plugin, INSTALLDIR . '/plugins/'); + if ($plugin_name == 'README.md') { + continue; + } + $plugins[] = $plugin_name; + } + unset($distribution_plugins); + $thirdparty_plugins = glob(INSTALLDIR . '/local/plugins/*'); + foreach ($thirdparty_plugins as $plugin) { + $plugins[] = ltrim($plugin, INSTALLDIR . '/local/plugins/'); + } + unset($thirdparty_plugins); + natsort($plugins); + return $plugins; } public function show() { + if (!$this->plugins) { + $this->out->element('p', null, + // TRANS: Text displayed on plugin admin page when no plugin are enabled. + _m('All plugins have been disabled from the ' . + 'site\'s configuration file.')); + } $this->startList(); $this->showPlugins(); $this->endList(); } - public function startList() + public function startList(): void { $this->out->elementStart('table', 'plugin_list'); } - public function endList() + public function endList(): void { $this->out->elementEnd('table'); } - public function showPlugins() + public function showPlugins(): void { foreach ($this->plugins as $plugin) { $pli = $this->newListItem($plugin); @@ -63,12 +101,106 @@ class PluginList extends Widget } } - public function newListItem($plugin) + public function newListItem($plugin): PluginListItem { return new PluginListItem($plugin, $this->out); } + + /** Local cache for plugin version info */ + protected static $versions = false; + + /** + * Lazy-load the set of active plugin version info. + * Warning: Plugin not modules, it doesn't include core modules. + * @return array + */ + public static function getPluginVersions(): array + { + if (!is_array(self::$versions)) { + $plugin_versions = []; + Event::handle('PluginVersion', [&$plugin_versions]); + self::$versions = $plugin_versions; + } + return self::$versions; + } + + /** + * We need a proper name for comparison, that is, without spaces nor the `(section)` + * Therefore, a plugin named "Diogo Cordeiro (diogo@fc.up.pt)" becomes "DiogoCordeiro" + * + * WARNING: You may have to use strtolower() in your situation + * + * @param string $plugin_name + * @return string Name without spaces nor parentheses section + */ + public static function internalizePluginName(string $plugin_name): string + { + $name_without_spaces = str_replace(' ', '', $plugin_name); + $name_without_spaces_nor_parentheses_section = substr($name_without_spaces, 0, strpos($name_without_spaces . '(', '(')); + return $name_without_spaces_nor_parentheses_section; + } + + /** + * It calls self::getPluginVersions() and for each it builds an array with the self::internalizePluginName() + * + * @return array + */ + public static function getActivePluginVersions(): array + { + $versions = self::getPluginVersions(); + $active_plugins = []; + foreach ($versions as $info) { + $internal_plugin_name = self::internalizePluginName($info['name']); + + // This is sensitive case + $key = 'disable-' . $internal_plugin_name; + if (common_config('plugins', $key)) { + continue; + } + + $active_plugins[] = $info; + } + return $active_plugins; + } + + /** + * Checks if a given plugin was loaded (added in config.php with addPlugin()) + * + * @param string $plugin + * @return bool + * @see PluginListItem->metaInfo() Warning: horribly inefficient and may explode! + */ + public static function isPluginLoaded(string $plugin): bool + { + $versions = self::getPluginVersions(); + foreach ($versions as $info) { + $internal_plugin_name = self::internalizePluginName($info['name']); + + // For a proper comparison, we do it in lower case + if (strtolower($internal_plugin_name) == strtolower($plugin)) { + return true; + } + } + return false; + } + + /** + * Checks if a given plugin is active (both loaded and not set as inactive in the database) + * + * @param string $plugin + * @return bool + * @see self::isPluginLoaded() Warning: horribly inefficient and may explode! + */ + public static function isPluginActive(string $plugin): bool + { + $key = 'disable-' . $plugin; + return self::isPluginLoaded($plugin) && !common_config('plugins', $key); + } } +/** + * Class PluginListItem + */ class PluginListItem extends Widget { /** Current plugin. */ @@ -87,13 +219,13 @@ class PluginListItem extends Widget { $meta = $this->metaInfo(); - $this->out->elementStart('tr', array('id' => 'plugin-' . $this->plugin)); + $this->out->elementStart('tr', ['id' => 'plugin-' . $this->plugin]); // Name and controls $this->out->elementStart('td'); $this->out->elementStart('div'); if (!empty($meta['homepage'])) { - $this->out->elementStart('a', array('href' => $meta['homepage'])); + $this->out->elementStart('a', ['href' => $meta['homepage']]); } $this->out->text($this->plugin); if (!empty($meta['homepage'])) { @@ -125,7 +257,7 @@ class PluginListItem extends Widget if (!empty($meta['rawdescription'])) { $this->out->raw($meta['rawdescription']); } elseif (!empty($meta['description'])) { - $this->out->raw($meta['description']); + $this->out->text($meta['description']); } $this->out->elementEnd('td'); @@ -140,11 +272,10 @@ class PluginListItem extends Widget */ protected function getControlForm() { - $key = 'disable-' . $this->plugin; - if (common_config('plugins', $key)) { - return new PluginEnableForm($this->out, $this->plugin); - } else { + if (PluginList::isPluginActive($this->plugin)) { return new PluginDisableForm($this->out, $this->plugin); + } else { + return new PluginEnableForm($this->out, $this->plugin); } } @@ -154,19 +285,19 @@ class PluginListItem extends Widget * Doesn't work for disabled plugins either. * * @fixme pull structured data from plugin source + * ^ Maybe by introducing a ini file in each plugin's directory? But a typical instance doesn't have all that many + * plugins anyway, no need for urgent action */ public function metaInfo() { - $versions = self::getPluginVersions(); + $versions = PluginList::getPluginVersions(); $found = false; foreach ($versions as $info) { - // We need a proper name for comparison, that is, without spaces nor the `(section)` - // Therefore, a plugin named "Diogo Cordeiro (diogo@fc.up.pt)" becomes "DiogoCordeiro" - $name_without_spaces = str_replace(' ', '', $info['name']); - $name_without_spaces_nor_parentheses_section = substr($name_without_spaces, 0, strpos($name_without_spaces.'(', '(')); + $internal_plugin_name = PluginList::internalizePluginName($info['name']); - if (strtolower($name_without_spaces_nor_parentheses_section) == strtolower($this->plugin)) { + // For a proper comparison, we do it in lower case + if (strtolower($internal_plugin_name) == strtolower($this->plugin)) { if ($found) { $found['rawdescription'] .= "
\n" . $info['rawdescription']; } else { @@ -180,21 +311,7 @@ class PluginListItem extends Widget } else { return ['name' => $this->plugin, // TRANS: Plugin description for a disabled plugin. - 'rawdescription' => _m('plugin-description', '(The plugin description is unavailable when a plugin has been disabled.)')]; + 'rawdescription' => _m('plugin-description', '(The plugin description is unavailable when a plugin hasn\'t been loaded.)')]; } } - - /** - * Lazy-load the set of active plugin version info - * @return array - */ - protected static function getPluginVersions() - { - if (!is_array(self::$versions)) { - $versions = []; - Event::handle('ModuleVersion', [&$versions]); - self::$versions = $versions; - } - return self::$versions; - } }