A quick view on Wordpress, Mantis and Jenkins plug-in API

I'm preparing a plug-in API proposal for TestLink, and thought that the best way to learn how to write a good plug-in API would be by looking at other application's plug-in API's.

This post contains information that I gathered for creating this new API, but that may also be useful for other programmers. I used Wordpress, Mantis and Jenkins in this study. Let me know if you find any mistakes or if you have any other suggestions.


Wordpress

http://www.wordpress.org

Plug-ins installation

Wordpress has a directory for plug-ins, by default it is $WP_HOME/wp-content/plugins. You can copy the plug-in PHP file, or its directory, or even a zip file to the Wordpress plug-ins folder. Should you copy a zip file, Wordpress decompresses the file for you.

In the Wordpress administration interface you can download and install plug-ins, as well as active, deactivate or uninstall plug-ins. There you can manage plug-in updates, as well as see if the plug-in version is compatible with the current version of Wordpress (users can vote).

Plug-ins API

You can get a list of plug-ins in the admin screen of Wordpress. Wordpress can tell whether a plug-in is installed or not, activated or not, using data from database. For each plug-in, Wordpress scans its directory (or include directly a PHP file, if present in the plug-ins folder) and searches for PHP files. What Wordpress is actually looking for, are PHP files that contain comments with information about the plug-in such as name, version, author, etc.

Wordpress provides two hook types, action and filter. The action hook is used to execute some action when an event occurs. For instance, you can add an action for when the plug-in is activated.

class MyPlugin {
     static function install() {
            // do not generate any output here
     }
}
register_activation_hook( __FILE__, array('MyPlugin', 'install') );

And the filter hook is used to modify content. For instance, you could want to modify the title of a post, so you could apply a filter on the function that displays the blog title. This filter could, say, upper case the title.

<?php
function theme_slug_filter_the_content( $content ) {
    $custom_content = 'YOUR CONTENT GOES HERE';
    $custom_content .= $content;
    return $custom_content;
}
add_filter( 'the_content', 'theme_slug_filter_the_content' );
?>

There is also the possibility to use pluggable functions. With them themes and plug-ins can define functions to be overriden by themes (or child themes) and plug-ins.

if ( !function_exists( 'mytheme_nav_menu' ) ) :
function mytheme_nav_menu() {
    if ( current_theme_supports( 'menus' ) ) :
        wp_nav_menu( array( 'theme_location' =&gt; 'primary_nav', 'fallback_cb' =&gt; 'wp_list_pages' ) );
    else :
        wp_list_pages();
    endif;
}
endif;

Plug-ins configuration

Plug-in can create tables or use the configurations table in Wordpress. It's also possible to create an admin page to modify these configuration from Wordpress administration section.


Mantis

http://www.mantisbt.org/

Plug-ins installation

The plug-ins installation happens copying the plug-in folder to the plug-ins folder in Mantis. By default, this folder is located at $MANTIS_HOME/plugins.

Plug-ins API

Mantis scans the plug-ins folder looking for other folders. For each folder, there should exist a PHP file with the same name of the directory, plus the extension .php. This file must include Mantis plug-ins API (class MantisPlugin.class.php).

The Mantis plug-ins API has general configurations for the plug-in, such as name, description, author, web site, configuration page (optional) among others. As well as functions for initializing and finalizing the plug-in.

By default the plug-in is not persisted in the database, only when installed. During the installation, the initialization functions are called. The plug-in hooks are also registered.

Certain actions in Mantis generate events (see: $MANTIS_HOME/core/events_inc.php). A plug-in can register functions for these events. This way, Mantis will call these functions when such event occurs.

**
 * requires MantisPlugin.class.php
 */
require_once( config_get( 'class_path' ) . 'MantisPlugin.class.php' );

/**
 * XmlImportExportPlugin Class
 */
class XmlImportExportPlugin extends MantisPlugin {
	
	//...

	/**
	 * Default plugin configuration.
	 */
	function hooks( ) {
		$hooks = array(
			'EVENT_MENU_MANAGE' =&gt; 'import_issues_menu',
			'EVENT_MENU_FILTER' =&gt; 'export_issues_menu',
		);
		return $hooks;
	}
}

There is a plug-in that comes bundled with Mantis, the Core plug-in. This plug-in allows other plug-ins to use it to verify required compatibility with the plug-ins API.

Plug-ins configuration

Plug-ins can define a set of configurations, with a default value. If a plug-in provides a configuration page, so it is possible to change these configurations with this page too (the programmer must prepare this page).

If a configuration has a default value, then it is not persisted in the database. But if the administrator changes the configuration, then it is persisted in the database, overriding the default value.

In the database, the configurations are stored in the same table for general configurations, but with the prefix plugin_<pluginname>_<configuration>, e.g.: plugin_MantisGraph_font (configuration font of MantisGraph plug-in).


Jenkins

http://www.jenkins-ci.org

Plug-ins installation

Plug-ins in Jenkins stay at the $JENKINS_HOME/plugins folder. You can copy the hpi or jpi files into this folder, or copy the plug-in's folder directly in there too. Jenkins will take care of initializing your plug-in, both during Jenkins start up or during runtime.

You can install, update or uninstall using Jenkins interface. You get notifications from the plug-ins updates available and compatibility with Jenkins current version.

Plug-ins API

There are several extension points in Jenkins (see: Jenkins Wiki page for Extension Points). Each one has a goal and is executed at certain point in Jenkins code. Normally your plug-in will have one extension point and one goal, but it is possible too that you use more than one extension point, create your own extension point or use other plug-ins too.

public class HelloWorldBuilder extends Builder {

    private final String name;

    // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
    @DataBoundConstructor
    public HelloWorldBuilder(String name) {
        this.name = name;
    }

    // ...

    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
        // This is where you 'build' the project.
        // Since this is a dummy, we just say 'hello world' and call that a build.

        // This also shows how you can consult the global configuration of the builder
        if (getDescriptor().getUseFrench())
            listener.getLogger().println(&quot;Bonjour, &quot;+name+&quot;!&quot;);
        else
            listener.getLogger().println(&quot;Hello, &quot;+name+&quot;!&quot;);
        return true;
    }

    // ...

}

When you initialize Jenkins, it scans the plug-ins directory and, for each plug-in found, it prepares a ClassLoader and call the initializion methods (if any) in the plug-in. There are methods for when the plug-in is stopping too.

After that, you can use the plug-ins features from the job configuration screen, or in other parts of the system. In the case of a job, you can use the Builder of a plug-in, for instance, configuring it according to the plug-in requirements, and then during the job execution a method (perform()) is executed for each one of the Builders configured in the job.

Plug-ins configuration

Each plug-in can have global of local configurations. It is also possible to modify Jenkins UI and add new screens to configure the plug-in. The configuration screen for the plug-in requires only snippets of Jelly or Groovy, that are embedded by Jenkins in the right configuration screen.

Below is the local (job) configuration for the plug-in shown above.

[xml]<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> <f:entry title="Name" field="name"> <f:textbox /> </f:entry> </j:jelly>[/xml]


In the next days I'll finish the proposal for TestLink and will post the details here too. And to keep the momentum, I'll investigate an idea posted here, about enhancing Apache Nutch's plug-in API using parts of the Jenkins API.