Dev:Creating A Plugin

From Habari Project

Jump to: navigation, search

Habari's functionality can be modified or extended using plugins, which are written in PHP. Writing a plugin for Habari is extremely simple.

The steps below describe in detail how to create a plugin for Habari, and provide some examples of the sorts of things that can be done with a plugin.

Contents

Step 1: Create The Plugin Class File

Create a new directory in your /user/plugins directory that uses the name of your plugin. As an example, you could create /user/plugins/mysample

In that directory, create a PHP file that contains the name of your plugin (the same as the directory) plus .plugin.php added to the end. For our example, we would create /user/plugins/mysample/mysample.plugin.php

Step 2: Create the Base Plugin Class and Metadata XML

All plugins in Habari must extend the core Plugin class. This allows the system to easily find and register your plugin. The following code creates a plugin called MyPlugin.

class MyPlugin extends Plugin
{
  // Your plugin code here
}

You should not create an instance of your plugin ($foo = new MyPlugin()) within your plugin class file. Habari will find your plugin and create an instance of it for you when your plugin is activated. It is able to do this because your plugin extends the core Plugin class. (If your plugin requires a separate class, Habari won't create an instance of it, because it wouldn't extend Plugin.)

Habari also needs some basic information about your plugin, and this information is provided in the plugin's XML. The XML file needs to be named the same as your plugin, changing the file extension from php to xml, for example myplugin.plugin.xml

<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin">
  <name>My Plugin</name>
  <license url="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Software License 2.0</license>
  <url>http://habariproject.org/</url>
  <author url="http://habariproject.org/">The Habari Community</author>
  <author url="http://example.com/">A. P. Lugin-Dev</author>
  <version>1.0</version>
  <description><![CDATA[Description.]]></description>
  <copyright>2013</copyright>
  <help>
    <value>
      <![CDATA[Add foo and bar to your theme, to display the foo and bar.]]>
    </value>
  </help>
</pluggable>


To have your plugin to appear properly in Habari's list of plugins in the admin, you need to provide these fields at a minimum:

name
The name of the plugin as it will appear in the admin console.
version
The version of your plugin. It is important that this version number be compatible with PHP's version_compare() function if you want your plugin to be compatible with the habariproject.org update beacon.
url
The URL location of your plugin. This will be used to create a link to your plugin from the admin console.
author
The name of the author of your plugin. (Your name!)
license
The distribution license of your plugin.
description
Description of your plugin.

If you plan to list your plugin in the catalog, you also have to provide a GUID, for example from http://www.somacon.com/p113.php, using the "Normal" setting. Or, in #habari on Freenode IRC type ,guid and the JibbyBot bot will attempt to magically create one for you. Note: these are random and "unique" strings.

Step 3: Add Actions and Filters

The plugin system in Habari is based on Actions and Filters.

An Action occurs when a certain event in the software takes place. For example, when a post is saved or displayed an Action is triggered. An Action notifies your plugin that the event has taken place, allowing your plugin to perform some function. Values that are passed to an action cannot be changed.

A Filter is similar to an Action but allows your plugin to change data that will be used by the system when an event occurs. Filters are passed the value of a parameter to filter, and return that parameter, possibly having altered it in some way. Some Filters also pass additional parameters to give plugins extra information. An example is a Filter that responds to a comment submission and returns the "spamminess" of the comment. The value that is returned from a Filter is passed to subsequent Filters as the first parameter, and is eventually used as the value for the filtered item.

Each Action or Filter has a name called a "hook name" that represents an event. A plugin "hook" is the place in the code where Habari checks to see if any plugins need to be informed of an event. A hook is either an Action or a Filter. See Hooks for more information on plugin hooks, including a list of available Actions and Filters. A plugin "sink" is one of the actual functions that is executed when the plugin hook code is run. Creating sinks that are activated for specific hooks is as simple as adding a method to your class with the appropriate name, either filter_hook_name or action_hook_name.

Action Example

Assume that you want your plugin to perform a task whenever the system has finished loading all plugins. The hook name of this hook is plugins_loaded. plugins_loaded is an Action.

To sink this action, create a new method in your plugin like so:

function action_plugins_loaded()
{
  Utils::debug('Hello!');
}

The prefix of action_ in your function name tells Habari that you are creating a sink for an Action. The plugins_loaded part of the function name tells Habari what hook you want to sink.

When you activate the plugin with this function in it, the "Hello!" message will be displayed inside the pink debug box immediately after all active plugins are loaded.

