<?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/>.

/**
 * Mantis Plugin Handling
 * @copyright Copyright 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
 * @copyright Copyright 2002  MantisBT Team - mantisbt-dev@lists.sourceforge.net
 * @link http://www.mantisbt.org
 * @package MantisBT
 * @subpackage classes
 */

/**
 * Base class that implements basic plugin functionality
 * and integration with MantisBT. See the Mantis wiki for
 * 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;
	const STATUS_MISSING_EVENTS = 5;

	/**
	 * name - Your plugin's full name. Required value.
	 */
	public $name		= null;
	/**
	 * description - A full description of your plugin.
	 */
	public $description	= null;
	/**
	 * page - The name of a plugin page for further information and administration.
	 */
	public $page		= null;
	/**
	 * version - Your plugin's version string. Required value.
	 */
	public $version		= null;
	/**
	 * requires - An array of key/value pairs of basename/version plugin dependencies.
	 * Prefixing a version with '<' will allow your plugin to specify a maximum version (non-inclusive) for a dependency.
	 */
	public $requires	= null;
	/**
	 * An array of key/value pairs of basename/version  plugin dependencies (soft dependency)
	 */
	public $uses		= null;
	/**
	 * author - Your name, or an array of names.
	 */
	public $author		= null;
	/**
	 * contact - An email address where you can be contacted.
	 */
	public $contact		= null;
	/**
	 * url - A web address for your plugin.
	 */
	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
	 */
	abstract public function register();

	/**
	 * this function allows your plugin to set itself up, include any
	 * necessary API's, declare or hook events, etc.
	 * Alternatively, your plugin can hook the EVENT_PLUGIN_INIT event
	 * that will be called after all plugins have been initialized.
	 * @return bool
	 */
	public function init() {}

	/**
	 * This function allows plugins to add new error messages for Mantis usage
	 *
	 * @return array The error_name=>error_message list to add
	 */
	public function errors() {
		return array();
	}

	/**
	 * return an array of default configuration name/value pairs
	 * @return array
	 */
	public function config() {
		return array();
	}

	/**
	 * This function allows you to declare a new event, or a set of events that the plugin will
	 * trigger. This function returns an associative array with event names as the key and the event
	 * type as the value.
	 *
	 * For example, to add an event "foo" to our Example plugin that does not expect a return value
	 * (an "execute" event type), and another event 'bar' that expects a single value that gets
	 * modified by each hooked function (a "chain" event type):
	 *
	 * <code>
	 * class ExamplePlugin extends MantisPlugin {
	 * ...
	 * function events() {
	 *   return array(
	 *       'EVENT_EXAMPLE_FOO' => EVENT_TYPE_EXECUTE,
	 *       'EVENT_EXAMPLE_BAR' => EVENT_TYPE_CHAIN,
	 *   );
	 *  }
	 * }
	 * </code>
	 *
	 * When the Example plugin is loaded, the event system in MantisBT will add these two events to
	 * its list of events, and will then allow other plugins or functions to hook them. Naming the
	 * events "EVENT_PLUGINNAME_EVENTNAME" is not necessary, but is considered best practice to
	 * avoid conflicts between plugins.
	 * @return array
	 */
	public function events() {
		return array();
	}

	/**
	 * Hooking other events (or events from your own plugin) is almost identical to declaring them.
	 * Instead of passing an event type as the value, your plugin must pass the name of a class
	 * method on your plugin that will be called when the event is triggered. For our Example
	 * plugin, we'll create a foo() and bar() method on our plugin class, and hook them to the
	 * events we declared earlier.
	 *
	 * <code>
	 * class ExamplePlugin extends MantisPlugin {
	 * 		...
	 * 		function hooks() {
	 *   	return array(
	 *    	 	'EVENT_EXAMPLE_FOO' => 'foo',
	 *       	'EVENT_EXAMPLE_BAR' => 'bar',
	 *   	);
	 * 	}
	 * 	function foo( $p_event ) {
	 *  	...
	 * 	}
	 * 	function bar( $p_event, $p_chained_param ) {
	 *  	...
	 *   	return $p_chained_param;
	 * 	}
	 * }
	 * </code>
	 * Note that both hooked methods need to accept the $p_event parameter, as that contains the
	 * event name triggering the method (for cases where you may want a method hooked to multiple
	 * events).
	 * The bar() * method also accepts and returns the chained parameter in order to match the
	 * expectations of the "bar" event.
	 * @return array
	 */
	public function hooks() {
		return array();
	}

	/**
	 * Array of Database Schema changes for the plugin
	 *
	 * For example, the following code example would try and perform 4 schema updates:
	 * 1. create a user table with a user id column.
	 * 2. add an additional column called 'count'.
	 * 3. Updates the existing database data by adding 1 to count
	 * 4. Tries to create a unique index on the column userid
	 *
	 * <code>
	 * function schema() {
	 *	return array(
	 *		array( 'CreateTableSQL', array( plugin_table( 'user' ), "
	 *	 		user_id		I		NOTNULL UNSIGNED PRIMARY",
	 *			array('mysql' => 'ENGINE=MyISAM DEFAULT CHARSET=utf8', 'pgsql' => 'WITHOUT OIDS')));
	 *		array( 'AddColumnSQL', array( plugin_table( 'user' ), "
	 *			count	I		NOTNULL UNSIGNED DEFAULT '0' " ) ),
	 *		array( 'UpdateSQL', array( plugin_table( 'user' ), " SET count=count+1" ) ),
	 *		array( 'CreateIndexSQL',
	 *         array( 'idx_<plugin>_userid', plugin_table('user'), 'userid', array('UNIQUE'))),
	 *  );
	 * }
	 * </code>
	 * @return array
	 */
	public function schema() {
		return array();
	}

	/**
	  * Perform pre-installation operations
	  *
	  * This method is called before installing the given plugin.
	  * It can be used to add pre-install checks on external requirements
	  *
	  * @return bool true if install can proceed
	  */
	public function install() {
		return true;
	}

	/**
	 * This callback is executed after the normal schema upgrade process has executed.
	 * This gives your plugin the chance to convert or normalize data after an upgrade
	 *
	 * @todo It is possible to call php functions from within the schema upgrade itself, so really needed?
	 *
	 * @param integer $p_schema Schema Version ID.
	 * @return boolean
	 */
	public function upgrade( $p_schema ) {
		return true;
	}

	/**
	 * This callback is executed after the normal uninstallation process, and should
	 * handle such operations as reverting database schemas, removing unnecessary data,
	 * etc. This callback should be used only if Mantis would break when this plugin
	 * is uninstalled without any other actions taken, as users may not want to lose
	 * data, or be able to re-install the plugin later.
	 * @return void
	 */
	public function uninstall() {
	}

	### Core plugin functionality ###

	/**
	 * Plugin Basename
	 */
	public $basename	= null;

	/**
	 * Constructor
	 *
	 * @param string $p_basename Plugin Basename.
	 */
	final public function __construct( $p_basename ) {
		$this->basename = $p_basename;
		$this->register();
	}

	/**
	 * Initialisation
	 * @return bool
	 */
	final public function __init() {
		plugin_config_defaults( $this->config() );
		event_declare_many( $this->events() );
		if( !plugin_event_hook_many( $this->hooks() ) ) {
			return false;
		}

		# Backwards-compatibility - return true if no return value
		return $this->init() ?? true;
	}

	/**
	 * Check the plugin's validity status.
	 *
	 * A plugin is considered valid if:
	 * - its base name is set {@see self::$name}
	 * - it has a version number {@see self::$version}
	 *
	 * @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;
	}
}
