This book is targeted at MantisBT developers, contributors and plugin authors. It documents the development process and provides reference information regarding the MantisBT core, including the database schema as well as the plugin system including an events reference.

There are a few steps the MantisBT team requires of contributors and developers when accepting code submissions. The user needs to configure Git to know their full name (not a screen name) and an email address they can be contacted at (not a throwaway address).

To set up your name and email address with Git, run the following commands, substituting your own real name and email address:

If you are planning to use your own fork to push and maintain your changes, then we recommend setting up an upstreamremote for MantisBT's official repository, which will make it easier to keep your repository up-to-date.

By default, the new clone will only track code from the primary remote branch, master, which is the latest development version of MantisBT. If you are planning to work with stable release or other development branches, you will need to set up local tracking branches in your repository.

The following command will set up a tracking branch for the current stable branch, master-1.3.x.

git checkout -b master-1.3.x origin/master-1.3.x

Note

With the introduction of submodules for some of the third-party libraries, you may encounter issues when switching to an older branch which still has code from those libraries in a subdirectory of /library rather than a submodule:

$ git checkout old_branch
error: The following untracked working tree files would be overwritten by checkout
(list of files)
Aborting

To resolve this, you first have to get rid of the submodules directories before you can checkout the branch. The command below will move all submodules to /tmp:

For each local or shared feature branch that you are working on, you will need to keep it up to date with the appropriate master branch. There are multiple methods for doing this, each better suited to a different type of feature branch. Both methods assume that you have already performed the previous step, to update your local tracking branches (see Section 1.3, “Maintaining Tracking Branches”).

If the topic branch in question is a local, private branch, that you are not sharing with other developers, the simplest and easiest method to stay up to date with master is to use the rebase command. This will append all of your feature branch commits into a linear history after the last commit on the master branch.

git rebase master feature

Note

Rebasing changes the ID for each commit in your feature branch, which will cause trouble for anyone sharing and/or following your branch.

The resulting conflict can be fixed by rebasing their copy of your branch onto your branch:

For any publicly-shared branches, where other users may be watching your feature branches, or cloning them locally for development work, you'll need to take a different approach to keeping it up to date with master.

To bring public branch up to date, you'll need to merge the current master branch, which will create a special "merge commit" in the branch history, causing a logical "split" in commit history where your branch started and joining at the merge. These merge commits are generally disliked, because they can crowd commit history, and because the history is no longer linear. They will be dealt with during the submission process (see Section 1.5, “Running PHPUnit tests”).

git checkout feature
git merge master

At this point, you can push the branch to your public repository, and anyone following the branch can then pull the changes directly into their local branch, either with another merge, or with a rebase, as necessitated by the public or private status of their own changes.

MantisBT has a suite of PHPUnit tests found in the tests directory. You are encouraged to add your own tests for the patches you are submitting, but please remember that your changes must not break existing tests.

In order to run the tests, you will need to have the PHP Soap extension, PHPUnit 3.4 or newer and Phing 2.4 or newer installed. The tests are configured using a bootstrap.php file. The boostrap.php.sample file contains the settings you will need to adjust to run all the tests.

Running the unit tests is done from root directory using the following command:

With this method, you can keep your changesets up-to-date with the official development repository, and likewise let anyone stay up to date with your repository, without needing to constantly upload and download new formatted patches whenever you change anything.

The process below describes a simple workflow that can help you make your submission if you are not familiar with Git; note that it is by no means the only way to do this.

Please make sure you provide a detailed description of the changes you are submitting, including the reason for it and if possible a reference (link) to an existing issue on our bugtracker. The team will usually review your changes and provide feedback within 7 days (but your mileage may vary).

Formatted patches are very similar to file diffs generated by other tools or source control systems, but contain far more information, including your name and email address, and for every commit in the set, the commit's timestamp, message, author, and more. They allow anyone to import the enclosed changesets directly into Git, where all of the commit information is preserved.

Assuming that you have an existing local that you've kept up to date with master as described in Section 1.4, “Preparing Feature Branches” currently checked out, generating a formatted patch set should be relatively straightforward, using an appropriate filename as the target of the patch set:

Once you've generated the formatted patch file, you can easily attach it to a bug report, or even use the patch file as an email to send to the developer mailing list. Developers, or other users, can then import this patch set into their local repositories using the following command, again substituting the appropriate filename:

If you are not able or not willing to make use of a fork of the official GitHub repository but have another publicly available one to host your changes, for example on a free hosting for public repository such as