Some Actions will pass values to the sink methods. These values can be checked and acted upon.

Filter Example

Assume that you want your plugin to check the spamminess of a comment. The hook name of a hook that will do this is spam_filter. spam_filter is a Filter.

To sink this Filter, create a new method in your plugin like so:

function filter_spam_filter($rating, $comment, $handler_vars)
{
  if ( strpos( $comment->content, 'viagra' ) ) {
    $rating++;
  }
  return $rating;
}

The prefix of filter_ in your function name tells Habari that you are creating a sink for a Filter. The spam_filter part of the function name tells Habari what hook you want to sink.

When you activate the plugin with this method, Habari will pass every submitted comment through it. The function looks for the word "viagra" in the content of the comment, and upon finding it, increases the spam rating of the comment.

Finally, the rating is returned for use by the next plugin, and by the system. Note that even when no adjustment is made to the value of $rating it *must* be returned, otherwise the system will not have a value to process.

In this sink method, the $handler_vars parameter is provided to the method by the system, but it is not used by the method. This parameter could have been omitted in the method declaration, but it might be useful to leave it there for future use.

XMLRPC Example

Implementing XMLRPC via plugins is very easy by use of a special prefix for a plugin function.

function xmlrpc_namespace__function($param1, $param2, ...);

A hook implemented in this way uses the "namespace" part of the hook name as the XMLRPC namespace, and the "function" part as the XMLRPC function. The two are separated by two underscore characters. The function above produces an XMLRPC endpoint named "namespace.function".

Values passed from to this hook function are those interpreted by processing the incoming values posted to Habari's XMLRPC endpoint.

To return a properly formatted XMLRPC value, simply return a standard PHP data type. This will be converted into a proper XMLRPC response by the plugin system.

Alternative Hook Registration

Plugs and themes have two alternate methods of registering functions to hooks that allow hooks to be registered dynamically.

Normally, specifying the function name indelibly attaches a method to a specific hook during execution. Using some ingenuity, you can register a method to a hook only when it is required, or have different functions registered at different times to the same hook to provide separate functionality based on configuration options.

Aliases

By creating a method named alias() in the pluggable class that returns an associative array, a plugin or theme can specify functions to use for specifically named hooks.

For example, to register the method my_hook_function() for the hook "filter_spam_filter", you could use this alias method:

function alias()
{
  return array(
    'my_hook_function' => 'filter_spam_filter',
  );
}

Or, if you wanted to register the method my_hook_function() for multiple hooks, say, "post_content_out", "post_content_excerpt", and "post_content_summary", you could use this alias method:


function alias()
{
  return array(
    'my_hook_function' => array( 'action_post_content_out', 'action_post_content_excerpt', 'action_post_content_summary' ),
  );
}

Plugins::register() Method

It is possible to directly register functions to a hook using the internal function Plugins::register()

This function is described in detail in the API documentation for the Plugins class.

Step 4: Common Plugin Tasks

The previous steps provide instructions for constructing the basic structure of a Habari plugin. With that knowledge, leveraging the many plugin hooks is a simple task.

What follows are a few things that are common tasks when creating a plugin.

Add Output to a Theme: Simple Data Method

So, you have created your killer plugin and want to provide theme developers a chance to show off your work. Based on the action example above, you can do something like this:

function action_add_template_vars( $theme )
{
  $theme->yourvariable = 'Sweet Plugin Output';
}

This makes $yourvariable available in the theme, where you can do something like

<?php echo $yourvariable ?>

and that will output the value directly in the theme template file.

Add Output to a Theme: Template Method

Your plugin can provide a template for outputting its own data that can be included cleanly from the current active theme.

First, register your template in the current theme. Using the Plugin class' add_template() method is powerful because it allows your plugin to provide a default template that themes can override by providing a template of the same name in the theme directory. This allows themes to use the same data, but style it so that it fits better into the theme's design.

Assuming your template is in the same directory as your plugin, use code similar to this to register your template:

public function action_init()
{
  $this->add_template('mytemplatename', dirname(__FILE__) . '/mytemplate.php');
}

In this case, the file named "mytemplate.php" will be registered under the template name "mytemplatename" when the plugin is initialized. If a template in the current theme were to call $theme->display('mytemplatename');, it would cause this newly registered template to be output at that location. Note that calling $theme->display() alone will not assign variables from your plugin into the new template for use, so you'll want to do a little more work.

