Skip to content

Commit

Permalink
Improve handling of invalid plugins
Browse files Browse the repository at this point in the history
When installing plugins in a MantisBT instance, or when moving /
upgrading MantisBT there are several things that can go wrong:

- The case of the directory in which the plugin is installed does not
  exactly match the plugin's name
- A registered plugin is no longer present on disk
- The plugin code could be invalid
- etc.

This feature branch adds an "Invalid plugins" section on Manage Plugins
page, allowing the Administrator to identify issues and eventually fix
them.

Fixes #26142, PR #1565
  • Loading branch information
dregad committed Jan 16, 2021
2 parents 50e1fc7 + 70fa6aa commit 11a6d0d
Show file tree
Hide file tree
Showing 12 changed files with 979 additions and 254 deletions.
130 changes: 130 additions & 0 deletions admin/check/check_plugins_inc.php
@@ -0,0 +1,130 @@
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.

/**
* Plugins Checks
* @package MantisBT
* @copyright Copyright (C) 2019 MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
*
* @uses check_api.php
* @uses config_api.php
* @uses plugin_api.php
* @uses constant_inc.php
*/

if( !defined( 'CHECK_PLUGINS_INC_ALLOW' ) ) {
return;
}

# MantisBT Check API
require_once( 'check_api.php' );
require_api( 'config_api.php' );
require_api( 'plugin_api.php' );
require_api( 'constant_inc.php' );

check_print_section_header_row( 'Plugins' );

# Initialize
$t_plugins = plugin_find_all();
ksort($t_plugins);
plugin_init_installed();
$t_installed_plugins = array_filter(
$t_plugins,
function( $p ) { return plugin_is_registered( $p->basename ); }
);
$t_manage_plugins_link = '<a href="' . helper_mantis_url( 'manage_plugin_page.php' ) . '%s">%s</a>';

# Info row - plugins count
check_print_info_row(
"Checking all available and installed plugins",
count( $t_plugins ) . ' plugins, '
. count( $t_installed_plugins ) . ' installed'
);

# Check installed plugins
foreach( $t_installed_plugins as $t_basename => $t_plugin ) {
check_print_test_row(
"Installed Plugin '$t_basename'' is operational",
plugin_is_loaded( $t_basename ),
array(
false => "Plugin could not be loaded; check "
. sprintf( $t_manage_plugins_link, '#installed', 'Manage Plugins page' )
. " to ensure its dependencies are met or if it needs to be upgraded."
)
);
}

# Check force-installed plugins
# Note: not using plugin_get_force_installed() as we don't want to check MantisCore
$t_forced_plugins = config_get_global( 'plugins_force_installed' );
foreach( $t_forced_plugins as $t_basename => $t_priority ) {
$t_plugin = plugin_register( $t_basename );
check_print_test_warn_row(
"Force-installed plugin '$t_basename' is available and valid",
!$t_plugin instanceof InvalidPlugin,
array(
false => $t_plugin->description
. " - review 'plugins_force_installed' configuration option"
)
);
}

# Check for invalid or missing plugins
$t_invalid_plugins = array_filter(
$t_plugins,
function( $p ) {
return $p instanceof InvalidPlugin;
}
);
foreach( $t_invalid_plugins as $t_plugin ) {
$t_description = "'$t_plugin->name': $t_plugin->description";
if( $t_plugin->status_message ) {
$t_description .= "<br>$t_plugin->status_message";
}
$t_msg_contact = "Contact the Plugin's author.";

switch( $t_plugin->status ) {
case MantisPlugin::STATUS_MISSING_PLUGIN:
check_print_test_row(
$t_description,
false,
array(
false => sprintf( $t_manage_plugins_link, '#invalid', 'Remove the Plugin' )
. " or reinstall its source code."
)
);
break;
case MantisPlugin::STATUS_MISSING_BASE_CLASS:
# Issue a warning instead of a failure, to cover the case of a directory
# created under plugins/ for other purposes than storing a plugin.
# https://github.com/mantisbt/mantisbt/pull/1565#discussion_r329311260
check_print_test_warn_row(
$t_description,
false,
array(
false => "Rename the Plugin's directory or " . $t_msg_contact
)
);
break;
default:
check_print_test_row(
$t_description,
false,
array( false => $t_msg_contact )
);
}
}
5 changes: 5 additions & 0 deletions admin/check/index.php
Expand Up @@ -183,6 +183,11 @@ function mode_url( $p_all, $p_errors ) {
define( 'CHECK_DISPLAY_INC_ALLOW', true );
include( 'check_display_inc.php' );
}

if( !$g_failed_test ) {
define( 'CHECK_PLUGINS_INC_ALLOW', true );
include( 'check_plugins_inc.php' );
}
?>
</table>
</div>
Expand Down
65 changes: 65 additions & 0 deletions core/classes/InvalidDefinitionPlugin.class.php
@@ -0,0 +1,65 @@
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.