you can still use it to submit a patch in a similar fashion to the Github method described above, although the process is slightly more complicated.

We'll assume you've already set up a publicly accessible repository at URL git@githosting.com:contrib.git, kept it up-to-date with MantisBT's official repository, and that you have pushed your feature branch MyBranch to it.

Generate the Pull Request

This will list information about your changes and how to access them. The process will attempt to verify that you've pushed the correct data to the public repository, and will generate a summary of changes.

Once your pull request has been posted, developers and other users can add your public repository as a remote, and track your feature branch in their own working repository using the following commands, replacing the remote name and local branch name as appropriate:

If the feature is approved for entry into MantisBT core, then the branch should first be rebased onto the latest HEAD so that Git can remove any unnecessary merge commits, and create a linear history. Once that's completed, the feature branch can be merged into master:

The event system in MantisBT uses the concept of signals and hooked events to drive dynamic actions. Functions, or plugin methods, can be hooked during runtime to various defined events, which can be signalled at any point to initiate execution of hooked functions.

Events are defined at runtime by name and event type (covered in the next section). Depending on the event type, signal parameters and return values from hooked functions will be handled in different ways to make certain types of common communication simplified.

Hooking events requires knowing the name of an already-declared event, and the name of the callback function (and possibly associated plugin) that will be hooked to the event. If hooking only a function, it must be declared in the global namespace.

event_hook( $event_name, $callback, [$plugin] );

In order to hook many functions at once, using key/value pairs of name => callback relations, in a single array:

When signalling events, the event type of the target event must be kept in mind when handling event parameters and return values. The general format for signalling an event uses the following structure:

Each type of event (and individual events themselves) will use different combinations of parameters and return values, so perusing Chapter 5, Events Reference is recommended for determining the unique needs of each event when signalling and hooking them.

There are five standard event types currently defined in MantisBT. Each type is a generalization of a certain "class" of solution to the problems that the event system is designed to solve. Each type allows for simplifying a different set of communication needs between event signals and hooked callback functions.

Each type of event (and individual events themselves) will use different combinations of parameters and return values, so perusing Chapter 5, Events Reference is recommended for determining the unique needs of each event when signalling and hooking them.

This event type allows for simple output and execution from hooked events. A single set of immutable parameters are sent to each callback, and the return value is inlined as output. This event is generally used for an event with a specific purpose of adding content or markup to the page.

These events only use the first parameter array, and return values from hooked functions are immediately sent to the output buffer via 'echo'. Another parameter $format can be used to model how the results are printed. This parameter can be either:

null, or ommited: The returned values are printed without further processing

<String>: A string to be used as separator for printed values

<Array>: An array of (prefix, separator, postfix) to be used for the printed values

This event type is designed to allow plugins to successively alter the parameters given to them, such that the end result returned to the caller is a mutated version of the original parameters. This is very useful for such things as output markup parsers.

The first set of parameters to the event are sent to the first hooked callback, which is then expected to alter the parameters and return the new values, which are then sent to the next callback to modify, and this continues for all callbacks. The return value from the last callback is then returned to the event signaller.

This type allows events to optionally make use of the second parameter set, which are sent to every callback in the series, but should not be returned by each callback. This allows the signalling function to send extra, immutable information to every callback in the chain. Example usage:

The design of this event type allows for multiple hooked callbacks to 'compete' for the event signal, based on priority and execution order. The first callback that can satisfy the needs of the signal is the last callback executed for the event, and its return value is the only one sent to the event caller. This is very useful for topics like user authentication.

These events only use the first parameter array, and the first non-null return value from a hook function is returned to the caller. Subsequent callbacks are never executed. Example usage:

The plugin system for MantisBT is designed as a lightweight extension to the standard MantisBT API, allowing for simple and flexible addition of new features and customization of core operations. It takes advantage of the new Event System (see Chapter 3, Event System) to offer developers rapid creation and testing of extensions, without the need to modify core files.

Plugins are defined as implementations, or subclasses, of the MantisPlugin class as defined in core/classes/MantisPlugin.php. Each plugin may define information about itself, as well as a list of conflicts and dependencies upon other plugins. There are many methods defined in the MantisPlugin class that may be used as convenient places to define extra behaviors, such as configuration options, event declarations, event hooks, errors, and database schemas. Outside a plugin's core class, there is a standard method of handling language strings, content pages, and files.

