How To Write a Lightpress Plugin
Table of Contents
Lightpress plugins are classes conforming to a very simple interface, which you can get for free by extending the LightPressPlugin class. Let's examine the key components of a Lightpress plugin by developing a simple Hello World! plugin.
The Base Class
If your new plugin class extends LightPressPlugin, you get most of the infrastructure of the plugin for free. Let's start with the class declaration:
<?php // save this code as plugins/HelloWorld.php inside the lightpress folder require_once 'classes/LightPressPlugin.php'; class HelloWorld extends LightPressPlugin { } ?>
Class Attributes
The plugin manager code inside Frontend uses a few public attributes in the plugin class to decide if, and when the plugin needs to run, and which data it needs to configure itself. Additional attributes are used in the admin console to display the plugin description. Some of the attributes will be explained later in this document, for now you can familiarize yourself with their names:
- description, used in the admin console to tell the user what the plugin is used for
- default_context, sets the default for the value that tells the plugin manager in which context (index, post, category, etc.) the plugin should be activated; the runtime value is the one set by the user through the admin console
- hooks, an array containing the name of the hooks in which the plugin will run
- active, if the plugin is active, set at runtime by the plugin code
- constructor_args, sets the name of the arguments that the plugin can receive at runtime in the constructor, and allows the admin console to build a form that lets the user configure them
Most of the attributes are already there in the base class, so you don't need to remember to add them to your plugin unless you need them to control the plugin's behaviour. Let's describe briefly the attributes you usually need to set in your class.
description
The description of the plugin is displayed in the admin console, and it should tell the user what the plugin does. A short sentence is usually enough.
<?php // save this code as plugins/HelloWorld.php inside the lightpress folder require_once 'classes/LightPressPlugin.php'; class HelloWorld extends LightPressPlugin { var $description = 'The simplest Lightpress plugin, useful only for didactic purposes'; } ?>
default_context
You most probably want to set a default context where your plugin will run, as most plugins are used only in specific contexts (eg the PostComments? plugin is only useful when viewing a post, or possibly a static page). Setting a default context also allows the user to activate the plugin from the admin console with a single click, by using the "Activate" button.
The available contexts are constants defined in Frontend that represent binary values, so that you can combine more than one context in which the plugin is active. The defined contexts are:
- LP_CONTEXT_INDEX, active when viewing the home page, with a value of 1
- LP_CONTEXT_CATEGORY, active when viewing a list of category posts, with a value of 2
- LP_CONTEXT_ARCHIVES, active when viewing a list of archive posts for a month, with a value of 4
- LP_CONTEXT_POST, active when viewing a single post, with a value of 8
- LP_CONTEXT_STATIC, active when viewing a single static page, with a value of 16
- LP_CONTEXT_SEARCH, active when viewing a list of search results, with a value of 32
- LP_CONTEXT_AUTHOR, active when viewing a list of posts by a single author, with a value of 64
- LP_CONTEXT_TAG, active when viewing a list of posts for a tag, with a value of 128
- LP_CONTEXT_ALL, a shortcut to set all of the contexts above with a single constant
To combine more than one context use the sum of their numeric values as PHP does not allow expressions when declaring a class attribute, for example:
<?php // save this code as plugins/HelloWorld.php inside the lightpress folder require_once 'classes/LightPressPlugin.php'; class HelloWorld extends LightPressPlugin { var $description = 'The simplest Lightpress plugin, useful only for didactic purposes'; var $default_context = LP_CONTEXT_ALL; // set the plugin to run in all contexts // alternative forms of setting the default contexts // var $default_context = LP_CONTEXT_INDEX; // set the plugin to run in the index context // var $default_context = 24; // set the plugin to run in the post and static context (8 | 16) } ?>
hooks
If the contexts determine for which kinds of requests the plugin should be run, the hooks define which phase of the response the plugin can influence. The hooks are named after the corresponding Frontend methods, and cover most of the "interesting" things the Frontend does to build the final output. Each hook carries with it a different set of data (the payload) that is passed as a reference to the plugin's run() method (more on this later), so that its values can be modified in place.
The hook (or hooks) your plugin is attached to are declared in the hooks attribute of your class, whose value is an array containing all the names of the hooks where your plugin will be called. Your plugin class will get instantiated only once, even if it's attached to multiple hooks. Conversely, its run() method will run for each different hook your class is attached to, using as arguments the current hook name and its specific payload.
The defined hooks are:
- get_posts, run before getting the posts data from the DB, passes the variables used to build the SQL query as payload; this hook is used to modify the SQL query that retrieves posts, eg to add a field or filter the retrieved posts
- get_posts_loop, run for each row returned by the SQL query that retrieves posts, passes an associative array with the post fields as payload; this hook is used to add, remove, or modify post attributes
- get_comments, run before getting comments from the DB, it carries as payload an associative array with three key/value pairs corresponding to the start and limit values of the LIMIT start, step SQL instruction and id of the post for which to retrieve comments; it is used to paginate through comments
- insert_comment, run before inserting a comment, it carries as payload the associative array with all comment fields and values; it is used to examine the comment and flag it as spam, to automatically convert URLs to A tags in the comment text, etc.
- sidebar, run before rendering the sidebar template, it carries an empty payload; this hook is used as a convenience that allows you to add stuff to the sidebar just before it gets rendered, eg a list of recent posts of comments, Flickr photos, Del.icio.us links, etc.
- parse_post, run when displaying a single post, after the post's and comments' attributes have been set as template variables; it carries an empty payload, and it is used to append things like Adsense ads to the post template
- parse_comment, run right after the comment attributes are set, and before they are sent to the Template manager; it carries the comment with all its attributes, including the presentation ones (class, index, etc.) as a payload, so that you can modify them
- start_render, run after all the template variables are set and before starting to render the templates, it carries as payload an associative array with the file names of the templates to be used for the header, main content, sidebar, and footer; it is used to change the default template, eg use a different one for each category, a different sidebar when viewing a single post, etc.
- pre_render, runs before starting the rendering process, it carries as a payload three flags that determine if the content and the sidebar are rendered, and the whole page is sent to the user as output; it is used to disable the rendering of the page content, by plugins like ArchivesListExtended? or CategoriesListExtended? that set the page content themselves
- post_render, runs after the header, footer and sidebar have been rendered and carries the same payload as the pre_render hook; it is used to avoid rendering the main page content depending on something that may have occurred after pre_render has run
- shutdown, runs after the page has been sent to the user, it carries the same payload as the pre_render and post_render hooks; it is used for things like server-side logging
Let's declare that our HelloWorld? plugin is attached to the post_render hook.
<?php // save this code as plugins/HelloWorld.php inside the lightpress folder require_once 'classes/LightPressPlugin.php'; class HelloWorld extends LightPressPlugin { var $description = 'The simplest Lightpress plugin, useful only for didactic purposes'; var $default_context = LP_CONTEXT_ALL; // set the plugin to run in all contexts var $hooks = array('post_render'); } ?>
constructor_args
This attribute is used by the constructor to determine which configuration variables, set by the user in the admin console, are passed to the constructor. It is explained below.
Preparing For Work, the Constructor Method
The constructor has a very important role in the plugin life, as it allows the plugin to receive the configuration values set by the user in the admin console. This process is controlled by three key parts of the code:
- the constructor_args attribute, an associative array containing key/value pairs for eachof the variables that the user can modify from the console, and the plugin constructor receives at runtime as constructor arguments; for each pair, the key correponds to the name of the argument, the value to the description the user will see in the admin console
- one public attribute for each variable name in constructor_args, initialized to its default value and of the correct type (more about the type later)
- the base class constructor
Let's see all three parts together in our HelloWorld? plugin, by using them to define a message variable that the user can customize from the admin console.
<?php // save this code as plugins/HelloWorld.php inside the lightpress folder require_once 'classes/LightPressPlugin.php'; class HelloWorld extends LightPressPlugin { var $description = 'The simplest Lightpress plugin, useful only for didactic purposes'; var $default_context = LP_CONTEXT_ALL; var $hooks = array('post_render'); var $constructor_args = array('message'=>'the message this plugin will display'); var $message = 'Hello, World!'; function HelloWorld(&$frontend, &$args, $dummy_run=false) { $this->LightPressPlugin($frontend, $args, $dummy_run); } } ?>
As you can see in the code above, in constructor_args we have declared that our plugin supports one user-configurable argument named message, and given a brief description that tells the user what the message is used for. When the user click on the plugin's Manage button in the admin console
http://lightpress.org/images/trac_documentation/plugins_1.jpg
she will see a screen allowing her to configure the plugin's context (showing the defaults we have set in the code using the default_context attribute), and an input field to set the message we have declared in constructor_args
http://lightpress.org/images/trac_documentation/plugins_2.jpg
The type of the default attribute (in our case $message) will determine its appearance. As of the 2.x version Lightpress supports: strings and numbers, which will be converted to either input fields or textareas depending on the length of their default value; booleans (true or false), which will be converted to radio buttons. Support for arrays might be added in future versions, if you need an array in your plugin, use a string and split the attribute in the constructor once it's been set.
At runtime, the plugin manager in Frontend will pass to the constructor's args argument an associative array containing all the attribute names you have defined in constructor_args, and either their default value or the value set by the user in the admin console as a value. The base LightPressPlugin? class does the work of unpacking the array, and setting the values to your instance's attributes, if you invoke its constructor from your own as in the code above.
The base class constructor also sets an instance attribute named _frontend with a reference to the Frontend instance passed by the manager to the constructor as its first argument ($frontend), so that you can use it in your own methods to access the db instance ($this->_frontend->db), the template instance ($this->_frontend->tpl), the options ($this->_frontend->options), and so on.
The last argument passed to the constructor (dummy_run) is a flag that signals whether the plugin is running in the WP admin console, or in Lightpress. Since the only way for the code running from the WP console to access your plugin's attributes is to create a new instance (there's no way in PHP 4 to access class attributes), the flag is used to signal your code that it can safely skip all operations in the constructor, and that the frontend reference it is receiving is a dummy object and not the real one.
One last bit of magic that happens in the constructor is the active attribute. This attribute is checked by the plugin manager in Frontend right after a new instance of your plugin has been created. If the attribute evaluates to false, the plugin manager will skip your plugin and hide its template variable. The active flag is useful for plugins that may not have something to display or change, for example an RSS plugin that fails to get its data from the network.
Doing the actual work, the run() method
The run method is where the actual work happens. This method is called by the plugin manager when the different hooks fire, and receives as its first argument the hook name, and as its second argument a reference to the hook specific payload. The payload is a reference, so that you can modify its data in place, for example the post attributes in get_posts.
The run method for our HelloWorld? plugin is quite simple, since it limits itself with setting the message as a template variable. Usually, plugins don't directly set variables but use templates, and parse them to a template variable composed by the prefix PLUGIN_ and the plugin name. In our case, this would be PLUGIN_HELLOWORLD.
<?php // save this code as plugins/HelloWorld.php inside the lightpress folder require_once 'classes/LightPressPlugin.php'; class HelloWorld extends LightPressPlugin { var $description = 'The simplest Lightpress plugin, useful only for didactic purposes'; var $default_context = LP_CONTEXT_ALL; var $hooks = array('post_render'); var $constructor_args = array('message'=>'the message this plugin will display'); var $message = 'Hello, World!'; function HelloWorld(&$frontend, &$args, $dummy_run=false) { $this->LightPressPlugin($frontend, $args, $dummy_run); } function run($hook, &$payload) { $tpl =& $this->_frontend->tpl; $tpl->setVar('PLUGIN_HELLOWORLD', $this->message); } } ?>
Hiding the plugin, the hide() method
There are cases where you want your plugin to run but you need to hide its output, for example in the AdminOptions? plugin when the current user has no rights to see the edit links for posts and comments. In these occasions, it's better to code a separate hide() method, and call it to set all the template variables used by your plugin to an empty string. This will allow your plugin to be compatible with a minor change in the API that will be added sometimes in the future, where the plugin manager will call its hide() method when the plugin is not active.
Conditionally allowing the plugin to be activated, the install() method
A few plugins need to be able to signal the user that they cannot be activated (eg the Akismet plugin when no API key has been set). If you need this functionality, add a method named install. This methid will be called from the admin console when the user tries to activate the plugin. To disable activation, simply return a non-empty string which will be used as a message to tell the user why the plugin can not be activated.
