Plugin System Overview

Author: John Reese

Status: Work in Progress

Design Overview

The dynamic plugins system for Mantis is based on a philosophy of simplicity and lightweight design. It provides a very flexible framework for adding features to Mantis, or modifying certain existing functionality, using plugins. The system has been created to utilize an event-based hook system, but also allows plugins to create new view pages and more. Most anything that isn’t directly allowed by the plugin system can easily be added on using the existing framework as a building block.

Inversion of Control

Rather than having each plugin execute and use the plugin API to set things up, the Mantis plugin manager uses the Inversion of Control design theories. This means that the core of plugins mostly consist of ‘callback functions’, usually discovered by the plugin manager at runtime. This greatly simplifies the work for the plugin developer, as it reduces the amount of necessary API calls from the plugin, as well as the amount of API functions the developer must know about. Considering the size of the rest of the Mantis API, this is a Good Thing.

Event System

Before going in depth with the inner workings of the plugin system, we will give an overview of the event system, which plays a critical role in giving plugins the flexibility and extensibility that they need. A full reference of the core events can be found in the Plugin Event Reference.

Overview

The Mantis event system requires all events to be ‘registered,’ by name and event type, before they can be used elsewhere. All of the core events are registered by default in core/events_inc.php, but any plugin can declare their own events at startup. Once an event has been declared, callback functions may be ‘hooked’ to an event. These hooked functions will then be called by the event system whenever the hooked event is ‘signaled’. The event type will determine how event parameters and callback return values are handled.

Event Types

There are multiple event types defined in the core event system. Each one handles parameters and return values differently, and each event may have it’s own specific usage of an event type.

Execute

This is the most basic event type in Mantis. When triggered, hooked callbacks will simply be called without any parameters, and their return values will be discarded. This will allow plugins to perform actions that do not directly affect the event originator.

Output

This type of event is designed specifically for plugins to generate output that will be displayed to the user. The event is passed either a separator string, or an array of prefix, separator, and postfix strings; these strings will be applied to the hooked callbacks’ output.

Chain

This event type is designed to allow plugins to process some input data one after the other, which each callback being passed the output from the callback before it. The parameter passed to the event becomes the input data for the first callback to process, and the return value of the last callback is returned to the event originator. In this method, actions such as output filtering are made simple.

Default

The default event type is designed to be as flexible and generic as possible. The parameter to the event is passed directly to each hooked callback, and the return value from the callback is placed in an array with the callback name as the key. The resulting array is then passed back to the originator. Any event that doesn’t fit one of the other event types can use this type to do anything.

Developing Plugins

The plugin system for Mantis utilizes a class-based hierarchy for plugins, with all plugins deriving from the MantisPlugin class. There are a few pre-defined plugin classes which make it simpler and easier to develop certain types of plugins, but these classes simply extend MantisPlugin itself, and add any simplicity on top of that.

This section covers the major topics needed to get started with plugin development. Anything else will be in the Advanced Topics section.

Getting Started

The most important step of getting a plugin to actually work with Mantis is properly advertising your plugin to the plugin manager, so that it may discover your plugin and allow the administrator to install it. This is done not only by creating the proper directory structure for your plugin, but by creating the right files with the right classes. Due to the use of Inversion of Control, you need not know much of the Plugin API to accomplish this task.

An example plugin with just the basic structure and registration can be seen with the Super Cow Powers sample.

Basic Structure

In order to have a valid plugin, you must choose a succinct, camelcase ‘basename’ for your plugin. This basename should only contain upper- and lower-case letters, and should not include versions or anything more specific than the plugin name. You must then create a directory in the plugins/ directory of Mantis with this basename, which will contain everything for your plugin. You will also need to have a file in this directory named ‘<basename>.php’ which will both provide Mantis with the appropriate information and form the core of your plugin.

A barebones plugin named ‘Testing’ will have a structure like this:

plugins/
  Testing/
    Testing.php

<basename>.php

This is the single most essential file for a plugin; Mantis will look for this file in your plugin directory, and it must contain a class declaration for your plugin. Your plugin’s class definition must be name in the form of ‘<basename>Plugin‘, and must extend from the ‘MantisPlugin’ class, either directly, or through one of the pre-defined plugin classes. In order to have a valid plugin, your class must at least override the abstract register() function, but there are other basic functions that you can also override.

register()

This class function must be overridden in order to have a valid extension class (it is marked abstract in MantisPlugin), and in order to have a valid plugin, this function must set at least some of properties on your class:

  • name - Your plugin’s full name. Required value.
  • description - A full description of your plugin.
  • page - The name of a plugin page for further information and administration.
  • version - Your plugin’s version string. Required value.
  • 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.
  • uses - Similar to ‘requires’, but soft dependencies; only fails if the soft dependency is registered, but not yet initialized
  • author - Your name, or an array of names.
  • contact - An email address where you can be contacted.
  • url - A web address for your plugin.