At page load, the core MantisBT API will find and process any conforming plugins. Plugins will be checked for minimal information, such as its name, version, and dependencies. Plugins that meet requirements will then be initialized. At this point, MantisBT will interact with the plugins when appropriate.

The plugin system includes a special set of API functions that provide convenience wrappers around the more useful MantisBT API calls, including configuration, language strings, and link generation. This API allows plugins to use core API's in "sandboxed" fashions to aid interoperation with other plugins, and simplification of common functionality.

This section will act as a walk through of how to build a plugin, from the bare basics all the way up to advanced topics. A general understanding of the concepts covered in the last section is assumed, as well as knowledge of how the event system works. Later topics in this section will require knowledge of database schemas and how they are used with MantisBT.

This walk through will be working towards building a single end result: the "Example" plugin as listed in Section 4.3, “Example Plugin Source Listing”. You may refer to the final source code along the way, although every part of it will be built up in steps throughout this section.

This section will introduce the general concepts of plugin structure, and how to get a barebones plugin working with MantisBT. Not much will be mentioned yet on the topic of adding functionality to plugins, just how to get the development process rolling.

The backbone of every plugin is what MantisBT calls the "basename", a succinct, and most importantly, unique name that identifies the plugin. It may not contain any spacing or special characters beyond the ASCII upper- and lowercase alphabet, numerals, and underscore. This is used to identify the plugin everywhere except for what the end-user sees. For our "Example" plugin, the basename we will use should be obvious enough: "Example".

Every plugin must be contained in a single directory named to match the plugin's basename, as well as contain at least a single PHP file, also named to match the basename, as such:

Example/
Example.php

This top-level PHP file must then contain a concrete class deriving from the MantisPlugin class, which must be named in the form of %Basename%Plugin, which for our purpose becomes ExamplePlugin.

Because of how MantisPlugin declares the register() method as abstract, our plugin must implement that method before PHP will find it semantically valid. This method is meant for one simple purpose, and should never be used for any other task: setting the plugin's information properties, including the plugin's name, description, version, and more.

Once your plugin defines its class, implements the register() method, and sets at least the name and version properties, it is then considered a "complete" plugin, and can be loaded and installed within MantisBT's plugin manager. At this stage, our Example plugin, with all the possible plugin properties set at registration, looks like this:

The plugin API provides a standard hierarchy and process for adding new pages and files to your plugin. For strict definitions, pages are PHP files that will be executed within the MantisBT core system, while files are defined as a separate set of raw data that will be passed to the client's browser exactly as it appears in the filesystem.

New pages for your plugin should be placed in your plugin's pages/ directory, and should be named using only letters and numbers, and must have a ".php" file extension. To generate a URI to the new page in MantisBT, the API function plugin_page() should be used. Our Example plugin will create a page named foo.php, which can then be accessed via plugin_page.php?page=Example/foo, the same URI that plugin_page() would have generated:

Adding non-PHP files, such as images or CSS stylesheets, follows a very similar pattern as pages. Files should be placed in the plugin's files/ directory, and can only contain a single period in the name. The file's URI is generated with the plugin_file() function. For our Example plugin, we'll create a basic CSS stylesheet, and modify the previously shown page to include the stylesheet:

Plugins have an integrated method for both declaring and hooking events, without needing to directly call the event API functions. These take the form of class methods on your plugin.

To declare a new event, or a set of events, that your plugin will trigger, override the events() method of your plugin class, and return an associative array with event names as the key, and the event type as the value. Let's add an event "foo" to our Example plugin that does not expect a return value (an "execute" event type), and another event 'bar' that expects a single value that gets modified by each hooked function (a "chain" event type):

When the Example plugin is loaded, the event system in MantisBT will add these two events to its list of events, and will then allow other plugins or functions to hook them. Naming the events "EVENT_PLUGINNAME_EVENTNAME" is not necessary, but is considered best practice to avoid conflicts between plugins.

Hooking other events (or events from your own plugin) is almost identical to declaring them. Instead of passing an event type as the value, your plugin must pass the name of a class method on your plugin that will be called when the event is triggered. For our Example plugin, we'll create a foo() and bar() method on our plugin class, and hook them to the events we declared earlier.

Note that both hooked methods need to accept the $p_event parameter, as that contains the event name triggering the method (for cases where you may want a method hooked to multiple events). The bar() method also accepts and returns the chained parameter in order to match the expectations of the "bar" event.

Now that we have our plugin's events declared and hooked, let's modify our earlier page so that triggers the events, and add some real processing to the hooked methods:

