I copied this from fluxbb.org some time ago, they killed the topic and ext system though, so here's a repost (just replace fluxbb with punbb when reading...) - credit to them for haven written it
=================================
The nature of the extension system allows you to easily extend PunBB's functionality without touching the source code of the core. The way PunBB manages this is using hooks and a manifest that describes how to use them.
This page outlines how we expect PunBB extensions to be written. These are both designed to make sure all extensions are secure and of good quality, and also to help developers write them. It is important that you read this entire page before you begin developing extensions.
General coding guidelines
The rules and recommendations set forth in this section apply to PHP, JavaScript, SQL and any other programming language (where applicable) in use by FluxBB. Please note that tabs in this document have been substituted by spaces for visual reasons only.
Naming convention
The naming rules apply to the naming of variables, functions, classes, attributes, arrays, array elements, HTML form fields, query string parameters, database tables, database fields as well as any other applicable entities.
*
All names should be all lowercase.
*
Use _ (underscore) as word separator. E.g. date_format. Never start a name with the underscore character (except for nested functions, which should start with an underscore character).
*
Use the prefix “num” for entities that represent a count. E.g. num_users. Use the prefix “cur” for entities that represent the current element when iterating through any type of collection (database result set, array etc).
*
Avoid all forms of Hungarian notation or derivatives or variations thereof.
*
Use common sense. If in doubt, look at similar sections in the FluxBB source code.
Brace policy and indentation
The indent style and brace policy of choice for the FluxBB project is the Allman style. All indentation should be made with tabs, not spaces. Here's an example:
if ($a == $b)
{
do_something();
}
else
{
do_something_else();
}
Note the whitespace between the keyword and the parenthesis. One allowed exception from the standard Allman style is “braceless” blocks. The use of braceless blocks is actually encouraged.
For example:
if ($a == $b)
do_something();
else
do_something_else();
If the author prefers it, the code block can also be placed on the same line as the control statement.
Line breaks
All line breaks should be LF only. Set your editor to save files with UNIX style line breaks.
PHP-specific
The following rules apply only to PHP.
*
Use singlequotes as opposed to doublequotes when working with strings. E.g. $str = 'Users: '.$num_users; as opposed to $str = "Users: $num_users";.
*
Leave one line of whitespace above and below each block of markup, provided the block constitutes multiple lines of markup. I.e. one empty line above each ?> and below each <?php. The exception to this is when there is only one line of code, in which case the entire section of code should be on one line.
*
Don't use PHP's short tags (<?, <?=), but use its full equivalent (<?php, <?php echo).
SQL-specific
The following rules apply only to SQL.
*
Always escape potentially harmful data using $forum_db->escape(). Expected integer values should be forced into integer form using intval() before use in a query.
*
Prefix tables names and table indices with $forum_db->prefix.
*
Use the SQL2003 style explicit join notation.
*
Whenever possible, write cross-database compatible queries. Developers should take advantage of forum_db->query_build, which allows them to easily create/extend cross-database compatible queries. In rare cases where database specific queries are needed, every effort should be made to provide alternative queries for other supported databases.
Writing secure code
Validating/escaping data before saving in the db and before outputting to browser
The manifest
All extensions come with a manifest file, manifest.xml. The manifest file is basically the entry point to an extension. It is a regular XML file that contains information about the extension, such as author name, short description of what it does, and what version of FluxBB it requires.1) It also has information about hooks, and how FluxBB should use them.
A typical manifest file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<extension engine="1.0">
<id>example_extension</id>
<title>Example Extension</title>
<version>0.1</version>
<description>This is a short description of the extension.</description>
<author>John Doe</author>
<minversion>1.3 Beta</minversion>
<maxtestedon>1.3 Beta</maxtestedon>
<dependencies>
<dependency>another_extension</dependency>
</dependencies>
<note type="install">Add notes before the user installs the extension</note>
<hooks>
<hook id="vf_start"><![CDATA[
// Include a file from the extension directory
require $ext_info['path'].'/foobar.php';
]]></hook>
<hook id="vf_pre_header_load"><![CDATA[
// Call a function from foobar.php
foobar_function();
]]></hook>
</hooks>
</extension>
Basic info
Now we will break this down:
<?xml version="1.0" encoding="UTF-8"?>
This is the basic XML declaration that describes what version of XML the document follows, and what character encoding is used. Since FluxBB has full support for UTF-8 from version 1.3 onwards, you should use UTF-8 encoding.
<extension engine="1.0">
This tells FluxBB which extension engine the extension was designed for. This is required in case the FluxBB extension system is significantly changed in the future (making old extensions incompatible). The current extension engine version is 1.0.
<id>example_extension</id>
This is an identifier of the extension. FluxBB uses this for identifying each of your installed extensions, so it should be unique. Note that the extension directory should be named after the extension ID. A good idea would be to use only lowercase letters and the underscore character “_” for spaces. The ID cannot be more than 50 characters long.
<title>Example Extension</title>
This is the title of your extension. Use your imagination.
<version>0.1</version>
The version number of your extension. You should follow the version numbering rules in determining what version number to use.
<author>John Doe</author>
This is your name. Does not require that much imagination now, does it?
<minversion>1.3 Beta</minversion>
<maxtestedon>1.3 Beta</maxtestedon>
The minimum of FluxBB the extension requires, and the maximum version it was tested on. If your FluxBB version is lower than minversion or maxtestedon, FluxBB gives you a warning when installing the extension.
Using dependencies
<dependencies>
<dependency>another_extension</dependency>
</dependencies>
This tells FluxBB to only allow this extension to be installed if the given dependencies are both installed and enabled, it will also stop any given extensions being uninstalled or disabled while this extension is running.
Install/Uninstall
Install
<install><![CDATA[
// Installation code
]]></install>
Upgrading
One important thing to note about the install procedure is it will be repeated when upgrading, therefore it should check before it does anything that only needs to be done once. You can detect an upgrade by the constant EXT_CUR_VERSION.
Uninstall
<uninstall><![CDATA[
// Uninstallation code
]]></uninstall>
Using Hooks
Hooks are special “tags” in the FluxBB code, that can be replaced with any piece of code specified in a manifest file. A typical hook looks like this:
($hook = get_hook('vf_start')) ? (defined('FORUM_USE_INCLUDE') ? include $hook : eval($hook)) : null;
What you need to pay attention to here is “vf_start”. This is the name of the hook. In this case, the hook resides in the beginning of viewforum.php (thus the prefix “vf_”). If you were to replace that hook with a code snippet of your own, you will need to remember this name for the manifest.
<hooks>
<hook id="vf_start"><![CDATA[
// Include a file from the extension directory
require $ext_info['path'].'/foobar.php';
]]></hook>
<hook id="vf_pre_header_load"><![CDATA[
// Call a function from foobar.php
foobar_function();
]]></hook>
</hooks>
The hooks section tells FluxBB what to do with the hooks. It has subsections for each hook used in the extension, identified with their name. The PHP code with which the hook is replaced with resides in the CDATA section (identified by the <![CDATA[ and ]]> tags). That is, the hooks vf_start and vf_pre_header_load will be replaced with anything you enter in their respective CDATA sections.2)
This example uses two hooks, both in viewforum.php.3) The first hook (vf_start) simply includes a PHP file from the extension directory. As you can see, we use $ext_info['path'] to get the path to the extensions folder. The second hook simply calls a function called defined in foobar.php.
In addition to “merely” adding functions through external files, hooks allow you to circumvent FluxBB's core functionality, and replace them with your own. For example, you can use temporary variables to get around the BBCode parser, and replace it with a completely different parser.4)
Multiple hooks
If you want to repeat code in multiple hooks you can do it by separating the hook nams with a ”,” the code below shows how to use the same code in vf_start and vt_start
<hooks>
<hook id="vf_start, vt_start"><![CDATA[
// Do something in both viewforum and viewtopic
]]></hook>
</hooks>
Using priority
When using hooks we can also specify a priority for our code. Priority is useful in avoiding conflicts with other extensions. For example, if your code tries to return a value, in most cases you would want your code to run last so as not to prevent other code from executing.
Priority ranges from 1-10, with 1 specifying code that should be run first and 10 specifying code that should be run last. If two extensions specify the same priority for the same hook, their installation order determines which runs first. The default priority is 5. An example of how to set priority is shown below.
<hooks>
<hook id="vf_start" priority="2"><![CDATA[
// Include a file from the extension directory
require $ext_info['path'].'/foobar.php';
]]></hook>
</hooks>
The extension directory
All extensions are contained within a single folder which is named after their id, this folder is then placed in the extensions directory within FluxBB, i.e. for the above extensions this would be extensions/example_extension/* where * is the extensions files.
The manifest file is saved as manifest.xml and put directly in the extensions folder.
Language files
If the extension contains any strings that could be translated these should be put in a language file. The structure for language files is extensions/ext_id/lang/Language.php, for example extensions/example_extension/lang/English.php. The language file would then contain
<?php
$lang_example_extension = array(
'Example 1' => 'An example language string',
);
To include the language file the following code would be used
if (file_exists($ext_info['path'].'/lang/'.$forum_user['language'].'.php'))
require $ext_info['path'].'/lang/'.$forum_user['language'].'.php';
else
require $ext_info['path'].'/lang/English.php';
Notice $ext_info['path'] is used for the extensions directory, and if the users language is not avaliable a default language is loaded.
Other extension issues
Including files
*
You must not use includes in the uninstall process, since the code within the uninstall hook is copied to the database upon installation (and thus must be able to be run by itself). All of the code for these must be contained within the manifest.xml file.
*
When using includes in hooks in general you must only include functions and libraries, you may not put all the code from a hook in a separate file and include it.
Version numbers
Extension versions should be numbered in the same format as FluxBB, that is for example 1.2.3 where 1 is the major version, 2 is the minor version and 3 is the release. Extensions in beta stages should be numbered 0.x.x, each time new functionality is added the minor version should be increased, and after a full/major rewrite the major number should be increased. Small bugfixes should increase the release number.
CSS/Dealing with styles
Using $ext_info
Every extension is provided with the $ext_info array, which contains information about the extension.
Variable Description Example
$ext_info['id'] The ID of current extension example_extension
$ext_info['path'] The relative path to the current extension (without trailing slash). Use it to include() files ./extensions/example_extension
$ext_info['url'] The URL to the folder of the current extension (absolute, without trailing slash). Use it when embedding stylesheets or images http://example.net/forums/extensions/example_extension
Extensions must be able to be disabled
Extensions in FluxBB can be disabled from the administration panel or via a constant in config.php. This action does not uninstall the extension but simply turns off its hooks. It is important you think about this situation when coding your extension. As a result, in general you can not perform destructive actions on the core database (eg: remove the Guest user, delete a core configuration value, shrink a column to an unusable size, drop a table or column, etc).
It is also important to make sure that any files in your extension that are accessed directly return some form of error message if they are accessed when the extension is disabled.
Databases
Supporting all databases
It is important your extension work in all 3 database systems supported by FluxBB. To do this, you should use the helper functions provided in the database abstraction layer as much as possible. If you need help testing the different databases you can ask in the forums.
Creating a table
Here is an example from install.php
$schema = array(
'FIELDS' => array(
'user_id' => array(
'datatype' => 'INT(10) UNSIGNED',
'allow_null' => false,
'default' => '1'
),
'ident' => array(
'datatype' => 'VARCHAR(200)',
'allow_null' => false,
'default' => '""'
),
'logged' => array(
'datatype' => 'INT(10) UNSIGNED',
'allow_null' => false,
'default' => '0'
),
'idle' => array(
'datatype' => 'TINYINT(1)',
'allow_null' => false,
'default' => '0'
),
'csrf_token' => array(
'datatype' => 'VARCHAR(40)',
'allow_null' => false,
'default' => '""'
),
'prev_url' => array(
'datatype' => 'VARCHAR(255)',
'allow_null' => true
)
),
'UNIQUE KEYS' => array(
'user_id_ident_idx' => array('user_id', 'ident')
),
'INDEXES' => array(
'user_id_idx' => array('user_id')
),
'ENGINE' => 'HEAP'
);
$forum_db->create_table('online', $schema);
The create_table function takes either two or three parameters. The first parameter is the name of the table to be added. The second is an array of schema for the table itself. The third parameter, which is optional, specifies whether or not to add the database prefix to the table name. If the third parameter is not specified or is set to false, the prefix will be prepended to the table name. If it is specified as true, the prefix will not be prepended.
The schema itself is an array made up of several elements. This helper function is not meant to enable people without SQL knowledge to use the database: developers should have an understanding of basic schema before working with any database related code.
*
FIELDS: Fields is an array, with the keys being field names and the values being an array of data about the field. The field data contains a key datatype that should be mapped to one of the acceptable field types, a key allow_null which should be set to either true or false, and an optional key default which specifies a default value for the field.
*
PRIMARY KEY: An array whose elements are field names from FIELDS.
*
UNIQUE KEYS: An array with keys that act as the name of unique indexes and values that are arrays of field names (again, the field names should match field names in FIELDS).
*
INDEXES: An array with keys that act as the name of indexes and values that are arrays of field names (again, the field names should match field names in FIELDS).
*
ENGINE: This element is optional and is only used by MySQL. It specifies the database engine that MySQL should use in creating this table.
Acceptable field types
When adding a column or creating a new table, you must specify the data type for each field. The following data-types are acceptable to use in the helper functions (create_table and add_field). These data-types are guaranteed to be translated properly when used.
*
SERIAL (translates to INT(10) UNSIGNED AUTO_INCREMENT in MySQL)
*
TINYINT/SMALLINT/MEDIUMINT/INT/BIGINT (##) UNSIGNED (UNSIGNED and (##) are optional)
*
VARCHAR/CHAR
*
TINYTEXT/MEDIUMTEXT/TEXT/LONGTEXT
*
FLOAT
*
DOUBLE
Using query builder
An example query from post.php. Don't use $query in extensions as this variable is used in the core and might interfere with it. In general, extension authors should use a variable naming scheme unique to their extension (eg: $ext_id_query, where $ext_id is the extension ID)
$query = array(
'SELECT' => 'f.id, f.forum_name, f.moderators, f.redirect_url, fp.post_replies, fp.post_topics, t.subject, t.closed, s.user_id AS is_subscribed',
'FROM' => 'topics AS t',
'JOINS' => array(
array(
'INNER JOIN' => 'forums AS f',
'ON' => 'f.id=t.forum_id'
),
array(
'LEFT JOIN' => 'forum_perms AS fp',
'ON' => '(fp.forum_id=f.id AND fp.group_id='.$forum_user['g_id'].')'
),
array(
'LEFT JOIN' => 'subscriptions AS s',
'ON' => '(t.id=s.topic_id AND s.user_id='.$forum_user['id'].')'
)
),
'WHERE' => '(fp.read_forum IS NULL OR fp.read_forum=1) AND t.id='.$tid
);
Extensible extensions
When writing extensions it is important you think about the task your extension will perform, where possible try to do one task and do it well. When writing more complex extensions it is best to break them down into multiple extensions and use dependencies to combine them. For example a donation extension could be separate from a cash extension but depend on it. Then users who do not want the donate function can choose not to install that part.
Make an extensible extension
Even extensions themselves can be extended. Use hooks in your extension to give other developers the chance to enhance your extension. Your hook may look like the following:
($hook = get_hook('xn_example_extension_common')) ? (defined('FORUM_USE_INCLUDE') ? include $hook : eval($hook)) : null;
*
xn stands for extension
*
Replace example_extension with the id of your extension to create a unique namespace
*
common is the key of your hook - in this case a common point (e.g.for variable modifications)
*
If the hook is placed within a FluxBB file it should contain the files id used for FluxBB hooks. i.e. xn_example_extension_vt_hookname for a hook in viewtopic.php
Use an extendable extension
Hooks of extensions can be used exactly like normal hooks. To guarantee that the extension you extended is available, add dependencies to your manifest.