init()

Overriding this function allows your plugin to set itself up, include any necessary API‘s, declare or hook events, etc. Alternatively, your can plugin can hook the EVENT_PLUGIN_INIT event that will be called after all plugins have be initialized.

Using the Event System

The event system is what makes Mantis plugins flexible and extensible. Events can be utilized in many ways and from many locations, but there is a standard ‘acceptable’ method suggested for using events within plugins.

For more detailed information on the event system, see the event_system.

Hooking Events

There are two ways to hook events in your plugins. Once you’ve hooked an event, the appropriate callback function will be called whenever the event is triggered.

hook()

The first way is with ‘batch’ event hooking, where all of the events needed by your plugin are hooked automatically during plugin initilization. This can be achieved by overriding the hook() function of your plugin class and returning an array of event ⇒ callback pairs. If you need multiple callbacks for a single event, use an array of function names as the value for an event pair.

<?php
 
class ... {
  ...
 
  function hook() {
    return array(
      // single callback per event
      'EVENT_PLUGIN_INIT' => 'test',
 
      // multiple callbacks per event
      'EVENT_PLUGIN_INIT' => array( 'test', 'test' ),
    );
  }
 
  // Callback function
  function test( $p_event, $p_params ) {}
}
plugin_event_hook()

However, the simplest way for a plugin to hook a individual function to an event is by calling plugin_event_hook( event, function ), passing the full event name and the name of the class’s callback function. An event can be hooked at any time, even after the normal hook initialization.

<?php
 
class ... {
  ...
 
  function hook() {
    return array(
      'EVENT_PLUGIN_INIT' => 'test',
    );
  }
 
  function test( $p_event, $p_params ) {
    plugin_event_hook( 'EVENT_PAGE_LAYOUT', 'test2' );
  }
 
  function test2() {}
}

Declaring Events

Events must be declared before they can be used, with not only the event name, but the event type, which determines how its callback functions will be handled. Although the core Mantis system has declared and uses many events already, plugins can declare their own events as well, and trigger them as needed.

event_declare( name, type )

This function will declare a single event with a given name and type. The only types currently allowed are:

  • EVENT_TYPE_EXECUTE
  • EVENT_TYPE_OUTPUT
  • EVENT_TYPE_CHAIN
  • EVENT_TYPE_DEFAULT

For example:

<?php
event_declare( 'EVENT_PLUGIN_TESTING_1', EVENT_TYPE_DEFAULT );
event_declare_many( events )

This function will declare multiple events at once, taking an array of event name ⇒ type pairs. For example:

<?php
event_declare_many( array(
    'EVENT_PLUGIN_TESTING_2' => EVENT_TYPE_EXECUTE,
    'EVENT_PLUGIN_TESTING_3' => EVENT_TYPE_OUTPUT,
) );

Language Strings

In order to make plugins more accessible and available to international users, plugins can utilize the benefits of the Mantis language system to consolidate text into separate files for each language, allowing the plugin to be easily translated for different languages.

Using Strings

Using international language strings in plugins is just as easy as it is in the core Mantis system. A simple call to lang_get( string ) is enough to have the named string displayed in the user’s language of choice, assuming the necessary translation file is available. If the plugin has not yet been translated to the needed language, the English language strings will be used by default.

Declaring Strings

If your plugin needs to use language strings that aren’t already declared by the core Mantis system, it is very simple to provide the appropriate language files with your plugin. All language files need to be named ‘strings_<language>.txt‘, and must be in the lang/ folder in your plugin’s root directory.

Drawing on the example plugin used above, the structure should follow this pattern:

plugins/
  testing/
    lang/
      strings_english.txt
      strings_german.txt
    events.php
    register.php

The language file itself should follow the same format as standard Mantis language files, a PHP script that declares strings in the following pattern:

<?php
$s_plugin_testing_title = "Testing Plugin";
$s_plugin_testing_manage = "Manage Plugin";

The above string would then be displayed using something like the following code:

<?php
echo lang_get( 'plugin_testing_title' );

Configuration

Many plugins will likely have some elements that can or need to be configurable by the site admin or end-user, such as access thresholds or behavioral preferences. As Mantis has a powerful configuration system already, plugins can take advantage of the existing configuration system by using the plugin API, which will ensure simplicity and separation of configurations between multiple plugins.

Default Configurations

To allow the usage of default configurations for plugins, developers can create a callback method in the plugin’s register.php file named plugin_callback_<basename>_config(). This callback should return an array of configuration option name/value pairs, which will be set as default global configuration values. These values will be overridden by any value stored in the database as shown below, so any persistent configuration will not be affected.

General Usage