When the first event "foo" is signaled, the Example plugin's foo() method will execute and echo a string. After that, the second event "bar" is signaled, and the page passes a string parameter; the plugin's bar() gets the string and replaces any instance of "foo" with "bar", and returns the resulting string. If any other plugin had hooked the event, that plugin could have further modified the new string from the Example plugin, or vice versa, depending on the loading order of plugins. The page then echos the modified string that was returned from the event.

Similar to events, plugins have a simplified method for declaring configuration options, as well as API functions for retrieving or setting those values at runtime.

Declaring a new configuration option is achieved just like declaring events. By overriding the config() method on your plugin class, your plugin can return an associative array of configuration options, with the option name as the key, and the default option as the array value. Our Example plugin will declare an option "foo_or_bar", with a default value of "foo":

Retrieving the current value of a plugin's configuration option is achieved by using the plugin API's plugin_config_get() function, and can be set to a modified value in the database using plugin_config_set(). With these functions, the config option is prefixed with the plugin's name, in attempt to automatically avoid conflicts in naming. Our Example plugin will demonstrate this by adding a secure form to the "config_page", and handling the form on a separate page "config_update" that will modify the value in the database, and redirect back to page "config_page", just like any other form and action page in MantisBT:

MantisBT has a very advanced set of localization tools, which allow all parts of of the application to be localized to the user's preferred language. This feature has been extended for use by plugins as well, so that a plugin can be localized in much the same method as used for the core system. Localizing a plugin involves creating a language file for each localization available, and using a special API call to retrieve the appropriate string for the user's language.

All language files for plugins follow the same format used in the core of MantisBT, should be placed in the plugin's lang/ directory, and named the same as the core language files. Strings specific to the plugin should be "namespaced" in a way that will minimize any risk of collision. Translating the plugin to other languages already supported by MantisBT is then as simple as creating a new strings file with the localized content; the MantisBT core will find and use the new language strings automatically.

We'll use the "configuration" pages from the previous examples, and dress them up with localized language strings, and add a few more flourishes to make the page act like a standard MantisBT page. First we need to create a language file for English, the default language of MantisBT and the default fallback language in the case that some strings have not yet been localized to the user's language:

The two calls to html_page_top() and html_page_bottom() trigger the standard MantisBT header and footer portions, respectively, which also displays things such as the menus and triggers other layout-related events. helper_alternate_class() generates the CSS classes for alternating row colors in the table. The rest of the HTML and CSS follows the "standard" MantisBT markup styles for content and layout.

The code in this section, for the Example plugin, is available for use, modification, and redistribution without any restrictions and without any warranty or implied warranties. You may use this code however you want.

In this chapter, an attempt will be made to list all events used (or planned for later use) in the MantisBT event system. Each listed event will include details for the event type, when the event is called, and the expected parameters and return values for event callbacks.

Here we show an example event definition. For each event, the event identifier will be listed along with the event types (see Section 3.3, “Event Types”) in parentheses. Below that should be a concise but thorough description of how the event is called and how to use it. Following that should be a list of event parameters (if any), as well as the expected return value (if any).

This event is triggered by the MantisBT plugin system after all registered and enabled plugins have been initialized (their init() functions have been called). This event should always be the first event triggered for any page load. No parameters are passed to hooked functions, and no return values are expected.

This event is the first point in page execution where all registered plugins are guaranteed to be enabled (assuming dependencies and such are met). At any point before this event, any or all plugins may not yet be loaded. Note that the core system has not yet completed the bootstrap process when this event is signalled.

Suggested uses for the event include:

Checking for plugins that aren't require for normal usage.

Interacting with other plugins outside the context of pages or events.

This event is triggered by the MantisBT bootstrap process just before emitting the headers. This enables plugins to emit their own headers or use API that enables tweaking values of headers emitted by core. An example, of headers that can be tweaked is Content-Security-Policy header which can be tweaked using http_csp_*() APIs.

This event is triggered by the MantisBT bootstrap process after all core APIs have been initialized, including the plugin system, but before control is relinquished from the bootstrap process back to the originating page. No parameters are passed to hooked functions, and no return values are expected.

This event is the first point in page execution where the entire system is considered loaded and ready.

This event is triggered by MantisBT to log a message. The contents of the message should be hyper linked based on the following rules: #123 means issue 123, ~123 means issue note 123, @P123 means project 123, @U123 means user 123. Logging plugins can capture extra context information like timestamp, current logged in user, etc. This event receives the logging string as a parameter.