With the template registered and created, create a theme function to gather your data and then display your theme. Consider what you want the function name to be that users will add to their templates to display your plugin's output. For this example, we'll use this call within one or more of the templates of the current theme to display the plugin output:

$theme->do_output();

The theme function to use to allow this would look like this in the plugin class:

public function theme_do_output( $theme )
{
  // Get data to output
  $mydata = 4;
  // Assign the data to be displayed by the plugin's custom template
  $theme->mydata = $mydata;
  // Return the output of the custom template
  return $theme->fetch( 'mytemplatename' );
}

This function is named theme_do_output() so that it answers to calls of $theme->do_output() from within templates. The parameter of $theme is the active theme from which the call has been made.

The function body gets the data that it produces and assigns it into the theme's $mydata variable. In this case, the value of $mydata in the custom theme will have the numeric value 4. Calling $theme->fetch( 'mytemplatename' ) renders the template that the plugin added with the add_template() call and returns it without outputting it. The text of the rendered template is returned from the theme_do_output() function, which is rendered by the display engine.

In the mytemplate.php file that comes with the theme, the $mydata variable should be output:

<div>Times I rewrote this plugin: <?php echo $mydata ?></div>

The most important aspect of this technique is that because you're providing data to a template rather than outputting HTML directly, the theme can include a template of the same name that overrides the one provided with the plugin. The styling in this template can provide output that better suits the active theme.

For examples of this technique in use, see the Linkoid and Twitter plugins in the habari-extras plugin repository.

Use FormUI to Create an Options Page

Adding an options control to your plugin is also very easy, and most users will be more comfortable with setting the options via the admin panel than editing plugin code directly. Habari provides a mechanism called FormUI that enables plugin developers to quickly create their own option controls.

Method 1: Simple

The simple method of adding configuration settings to a plugin allows you to create a single "Configure" link in the dropbutton next to the plugin in the list of plugins on the main plugin page. When a user clicks this link, the admin will display a form that you specify.

To implement this configuration form, simply add the following method to your plugin class:

public function configure()
{
  $ui = new FormUI( 'my_custom_form_name' );
  $ui->append( 'text', 'customvalue', 'pluginname__customvalue', _t('Your custom value:', 'plugin_locale') );
  $ui->append('submit', 'save', _t('Save', 'plugin_locale'));
  return $ui;
}

The code contained within the configure() method creates and returns an instance of FormUI. For full details of the workings of FormUI, see the FormUI documentation.

Method 2: Flexible

The following method of adding configuration settings to a plugin is a bit more complicated than the simple method, but has the potential to add multiple pages as separate options within the dropbutton on the plugin configuration page.

The basic structure that needs to be added to the plugin to implement this method of configuration is as follows:

public function filter_plugin_config( $actions )
{
  $actions['configure'] = _t('Configure');
  return $actions;
}

The _t() function translates the string into the language used by the site. For English-language blogs, the output would be "Configure", while for Spanish-language blogs, for example, the output might be "Configurar".

public function action_plugin_ui_configure()
{
  $ui = new FormUI( strtolower( get_class( $this ) ) );
  $customvalue = $ui->append( 'text', 'customvalue', 'pluginname__customvalue', _t('Your custom value:') );
  /*
  Optionally set the value with something like the following:
  $customvalue->value = Options::get( $name.'pluginname__customvalue' );
  */
  $ui->append('submit', 'save', _t('Save'));
  $ui->out();
}

The first function, filter_plugin_config, creates a simple configure link for the plugin, on the Plugin Administration page.

action_plugin_ui_configure outputs the configuration form for the plugin. The "configure" portion of the function name is the key of the array returned in filter_plugin_config (not the value, which could be translated into a different language during execution). The output shown here can be anything, but usually consists of the output from a FormUI object.

The action_plugin_ui_configure function adds the form controls, in this example a text field called customvalue is added. The third argument to FormUI->append() sets the storage name of the plugin option. The Habari community suggests prefacing option storage names with the plugin name followed by two underscores, to avoid conflicts with other plugins that have similarly named options.

To read the value of the variable you have defined in an Action or Filter, call Options::get( 'pluginname__customvalue' );, and build logic around the value it returns.

To store a user-specific option - an option that is different for each logged-in user - apply the prefix "user:" to the storage name of the added control. Instead of being stored in the options table, the user-specific option will be stored in the user's info record. So a plugin with an option named "foo__bar" will have its value accessible from:

