2. Sample Plugin

The following code listings illustrate a basic sample plugin for the generic plugin category. This plugin can be installed by placing all of its files in a directory called plugins/generic/example.

This plugin will add an entry to the Journal Manager's list of functions, available by following the “Journal Manager" link from User Home.

2.1. Loader Stub

The plugin is loaded by OJS by loading a file in the plugin directory called index.php. This is a loader stub responsible for instantiating and returning the plugin object:

Example 5.1. Plugin Loader Stub

<?php
require_once('ExamplePlugin.inc.php');
return new ExamplePlugin();
?>

2.2. Plugin Object

The plugin object encapsulates the plugin and generally will do most of the work. In this case, since this plugin will be in the generic category, the object must extend the GenericPlugin class:

Example 5.2. Plugin Object

<?php 
import('classes.plugins.GenericPlugin'); 
class ExamplePlugin extends GenericPlugin { 
    function register($category, $path) { 
        if (parent::register($category, $path)) { 
            HookRegistry::register( 
                'Templates::Manager::Index::ManagementPages', 
                array(&$this, 'callback') 
            ); 
            return true; 
        } 
        return false; 
    } 
    function getName() { 
        return 'ExamplePlugin'; 
    } 
    function getDisplayName() { 
        return 'Example Plugin'; 
    } 
    function getDescription() { 
        return 'A description of this plugin'; 
    } 
    function callback($hookName, $args) { 
        $params =& $args[0]; 
        $smarty =& $args[1]; 
        $output =& $args[2]; 
        $output = '<li>&#187; <a href=”http://pkp.sfu.ca”>My New Link</a></li>'; 
        return false; 
    } 
} 
?>

The above code illustrates a few of the most important parts of plugins: the register function, hook registration and callback, and plugin management.

2.3. Registration Function

Whenever OJS loads and registers a plugin, the plugin's register(...) function will be called. This is an opportunity for the plugin to register against any hooks it needs, load configuration, initialize data structures, etc. In the above example, all the plugin needs to do (aside from calling the parent class's register function) is register against the Templates::Manager::Index::ManagementPages hook.

Another common task to perform in the registration function is loading locale data. Locale data should be included in subdirectories of the plugin's directory called locale/[localeName]/locale.xml, where [localeName] is the standard symbolic name of the locale, such as en_US for US English. In order for these data files to be loaded, plugins should call $this->addLocaleData(); in the registration function after calling the parent registration function.

2.4. Hook Registration and Callback

The above example serves as a clear illustration of hook registration and callback; along with the list of hooks below, this should provide all the required information for extending OJS using hooks. However, there are a few important details that need further examination.

The process by which a plugin registers against a hook is as follows:

Example 5.3. Hook Registration Process

HookRegistry::register(
    'Templates::Manager::Index::ManagementPages',
    array(&$this, 'callback')
);

In the example above, the parameters to HookRegistry::register are:

  1. The name of the hook. See the complete list of hooks below.

  2. The callback function to which control should be passed when the hook is encountered. This is the same callback format used by PHP's call_user_func function; see the documentation at http://php.net for more information. It is important that $this be included in the array by reference, or you may encounter problems with multiple instances of the plugin object.

The definition of the callback function (named and located in the above registration call) is:

Example 5.4. Hook Registration Definition

function callback($hookName, $args) { 
    $params =& $args[0]; 
    $smarty =& $args[1]; 
    $output =& $args[2]; 
    ... 
} 

The parameter list for the callback function is always the same:

  1. The name of the hook that resulted in the callback receiving control (which can be useful when several hook registrations are made with the same callback function), and

  2. An array of additional parameters passed to the callback. The contents of this array depend on the hook being registered against. Since this is a template hook, the callback can expect the three parameters named above.

The array-based passing of parameters is slightly cumbersome, but it allows hook calls to compatibly pass references to parameters if desired. Otherwise, for example, the above code would receive a duplicated Smarty object rather than the actual Smarty object and any changes to attributes of the $smarty object would disappear upon returning.

Finally, the return value from a hook callback is very important. If a hook callback returns true, the hook registry considers this callback to have definitively “handled" the hook and will not call further registered callbacks on the same hook. If the callback returns false, other callbacks registered on the same hook after the current one will have a chance to handle the hook call.

The above example adds a link to the Journal Manager's list of management functions. If another plugin (or even the same plugin) was registered to add another link to the same list, and this plugin returned true, the other plugin's hook registration would not be called.

2.5. Plugin Management

In the example plugin, there are three functions that provide metadata about the plugin: getName(), getDisplayName(), and getDescription(). These are part of a management interface that is available to the Journal Manager under “System Plugins".

The result of the getName() call is used to refer to the plugin symbolically and need not be human-readable; however, the getDisplayName() and getDescription() functions should return localized values. This was not done in the above example for brevity.

The management interface allows plugins to specify various management functions the Journal Manager can perform on the plugin using the getManagementVerbs() and manage($verb, $args) functions. getManagementVerbs() should return an array of two-element arrays as follows:

Example 5.5. Specifying Management Verbs

$verbs = parent::getManagementVerbs(); 
$verbs[] =  array('func1', Locale::translate('my.localization.key.for.func1')); 
$verbs[] =  array('func2', Locale::translate('my.localization.key.for.func2')); 

Note that the parent call should be respected as above, as some plugin categories provide management verbs automatically.

Using the above sample code, the plugin should be ready to receive the management verbs func1 and func2 as follows (once again respecting any management verbs provided by the parent class):

Example 5.6. Receiving Management Verbs

function manage($verb, $args) { 
    if (!parent::manage($verb, $args)) switch ($verb) { 
        case 'func1': 
            // Handle func1 here. 
            break; 
        case 'func2': 
            // Handle func2 here. 
            break; 
        default: 
            return false; 
        } 
    return true; 
    }