Mantis Bug Tracker Developers Guide Copyright © 2012 The MantisBT Team A reference guide and documentation of the Mantis Bug Tracker for developers and community members. Build Date: 1 April 2012 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. __________________________________________________________ Table of Contents 1. Contributing to MantisBT 1.1. Initial Setup 1.2. Cloning the Repository 1.3. Maintaining Tracking Branches 1.4. Preparing Feature Branches 1.4.1. Private Branches 1.4.2. Public Branches 1.5. Running PHPUnit tests 1.5.1. Running the SOAP tests 1.6. Submitting Changes 1.6.1. Via Formatted Patches 1.6.2. Via Public Repository 2. Database Schema Management 2.1. Schema Definition 2.2. Installation / Upgrade Process 3. Event System 3.1. General Concepts 3.2. API Usage 3.3. Event Types 4. Plugin System 4.1. General Concepts 4.2. Building a Plugin 4.2.1. The Basics 4.2.2. Pages and Files 4.2.3. Events 4.2.4. Configuration 4.2.5. Language and Localization 4.3. Example Plugin Source Listing 4.3.1. Example/Example.php 4.3.2. Example/files/foo.css 4.3.3. Example/lang/strings_english.txt 4.3.4. Example/page/config_page.php 4.3.5. Example/pages/config_update.php 4.3.6. Example/page/foo.php 4.4. API Usage 5. Event Reference 5.1. Introduction 5.2. System Events 5.3. Output Modifier Events 5.3.1. String Display 5.3.2. Menu Items 5.3.3. Page Layout 5.4. Bug Filter Events 5.4.1. Custom Filters and Columns 5.5. Bug and Bugnote Events 5.5.1. Bug View 5.5.2. Bug Actions 5.5.3. Bugnote View 5.5.4. Bugnote Actions 5.6. Notification Events 5.6.1. Recipient Selection 5.7. User Account Events 5.7.1. Account Preferences 5.8. Management Events 5.8.1. Projects and Versions 6. Integrating with MantisBT 6.1. Java integration 6.1.1. Prebuilt SOAP stubs using Axis 6.1.2. Usage in OSGi environments 6.2. Compatibility between releases 6.3. Support 7. Appendix 7.1. Git References __________________________________________________________ Chapter 1. Contributing to MantisBT MantisBT uses the source control tool Git for tracking development of the project. If you are new to Git, you can find some good resources for learning and installing Git in the Appendix. __________________________________________________________ 1.1. Initial Setup There are a few steps the MantisBT team requires of contributers 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: $ git config --global user.name "John Smith" $ git config --global user.email "jsmith@mantisbt.org" Optionally, you may want to also configure Git to use terminal colors when displaying file diffs and other information, and you may want to alias certain Git actions to shorter phrases for less typing: $ git config --global color.diff "auto" $ git config --global color.status "auto" $ git config --global color.branch "auto" $ git config --global alias.st "status" $ git config --global alias.di "diff" $ git config --global alias.co "checkout" $ git config --global alias.ci "commit" __________________________________________________________ 1.2. Cloning the Repository The primary repository for MantisBT is hosted and available in multiple methods depending on user status and intentions. For most contributers, the public clone URL is git://github.com/mantisbt/mantisbt.git. To clone the repository, perform the following from your target workspace: $ git clone git://github.com/mantisbt/mantisbt.git If you are a member of the MantisBT development team with write access to the repository, there is a special clone URL that uses your SSH key to handle access permissions and allow you read and write access. Note: This action will fail if you do not have developer access or do not have your public SSH key set up correctly. $ git clone git@github.com:mantisbt/mantisbt.git After performing the cloning operation, you should end up with a new directory in your workspace, mantisbt/. By default, it will only track code from the primary remote branch, master, which is the latest development version of MantisBT. For contributers planning to work with stable release branches, or other development branches, you will need to set up local tracking branches in your repository. The following commands will set up a tracking branch for the current stable branch, master-1.2.x. $ git checkout -b master-1.2.x origin/master-1.2.x __________________________________________________________ 1.3. Maintaining Tracking Branches In order to keep your local repository up to date with the official, there are a few simple commands needed for any tracking branches that you may have, including master and master-1.2.x. First, you'll need to got the latest information from the remote repo: $ git fetch origin Then for each tracking branch you have, enter the following commands: $ git checkout master $ git rebase origin/master Alternatively, you may combine the above steps into a single command for each remote tracking branch: $ git checkout master $ git pull --rebase __________________________________________________________ 1.4. Preparing Feature Branches 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. __________________________________________________________ 1.4.1. Private 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 checkout feature $ git rebase master Do note that this changes the commit ID for each commit in your feature branch, which will cause trouble for anyone sharing and/or following your branch. In this case, if you have rebased a branch that other users are watching or working on, they can fix the resulting conflict by rebasing their copy of your branch onto your branch: $ git checkout feature $ git fetch remote/feature $ git rebase remote/feature __________________________________________________________ 1.4.2. Public Branches 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. $ 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. __________________________________________________________ 1.5. Running PHPUnit tests 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: $ phing test __________________________________________________________ 1.5.1. Running the SOAP tests MantisBT ships with a suite of SOAP tests which require an initial set up to be executed. The required steps are: * Install MantisBT locally and configure a project and a category. * Adjust the bootstrap.php file to point to your local installation. * Customize the config_inc.php to enable all the features tested using the SOAP tests. The simplest way to do that is to run all the tests once and ajust it based on the skipped tests. __________________________________________________________ 1.6. Submitting Changes When you have a set of changes to MantisBT that you would like to contribute to the project, there are two preferred methods of making those changes available for project developers to find, retrieve, test, and commit. The simplest method uses Git to generate a specially-formatted patch, and the other uses a public repository to host changes that developers can pull from. 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. This formatted patch allows anyone to import the enclosed changesets directly into Git, where all of the commit information is preserved. Using a public repository to host your changes is marginally more complicated than submitting a formatted patch, but is more versatile. It allows you to keep your changesets up to date with the offiicial development repository, and it lets anyone stay up to date with your repository, without needing to constantly upload and download new formatted patches whenever you change anything. There is no need for a special server, as free hosting for public repositories can be found on many sites, such as MantisForge.org, GitHub, or Gitorious. __________________________________________________________ 1.6.1. Via Formatted Patches Assuming that you have an existing local branch that you've kept up to date with master as described in Preparing Feature Branches, generating a formatted patch set should be relatively straightforward, using an appropriate filename as the target of the patch set: $ git format-patch --binary --stdout origin/master..HEAD > feature_branc h.patch 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: $ git am --signoff feature_branch.patch __________________________________________________________ 1.6.2. Via Public Repository We'll assume that you've already set up a public repository, either on a free repository hosting site, or using git-daemon on your own machine, and that you know both the public clone URL and the private push URL for your public repository. For the purpose of this demonstration, we'll use a public clone URL of git://mantisbt.org/contrib.git, a private push URL of git@mantisbt.org:contrib.git, and a hypothetical topic branch named feature. You'll need to start by registering your public repository as a 'remote' for your working repository, and then push your topic branch to the public repository. We'll call the remote public for this; remember to replace the URL's and branch name as appropriate: $ git remote add public git@mantisbt.org:contrib.git $ git push public feature Next, you'll need to generate a 'pull request', which will list information about your changes and how to access them. This process will attempt to verify that you've pushed the correct data to the public repository, and will generate a summary of changes that you should paste into a bug report or into an email to the developer mailing list: $ git request-pull origin/master git://mantisbt.org/contrib.git feature 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: $ git remote add feature git://mantisbt.org/contrib.git $ git checkout -b feature feature/feature If a remote branch is approved for entry into master, then it should first be rebased onto the latest commits, so that Git can remove any unnecessary merge commits, and create a single linear history for the branch. Once that's completed, the branch can be fast-forwarded onto master: $ git checkout feature $ git rebase master $ git checkout master $ git merge --ff feature If a feature branch contains commits by non-developers, the branch should be signed off by the developer handling the merge, as a replacement for the above process: $ git checkout feature $ git rebase master $ git format-patch --binary --stdout master..HEAD > feature_branch.patch $ git am --signoff feature_branch.patch __________________________________________________________ Chapter 2. Database Schema Management 2.1. Schema Definition TODO: Discuss the ADODB datadict formats and the format MantisBT expects for schema deinitions. __________________________________________________________ 2.2. Installation / Upgrade Process TODO: Discuss how MantisBT handles a database installation / upgrade, including the use of the config system and schema definitions. __________________________________________________________ Chapter 3. Event System 3.1. General Concepts 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. __________________________________________________________ 3.2. API Usage This is a general overview of the event API. For more detailed analysis, you may reference the file core/event_api.php in the codebase. Declaring Events When declaring events, the only information needed is the event name and event type. Events can be declared alone using the form: event_declare( $name, $type=EVENT_TYPE_DEFAULT ); or they can be declared in groups using key/value pairs of name => type relations, stored in a single array, such as: $events = array( $name_1 => $type_1, $name_2 => $type_2, ... ); event_declare_many( $events ); Hooking Events 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: $events = array( $event_1 => $callback_1, $event_2 => $callback_2, ... ); event_hook( $events, [$plugin] ); Signalling Events 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: $value = event_signal( $event_name, [ array( $param, ... ), [ array( $st atic_param, ... ) ] ] ); Each type of event (and individual events themselves) will use different combinations of parameters and return values, so use of the Event Reference is reccommended for determining the unique needs of each event when signalling and hooking them. __________________________________________________________ 3.3. Event Types 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 use of the Event Reference is reccommended for determining the unique needs of each event when signalling and hooking them. EVENT_TYPE_EXECUTE This is the simplest event type, meant for initiating basic hook execution without needing to communicate more than a set of immutable parameters to the event, and expecting no return of data. These events only use the first parameter array, and return values from hooked functions are ignored. Example usage: event_signal( $event_name, [ array( $param, ... ) ] ); EVENT_TYPE_OUTPUT 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 immediatly sent to the output buffer via 'echo'. Example usage: event_signal( $event_name, [ array( $param, ... ) ] ); EVENT_TYPE_CHAIN This event type is designed to allow plugins to succesively 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: $value = event_signal( $event_name, $param, [ array( $static_param, ... ) ] ); EVENT_TYPE_FIRST 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: $value = event_signal( $event_name, [ array( $param, ... ) ] ); EVENT_TYPE_DEFAULT This is the fallback event type, in which the return values from all hooked callbacks are stored in a special array structure. This allows the event caller to gather data separately from all events. These events only use the first parameter array, and return values from hooked functions are returned in a multi-dimensional array keyed by plugin name and hooked function name. Example usage: $values = event_signal( $event_name, [ array( $param, ... ) ] ); __________________________________________________________ Chapter 4. Plugin System 4.1. General Concepts 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 ofthe new 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. __________________________________________________________ 4.2. Building a Plugin This section will act as a walkthrough 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 walkthrough will be working towards building a single end result: the "Example" plugin as listed in the next section. 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. __________________________________________________________ 4.2.1. The Basics 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. __________________________________________________________ 4.2.1.1. Plugin Structure 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: Example/Example.php name = 'Example'; # Proper name of plugin $this->description = ''; # Short description of the plugin $this->page = ''; # Default plugin page $this->version = '1.0'; # Plugin version string $this->requires = array( # Plugin dependencies, array of base name => version pairs 'MantisCore' => '1.2.0, <= 1.2.0', # Should always depend on an appropriate version of MantisBT ); $this->author = ''; # Author/team name $this->contact = ''; # Author/team e-mail address $this->url = ''; # Support webpage } } This alone will allow the Example plugin to be installed with MantisBT, and is the foundation of any plugin. More of the plugin development process will be continued in the next sections. __________________________________________________________ 4.2.2. Pages and Files 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: Example/pages/foo.php Here is a link to page foo .
'; 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: Example/files/foo.css p.foo { color: red; } Example/pages/foo.php Here is a link to page foo .'; echo '', 'This is red text.
'; Note that while plugin_page() expects on the page's name, without the extension, plugin_file() requires the entire filename so that it can distinguish between foo.css and a potential file foo.png. The plugin's filesystem structure at this point looks like this: Example/ Example.php pages/ foo.php files/ foo.css __________________________________________________________ 4.2.3. Events 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): Example/Example.php EVENT_TYPE_EXECUTE, 'EVENT_EXAMPLE_BAR' => EVENT_TYPE_CHAIN, ); } } 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. Example/Example.php 'foo', 'EVENT_EXAMPLE_BAR' => 'bar', ); } function foo( $p_event ) { ... } function bar( $p_event, $p_chained_param ) { ... return $p_chained_param; } } 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: Example/Example.php Here is a link to page foo .'; '', ''; event_signal( 'EVENT_EXAMPLE_FOO' ); $t_string = 'A sentence with the word "foo" in it.'; $t_new_string = event_signal( 'EVENT_EXAMPLE_BAR', array( $t_string ) ); echo $t_new_string, '
'; 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. __________________________________________________________ 4.2.4. Configuration 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": Example/Example.php '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: Example/pages/config_page.php Example/pages/config_update.php'; event_signal( 'EVENT_EXAMPLE_FOO' ); $t_string = 'A sentence with the word "foo" in it.'; $t_new_string = event_signal( 'EVENT_EXAMPLE_BAR', array( $t_string ) ); echo $t_new_string, '
'; __________________________________________________________ 4.4. API Usage This is a general overview of the plugin API. For more detailed analysis, you may reference the file core/plugin_api.php in the codebase. __________________________________________________________ Chapter 5. Event Reference 5.1. Introduction 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 type 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). EVENT_EXAMPLE (Default) This is an example event description. Parameters *