/**
* MantisBT Invalid Incomplete Definition Plugin
* @copyright Copyright 2020 MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
* @package MantisBT
* @subpackage classes
*/

/**
* MantisBT Invalid Definition Plugin class
*
* The purpose of this class is to handle incomplete plugin definitions, i.e.
* having undefined 'name' or 'version' properties.
*
* For Plugin API internal use only.
*/
class InvalidDefinitionPlugin extends InvalidPlugin {

function register() {
$this->name = $this->basename;
$this->description = lang_get( 'plugin_invalid_description' );

$this->status = self::STATUS_INCOMPLETE_DEFINITION;
}

public function setInvalidPlugin( MantisPlugin $p_plugin ) {
parent::setInvalidPlugin( $p_plugin );

$t_missing = array();

# Add the reference plugin's name, if defined
if( $p_plugin->name ) {
$this->name .= " ($p_plugin->name)";
} else {
$t_missing[] = 'name';
}

if( !$p_plugin->version ) {
$t_missing[] = 'version';
}

if( !empty( $t_missing ) ) {
$this->status_message = sprintf(
lang_get( 'plugin_invalid_status_message' ),
implode( ', ', $t_missing )
);
}
}
}
70 changes: 70 additions & 0 deletions core/classes/InvalidPlugin.class.php
@@ -0,0 +1,70 @@
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.

/**
* MantisBT Invalid Plugin
* @copyright Copyright 2019 MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
* @package MantisBT
* @subpackage classes
*/

/**
* MantisBT Generic Invalid Plugin class
*
* The purpose of this class is to handle invalid plugins. It is used as a base
* for other, specialized invalid plugin classes, e.g.
* @see InvalidIncompleteDefinitionPlugin
* @see MissingPlugin
* @see MissingClassPlugin
*
* For Plugin API internal use only.
*/
class InvalidPlugin extends MantisPlugin {
/**
* The reference, invalid Plugin.
*
* This is used for plugins that are considered invalid even though they
* can be loaded, so we can query the reference plugin's properties.
*
* @var MantisPlugin $ref_plugin
*/
public $ref_plugin;

/**
* Flag indicating whether the plugin can be removed from manage plugins page.
* @var bool $removable True if it can be removed,
* False if manual intervention is required.
*/
public $removable = true;

function register() {
$this->name = $this->basename;
$this->description = lang_get( 'plugin_invalid_description' );

$this->status = self::STATUS_INVALID;
}

/**
* Initialize the invalid plugin.
* @see MantisPlugin::getInvalidPlugin()
*
* @param MantisPlugin $p_plugin Reference, invalid plugin
*/
public function setInvalidPlugin( MantisPlugin $p_plugin ) {
$this->ref_plugin = $p_plugin;
}
}
59 changes: 59 additions & 0 deletions core/classes/MantisPlugin.class.php
Expand Up @@ -29,6 +29,26 @@
* more information.
*/
abstract class MantisPlugin {
/**
* Constants indicating the Plugin's validity status
*
* - VALID - Plugin is valid
* - INVALID - Generic invalid status
* - INCOMPLETE_DEFINITION - The plugin's definition is incomplete
* (i.e. required properties 'name' or 'version' are missing)
* - MISSING_BASE_CLASS - Plugin directory exists but does not contain a
* matching Class
* - MISSING_PLUGIN - The plugin has been installed, but its source code
* is no longer available in plugin_path directory
*
* @see MantisPlugin::$status
*/
const STATUS_VALID = 0;
const STATUS_INVALID = 1;
const STATUS_INCOMPLETE_DEFINITION = 2;
const STATUS_MISSING_BASE_CLASS = 3;
const STATUS_MISSING_PLUGIN = 4;

/**
* name - Your plugin's full name. Required value.
*/
Expand Down Expand Up @@ -67,6 +87,18 @@ abstract class MantisPlugin {
*/
public $url = null;

/**
* Plugin's validity status
* @var int $status
*/
public $status = self::STATUS_VALID;

/**
* Explanation of the reason why the plugin is not valid
* @var string $status_message
*/
public $status_message = '';

/**
* this function registers your plugin - must set at least name and version
* @return void
Expand Down Expand Up @@ -259,4 +291,31 @@ final public function __init() {

$this->init();
}

/**
* Check the plugin's validity status.
*
* @return bool True if the plugin is valid.
*/
public function isValid() {
return $this->name !== null && $this->version !== null;
}

/**
* Creates an InvalidPlugin object from the current plugin.
*
* Invalid Plugin objects are used by manage_plugin_page.php to present
* relevant information about the invalid plugin to the administrator so
* they can take appropriate action to fix the problem.
*
* By default, it returns an InvalidDefinitionPlugin object.
*
* @return InvalidPlugin
*/
public function getInvalidPlugin() {
$t_plugin = new InvalidDefinitionPlugin( $this->basename );
$t_plugin->setInvalidPlugin( $this );

return $t_plugin;
}
}

0 comments on commit 11a6d0d

Please sign in to comment.