User::identify()->info->foo__bar

For more information about using FormUI, including other controls and validation, see the FormUI documentation.

Advanced Configuration

You can add configuration buttons to other plugins using filter_plugin_config_any() and testing for a specific plugin id:

public function filter_plugin_config_any( $actions, $plugin_id )
{
  if($plugin_id == $this->plugin_id()) {
    $actions['my_custom_action'] = _t('My Custom Action');
  }
  return $actions;
}

The same can be done with action_plugin_ui_any to produce form output for any plugins:

public function action_plugin_ui_any( $plugin_id, $action )
{
  if ( $plugin_id == $this->plugin_id() ) {
    switch ( $action ) {
    case 'my_custom_action' :
      $ui = new FormUI( strtolower( get_class( $this ) ) );
      $customvalue = $ui->append( 'text', 'customvalue', 'pluginname__customvalue', _t('Your custom value:') );
      /*
      Optionally set the value with something like the following:
      $customvalue->value = Options::get( $name.'pluginname__customvalue' );
      */
      $ui->append('submit', 'save', _t('Save'));
      $ui->out();
      break;
    }
  }
}

Add a Rewrite Rule

Occasionally, plugins wish to add and handle new URLs. The Plugin class includes a couple of helpers to make this easy.

First, you must add your rule. This can be done with a single line, including a url template and a handler.

// Example, in a plugin class, dispatches URLs like "/hi/test/anything" to the 'test_foo' action
function action_init()
{
$this->add_rule('"hi"/"test"/foo', 'test_foo');
}

Once the rule has been added, a handler must be implemented. This handler can call on variables and display any arbitrary templates.

public function action_plugin_act_test_foo($handler)
{
$handler->theme->foo = $handler->handler_vars['foo']; // Will output "anything" for /hi/test/anything
$handler->theme->display('some_template');
}

Change the Priority of Execution for Your Plugin

Sometimes you want your implementation of a plugin hook to execute before certain core hooks or other plugins' implementations. To do this, you need to set the priority of your hooks.

By default, the priority of every hook in your plugin is 8. Hooks with a lower priority execute sooner.

To set the priority of one of your hooks, add the method set_priorities() in your plugin class. Have it return an associative array using the full function name of the hook (including the 'filter_' or 'action_' prefix) as the key and the priority integer as the value.

function set_priorities()
{
  return array(
    'action_add_template_vars' => 10,
  );
}

Habari will process this function for priorities, and use them to order the execution of plugin hooks.

Creating an Admin Page

The process of providing a new page in the administration interface is outlined in a separate page.

Creating an ACL token

The powerful ACL system built into Habari allows plugins to extend it with additional tokens. Each token represents a specific piece of functionality or action, which may be provided by the core system or a plugin. When creating a plugin, you may need to limit certain actions to groups of users. To do so, you must create an ACL token.

There is only a single line of code to create a new token, which can be placed within the plugin activation function. This code includes a name, description, and category for your new token.

ACL::create_token( 'token_name', _t('Token Description'), 'Category', false );

However, you may want to automatically grant access to the administrator group.

$group = UserGroup::get_by_name( 'admin' );
$group->grant( 'token_name' );

Finally, it is best practice to clean up after your plugin by deleting the token when your plugin is deactivated.

ACL::destroy_token( 'token_name' );

To put everything together, this code will create a new ACL token and grant the admin group access to it:

public function action_plugin_activation( $file )
{
	if ( $file == str_replace( '\\','/', $this->get_file() ) ) {
		# create default access token
		ACL::create_token( 'token_name', _t('Token Description'), 'Category', false );
		$group = UserGroup::get_by_name( 'admin' );
		$group->grant( 'token_name' );
	}
}
 
public function action_plugin_deactivation( $file )
{
	if ( $file == str_replace( '\\','/', $this->get_file() ) ) {
		# delete default access token
		ACL::destroy_token( 'token_name' );
	}
}

Create a Content Type

This topic has been branched out into a separate page.


Step 5: Share your work

This is the same process for plugins and themes, so we moved that to an own page.

Other Development Pages · Developer Introduction
Personal tools
This is a cached copy of the requested page, and may not be up to date.

Sorry! This site is experiencing technical difficulties.
Try waiting a few minutes and reloading.

(Cannot contact the database server: Access denied for user 'habari'@'localhost' to database 'habari_wiki' (localhost))


You can try searching via Google in the meantime.
Note that their indexes of our content may be out of date.