Creating A Plugin
From Habari Project
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
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 { function info() { return array( 'name' => 'My Plugin', 'version' => '1.0', 'url' => 'http://habariproject.org/', 'author' => 'Habari Community', 'authorurl' => 'http://habariproject.org/', 'license' => 'Apache License 2.0', 'description' => 'Description', ); } }
The info() member of the plugin is required. It indicates the basic plugin information. If you want your plugin to appear properly in Habari's list of plugins in the admin, then 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!)
- authorurl
- The URL location of your blog/web site if any.
- license
- The distribution license of your plugin.
- description
- Description of your plugin.
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.)
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 Plugin 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.
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.
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.
The basic structure that needs to be added to the plugin, is as follows:
public function filter_plugin_config( $actions, $plugin_id ) { if ( $plugin_id == $this->plugin_id() ) { $actions[]= _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( $plugin_id, $action ) { if ( $plugin_id == $this->plugin_id() ) { switch ( $action ) { case _t('Configure') : $ui = new FormUI( strtolower( get_class( $this ) ) ); $customvalue= $ui->append( 'text', 'customvalue', 'pluginname__customvalue', _t('Your custom value:') ); $ui->out(); break; } } }
The first function, filter_plugin_config, creates a simple configure link for the plugin, on the Plugin Administration page. action_plugin_ui 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.
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.
Create an Automatic Update Notification
Habari can notify users of changes to your plugin if you follow a few simple steps.
Step 1: Get GUID, for example from http://www.somacon.com/p113.php, using the "Normal" setting.
Step 2: Add this function to your plugin, and replace the strings therein:
function action_update_check() { Update::add( 'My Plugin Name', 'My GUID (from link above)', $this->info->version ); }
Step 3: Beacon settings for plugins are stored on http://habariproject.org. You should update these when your plugin is updated. There will eventually be a script that processes changes to the beacon information, but this script does not yet exist. To update the beacon, contact someone with access to edit files on the server. Be sure to specify a brief description of changes and whether the update is a "Critical", "Feature", or "Bugfix" update.