To retrieve a plugin configuration value, your plugin can call plugin_config_get( name ) to get value from the database (if explicitly set), or from the default configuration as explained above. In order to store configuration values in the database, your plugin will need to call plugin_config_set( name, value ), which will create or overwrite the existing value. After this call, then any subsequent call to plugin_config_get() should return this value.

Creating Pages

In order to create entirely new features, plugins must be able to create new pages without needing to worry about all of the Mantis core API. For this reason, the Mantis plugin system includes a very simplified method for structuring and linking to plugin pages.

Structure

Plugin pages can be named and structured as needed, but they must all be located in the pages/ subdirectory of the plugin root.

Using the above example plugin as a basis, new pages can be created in the following pattern:

plugins/
  testing/
    lang/
      strings_english.txt
      strings_german.txt
    pages/
      about.php
      manage.php
    events.php
    register.php

Usage

Once a page has been created, it can be linked to by using the plugin_page( page ) function to generate the appropriate URL. If a link is needed to another plugin’s page, the plugin_page( page, plugin ) function form can be substituted. By using this generated link, you page does not need to deal with locating and calling the core API‘s to initialize Mantis, and it can simply start with the actual code needed for the page.

Example Page

about.php

This is an example of a basic display page, which outputs the standard Mantis header and footer, but does nothing else:

<?php
html_page_top1( lang_get( 'plugin_testing_title' ) );
html_page_top2();

# Create a basic href link to the manage.php plugin page
echo 'A href="', plugin_page( 'manage' ), '">', lang_get( 'plugin_testing_manage' ), '</a>';

html_page_bottom1( __FILE__ );

Advanced Plugin Topics

Schema Management

For plugins that need to store information in the database, managing database schema across multiple versions and changes can be difficult, so the Mantis plugin system uses the same type of schema management system as the full application to track and upgrade the database schema for plugins.

Declaring a Schema

In the world of Mantis, a database schema is defined by a list of schema changes that are applied sequentially starting from nothing, such as adding tables, adding or modifying columns, etc. The schema history must be linear, and previous schema entries must not change for the upgrade process to work correctly. Fixing existing schema entries may be done by creating new entries in the list which make the necessary modifications.

plugin_callback_<basename>_schema()

This callback function will contain the schema information for a plugin. This function must return an array of schema entries (as defined below), which the plugin system will use for installation and automated schema upgrades.

Table Names

So that plugins will not have conflicting table names and schema entries, the plugin API includes the plugin_table( name ) function, which will return a full table name for your plugin, including the standard prefix and postfix as defined by the Mantis installation. This function should be used for all table names.

Schema Entries

All schema entries follow a standard pattern:

<?php
$schema[] = array( entryType, entryData );

entryType is a string defining how the entryData will be interpreted to modify the schema. All schema strings use the ADOdb ‘DataDict’ string syntaxes, which can be found in the ADOdb Data Dictionary Manual.

For an example of how schema entries should be used, please see the admin/schema.php file.

Adding/Creating a Table
<?php
$schema[] = array( 'CreateTableSQL', array( tableName, tableDeclaration ) );
Adding a Column
<?php
$schema[] = array( 'AddColumnSQL', array( tableName, columnDeclaration ) );
Altering a Column
<?php
$schema[] = array( 'AlterColumnSQL', array( tableName, columnDeclaration ) );
Adding an Index
<?php
$schema[] = array( 'CreateIndexSQL', array( indexName, tableName, columnNames ) );
Inserting Data
<?php
$schema[] = array( 'InsertData', array( tableName, rowDeclarations ) );
Dropping a Column
<?php
$schema[] = array( 'DropColumnSQL', array( tableName, columnName ) );
Dropping a Table
<?php
$schema[] = array( 'DropTableSQL', array( tableName ) );

Installation / Upgrade / Uninstallation

In order to allow more advanced plugins the ability to handle the process of installing, upgrading, or uninstalling, the Mantis plugin system has defined some custom callback functions for each task:

plugin_callback_<basename>_install()

This callback is executed before the normal plugin installation process has started. This should be used for checking conditions in Mantis to see if the plugin can be used, or for other general setup procedures. This callback must return TRUE if the installation should continue, or FALSE if installation should be halted. This allows your plugin to prevent installation in case of errors or other hazardous situations.

plugin_callback_<basename>_upgrade()

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. Your callback will receive a single parameter: the schema version from *before* the upgrade took place, allowing your plugin to know how much upgrade work needs to be done (eg. switch/case statements are perfect here). By definition of the install process, this function will also be called during installation when the plugin schema is ‘upgraded’ from nothing.

plugin_callback_<basename>_uninstall()

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.

 
Logged in as: anonymous
mantisbt/plugins_overview.txt · Last modified: 2011/12/30 10:35 by tandler