These events make it possible to dynamically modify output strings to interpret or add semantic meaning or markup. Examples include the creation of links to other bugs or bugnotes, as well as handling urls to other sites in general.

This is an event to format text before being sent in an email. Callbacks should be used to process text and convert it into a plaintext-readable format so that users with textual email clients can best utilize the information. Hyperlinks and other markup should be removed, leaving the core content by itself.

This is an event to display generic formatted text. The string to be displayed is passed between hooked callbacks, each taking a turn to modify the output in some specific manner. Text passed to this may be processed for all types of formatting and markup, including clickable links, presentation adjustments, etc.

This is an event to display generic unformatted text. The string to be displayed is passed between hooked callbacks, each taking a turn to modify the output in some specific manner. Text passed to this event should only be processed for the most basic formatting, such as preserving line breaks and special characters.

This event gives plugins the opportunity to add new links to the main menu at the top (or bottom) of every page in MantisBT. New links will be added after the 'Docs' link, and before the 'Manage' link. Hooked events may return multiple links, in which case each link in the array will be automatically separated with the '|' symbol as usual.

This event gives plugins the opportunity to add new links to the main menu at the top (or bottom) of every page in MantisBT. New links will be added after the 'Main' link, and before the 'My View' link. Hooked events may return multiple links, in which case each link in the array will be automatically separated with the '|' symbol as usual.

This event gives plugins the opportunity to add new links to the management menu available to site administrators from the 'Manage' link on the main menu. Plugins should try to minimize use of these links to functions dealing with core MantisBT management.

This event gives plugins the opportunity to add new links to the configuration management menu available to site administrators from the 'Manage Configuration' link on the standard management menu. Plugins should try to minimize use of these links to functions dealing with core MantisBT configuration.

These events offer the chance to create output at points relevant to the overall page layout of MantisBT. Page headers, footers, stylesheets, and more can be created. Events listed below are in order of runtime execution.

This event allows plugins to output HTML code from inside the <head> tag, for use with CSS, Javascript, RSS, or any other similar resources. Note that this event is signaled after all other CSS and Javascript resources are linked by MantisBT.

This event allows plugins to output HTML code immediately after the line of an attachment. Receives the attachment data as a parameter, in the form of an attachment array from within the array returned by the file_get_visible_attachments() function.

This event allows a plugin to register custom filter objects (based on the MantisFilter class) that will allow the user to search for issues based on custom criteria or datasets. The plugin can return either a class name (which will be instantiated at runtime) or an already instantiated object. The plugin must ensure that the filter class has been defined before returning the class name for this event.

This event allows a plugin to register custom column objects (based on the MantisColumn class) that will allow the user to view data for issues based on custom datasets. The plugin can return either a class name (which will be instantiated at runtime) or an already instantiated object. The plugin must ensure that the column class has been defined before returning the class name for this event.

This event allows a plugin to either process information or display some data in the bug view page. It is triggered after the row containing the target version and product build fields, and before the bug summary is displayed.

Any output here should be defining appropriate rows and columns for the surrounding

This event allows plugins to perform pre-processing of the new bug data structure after being reported from the user, but before the data is saved to the database. At this point, the issue ID is not yet known, as the data has not yet been persisted.

This event allows plugins to perform post-processing of the bug data structure after being reported from the user and being saved to the database. At this point, the issue ID is actually known, and is passed as a second parameter.

This event allows a plugin to specify a set of users to be included as recipients for a notification. The set of users returned is added to the list of recipients already generated from the existing notification flags and selection process.

This event allows a plugin to selectively exclude individual users from the recipient list for a notification. The event is signalled for every user in the final recipient list, including recipients added by the event NOTIFY_USER_INCLUDE as described above.

This event gets the user's avatar as an instance of the Avatar class. The first plugin to respond with an avatar wins. Hence, in case of multiple avatar plugins, make sure to tweak the priorities. Avatars should return null if they don't have an avatar for the specified user id.

This event allows plugins to do post-processing of newly-created users. This event is triggered for each user created. The Manage Users create form is one possible case for triggering such events, but there can be other ways users can be created.

The SOAP API signature will change between minor releases, typically to add new functionality or to extend existing features.

Some of these changes might require a refresh of the client libraries generated, for instance Apache Axis 1 SOAP stubs must be regenerated if a complex type receives a new property. Such changes will be announced before the release of the new MantisBT version on the mantisbt-soap-dev mailing list. Typically there will be two weeks time to integrate the new SOAP stubs.